code.gitea.io/gitea@v1.19.3/modules/setting/security.go (about)

     1  // Copyright 2023 The Gitea Authors. All rights reserved.
     2  // SPDX-License-Identifier: MIT
     3  
     4  package setting
     5  
     6  import (
     7  	"net/url"
     8  	"os"
     9  	"strings"
    10  
    11  	"code.gitea.io/gitea/modules/auth/password/hash"
    12  	"code.gitea.io/gitea/modules/generate"
    13  	"code.gitea.io/gitea/modules/log"
    14  
    15  	ini "gopkg.in/ini.v1"
    16  )
    17  
    18  var (
    19  	// Security settings
    20  	InstallLock                        bool
    21  	SecretKey                          string
    22  	InternalToken                      string // internal access token
    23  	LogInRememberDays                  int
    24  	CookieUserName                     string
    25  	CookieRememberName                 string
    26  	ReverseProxyAuthUser               string
    27  	ReverseProxyAuthEmail              string
    28  	ReverseProxyAuthFullName           string
    29  	ReverseProxyLimit                  int
    30  	ReverseProxyTrustedProxies         []string
    31  	MinPasswordLength                  int
    32  	ImportLocalPaths                   bool
    33  	DisableGitHooks                    bool
    34  	DisableWebhooks                    bool
    35  	OnlyAllowPushIfGiteaEnvironmentSet bool
    36  	PasswordComplexity                 []string
    37  	PasswordHashAlgo                   string
    38  	PasswordCheckPwn                   bool
    39  	SuccessfulTokensCacheSize          int
    40  	CSRFCookieName                     = "_csrf"
    41  	CSRFCookieHTTPOnly                 = true
    42  )
    43  
    44  // loadSecret load the secret from ini by uriKey or verbatimKey, only one of them could be set
    45  // If the secret is loaded from uriKey (file), the file should be non-empty, to guarantee the behavior stable and clear.
    46  func loadSecret(sec *ini.Section, uriKey, verbatimKey string) string {
    47  	// don't allow setting both URI and verbatim string
    48  	uri := sec.Key(uriKey).String()
    49  	verbatim := sec.Key(verbatimKey).String()
    50  	if uri != "" && verbatim != "" {
    51  		log.Fatal("Cannot specify both %s and %s", uriKey, verbatimKey)
    52  	}
    53  
    54  	// if we have no URI, use verbatim
    55  	if uri == "" {
    56  		return verbatim
    57  	}
    58  
    59  	tempURI, err := url.Parse(uri)
    60  	if err != nil {
    61  		log.Fatal("Failed to parse %s (%s): %v", uriKey, uri, err)
    62  	}
    63  	switch tempURI.Scheme {
    64  	case "file":
    65  		buf, err := os.ReadFile(tempURI.RequestURI())
    66  		if err != nil {
    67  			log.Fatal("Failed to read %s (%s): %v", uriKey, tempURI.RequestURI(), err)
    68  		}
    69  		val := strings.TrimSpace(string(buf))
    70  		if val == "" {
    71  			// The file shouldn't be empty, otherwise we can not know whether the user has ever set the KEY or KEY_URI
    72  			// For example: if INTERNAL_TOKEN_URI=file:///empty-file,
    73  			// Then if the token is re-generated during installation and saved to INTERNAL_TOKEN
    74  			// Then INTERNAL_TOKEN and INTERNAL_TOKEN_URI both exist, that's a fatal error (they shouldn't)
    75  			log.Fatal("Failed to read %s (%s): the file is empty", uriKey, tempURI.RequestURI())
    76  		}
    77  		return val
    78  
    79  	// only file URIs are allowed
    80  	default:
    81  		log.Fatal("Unsupported URI-Scheme %q (INTERNAL_TOKEN_URI = %q)", tempURI.Scheme, uri)
    82  		return ""
    83  	}
    84  }
    85  
    86  // generateSaveInternalToken generates and saves the internal token to app.ini
    87  func generateSaveInternalToken() {
    88  	token, err := generate.NewInternalToken()
    89  	if err != nil {
    90  		log.Fatal("Error generate internal token: %v", err)
    91  	}
    92  
    93  	InternalToken = token
    94  	CreateOrAppendToCustomConf("security.INTERNAL_TOKEN", func(cfg *ini.File) {
    95  		cfg.Section("security").Key("INTERNAL_TOKEN").SetValue(token)
    96  	})
    97  }
    98  
    99  func loadSecurityFrom(rootCfg ConfigProvider) {
   100  	sec := rootCfg.Section("security")
   101  	InstallLock = sec.Key("INSTALL_LOCK").MustBool(false)
   102  	LogInRememberDays = sec.Key("LOGIN_REMEMBER_DAYS").MustInt(7)
   103  	CookieUserName = sec.Key("COOKIE_USERNAME").MustString("gitea_awesome")
   104  	SecretKey = loadSecret(sec, "SECRET_KEY_URI", "SECRET_KEY")
   105  	if SecretKey == "" {
   106  		// FIXME: https://github.com/go-gitea/gitea/issues/16832
   107  		// Until it supports rotating an existing secret key, we shouldn't move users off of the widely used default value
   108  		SecretKey = "!#@FDEWREWR&*(" //nolint:gosec
   109  	}
   110  
   111  	CookieRememberName = sec.Key("COOKIE_REMEMBER_NAME").MustString("gitea_incredible")
   112  
   113  	ReverseProxyAuthUser = sec.Key("REVERSE_PROXY_AUTHENTICATION_USER").MustString("X-WEBAUTH-USER")
   114  	ReverseProxyAuthEmail = sec.Key("REVERSE_PROXY_AUTHENTICATION_EMAIL").MustString("X-WEBAUTH-EMAIL")
   115  	ReverseProxyAuthFullName = sec.Key("REVERSE_PROXY_AUTHENTICATION_FULL_NAME").MustString("X-WEBAUTH-FULLNAME")
   116  
   117  	ReverseProxyLimit = sec.Key("REVERSE_PROXY_LIMIT").MustInt(1)
   118  	ReverseProxyTrustedProxies = sec.Key("REVERSE_PROXY_TRUSTED_PROXIES").Strings(",")
   119  	if len(ReverseProxyTrustedProxies) == 0 {
   120  		ReverseProxyTrustedProxies = []string{"127.0.0.0/8", "::1/128"}
   121  	}
   122  
   123  	MinPasswordLength = sec.Key("MIN_PASSWORD_LENGTH").MustInt(6)
   124  	ImportLocalPaths = sec.Key("IMPORT_LOCAL_PATHS").MustBool(false)
   125  	DisableGitHooks = sec.Key("DISABLE_GIT_HOOKS").MustBool(true)
   126  	DisableWebhooks = sec.Key("DISABLE_WEBHOOKS").MustBool(false)
   127  	OnlyAllowPushIfGiteaEnvironmentSet = sec.Key("ONLY_ALLOW_PUSH_IF_GITEA_ENVIRONMENT_SET").MustBool(true)
   128  
   129  	// Ensure that the provided default hash algorithm is a valid hash algorithm
   130  	var algorithm *hash.PasswordHashAlgorithm
   131  	PasswordHashAlgo, algorithm = hash.SetDefaultPasswordHashAlgorithm(sec.Key("PASSWORD_HASH_ALGO").MustString(""))
   132  	if algorithm == nil {
   133  		log.Fatal("The provided password hash algorithm was invalid: %s", sec.Key("PASSWORD_HASH_ALGO").MustString(""))
   134  	}
   135  
   136  	CSRFCookieHTTPOnly = sec.Key("CSRF_COOKIE_HTTP_ONLY").MustBool(true)
   137  	PasswordCheckPwn = sec.Key("PASSWORD_CHECK_PWN").MustBool(false)
   138  	SuccessfulTokensCacheSize = sec.Key("SUCCESSFUL_TOKENS_CACHE_SIZE").MustInt(20)
   139  
   140  	InternalToken = loadSecret(sec, "INTERNAL_TOKEN_URI", "INTERNAL_TOKEN")
   141  	if InstallLock && InternalToken == "" {
   142  		// if Gitea has been installed but the InternalToken hasn't been generated (upgrade from an old release), we should generate
   143  		// some users do cluster deployment, they still depend on this auto-generating behavior.
   144  		generateSaveInternalToken()
   145  	}
   146  
   147  	cfgdata := sec.Key("PASSWORD_COMPLEXITY").Strings(",")
   148  	if len(cfgdata) == 0 {
   149  		cfgdata = []string{"off"}
   150  	}
   151  	PasswordComplexity = make([]string, 0, len(cfgdata))
   152  	for _, name := range cfgdata {
   153  		name := strings.ToLower(strings.Trim(name, `"`))
   154  		if name != "" {
   155  			PasswordComplexity = append(PasswordComplexity, name)
   156  		}
   157  	}
   158  }