mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-07 03:03:59 +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:
commit
76506f1d87
@ -44,6 +44,7 @@ import (
|
|||||||
"k8s.io/kubernetes/pkg/controlplane/reconcilers"
|
"k8s.io/kubernetes/pkg/controlplane/reconcilers"
|
||||||
kubeoptions "k8s.io/kubernetes/pkg/kubeapiserver/options"
|
kubeoptions "k8s.io/kubernetes/pkg/kubeapiserver/options"
|
||||||
kubeletclient "k8s.io/kubernetes/pkg/kubelet/client"
|
kubeletclient "k8s.io/kubernetes/pkg/kubelet/client"
|
||||||
|
"k8s.io/kubernetes/pkg/serviceaccount"
|
||||||
netutils "k8s.io/utils/net"
|
netutils "k8s.io/utils/net"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -272,8 +273,9 @@ func TestAddFlags(t *testing.T) {
|
|||||||
OIDC: s.Authentication.OIDC,
|
OIDC: s.Authentication.OIDC,
|
||||||
RequestHeader: &apiserveroptions.RequestHeaderAuthenticationOptions{},
|
RequestHeader: &apiserveroptions.RequestHeaderAuthenticationOptions{},
|
||||||
ServiceAccounts: &kubeoptions.ServiceAccountAuthenticationOptions{
|
ServiceAccounts: &kubeoptions.ServiceAccountAuthenticationOptions{
|
||||||
Lookup: true,
|
Lookup: true,
|
||||||
ExtendExpiration: true,
|
ExtendExpiration: true,
|
||||||
|
MaxExtendedExpiration: serviceaccount.ExpirationExtensionSeconds * time.Second,
|
||||||
},
|
},
|
||||||
TokenFile: &kubeoptions.TokenFileAuthenticationOptions{},
|
TokenFile: &kubeoptions.TokenFileAuthenticationOptions{},
|
||||||
TokenSuccessCacheTTL: 10 * time.Second,
|
TokenSuccessCacheTTL: 10 * time.Second,
|
||||||
@ -348,7 +350,7 @@ func TestAddFlags(t *testing.T) {
|
|||||||
s.Authorization.AreLegacyFlagsSet = nil
|
s.Authorization.AreLegacyFlagsSet = nil
|
||||||
|
|
||||||
if !reflect.DeepEqual(expected, s) {
|
if !reflect.DeepEqual(expected, s) {
|
||||||
t.Errorf("Got different run options than expected.\nDifference detected on:\n%s", cmp.Diff(expected, s, cmpopts.IgnoreUnexported(admission.Plugins{}, kubeoptions.OIDCAuthenticationOptions{}, kubeoptions.AnonymousAuthenticationOptions{})))
|
t.Errorf("Got different run options than expected.\nDifference detected on:\n%s", cmp.Diff(expected, s, cmpopts.IgnoreFields(apiserveroptions.ServerRunOptions{}, "ComponentGlobalsRegistry"), cmpopts.IgnoreUnexported(admission.Plugins{}, kubeoptions.OIDCAuthenticationOptions{}, kubeoptions.AnonymousAuthenticationOptions{})))
|
||||||
}
|
}
|
||||||
testEffectiveVersion := s.GenericServerRunOptions.ComponentGlobalsRegistry.EffectiveVersionFor("test")
|
testEffectiveVersion := s.GenericServerRunOptions.ComponentGlobalsRegistry.EffectiveVersionFor("test")
|
||||||
if testEffectiveVersion.EmulationVersion().String() != "1.31" {
|
if testEffectiveVersion.EmulationVersion().String() != "1.31" {
|
||||||
|
@ -52,8 +52,8 @@ func (c *CompletedConfig) NewCoreGenericConfig() *corerest.GenericConfig {
|
|||||||
LoopbackClientConfig: c.Generic.LoopbackClientConfig,
|
LoopbackClientConfig: c.Generic.LoopbackClientConfig,
|
||||||
ServiceAccountIssuer: c.Extra.ServiceAccountIssuer,
|
ServiceAccountIssuer: c.Extra.ServiceAccountIssuer,
|
||||||
ExtendExpiration: c.Extra.ExtendExpiration,
|
ExtendExpiration: c.Extra.ExtendExpiration,
|
||||||
IsTokenSignerExternal: c.Extra.IsTokenSignerExternal,
|
|
||||||
ServiceAccountMaxExpiration: c.Extra.ServiceAccountMaxExpiration,
|
ServiceAccountMaxExpiration: c.Extra.ServiceAccountMaxExpiration,
|
||||||
|
MaxExtendedExpiration: c.Extra.ServiceAccountExtendedMaxExpiration,
|
||||||
APIAudiences: c.Generic.Authentication.APIAudiences,
|
APIAudiences: c.Generic.Authentication.APIAudiences,
|
||||||
Informers: c.Extra.VersionedInformers,
|
Informers: c.Extra.VersionedInformers,
|
||||||
}
|
}
|
||||||
|
@ -89,10 +89,10 @@ type Extra struct {
|
|||||||
// version skew. If unset, AdvertiseAddress/BindAddress will be used.
|
// version skew. If unset, AdvertiseAddress/BindAddress will be used.
|
||||||
PeerAdvertiseAddress peerreconcilers.PeerAdvertiseAddress
|
PeerAdvertiseAddress peerreconcilers.PeerAdvertiseAddress
|
||||||
|
|
||||||
ServiceAccountIssuer serviceaccount.TokenGenerator
|
ServiceAccountIssuer serviceaccount.TokenGenerator
|
||||||
ServiceAccountMaxExpiration time.Duration
|
ServiceAccountMaxExpiration time.Duration
|
||||||
ExtendExpiration bool
|
ServiceAccountExtendedMaxExpiration time.Duration
|
||||||
IsTokenSignerExternal bool
|
ExtendExpiration bool
|
||||||
|
|
||||||
// ServiceAccountIssuerDiscovery
|
// ServiceAccountIssuerDiscovery
|
||||||
ServiceAccountIssuerURL string
|
ServiceAccountIssuerURL string
|
||||||
@ -299,10 +299,10 @@ func CreateConfig(
|
|||||||
ProxyTransport: proxyTransport,
|
ProxyTransport: proxyTransport,
|
||||||
SystemNamespaces: opts.SystemNamespaces,
|
SystemNamespaces: opts.SystemNamespaces,
|
||||||
|
|
||||||
ServiceAccountIssuer: opts.ServiceAccountIssuer,
|
ServiceAccountIssuer: opts.ServiceAccountIssuer,
|
||||||
ServiceAccountMaxExpiration: opts.ServiceAccountTokenMaxExpiration,
|
ServiceAccountMaxExpiration: opts.ServiceAccountTokenMaxExpiration,
|
||||||
ExtendExpiration: opts.Authentication.ServiceAccounts.ExtendExpiration,
|
ServiceAccountExtendedMaxExpiration: opts.Authentication.ServiceAccounts.MaxExtendedExpiration,
|
||||||
IsTokenSignerExternal: opts.Authentication.ServiceAccounts.IsTokenSignerExternal,
|
ExtendExpiration: opts.Authentication.ServiceAccounts.ExtendExpiration,
|
||||||
|
|
||||||
VersionedInformers: versionedInformers,
|
VersionedInformers: versionedInformers,
|
||||||
},
|
},
|
||||||
|
@ -316,15 +316,19 @@ func (o *Options) completeServiceAccountOptions(ctx context.Context, completed *
|
|||||||
if metadata.MaxTokenExpirationSeconds < validation.MinTokenAgeSec {
|
if metadata.MaxTokenExpirationSeconds < validation.MinTokenAgeSec {
|
||||||
return fmt.Errorf("max token life supported by external-jwt-signer (%ds) is less than acceptable (min %ds)", metadata.MaxTokenExpirationSeconds, validation.MinTokenAgeSec)
|
return fmt.Errorf("max token life supported by external-jwt-signer (%ds) is less than acceptable (min %ds)", metadata.MaxTokenExpirationSeconds, validation.MinTokenAgeSec)
|
||||||
}
|
}
|
||||||
if completed.Authentication.ServiceAccounts.MaxExpiration != 0 {
|
maxExternalExpiration := time.Duration(metadata.MaxTokenExpirationSeconds) * time.Second
|
||||||
return fmt.Errorf("service-account-max-token-expiration and service-account-signing-endpoint are mutually exclusive and cannot be set at the same time")
|
switch {
|
||||||
|
case completed.Authentication.ServiceAccounts.MaxExpiration == 0:
|
||||||
|
completed.Authentication.ServiceAccounts.MaxExpiration = maxExternalExpiration
|
||||||
|
case completed.Authentication.ServiceAccounts.MaxExpiration > maxExternalExpiration:
|
||||||
|
return fmt.Errorf("service-account-max-token-expiration cannot be set longer than the token expiration supported by service-account-signing-endpoint: %s > %s", completed.Authentication.ServiceAccounts.MaxExpiration, maxExternalExpiration)
|
||||||
}
|
}
|
||||||
transitionWarningFmt = "service-account-extend-token-expiration is true, in order to correctly trigger safe transition logic, token lifetime supported by external-jwt-signer must be longer than %d seconds (currently %s)"
|
transitionWarningFmt = "service-account-extend-token-expiration is true, in order to correctly trigger safe transition logic, token lifetime supported by external-jwt-signer must be longer than %d seconds (currently %s)"
|
||||||
expExtensionWarningFmt = "service-account-extend-token-expiration is true, tokens validity will be caped at the smaller of %d seconds and maximum token lifetime supported by external-jwt-signer (%s)"
|
expExtensionWarningFmt = "service-account-extend-token-expiration is true, tokens validity will be caped at the smaller of %d seconds and maximum token lifetime supported by external-jwt-signer (%s)"
|
||||||
completed.ServiceAccountIssuer = plugin
|
completed.ServiceAccountIssuer = plugin
|
||||||
completed.Authentication.ServiceAccounts.ExternalPublicKeysGetter = cache
|
completed.Authentication.ServiceAccounts.ExternalPublicKeysGetter = cache
|
||||||
completed.Authentication.ServiceAccounts.MaxExpiration = time.Duration(metadata.MaxTokenExpirationSeconds) * time.Second
|
// shorten ExtendedExpiration, if needed, to fit within the external signer's max expiration
|
||||||
completed.Authentication.ServiceAccounts.IsTokenSignerExternal = true
|
completed.Authentication.ServiceAccounts.MaxExtendedExpiration = min(maxExternalExpiration, completed.Authentication.ServiceAccounts.MaxExtendedExpiration)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,7 +32,6 @@ import (
|
|||||||
"github.com/google/go-cmp/cmp/cmpopts"
|
"github.com/google/go-cmp/cmp/cmpopts"
|
||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
noopoteltrace "go.opentelemetry.io/otel/trace/noop"
|
noopoteltrace "go.opentelemetry.io/otel/trace/noop"
|
||||||
|
|
||||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||||
"k8s.io/apiserver/pkg/admission"
|
"k8s.io/apiserver/pkg/admission"
|
||||||
apiserveroptions "k8s.io/apiserver/pkg/server/options"
|
apiserveroptions "k8s.io/apiserver/pkg/server/options"
|
||||||
@ -46,6 +45,7 @@ import (
|
|||||||
"k8s.io/component-base/metrics"
|
"k8s.io/component-base/metrics"
|
||||||
utilversion "k8s.io/component-base/version"
|
utilversion "k8s.io/component-base/version"
|
||||||
kubeoptions "k8s.io/kubernetes/pkg/kubeapiserver/options"
|
kubeoptions "k8s.io/kubernetes/pkg/kubeapiserver/options"
|
||||||
|
"k8s.io/kubernetes/pkg/serviceaccount"
|
||||||
v1alpha1testing "k8s.io/kubernetes/pkg/serviceaccount/externaljwt/plugin/testing/v1alpha1"
|
v1alpha1testing "k8s.io/kubernetes/pkg/serviceaccount/externaljwt/plugin/testing/v1alpha1"
|
||||||
netutils "k8s.io/utils/net"
|
netutils "k8s.io/utils/net"
|
||||||
)
|
)
|
||||||
@ -264,8 +264,9 @@ func TestAddFlags(t *testing.T) {
|
|||||||
OIDC: s.Authentication.OIDC,
|
OIDC: s.Authentication.OIDC,
|
||||||
RequestHeader: &apiserveroptions.RequestHeaderAuthenticationOptions{},
|
RequestHeader: &apiserveroptions.RequestHeaderAuthenticationOptions{},
|
||||||
ServiceAccounts: &kubeoptions.ServiceAccountAuthenticationOptions{
|
ServiceAccounts: &kubeoptions.ServiceAccountAuthenticationOptions{
|
||||||
Lookup: true,
|
Lookup: true,
|
||||||
ExtendExpiration: true,
|
ExtendExpiration: true,
|
||||||
|
MaxExtendedExpiration: serviceaccount.ExpirationExtensionSeconds * time.Second,
|
||||||
},
|
},
|
||||||
TokenFile: &kubeoptions.TokenFileAuthenticationOptions{},
|
TokenFile: &kubeoptions.TokenFileAuthenticationOptions{},
|
||||||
TokenSuccessCacheTTL: 10 * time.Second,
|
TokenSuccessCacheTTL: 10 * time.Second,
|
||||||
@ -309,7 +310,7 @@ func TestAddFlags(t *testing.T) {
|
|||||||
s.Authorization.AreLegacyFlagsSet = nil
|
s.Authorization.AreLegacyFlagsSet = nil
|
||||||
|
|
||||||
if !reflect.DeepEqual(expected, s) {
|
if !reflect.DeepEqual(expected, s) {
|
||||||
t.Errorf("Got different run options than expected.\nDifference detected on:\n%s", cmp.Diff(expected, s, cmpopts.IgnoreUnexported(admission.Plugins{}, kubeoptions.OIDCAuthenticationOptions{}, kubeoptions.AnonymousAuthenticationOptions{})))
|
t.Errorf("Got different run options than expected.\nDifference detected on:\n%s", cmp.Diff(expected, s, cmpopts.IgnoreFields(apiserveroptions.ServerRunOptions{}, "ComponentGlobalsRegistry"), cmpopts.IgnoreUnexported(admission.Plugins{}, kubeoptions.OIDCAuthenticationOptions{}, kubeoptions.AnonymousAuthenticationOptions{})))
|
||||||
}
|
}
|
||||||
|
|
||||||
testEffectiveVersion := s.GenericServerRunOptions.ComponentGlobalsRegistry.EffectiveVersionFor("test")
|
testEffectiveVersion := s.GenericServerRunOptions.ComponentGlobalsRegistry.EffectiveVersionFor("test")
|
||||||
@ -352,13 +353,14 @@ func TestCompleteForServiceAccount(t *testing.T) {
|
|||||||
externalSigner bool
|
externalSigner bool
|
||||||
signingKeyFiles string
|
signingKeyFiles string
|
||||||
maxExpiration time.Duration
|
maxExpiration time.Duration
|
||||||
|
maxExtendedExpiration time.Duration
|
||||||
externalMaxExpirationSec int64
|
externalMaxExpirationSec int64
|
||||||
fetchError error
|
fetchError error
|
||||||
metadataError error
|
metadataError error
|
||||||
|
|
||||||
wantError error
|
wantError error
|
||||||
expectedMaxtokenExp time.Duration
|
expectedMaxtokenExp time.Duration
|
||||||
expectedIsExternalSigner bool
|
expectedExtendedMaxTokenExp time.Duration
|
||||||
externalPublicKeyGetterPresent bool
|
externalPublicKeyGetterPresent bool
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
@ -373,7 +375,7 @@ func TestCompleteForServiceAccount(t *testing.T) {
|
|||||||
wantError: fmt.Errorf("service-account-signing-key-file and service-account-signing-endpoint are mutually exclusive and cannot be set at the same time"),
|
wantError: fmt.Errorf("service-account-signing-key-file and service-account-signing-endpoint are mutually exclusive and cannot be set at the same time"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "max token expiration breaching accepteable values",
|
desc: "max token expiration breaching acceptable values",
|
||||||
issuers: []string{
|
issuers: []string{
|
||||||
"iss",
|
"iss",
|
||||||
},
|
},
|
||||||
@ -392,35 +394,50 @@ func TestCompleteForServiceAccount(t *testing.T) {
|
|||||||
signingKeyFiles: "private_key.pem",
|
signingKeyFiles: "private_key.pem",
|
||||||
maxExpiration: time.Second * 3600,
|
maxExpiration: time.Second * 3600,
|
||||||
|
|
||||||
expectedIsExternalSigner: false,
|
|
||||||
externalPublicKeyGetterPresent: false,
|
externalPublicKeyGetterPresent: false,
|
||||||
expectedMaxtokenExp: time.Second * 3600,
|
expectedMaxtokenExp: time.Second * 3600,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "signing endpoint provided",
|
desc: "signing endpoint provided, use endpoint expiration",
|
||||||
issuers: []string{
|
issuers: []string{
|
||||||
"iss",
|
"iss",
|
||||||
},
|
},
|
||||||
externalSigner: true,
|
externalSigner: true,
|
||||||
signingKeyFiles: "",
|
signingKeyFiles: "",
|
||||||
maxExpiration: 0,
|
maxExpiration: 0,
|
||||||
|
maxExtendedExpiration: 365 * 24 * time.Hour,
|
||||||
externalMaxExpirationSec: 600, // 10m
|
externalMaxExpirationSec: 600, // 10m
|
||||||
|
|
||||||
expectedIsExternalSigner: true,
|
expectedMaxtokenExp: 10 * time.Minute,
|
||||||
|
expectedExtendedMaxTokenExp: 10 * time.Minute,
|
||||||
externalPublicKeyGetterPresent: true,
|
externalPublicKeyGetterPresent: true,
|
||||||
expectedMaxtokenExp: time.Second * 600, // 10m
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "signing endpoint provided and max token expiration set",
|
desc: "signing endpoint provided, use local smaller expirations",
|
||||||
issuers: []string{
|
issuers: []string{
|
||||||
"iss",
|
"iss",
|
||||||
},
|
},
|
||||||
externalSigner: true,
|
externalSigner: true,
|
||||||
signingKeyFiles: "",
|
signingKeyFiles: "",
|
||||||
maxExpiration: time.Second * 3600,
|
maxExpiration: 1 * time.Hour,
|
||||||
externalMaxExpirationSec: 600, // 10m
|
maxExtendedExpiration: 24 * time.Hour,
|
||||||
|
externalMaxExpirationSec: 31556952, // 1 year
|
||||||
|
|
||||||
wantError: fmt.Errorf("service-account-max-token-expiration and service-account-signing-endpoint are mutually exclusive and cannot be set at the same time"),
|
expectedMaxtokenExp: 1 * time.Hour,
|
||||||
|
expectedExtendedMaxTokenExp: 24 * time.Hour,
|
||||||
|
externalPublicKeyGetterPresent: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "signing endpoint provided and want larger than signer can provide",
|
||||||
|
issuers: []string{
|
||||||
|
"iss",
|
||||||
|
},
|
||||||
|
externalSigner: true,
|
||||||
|
signingKeyFiles: "",
|
||||||
|
maxExpiration: 1 * time.Hour, // want 1hr
|
||||||
|
externalMaxExpirationSec: 600, // signer can only sign 10m
|
||||||
|
|
||||||
|
wantError: fmt.Errorf("service-account-max-token-expiration cannot be set longer than the token expiration supported by service-account-signing-endpoint: 1h0m0s > 10m0s"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "signing endpoint provided but return smaller than accaptable max token exp",
|
desc: "signing endpoint provided but return smaller than accaptable max token exp",
|
||||||
@ -481,8 +498,9 @@ func TestCompleteForServiceAccount(t *testing.T) {
|
|||||||
options.ServiceAccountSigningKeyFile = tc.signingKeyFiles
|
options.ServiceAccountSigningKeyFile = tc.signingKeyFiles
|
||||||
options.Authentication = &kubeoptions.BuiltInAuthenticationOptions{
|
options.Authentication = &kubeoptions.BuiltInAuthenticationOptions{
|
||||||
ServiceAccounts: &kubeoptions.ServiceAccountAuthenticationOptions{
|
ServiceAccounts: &kubeoptions.ServiceAccountAuthenticationOptions{
|
||||||
Issuers: tc.issuers,
|
Issuers: tc.issuers,
|
||||||
MaxExpiration: tc.maxExpiration,
|
MaxExpiration: tc.maxExpiration,
|
||||||
|
MaxExtendedExpiration: tc.maxExtendedExpiration,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -507,8 +525,8 @@ func TestCompleteForServiceAccount(t *testing.T) {
|
|||||||
if tc.externalPublicKeyGetterPresent != (co.Authentication.ServiceAccounts.ExternalPublicKeysGetter != nil) {
|
if tc.externalPublicKeyGetterPresent != (co.Authentication.ServiceAccounts.ExternalPublicKeysGetter != nil) {
|
||||||
t.Errorf("Unexpected value of ExternalPublicKeysGetter: %v", co.Authentication.ServiceAccounts.ExternalPublicKeysGetter)
|
t.Errorf("Unexpected value of ExternalPublicKeysGetter: %v", co.Authentication.ServiceAccounts.ExternalPublicKeysGetter)
|
||||||
}
|
}
|
||||||
if tc.expectedIsExternalSigner != co.Authentication.ServiceAccounts.IsTokenSignerExternal {
|
if tc.expectedExtendedMaxTokenExp != co.Authentication.ServiceAccounts.MaxExtendedExpiration {
|
||||||
t.Errorf("Expected IsTokenSignerExternal %v, found %v", tc.expectedIsExternalSigner, co.Authentication.ServiceAccounts.IsTokenSignerExternal)
|
t.Errorf("Expected MaxExtendedExpiration %v, found %v", tc.expectedExtendedMaxTokenExp, co.Authentication.ServiceAccounts.MaxExtendedExpiration)
|
||||||
}
|
}
|
||||||
if tc.expectedMaxtokenExp.Seconds() != co.Authentication.ServiceAccounts.MaxExpiration.Seconds() {
|
if tc.expectedMaxtokenExp.Seconds() != co.Authentication.ServiceAccounts.MaxExpiration.Seconds() {
|
||||||
t.Errorf("Expected MaxExpiration to be %v, found %v", tc.expectedMaxtokenExp, co.Authentication.ServiceAccounts.MaxExpiration)
|
t.Errorf("Expected MaxExpiration to be %v, found %v", tc.expectedMaxtokenExp, co.Authentication.ServiceAccounts.MaxExpiration)
|
||||||
|
@ -131,9 +131,9 @@ type ServiceAccountAuthenticationOptions struct {
|
|||||||
Lookup bool
|
Lookup bool
|
||||||
Issuers []string
|
Issuers []string
|
||||||
JWKSURI string
|
JWKSURI string
|
||||||
MaxExpiration time.Duration
|
|
||||||
ExtendExpiration bool
|
ExtendExpiration bool
|
||||||
IsTokenSignerExternal bool
|
MaxExpiration time.Duration
|
||||||
|
MaxExtendedExpiration time.Duration
|
||||||
// OptionalTokenGetter is a function that returns a service account token getter.
|
// OptionalTokenGetter is a function that returns a service account token getter.
|
||||||
// If not set, the default token getter will be used.
|
// If not set, the default token getter will be used.
|
||||||
OptionalTokenGetter func(factory informers.SharedInformerFactory) serviceaccount.ServiceAccountTokenGetter
|
OptionalTokenGetter func(factory informers.SharedInformerFactory) serviceaccount.ServiceAccountTokenGetter
|
||||||
@ -224,6 +224,7 @@ func (o *BuiltInAuthenticationOptions) WithServiceAccounts() *BuiltInAuthenticat
|
|||||||
}
|
}
|
||||||
o.ServiceAccounts.Lookup = true
|
o.ServiceAccounts.Lookup = true
|
||||||
o.ServiceAccounts.ExtendExpiration = true
|
o.ServiceAccounts.ExtendExpiration = true
|
||||||
|
o.ServiceAccounts.MaxExtendedExpiration = serviceaccount.ExpirationExtensionSeconds * time.Second
|
||||||
return o
|
return o
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -437,11 +437,12 @@ func TestBuiltInAuthenticationOptionsAddFlags(t *testing.T) {
|
|||||||
AllowedNames: []string{"kube-aggregator"},
|
AllowedNames: []string{"kube-aggregator"},
|
||||||
},
|
},
|
||||||
ServiceAccounts: &ServiceAccountAuthenticationOptions{
|
ServiceAccounts: &ServiceAccountAuthenticationOptions{
|
||||||
KeyFiles: []string{"cert", "key"},
|
KeyFiles: []string{"cert", "key"},
|
||||||
Lookup: true,
|
Lookup: true,
|
||||||
Issuers: []string{"http://foo.bar.com"},
|
Issuers: []string{"http://foo.bar.com"},
|
||||||
JWKSURI: "https://qux.com",
|
JWKSURI: "https://qux.com",
|
||||||
ExtendExpiration: true,
|
ExtendExpiration: true,
|
||||||
|
MaxExtendedExpiration: serviceaccount.ExpirationExtensionSeconds * time.Second,
|
||||||
},
|
},
|
||||||
TokenFile: &TokenFileAuthenticationOptions{
|
TokenFile: &TokenFileAuthenticationOptions{
|
||||||
TokenFile: "tokenfile",
|
TokenFile: "tokenfile",
|
||||||
|
@ -223,7 +223,7 @@ func (p *legacyProvider) NewRESTStorage(apiResourceConfigSource serverstorage.AP
|
|||||||
utilfeature.DefaultFeatureGate.Enabled(features.ServiceAccountTokenPodNodeInfo) {
|
utilfeature.DefaultFeatureGate.Enabled(features.ServiceAccountTokenPodNodeInfo) {
|
||||||
nodeGetter = nodeStorage.Node.Store
|
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 {
|
if err != nil {
|
||||||
return genericapiserver.APIGroupInfo{}, err
|
return genericapiserver.APIGroupInfo{}, err
|
||||||
}
|
}
|
||||||
|
@ -57,7 +57,7 @@ type GenericConfig struct {
|
|||||||
ServiceAccountIssuer serviceaccount.TokenGenerator
|
ServiceAccountIssuer serviceaccount.TokenGenerator
|
||||||
ServiceAccountMaxExpiration time.Duration
|
ServiceAccountMaxExpiration time.Duration
|
||||||
ExtendExpiration bool
|
ExtendExpiration bool
|
||||||
IsTokenSignerExternal bool
|
MaxExtendedExpiration time.Duration
|
||||||
|
|
||||||
APIAudiences authenticator.Audiences
|
APIAudiences authenticator.Audiences
|
||||||
|
|
||||||
@ -103,9 +103,9 @@ func (c *GenericConfig) NewRESTStorage(apiResourceConfigSource serverstorage.API
|
|||||||
|
|
||||||
var serviceAccountStorage *serviceaccountstore.REST
|
var serviceAccountStorage *serviceaccountstore.REST
|
||||||
if c.ServiceAccountIssuer != nil {
|
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 {
|
} 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 {
|
if err != nil {
|
||||||
return genericapiserver.APIGroupInfo{}, err
|
return genericapiserver.APIGroupInfo{}, err
|
||||||
|
@ -39,7 +39,7 @@ type REST struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewREST returns a RESTStorage object that will work against service accounts.
|
// 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{
|
store := &genericregistry.Store{
|
||||||
NewFunc: func() runtime.Object { return &api.ServiceAccount{} },
|
NewFunc: func() runtime.Object { return &api.ServiceAccount{} },
|
||||||
NewListFunc: func() runtime.Object { return &api.ServiceAccountList{} },
|
NewListFunc: func() runtime.Object { return &api.ServiceAccountList{} },
|
||||||
@ -61,16 +61,16 @@ func NewREST(optsGetter generic.RESTOptionsGetter, issuer token.TokenGenerator,
|
|||||||
var trest *TokenREST
|
var trest *TokenREST
|
||||||
if issuer != nil && podStorage != nil && secretStorage != nil {
|
if issuer != nil && podStorage != nil && secretStorage != nil {
|
||||||
trest = &TokenREST{
|
trest = &TokenREST{
|
||||||
svcaccts: store,
|
svcaccts: store,
|
||||||
pods: podStorage,
|
pods: podStorage,
|
||||||
secrets: secretStorage,
|
secrets: secretStorage,
|
||||||
nodes: nodeStorage,
|
nodes: nodeStorage,
|
||||||
issuer: issuer,
|
issuer: issuer,
|
||||||
auds: auds,
|
auds: auds,
|
||||||
audsSet: sets.NewString(auds...),
|
audsSet: sets.NewString(auds...),
|
||||||
maxExpirationSeconds: int64(max.Seconds()),
|
maxExpirationSeconds: int64(max.Seconds()),
|
||||||
extendExpiration: extendExpiration,
|
maxExtendedExpirationSeconds: int64(maxExtendedExpiration.Seconds()),
|
||||||
isTokenSignerExternal: isTokenSignerExternal,
|
extendExpiration: extendExpiration,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,6 +19,7 @@ package storage
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"gopkg.in/go-jose/go-jose.v2/jwt"
|
"gopkg.in/go-jose/go-jose.v2/jwt"
|
||||||
|
|
||||||
@ -55,7 +56,7 @@ func newTokenStorage(t *testing.T, issuer token.TokenGenerator, auds authenticat
|
|||||||
ResourcePrefix: "serviceaccounts",
|
ResourcePrefix: "serviceaccounts",
|
||||||
}
|
}
|
||||||
// set issuer, podStore and secretStore to allow the token endpoint to be initialised
|
// 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 {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error from REST storage: %v", err)
|
t.Fatalf("unexpected error from REST storage: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -56,16 +56,16 @@ func (r *TokenREST) Destroy() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type TokenREST struct {
|
type TokenREST struct {
|
||||||
svcaccts rest.Getter
|
svcaccts rest.Getter
|
||||||
pods rest.Getter
|
pods rest.Getter
|
||||||
secrets rest.Getter
|
secrets rest.Getter
|
||||||
nodes rest.Getter
|
nodes rest.Getter
|
||||||
issuer token.TokenGenerator
|
issuer token.TokenGenerator
|
||||||
auds authenticator.Audiences
|
auds authenticator.Audiences
|
||||||
audsSet sets.String
|
audsSet sets.String
|
||||||
maxExpirationSeconds int64
|
maxExpirationSeconds int64
|
||||||
extendExpiration bool
|
extendExpiration bool
|
||||||
isTokenSignerExternal bool
|
maxExtendedExpirationSeconds int64
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ = rest.NamedCreater(&TokenREST{})
|
var _ = rest.NamedCreater(&TokenREST{})
|
||||||
@ -218,13 +218,7 @@ func (r *TokenREST) Create(ctx context.Context, name string, obj runtime.Object,
|
|||||||
exp := req.Spec.ExpirationSeconds
|
exp := req.Spec.ExpirationSeconds
|
||||||
if r.extendExpiration && pod != nil && req.Spec.ExpirationSeconds == token.WarnOnlyBoundTokenExpirationSeconds && r.isKubeAudiences(req.Spec.Audiences) {
|
if r.extendExpiration && pod != nil && req.Spec.ExpirationSeconds == token.WarnOnlyBoundTokenExpirationSeconds && r.isKubeAudiences(req.Spec.Audiences) {
|
||||||
warnAfter = exp
|
warnAfter = exp
|
||||||
// If token issuer is external-jwt-signer, then choose the smaller of
|
exp = r.maxExtendedExpirationSeconds
|
||||||
// ExpirationExtensionSeconds and max token lifetime supported by external signer.
|
|
||||||
if r.isTokenSignerExternal {
|
|
||||||
exp = min(r.maxExpirationSeconds, token.ExpirationExtensionSeconds)
|
|
||||||
} else {
|
|
||||||
exp = token.ExpirationExtensionSeconds
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sc, pc, err := token.Claims(*svcacct, pod, secret, node, exp, warnAfter, req.Spec.Audiences)
|
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) {
|
func TestCreate_Token_WithExpiryCap(t *testing.T) {
|
||||||
|
|
||||||
testcases := []struct {
|
testcases := []struct {
|
||||||
desc string
|
desc string
|
||||||
extendExpiration bool
|
extendExpiration bool
|
||||||
maxExpirationSeconds int
|
maxExpirationSeconds int
|
||||||
expectedTokenAgeSec int
|
maxExtendedExpirationSeconds int
|
||||||
isExternal bool
|
expectedTokenAgeSec int
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
desc: "maxExpirationSeconds honoured",
|
desc: "passed expiration respected if less than max",
|
||||||
extendExpiration: true,
|
extendExpiration: false,
|
||||||
maxExpirationSeconds: 5 * 60 * 60, // 5h
|
maxExpirationSeconds: 5 * 60 * 60, // 5h
|
||||||
expectedTokenAgeSec: 5 * 60 * 60, // 5h
|
maxExtendedExpirationSeconds: token.ExpirationExtensionSeconds, // 1y
|
||||||
isExternal: true,
|
expectedTokenAgeSec: token.WarnOnlyBoundTokenExpirationSeconds, // 1h 7s
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "ExpirationExtensionSeconds used for exp",
|
desc: "maxExtendedExpirationSeconds honoured",
|
||||||
extendExpiration: true,
|
extendExpiration: true,
|
||||||
maxExpirationSeconds: 2 * 365 * 24 * 60 * 60, // 2 years
|
maxExpirationSeconds: 2 * 60 * 60, // 2h
|
||||||
expectedTokenAgeSec: token.ExpirationExtensionSeconds, // 1y
|
maxExtendedExpirationSeconds: 5 * 60 * 60, // 5h
|
||||||
isExternal: true,
|
expectedTokenAgeSec: 5 * 60 * 60, // 5h
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "ExpirationExtensionSeconds used for exp",
|
desc: "ExpirationExtensionSeconds used for exp",
|
||||||
extendExpiration: true,
|
extendExpiration: true,
|
||||||
maxExpirationSeconds: 5 * 60 * 60, // 5h
|
maxExpirationSeconds: 2 * 365 * 24 * 60 * 60, // 2y
|
||||||
expectedTokenAgeSec: token.ExpirationExtensionSeconds, // 1y
|
maxExtendedExpirationSeconds: token.ExpirationExtensionSeconds, // 1y
|
||||||
isExternal: false,
|
expectedTokenAgeSec: token.ExpirationExtensionSeconds, // 1y
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "requested time use with extension disabled",
|
desc: "ExpirationSeconds used for exp",
|
||||||
extendExpiration: false,
|
extendExpiration: true,
|
||||||
maxExpirationSeconds: 5 * 60 * 60, // 5h
|
maxExpirationSeconds: 5 * 60 * 60, // 5h
|
||||||
expectedTokenAgeSec: 3607, // 1h
|
maxExtendedExpirationSeconds: token.ExpirationExtensionSeconds, // 1y
|
||||||
isExternal: true,
|
expectedTokenAgeSec: token.ExpirationExtensionSeconds, // 1y
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "maxExpirationSeconds honoured with extension disabled",
|
desc: "requested time use with extension disabled",
|
||||||
extendExpiration: false,
|
extendExpiration: false,
|
||||||
maxExpirationSeconds: 30 * 60, // 30m
|
maxExpirationSeconds: 5 * 60 * 60, // 5h
|
||||||
expectedTokenAgeSec: 30 * 60, // 30m
|
expectedTokenAgeSec: 3607, // 1h
|
||||||
isExternal: true,
|
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)
|
ctx = request.WithNamespace(ctx, serviceAccount.Namespace)
|
||||||
storage.Token.extendExpiration = tc.extendExpiration
|
storage.Token.extendExpiration = tc.extendExpiration
|
||||||
storage.Token.maxExpirationSeconds = int64(tc.maxExpirationSeconds)
|
storage.Token.maxExpirationSeconds = int64(tc.maxExpirationSeconds)
|
||||||
storage.Token.isTokenSignerExternal = tc.isExternal
|
storage.Token.maxExtendedExpirationSeconds = int64(tc.maxExtendedExpirationSeconds)
|
||||||
|
|
||||||
tokenReqTimeStamp := time.Now()
|
tokenReqTimeStamp := time.Now()
|
||||||
out, err := storage.Token.Create(ctx, serviceAccount.Name, &authenticationapi.TokenRequest{
|
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)
|
t.Fatalf("Error unmarshalling Claims: %v", err)
|
||||||
}
|
}
|
||||||
structuredClaim.Expiry.Time()
|
structuredClaim.Expiry.Time()
|
||||||
upperBound := tokenReqTimeStamp.Add(time.Duration(tc.expectedTokenAgeSec+10) * time.Second)
|
confidenceInterval := 10 // seconds
|
||||||
lowerBound := tokenReqTimeStamp.Add(time.Duration(tc.expectedTokenAgeSec-10) * time.Second)
|
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.
|
// check for token expiration with a toleration of +/-10s after tokenReqTimeStamp to make for latencies.
|
||||||
if structuredClaim.Expiry.Time().After(upperBound) ||
|
if structuredClaim.Expiry.Time().After(upperBound) ||
|
||||||
structuredClaim.Expiry.Time().Before(lowerBound) {
|
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
})
|
})
|
||||||
|
Loading…
Reference in New Issue
Block a user