diff --git a/pkg/kubeapiserver/authenticator/config.go b/pkg/kubeapiserver/authenticator/config.go index 72272bb283..359af1afb9 100644 --- a/pkg/kubeapiserver/authenticator/config.go +++ b/pkg/kubeapiserver/authenticator/config.go @@ -64,7 +64,7 @@ type Config struct { ServiceAccountKeyFiles []string ServiceAccountLookup bool ServiceAccountIssuer string - APIAudiences []string + APIAudiences authenticator.Audiences WebhookTokenAuthnConfigFile string WebhookTokenAuthnCacheTTL time.Duration @@ -135,14 +135,14 @@ func (config Config) New() (authenticator.Request, *spec.SecurityDefinitions, er tokenAuthenticators = append(tokenAuthenticators, authenticator.WrapAudienceAgnosticToken(config.APIAudiences, tokenAuth)) } if len(config.ServiceAccountKeyFiles) > 0 { - serviceAccountAuth, err := newLegacyServiceAccountAuthenticator(config.ServiceAccountKeyFiles, config.ServiceAccountLookup, config.ServiceAccountTokenGetter) + serviceAccountAuth, err := newLegacyServiceAccountAuthenticator(config.ServiceAccountKeyFiles, config.ServiceAccountLookup, config.APIAudiences, config.ServiceAccountTokenGetter) if err != nil { return nil, nil, err } - tokenAuthenticators = append(tokenAuthenticators, authenticator.WrapAudienceAgnosticToken(config.APIAudiences, serviceAccountAuth)) + tokenAuthenticators = append(tokenAuthenticators, serviceAccountAuth) } if utilfeature.DefaultFeatureGate.Enabled(features.TokenRequest) && config.ServiceAccountIssuer != "" { - serviceAccountAuth, err := newServiceAccountAuthenticator(config.ServiceAccountIssuer, config.APIAudiences, config.ServiceAccountKeyFiles, config.ServiceAccountTokenGetter) + serviceAccountAuth, err := newServiceAccountAuthenticator(config.ServiceAccountIssuer, config.ServiceAccountKeyFiles, config.APIAudiences, config.ServiceAccountTokenGetter) if err != nil { return nil, nil, err } @@ -276,7 +276,7 @@ func newAuthenticatorFromOIDCIssuerURL(opts oidc.Options) (authenticator.Token, } // newLegacyServiceAccountAuthenticator returns an authenticator.Token or an error -func newLegacyServiceAccountAuthenticator(keyfiles []string, lookup bool, serviceAccountGetter serviceaccount.ServiceAccountTokenGetter) (authenticator.Token, error) { +func newLegacyServiceAccountAuthenticator(keyfiles []string, lookup bool, apiAudiences authenticator.Audiences, serviceAccountGetter serviceaccount.ServiceAccountTokenGetter) (authenticator.Token, error) { allPublicKeys := []interface{}{} for _, keyfile := range keyfiles { publicKeys, err := certutil.PublicKeysFromFile(keyfile) @@ -286,12 +286,12 @@ func newLegacyServiceAccountAuthenticator(keyfiles []string, lookup bool, servic allPublicKeys = append(allPublicKeys, publicKeys...) } - tokenAuthenticator := serviceaccount.JWTTokenAuthenticator(serviceaccount.LegacyIssuer, allPublicKeys, serviceaccount.NewLegacyValidator(lookup, serviceAccountGetter)) + tokenAuthenticator := serviceaccount.JWTTokenAuthenticator(serviceaccount.LegacyIssuer, allPublicKeys, apiAudiences, serviceaccount.NewLegacyValidator(lookup, serviceAccountGetter)) return tokenAuthenticator, nil } // newServiceAccountAuthenticator returns an authenticator.Token or an error -func newServiceAccountAuthenticator(iss string, audiences []string, keyfiles []string, serviceAccountGetter serviceaccount.ServiceAccountTokenGetter) (authenticator.Token, error) { +func newServiceAccountAuthenticator(iss string, keyfiles []string, apiAudiences authenticator.Audiences, serviceAccountGetter serviceaccount.ServiceAccountTokenGetter) (authenticator.Token, error) { allPublicKeys := []interface{}{} for _, keyfile := range keyfiles { publicKeys, err := certutil.PublicKeysFromFile(keyfile) @@ -301,7 +301,7 @@ func newServiceAccountAuthenticator(iss string, audiences []string, keyfiles []s allPublicKeys = append(allPublicKeys, publicKeys...) } - tokenAuthenticator := serviceaccount.JWTTokenAuthenticator(iss, allPublicKeys, serviceaccount.NewValidator(audiences, serviceAccountGetter)) + tokenAuthenticator := serviceaccount.JWTTokenAuthenticator(iss, allPublicKeys, apiAudiences, serviceaccount.NewValidator(serviceAccountGetter)) return tokenAuthenticator, nil } diff --git a/pkg/serviceaccount/BUILD b/pkg/serviceaccount/BUILD index 485dcf07d0..d0ad133b62 100644 --- a/pkg/serviceaccount/BUILD +++ b/pkg/serviceaccount/BUILD @@ -54,6 +54,7 @@ go_test( "//pkg/controller/serviceaccount:go_default_library", "//staging/src/k8s.io/api/core/v1:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//staging/src/k8s.io/apiserver/pkg/authentication/authenticator:go_default_library", "//staging/src/k8s.io/client-go/kubernetes:go_default_library", "//staging/src/k8s.io/client-go/kubernetes/fake:go_default_library", "//staging/src/k8s.io/client-go/util/cert:go_default_library", diff --git a/pkg/serviceaccount/claims.go b/pkg/serviceaccount/claims.go index 3b9cd08c28..3d48b6f2dc 100644 --- a/pkg/serviceaccount/claims.go +++ b/pkg/serviceaccount/claims.go @@ -80,15 +80,13 @@ func Claims(sa core.ServiceAccount, pod *core.Pod, secret *core.Secret, expirati return sc, pc } -func NewValidator(audiences []string, getter ServiceAccountTokenGetter) Validator { +func NewValidator(getter ServiceAccountTokenGetter) Validator { return &validator{ - auds: audiences, getter: getter, } } type validator struct { - auds []string getter ServiceAccountTokenGetter } @@ -112,19 +110,6 @@ func (v *validator) Validate(_ string, public *jwt.Claims, privateObj interface{ return nil, errors.New("Token could not be validated.") } - var audValid bool - - for _, aud := range v.auds { - audValid = public.Audience.Contains(aud) - if audValid { - break - } - } - - if !audValid { - return nil, errors.New("Token is invalid for this audience.") - } - namespace := private.Kubernetes.Namespace saref := private.Kubernetes.Svcacct podref := private.Kubernetes.Pod diff --git a/pkg/serviceaccount/jwt.go b/pkg/serviceaccount/jwt.go index 23c8934fbf..233fdee2d6 100644 --- a/pkg/serviceaccount/jwt.go +++ b/pkg/serviceaccount/jwt.go @@ -111,18 +111,20 @@ func (j *jwtTokenGenerator) GenerateToken(claims *jwt.Claims, privateClaims inte // JWTTokenAuthenticator authenticates tokens as JWT tokens produced by JWTTokenGenerator // Token signatures are verified using each of the given public keys until one works (allowing key rotation) // If lookup is true, the service account and secret referenced as claims inside the token are retrieved and verified with the provided ServiceAccountTokenGetter -func JWTTokenAuthenticator(iss string, keys []interface{}, validator Validator) authenticator.Token { +func JWTTokenAuthenticator(iss string, keys []interface{}, implicitAuds authenticator.Audiences, validator Validator) authenticator.Token { return &jwtTokenAuthenticator{ - iss: iss, - keys: keys, - validator: validator, + iss: iss, + keys: keys, + implicitAuds: implicitAuds, + validator: validator, } } type jwtTokenAuthenticator struct { - iss string - keys []interface{} - validator Validator + iss string + keys []interface{} + validator Validator + implicitAuds authenticator.Audiences } // Validator is called by the JWT token authenticator to apply domain specific @@ -170,6 +172,23 @@ func (j *jwtTokenAuthenticator) AuthenticateToken(ctx context.Context, tokenData return nil, false, utilerrors.NewAggregate(errlist) } + tokenAudiences := authenticator.Audiences(public.Audience) + if len(tokenAudiences) == 0 { + // only apiserver audiences are allowed for legacy tokens + tokenAudiences = j.implicitAuds + } + + requestedAudiences, ok := authenticator.AudiencesFrom(ctx) + if !ok { + // default to apiserver audiences + requestedAudiences = j.implicitAuds + } + + auds := authenticator.Audiences(tokenAudiences).Intersect(requestedAudiences) + if len(auds) == 0 && len(j.implicitAuds) != 0 { + return nil, false, fmt.Errorf("token audiences %q is invalid for the target audiences %q", tokenAudiences, requestedAudiences) + } + // If we get here, we have a token with a recognized signature and // issuer string. sa, err := j.validator.Validate(tokenData, public, private) @@ -177,7 +196,10 @@ func (j *jwtTokenAuthenticator) AuthenticateToken(ctx context.Context, tokenData return nil, false, err } - return &authenticator.Response{User: sa.UserInfo()}, true, nil + return &authenticator.Response{ + User: sa.UserInfo(), + Audiences: auds, + }, true, nil } // hasCorrectIssuer returns true if tokenData is a valid JWT in compact diff --git a/pkg/serviceaccount/jwt_test.go b/pkg/serviceaccount/jwt_test.go index 61cca23e12..36be027c09 100644 --- a/pkg/serviceaccount/jwt_test.go +++ b/pkg/serviceaccount/jwt_test.go @@ -23,6 +23,7 @@ import ( "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apiserver/pkg/authentication/authenticator" clientset "k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes/fake" certutil "k8s.io/client-go/util/cert" @@ -275,16 +276,18 @@ func TestTokenGenerateAndValidate(t *testing.T) { } for k, tc := range testCases { + auds := authenticator.Audiences{"api"} getter := serviceaccountcontroller.NewGetterFromClient(tc.Client) - authenticator := serviceaccount.JWTTokenAuthenticator(serviceaccount.LegacyIssuer, tc.Keys, serviceaccount.NewLegacyValidator(tc.Client != nil, getter)) + authn := serviceaccount.JWTTokenAuthenticator(serviceaccount.LegacyIssuer, tc.Keys, auds, serviceaccount.NewLegacyValidator(tc.Client != nil, getter)) // An invalid, non-JWT token should always fail - if _, ok, err := authenticator.AuthenticateToken(context.Background(), "invalid token"); err != nil || ok { + ctx := authenticator.WithAudiences(context.Background(), auds) + if _, ok, err := authn.AuthenticateToken(ctx, "invalid token"); err != nil || ok { t.Errorf("%s: Expected err=nil, ok=false for non-JWT token", k) continue } - resp, ok, err := authenticator.AuthenticateToken(context.Background(), tc.Token) + resp, ok, err := authn.AuthenticateToken(ctx, tc.Token) if (err != nil) != tc.ExpectedErr { t.Errorf("%s: Expected error=%v, got %v", k, tc.ExpectedErr, err) continue diff --git a/test/integration/auth/svcaccttoken_test.go b/test/integration/auth/svcaccttoken_test.go index 6409c475be..6a5052f0a9 100644 --- a/test/integration/auth/svcaccttoken_test.go +++ b/test/integration/auth/svcaccttoken_test.go @@ -32,6 +32,7 @@ import ( v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" + "k8s.io/apiserver/pkg/authentication/authenticator" "k8s.io/apiserver/pkg/authentication/request/bearertoken" apiserverserviceaccount "k8s.io/apiserver/pkg/authentication/serviceaccount" "k8s.io/apiserver/pkg/authorization/authorizerfactory" @@ -63,7 +64,7 @@ func TestServiceAccountTokenCreate(t *testing.T) { pk := sk.(*ecdsa.PrivateKey).PublicKey const iss = "https://foo.bar.example.com" - aud := []string{"api"} + aud := authenticator.Audiences{"api"} maxExpirationSeconds := int64(60 * 60) maxExpirationDuration, err := time.ParseDuration(fmt.Sprintf("%ds", maxExpirationSeconds)) @@ -76,11 +77,13 @@ func TestServiceAccountTokenCreate(t *testing.T) { // Start the server masterConfig := framework.NewIntegrationTestMasterConfig() masterConfig.GenericConfig.Authorization.Authorizer = authorizerfactory.NewAlwaysAllowAuthorizer() + masterConfig.GenericConfig.Authentication.APIAudiences = aud masterConfig.GenericConfig.Authentication.Authenticator = bearertoken.New( serviceaccount.JWTTokenAuthenticator( iss, []interface{}{&pk}, - serviceaccount.NewValidator(aud, serviceaccountgetter.NewGetterFromClient(gcs)), + aud, + serviceaccount.NewValidator(serviceaccountgetter.NewGetterFromClient(gcs)), ), ) tokenGenerator, err := serviceaccount.JWTTokenGenerator(iss, sk) diff --git a/test/integration/serviceaccount/service_account_test.go b/test/integration/serviceaccount/service_account_test.go index c554597a8d..5139911db6 100644 --- a/test/integration/serviceaccount/service_account_test.go +++ b/test/integration/serviceaccount/service_account_test.go @@ -374,7 +374,7 @@ func startServiceAccountTestServer(t *testing.T) (*clientset.Clientset, restclie }) serviceAccountKey, _ := rsa.GenerateKey(rand.Reader, 2048) serviceAccountTokenGetter := serviceaccountcontroller.NewGetterFromClient(rootClientset) - serviceAccountTokenAuth := serviceaccount.JWTTokenAuthenticator(serviceaccount.LegacyIssuer, []interface{}{&serviceAccountKey.PublicKey}, serviceaccount.NewLegacyValidator(true, serviceAccountTokenGetter)) + serviceAccountTokenAuth := serviceaccount.JWTTokenAuthenticator(serviceaccount.LegacyIssuer, []interface{}{&serviceAccountKey.PublicKey}, nil, serviceaccount.NewLegacyValidator(true, serviceAccountTokenGetter)) authenticator := union.New( bearertoken.New(rootTokenAuth), bearertoken.New(serviceAccountTokenAuth),