mirror of
https://github.com/portainer/portainer.git
synced 2025-08-02 20:35:25 +02:00
feat(kubeconfig): Introduce the ability to change the expiry of a kubeconfig EE-1153 (#5421)
* feat(kubeconfig) EE-1153 Introduce the ability to change the expiry of a kubeconfig * feat(kubeconfig) EE-1153 pr feedback update * feat(kubeconfig) EE-1153 code cleanup Co-authored-by: Simon Meng <simon.meng@portainer.io>
This commit is contained in:
parent
c597ae96e2
commit
35013e7b6a
16 changed files with 221 additions and 37 deletions
|
@ -16,6 +16,7 @@ import (
|
|||
type Service struct {
|
||||
secret []byte
|
||||
userSessionTimeout time.Duration
|
||||
dataStore portainer.DataStore
|
||||
}
|
||||
|
||||
type claims struct {
|
||||
|
@ -31,7 +32,7 @@ var (
|
|||
)
|
||||
|
||||
// NewService initializes a new service. It will generate a random key that will be used to sign JWT tokens.
|
||||
func NewService(userSessionDuration string) (*Service, error) {
|
||||
func NewService(userSessionDuration string, dataStore portainer.DataStore) (*Service, error) {
|
||||
userSessionTimeout, err := time.ParseDuration(userSessionDuration)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -45,19 +46,28 @@ func NewService(userSessionDuration string) (*Service, error) {
|
|||
service := &Service{
|
||||
secret,
|
||||
userSessionTimeout,
|
||||
dataStore,
|
||||
}
|
||||
return service, nil
|
||||
}
|
||||
|
||||
func (service *Service) defaultExpireAt() (int64) {
|
||||
return time.Now().Add(service.userSessionTimeout).Unix()
|
||||
}
|
||||
|
||||
// GenerateToken generates a new JWT token.
|
||||
func (service *Service) GenerateToken(data *portainer.TokenData) (string, error) {
|
||||
return service.generateSignedToken(data, nil)
|
||||
return service.generateSignedToken(data, service.defaultExpireAt())
|
||||
}
|
||||
|
||||
// GenerateTokenForOAuth generates a new JWT for OAuth login
|
||||
// token expiry time from the OAuth provider is considered
|
||||
func (service *Service) GenerateTokenForOAuth(data *portainer.TokenData, expiryTime *time.Time) (string, error) {
|
||||
return service.generateSignedToken(data, expiryTime)
|
||||
expireAt := service.defaultExpireAt()
|
||||
if expiryTime != nil && !expiryTime.IsZero() {
|
||||
expireAt = expiryTime.Unix()
|
||||
}
|
||||
return service.generateSignedToken(data, expireAt)
|
||||
}
|
||||
|
||||
// ParseAndVerifyToken parses a JWT token and verify its validity. It returns an error if token is invalid.
|
||||
|
@ -88,17 +98,13 @@ func (service *Service) SetUserSessionDuration(userSessionDuration time.Duration
|
|||
service.userSessionTimeout = userSessionDuration
|
||||
}
|
||||
|
||||
func (service *Service) generateSignedToken(data *portainer.TokenData, expiryTime *time.Time) (string, error) {
|
||||
expireToken := time.Now().Add(service.userSessionTimeout).Unix()
|
||||
if expiryTime != nil && !expiryTime.IsZero() {
|
||||
expireToken = expiryTime.Unix()
|
||||
}
|
||||
func (service *Service) generateSignedToken(data *portainer.TokenData, expiresAt int64) (string, error) {
|
||||
cl := claims{
|
||||
UserID: int(data.ID),
|
||||
Username: data.Username,
|
||||
Role: int(data.Role),
|
||||
StandardClaims: jwt.StandardClaims{
|
||||
ExpiresAt: expireToken,
|
||||
ExpiresAt: expiresAt,
|
||||
},
|
||||
}
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, cl)
|
||||
|
|
26
api/jwt/jwt_kubeconfig.go
Normal file
26
api/jwt/jwt_kubeconfig.go
Normal file
|
@ -0,0 +1,26 @@
|
|||
package jwt
|
||||
|
||||
import (
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"time"
|
||||
)
|
||||
|
||||
// GenerateTokenForKubeconfig generates a new JWT token for Kubeconfig
|
||||
func (service *Service) GenerateTokenForKubeconfig(data *portainer.TokenData) (string, error) {
|
||||
settings, err := service.dataStore.Settings().Settings()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
expiryDuration, err := time.ParseDuration(settings.KubeconfigExpiry)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
expiryAt := time.Now().Add(expiryDuration).Unix()
|
||||
if expiryDuration == time.Duration(0) {
|
||||
expiryAt = 0
|
||||
}
|
||||
|
||||
return service.generateSignedToken(data, expiryAt)
|
||||
}
|
81
api/jwt/jwt_kubeconfig_test.go
Normal file
81
api/jwt/jwt_kubeconfig_test.go
Normal file
|
@ -0,0 +1,81 @@
|
|||
package jwt
|
||||
|
||||
import (
|
||||
"github.com/dgrijalva/jwt-go"
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
i "github.com/portainer/portainer/api/internal/testhelpers"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestService_GenerateTokenForKubeconfig(t *testing.T) {
|
||||
type fields struct {
|
||||
userSessionTimeout string
|
||||
dataStore portainer.DataStore
|
||||
}
|
||||
|
||||
type args struct {
|
||||
data *portainer.TokenData
|
||||
}
|
||||
|
||||
mySettings := &portainer.Settings{
|
||||
KubeconfigExpiry: "0",
|
||||
}
|
||||
|
||||
myFields := fields{
|
||||
userSessionTimeout: "24h",
|
||||
dataStore: i.NewDatastore(i.WithSettings(mySettings)),
|
||||
}
|
||||
|
||||
myTokenData := &portainer.TokenData{
|
||||
Username: "Joe",
|
||||
ID: 1,
|
||||
Role: 1,
|
||||
}
|
||||
|
||||
myArgs := args{
|
||||
data: myTokenData,
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
wantExpiresAt int64
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "kubeconfig no expiry",
|
||||
fields: myFields,
|
||||
args: myArgs,
|
||||
wantExpiresAt: 0,
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
service, err := NewService(tt.fields.userSessionTimeout, tt.fields.dataStore)
|
||||
assert.NoError(t, err, "failed to create a copy of service")
|
||||
|
||||
got, err := service.GenerateTokenForKubeconfig(tt.args.data)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("GenerateTokenForKubeconfig() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
|
||||
parsedToken, err := jwt.ParseWithClaims(got, &claims{}, func(token *jwt.Token) (interface{}, error) {
|
||||
return service.secret, nil
|
||||
})
|
||||
assert.NoError(t, err, "failed to parse generated token")
|
||||
|
||||
tokenClaims, ok := parsedToken.Claims.(*claims)
|
||||
assert.Equal(t, true, ok, "failed to claims out of generated ticket")
|
||||
|
||||
assert.Equal(t, myTokenData.Username, tokenClaims.Username)
|
||||
assert.Equal(t, int(myTokenData.ID), tokenClaims.UserID)
|
||||
assert.Equal(t, int(myTokenData.Role), tokenClaims.Role)
|
||||
assert.Equal(t, tt.wantExpiresAt, tokenClaims.ExpiresAt)
|
||||
})
|
||||
}
|
||||
}
|
|
@ -10,7 +10,7 @@ import (
|
|||
)
|
||||
|
||||
func TestGenerateSignedToken(t *testing.T) {
|
||||
svc, err := NewService("24h")
|
||||
svc, err := NewService("24h", nil)
|
||||
assert.NoError(t, err, "failed to create a copy of service")
|
||||
|
||||
token := &portainer.TokenData{
|
||||
|
@ -18,9 +18,9 @@ func TestGenerateSignedToken(t *testing.T) {
|
|||
ID: 1,
|
||||
Role: 1,
|
||||
}
|
||||
expirtationTime := time.Now().Add(1 * time.Hour)
|
||||
expiresAt := time.Now().Add(1 * time.Hour).Unix()
|
||||
|
||||
generatedToken, err := svc.generateSignedToken(token, &expirtationTime)
|
||||
generatedToken, err := svc.generateSignedToken(token, expiresAt)
|
||||
assert.NoError(t, err, "failed to generate a signed token")
|
||||
|
||||
parsedToken, err := jwt.ParseWithClaims(generatedToken, &claims{}, func(token *jwt.Token) (interface{}, error) {
|
||||
|
@ -34,5 +34,5 @@ func TestGenerateSignedToken(t *testing.T) {
|
|||
assert.Equal(t, token.Username, tokenClaims.Username)
|
||||
assert.Equal(t, int(token.ID), tokenClaims.UserID)
|
||||
assert.Equal(t, int(token.Role), tokenClaims.Role)
|
||||
assert.Equal(t, expirtationTime.Unix(), tokenClaims.ExpiresAt)
|
||||
assert.Equal(t, expiresAt, tokenClaims.ExpiresAt)
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue