Always load or generate oauth2 jwt secret (#30942)

Fix #30923
This commit is contained in:
wxiaoguang 2024-05-14 22:21:38 +08:00 committed by GitHub
parent f4f4e18b14
commit effb405cae
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 44 additions and 12 deletions

View File

@ -126,16 +126,15 @@ func loadOAuth2From(rootCfg ConfigProvider) {
OAuth2.Enabled = sec.Key("ENABLE").MustBool(OAuth2.Enabled) OAuth2.Enabled = sec.Key("ENABLE").MustBool(OAuth2.Enabled)
} }
if !OAuth2.Enabled {
return
}
jwtSecretBase64 := loadSecret(sec, "JWT_SECRET_URI", "JWT_SECRET")
if !filepath.IsAbs(OAuth2.JWTSigningPrivateKeyFile) { if !filepath.IsAbs(OAuth2.JWTSigningPrivateKeyFile) {
OAuth2.JWTSigningPrivateKeyFile = filepath.Join(AppDataPath, OAuth2.JWTSigningPrivateKeyFile) OAuth2.JWTSigningPrivateKeyFile = filepath.Join(AppDataPath, OAuth2.JWTSigningPrivateKeyFile)
} }
// FIXME: at the moment, no matter oauth2 is enabled or not, it must generate a "oauth2 JWT_SECRET"
// Because this secret is also used as GeneralTokenSigningSecret (as a quick not-that-breaking fix for some legacy problems).
// Including: CSRF token, account validation token, etc ...
// In main branch, the signing token should be refactored (eg: one unique for LFS/OAuth2/etc ...)
jwtSecretBase64 := loadSecret(sec, "JWT_SECRET_URI", "JWT_SECRET")
if InstallLock { if InstallLock {
jwtSecretBytes, err := generate.DecodeJwtSecretBase64(jwtSecretBase64) jwtSecretBytes, err := generate.DecodeJwtSecretBase64(jwtSecretBase64)
if err != nil { if err != nil {
@ -157,8 +156,6 @@ func loadOAuth2From(rootCfg ConfigProvider) {
} }
} }
// generalSigningSecret is used as container for a []byte value
// instead of an additional mutex, we use CompareAndSwap func to change the value thread save
var generalSigningSecret atomic.Pointer[[]byte] var generalSigningSecret atomic.Pointer[[]byte]
func GetGeneralTokenSigningSecret() []byte { func GetGeneralTokenSigningSecret() []byte {
@ -166,11 +163,9 @@ func GetGeneralTokenSigningSecret() []byte {
if old == nil || len(*old) == 0 { if old == nil || len(*old) == 0 {
jwtSecret, _, err := generate.NewJwtSecretWithBase64() jwtSecret, _, err := generate.NewJwtSecretWithBase64()
if err != nil { if err != nil {
log.Fatal("Unable to generate general JWT secret: %s", err.Error()) log.Fatal("Unable to generate general JWT secret: %v", err)
} }
if generalSigningSecret.CompareAndSwap(old, &jwtSecret) { if generalSigningSecret.CompareAndSwap(old, &jwtSecret) {
// FIXME: in main branch, the signing token should be refactored (eg: one unique for LFS/OAuth2/etc ...)
LogStartupProblem(1, log.WARN, "OAuth2 is not enabled, unable to use a persistent signing secret, a new one is generated, which is not persistent between restarts and cluster nodes")
return jwtSecret return jwtSecret
} }
return *generalSigningSecret.Load() return *generalSigningSecret.Load()

View File

@ -4,6 +4,7 @@
package setting package setting
import ( import (
"os"
"testing" "testing"
"code.gitea.io/gitea/modules/generate" "code.gitea.io/gitea/modules/generate"
@ -14,7 +15,7 @@ import (
func TestGetGeneralSigningSecret(t *testing.T) { func TestGetGeneralSigningSecret(t *testing.T) {
// when there is no general signing secret, it should be generated, and keep the same value // when there is no general signing secret, it should be generated, and keep the same value
assert.Nil(t, generalSigningSecret.Load()) generalSigningSecret.Store(nil)
s1 := GetGeneralTokenSigningSecret() s1 := GetGeneralTokenSigningSecret()
assert.NotNil(t, s1) assert.NotNil(t, s1)
s2 := GetGeneralTokenSigningSecret() s2 := GetGeneralTokenSigningSecret()
@ -33,6 +34,31 @@ JWT_SECRET = BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
assert.EqualValues(t, expected, actual) assert.EqualValues(t, expected, actual)
} }
func TestGetGeneralSigningSecretSave(t *testing.T) {
defer test.MockVariableValue(&InstallLock, true)()
old := GetGeneralTokenSigningSecret()
assert.Len(t, old, 32)
tmpFile := t.TempDir() + "/app.ini"
_ = os.WriteFile(tmpFile, nil, 0o644)
cfg, _ := NewConfigProviderFromFile(tmpFile)
loadOAuth2From(cfg)
generated := GetGeneralTokenSigningSecret()
assert.Len(t, generated, 32)
assert.NotEqual(t, old, generated)
generalSigningSecret.Store(nil)
cfg, _ = NewConfigProviderFromFile(tmpFile)
loadOAuth2From(cfg)
again := GetGeneralTokenSigningSecret()
assert.Equal(t, generated, again)
iniContent, err := os.ReadFile(tmpFile)
assert.NoError(t, err)
assert.Contains(t, string(iniContent), "JWT_SECRET = ")
}
func TestOauth2DefaultApplications(t *testing.T) { func TestOauth2DefaultApplications(t *testing.T) {
cfg, _ := NewConfigProviderFromData(``) cfg, _ := NewConfigProviderFromData(``)
loadOAuth2From(cfg) loadOAuth2From(cfg)

View File

@ -481,6 +481,17 @@ func SubmitInstall(ctx *context.Context) {
cfg.Section("security").Key("INTERNAL_TOKEN").SetValue(internalToken) cfg.Section("security").Key("INTERNAL_TOKEN").SetValue(internalToken)
} }
// FIXME: at the moment, no matter oauth2 is enabled or not, it must generate a "oauth2 JWT_SECRET"
// see the "loadOAuth2From" in "setting/oauth2.go"
if !cfg.Section("oauth2").HasKey("JWT_SECRET") && !cfg.Section("oauth2").HasKey("JWT_SECRET_URI") {
_, jwtSecretBase64, err := generate.NewJwtSecretWithBase64()
if err != nil {
ctx.RenderWithErr(ctx.Tr("install.secret_key_failed", err), tplInstall, &form)
return
}
cfg.Section("oauth2").Key("JWT_SECRET").SetValue(jwtSecretBase64)
}
// if there is already a SECRET_KEY, we should not overwrite it, otherwise the encrypted data will not be able to be decrypted // if there is already a SECRET_KEY, we should not overwrite it, otherwise the encrypted data will not be able to be decrypted
if setting.SecretKey == "" { if setting.SecretKey == "" {
var secretKey string var secretKey string