/
uzer_007
/
Rixea
Обзор
Документация
Войти
/
uzer_007
/
Rixea
Код
Запросы
0
Задачи
Вики
Пакеты
5
Релизы
2
CI/CD
Аналитика
Безопасность
master
modules/setting/security.go
227 строк
8 KB
uzer_007
feat: первая версия Rixea
02 авг 2026, 22:16
02 авг 2026, 22:16
d4f7504
Код
Авторство
О чём код?
// Copyright 2026 Uzer_007. All rights reserved. // SPDX-License-Identifier: MIT package setting import ( "net/url" "os" "strings" "gitverse.ru/uzer_007/Rixea/modules/auth/password/hash" "gitverse.ru/uzer_007/Rixea/modules/cryptoprofile" "gitverse.ru/uzer_007/Rixea/modules/generate" "gitverse.ru/uzer_007/Rixea/modules/log" ) // Security settings var Security = struct { // TODO: move more settings to this struct in future XFrameOptions string XContentTypeOptions string ContentSecurityPolicyGeneral string // it only supports empty (default policy) or "unset", maybe it can support more in the future AllowedHostList string }{ XFrameOptions: "SAMEORIGIN", XContentTypeOptions: "nosniff", AllowedHostList: "external", } var ( InstallLock bool SecretKey string InternalToken string // internal access token LogInRememberDays int CookieRememberName string ReverseProxyAuthUser string ReverseProxyAuthEmail string ReverseProxyAuthFullName string ReverseProxyLimit int ReverseProxyLogoutRedirect string ReverseProxyTrustedProxies []string MinPasswordLength int ImportLocalPaths bool DisableGitHooks = true DisableWebhooks bool OnlyAllowPushIfGiteaEnvironmentSet bool PasswordComplexity []string CryptoProfile cryptoprofile.Profile PasswordHashAlgo string PasswordCheckPwn bool SuccessfulTokensCacheSize int DisableQueryAuthToken = true RecordUserSignupMetadata = false TwoFactorAuthEnforced = false ) // 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") LogInRememberDays = sec.Key("LOGIN_REMEMBER_DAYS").MustInt(31) SecretKey = loadSecret(sec, "SECRET_KEY_URI", "SECRET_KEY") if InstallLock && SecretKey == "" { log.Fatal("[security].SECRET_KEY is required for an installed Rixea instance") } profileValue := sec.Key("CRYPTO_PROFILE").String() CryptoProfile = cryptoprofile.ProfileUnset if profileValue != "" { profile, err := cryptoprofile.Parse(profileValue) if err != nil { log.Fatal("Invalid [security].CRYPTO_PROFILE: %v", err) } CryptoProfile = profile } else if InstallLock { log.Fatal("[security].CRYPTO_PROFILE must be explicitly set to gost or standard") } CookieRememberName = sec.Key("COOKIE_REMEMBER_NAME").MustString("rixea_remember") 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) ReverseProxyLogoutRedirect = sec.Key("REVERSE_PROXY_LOGOUT_REDIRECT").String() 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_RIXEA_ENVIRONMENT_SET").MustBool(true) // Ensure that the provided default hash algorithm is a valid hash algorithm var algorithm *hash.PasswordHashAlgorithm passwordAlgorithm := sec.Key("PASSWORD_HASH_ALGO").String() if passwordAlgorithm == "" { if CryptoProfile.IsValid() { passwordAlgorithm = CryptoProfile.DefaultPasswordAlgorithm() } else { // The installer has not selected a profile yet. This value is not persisted. passwordAlgorithm = cryptoprofile.ProfileStandard.DefaultPasswordAlgorithm() } } PasswordHashAlgo, algorithm = hash.SetDefaultPasswordHashAlgorithm(passwordAlgorithm) if algorithm == nil { log.Fatal("The provided password hash algorithm was invalid: %s", passwordAlgorithm) } PasswordCheckPwn = sec.Key("PASSWORD_CHECK_PWN").MustBool(false) SuccessfulTokensCacheSize = sec.Key("SUCCESSFUL_TOKENS_CACHE_SIZE").MustInt(20) deprecatedSetting(rootCfg, "cors", "X_FRAME_OPTIONS", "security", "X_FRAME_OPTIONS", "v1.26.0") if !sec.HasKey("X_FRAME_OPTIONS") { Security.XFrameOptions = rootCfg.Section("cors").Key("X_FRAME_OPTIONS").MustString(Security.XFrameOptions) } Security.AllowedHostList = "external" if err := sec.MapTo(&Security); err != nil { log.Fatal("Failed to map security settings: %v", err) } Security.AllowedHostList = strings.TrimSpace(Security.AllowedHostList) if Security.AllowedHostList == "" { Security.AllowedHostList = "external" } twoFactorAuth := sec.Key("TWO_FACTOR_AUTH").String() switch twoFactorAuth { case "": case "enforced": TwoFactorAuthEnforced = true default: log.Fatal("Invalid two-factor auth option: %s", twoFactorAuth) } InternalToken = loadSecret(sec, "INTERNAL_TOKEN_URI", "INTERNAL_TOKEN") if InstallLock && InternalToken == "" { // if Rixea 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") DisableQueryAuthToken = sec.Key("DISABLE_QUERY_AUTH_TOKEN").MustBool(true) RecordUserSignupMetadata = sec.Key("RECORD_USER_SIGNUP_METADATA").MustBool(false) if sectionHasDisableQueryAuthToken && !DisableQueryAuthToken { log.Warn("API tokens in URL query parameters are explicitly enabled; this can expose secrets in logs and browser history") } }