Merge pull request #123305 from aramase/aramase/f/kep_3331_audience_match_policy_follow_up
Add integration test for multiple audience in structured authn
This commit is contained in:
		@@ -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
 | 
			
		||||
 
 | 
			
		||||
@@ -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 {
 | 
			
		||||
 
 | 
			
		||||
@@ -578,11 +578,9 @@ 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) {
 | 
			
		||||
	if v.audiences.HasAny(t.Audience...) {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return fmt.Errorf("oidc: expected audience in %q got %q", sets.List(v.audiences), t.Audience)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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{
 | 
			
		||||
 
 | 
			
		||||
@@ -761,6 +761,58 @@ jwt:
 | 
			
		||||
			},
 | 
			
		||||
			wantUser: nil,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "multiple audiences check with claim validation rule is ok",
 | 
			
		||||
			authConfigFn: func(t *testing.T, issuerURL, caCert string) string {
 | 
			
		||||
				return fmt.Sprintf(`
 | 
			
		||||
apiVersion: apiserver.config.k8s.io/v1alpha1
 | 
			
		||||
kind: AuthenticationConfiguration
 | 
			
		||||
jwt:
 | 
			
		||||
- issuer:
 | 
			
		||||
    url: %s
 | 
			
		||||
    audiences:
 | 
			
		||||
    - baz
 | 
			
		||||
    - foo
 | 
			
		||||
    audienceMatchPolicy: MatchAny
 | 
			
		||||
    certificateAuthority: |
 | 
			
		||||
        %s
 | 
			
		||||
  claimMappings:
 | 
			
		||||
    username:
 | 
			
		||||
      expression: "'k8s-' + claims.sub"
 | 
			
		||||
    uid:
 | 
			
		||||
      expression: "claims.uid"
 | 
			
		||||
  claimValidationRules:
 | 
			
		||||
  - expression: 'sets.equivalent(claims.aud, ["bar", "foo", "baz"])'
 | 
			
		||||
    message: 'aud claim must be exactly match list ["bar", "foo", "baz"]'
 | 
			
		||||
`, issuerURL, indentCertificateAuthority(caCert))
 | 
			
		||||
			},
 | 
			
		||||
			configureInfrastructure: configureTestInfrastructure[*rsa.PrivateKey, *rsa.PublicKey],
 | 
			
		||||
			configureOIDCServerBehaviour: func(t *testing.T, oidcServer *utilsoidc.TestServer, signingPrivateKey *rsa.PrivateKey) {
 | 
			
		||||
				idTokenLifetime := time.Second * 1200
 | 
			
		||||
				oidcServer.TokenHandler().EXPECT().Token().Times(1).DoAndReturn(utilsoidc.TokenHandlerBehaviorReturningPredefinedJWT(
 | 
			
		||||
					t,
 | 
			
		||||
					signingPrivateKey,
 | 
			
		||||
					map[string]interface{}{
 | 
			
		||||
						"iss": oidcServer.URL(),
 | 
			
		||||
						"sub": defaultOIDCClaimedUsername,
 | 
			
		||||
						"aud": []string{"foo", "bar", "baz"},
 | 
			
		||||
						"exp": time.Now().Add(idTokenLifetime).Unix(),
 | 
			
		||||
						"uid": "1234",
 | 
			
		||||
					},
 | 
			
		||||
					defaultStubAccessToken,
 | 
			
		||||
					defaultStubRefreshToken,
 | 
			
		||||
				))
 | 
			
		||||
			},
 | 
			
		||||
			configureClient: configureClientFetchingOIDCCredentials,
 | 
			
		||||
			assertErrFn: func(t *testing.T, errorToCheck error) {
 | 
			
		||||
				assert.NoError(t, errorToCheck)
 | 
			
		||||
			},
 | 
			
		||||
			wantUser: &authenticationv1.UserInfo{
 | 
			
		||||
				Username: "k8s-john_doe",
 | 
			
		||||
				Groups:   []string{"system:authenticated"},
 | 
			
		||||
				UID:      "1234",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user