Always load or generate oauth2 jwt secret (#30942)
Fix #30923 (cherry picked from commit effb405cae88474c27f5c8322a2627019af1cf64) Signed-off-by: Gergely Nagy <forgejo@gergo.csillger.hu> Conflicts: - modules/setting/oauth2.go Conflicted due to different ways of logging. Since the log message is removed anyway, resolved by removing it. - modules/setting/oauth2_test.go Manually copied the test added by Gitea. - routers/install/install.go Not a conflict per se, but adjusted to use NewJwtSecret().
This commit is contained in:
		
							parent
							
								
									1be797faba
								
							
						
					
					
						commit
						193ac67176
					
				
					 3 changed files with 44 additions and 12 deletions
				
			
		| 
						 | 
					@ -124,16 +124,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.DecodeJwtSecret(jwtSecretBase64)
 | 
							jwtSecretBytes, err := generate.DecodeJwtSecret(jwtSecretBase64)
 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
| 
						 | 
					@ -155,8 +154,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 {
 | 
				
			||||||
| 
						 | 
					@ -164,11 +161,9 @@ func GetGeneralTokenSigningSecret() []byte {
 | 
				
			||||||
	if old == nil || len(*old) == 0 {
 | 
						if old == nil || len(*old) == 0 {
 | 
				
			||||||
		jwtSecret, _, err := generate.NewJwtSecret()
 | 
							jwtSecret, _, err := generate.NewJwtSecret()
 | 
				
			||||||
		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 ...)
 | 
					 | 
				
			||||||
			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()
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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()
 | 
				
			||||||
| 
						 | 
					@ -32,3 +33,28 @@ JWT_SECRET = BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
 | 
				
			||||||
	assert.Len(t, actual, 32)
 | 
						assert.Len(t, actual, 32)
 | 
				
			||||||
	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 = ")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -486,6 +486,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.NewJwtSecret()
 | 
				
			||||||
 | 
							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
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue