mirror of
https://github.com/optim-enterprises-bv/kubernetes.git
synced 2025-11-02 03:08:15 +00:00
Merge pull request #129816 from sambdavidson/master
Improve SA max token expiry with external signer logic, and plumb extended expiry duration.
This commit is contained in:
@@ -223,7 +223,7 @@ func (p *legacyProvider) NewRESTStorage(apiResourceConfigSource serverstorage.AP
|
||||
utilfeature.DefaultFeatureGate.Enabled(features.ServiceAccountTokenPodNodeInfo) {
|
||||
nodeGetter = nodeStorage.Node.Store
|
||||
}
|
||||
serviceAccountStorage, err = serviceaccountstore.NewREST(restOptionsGetter, p.ServiceAccountIssuer, p.APIAudiences, p.ServiceAccountMaxExpiration, podStorage.Pod.Store, storage["secrets"].(rest.Getter), nodeGetter, p.ExtendExpiration, p.IsTokenSignerExternal)
|
||||
serviceAccountStorage, err = serviceaccountstore.NewREST(restOptionsGetter, p.ServiceAccountIssuer, p.APIAudiences, p.ServiceAccountMaxExpiration, podStorage.Pod.Store, storage["secrets"].(rest.Getter), nodeGetter, p.ExtendExpiration, p.MaxExtendedExpiration)
|
||||
if err != nil {
|
||||
return genericapiserver.APIGroupInfo{}, err
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ type GenericConfig struct {
|
||||
ServiceAccountIssuer serviceaccount.TokenGenerator
|
||||
ServiceAccountMaxExpiration time.Duration
|
||||
ExtendExpiration bool
|
||||
IsTokenSignerExternal bool
|
||||
MaxExtendedExpiration time.Duration
|
||||
|
||||
APIAudiences authenticator.Audiences
|
||||
|
||||
@@ -103,9 +103,9 @@ func (c *GenericConfig) NewRESTStorage(apiResourceConfigSource serverstorage.API
|
||||
|
||||
var serviceAccountStorage *serviceaccountstore.REST
|
||||
if c.ServiceAccountIssuer != nil {
|
||||
serviceAccountStorage, err = serviceaccountstore.NewREST(restOptionsGetter, c.ServiceAccountIssuer, c.APIAudiences, c.ServiceAccountMaxExpiration, newNotFoundGetter(schema.GroupResource{Resource: "pods"}), secretStorage.Store, newNotFoundGetter(schema.GroupResource{Resource: "nodes"}), c.ExtendExpiration, c.IsTokenSignerExternal)
|
||||
serviceAccountStorage, err = serviceaccountstore.NewREST(restOptionsGetter, c.ServiceAccountIssuer, c.APIAudiences, c.ServiceAccountMaxExpiration, newNotFoundGetter(schema.GroupResource{Resource: "pods"}), secretStorage.Store, newNotFoundGetter(schema.GroupResource{Resource: "nodes"}), c.ExtendExpiration, c.MaxExtendedExpiration)
|
||||
} else {
|
||||
serviceAccountStorage, err = serviceaccountstore.NewREST(restOptionsGetter, nil, nil, 0, newNotFoundGetter(schema.GroupResource{Resource: "pods"}), newNotFoundGetter(schema.GroupResource{Resource: "secrets"}), newNotFoundGetter(schema.GroupResource{Resource: "nodes"}), false, c.IsTokenSignerExternal)
|
||||
serviceAccountStorage, err = serviceaccountstore.NewREST(restOptionsGetter, nil, nil, 0, newNotFoundGetter(schema.GroupResource{Resource: "pods"}), newNotFoundGetter(schema.GroupResource{Resource: "secrets"}), newNotFoundGetter(schema.GroupResource{Resource: "nodes"}), false, c.MaxExtendedExpiration)
|
||||
}
|
||||
if err != nil {
|
||||
return genericapiserver.APIGroupInfo{}, err
|
||||
|
||||
@@ -39,7 +39,7 @@ type REST struct {
|
||||
}
|
||||
|
||||
// NewREST returns a RESTStorage object that will work against service accounts.
|
||||
func NewREST(optsGetter generic.RESTOptionsGetter, issuer token.TokenGenerator, auds authenticator.Audiences, max time.Duration, podStorage, secretStorage, nodeStorage rest.Getter, extendExpiration bool, isTokenSignerExternal bool) (*REST, error) {
|
||||
func NewREST(optsGetter generic.RESTOptionsGetter, issuer token.TokenGenerator, auds authenticator.Audiences, max time.Duration, podStorage, secretStorage, nodeStorage rest.Getter, extendExpiration bool, maxExtendedExpiration time.Duration) (*REST, error) {
|
||||
store := &genericregistry.Store{
|
||||
NewFunc: func() runtime.Object { return &api.ServiceAccount{} },
|
||||
NewListFunc: func() runtime.Object { return &api.ServiceAccountList{} },
|
||||
@@ -61,16 +61,16 @@ func NewREST(optsGetter generic.RESTOptionsGetter, issuer token.TokenGenerator,
|
||||
var trest *TokenREST
|
||||
if issuer != nil && podStorage != nil && secretStorage != nil {
|
||||
trest = &TokenREST{
|
||||
svcaccts: store,
|
||||
pods: podStorage,
|
||||
secrets: secretStorage,
|
||||
nodes: nodeStorage,
|
||||
issuer: issuer,
|
||||
auds: auds,
|
||||
audsSet: sets.NewString(auds...),
|
||||
maxExpirationSeconds: int64(max.Seconds()),
|
||||
extendExpiration: extendExpiration,
|
||||
isTokenSignerExternal: isTokenSignerExternal,
|
||||
svcaccts: store,
|
||||
pods: podStorage,
|
||||
secrets: secretStorage,
|
||||
nodes: nodeStorage,
|
||||
issuer: issuer,
|
||||
auds: auds,
|
||||
audsSet: sets.NewString(auds...),
|
||||
maxExpirationSeconds: int64(max.Seconds()),
|
||||
maxExtendedExpirationSeconds: int64(maxExtendedExpiration.Seconds()),
|
||||
extendExpiration: extendExpiration,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ package storage
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"gopkg.in/go-jose/go-jose.v2/jwt"
|
||||
|
||||
@@ -55,7 +56,7 @@ func newTokenStorage(t *testing.T, issuer token.TokenGenerator, auds authenticat
|
||||
ResourcePrefix: "serviceaccounts",
|
||||
}
|
||||
// set issuer, podStore and secretStore to allow the token endpoint to be initialised
|
||||
rest, err := NewREST(restOptions, issuer, auds, 0, podStorage, secretStorage, nodeStorage, false, false)
|
||||
rest, err := NewREST(restOptions, issuer, auds, 0, podStorage, secretStorage, nodeStorage, false, time.Hour*9999)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error from REST storage: %v", err)
|
||||
}
|
||||
|
||||
@@ -56,16 +56,16 @@ func (r *TokenREST) Destroy() {
|
||||
}
|
||||
|
||||
type TokenREST struct {
|
||||
svcaccts rest.Getter
|
||||
pods rest.Getter
|
||||
secrets rest.Getter
|
||||
nodes rest.Getter
|
||||
issuer token.TokenGenerator
|
||||
auds authenticator.Audiences
|
||||
audsSet sets.String
|
||||
maxExpirationSeconds int64
|
||||
extendExpiration bool
|
||||
isTokenSignerExternal bool
|
||||
svcaccts rest.Getter
|
||||
pods rest.Getter
|
||||
secrets rest.Getter
|
||||
nodes rest.Getter
|
||||
issuer token.TokenGenerator
|
||||
auds authenticator.Audiences
|
||||
audsSet sets.String
|
||||
maxExpirationSeconds int64
|
||||
extendExpiration bool
|
||||
maxExtendedExpirationSeconds int64
|
||||
}
|
||||
|
||||
var _ = rest.NamedCreater(&TokenREST{})
|
||||
@@ -218,13 +218,7 @@ func (r *TokenREST) Create(ctx context.Context, name string, obj runtime.Object,
|
||||
exp := req.Spec.ExpirationSeconds
|
||||
if r.extendExpiration && pod != nil && req.Spec.ExpirationSeconds == token.WarnOnlyBoundTokenExpirationSeconds && r.isKubeAudiences(req.Spec.Audiences) {
|
||||
warnAfter = exp
|
||||
// If token issuer is external-jwt-signer, then choose the smaller of
|
||||
// ExpirationExtensionSeconds and max token lifetime supported by external signer.
|
||||
if r.isTokenSignerExternal {
|
||||
exp = min(r.maxExpirationSeconds, token.ExpirationExtensionSeconds)
|
||||
} else {
|
||||
exp = token.ExpirationExtensionSeconds
|
||||
}
|
||||
exp = r.maxExtendedExpirationSeconds
|
||||
}
|
||||
|
||||
sc, pc, err := token.Claims(*svcacct, pod, secret, node, exp, warnAfter, req.Spec.Audiences)
|
||||
|
||||
@@ -42,46 +42,53 @@ import (
|
||||
func TestCreate_Token_WithExpiryCap(t *testing.T) {
|
||||
|
||||
testcases := []struct {
|
||||
desc string
|
||||
extendExpiration bool
|
||||
maxExpirationSeconds int
|
||||
expectedTokenAgeSec int
|
||||
isExternal bool
|
||||
desc string
|
||||
extendExpiration bool
|
||||
maxExpirationSeconds int
|
||||
maxExtendedExpirationSeconds int
|
||||
expectedTokenAgeSec int
|
||||
}{
|
||||
{
|
||||
desc: "maxExpirationSeconds honoured",
|
||||
extendExpiration: true,
|
||||
maxExpirationSeconds: 5 * 60 * 60, // 5h
|
||||
expectedTokenAgeSec: 5 * 60 * 60, // 5h
|
||||
isExternal: true,
|
||||
desc: "passed expiration respected if less than max",
|
||||
extendExpiration: false,
|
||||
maxExpirationSeconds: 5 * 60 * 60, // 5h
|
||||
maxExtendedExpirationSeconds: token.ExpirationExtensionSeconds, // 1y
|
||||
expectedTokenAgeSec: token.WarnOnlyBoundTokenExpirationSeconds, // 1h 7s
|
||||
},
|
||||
{
|
||||
desc: "ExpirationExtensionSeconds used for exp",
|
||||
extendExpiration: true,
|
||||
maxExpirationSeconds: 2 * 365 * 24 * 60 * 60, // 2 years
|
||||
expectedTokenAgeSec: token.ExpirationExtensionSeconds, // 1y
|
||||
isExternal: true,
|
||||
desc: "maxExtendedExpirationSeconds honoured",
|
||||
extendExpiration: true,
|
||||
maxExpirationSeconds: 2 * 60 * 60, // 2h
|
||||
maxExtendedExpirationSeconds: 5 * 60 * 60, // 5h
|
||||
expectedTokenAgeSec: 5 * 60 * 60, // 5h
|
||||
},
|
||||
{
|
||||
desc: "ExpirationExtensionSeconds used for exp",
|
||||
extendExpiration: true,
|
||||
maxExpirationSeconds: 5 * 60 * 60, // 5h
|
||||
expectedTokenAgeSec: token.ExpirationExtensionSeconds, // 1y
|
||||
isExternal: false,
|
||||
desc: "ExpirationExtensionSeconds used for exp",
|
||||
extendExpiration: true,
|
||||
maxExpirationSeconds: 2 * 365 * 24 * 60 * 60, // 2y
|
||||
maxExtendedExpirationSeconds: token.ExpirationExtensionSeconds, // 1y
|
||||
expectedTokenAgeSec: token.ExpirationExtensionSeconds, // 1y
|
||||
},
|
||||
{
|
||||
desc: "requested time use with extension disabled",
|
||||
extendExpiration: false,
|
||||
maxExpirationSeconds: 5 * 60 * 60, // 5h
|
||||
expectedTokenAgeSec: 3607, // 1h
|
||||
isExternal: true,
|
||||
desc: "ExpirationSeconds used for exp",
|
||||
extendExpiration: true,
|
||||
maxExpirationSeconds: 5 * 60 * 60, // 5h
|
||||
maxExtendedExpirationSeconds: token.ExpirationExtensionSeconds, // 1y
|
||||
expectedTokenAgeSec: token.ExpirationExtensionSeconds, // 1y
|
||||
},
|
||||
{
|
||||
desc: "maxExpirationSeconds honoured with extension disabled",
|
||||
extendExpiration: false,
|
||||
maxExpirationSeconds: 30 * 60, // 30m
|
||||
expectedTokenAgeSec: 30 * 60, // 30m
|
||||
isExternal: true,
|
||||
desc: "requested time use with extension disabled",
|
||||
extendExpiration: false,
|
||||
maxExpirationSeconds: 5 * 60 * 60, // 5h
|
||||
expectedTokenAgeSec: 3607, // 1h
|
||||
maxExtendedExpirationSeconds: token.ExpirationExtensionSeconds,
|
||||
},
|
||||
{
|
||||
desc: "maxExpirationSeconds honoured with extension disabled",
|
||||
extendExpiration: false,
|
||||
maxExpirationSeconds: 30 * 60, // 30m
|
||||
expectedTokenAgeSec: 30 * 60, // 30m
|
||||
maxExtendedExpirationSeconds: token.ExpirationExtensionSeconds,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -127,7 +134,7 @@ func TestCreate_Token_WithExpiryCap(t *testing.T) {
|
||||
ctx = request.WithNamespace(ctx, serviceAccount.Namespace)
|
||||
storage.Token.extendExpiration = tc.extendExpiration
|
||||
storage.Token.maxExpirationSeconds = int64(tc.maxExpirationSeconds)
|
||||
storage.Token.isTokenSignerExternal = tc.isExternal
|
||||
storage.Token.maxExtendedExpirationSeconds = int64(tc.maxExtendedExpirationSeconds)
|
||||
|
||||
tokenReqTimeStamp := time.Now()
|
||||
out, err := storage.Token.Create(ctx, serviceAccount.Name, &authenticationapi.TokenRequest{
|
||||
@@ -161,13 +168,15 @@ func TestCreate_Token_WithExpiryCap(t *testing.T) {
|
||||
t.Fatalf("Error unmarshalling Claims: %v", err)
|
||||
}
|
||||
structuredClaim.Expiry.Time()
|
||||
upperBound := tokenReqTimeStamp.Add(time.Duration(tc.expectedTokenAgeSec+10) * time.Second)
|
||||
lowerBound := tokenReqTimeStamp.Add(time.Duration(tc.expectedTokenAgeSec-10) * time.Second)
|
||||
confidenceInterval := 10 // seconds
|
||||
upperBound := tokenReqTimeStamp.Add(time.Duration(tc.expectedTokenAgeSec+confidenceInterval) * time.Second)
|
||||
lowerBound := tokenReqTimeStamp.Add(time.Duration(tc.expectedTokenAgeSec-confidenceInterval) * time.Second)
|
||||
|
||||
// check for token expiration with a toleration of +/-10s after tokenReqTimeStamp to make for latencies.
|
||||
if structuredClaim.Expiry.Time().After(upperBound) ||
|
||||
structuredClaim.Expiry.Time().Before(lowerBound) {
|
||||
t.Fatalf("expected token expiration to be between %v to %v\n was %v", upperBound, lowerBound, structuredClaim.Expiry.Time())
|
||||
expiryDiff := structuredClaim.Expiry.Time().Sub(tokenReqTimeStamp)
|
||||
t.Fatalf("expected token expiration to be %v (±%ds) in the future, was %v", time.Duration(tc.expectedTokenAgeSec)*time.Second, confidenceInterval, expiryDiff)
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user