mirror of https://github.com/portainer/portainer
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>pull/5458/head
parent
c597ae96e2
commit
35013e7b6a
|
@ -45,6 +45,7 @@ func (store *Store) Init() error {
|
||||||
EdgeAgentCheckinInterval: portainer.DefaultEdgeAgentCheckinIntervalInSeconds,
|
EdgeAgentCheckinInterval: portainer.DefaultEdgeAgentCheckinIntervalInSeconds,
|
||||||
TemplatesURL: portainer.DefaultTemplatesURL,
|
TemplatesURL: portainer.DefaultTemplatesURL,
|
||||||
UserSessionTimeout: portainer.DefaultUserSessionTimeout,
|
UserSessionTimeout: portainer.DefaultUserSessionTimeout,
|
||||||
|
KubeconfigExpiry: portainer.DefaultKubeconfigExpiry,
|
||||||
}
|
}
|
||||||
|
|
||||||
err = store.SettingsService.UpdateSettings(defaultSettings)
|
err = store.SettingsService.UpdateSettings(defaultSettings)
|
||||||
|
|
|
@ -24,6 +24,10 @@ func (m *Migrator) migrateDBVersionToDB32() error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := m.kubeconfigExpiryToDB32(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -211,3 +215,12 @@ func findResourcesToUpdateForDB32(dockerID string, volumesData map[string]interf
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *Migrator) kubeconfigExpiryToDB32() error {
|
||||||
|
settings, err := m.settingsService.Settings()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
settings.KubeconfigExpiry = portainer.DefaultKubeconfigExpiry
|
||||||
|
return m.settingsService.UpdateSettings(settings)
|
||||||
|
}
|
|
@ -114,7 +114,7 @@ func initJWTService(dataStore portainer.DataStore) (portainer.JWTService, error)
|
||||||
settings.UserSessionTimeout = portainer.DefaultUserSessionTimeout
|
settings.UserSessionTimeout = portainer.DefaultUserSessionTimeout
|
||||||
dataStore.Settings().UpdateSettings(settings)
|
dataStore.Settings().UpdateSettings(settings)
|
||||||
}
|
}
|
||||||
jwtService, err := jwt.NewService(settings.UserSessionTimeout)
|
jwtService, err := jwt.NewService(settings.UserSessionTimeout, dataStore)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,7 @@ type Handler struct {
|
||||||
dataStore portainer.DataStore
|
dataStore portainer.DataStore
|
||||||
kubernetesClientFactory *cli.ClientFactory
|
kubernetesClientFactory *cli.ClientFactory
|
||||||
authorizationService *authorization.Service
|
authorizationService *authorization.Service
|
||||||
|
JwtService portainer.JWTService
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewHandler creates a handler to process pre-proxied requests to external APIs.
|
// NewHandler creates a handler to process pre-proxied requests to external APIs.
|
||||||
|
|
|
@ -3,14 +3,11 @@ package kubernetes
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
|
||||||
|
|
||||||
httperror "github.com/portainer/libhttp/error"
|
httperror "github.com/portainer/libhttp/error"
|
||||||
"github.com/portainer/libhttp/request"
|
"github.com/portainer/libhttp/request"
|
||||||
"github.com/portainer/libhttp/response"
|
"github.com/portainer/libhttp/response"
|
||||||
portainer "github.com/portainer/portainer/api"
|
portainer "github.com/portainer/portainer/api"
|
||||||
bolterrors "github.com/portainer/portainer/api/bolt/errors"
|
bolterrors "github.com/portainer/portainer/api/bolt/errors"
|
||||||
httperrors "github.com/portainer/portainer/api/http/errors"
|
|
||||||
"github.com/portainer/portainer/api/http/security"
|
"github.com/portainer/portainer/api/http/security"
|
||||||
kcli "github.com/portainer/portainer/api/kubernetes/cli"
|
kcli "github.com/portainer/portainer/api/kubernetes/cli"
|
||||||
|
|
||||||
|
@ -46,16 +43,16 @@ func (handler *Handler) getKubernetesConfig(w http.ResponseWriter, r *http.Reque
|
||||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to find an endpoint with the specified identifier inside the database", err}
|
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to find an endpoint with the specified identifier inside the database", err}
|
||||||
}
|
}
|
||||||
|
|
||||||
bearerToken, err := extractBearerToken(r)
|
|
||||||
if err != nil {
|
|
||||||
return &httperror.HandlerError{http.StatusUnauthorized, "Unauthorized", err}
|
|
||||||
}
|
|
||||||
|
|
||||||
tokenData, err := security.RetrieveTokenData(r)
|
tokenData, err := security.RetrieveTokenData(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &httperror.HandlerError{http.StatusForbidden, "Permission denied to access endpoint", err}
|
return &httperror.HandlerError{http.StatusForbidden, "Permission denied to access endpoint", err}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bearerToken, err := handler.JwtService.GenerateTokenForKubeconfig(tokenData)
|
||||||
|
if err != nil {
|
||||||
|
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to generate JWT token", err}
|
||||||
|
}
|
||||||
|
|
||||||
cli, err := handler.kubernetesClientFactory.GetKubeClient(endpoint)
|
cli, err := handler.kubernetesClientFactory.GetKubeClient(endpoint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to create Kubernetes client", err}
|
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to create Kubernetes client", err}
|
||||||
|
@ -84,20 +81,6 @@ func (handler *Handler) getKubernetesConfig(w http.ResponseWriter, r *http.Reque
|
||||||
return response.JSON(w, config)
|
return response.JSON(w, config)
|
||||||
}
|
}
|
||||||
|
|
||||||
// extractBearerToken extracts user's portainer bearer token from request auth header
|
|
||||||
func extractBearerToken(r *http.Request) (string, error) {
|
|
||||||
token := ""
|
|
||||||
tokens := r.Header["Authorization"]
|
|
||||||
if len(tokens) >= 1 {
|
|
||||||
token = tokens[0]
|
|
||||||
token = strings.TrimPrefix(token, "Bearer ")
|
|
||||||
}
|
|
||||||
if token == "" {
|
|
||||||
return "", httperrors.ErrUnauthorized
|
|
||||||
}
|
|
||||||
return token, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// getProxyUrl generates portainer proxy url which acts as proxy to k8s api server
|
// getProxyUrl generates portainer proxy url which acts as proxy to k8s api server
|
||||||
func getProxyUrl(r *http.Request, endpointID int) string {
|
func getProxyUrl(r *http.Request, endpointID int) string {
|
||||||
return fmt.Sprintf("https://%s/api/endpoints/%d/kubernetes", r.Host, endpointID)
|
return fmt.Sprintf("https://%s/api/endpoints/%d/kubernetes", r.Host, endpointID)
|
||||||
|
|
|
@ -32,6 +32,8 @@ type settingsUpdatePayload struct {
|
||||||
EnableEdgeComputeFeatures *bool `example:"true"`
|
EnableEdgeComputeFeatures *bool `example:"true"`
|
||||||
// The duration of a user session
|
// The duration of a user session
|
||||||
UserSessionTimeout *string `example:"5m"`
|
UserSessionTimeout *string `example:"5m"`
|
||||||
|
// The expiry of a Kubeconfig
|
||||||
|
KubeconfigExpiry *string `example:"24h" default:"0"`
|
||||||
// Whether telemetry is enabled
|
// Whether telemetry is enabled
|
||||||
EnableTelemetry *bool `example:"false"`
|
EnableTelemetry *bool `example:"false"`
|
||||||
}
|
}
|
||||||
|
@ -52,6 +54,12 @@ func (payload *settingsUpdatePayload) Validate(r *http.Request) error {
|
||||||
return errors.New("Invalid user session timeout")
|
return errors.New("Invalid user session timeout")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if payload.KubeconfigExpiry != nil {
|
||||||
|
_, err := time.ParseDuration(*payload.KubeconfigExpiry)
|
||||||
|
if err != nil {
|
||||||
|
return errors.New("Invalid Kubeconfig Expiry")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -135,6 +143,10 @@ func (handler *Handler) settingsUpdate(w http.ResponseWriter, r *http.Request) *
|
||||||
settings.EdgeAgentCheckinInterval = *payload.EdgeAgentCheckinInterval
|
settings.EdgeAgentCheckinInterval = *payload.EdgeAgentCheckinInterval
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if payload.KubeconfigExpiry != nil {
|
||||||
|
settings.KubeconfigExpiry = *payload.KubeconfigExpiry
|
||||||
|
}
|
||||||
|
|
||||||
if payload.UserSessionTimeout != nil {
|
if payload.UserSessionTimeout != nil {
|
||||||
settings.UserSessionTimeout = *payload.UserSessionTimeout
|
settings.UserSessionTimeout = *payload.UserSessionTimeout
|
||||||
|
|
||||||
|
|
|
@ -161,6 +161,7 @@ func (server *Server) Start() error {
|
||||||
endpointProxyHandler.ReverseTunnelService = server.ReverseTunnelService
|
endpointProxyHandler.ReverseTunnelService = server.ReverseTunnelService
|
||||||
|
|
||||||
var kubernetesHandler = kubehandler.NewHandler(requestBouncer, server.AuthorizationService, server.DataStore, server.KubernetesClientFactory)
|
var kubernetesHandler = kubehandler.NewHandler(requestBouncer, server.AuthorizationService, server.DataStore, server.KubernetesClientFactory)
|
||||||
|
kubernetesHandler.JwtService = server.JWTService
|
||||||
|
|
||||||
var fileHandler = file.NewHandler(filepath.Join(server.AssetsPath, "public"))
|
var fileHandler = file.NewHandler(filepath.Join(server.AssetsPath, "public"))
|
||||||
|
|
||||||
|
|
|
@ -70,6 +70,21 @@ func NewDatastore(options ...datastoreOption) *datastore {
|
||||||
return &d
|
return &d
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
type stubSettingsService struct {
|
||||||
|
settings *portainer.Settings
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stubSettingsService) Settings() (*portainer.Settings, error) { return s.settings, nil }
|
||||||
|
func (s *stubSettingsService) UpdateSettings(settings *portainer.Settings) error { return nil }
|
||||||
|
|
||||||
|
func WithSettings(settings *portainer.Settings) datastoreOption {
|
||||||
|
return func(d *datastore) {
|
||||||
|
d.settings = &stubSettingsService{settings: settings}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
type stubUserService struct {
|
type stubUserService struct {
|
||||||
users []portainer.User
|
users []portainer.User
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@ import (
|
||||||
type Service struct {
|
type Service struct {
|
||||||
secret []byte
|
secret []byte
|
||||||
userSessionTimeout time.Duration
|
userSessionTimeout time.Duration
|
||||||
|
dataStore portainer.DataStore
|
||||||
}
|
}
|
||||||
|
|
||||||
type claims struct {
|
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.
|
// 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)
|
userSessionTimeout, err := time.ParseDuration(userSessionDuration)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -45,19 +46,28 @@ func NewService(userSessionDuration string) (*Service, error) {
|
||||||
service := &Service{
|
service := &Service{
|
||||||
secret,
|
secret,
|
||||||
userSessionTimeout,
|
userSessionTimeout,
|
||||||
|
dataStore,
|
||||||
}
|
}
|
||||||
return service, nil
|
return service, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (service *Service) defaultExpireAt() (int64) {
|
||||||
|
return time.Now().Add(service.userSessionTimeout).Unix()
|
||||||
|
}
|
||||||
|
|
||||||
// GenerateToken generates a new JWT token.
|
// GenerateToken generates a new JWT token.
|
||||||
func (service *Service) GenerateToken(data *portainer.TokenData) (string, error) {
|
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
|
// GenerateTokenForOAuth generates a new JWT for OAuth login
|
||||||
// token expiry time from the OAuth provider is considered
|
// token expiry time from the OAuth provider is considered
|
||||||
func (service *Service) GenerateTokenForOAuth(data *portainer.TokenData, expiryTime *time.Time) (string, error) {
|
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.
|
// 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
|
service.userSessionTimeout = userSessionDuration
|
||||||
}
|
}
|
||||||
|
|
||||||
func (service *Service) generateSignedToken(data *portainer.TokenData, expiryTime *time.Time) (string, error) {
|
func (service *Service) generateSignedToken(data *portainer.TokenData, expiresAt int64) (string, error) {
|
||||||
expireToken := time.Now().Add(service.userSessionTimeout).Unix()
|
|
||||||
if expiryTime != nil && !expiryTime.IsZero() {
|
|
||||||
expireToken = expiryTime.Unix()
|
|
||||||
}
|
|
||||||
cl := claims{
|
cl := claims{
|
||||||
UserID: int(data.ID),
|
UserID: int(data.ID),
|
||||||
Username: data.Username,
|
Username: data.Username,
|
||||||
Role: int(data.Role),
|
Role: int(data.Role),
|
||||||
StandardClaims: jwt.StandardClaims{
|
StandardClaims: jwt.StandardClaims{
|
||||||
ExpiresAt: expireToken,
|
ExpiresAt: expiresAt,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, cl)
|
token := jwt.NewWithClaims(jwt.SigningMethodHS256, cl)
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
|
@ -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) {
|
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")
|
assert.NoError(t, err, "failed to create a copy of service")
|
||||||
|
|
||||||
token := &portainer.TokenData{
|
token := &portainer.TokenData{
|
||||||
|
@ -18,9 +18,9 @@ func TestGenerateSignedToken(t *testing.T) {
|
||||||
ID: 1,
|
ID: 1,
|
||||||
Role: 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")
|
assert.NoError(t, err, "failed to generate a signed token")
|
||||||
|
|
||||||
parsedToken, err := jwt.ParseWithClaims(generatedToken, &claims{}, func(token *jwt.Token) (interface{}, error) {
|
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, token.Username, tokenClaims.Username)
|
||||||
assert.Equal(t, int(token.ID), tokenClaims.UserID)
|
assert.Equal(t, int(token.ID), tokenClaims.UserID)
|
||||||
assert.Equal(t, int(token.Role), tokenClaims.Role)
|
assert.Equal(t, int(token.Role), tokenClaims.Role)
|
||||||
assert.Equal(t, expirtationTime.Unix(), tokenClaims.ExpiresAt)
|
assert.Equal(t, expiresAt, tokenClaims.ExpiresAt)
|
||||||
}
|
}
|
||||||
|
|
|
@ -689,6 +689,8 @@ type (
|
||||||
EnableEdgeComputeFeatures bool `json:"EnableEdgeComputeFeatures" example:""`
|
EnableEdgeComputeFeatures bool `json:"EnableEdgeComputeFeatures" example:""`
|
||||||
// The duration of a user session
|
// The duration of a user session
|
||||||
UserSessionTimeout string `json:"UserSessionTimeout" example:"5m"`
|
UserSessionTimeout string `json:"UserSessionTimeout" example:"5m"`
|
||||||
|
// The expiry of a Kubeconfig
|
||||||
|
KubeconfigExpiry string `json:"KubeconfigExpiry" example:"24h"`
|
||||||
// Whether telemetry is enabled
|
// Whether telemetry is enabled
|
||||||
EnableTelemetry bool `json:"EnableTelemetry" example:"false"`
|
EnableTelemetry bool `json:"EnableTelemetry" example:"false"`
|
||||||
|
|
||||||
|
@ -1215,6 +1217,7 @@ type (
|
||||||
JWTService interface {
|
JWTService interface {
|
||||||
GenerateToken(data *TokenData) (string, error)
|
GenerateToken(data *TokenData) (string, error)
|
||||||
GenerateTokenForOAuth(data *TokenData, expiryTime *time.Time) (string, error)
|
GenerateTokenForOAuth(data *TokenData, expiryTime *time.Time) (string, error)
|
||||||
|
GenerateTokenForKubeconfig(data *TokenData) (string, error)
|
||||||
ParseAndVerifyToken(token string) (*TokenData, error)
|
ParseAndVerifyToken(token string) (*TokenData, error)
|
||||||
SetUserSessionDuration(userSessionDuration time.Duration)
|
SetUserSessionDuration(userSessionDuration time.Duration)
|
||||||
}
|
}
|
||||||
|
@ -1452,6 +1455,8 @@ const (
|
||||||
DefaultTemplatesURL = "https://raw.githubusercontent.com/portainer/templates/master/templates-2.0.json"
|
DefaultTemplatesURL = "https://raw.githubusercontent.com/portainer/templates/master/templates-2.0.json"
|
||||||
// DefaultUserSessionTimeout represents the default timeout after which the user session is cleared
|
// DefaultUserSessionTimeout represents the default timeout after which the user session is cleared
|
||||||
DefaultUserSessionTimeout = "8h"
|
DefaultUserSessionTimeout = "8h"
|
||||||
|
// DefaultUserSessionTimeout represents the default timeout after which the user session is cleared
|
||||||
|
DefaultKubeconfigExpiry = "0"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
|
@ -10,6 +10,7 @@ export function SettingsViewModel(data) {
|
||||||
this.EnableEdgeComputeFeatures = data.EnableEdgeComputeFeatures;
|
this.EnableEdgeComputeFeatures = data.EnableEdgeComputeFeatures;
|
||||||
this.UserSessionTimeout = data.UserSessionTimeout;
|
this.UserSessionTimeout = data.UserSessionTimeout;
|
||||||
this.EnableTelemetry = data.EnableTelemetry;
|
this.EnableTelemetry = data.EnableTelemetry;
|
||||||
|
this.KubeconfigExpiry = data.KubeconfigExpiry;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function PublicSettingsViewModel(settings) {
|
export function PublicSettingsViewModel(settings) {
|
||||||
|
|
|
@ -118,6 +118,24 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- !edge -->
|
<!-- !edge -->
|
||||||
|
<!-- kube -->
|
||||||
|
<div class="col-sm-12 form-section-title">
|
||||||
|
Kubernetes
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="edge_checkin" class="col-sm-2 control-label text-left">
|
||||||
|
Kubeconfig expiry
|
||||||
|
</label>
|
||||||
|
<div class="col-sm-10">
|
||||||
|
<select
|
||||||
|
id="kubeconfig_expiry"
|
||||||
|
class="form-control"
|
||||||
|
ng-model="settings.KubeconfigExpiry"
|
||||||
|
ng-options="opt.value as opt.key for opt in state.availableKubeconfigExpiryOptions"
|
||||||
|
></select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- ! kube -->
|
||||||
<!-- actions -->
|
<!-- actions -->
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div class="col-sm-12">
|
<div class="col-sm-12">
|
||||||
|
|
|
@ -24,7 +24,28 @@ angular.module('portainer.app').controller('SettingsController', [
|
||||||
value: 30,
|
value: 30,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
availableKubeconfigExpiryOptions: [
|
||||||
|
{
|
||||||
|
key: '1 day',
|
||||||
|
value: '24h',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: '7 days',
|
||||||
|
value: `${24 * 7}h`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: '30 days',
|
||||||
|
value: `${24 * 30}h`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: '1 year',
|
||||||
|
value: `${24 * 30 * 12}h`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'No expiry',
|
||||||
|
value: '0',
|
||||||
|
},
|
||||||
|
],
|
||||||
backupInProgress: false,
|
backupInProgress: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue