From 1bc99127a6be5a9a99ca8a249fcb0ce9f40f73ab Mon Sep 17 00:00:00 2001 From: Anish Ramasekar Date: Wed, 14 Feb 2024 17:04:21 -0800 Subject: [PATCH] Add integration test for multiple audience in structured authn Signed-off-by: Anish Ramasekar Kubernetes-commit: 0feb1d5173c94e28da79963fb296296b005dd6a1 --- pkg/apis/apiserver/v1alpha1/types.go | 2 +- pkg/apis/apiserver/validation/validation.go | 1 + plugin/pkg/authenticator/token/oidc/oidc.go | 6 +- .../pkg/authenticator/token/oidc/oidc_test.go | 109 ++++++++++++++++++ 4 files changed, 113 insertions(+), 5 deletions(-) diff --git a/pkg/apis/apiserver/v1alpha1/types.go b/pkg/apis/apiserver/v1alpha1/types.go index 9a859103c..74b1aa002 100644 --- a/pkg/apis/apiserver/v1alpha1/types.go +++ b/pkg/apis/apiserver/v1alpha1/types.go @@ -242,7 +242,7 @@ type Issuer struct { AudienceMatchPolicy AudienceMatchPolicyType `json:"audienceMatchPolicy,omitempty"` } -// AudienceMatchPolicyType is a set of valid values for Issuer.AudienceMatchPolicy +// AudienceMatchPolicyType is a set of valid values for issuer.audienceMatchPolicy type AudienceMatchPolicyType string // Valid types for AudienceMatchPolicyType diff --git a/pkg/apis/apiserver/validation/validation.go b/pkg/apis/apiserver/validation/validation.go index 01a0538c1..eb0d3dd6d 100644 --- a/pkg/apis/apiserver/validation/validation.go +++ b/pkg/apis/apiserver/validation/validation.go @@ -219,6 +219,7 @@ func validateClaimValidationRules(compiler authenticationcel.Compiler, celMapper compilationResult, err := compileClaimsCELExpression(compiler, &authenticationcel.ClaimValidationCondition{ Expression: rule.Expression, + Message: rule.Message, }, fldPath.Child("expression")) if err != nil { diff --git a/plugin/pkg/authenticator/token/oidc/oidc.go b/plugin/pkg/authenticator/token/oidc/oidc.go index 79ef5c920..0b1f682c5 100644 --- a/plugin/pkg/authenticator/token/oidc/oidc.go +++ b/plugin/pkg/authenticator/token/oidc/oidc.go @@ -578,10 +578,8 @@ func (v *idTokenVerifier) verifyAudience(t *oidc.IDToken) error { if v.audiences.Len() == 0 { return fmt.Errorf("oidc: invalid configuration, audiences cannot be empty") } - for _, aud := range t.Audience { - if v.audiences.Has(aud) { - return nil - } + if v.audiences.HasAny(t.Audience...) { + return nil } return fmt.Errorf("oidc: expected audience in %q got %q", sets.List(v.audiences), t.Audience) diff --git a/plugin/pkg/authenticator/token/oidc/oidc_test.go b/plugin/pkg/authenticator/token/oidc/oidc_test.go index 1cdc2831b..55184ff99 100644 --- a/plugin/pkg/authenticator/token/oidc/oidc_test.go +++ b/plugin/pkg/authenticator/token/oidc/oidc_test.go @@ -1554,6 +1554,39 @@ func TestToken(t *testing.T) { Name: "jane", }, }, + { + name: "multiple-audiences in authentication config, multiple matches", + options: Options{ + JWTAuthenticator: apiserver.JWTAuthenticator{ + Issuer: apiserver.Issuer{ + URL: "https://auth.example.com", + Audiences: []string{"random-client", "my-client", "other-client"}, + AudienceMatchPolicy: "MatchAny", + }, + ClaimMappings: apiserver.ClaimMappings{ + Username: apiserver.PrefixedClaimOrExpression{ + Claim: "username", + Prefix: pointer.String(""), + }, + }, + }, + now: func() time.Time { return now }, + }, + signingKey: loadRSAPrivKey(t, "testdata/rsa_1.pem", jose.RS256), + pubKeys: []*jose.JSONWebKey{ + loadRSAKey(t, "testdata/rsa_1.pem", jose.RS256), + }, + claims: fmt.Sprintf(`{ + "iss": "https://auth.example.com", + "aud": ["not-my-client", "my-client", "other-client"], + "azp": "not-my-client", + "username": "jane", + "exp": %d + }`, valid.Unix()), + want: &user.DefaultInfo{ + Name: "jane", + }, + }, { name: "multiple-audiences in authentication config, no match", options: Options{ @@ -1585,6 +1618,82 @@ func TestToken(t *testing.T) { }`, valid.Unix()), wantErr: `oidc: verify token: oidc: expected audience in ["my-client" "random-client"] got ["not-my-client"]`, }, + { + name: "nuanced audience validation using claim validation rules", + options: Options{ + JWTAuthenticator: apiserver.JWTAuthenticator{ + Issuer: apiserver.Issuer{ + URL: "https://auth.example.com", + Audiences: []string{"bar", "foo", "baz"}, + AudienceMatchPolicy: "MatchAny", + }, + ClaimMappings: apiserver.ClaimMappings{ + Username: apiserver.PrefixedClaimOrExpression{ + Claim: "username", + Prefix: pointer.String(""), + }, + }, + ClaimValidationRules: []apiserver.ClaimValidationRule{ + { + Expression: `sets.equivalent(claims.aud, ["bar", "foo", "baz"])`, + Message: "audience must exactly contain [bar, foo, baz]", + }, + }, + }, + now: func() time.Time { return now }, + }, + signingKey: loadRSAPrivKey(t, "testdata/rsa_1.pem", jose.RS256), + pubKeys: []*jose.JSONWebKey{ + loadRSAKey(t, "testdata/rsa_1.pem", jose.RS256), + }, + claims: fmt.Sprintf(`{ + "iss": "https://auth.example.com", + "aud": ["foo", "bar", "baz"], + "azp": "not-my-client", + "username": "jane", + "exp": %d + }`, valid.Unix()), + want: &user.DefaultInfo{ + Name: "jane", + }, + }, + { + name: "audience validation using claim validation rules fails", + options: Options{ + JWTAuthenticator: apiserver.JWTAuthenticator{ + Issuer: apiserver.Issuer{ + URL: "https://auth.example.com", + Audiences: []string{"bar", "foo", "baz"}, + AudienceMatchPolicy: "MatchAny", + }, + ClaimMappings: apiserver.ClaimMappings{ + Username: apiserver.PrefixedClaimOrExpression{ + Claim: "username", + Prefix: pointer.String(""), + }, + }, + ClaimValidationRules: []apiserver.ClaimValidationRule{ + { + Expression: `sets.equivalent(claims.aud, ["bar", "foo", "baz"])`, + Message: "audience must exactly contain [bar, foo, baz]", + }, + }, + }, + now: func() time.Time { return now }, + }, + signingKey: loadRSAPrivKey(t, "testdata/rsa_1.pem", jose.RS256), + pubKeys: []*jose.JSONWebKey{ + loadRSAKey(t, "testdata/rsa_1.pem", jose.RS256), + }, + claims: fmt.Sprintf(`{ + "iss": "https://auth.example.com", + "aud": ["foo", "baz"], + "azp": "not-my-client", + "username": "jane", + "exp": %d + }`, valid.Unix()), + wantErr: `oidc: error evaluating claim validation expression: validation expression 'sets.equivalent(claims.aud, ["bar", "foo", "baz"])' failed: audience must exactly contain [bar, foo, baz]`, + }, { name: "invalid-issuer", options: Options{