diff options
Diffstat (limited to 'modules/setting/security.go')
-rw-r--r-- | modules/setting/security.go | 171 |
1 files changed, 171 insertions, 0 deletions
diff --git a/modules/setting/security.go b/modules/setting/security.go new file mode 100644 index 00000000..3d7b1f9c --- /dev/null +++ b/modules/setting/security.go @@ -0,0 +1,171 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package setting + +import ( + "net/url" + "os" + "strings" + + "code.gitea.io/gitea/modules/auth/password/hash" + "code.gitea.io/gitea/modules/generate" + "code.gitea.io/gitea/modules/log" +) + +var ( + // Security settings + InstallLock bool + SecretKey string + InternalToken string // internal access token + LogInRememberDays int + CookieRememberName string + ReverseProxyAuthUser string + ReverseProxyAuthEmail string + ReverseProxyAuthFullName string + ReverseProxyLimit int + ReverseProxyTrustedProxies []string + MinPasswordLength int + ImportLocalPaths bool + DisableGitHooks bool + DisableWebhooks bool + OnlyAllowPushIfGiteaEnvironmentSet bool + PasswordComplexity []string + PasswordHashAlgo string + PasswordCheckPwn bool + SuccessfulTokensCacheSize int + DisableQueryAuthToken bool + CSRFCookieName = "_csrf" + CSRFCookieHTTPOnly = true +) + +// loadSecret load the secret from ini by uriKey or verbatimKey, only one of them could be set +// If the secret is loaded from uriKey (file), the file should be non-empty, to guarantee the behavior stable and clear. +func loadSecret(sec ConfigSection, uriKey, verbatimKey string) string { + // don't allow setting both URI and verbatim string + uri := sec.Key(uriKey).String() + verbatim := sec.Key(verbatimKey).String() + if uri != "" && verbatim != "" { + log.Fatal("Cannot specify both %s and %s", uriKey, verbatimKey) + } + + // if we have no URI, use verbatim + if uri == "" { + return verbatim + } + + tempURI, err := url.Parse(uri) + if err != nil { + log.Fatal("Failed to parse %s (%s): %v", uriKey, uri, err) + } + switch tempURI.Scheme { + case "file": + buf, err := os.ReadFile(tempURI.RequestURI()) + if err != nil { + log.Fatal("Failed to read %s (%s): %v", uriKey, tempURI.RequestURI(), err) + } + val := strings.TrimSpace(string(buf)) + if val == "" { + // The file shouldn't be empty, otherwise we can not know whether the user has ever set the KEY or KEY_URI + // For example: if INTERNAL_TOKEN_URI=file:///empty-file, + // Then if the token is re-generated during installation and saved to INTERNAL_TOKEN + // Then INTERNAL_TOKEN and INTERNAL_TOKEN_URI both exist, that's a fatal error (they shouldn't) + log.Fatal("Failed to read %s (%s): the file is empty", uriKey, tempURI.RequestURI()) + } + return val + + // only file URIs are allowed + default: + log.Fatal("Unsupported URI-Scheme %q (%q = %q)", tempURI.Scheme, uriKey, uri) + return "" + } +} + +// generateSaveInternalToken generates and saves the internal token to app.ini +func generateSaveInternalToken(rootCfg ConfigProvider) { + token, err := generate.NewInternalToken() + if err != nil { + log.Fatal("Error generate internal token: %v", err) + } + + InternalToken = token + saveCfg, err := rootCfg.PrepareSaving() + if err != nil { + log.Fatal("Error saving internal token: %v", err) + } + rootCfg.Section("security").Key("INTERNAL_TOKEN").SetValue(token) + saveCfg.Section("security").Key("INTERNAL_TOKEN").SetValue(token) + if err = saveCfg.Save(); err != nil { + log.Fatal("Error saving internal token: %v", err) + } +} + +func loadSecurityFrom(rootCfg ConfigProvider) { + sec := rootCfg.Section("security") + InstallLock = HasInstallLock(rootCfg) + LogInRememberDays = sec.Key("LOGIN_REMEMBER_DAYS").MustInt(31) + SecretKey = loadSecret(sec, "SECRET_KEY_URI", "SECRET_KEY") + if SecretKey == "" { + // FIXME: https://github.com/go-gitea/gitea/issues/16832 + // Until it supports rotating an existing secret key, we shouldn't move users off of the widely used default value + SecretKey = "!#@FDEWREWR&*(" //nolint:gosec + } + + CookieRememberName = sec.Key("COOKIE_REMEMBER_NAME").MustString("gitea_incredible") + + ReverseProxyAuthUser = sec.Key("REVERSE_PROXY_AUTHENTICATION_USER").MustString("X-WEBAUTH-USER") + ReverseProxyAuthEmail = sec.Key("REVERSE_PROXY_AUTHENTICATION_EMAIL").MustString("X-WEBAUTH-EMAIL") + ReverseProxyAuthFullName = sec.Key("REVERSE_PROXY_AUTHENTICATION_FULL_NAME").MustString("X-WEBAUTH-FULLNAME") + + ReverseProxyLimit = sec.Key("REVERSE_PROXY_LIMIT").MustInt(1) + ReverseProxyTrustedProxies = sec.Key("REVERSE_PROXY_TRUSTED_PROXIES").Strings(",") + if len(ReverseProxyTrustedProxies) == 0 { + ReverseProxyTrustedProxies = []string{"127.0.0.0/8", "::1/128"} + } + + MinPasswordLength = sec.Key("MIN_PASSWORD_LENGTH").MustInt(8) + ImportLocalPaths = sec.Key("IMPORT_LOCAL_PATHS").MustBool(false) + DisableGitHooks = sec.Key("DISABLE_GIT_HOOKS").MustBool(true) + DisableWebhooks = sec.Key("DISABLE_WEBHOOKS").MustBool(false) + OnlyAllowPushIfGiteaEnvironmentSet = sec.Key("ONLY_ALLOW_PUSH_IF_GITEA_ENVIRONMENT_SET").MustBool(true) + + // Ensure that the provided default hash algorithm is a valid hash algorithm + var algorithm *hash.PasswordHashAlgorithm + PasswordHashAlgo, algorithm = hash.SetDefaultPasswordHashAlgorithm(sec.Key("PASSWORD_HASH_ALGO").MustString("")) + if algorithm == nil { + log.Fatal("The provided password hash algorithm was invalid: %s", sec.Key("PASSWORD_HASH_ALGO").MustString("")) + } + + CSRFCookieHTTPOnly = sec.Key("CSRF_COOKIE_HTTP_ONLY").MustBool(true) + PasswordCheckPwn = sec.Key("PASSWORD_CHECK_PWN").MustBool(false) + SuccessfulTokensCacheSize = sec.Key("SUCCESSFUL_TOKENS_CACHE_SIZE").MustInt(20) + + InternalToken = loadSecret(sec, "INTERNAL_TOKEN_URI", "INTERNAL_TOKEN") + if InstallLock && InternalToken == "" { + // if Gitea has been installed but the InternalToken hasn't been generated (upgrade from an old release), we should generate + // some users do cluster deployment, they still depend on this auto-generating behavior. + generateSaveInternalToken(rootCfg) + } + + cfgdata := sec.Key("PASSWORD_COMPLEXITY").Strings(",") + if len(cfgdata) == 0 { + cfgdata = []string{"off"} + } + PasswordComplexity = make([]string, 0, len(cfgdata)) + for _, name := range cfgdata { + name := strings.ToLower(strings.Trim(name, `"`)) + if name != "" { + PasswordComplexity = append(PasswordComplexity, name) + } + } + + sectionHasDisableQueryAuthToken := sec.HasKey("DISABLE_QUERY_AUTH_TOKEN") + + // TODO: default value should be true in future releases + DisableQueryAuthToken = sec.Key("DISABLE_QUERY_AUTH_TOKEN").MustBool(false) + + // warn if the setting is set to false explicitly + if sectionHasDisableQueryAuthToken && !DisableQueryAuthToken { + log.Warn("Enabling Query API Auth tokens is not recommended. DISABLE_QUERY_AUTH_TOKEN will default to true in gitea 1.23 and will be removed in gitea 1.24.") + } +} |