mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-29 10:57:44 +09:00 
			
		
		
		
	Allow specifying SECRET_KEY_URI, similar to INTERNAL_TOKEN_URI (#19663)
Only load SECRET_KEY and INTERNAL_TOKEN if they exist. Never write the config file if the keys do not exist, which was only a fallback for Gitea upgraded from < 1.5 Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
		| @@ -203,7 +203,7 @@ func setPort(port string) error { | |||||||
| 		defaultLocalURL += ":" + setting.HTTPPort + "/" | 		defaultLocalURL += ":" + setting.HTTPPort + "/" | ||||||
|  |  | ||||||
| 		// Save LOCAL_ROOT_URL if port changed | 		// Save LOCAL_ROOT_URL if port changed | ||||||
| 		setting.CreateOrAppendToCustomConf(func(cfg *ini.File) { | 		setting.CreateOrAppendToCustomConf("server.LOCAL_ROOT_URL", func(cfg *ini.File) { | ||||||
| 			cfg.Section("server").Key("LOCAL_ROOT_URL").SetValue(defaultLocalURL) | 			cfg.Section("server").Key("LOCAL_ROOT_URL").SetValue(defaultLocalURL) | ||||||
| 		}) | 		}) | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -379,14 +379,19 @@ LOG_SQL = false ; if unset defaults to true | |||||||
| ;; Whether the installer is disabled (set to true to disable the installer) | ;; Whether the installer is disabled (set to true to disable the installer) | ||||||
| INSTALL_LOCK = false | INSTALL_LOCK = false | ||||||
| ;; | ;; | ||||||
| ;; Global secret key that will be used - if blank will be regenerated. | ;; Global secret key that will be used | ||||||
|  | ;; This key is VERY IMPORTANT. If you lose it, the data encrypted by it (like 2FA secret) can't be decrypted anymore. | ||||||
| SECRET_KEY = | SECRET_KEY = | ||||||
| ;; | ;; | ||||||
|  | ;; Alternative location to specify secret key, instead of this file; you cannot specify both this and SECRET_KEY, and must pick one | ||||||
|  | ;; This key is VERY IMPORTANT. If you lose it, the data encrypted by it (like 2FA secret) can't be decrypted anymore. | ||||||
|  | ;SECRET_KEY_URI = file:/etc/gitea/secret_key | ||||||
|  | ;; | ||||||
| ;; Secret used to validate communication within Gitea binary. | ;; Secret used to validate communication within Gitea binary. | ||||||
| INTERNAL_TOKEN= | INTERNAL_TOKEN= | ||||||
| ;; | ;; | ||||||
| ;; Instead of defining internal token in the configuration, this configuration option can be used to give Gitea a path to a file that contains the internal token (example value: file:/etc/gitea/internal_token) | ;; Alternative location to specify internal token, instead of this file; you cannot specify both this and INTERNAL_TOKEN, and must pick one | ||||||
| ;INTERNAL_TOKEN_URI = ;e.g. /etc/gitea/internal_token | ;INTERNAL_TOKEN_URI = file:/etc/gitea/internal_token | ||||||
| ;; | ;; | ||||||
| ;; How long to remember that a user is logged in before requiring relogin (in days) | ;; How long to remember that a user is logged in before requiring relogin (in days) | ||||||
| ;LOGIN_REMEMBER_DAYS = 7 | ;LOGIN_REMEMBER_DAYS = 7 | ||||||
|   | |||||||
| @@ -494,7 +494,8 @@ Certain queues have defaults that override the defaults set in `[queue]` (this o | |||||||
| ## Security (`security`) | ## Security (`security`) | ||||||
|  |  | ||||||
| - `INSTALL_LOCK`: **false**: Controls access to the installation page. When set to "true", the installation page is not accessible. | - `INSTALL_LOCK`: **false**: Controls access to the installation page. When set to "true", the installation page is not accessible. | ||||||
| - `SECRET_KEY`: **\<random at every install\>**: Global secret key. This should be changed. | - `SECRET_KEY`: **\<random at every install\>**: Global secret key. This key is VERY IMPORTANT, if you lost it, the data encrypted by it (like 2FA secret) can't be decrypted anymore. | ||||||
|  | - `SECRET_KEY_URI`: **<empty>**: Instead of defining SECRET_KEY, this option can be used to use the key stored in a file (example value: `file:/etc/gitea/secret_key`). It shouldn't be lost like SECRET_KEY. | ||||||
| - `LOGIN_REMEMBER_DAYS`: **7**: Cookie lifetime, in days. | - `LOGIN_REMEMBER_DAYS`: **7**: Cookie lifetime, in days. | ||||||
| - `COOKIE_USERNAME`: **gitea\_awesome**: Name of the cookie used to store the current username. | - `COOKIE_USERNAME`: **gitea\_awesome**: Name of the cookie used to store the current username. | ||||||
| - `COOKIE_REMEMBER_NAME`: **gitea\_incredible**: Name of cookie used to store authentication | - `COOKIE_REMEMBER_NAME`: **gitea\_incredible**: Name of cookie used to store authentication | ||||||
| @@ -520,7 +521,7 @@ Certain queues have defaults that override the defaults set in `[queue]` (this o | |||||||
| - `ONLY_ALLOW_PUSH_IF_GITEA_ENVIRONMENT_SET`: **true**: Set to `false` to allow local users to push to gitea-repositories without setting up the Gitea environment. This is not recommended and if you want local users to push to Gitea repositories you should set the environment appropriately. | - `ONLY_ALLOW_PUSH_IF_GITEA_ENVIRONMENT_SET`: **true**: Set to `false` to allow local users to push to gitea-repositories without setting up the Gitea environment. This is not recommended and if you want local users to push to Gitea repositories you should set the environment appropriately. | ||||||
| - `IMPORT_LOCAL_PATHS`: **false**: Set to `false` to prevent all users (including admin) from importing local path on server. | - `IMPORT_LOCAL_PATHS`: **false**: Set to `false` to prevent all users (including admin) from importing local path on server. | ||||||
| - `INTERNAL_TOKEN`: **\<random at every install if no uri set\>**: Secret used to validate communication within Gitea binary. | - `INTERNAL_TOKEN`: **\<random at every install if no uri set\>**: Secret used to validate communication within Gitea binary. | ||||||
| - `INTERNAL_TOKEN_URI`: **<empty>**: Instead of defining internal token in the configuration, this configuration option can be used to give Gitea a path to a file that contains the internal token (example value: `file:/etc/gitea/internal_token`) | - `INTERNAL_TOKEN_URI`: **<empty>**: Instead of defining INTERNAL_TOKEN in the configuration, this configuration option can be used to give Gitea a path to a file that contains the internal token (example value: `file:/etc/gitea/internal_token`) | ||||||
| - `PASSWORD_HASH_ALGO`: **pbkdf2**: The hash algorithm to use \[argon2, pbkdf2, scrypt, bcrypt\], argon2 will spend more memory than others. | - `PASSWORD_HASH_ALGO`: **pbkdf2**: The hash algorithm to use \[argon2, pbkdf2, scrypt, bcrypt\], argon2 will spend more memory than others. | ||||||
| - `CSRF_COOKIE_HTTP_ONLY`: **true**: Set false to allow JavaScript to read CSRF cookie. | - `CSRF_COOKIE_HTTP_ONLY`: **true**: Set false to allow JavaScript to read CSRF cookie. | ||||||
| - `MIN_PASSWORD_LENGTH`: **6**: Minimum password length for new users. | - `MIN_PASSWORD_LENGTH`: **6**: Minimum password length for new users. | ||||||
|   | |||||||
| @@ -62,7 +62,7 @@ func newLFSService() { | |||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			// Save secret | 			// Save secret | ||||||
| 			CreateOrAppendToCustomConf(func(cfg *ini.File) { | 			CreateOrAppendToCustomConf("server.LFS_JWT_SECRET", func(cfg *ini.File) { | ||||||
| 				cfg.Section("server").Key("LFS_JWT_SECRET").SetValue(LFS.JWTSecretBase64) | 				cfg.Section("server").Key("LFS_JWT_SECRET").SetValue(LFS.JWTSecretBase64) | ||||||
| 			}) | 			}) | ||||||
| 		} | 		} | ||||||
|   | |||||||
| @@ -21,7 +21,6 @@ import ( | |||||||
| 	"text/template" | 	"text/template" | ||||||
| 	"time" | 	"time" | ||||||
|  |  | ||||||
| 	"code.gitea.io/gitea/modules/generate" |  | ||||||
| 	"code.gitea.io/gitea/modules/json" | 	"code.gitea.io/gitea/modules/json" | ||||||
| 	"code.gitea.io/gitea/modules/log" | 	"code.gitea.io/gitea/modules/log" | ||||||
| 	"code.gitea.io/gitea/modules/user" | 	"code.gitea.io/gitea/modules/user" | ||||||
| @@ -923,9 +922,15 @@ func loadFromConf(allowEmpty bool, extraConfig string) { | |||||||
|  |  | ||||||
| 	sec = Cfg.Section("security") | 	sec = Cfg.Section("security") | ||||||
| 	InstallLock = sec.Key("INSTALL_LOCK").MustBool(false) | 	InstallLock = sec.Key("INSTALL_LOCK").MustBool(false) | ||||||
| 	SecretKey = sec.Key("SECRET_KEY").MustString("!#@FDEWREWR&*(") |  | ||||||
| 	LogInRememberDays = sec.Key("LOGIN_REMEMBER_DAYS").MustInt(7) | 	LogInRememberDays = sec.Key("LOGIN_REMEMBER_DAYS").MustInt(7) | ||||||
| 	CookieUserName = sec.Key("COOKIE_USERNAME").MustString("gitea_awesome") | 	CookieUserName = sec.Key("COOKIE_USERNAME").MustString("gitea_awesome") | ||||||
|  | 	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") | 	CookieRememberName = sec.Key("COOKIE_REMEMBER_NAME").MustString("gitea_incredible") | ||||||
|  |  | ||||||
| 	ReverseProxyAuthUser = sec.Key("REVERSE_PROXY_AUTHENTICATION_USER").MustString("X-WEBAUTH-USER") | 	ReverseProxyAuthUser = sec.Key("REVERSE_PROXY_AUTHENTICATION_USER").MustString("X-WEBAUTH-USER") | ||||||
| @@ -948,11 +953,7 @@ func loadFromConf(allowEmpty bool, extraConfig string) { | |||||||
| 	PasswordCheckPwn = sec.Key("PASSWORD_CHECK_PWN").MustBool(false) | 	PasswordCheckPwn = sec.Key("PASSWORD_CHECK_PWN").MustBool(false) | ||||||
| 	SuccessfulTokensCacheSize = sec.Key("SUCCESSFUL_TOKENS_CACHE_SIZE").MustInt(20) | 	SuccessfulTokensCacheSize = sec.Key("SUCCESSFUL_TOKENS_CACHE_SIZE").MustInt(20) | ||||||
|  |  | ||||||
| 	InternalToken = loadInternalToken(sec) | 	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 |  | ||||||
| 		generateSaveInternalToken() |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	cfgdata := sec.Key("PASSWORD_COMPLEXITY").Strings(",") | 	cfgdata := sec.Key("PASSWORD_COMPLEXITY").Strings(",") | ||||||
| 	if len(cfgdata) == 0 { | 	if len(cfgdata) == 0 { | ||||||
| @@ -1141,51 +1142,36 @@ func parseAuthorizedPrincipalsAllow(values []string) ([]string, bool) { | |||||||
| 	return authorizedPrincipalsAllow, true | 	return authorizedPrincipalsAllow, true | ||||||
| } | } | ||||||
|  |  | ||||||
| func loadInternalToken(sec *ini.Section) string { | func loadSecret(sec *ini.Section, uriKey, verbatimKey string) string { | ||||||
| 	uri := sec.Key("INTERNAL_TOKEN_URI").String() | 	// don't allow setting both URI and verbatim string | ||||||
| 	if uri == "" { | 	uri := sec.Key(uriKey).String() | ||||||
| 		return sec.Key("INTERNAL_TOKEN").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) | 	tempURI, err := url.Parse(uri) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		log.Fatal("Failed to parse INTERNAL_TOKEN_URI (%s): %v", uri, err) | 		log.Fatal("Failed to parse %s (%s): %v", uriKey, uri, err) | ||||||
| 	} | 	} | ||||||
| 	switch tempURI.Scheme { | 	switch tempURI.Scheme { | ||||||
| 	case "file": | 	case "file": | ||||||
| 		buf, err := os.ReadFile(tempURI.RequestURI()) | 		buf, err := os.ReadFile(tempURI.RequestURI()) | ||||||
| 		if err != nil && !os.IsNotExist(err) { | 		if err != nil { | ||||||
| 			log.Fatal("Failed to open InternalTokenURI (%s): %v", uri, err) | 			log.Fatal("Failed to read %s (%s): %v", uriKey, tempURI.RequestURI(), err) | ||||||
| 		} |  | ||||||
| 		// No token in the file, generate one and store it. |  | ||||||
| 		if len(buf) == 0 { |  | ||||||
| 			token, err := generate.NewInternalToken() |  | ||||||
| 			if err != nil { |  | ||||||
| 				log.Fatal("Error generate internal token: %v", err) |  | ||||||
| 			} |  | ||||||
| 			err = os.WriteFile(tempURI.RequestURI(), []byte(token), 0o600) |  | ||||||
| 			if err != nil { |  | ||||||
| 				log.Fatal("Error writing to InternalTokenURI (%s): %v", uri, err) |  | ||||||
| 			} |  | ||||||
| 			return token |  | ||||||
| 		} | 		} | ||||||
| 		return strings.TrimSpace(string(buf)) | 		return strings.TrimSpace(string(buf)) | ||||||
|  |  | ||||||
|  | 	// only file URIs are allowed | ||||||
| 	default: | 	default: | ||||||
| 		log.Fatal("Unsupported URI-Scheme %q (INTERNAL_TOKEN_URI = %q)", tempURI.Scheme, uri) | 		log.Fatal("Unsupported URI-Scheme %q (INTERNAL_TOKEN_URI = %q)", tempURI.Scheme, uri) | ||||||
|  | 		return "" | ||||||
| 	} | 	} | ||||||
| 	return "" |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // generateSaveInternalToken generates and saves the internal token to app.ini |  | ||||||
| func generateSaveInternalToken() { |  | ||||||
| 	token, err := generate.NewInternalToken() |  | ||||||
| 	if err != nil { |  | ||||||
| 		log.Fatal("Error generate internal token: %v", err) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	InternalToken = token |  | ||||||
| 	CreateOrAppendToCustomConf(func(cfg *ini.File) { |  | ||||||
| 		cfg.Section("security").Key("INTERNAL_TOKEN").SetValue(token) |  | ||||||
| 	}) |  | ||||||
| } | } | ||||||
|  |  | ||||||
| // MakeAbsoluteAssetURL returns the absolute asset url prefix without a trailing slash | // MakeAbsoluteAssetURL returns the absolute asset url prefix without a trailing slash | ||||||
| @@ -1249,7 +1235,12 @@ func MakeManifestData(appName, appURL, absoluteAssetURL string) []byte { | |||||||
|  |  | ||||||
| // CreateOrAppendToCustomConf creates or updates the custom config. | // CreateOrAppendToCustomConf creates or updates the custom config. | ||||||
| // Use the callback to set individual values. | // Use the callback to set individual values. | ||||||
| func CreateOrAppendToCustomConf(callback func(cfg *ini.File)) { | func CreateOrAppendToCustomConf(purpose string, callback func(cfg *ini.File)) { | ||||||
|  | 	if CustomConf == "" { | ||||||
|  | 		log.Error("Custom config path must not be empty") | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	cfg := ini.Empty() | 	cfg := ini.Empty() | ||||||
| 	isFile, err := util.IsFile(CustomConf) | 	isFile, err := util.IsFile(CustomConf) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| @@ -1264,8 +1255,6 @@ func CreateOrAppendToCustomConf(callback func(cfg *ini.File)) { | |||||||
|  |  | ||||||
| 	callback(cfg) | 	callback(cfg) | ||||||
|  |  | ||||||
| 	log.Info("Settings saved to: %q", CustomConf) |  | ||||||
|  |  | ||||||
| 	if err := os.MkdirAll(filepath.Dir(CustomConf), os.ModePerm); err != nil { | 	if err := os.MkdirAll(filepath.Dir(CustomConf), os.ModePerm); err != nil { | ||||||
| 		log.Fatal("failed to create '%s': %v", CustomConf, err) | 		log.Fatal("failed to create '%s': %v", CustomConf, err) | ||||||
| 		return | 		return | ||||||
| @@ -1273,6 +1262,7 @@ func CreateOrAppendToCustomConf(callback func(cfg *ini.File)) { | |||||||
| 	if err := cfg.SaveTo(CustomConf); err != nil { | 	if err := cfg.SaveTo(CustomConf); err != nil { | ||||||
| 		log.Fatal("error saving to custom config: %v", err) | 		log.Fatal("error saving to custom config: %v", err) | ||||||
| 	} | 	} | ||||||
|  | 	log.Info("Settings for %s saved to: %q", purpose, CustomConf) | ||||||
|  |  | ||||||
| 	// Change permissions to be more restrictive | 	// Change permissions to be more restrictive | ||||||
| 	fi, err := os.Stat(CustomConf) | 	fi, err := os.Stat(CustomConf) | ||||||
|   | |||||||
| @@ -24,6 +24,11 @@ func CheckInternalToken(next http.Handler) http.Handler { | |||||||
| 	return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { | 	return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { | ||||||
| 		tokens := req.Header.Get("Authorization") | 		tokens := req.Header.Get("Authorization") | ||||||
| 		fields := strings.SplitN(tokens, " ", 2) | 		fields := strings.SplitN(tokens, " ", 2) | ||||||
|  | 		if setting.InternalToken == "" { | ||||||
|  | 			log.Warn(`The INTERNAL_TOKEN setting is missing from the configuration file: %q, internal API can't work.`, setting.CustomConf) | ||||||
|  | 			http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
| 		if len(fields) != 2 || fields[0] != "Bearer" || fields[1] != setting.InternalToken { | 		if len(fields) != 2 || fields[0] != "Bearer" || fields[1] != setting.InternalToken { | ||||||
| 			log.Debug("Forbidden attempt to access internal url: Authorization header: %s", tokens) | 			log.Debug("Forbidden attempt to access internal url: Authorization header: %s", tokens) | ||||||
| 			http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden) | 			http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden) | ||||||
|   | |||||||
| @@ -364,7 +364,7 @@ func loadOrCreateSymmetricKey() (interface{}, error) { | |||||||
| 			return nil, err | 			return nil, err | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		setting.CreateOrAppendToCustomConf(func(cfg *ini.File) { | 		setting.CreateOrAppendToCustomConf("oauth2.JWT_SECRET", func(cfg *ini.File) { | ||||||
| 			secretBase64 := base64.RawURLEncoding.EncodeToString(key) | 			secretBase64 := base64.RawURLEncoding.EncodeToString(key) | ||||||
| 			cfg.Section("oauth2").Key("JWT_SECRET").SetValue(secretBase64) | 			cfg.Section("oauth2").Key("JWT_SECRET").SetValue(secretBase64) | ||||||
| 		}) | 		}) | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user