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 }