mirror of
				https://github.com/optim-enterprises-bv/kubernetes.git
				synced 2025-11-04 04:08:16 +00:00 
			
		
		
		
	Add integration test for multiple audience in structured authn
Signed-off-by: Anish Ramasekar <anish.ramasekar@gmail.com>
This commit is contained in:
		@@ -242,7 +242,7 @@ type Issuer struct {
 | 
				
			|||||||
	AudienceMatchPolicy AudienceMatchPolicyType `json:"audienceMatchPolicy,omitempty"`
 | 
						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
 | 
					type AudienceMatchPolicyType string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Valid types for AudienceMatchPolicyType
 | 
					// Valid types for AudienceMatchPolicyType
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -219,6 +219,7 @@ func validateClaimValidationRules(compiler authenticationcel.Compiler, celMapper
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
			compilationResult, err := compileClaimsCELExpression(compiler, &authenticationcel.ClaimValidationCondition{
 | 
								compilationResult, err := compileClaimsCELExpression(compiler, &authenticationcel.ClaimValidationCondition{
 | 
				
			||||||
				Expression: rule.Expression,
 | 
									Expression: rule.Expression,
 | 
				
			||||||
 | 
									Message:    rule.Message,
 | 
				
			||||||
			}, fldPath.Child("expression"))
 | 
								}, fldPath.Child("expression"))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			if err != nil {
 | 
								if err != nil {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -578,10 +578,8 @@ func (v *idTokenVerifier) verifyAudience(t *oidc.IDToken) error {
 | 
				
			|||||||
	if v.audiences.Len() == 0 {
 | 
						if v.audiences.Len() == 0 {
 | 
				
			||||||
		return fmt.Errorf("oidc: invalid configuration, audiences cannot be empty")
 | 
							return fmt.Errorf("oidc: invalid configuration, audiences cannot be empty")
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	for _, aud := range t.Audience {
 | 
						if v.audiences.HasAny(t.Audience...) {
 | 
				
			||||||
		if v.audiences.Has(aud) {
 | 
							return nil
 | 
				
			||||||
			return nil
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return fmt.Errorf("oidc: expected audience in %q got %q", sets.List(v.audiences), t.Audience)
 | 
						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: "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",
 | 
								name: "multiple-audiences in authentication config, no match",
 | 
				
			||||||
			options: Options{
 | 
								options: Options{
 | 
				
			||||||
@@ -1585,6 +1618,82 @@ func TestToken(t *testing.T) {
 | 
				
			|||||||
			}`, valid.Unix()),
 | 
								}`, valid.Unix()),
 | 
				
			||||||
			wantErr: `oidc: verify token: oidc: expected audience in ["my-client" "random-client"] got ["not-my-client"]`,
 | 
								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",
 | 
								name: "invalid-issuer",
 | 
				
			||||||
			options: Options{
 | 
								options: Options{
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -761,6 +761,58 @@ jwt:
 | 
				
			|||||||
			},
 | 
								},
 | 
				
			||||||
			wantUser: nil,
 | 
								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 {
 | 
						for _, tt := range tests {
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user