209 lines · 7.5 KB · Go
RawDownload
// Copyright 2023 The Gitea Authors. All rights reserved.// SPDX-License-Identifier: MIT package setting import (	"net/url"	"os"	"strings" 	"forgejo.org/modules/auth/password/hash"	"forgejo.org/modules/generate"	"forgejo.org/modules/keying"	"forgejo.org/modules/log") var (	// Security settings	InstallLock                        bool	SecretKey                          string	InternalToken                      string // internal access token	LogInRememberDays                  int	GlobalTwoFactorRequirement         TwoFactorRequirementType	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) // 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.inifunc 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	}	keying.Init([]byte(SecretKey)) 	GlobalTwoFactorRequirement = NewTwoFactorRequirementType(sec.Key("GLOBAL_TWO_FACTOR_REQUIREMENT").String()) 	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(""))	} 	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 be removed in Forgejo v13.0.0.")	}} type TwoFactorRequirementType string // llu:TrKeysSuffix admin.config.global_2fa_requirement.const (	NoneTwoFactorRequirement  TwoFactorRequirementType = "none"	AllTwoFactorRequirement   TwoFactorRequirementType = "all"	AdminTwoFactorRequirement TwoFactorRequirementType = "admin") func NewTwoFactorRequirementType(twoFactorRequirement string) TwoFactorRequirementType {	switch twoFactorRequirement {	case AllTwoFactorRequirement.String():		return AllTwoFactorRequirement	case AdminTwoFactorRequirement.String():		return AdminTwoFactorRequirement	default:		return NoneTwoFactorRequirement	}} func (r TwoFactorRequirementType) String() string {	return string(r)} func (r TwoFactorRequirementType) IsNone() bool {	return r == NoneTwoFactorRequirement} func (r TwoFactorRequirementType) IsAll() bool {	return r == AllTwoFactorRequirement} func (r TwoFactorRequirementType) IsAdmin() bool {	return r == AdminTwoFactorRequirement}