Skip to content

Commit 27f712b

Browse files
wxiaoguangsilverwind
authored andcommitted
Use general token signing secret (go-gitea#29205)
Use a clearly defined "signing secret" for token signing.
1 parent 3f492ee commit 27f712b

File tree

9 files changed

+82
-33
lines changed

9 files changed

+82
-33
lines changed

modules/base/tool.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ func CreateTimeLimitCode(data string, minutes int, startInf any) string {
115115

116116
// create sha1 encode string
117117
sh := sha1.New()
118-
_, _ = sh.Write([]byte(fmt.Sprintf("%s%s%s%s%d", data, setting.SecretKey, startStr, endStr, minutes)))
118+
_, _ = sh.Write([]byte(fmt.Sprintf("%s%s%s%s%d", data, hex.EncodeToString(setting.GetGeneralTokenSigningSecret()), startStr, endStr, minutes)))
119119
encoded := hex.EncodeToString(sh.Sum(nil))
120120

121121
code := fmt.Sprintf("%s%06d%s", startStr, minutes, encoded)

modules/context/context.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ package context
66

77
import (
88
"context"
9+
"encoding/hex"
910
"fmt"
1011
"html/template"
1112
"io"
@@ -124,7 +125,7 @@ func NewWebContext(base *Base, render Render, session session.Store) *Context {
124125
func Contexter() func(next http.Handler) http.Handler {
125126
rnd := templates.HTMLRenderer()
126127
csrfOpts := CsrfOptions{
127-
Secret: setting.SecretKey,
128+
Secret: hex.EncodeToString(setting.GetGeneralTokenSigningSecret()),
128129
Cookie: setting.CSRFCookieName,
129130
SetCookie: true,
130131
Secure: setting.SessionConfig.Secure,

modules/setting/lfs.go

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,11 @@ import (
1212

1313
// LFS represents the configuration for Git LFS
1414
var LFS = struct {
15-
StartServer bool `ini:"LFS_START_SERVER"`
16-
JWTSecretBase64 string `ini:"LFS_JWT_SECRET"`
17-
JWTSecretBytes []byte `ini:"-"`
18-
HTTPAuthExpiry time.Duration `ini:"LFS_HTTP_AUTH_EXPIRY"`
19-
MaxFileSize int64 `ini:"LFS_MAX_FILE_SIZE"`
20-
LocksPagingNum int `ini:"LFS_LOCKS_PAGING_NUM"`
15+
StartServer bool `ini:"LFS_START_SERVER"`
16+
JWTSecretBytes []byte `ini:"-"`
17+
HTTPAuthExpiry time.Duration `ini:"LFS_HTTP_AUTH_EXPIRY"`
18+
MaxFileSize int64 `ini:"LFS_MAX_FILE_SIZE"`
19+
LocksPagingNum int `ini:"LFS_LOCKS_PAGING_NUM"`
2120

2221
Storage *Storage
2322
}{}
@@ -59,10 +58,10 @@ func loadLFSFrom(rootCfg ConfigProvider) error {
5958
return nil
6059
}
6160

62-
LFS.JWTSecretBase64 = loadSecret(rootCfg.Section("server"), "LFS_JWT_SECRET_URI", "LFS_JWT_SECRET")
63-
LFS.JWTSecretBytes, err = generate.DecodeJwtSecretBase64(LFS.JWTSecretBase64)
61+
jwtSecretBase64 := loadSecret(rootCfg.Section("server"), "LFS_JWT_SECRET_URI", "LFS_JWT_SECRET")
62+
LFS.JWTSecretBytes, err = generate.DecodeJwtSecretBase64(jwtSecretBase64)
6463
if err != nil {
65-
LFS.JWTSecretBytes, LFS.JWTSecretBase64, err = generate.NewJwtSecretWithBase64()
64+
LFS.JWTSecretBytes, jwtSecretBase64, err = generate.NewJwtSecretWithBase64()
6665
if err != nil {
6766
return fmt.Errorf("error generating JWT Secret for custom config: %v", err)
6867
}
@@ -72,8 +71,8 @@ func loadLFSFrom(rootCfg ConfigProvider) error {
7271
if err != nil {
7372
return fmt.Errorf("error saving JWT Secret for custom config: %v", err)
7473
}
75-
rootCfg.Section("server").Key("LFS_JWT_SECRET").SetValue(LFS.JWTSecretBase64)
76-
saveCfg.Section("server").Key("LFS_JWT_SECRET").SetValue(LFS.JWTSecretBase64)
74+
rootCfg.Section("server").Key("LFS_JWT_SECRET").SetValue(jwtSecretBase64)
75+
saveCfg.Section("server").Key("LFS_JWT_SECRET").SetValue(jwtSecretBase64)
7776
if err := saveCfg.Save(); err != nil {
7877
return fmt.Errorf("error saving JWT Secret for custom config: %v", err)
7978
}

modules/setting/oauth2.go

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ package setting
66
import (
77
"math"
88
"path/filepath"
9+
"sync/atomic"
910

1011
"code.gitea.io/gitea/modules/generate"
1112
"code.gitea.io/gitea/modules/log"
@@ -96,7 +97,6 @@ var OAuth2 = struct {
9697
RefreshTokenExpirationTime int64
9798
InvalidateRefreshTokens bool
9899
JWTSigningAlgorithm string `ini:"JWT_SIGNING_ALGORITHM"`
99-
JWTSecretBase64 string `ini:"JWT_SECRET"`
100100
JWTSigningPrivateKeyFile string `ini:"JWT_SIGNING_PRIVATE_KEY_FILE"`
101101
MaxTokenLength int
102102
DefaultApplications []string
@@ -128,28 +128,50 @@ func loadOAuth2From(rootCfg ConfigProvider) {
128128
return
129129
}
130130

131-
OAuth2.JWTSecretBase64 = loadSecret(sec, "JWT_SECRET_URI", "JWT_SECRET")
131+
jwtSecretBase64 := loadSecret(sec, "JWT_SECRET_URI", "JWT_SECRET")
132132

133133
if !filepath.IsAbs(OAuth2.JWTSigningPrivateKeyFile) {
134134
OAuth2.JWTSigningPrivateKeyFile = filepath.Join(AppDataPath, OAuth2.JWTSigningPrivateKeyFile)
135135
}
136136

137137
if InstallLock {
138-
if _, err := generate.DecodeJwtSecretBase64(OAuth2.JWTSecretBase64); err != nil {
139-
_, OAuth2.JWTSecretBase64, err = generate.NewJwtSecretWithBase64()
138+
jwtSecretBytes, err := generate.DecodeJwtSecretBase64(jwtSecretBase64)
139+
if err != nil {
140+
jwtSecretBytes, jwtSecretBase64, err = generate.NewJwtSecretWithBase64()
140141
if err != nil {
141142
log.Fatal("error generating JWT secret: %v", err)
142143
}
143-
144144
saveCfg, err := rootCfg.PrepareSaving()
145145
if err != nil {
146146
log.Fatal("save oauth2.JWT_SECRET failed: %v", err)
147147
}
148-
rootCfg.Section("oauth2").Key("JWT_SECRET").SetValue(OAuth2.JWTSecretBase64)
149-
saveCfg.Section("oauth2").Key("JWT_SECRET").SetValue(OAuth2.JWTSecretBase64)
148+
rootCfg.Section("oauth2").Key("JWT_SECRET").SetValue(jwtSecretBase64)
149+
saveCfg.Section("oauth2").Key("JWT_SECRET").SetValue(jwtSecretBase64)
150150
if err := saveCfg.Save(); err != nil {
151151
log.Fatal("save oauth2.JWT_SECRET failed: %v", err)
152152
}
153153
}
154+
generalSigningSecret.Store(&jwtSecretBytes)
155+
}
156+
}
157+
158+
// generalSigningSecret is used as container for a []byte value
159+
// instead of an additional mutex, we use CompareAndSwap func to change the value thread save
160+
var generalSigningSecret atomic.Pointer[[]byte]
161+
162+
func GetGeneralTokenSigningSecret() []byte {
163+
old := generalSigningSecret.Load()
164+
if old == nil || len(*old) == 0 {
165+
jwtSecret, _, err := generate.NewJwtSecretWithBase64()
166+
if err != nil {
167+
log.Fatal("Unable to generate general JWT secret: %s", err.Error())
168+
}
169+
if generalSigningSecret.CompareAndSwap(old, &jwtSecret) {
170+
// FIXME: in main branch, the signing token should be refactored (eg: one unique for LFS/OAuth2/etc ...)
171+
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")
172+
return jwtSecret
173+
}
174+
return *generalSigningSecret.Load()
154175
}
176+
return *old
155177
}

modules/setting/oauth2_test.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// Copyright 2024 The Gitea Authors. All rights reserved.
2+
// SPDX-License-Identifier: MIT
3+
4+
package setting
5+
6+
import (
7+
"testing"
8+
9+
"code.gitea.io/gitea/modules/generate"
10+
"code.gitea.io/gitea/modules/test"
11+
12+
"github.com/stretchr/testify/assert"
13+
)
14+
15+
func TestGetGeneralSigningSecret(t *testing.T) {
16+
// when there is no general signing secret, it should be generated, and keep the same value
17+
assert.Nil(t, generalSigningSecret.Load())
18+
s1 := GetGeneralTokenSigningSecret()
19+
assert.NotNil(t, s1)
20+
s2 := GetGeneralTokenSigningSecret()
21+
assert.Equal(t, s1, s2)
22+
23+
// the config value should always override any pre-generated value
24+
cfg, _ := NewConfigProviderFromData(`
25+
[oauth2]
26+
JWT_SECRET = BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
27+
`)
28+
defer test.MockVariableValue(&InstallLock, true)()
29+
loadOAuth2From(cfg)
30+
actual := GetGeneralTokenSigningSecret()
31+
expected, _ := generate.DecodeJwtSecretBase64("BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB")
32+
assert.Len(t, actual, 32)
33+
assert.EqualValues(t, expected, actual)
34+
}

services/actions/auth.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ func CreateAuthorizationToken(taskID, runID, jobID int64) (string, error) {
3838
}
3939
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
4040

41-
tokenString, err := token.SignedString([]byte(setting.SecretKey))
41+
tokenString, err := token.SignedString(setting.GetGeneralTokenSigningSecret())
4242
if err != nil {
4343
return "", err
4444
}
@@ -62,7 +62,7 @@ func ParseAuthorizationToken(req *http.Request) (int64, error) {
6262
if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok {
6363
return nil, fmt.Errorf("unexpected signing method: %v", t.Header["alg"])
6464
}
65-
return []byte(setting.SecretKey), nil
65+
return setting.GetGeneralTokenSigningSecret(), nil
6666
})
6767
if err != nil {
6868
return 0, err

services/actions/auth_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ func TestCreateAuthorizationToken(t *testing.T) {
2020
assert.NotEqual(t, "", token)
2121
claims := jwt.MapClaims{}
2222
_, err = jwt.ParseWithClaims(token, claims, func(t *jwt.Token) (interface{}, error) {
23-
return []byte(setting.SecretKey), nil
23+
return setting.GetGeneralTokenSigningSecret(), nil
2424
})
2525
assert.Nil(t, err)
2626
scp, ok := claims["scp"]

services/auth/source/oauth2/jwtsigningkey.go

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ import (
1818
"path/filepath"
1919
"strings"
2020

21-
"code.gitea.io/gitea/modules/generate"
2221
"code.gitea.io/gitea/modules/log"
2322
"code.gitea.io/gitea/modules/setting"
2423
"code.gitea.io/gitea/modules/util"
@@ -301,7 +300,7 @@ func InitSigningKey() error {
301300
case "HS384":
302301
fallthrough
303302
case "HS512":
304-
key, err = loadSymmetricKey()
303+
key = setting.GetGeneralTokenSigningSecret()
305304
case "RS256":
306305
fallthrough
307306
case "RS384":
@@ -334,12 +333,6 @@ func InitSigningKey() error {
334333
return nil
335334
}
336335

337-
// loadSymmetricKey checks if the configured secret is valid.
338-
// If it is not valid, it will return an error.
339-
func loadSymmetricKey() (any, error) {
340-
return generate.DecodeJwtSecretBase64(setting.OAuth2.JWTSecretBase64)
341-
}
342-
343336
// loadOrCreateAsymmetricKey checks if the configured private key exists.
344337
// If it does not exist a new random key gets generated and saved on the configured path.
345338
func loadOrCreateAsymmetricKey() (any, error) {

services/packages/auth.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ func CreateAuthorizationToken(u *user_model.User) (string, error) {
3333
}
3434
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
3535

36-
tokenString, err := token.SignedString([]byte(setting.SecretKey))
36+
tokenString, err := token.SignedString(setting.GetGeneralTokenSigningSecret())
3737
if err != nil {
3838
return "", err
3939
}
@@ -57,7 +57,7 @@ func ParseAuthorizationToken(req *http.Request) (int64, error) {
5757
if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok {
5858
return nil, fmt.Errorf("unexpected signing method: %v", t.Header["alg"])
5959
}
60-
return []byte(setting.SecretKey), nil
60+
return setting.GetGeneralTokenSigningSecret(), nil
6161
})
6262
if err != nil {
6363
return 0, err

0 commit comments

Comments
 (0)