mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2026-02-20 02:00:05 -05:00
Refactor services/lfs: Change token code to use SigningKey
This now also enables use of token algorithms other than HS256. In this case, signing key initialization also happens during settings initialization, because LFS is also used in CLI commands.
This commit is contained in:
parent
fd8249acc6
commit
a8a2d7e8f6
7 changed files with 70 additions and 25 deletions
|
|
@ -290,10 +290,9 @@ func runServ(ctx context.Context, c *cli.Command) error {
|
|||
Op: lfsVerb,
|
||||
UserID: results.UserID,
|
||||
}
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||
|
||||
// Sign and get the complete encoded token as a string using the secret
|
||||
tokenString, err := token.SignedString(setting.LFS.JWTSecretBytes)
|
||||
tokenString, err := setting.LFS.SigningKey.JWT(claims)
|
||||
if err != nil {
|
||||
return fail(ctx, "Failed to sign JWT Token", "Failed to sign JWT token: %v", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -313,6 +313,9 @@ RUN_USER = ; git
|
|||
;LFS_START_SERVER = false
|
||||
;;
|
||||
;;
|
||||
;; see JWT_* under [oauth2]
|
||||
;LFS_JWT_SIGNING_ALGORITHM = HS256
|
||||
;LFS_JWT_SIGNING_PRIVATE_KEY_FILE = jwt/lfs_private.pem
|
||||
;; LFS authentication secret, change this yourself
|
||||
;LFS_JWT_SECRET =
|
||||
;;
|
||||
|
|
@ -544,6 +547,7 @@ ENABLED = true
|
|||
;; Private key file path used to sign OAuth2 tokens. The path is relative to APP_DATA_PATH.
|
||||
;; This setting is only needed if JWT_SIGNING_ALGORITHM is set to RS256, RS384, RS512, ES256, ES384 or ES512.
|
||||
;; The file must contain a RSA or ECDSA private key in the PKCS8 format. If no key exists a 4096 bit key will be created for you.
|
||||
;; XXX jwt/ is a misnomer, it should rather be oauth2/, because we use many JWTs
|
||||
;JWT_SIGNING_PRIVATE_KEY_FILE = jwt/private.pem
|
||||
;;
|
||||
;; OAuth2 authentication secret for access and refresh tokens, change this yourself to a unique string. CLI generate option is helpful in this case. https://forgejo.org/docs/latest/admin/command-line/#generate-secret
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import (
|
|||
"fmt"
|
||||
"time"
|
||||
|
||||
"forgejo.org/modules/generate"
|
||||
"forgejo.org/modules/jwtx"
|
||||
)
|
||||
|
||||
// LFS represents the server-side configuration for Git LFS.
|
||||
|
|
@ -16,13 +16,13 @@ import (
|
|||
// Could be refactored in the future while keeping backwards compatibility.
|
||||
var LFS = struct {
|
||||
StartServer bool `ini:"LFS_START_SERVER"`
|
||||
JWTSecretBytes []byte `ini:"-"`
|
||||
HTTPAuthExpiry time.Duration `ini:"LFS_HTTP_AUTH_EXPIRY"`
|
||||
MaxFileSize int64 `ini:"LFS_MAX_FILE_SIZE"`
|
||||
LocksPagingNum int `ini:"LFS_LOCKS_PAGING_NUM"`
|
||||
MaxBatchSize int `ini:"LFS_MAX_BATCH_SIZE"`
|
||||
|
||||
Storage *Storage
|
||||
SigningKey jwtx.SigningKey
|
||||
Storage *Storage
|
||||
}{}
|
||||
|
||||
// LFSClient represents configuration for Gitea's LFS clients, for example: mirroring upstream Git LFS
|
||||
|
|
@ -77,20 +77,14 @@ func loadLFSFrom(rootCfg ConfigProvider) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
jwtSecretBase64 := loadSecret(rootCfg.Section("server"), "LFS_JWT_SECRET_URI", "LFS_JWT_SECRET")
|
||||
LFS.JWTSecretBytes, err = generate.DecodeJwtSecret(jwtSecretBase64)
|
||||
if err != nil {
|
||||
LFS.JWTSecretBytes, jwtSecretBase64 = generate.NewJwtSecret()
|
||||
|
||||
// Save secret
|
||||
saveCfg, err := rootCfg.PrepareSaving()
|
||||
if err != nil {
|
||||
return fmt.Errorf("error saving JWT Secret for custom config: %v", err)
|
||||
// XXX #11024 check nil because settings loaded twice
|
||||
if LFS.SigningKey == nil {
|
||||
keyCfg, err := loadKeyCfg(rootCfg, "server", "LFS_JWT_", "HS256", "lfs/private.pem")
|
||||
if err == nil {
|
||||
LFS.SigningKey, err = jwtx.InitSigningKey(&keyCfg.Signing)
|
||||
}
|
||||
rootCfg.Section("server").Key("LFS_JWT_SECRET").SetValue(jwtSecretBase64)
|
||||
saveCfg.Section("server").Key("LFS_JWT_SECRET").SetValue(jwtSecretBase64)
|
||||
if err := saveCfg.Save(); err != nil {
|
||||
return fmt.Errorf("error saving JWT Secret for custom config: %v", err)
|
||||
if err != nil {
|
||||
return fmt.Errorf("lfs key initialization failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -164,6 +164,7 @@ func initLFS() (err error) {
|
|||
LFS = DiscardStorage("LFS isn't enabled")
|
||||
return nil
|
||||
}
|
||||
|
||||
log.Info("Initialising LFS storage with type: %s", setting.LFS.Storage.Type)
|
||||
LFS, err = NewStorage(setting.LFS.Storage.Type, setting.LFS.Storage)
|
||||
return err
|
||||
|
|
|
|||
|
|
@ -581,10 +581,11 @@ func authenticate(ctx *context.Context, repository *repo_model.Repository, autho
|
|||
|
||||
func handleLFSToken(ctx stdCtx.Context, tokenSHA string, target *repo_model.Repository, mode perm.AccessMode) (*user_model.User, error) {
|
||||
token, err := jwt.ParseWithClaims(tokenSHA, &Claims{}, func(t *jwt.Token) (any, error) {
|
||||
if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok {
|
||||
k := setting.LFS.SigningKey
|
||||
if t.Method != k.SigningMethod() {
|
||||
return nil, fmt.Errorf("unexpected signing method: %v", t.Header["alg"])
|
||||
}
|
||||
return setting.LFS.JWTSecretBytes, nil
|
||||
return k.VerifyKey(), nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, errors.New("invalid token")
|
||||
|
|
|
|||
|
|
@ -41,18 +41,23 @@ func getLFSAuthTokenWithBearer(opts authTokenOptions) (string, error) {
|
|||
Op: opts.Op,
|
||||
UserID: opts.UserID,
|
||||
}
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||
|
||||
// Sign and get the complete encoded token as a string using the secret
|
||||
tokenString, err := token.SignedString(setting.LFS.JWTSecretBytes)
|
||||
tokenString, err := setting.LFS.SigningKey.JWT(claims)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to sign LFS JWT token: %w", err)
|
||||
}
|
||||
return "Bearer " + tokenString, nil
|
||||
}
|
||||
|
||||
func TestAuthenticate(t *testing.T) {
|
||||
func testAuthenticate(t *testing.T, cfg string) {
|
||||
require.NoError(t, unittest.PrepareTestDatabase())
|
||||
var err error
|
||||
setting.CfgProvider, err = setting.NewConfigProviderFromData(cfg)
|
||||
require.NoError(t, err, "Config")
|
||||
setting.LoadCommonSettings()
|
||||
assert.True(t, setting.LFS.StartServer, "LFS_START_SERVER = true")
|
||||
assert.NotNil(t, setting.LFS.SigningKey, "SigningKey initialized")
|
||||
repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
|
||||
|
||||
token2, _ := getLFSAuthTokenWithBearer(authTokenOptions{Op: "download", UserID: 2, RepoID: 1})
|
||||
|
|
@ -80,3 +85,30 @@ func TestAuthenticate(t *testing.T) {
|
|||
assert.True(t, authenticate(ctx, repo1, prefixBearer+token2, true, false))
|
||||
})
|
||||
}
|
||||
|
||||
type namedCfg struct {
|
||||
name, cfg string
|
||||
}
|
||||
|
||||
var iniCommon = `[security]
|
||||
INSTALL_LOCK = true
|
||||
INTERNAL_TOKEN = ForgejoForgejoForgejoForgejoForgejoForgejo_ # don't use in prod
|
||||
[oauth2]
|
||||
JWT_SECRET = ForgejoForgejoForgejoForgejoForgejoForgejo_ # don't use in prod
|
||||
[server]
|
||||
LFS_START_SERVER = true
|
||||
`
|
||||
|
||||
var cfgVariants = []namedCfg{
|
||||
{name: "HS256_default", cfg: `LFS_JWT_SECRET = ForgejoForgejoForgejoForgejoForgejoForgejo_`},
|
||||
{name: "RS256", cfg: `LFS_JWT_SIGNING_ALGORITHM = RS256`},
|
||||
}
|
||||
|
||||
func TestAuthenticate(t *testing.T) {
|
||||
// XXX #11024
|
||||
setting.InstallLock = true
|
||||
for _, v := range cfgVariants {
|
||||
cfg := iniCommon + v.cfg
|
||||
t.Run(v.name, func(t *testing.T) { testAuthenticate(t, cfg) })
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,11 +21,25 @@ import (
|
|||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
var ini = `[security]
|
||||
INSTALL_LOCK = true
|
||||
INTERNAL_TOKEN = ForgejoForgejoForgejoForgejoForgejoForgejo_ # don't use in prod
|
||||
[oauth2]
|
||||
JWT_SECRET = ForgejoForgejoForgejoForgejoForgejoForgejo_ # don't use in prod
|
||||
[server]
|
||||
LFS_START_SERVER = true
|
||||
LFS_JWT_SECRET = ForgejoForgejoForgejoForgejoForgejoForgejo_ # don't use in prod
|
||||
`
|
||||
|
||||
func TestGarbageCollectLFSMetaObjects(t *testing.T) {
|
||||
var err error
|
||||
setting.CfgProvider, err = setting.NewConfigProviderFromData(ini)
|
||||
require.NoError(t, err, "Config")
|
||||
setting.LoadCommonSettings()
|
||||
|
||||
unittest.PrepareTestEnv(t)
|
||||
|
||||
setting.LFS.StartServer = true
|
||||
err := storage.Init()
|
||||
err = storage.Init()
|
||||
require.NoError(t, err)
|
||||
|
||||
repo, err := repo_model.GetRepositoryByOwnerAndName(db.DefaultContext, "user2", "lfs")
|
||||
|
|
|
|||
Loading…
Reference in a new issue