mirror of https://github.com/portainer/portainer
fix(jwt): fix handling of non-expiring JWT tokens BE-11242 (#12220)
parent
dbe7cd16d4
commit
5fd4f52e35
|
@ -381,7 +381,9 @@ func (bouncer *RequestBouncer) RevokeJWT(token string) {
|
||||||
|
|
||||||
func (bouncer *RequestBouncer) cleanUpExpiredJWTPass() {
|
func (bouncer *RequestBouncer) cleanUpExpiredJWTPass() {
|
||||||
bouncer.revokedJWT.Range(func(key, value any) bool {
|
bouncer.revokedJWT.Range(func(key, value any) bool {
|
||||||
if time.Now().After(value.(time.Time)) {
|
if t := value.(time.Time); t.IsZero() {
|
||||||
|
return true
|
||||||
|
} else if time.Now().After(t) {
|
||||||
bouncer.revokedJWT.Delete(key)
|
bouncer.revokedJWT.Delete(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -473,6 +473,17 @@ func TestJWTRevocation(t *testing.T) {
|
||||||
token, _, err := jwtService.GenerateToken(&portainer.TokenData{ID: 1})
|
token, _, err := jwtService.GenerateToken(&portainer.TokenData{ID: 1})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
settings, err := store.Settings().Settings()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
settings.KubeconfigExpiry = "0"
|
||||||
|
|
||||||
|
err = store.Settings().UpdateSettings(settings)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
kubeToken, err := jwtService.GenerateTokenForKubeconfig(&portainer.TokenData{ID: 1})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
apiKeyService := apikey.NewAPIKeyService(nil, nil)
|
apiKeyService := apikey.NewAPIKeyService(nil, nil)
|
||||||
|
|
||||||
bouncer := NewRequestBouncer(store, jwtService, apiKeyService)
|
bouncer := NewRequestBouncer(store, jwtService, apiKeyService)
|
||||||
|
@ -491,6 +502,7 @@ func TestJWTRevocation(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
bouncer.RevokeJWT(token)
|
bouncer.RevokeJWT(token)
|
||||||
|
bouncer.RevokeJWT(kubeToken)
|
||||||
|
|
||||||
revokeLen := func() (l int) {
|
revokeLen := func() (l int) {
|
||||||
bouncer.revokedJWT.Range(func(key, value any) bool {
|
bouncer.revokedJWT.Range(func(key, value any) bool {
|
||||||
|
@ -501,7 +513,7 @@ func TestJWTRevocation(t *testing.T) {
|
||||||
|
|
||||||
return l
|
return l
|
||||||
}
|
}
|
||||||
require.Equal(t, 1, revokeLen())
|
require.Equal(t, 2, revokeLen())
|
||||||
|
|
||||||
_, err = bouncer.JWTAuthLookup(r)
|
_, err = bouncer.JWTAuthLookup(r)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
|
@ -513,5 +525,5 @@ func TestJWTRevocation(t *testing.T) {
|
||||||
|
|
||||||
bouncer.cleanUpExpiredJWTPass()
|
bouncer.cleanUpExpiredJWTPass()
|
||||||
|
|
||||||
require.Equal(t, 0, revokeLen())
|
require.Equal(t, 1, revokeLen())
|
||||||
}
|
}
|
||||||
|
|
|
@ -137,6 +137,10 @@ func (service *Service) ParseAndVerifyToken(token string) (*portainer.TokenData,
|
||||||
return nil, "", time.Time{}, errInvalidJWTToken
|
return nil, "", time.Time{}, errInvalidJWTToken
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if cl.ExpiresAt == nil {
|
||||||
|
cl.ExpiresAt = &jwt.NumericDate{}
|
||||||
|
}
|
||||||
|
|
||||||
return &portainer.TokenData{
|
return &portainer.TokenData{
|
||||||
ID: portainer.UserID(cl.UserID),
|
ID: portainer.UserID(cl.UserID),
|
||||||
Username: cl.Username,
|
Username: cl.Username,
|
||||||
|
|
|
@ -3,14 +3,20 @@ package jwt
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/golang-jwt/jwt/v4"
|
|
||||||
portainer "github.com/portainer/portainer/api"
|
portainer "github.com/portainer/portainer/api"
|
||||||
"github.com/portainer/portainer/api/dataservices"
|
"github.com/portainer/portainer/api/dataservices"
|
||||||
i "github.com/portainer/portainer/api/internal/testhelpers"
|
"github.com/portainer/portainer/api/datastore"
|
||||||
|
|
||||||
|
"github.com/golang-jwt/jwt/v4"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestService_GenerateTokenForKubeconfig(t *testing.T) {
|
func TestService_GenerateTokenForKubeconfig(t *testing.T) {
|
||||||
|
_, store := datastore.MustNewTestStore(t, true, false)
|
||||||
|
|
||||||
|
err := store.User().Create(&portainer.User{ID: 1})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
type fields struct {
|
type fields struct {
|
||||||
userSessionTimeout string
|
userSessionTimeout string
|
||||||
dataStore dataservices.DataStore
|
dataStore dataservices.DataStore
|
||||||
|
@ -20,13 +26,17 @@ func TestService_GenerateTokenForKubeconfig(t *testing.T) {
|
||||||
data *portainer.TokenData
|
data *portainer.TokenData
|
||||||
}
|
}
|
||||||
|
|
||||||
mySettings := &portainer.Settings{
|
settings, err := store.Settings().Settings()
|
||||||
KubeconfigExpiry: "0",
|
assert.NoError(t, err)
|
||||||
}
|
|
||||||
|
settings.KubeconfigExpiry = "0"
|
||||||
|
|
||||||
|
err = store.Settings().UpdateSettings(settings)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
myFields := fields{
|
myFields := fields{
|
||||||
userSessionTimeout: "24h",
|
userSessionTimeout: "24h",
|
||||||
dataStore: i.NewDatastore(i.WithSettingsService(mySettings)),
|
dataStore: store,
|
||||||
}
|
}
|
||||||
|
|
||||||
myTokenData := &portainer.TokenData{
|
myTokenData := &portainer.TokenData{
|
||||||
|
@ -66,6 +76,9 @@ func TestService_GenerateTokenForKubeconfig(t *testing.T) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_, _, _, err = service.ParseAndVerifyToken(got)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
parsedToken, err := jwt.ParseWithClaims(got, &claims{}, func(token *jwt.Token) (any, error) {
|
parsedToken, err := jwt.ParseWithClaims(got, &claims{}, func(token *jwt.Token) (any, error) {
|
||||||
return service.secrets[kubeConfigScope], nil
|
return service.secrets[kubeConfigScope], nil
|
||||||
})
|
})
|
||||||
|
|
Loading…
Reference in New Issue