Merge pull request #121078 from aramase/aramase/f/kep_3331_cel_integration
Implement CEL for StructuredAuthenticationConfig
This commit is contained in:
		@@ -172,6 +172,7 @@ type JWTAuthenticator struct {
 | 
				
			|||||||
	Issuer               Issuer
 | 
						Issuer               Issuer
 | 
				
			||||||
	ClaimValidationRules []ClaimValidationRule
 | 
						ClaimValidationRules []ClaimValidationRule
 | 
				
			||||||
	ClaimMappings        ClaimMappings
 | 
						ClaimMappings        ClaimMappings
 | 
				
			||||||
 | 
						UserValidationRules  []UserValidationRule
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Issuer provides the configuration for a external provider specific settings.
 | 
					// Issuer provides the configuration for a external provider specific settings.
 | 
				
			||||||
@@ -185,18 +186,43 @@ type Issuer struct {
 | 
				
			|||||||
type ClaimValidationRule struct {
 | 
					type ClaimValidationRule struct {
 | 
				
			||||||
	Claim         string
 | 
						Claim         string
 | 
				
			||||||
	RequiredValue string
 | 
						RequiredValue string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						Expression string
 | 
				
			||||||
 | 
						Message    string
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// ClaimMappings provides the configuration for claim mapping
 | 
					// ClaimMappings provides the configuration for claim mapping
 | 
				
			||||||
type ClaimMappings struct {
 | 
					type ClaimMappings struct {
 | 
				
			||||||
	Username PrefixedClaimOrExpression
 | 
						Username PrefixedClaimOrExpression
 | 
				
			||||||
	Groups   PrefixedClaimOrExpression
 | 
						Groups   PrefixedClaimOrExpression
 | 
				
			||||||
 | 
						UID      ClaimOrExpression
 | 
				
			||||||
 | 
						Extra    []ExtraMapping
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// PrefixedClaimOrExpression provides the configuration for a single prefixed claim or expression.
 | 
					// PrefixedClaimOrExpression provides the configuration for a single prefixed claim or expression.
 | 
				
			||||||
type PrefixedClaimOrExpression struct {
 | 
					type PrefixedClaimOrExpression struct {
 | 
				
			||||||
	Claim  string
 | 
						Claim  string
 | 
				
			||||||
	Prefix *string
 | 
						Prefix *string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						Expression string
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ClaimOrExpression provides the configuration for a single claim or expression.
 | 
				
			||||||
 | 
					type ClaimOrExpression struct {
 | 
				
			||||||
 | 
						Claim      string
 | 
				
			||||||
 | 
						Expression string
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ExtraMapping provides the configuration for a single extra mapping.
 | 
				
			||||||
 | 
					type ExtraMapping struct {
 | 
				
			||||||
 | 
						Key             string
 | 
				
			||||||
 | 
						ValueExpression string
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// UserValidationRule provides the configuration for a single user validation rule.
 | 
				
			||||||
 | 
					type UserValidationRule struct {
 | 
				
			||||||
 | 
						Expression string
 | 
				
			||||||
 | 
						Message    string
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
 | 
					// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -192,6 +192,13 @@ type JWTAuthenticator struct {
 | 
				
			|||||||
	// claimMappings points claims of a token to be treated as user attributes.
 | 
						// claimMappings points claims of a token to be treated as user attributes.
 | 
				
			||||||
	// +required
 | 
						// +required
 | 
				
			||||||
	ClaimMappings ClaimMappings `json:"claimMappings"`
 | 
						ClaimMappings ClaimMappings `json:"claimMappings"`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// userValidationRules are rules that are applied to final user before completing authentication.
 | 
				
			||||||
 | 
						// These allow invariants to be applied to incoming identities such as preventing the
 | 
				
			||||||
 | 
						// use of the system: prefix that is commonly used by Kubernetes components.
 | 
				
			||||||
 | 
						// The validation rules are logically ANDed together and must all return true for the validation to pass.
 | 
				
			||||||
 | 
						// +optional
 | 
				
			||||||
 | 
						UserValidationRules []UserValidationRule `json:"userValidationRules,omitempty"`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Issuer provides the configuration for a external provider specific settings.
 | 
					// Issuer provides the configuration for a external provider specific settings.
 | 
				
			||||||
@@ -225,14 +232,36 @@ type ClaimValidationRule struct {
 | 
				
			|||||||
	// claim is the name of a required claim.
 | 
						// claim is the name of a required claim.
 | 
				
			||||||
	// Same as --oidc-required-claim flag.
 | 
						// Same as --oidc-required-claim flag.
 | 
				
			||||||
	// Only string claim keys are supported.
 | 
						// Only string claim keys are supported.
 | 
				
			||||||
	// +required
 | 
						// Mutually exclusive with expression and message.
 | 
				
			||||||
	Claim string `json:"claim"`
 | 
						// +optional
 | 
				
			||||||
 | 
						Claim string `json:"claim,omitempty"`
 | 
				
			||||||
	// requiredValue is the value of a required claim.
 | 
						// requiredValue is the value of a required claim.
 | 
				
			||||||
	// Same as --oidc-required-claim flag.
 | 
						// Same as --oidc-required-claim flag.
 | 
				
			||||||
	// Only string claim values are supported.
 | 
						// Only string claim values are supported.
 | 
				
			||||||
	// If claim is set and requiredValue is not set, the claim must be present with a value set to the empty string.
 | 
						// If claim is set and requiredValue is not set, the claim must be present with a value set to the empty string.
 | 
				
			||||||
 | 
						// Mutually exclusive with expression and message.
 | 
				
			||||||
	// +optional
 | 
						// +optional
 | 
				
			||||||
	RequiredValue string `json:"requiredValue"`
 | 
						RequiredValue string `json:"requiredValue,omitempty"`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// expression represents the expression which will be evaluated by CEL.
 | 
				
			||||||
 | 
						// Must produce a boolean.
 | 
				
			||||||
 | 
						//
 | 
				
			||||||
 | 
						// CEL expressions have access to the contents of the token claims, organized into CEL variable:
 | 
				
			||||||
 | 
						// - 'claims' is a map of claim names to claim values.
 | 
				
			||||||
 | 
						//   For example, a variable named 'sub' can be accessed as 'claims.sub'.
 | 
				
			||||||
 | 
						//   Nested claims can be accessed using dot notation, e.g. 'claims.email.verified'.
 | 
				
			||||||
 | 
						// Must return true for the validation to pass.
 | 
				
			||||||
 | 
						//
 | 
				
			||||||
 | 
						// Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/
 | 
				
			||||||
 | 
						//
 | 
				
			||||||
 | 
						// Mutually exclusive with claim and requiredValue.
 | 
				
			||||||
 | 
						// +optional
 | 
				
			||||||
 | 
						Expression string `json:"expression,omitempty"`
 | 
				
			||||||
 | 
						// message customizes the returned error message when expression returns false.
 | 
				
			||||||
 | 
						// message is a literal string.
 | 
				
			||||||
 | 
						// Mutually exclusive with claim and requiredValue.
 | 
				
			||||||
 | 
						// +optional
 | 
				
			||||||
 | 
						Message string `json:"message,omitempty"`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// ClaimMappings provides the configuration for claim mapping
 | 
					// ClaimMappings provides the configuration for claim mapping
 | 
				
			||||||
@@ -240,6 +269,7 @@ type ClaimMappings struct {
 | 
				
			|||||||
	// username represents an option for the username attribute.
 | 
						// username represents an option for the username attribute.
 | 
				
			||||||
	// The claim's value must be a singular string.
 | 
						// The claim's value must be a singular string.
 | 
				
			||||||
	// Same as the --oidc-username-claim and --oidc-username-prefix flags.
 | 
						// Same as the --oidc-username-claim and --oidc-username-prefix flags.
 | 
				
			||||||
 | 
						// If username.expression is set, the expression must produce a string value.
 | 
				
			||||||
	//
 | 
						//
 | 
				
			||||||
	// In the flag based approach, the --oidc-username-claim and --oidc-username-prefix are optional. If --oidc-username-claim is not set,
 | 
						// In the flag based approach, the --oidc-username-claim and --oidc-username-prefix are optional. If --oidc-username-claim is not set,
 | 
				
			||||||
	// the default value is "sub". For the authentication config, there is no defaulting for claim or prefix. The claim and prefix must be set explicitly.
 | 
						// the default value is "sub". For the authentication config, there is no defaulting for claim or prefix. The claim and prefix must be set explicitly.
 | 
				
			||||||
@@ -254,19 +284,136 @@ type ClaimMappings struct {
 | 
				
			|||||||
	Username PrefixedClaimOrExpression `json:"username"`
 | 
						Username PrefixedClaimOrExpression `json:"username"`
 | 
				
			||||||
	// groups represents an option for the groups attribute.
 | 
						// groups represents an option for the groups attribute.
 | 
				
			||||||
	// The claim's value must be a string or string array claim.
 | 
						// The claim's value must be a string or string array claim.
 | 
				
			||||||
	// // If groups.claim is set, the prefix must be specified (and can be the empty string).
 | 
						// If groups.claim is set, the prefix must be specified (and can be the empty string).
 | 
				
			||||||
 | 
						// If groups.expression is set, the expression must produce a string or string array value.
 | 
				
			||||||
 | 
						//  "", [], and null values are treated as the group mapping not being present.
 | 
				
			||||||
	// +optional
 | 
						// +optional
 | 
				
			||||||
	Groups PrefixedClaimOrExpression `json:"groups,omitempty"`
 | 
						Groups PrefixedClaimOrExpression `json:"groups,omitempty"`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// uid represents an option for the uid attribute.
 | 
				
			||||||
 | 
						// Claim must be a singular string claim.
 | 
				
			||||||
 | 
						// If uid.expression is set, the expression must produce a string value.
 | 
				
			||||||
 | 
						// +optional
 | 
				
			||||||
 | 
						UID ClaimOrExpression `json:"uid"`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// extra represents an option for the extra attribute.
 | 
				
			||||||
 | 
						// expression must produce a string or string array value.
 | 
				
			||||||
 | 
						// If the value is empty, the extra mapping will not be present.
 | 
				
			||||||
 | 
						//
 | 
				
			||||||
 | 
						// hard-coded extra key/value
 | 
				
			||||||
 | 
						// - key: "foo"
 | 
				
			||||||
 | 
						//   valueExpression: "'bar'"
 | 
				
			||||||
 | 
						// This will result in an extra attribute - foo: ["bar"]
 | 
				
			||||||
 | 
						//
 | 
				
			||||||
 | 
						// hard-coded key, value copying claim value
 | 
				
			||||||
 | 
						// - key: "foo"
 | 
				
			||||||
 | 
						//   valueExpression: "claims.some_claim"
 | 
				
			||||||
 | 
						// This will result in an extra attribute - foo: [value of some_claim]
 | 
				
			||||||
 | 
						//
 | 
				
			||||||
 | 
						// hard-coded key, value derived from claim value
 | 
				
			||||||
 | 
						// - key: "admin"
 | 
				
			||||||
 | 
						//   valueExpression: '(has(claims.is_admin) && claims.is_admin) ? "true":""'
 | 
				
			||||||
 | 
						// This will result in:
 | 
				
			||||||
 | 
						//  - if is_admin claim is present and true, extra attribute - admin: ["true"]
 | 
				
			||||||
 | 
						//  - if is_admin claim is present and false or is_admin claim is not present, no extra attribute will be added
 | 
				
			||||||
 | 
						//
 | 
				
			||||||
 | 
						// +optional
 | 
				
			||||||
 | 
						Extra []ExtraMapping `json:"extra,omitempty"`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// PrefixedClaimOrExpression provides the configuration for a single prefixed claim or expression.
 | 
					// PrefixedClaimOrExpression provides the configuration for a single prefixed claim or expression.
 | 
				
			||||||
type PrefixedClaimOrExpression struct {
 | 
					type PrefixedClaimOrExpression struct {
 | 
				
			||||||
	// claim is the JWT claim to use.
 | 
						// claim is the JWT claim to use.
 | 
				
			||||||
 | 
						// Mutually exclusive with expression.
 | 
				
			||||||
	// +optional
 | 
						// +optional
 | 
				
			||||||
	Claim string `json:"claim"`
 | 
						Claim string `json:"claim,omitempty"`
 | 
				
			||||||
	// prefix is prepended to claim's value to prevent clashes with existing names.
 | 
						// prefix is prepended to claim's value to prevent clashes with existing names.
 | 
				
			||||||
 | 
						// prefix needs to be set if claim is set and can be the empty string.
 | 
				
			||||||
 | 
						// Mutually exclusive with expression.
 | 
				
			||||||
 | 
						// +optional
 | 
				
			||||||
 | 
						Prefix *string `json:"prefix,omitempty"`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// expression represents the expression which will be evaluated by CEL.
 | 
				
			||||||
 | 
						//
 | 
				
			||||||
 | 
						// CEL expressions have access to the contents of the token claims, organized into CEL variable:
 | 
				
			||||||
 | 
						// - 'claims' is a map of claim names to claim values.
 | 
				
			||||||
 | 
						//   For example, a variable named 'sub' can be accessed as 'claims.sub'.
 | 
				
			||||||
 | 
						//   Nested claims can be accessed using dot notation, e.g. 'claims.email.verified'.
 | 
				
			||||||
 | 
						//
 | 
				
			||||||
 | 
						// Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/
 | 
				
			||||||
 | 
						//
 | 
				
			||||||
 | 
						// Mutually exclusive with claim and prefix.
 | 
				
			||||||
 | 
						// +optional
 | 
				
			||||||
 | 
						Expression string `json:"expression,omitempty"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ClaimOrExpression provides the configuration for a single claim or expression.
 | 
				
			||||||
 | 
					type ClaimOrExpression struct {
 | 
				
			||||||
 | 
						// claim is the JWT claim to use.
 | 
				
			||||||
 | 
						// Either claim or expression must be set.
 | 
				
			||||||
 | 
						// Mutually exclusive with expression.
 | 
				
			||||||
 | 
						// +optional
 | 
				
			||||||
 | 
						Claim string `json:"claim,omitempty"`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// expression represents the expression which will be evaluated by CEL.
 | 
				
			||||||
 | 
						//
 | 
				
			||||||
 | 
						// CEL expressions have access to the contents of the token claims, organized into CEL variable:
 | 
				
			||||||
 | 
						// - 'claims' is a map of claim names to claim values.
 | 
				
			||||||
 | 
						//   For example, a variable named 'sub' can be accessed as 'claims.sub'.
 | 
				
			||||||
 | 
						//   Nested claims can be accessed using dot notation, e.g. 'claims.email.verified'.
 | 
				
			||||||
 | 
						//
 | 
				
			||||||
 | 
						// Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/
 | 
				
			||||||
 | 
						//
 | 
				
			||||||
 | 
						// Mutually exclusive with claim.
 | 
				
			||||||
 | 
						// +optional
 | 
				
			||||||
 | 
						Expression string `json:"expression,omitempty"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ExtraMapping provides the configuration for a single extra mapping.
 | 
				
			||||||
 | 
					type ExtraMapping struct {
 | 
				
			||||||
 | 
						// key is a string to use as the extra attribute key.
 | 
				
			||||||
 | 
						// key must be a domain-prefix path (e.g. example.org/foo). All characters before the first "/" must be a valid
 | 
				
			||||||
 | 
						// subdomain as defined by RFC 1123. All characters trailing the first "/" must
 | 
				
			||||||
 | 
						// be valid HTTP Path characters as defined by RFC 3986.
 | 
				
			||||||
 | 
						// key must be lowercase.
 | 
				
			||||||
	// +required
 | 
						// +required
 | 
				
			||||||
	Prefix *string `json:"prefix"`
 | 
						Key string `json:"key"`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// valueExpression is a CEL expression to extract extra attribute value.
 | 
				
			||||||
 | 
						// valueExpression must produce a string or string array value.
 | 
				
			||||||
 | 
						// "", [], and null values are treated as the extra mapping not being present.
 | 
				
			||||||
 | 
						// Empty string values contained within a string array are filtered out.
 | 
				
			||||||
 | 
						//
 | 
				
			||||||
 | 
						// CEL expressions have access to the contents of the token claims, organized into CEL variable:
 | 
				
			||||||
 | 
						// - 'claims' is a map of claim names to claim values.
 | 
				
			||||||
 | 
						//   For example, a variable named 'sub' can be accessed as 'claims.sub'.
 | 
				
			||||||
 | 
						//   Nested claims can be accessed using dot notation, e.g. 'claims.email.verified'.
 | 
				
			||||||
 | 
						//
 | 
				
			||||||
 | 
						// Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/
 | 
				
			||||||
 | 
						//
 | 
				
			||||||
 | 
						// +required
 | 
				
			||||||
 | 
						ValueExpression string `json:"valueExpression"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// UserValidationRule provides the configuration for a single user info validation rule.
 | 
				
			||||||
 | 
					type UserValidationRule struct {
 | 
				
			||||||
 | 
						// expression represents the expression which will be evaluated by CEL.
 | 
				
			||||||
 | 
						// Must return true for the validation to pass.
 | 
				
			||||||
 | 
						//
 | 
				
			||||||
 | 
						// CEL expressions have access to the contents of UserInfo, organized into CEL variable:
 | 
				
			||||||
 | 
						// - 'user' - authentication.k8s.io/v1, Kind=UserInfo object
 | 
				
			||||||
 | 
						//    Refer to https://github.com/kubernetes/api/blob/release-1.28/authentication/v1/types.go#L105-L122 for the definition.
 | 
				
			||||||
 | 
						//    API documentation: https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.28/#userinfo-v1-authentication-k8s-io
 | 
				
			||||||
 | 
						//
 | 
				
			||||||
 | 
						// Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/
 | 
				
			||||||
 | 
						//
 | 
				
			||||||
 | 
						// +required
 | 
				
			||||||
 | 
						Expression string `json:"expression"`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// message customizes the returned error message when rule returns false.
 | 
				
			||||||
 | 
						// message is a literal string.
 | 
				
			||||||
 | 
						// +optional
 | 
				
			||||||
 | 
						Message string `json:"message,omitempty"`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
 | 
					// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -96,6 +96,16 @@ func RegisterConversions(s *runtime.Scheme) error {
 | 
				
			|||||||
	}); err != nil {
 | 
						}); err != nil {
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
						if err := s.AddGeneratedConversionFunc((*ClaimOrExpression)(nil), (*apiserver.ClaimOrExpression)(nil), func(a, b interface{}, scope conversion.Scope) error {
 | 
				
			||||||
 | 
							return Convert_v1alpha1_ClaimOrExpression_To_apiserver_ClaimOrExpression(a.(*ClaimOrExpression), b.(*apiserver.ClaimOrExpression), scope)
 | 
				
			||||||
 | 
						}); err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if err := s.AddGeneratedConversionFunc((*apiserver.ClaimOrExpression)(nil), (*ClaimOrExpression)(nil), func(a, b interface{}, scope conversion.Scope) error {
 | 
				
			||||||
 | 
							return Convert_apiserver_ClaimOrExpression_To_v1alpha1_ClaimOrExpression(a.(*apiserver.ClaimOrExpression), b.(*ClaimOrExpression), scope)
 | 
				
			||||||
 | 
						}); err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
	if err := s.AddGeneratedConversionFunc((*ClaimValidationRule)(nil), (*apiserver.ClaimValidationRule)(nil), func(a, b interface{}, scope conversion.Scope) error {
 | 
						if err := s.AddGeneratedConversionFunc((*ClaimValidationRule)(nil), (*apiserver.ClaimValidationRule)(nil), func(a, b interface{}, scope conversion.Scope) error {
 | 
				
			||||||
		return Convert_v1alpha1_ClaimValidationRule_To_apiserver_ClaimValidationRule(a.(*ClaimValidationRule), b.(*apiserver.ClaimValidationRule), scope)
 | 
							return Convert_v1alpha1_ClaimValidationRule_To_apiserver_ClaimValidationRule(a.(*ClaimValidationRule), b.(*apiserver.ClaimValidationRule), scope)
 | 
				
			||||||
	}); err != nil {
 | 
						}); err != nil {
 | 
				
			||||||
@@ -131,6 +141,16 @@ func RegisterConversions(s *runtime.Scheme) error {
 | 
				
			|||||||
	}); err != nil {
 | 
						}); err != nil {
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
						if err := s.AddGeneratedConversionFunc((*ExtraMapping)(nil), (*apiserver.ExtraMapping)(nil), func(a, b interface{}, scope conversion.Scope) error {
 | 
				
			||||||
 | 
							return Convert_v1alpha1_ExtraMapping_To_apiserver_ExtraMapping(a.(*ExtraMapping), b.(*apiserver.ExtraMapping), scope)
 | 
				
			||||||
 | 
						}); err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if err := s.AddGeneratedConversionFunc((*apiserver.ExtraMapping)(nil), (*ExtraMapping)(nil), func(a, b interface{}, scope conversion.Scope) error {
 | 
				
			||||||
 | 
							return Convert_apiserver_ExtraMapping_To_v1alpha1_ExtraMapping(a.(*apiserver.ExtraMapping), b.(*ExtraMapping), scope)
 | 
				
			||||||
 | 
						}); err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
	if err := s.AddGeneratedConversionFunc((*Issuer)(nil), (*apiserver.Issuer)(nil), func(a, b interface{}, scope conversion.Scope) error {
 | 
						if err := s.AddGeneratedConversionFunc((*Issuer)(nil), (*apiserver.Issuer)(nil), func(a, b interface{}, scope conversion.Scope) error {
 | 
				
			||||||
		return Convert_v1alpha1_Issuer_To_apiserver_Issuer(a.(*Issuer), b.(*apiserver.Issuer), scope)
 | 
							return Convert_v1alpha1_Issuer_To_apiserver_Issuer(a.(*Issuer), b.(*apiserver.Issuer), scope)
 | 
				
			||||||
	}); err != nil {
 | 
						}); err != nil {
 | 
				
			||||||
@@ -211,6 +231,16 @@ func RegisterConversions(s *runtime.Scheme) error {
 | 
				
			|||||||
	}); err != nil {
 | 
						}); err != nil {
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
						if err := s.AddGeneratedConversionFunc((*UserValidationRule)(nil), (*apiserver.UserValidationRule)(nil), func(a, b interface{}, scope conversion.Scope) error {
 | 
				
			||||||
 | 
							return Convert_v1alpha1_UserValidationRule_To_apiserver_UserValidationRule(a.(*UserValidationRule), b.(*apiserver.UserValidationRule), scope)
 | 
				
			||||||
 | 
						}); err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if err := s.AddGeneratedConversionFunc((*apiserver.UserValidationRule)(nil), (*UserValidationRule)(nil), func(a, b interface{}, scope conversion.Scope) error {
 | 
				
			||||||
 | 
							return Convert_apiserver_UserValidationRule_To_v1alpha1_UserValidationRule(a.(*apiserver.UserValidationRule), b.(*UserValidationRule), scope)
 | 
				
			||||||
 | 
						}); err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
	if err := s.AddGeneratedConversionFunc((*WebhookConfiguration)(nil), (*apiserver.WebhookConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error {
 | 
						if err := s.AddGeneratedConversionFunc((*WebhookConfiguration)(nil), (*apiserver.WebhookConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error {
 | 
				
			||||||
		return Convert_v1alpha1_WebhookConfiguration_To_apiserver_WebhookConfiguration(a.(*WebhookConfiguration), b.(*apiserver.WebhookConfiguration), scope)
 | 
							return Convert_v1alpha1_WebhookConfiguration_To_apiserver_WebhookConfiguration(a.(*WebhookConfiguration), b.(*apiserver.WebhookConfiguration), scope)
 | 
				
			||||||
	}); err != nil {
 | 
						}); err != nil {
 | 
				
			||||||
@@ -364,6 +394,10 @@ func autoConvert_v1alpha1_ClaimMappings_To_apiserver_ClaimMappings(in *ClaimMapp
 | 
				
			|||||||
	if err := Convert_v1alpha1_PrefixedClaimOrExpression_To_apiserver_PrefixedClaimOrExpression(&in.Groups, &out.Groups, s); err != nil {
 | 
						if err := Convert_v1alpha1_PrefixedClaimOrExpression_To_apiserver_PrefixedClaimOrExpression(&in.Groups, &out.Groups, s); err != nil {
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
						if err := Convert_v1alpha1_ClaimOrExpression_To_apiserver_ClaimOrExpression(&in.UID, &out.UID, s); err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						out.Extra = *(*[]apiserver.ExtraMapping)(unsafe.Pointer(&in.Extra))
 | 
				
			||||||
	return nil
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -379,6 +413,10 @@ func autoConvert_apiserver_ClaimMappings_To_v1alpha1_ClaimMappings(in *apiserver
 | 
				
			|||||||
	if err := Convert_apiserver_PrefixedClaimOrExpression_To_v1alpha1_PrefixedClaimOrExpression(&in.Groups, &out.Groups, s); err != nil {
 | 
						if err := Convert_apiserver_PrefixedClaimOrExpression_To_v1alpha1_PrefixedClaimOrExpression(&in.Groups, &out.Groups, s); err != nil {
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
						if err := Convert_apiserver_ClaimOrExpression_To_v1alpha1_ClaimOrExpression(&in.UID, &out.UID, s); err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						out.Extra = *(*[]ExtraMapping)(unsafe.Pointer(&in.Extra))
 | 
				
			||||||
	return nil
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -387,9 +425,33 @@ func Convert_apiserver_ClaimMappings_To_v1alpha1_ClaimMappings(in *apiserver.Cla
 | 
				
			|||||||
	return autoConvert_apiserver_ClaimMappings_To_v1alpha1_ClaimMappings(in, out, s)
 | 
						return autoConvert_apiserver_ClaimMappings_To_v1alpha1_ClaimMappings(in, out, s)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func autoConvert_v1alpha1_ClaimOrExpression_To_apiserver_ClaimOrExpression(in *ClaimOrExpression, out *apiserver.ClaimOrExpression, s conversion.Scope) error {
 | 
				
			||||||
 | 
						out.Claim = in.Claim
 | 
				
			||||||
 | 
						out.Expression = in.Expression
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Convert_v1alpha1_ClaimOrExpression_To_apiserver_ClaimOrExpression is an autogenerated conversion function.
 | 
				
			||||||
 | 
					func Convert_v1alpha1_ClaimOrExpression_To_apiserver_ClaimOrExpression(in *ClaimOrExpression, out *apiserver.ClaimOrExpression, s conversion.Scope) error {
 | 
				
			||||||
 | 
						return autoConvert_v1alpha1_ClaimOrExpression_To_apiserver_ClaimOrExpression(in, out, s)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func autoConvert_apiserver_ClaimOrExpression_To_v1alpha1_ClaimOrExpression(in *apiserver.ClaimOrExpression, out *ClaimOrExpression, s conversion.Scope) error {
 | 
				
			||||||
 | 
						out.Claim = in.Claim
 | 
				
			||||||
 | 
						out.Expression = in.Expression
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Convert_apiserver_ClaimOrExpression_To_v1alpha1_ClaimOrExpression is an autogenerated conversion function.
 | 
				
			||||||
 | 
					func Convert_apiserver_ClaimOrExpression_To_v1alpha1_ClaimOrExpression(in *apiserver.ClaimOrExpression, out *ClaimOrExpression, s conversion.Scope) error {
 | 
				
			||||||
 | 
						return autoConvert_apiserver_ClaimOrExpression_To_v1alpha1_ClaimOrExpression(in, out, s)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func autoConvert_v1alpha1_ClaimValidationRule_To_apiserver_ClaimValidationRule(in *ClaimValidationRule, out *apiserver.ClaimValidationRule, s conversion.Scope) error {
 | 
					func autoConvert_v1alpha1_ClaimValidationRule_To_apiserver_ClaimValidationRule(in *ClaimValidationRule, out *apiserver.ClaimValidationRule, s conversion.Scope) error {
 | 
				
			||||||
	out.Claim = in.Claim
 | 
						out.Claim = in.Claim
 | 
				
			||||||
	out.RequiredValue = in.RequiredValue
 | 
						out.RequiredValue = in.RequiredValue
 | 
				
			||||||
 | 
						out.Expression = in.Expression
 | 
				
			||||||
 | 
						out.Message = in.Message
 | 
				
			||||||
	return nil
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -401,6 +463,8 @@ func Convert_v1alpha1_ClaimValidationRule_To_apiserver_ClaimValidationRule(in *C
 | 
				
			|||||||
func autoConvert_apiserver_ClaimValidationRule_To_v1alpha1_ClaimValidationRule(in *apiserver.ClaimValidationRule, out *ClaimValidationRule, s conversion.Scope) error {
 | 
					func autoConvert_apiserver_ClaimValidationRule_To_v1alpha1_ClaimValidationRule(in *apiserver.ClaimValidationRule, out *ClaimValidationRule, s conversion.Scope) error {
 | 
				
			||||||
	out.Claim = in.Claim
 | 
						out.Claim = in.Claim
 | 
				
			||||||
	out.RequiredValue = in.RequiredValue
 | 
						out.RequiredValue = in.RequiredValue
 | 
				
			||||||
 | 
						out.Expression = in.Expression
 | 
				
			||||||
 | 
						out.Message = in.Message
 | 
				
			||||||
	return nil
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -492,6 +556,28 @@ func Convert_apiserver_EgressSelectorConfiguration_To_v1alpha1_EgressSelectorCon
 | 
				
			|||||||
	return autoConvert_apiserver_EgressSelectorConfiguration_To_v1alpha1_EgressSelectorConfiguration(in, out, s)
 | 
						return autoConvert_apiserver_EgressSelectorConfiguration_To_v1alpha1_EgressSelectorConfiguration(in, out, s)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func autoConvert_v1alpha1_ExtraMapping_To_apiserver_ExtraMapping(in *ExtraMapping, out *apiserver.ExtraMapping, s conversion.Scope) error {
 | 
				
			||||||
 | 
						out.Key = in.Key
 | 
				
			||||||
 | 
						out.ValueExpression = in.ValueExpression
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Convert_v1alpha1_ExtraMapping_To_apiserver_ExtraMapping is an autogenerated conversion function.
 | 
				
			||||||
 | 
					func Convert_v1alpha1_ExtraMapping_To_apiserver_ExtraMapping(in *ExtraMapping, out *apiserver.ExtraMapping, s conversion.Scope) error {
 | 
				
			||||||
 | 
						return autoConvert_v1alpha1_ExtraMapping_To_apiserver_ExtraMapping(in, out, s)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func autoConvert_apiserver_ExtraMapping_To_v1alpha1_ExtraMapping(in *apiserver.ExtraMapping, out *ExtraMapping, s conversion.Scope) error {
 | 
				
			||||||
 | 
						out.Key = in.Key
 | 
				
			||||||
 | 
						out.ValueExpression = in.ValueExpression
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Convert_apiserver_ExtraMapping_To_v1alpha1_ExtraMapping is an autogenerated conversion function.
 | 
				
			||||||
 | 
					func Convert_apiserver_ExtraMapping_To_v1alpha1_ExtraMapping(in *apiserver.ExtraMapping, out *ExtraMapping, s conversion.Scope) error {
 | 
				
			||||||
 | 
						return autoConvert_apiserver_ExtraMapping_To_v1alpha1_ExtraMapping(in, out, s)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func autoConvert_v1alpha1_Issuer_To_apiserver_Issuer(in *Issuer, out *apiserver.Issuer, s conversion.Scope) error {
 | 
					func autoConvert_v1alpha1_Issuer_To_apiserver_Issuer(in *Issuer, out *apiserver.Issuer, s conversion.Scope) error {
 | 
				
			||||||
	out.URL = in.URL
 | 
						out.URL = in.URL
 | 
				
			||||||
	out.CertificateAuthority = in.CertificateAuthority
 | 
						out.CertificateAuthority = in.CertificateAuthority
 | 
				
			||||||
@@ -524,6 +610,7 @@ func autoConvert_v1alpha1_JWTAuthenticator_To_apiserver_JWTAuthenticator(in *JWT
 | 
				
			|||||||
	if err := Convert_v1alpha1_ClaimMappings_To_apiserver_ClaimMappings(&in.ClaimMappings, &out.ClaimMappings, s); err != nil {
 | 
						if err := Convert_v1alpha1_ClaimMappings_To_apiserver_ClaimMappings(&in.ClaimMappings, &out.ClaimMappings, s); err != nil {
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
						out.UserValidationRules = *(*[]apiserver.UserValidationRule)(unsafe.Pointer(&in.UserValidationRules))
 | 
				
			||||||
	return nil
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -540,6 +627,7 @@ func autoConvert_apiserver_JWTAuthenticator_To_v1alpha1_JWTAuthenticator(in *api
 | 
				
			|||||||
	if err := Convert_apiserver_ClaimMappings_To_v1alpha1_ClaimMappings(&in.ClaimMappings, &out.ClaimMappings, s); err != nil {
 | 
						if err := Convert_apiserver_ClaimMappings_To_v1alpha1_ClaimMappings(&in.ClaimMappings, &out.ClaimMappings, s); err != nil {
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
						out.UserValidationRules = *(*[]UserValidationRule)(unsafe.Pointer(&in.UserValidationRules))
 | 
				
			||||||
	return nil
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -551,6 +639,7 @@ func Convert_apiserver_JWTAuthenticator_To_v1alpha1_JWTAuthenticator(in *apiserv
 | 
				
			|||||||
func autoConvert_v1alpha1_PrefixedClaimOrExpression_To_apiserver_PrefixedClaimOrExpression(in *PrefixedClaimOrExpression, out *apiserver.PrefixedClaimOrExpression, s conversion.Scope) error {
 | 
					func autoConvert_v1alpha1_PrefixedClaimOrExpression_To_apiserver_PrefixedClaimOrExpression(in *PrefixedClaimOrExpression, out *apiserver.PrefixedClaimOrExpression, s conversion.Scope) error {
 | 
				
			||||||
	out.Claim = in.Claim
 | 
						out.Claim = in.Claim
 | 
				
			||||||
	out.Prefix = (*string)(unsafe.Pointer(in.Prefix))
 | 
						out.Prefix = (*string)(unsafe.Pointer(in.Prefix))
 | 
				
			||||||
 | 
						out.Expression = in.Expression
 | 
				
			||||||
	return nil
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -562,6 +651,7 @@ func Convert_v1alpha1_PrefixedClaimOrExpression_To_apiserver_PrefixedClaimOrExpr
 | 
				
			|||||||
func autoConvert_apiserver_PrefixedClaimOrExpression_To_v1alpha1_PrefixedClaimOrExpression(in *apiserver.PrefixedClaimOrExpression, out *PrefixedClaimOrExpression, s conversion.Scope) error {
 | 
					func autoConvert_apiserver_PrefixedClaimOrExpression_To_v1alpha1_PrefixedClaimOrExpression(in *apiserver.PrefixedClaimOrExpression, out *PrefixedClaimOrExpression, s conversion.Scope) error {
 | 
				
			||||||
	out.Claim = in.Claim
 | 
						out.Claim = in.Claim
 | 
				
			||||||
	out.Prefix = (*string)(unsafe.Pointer(in.Prefix))
 | 
						out.Prefix = (*string)(unsafe.Pointer(in.Prefix))
 | 
				
			||||||
 | 
						out.Expression = in.Expression
 | 
				
			||||||
	return nil
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -678,6 +768,28 @@ func Convert_apiserver_UDSTransport_To_v1alpha1_UDSTransport(in *apiserver.UDSTr
 | 
				
			|||||||
	return autoConvert_apiserver_UDSTransport_To_v1alpha1_UDSTransport(in, out, s)
 | 
						return autoConvert_apiserver_UDSTransport_To_v1alpha1_UDSTransport(in, out, s)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func autoConvert_v1alpha1_UserValidationRule_To_apiserver_UserValidationRule(in *UserValidationRule, out *apiserver.UserValidationRule, s conversion.Scope) error {
 | 
				
			||||||
 | 
						out.Expression = in.Expression
 | 
				
			||||||
 | 
						out.Message = in.Message
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Convert_v1alpha1_UserValidationRule_To_apiserver_UserValidationRule is an autogenerated conversion function.
 | 
				
			||||||
 | 
					func Convert_v1alpha1_UserValidationRule_To_apiserver_UserValidationRule(in *UserValidationRule, out *apiserver.UserValidationRule, s conversion.Scope) error {
 | 
				
			||||||
 | 
						return autoConvert_v1alpha1_UserValidationRule_To_apiserver_UserValidationRule(in, out, s)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func autoConvert_apiserver_UserValidationRule_To_v1alpha1_UserValidationRule(in *apiserver.UserValidationRule, out *UserValidationRule, s conversion.Scope) error {
 | 
				
			||||||
 | 
						out.Expression = in.Expression
 | 
				
			||||||
 | 
						out.Message = in.Message
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Convert_apiserver_UserValidationRule_To_v1alpha1_UserValidationRule is an autogenerated conversion function.
 | 
				
			||||||
 | 
					func Convert_apiserver_UserValidationRule_To_v1alpha1_UserValidationRule(in *apiserver.UserValidationRule, out *UserValidationRule, s conversion.Scope) error {
 | 
				
			||||||
 | 
						return autoConvert_apiserver_UserValidationRule_To_v1alpha1_UserValidationRule(in, out, s)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func autoConvert_v1alpha1_WebhookConfiguration_To_apiserver_WebhookConfiguration(in *WebhookConfiguration, out *apiserver.WebhookConfiguration, s conversion.Scope) error {
 | 
					func autoConvert_v1alpha1_WebhookConfiguration_To_apiserver_WebhookConfiguration(in *WebhookConfiguration, out *apiserver.WebhookConfiguration, s conversion.Scope) error {
 | 
				
			||||||
	out.AuthorizedTTL = in.AuthorizedTTL
 | 
						out.AuthorizedTTL = in.AuthorizedTTL
 | 
				
			||||||
	out.UnauthorizedTTL = in.UnauthorizedTTL
 | 
						out.UnauthorizedTTL = in.UnauthorizedTTL
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -168,6 +168,12 @@ func (in *ClaimMappings) DeepCopyInto(out *ClaimMappings) {
 | 
				
			|||||||
	*out = *in
 | 
						*out = *in
 | 
				
			||||||
	in.Username.DeepCopyInto(&out.Username)
 | 
						in.Username.DeepCopyInto(&out.Username)
 | 
				
			||||||
	in.Groups.DeepCopyInto(&out.Groups)
 | 
						in.Groups.DeepCopyInto(&out.Groups)
 | 
				
			||||||
 | 
						out.UID = in.UID
 | 
				
			||||||
 | 
						if in.Extra != nil {
 | 
				
			||||||
 | 
							in, out := &in.Extra, &out.Extra
 | 
				
			||||||
 | 
							*out = make([]ExtraMapping, len(*in))
 | 
				
			||||||
 | 
							copy(*out, *in)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
	return
 | 
						return
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -181,6 +187,22 @@ func (in *ClaimMappings) DeepCopy() *ClaimMappings {
 | 
				
			|||||||
	return out
 | 
						return out
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 | 
				
			||||||
 | 
					func (in *ClaimOrExpression) DeepCopyInto(out *ClaimOrExpression) {
 | 
				
			||||||
 | 
						*out = *in
 | 
				
			||||||
 | 
						return
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClaimOrExpression.
 | 
				
			||||||
 | 
					func (in *ClaimOrExpression) DeepCopy() *ClaimOrExpression {
 | 
				
			||||||
 | 
						if in == nil {
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						out := new(ClaimOrExpression)
 | 
				
			||||||
 | 
						in.DeepCopyInto(out)
 | 
				
			||||||
 | 
						return out
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 | 
					// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 | 
				
			||||||
func (in *ClaimValidationRule) DeepCopyInto(out *ClaimValidationRule) {
 | 
					func (in *ClaimValidationRule) DeepCopyInto(out *ClaimValidationRule) {
 | 
				
			||||||
	*out = *in
 | 
						*out = *in
 | 
				
			||||||
@@ -267,6 +289,22 @@ func (in *EgressSelectorConfiguration) DeepCopyObject() runtime.Object {
 | 
				
			|||||||
	return nil
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 | 
				
			||||||
 | 
					func (in *ExtraMapping) DeepCopyInto(out *ExtraMapping) {
 | 
				
			||||||
 | 
						*out = *in
 | 
				
			||||||
 | 
						return
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExtraMapping.
 | 
				
			||||||
 | 
					func (in *ExtraMapping) DeepCopy() *ExtraMapping {
 | 
				
			||||||
 | 
						if in == nil {
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						out := new(ExtraMapping)
 | 
				
			||||||
 | 
						in.DeepCopyInto(out)
 | 
				
			||||||
 | 
						return out
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 | 
					// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 | 
				
			||||||
func (in *Issuer) DeepCopyInto(out *Issuer) {
 | 
					func (in *Issuer) DeepCopyInto(out *Issuer) {
 | 
				
			||||||
	*out = *in
 | 
						*out = *in
 | 
				
			||||||
@@ -298,6 +336,11 @@ func (in *JWTAuthenticator) DeepCopyInto(out *JWTAuthenticator) {
 | 
				
			|||||||
		copy(*out, *in)
 | 
							copy(*out, *in)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	in.ClaimMappings.DeepCopyInto(&out.ClaimMappings)
 | 
						in.ClaimMappings.DeepCopyInto(&out.ClaimMappings)
 | 
				
			||||||
 | 
						if in.UserValidationRules != nil {
 | 
				
			||||||
 | 
							in, out := &in.UserValidationRules, &out.UserValidationRules
 | 
				
			||||||
 | 
							*out = make([]UserValidationRule, len(*in))
 | 
				
			||||||
 | 
							copy(*out, *in)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
	return
 | 
						return
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -437,6 +480,22 @@ func (in *UDSTransport) DeepCopy() *UDSTransport {
 | 
				
			|||||||
	return out
 | 
						return out
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 | 
				
			||||||
 | 
					func (in *UserValidationRule) DeepCopyInto(out *UserValidationRule) {
 | 
				
			||||||
 | 
						*out = *in
 | 
				
			||||||
 | 
						return
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UserValidationRule.
 | 
				
			||||||
 | 
					func (in *UserValidationRule) DeepCopy() *UserValidationRule {
 | 
				
			||||||
 | 
						if in == nil {
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						out := new(UserValidationRule)
 | 
				
			||||||
 | 
						in.DeepCopyInto(out)
 | 
				
			||||||
 | 
						return out
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 | 
					// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 | 
				
			||||||
func (in *WebhookConfiguration) DeepCopyInto(out *WebhookConfiguration) {
 | 
					func (in *WebhookConfiguration) DeepCopyInto(out *WebhookConfiguration) {
 | 
				
			||||||
	*out = *in
 | 
						*out = *in
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -31,6 +31,7 @@ import (
 | 
				
			|||||||
	utilvalidation "k8s.io/apimachinery/pkg/util/validation"
 | 
						utilvalidation "k8s.io/apimachinery/pkg/util/validation"
 | 
				
			||||||
	"k8s.io/apimachinery/pkg/util/validation/field"
 | 
						"k8s.io/apimachinery/pkg/util/validation/field"
 | 
				
			||||||
	api "k8s.io/apiserver/pkg/apis/apiserver"
 | 
						api "k8s.io/apiserver/pkg/apis/apiserver"
 | 
				
			||||||
 | 
						authenticationcel "k8s.io/apiserver/pkg/authentication/cel"
 | 
				
			||||||
	authorizationcel "k8s.io/apiserver/pkg/authorization/cel"
 | 
						authorizationcel "k8s.io/apiserver/pkg/authorization/cel"
 | 
				
			||||||
	"k8s.io/apiserver/pkg/cel"
 | 
						"k8s.io/apiserver/pkg/cel"
 | 
				
			||||||
	"k8s.io/apiserver/pkg/cel/environment"
 | 
						"k8s.io/apiserver/pkg/cel/environment"
 | 
				
			||||||
@@ -75,26 +76,32 @@ func ValidateAuthenticationConfiguration(c *api.AuthenticationConfiguration) fie
 | 
				
			|||||||
	// check and add validation for duplicate issuers.
 | 
						// check and add validation for duplicate issuers.
 | 
				
			||||||
	for i, a := range c.JWT {
 | 
						for i, a := range c.JWT {
 | 
				
			||||||
		fldPath := root.Index(i)
 | 
							fldPath := root.Index(i)
 | 
				
			||||||
		allErrs = append(allErrs, validateJWTAuthenticator(a, fldPath)...)
 | 
							_, errs := validateJWTAuthenticator(a, fldPath, utilfeature.DefaultFeatureGate.Enabled(features.StructuredAuthenticationConfiguration))
 | 
				
			||||||
 | 
							allErrs = append(allErrs, errs...)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return allErrs
 | 
						return allErrs
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// ValidateJWTAuthenticator validates a given JWTAuthenticator.
 | 
					// CompileAndValidateJWTAuthenticator validates a given JWTAuthenticator and returns a CELMapper with the compiled
 | 
				
			||||||
 | 
					// CEL expressions for claim mappings and validation rules.
 | 
				
			||||||
// This is exported for use in oidc package.
 | 
					// This is exported for use in oidc package.
 | 
				
			||||||
func ValidateJWTAuthenticator(authenticator api.JWTAuthenticator) field.ErrorList {
 | 
					func CompileAndValidateJWTAuthenticator(authenticator api.JWTAuthenticator) (authenticationcel.CELMapper, field.ErrorList) {
 | 
				
			||||||
	return validateJWTAuthenticator(authenticator, nil)
 | 
						return validateJWTAuthenticator(authenticator, nil, utilfeature.DefaultFeatureGate.Enabled(features.StructuredAuthenticationConfiguration))
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func validateJWTAuthenticator(authenticator api.JWTAuthenticator, fldPath *field.Path) field.ErrorList {
 | 
					func validateJWTAuthenticator(authenticator api.JWTAuthenticator, fldPath *field.Path, structuredAuthnFeatureEnabled bool) (authenticationcel.CELMapper, field.ErrorList) {
 | 
				
			||||||
	var allErrs field.ErrorList
 | 
						var allErrs field.ErrorList
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	allErrs = append(allErrs, validateIssuer(authenticator.Issuer, fldPath.Child("issuer"))...)
 | 
						compiler := authenticationcel.NewCompiler(environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion()))
 | 
				
			||||||
	allErrs = append(allErrs, validateClaimValidationRules(authenticator.ClaimValidationRules, fldPath.Child("claimValidationRules"))...)
 | 
						mapper := &authenticationcel.CELMapper{}
 | 
				
			||||||
	allErrs = append(allErrs, validateClaimMappings(authenticator.ClaimMappings, fldPath.Child("claimMappings"))...)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return allErrs
 | 
						allErrs = append(allErrs, validateIssuer(authenticator.Issuer, fldPath.Child("issuer"))...)
 | 
				
			||||||
 | 
						allErrs = append(allErrs, validateClaimValidationRules(compiler, mapper, authenticator.ClaimValidationRules, fldPath.Child("claimValidationRules"), structuredAuthnFeatureEnabled)...)
 | 
				
			||||||
 | 
						allErrs = append(allErrs, validateClaimMappings(compiler, mapper, authenticator.ClaimMappings, fldPath.Child("claimMappings"), structuredAuthnFeatureEnabled)...)
 | 
				
			||||||
 | 
						allErrs = append(allErrs, validateUserValidationRules(compiler, mapper, authenticator.UserValidationRules, fldPath.Child("userValidationRules"), structuredAuthnFeatureEnabled)...)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return *mapper, allErrs
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func validateIssuer(issuer api.Issuer, fldPath *field.Path) field.ErrorList {
 | 
					func validateIssuer(issuer api.Issuer, fldPath *field.Path) field.ErrorList {
 | 
				
			||||||
@@ -174,48 +181,250 @@ func validateCertificateAuthority(certificateAuthority string, fldPath *field.Pa
 | 
				
			|||||||
	return allErrs
 | 
						return allErrs
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func validateClaimValidationRules(rules []api.ClaimValidationRule, fldPath *field.Path) field.ErrorList {
 | 
					func validateClaimValidationRules(compiler authenticationcel.Compiler, celMapper *authenticationcel.CELMapper, rules []api.ClaimValidationRule, fldPath *field.Path, structuredAuthnFeatureEnabled bool) field.ErrorList {
 | 
				
			||||||
	var allErrs field.ErrorList
 | 
						var allErrs field.ErrorList
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	seenClaims := sets.NewString()
 | 
						seenClaims := sets.NewString()
 | 
				
			||||||
 | 
						seenExpressions := sets.NewString()
 | 
				
			||||||
 | 
						var compilationResults []authenticationcel.CompilationResult
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for i, rule := range rules {
 | 
						for i, rule := range rules {
 | 
				
			||||||
		fldPath := fldPath.Index(i)
 | 
							fldPath := fldPath.Index(i)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if len(rule.Claim) == 0 {
 | 
							if len(rule.Expression) > 0 && !structuredAuthnFeatureEnabled {
 | 
				
			||||||
			allErrs = append(allErrs, field.Required(fldPath.Child("claim"), "claim name is required"))
 | 
								allErrs = append(allErrs, field.Invalid(fldPath.Child("expression"), rule.Expression, "expression is not supported when StructuredAuthenticationConfiguration feature gate is disabled"))
 | 
				
			||||||
			continue
 | 
					 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							switch {
 | 
				
			||||||
 | 
							case len(rule.Claim) > 0 && len(rule.Expression) > 0:
 | 
				
			||||||
 | 
								allErrs = append(allErrs, field.Invalid(fldPath, rule.Claim, "claim and expression can't both be set"))
 | 
				
			||||||
 | 
							case len(rule.Claim) == 0 && len(rule.Expression) == 0:
 | 
				
			||||||
 | 
								allErrs = append(allErrs, field.Required(fldPath, "claim or expression is required"))
 | 
				
			||||||
 | 
							case len(rule.Claim) > 0:
 | 
				
			||||||
 | 
								if len(rule.Message) > 0 {
 | 
				
			||||||
 | 
									allErrs = append(allErrs, field.Invalid(fldPath.Child("message"), rule.Message, "message can't be set when claim is set"))
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
			if seenClaims.Has(rule.Claim) {
 | 
								if seenClaims.Has(rule.Claim) {
 | 
				
			||||||
				allErrs = append(allErrs, field.Duplicate(fldPath.Child("claim"), rule.Claim))
 | 
									allErrs = append(allErrs, field.Duplicate(fldPath.Child("claim"), rule.Claim))
 | 
				
			||||||
			continue
 | 
					 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			seenClaims.Insert(rule.Claim)
 | 
								seenClaims.Insert(rule.Claim)
 | 
				
			||||||
 | 
							case len(rule.Expression) > 0:
 | 
				
			||||||
 | 
								if len(rule.RequiredValue) > 0 {
 | 
				
			||||||
 | 
									allErrs = append(allErrs, field.Invalid(fldPath.Child("requiredValue"), rule.RequiredValue, "requiredValue can't be set when expression is set"))
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								if seenExpressions.Has(rule.Expression) {
 | 
				
			||||||
 | 
									allErrs = append(allErrs, field.Duplicate(fldPath.Child("expression"), rule.Expression))
 | 
				
			||||||
 | 
									continue
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								seenExpressions.Insert(rule.Expression)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								compilationResult, err := compileClaimsCELExpression(compiler, &authenticationcel.ClaimValidationCondition{
 | 
				
			||||||
 | 
									Expression: rule.Expression,
 | 
				
			||||||
 | 
								}, fldPath.Child("expression"))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									allErrs = append(allErrs, err)
 | 
				
			||||||
 | 
									continue
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								if compilationResult != nil {
 | 
				
			||||||
 | 
									compilationResults = append(compilationResults, *compilationResult)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if structuredAuthnFeatureEnabled && len(compilationResults) > 0 {
 | 
				
			||||||
 | 
							celMapper.ClaimValidationRules = authenticationcel.NewClaimsMapper(compilationResults)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return allErrs
 | 
						return allErrs
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func validateClaimMappings(m api.ClaimMappings, fldPath *field.Path) field.ErrorList {
 | 
					func validateClaimMappings(compiler authenticationcel.Compiler, celMapper *authenticationcel.CELMapper, m api.ClaimMappings, fldPath *field.Path, structuredAuthnFeatureEnabled bool) field.ErrorList {
 | 
				
			||||||
	var allErrs field.ErrorList
 | 
						var allErrs field.ErrorList
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if len(m.Username.Claim) == 0 {
 | 
						if !structuredAuthnFeatureEnabled {
 | 
				
			||||||
		allErrs = append(allErrs, field.Required(fldPath.Child("username", "claim"), "claim name is required"))
 | 
							if len(m.Username.Expression) > 0 {
 | 
				
			||||||
 | 
								allErrs = append(allErrs, field.Invalid(fldPath.Child("username").Child("expression"), m.Username.Expression, "expression is not supported when StructuredAuthenticationConfiguration feature gate is disabled"))
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	// TODO(aramase): when Expression is added to PrefixedClaimOrExpression, check prefix and expression are not both set.
 | 
							if len(m.Groups.Expression) > 0 {
 | 
				
			||||||
	if m.Username.Prefix == nil {
 | 
								allErrs = append(allErrs, field.Invalid(fldPath.Child("groups").Child("expression"), m.Groups.Expression, "expression is not supported when StructuredAuthenticationConfiguration feature gate is disabled"))
 | 
				
			||||||
		allErrs = append(allErrs, field.Required(fldPath.Child("username", "prefix"), "prefix is required"))
 | 
					 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	if len(m.Groups.Claim) > 0 && m.Groups.Prefix == nil {
 | 
							if len(m.UID.Claim) > 0 || len(m.UID.Expression) > 0 {
 | 
				
			||||||
		allErrs = append(allErrs, field.Required(fldPath.Child("groups", "prefix"), "prefix is required when claim is set"))
 | 
								allErrs = append(allErrs, field.Invalid(fldPath.Child("uid"), "", "uid claim mapping is not supported when StructuredAuthenticationConfiguration feature gate is disabled"))
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	if m.Groups.Prefix != nil && len(m.Groups.Claim) == 0 {
 | 
							if len(m.Extra) > 0 {
 | 
				
			||||||
		allErrs = append(allErrs, field.Required(fldPath.Child("groups", "claim"), "non-empty claim name is required when prefix is set"))
 | 
								allErrs = append(allErrs, field.Invalid(fldPath.Child("extra"), "", "extra claim mapping is not supported when StructuredAuthenticationConfiguration feature gate is disabled"))
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						compilationResult, err := validatePrefixClaimOrExpression(compiler, m.Username, fldPath.Child("username"), true, structuredAuthnFeatureEnabled)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							allErrs = append(allErrs, err...)
 | 
				
			||||||
 | 
						} else if compilationResult != nil && structuredAuthnFeatureEnabled {
 | 
				
			||||||
 | 
							celMapper.Username = authenticationcel.NewClaimsMapper([]authenticationcel.CompilationResult{*compilationResult})
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						compilationResult, err = validatePrefixClaimOrExpression(compiler, m.Groups, fldPath.Child("groups"), false, structuredAuthnFeatureEnabled)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							allErrs = append(allErrs, err...)
 | 
				
			||||||
 | 
						} else if compilationResult != nil && structuredAuthnFeatureEnabled {
 | 
				
			||||||
 | 
							celMapper.Groups = authenticationcel.NewClaimsMapper([]authenticationcel.CompilationResult{*compilationResult})
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						switch {
 | 
				
			||||||
 | 
						case len(m.UID.Claim) > 0 && len(m.UID.Expression) > 0:
 | 
				
			||||||
 | 
							allErrs = append(allErrs, field.Invalid(fldPath.Child("uid"), "", "claim and expression can't both be set"))
 | 
				
			||||||
 | 
						case len(m.UID.Expression) > 0:
 | 
				
			||||||
 | 
							compilationResult, err := compileClaimsCELExpression(compiler, &authenticationcel.ClaimMappingExpression{
 | 
				
			||||||
 | 
								Expression: m.UID.Expression,
 | 
				
			||||||
 | 
							}, fldPath.Child("uid").Child("expression"))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								allErrs = append(allErrs, err)
 | 
				
			||||||
 | 
							} else if structuredAuthnFeatureEnabled && compilationResult != nil {
 | 
				
			||||||
 | 
								celMapper.UID = authenticationcel.NewClaimsMapper([]authenticationcel.CompilationResult{*compilationResult})
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var extraCompilationResults []authenticationcel.CompilationResult
 | 
				
			||||||
 | 
						seenExtraKeys := sets.NewString()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for i, mapping := range m.Extra {
 | 
				
			||||||
 | 
							fldPath := fldPath.Child("extra").Index(i)
 | 
				
			||||||
 | 
							// Key should be namespaced to the authenticator or authenticator/authorizer pair making use of them.
 | 
				
			||||||
 | 
							// For instance: "example.org/foo" instead of "foo".
 | 
				
			||||||
 | 
							// xref: https://github.com/kubernetes/kubernetes/blob/3825e206cb162a7ad7431a5bdf6a065ae8422cf7/staging/src/k8s.io/apiserver/pkg/authentication/user/user.go#L31-L41
 | 
				
			||||||
 | 
							// IsDomainPrefixedPath checks for non-empty key and that the key is prefixed with a domain name.
 | 
				
			||||||
 | 
							allErrs = append(allErrs, utilvalidation.IsDomainPrefixedPath(fldPath.Child("key"), mapping.Key)...)
 | 
				
			||||||
 | 
							if mapping.Key != strings.ToLower(mapping.Key) {
 | 
				
			||||||
 | 
								allErrs = append(allErrs, field.Invalid(fldPath.Child("key"), mapping.Key, "key must be lowercase"))
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if seenExtraKeys.Has(mapping.Key) {
 | 
				
			||||||
 | 
								allErrs = append(allErrs, field.Duplicate(fldPath.Child("key"), mapping.Key))
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							seenExtraKeys.Insert(mapping.Key)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if len(mapping.ValueExpression) == 0 {
 | 
				
			||||||
 | 
								allErrs = append(allErrs, field.Required(fldPath.Child("valueExpression"), "valueExpression is required"))
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							compilationResult, err := compileClaimsCELExpression(compiler, &authenticationcel.ExtraMappingExpression{
 | 
				
			||||||
 | 
								Key:        mapping.Key,
 | 
				
			||||||
 | 
								Expression: mapping.ValueExpression,
 | 
				
			||||||
 | 
							}, fldPath.Child("valueExpression"))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								allErrs = append(allErrs, err)
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if compilationResult != nil {
 | 
				
			||||||
 | 
								extraCompilationResults = append(extraCompilationResults, *compilationResult)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if structuredAuthnFeatureEnabled && len(extraCompilationResults) > 0 {
 | 
				
			||||||
 | 
							celMapper.Extra = authenticationcel.NewClaimsMapper(extraCompilationResults)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return allErrs
 | 
						return allErrs
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func validatePrefixClaimOrExpression(compiler authenticationcel.Compiler, mapping api.PrefixedClaimOrExpression, fldPath *field.Path, claimOrExpressionRequired, structuredAuthnFeatureEnabled bool) (*authenticationcel.CompilationResult, field.ErrorList) {
 | 
				
			||||||
 | 
						var allErrs field.ErrorList
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var compilationResult *authenticationcel.CompilationResult
 | 
				
			||||||
 | 
						switch {
 | 
				
			||||||
 | 
						case len(mapping.Expression) > 0 && len(mapping.Claim) > 0:
 | 
				
			||||||
 | 
							allErrs = append(allErrs, field.Invalid(fldPath, "", "claim and expression can't both be set"))
 | 
				
			||||||
 | 
						case len(mapping.Expression) == 0 && len(mapping.Claim) == 0 && claimOrExpressionRequired:
 | 
				
			||||||
 | 
							allErrs = append(allErrs, field.Required(fldPath, "claim or expression is required"))
 | 
				
			||||||
 | 
						case len(mapping.Expression) > 0:
 | 
				
			||||||
 | 
							var err *field.Error
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if mapping.Prefix != nil {
 | 
				
			||||||
 | 
								allErrs = append(allErrs, field.Invalid(fldPath.Child("prefix"), *mapping.Prefix, "prefix can't be set when expression is set"))
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							compilationResult, err = compileClaimsCELExpression(compiler, &authenticationcel.ClaimMappingExpression{
 | 
				
			||||||
 | 
								Expression: mapping.Expression,
 | 
				
			||||||
 | 
							}, fldPath.Child("expression"))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								allErrs = append(allErrs, err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						case len(mapping.Claim) > 0:
 | 
				
			||||||
 | 
							if mapping.Prefix == nil {
 | 
				
			||||||
 | 
								allErrs = append(allErrs, field.Required(fldPath.Child("prefix"), "prefix is required when claim is set. It can be set to an empty string to disable prefixing"))
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return compilationResult, allErrs
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func validateUserValidationRules(compiler authenticationcel.Compiler, celMapper *authenticationcel.CELMapper, rules []api.UserValidationRule, fldPath *field.Path, structuredAuthnFeatureEnabled bool) field.ErrorList {
 | 
				
			||||||
 | 
						var allErrs field.ErrorList
 | 
				
			||||||
 | 
						var compilationResults []authenticationcel.CompilationResult
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if len(rules) > 0 && !structuredAuthnFeatureEnabled {
 | 
				
			||||||
 | 
							allErrs = append(allErrs, field.Invalid(fldPath, "", "user validation rules are not supported when StructuredAuthenticationConfiguration feature gate is disabled"))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						seenExpressions := sets.NewString()
 | 
				
			||||||
 | 
						for i, rule := range rules {
 | 
				
			||||||
 | 
							fldPath := fldPath.Index(i)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if len(rule.Expression) == 0 {
 | 
				
			||||||
 | 
								allErrs = append(allErrs, field.Required(fldPath.Child("expression"), "expression is required"))
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if seenExpressions.Has(rule.Expression) {
 | 
				
			||||||
 | 
								allErrs = append(allErrs, field.Duplicate(fldPath.Child("expression"), rule.Expression))
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							seenExpressions.Insert(rule.Expression)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							compilationResult, err := compileUserCELExpression(compiler, &authenticationcel.UserValidationCondition{
 | 
				
			||||||
 | 
								Expression: rule.Expression,
 | 
				
			||||||
 | 
								Message:    rule.Message,
 | 
				
			||||||
 | 
							}, fldPath.Child("expression"))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								allErrs = append(allErrs, err)
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if compilationResult != nil {
 | 
				
			||||||
 | 
								compilationResults = append(compilationResults, *compilationResult)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if structuredAuthnFeatureEnabled && len(compilationResults) > 0 {
 | 
				
			||||||
 | 
							celMapper.UserValidationRules = authenticationcel.NewUserMapper(compilationResults)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return allErrs
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func compileClaimsCELExpression(compiler authenticationcel.Compiler, expression authenticationcel.ExpressionAccessor, fldPath *field.Path) (*authenticationcel.CompilationResult, *field.Error) {
 | 
				
			||||||
 | 
						compilationResult, err := compiler.CompileClaimsExpression(expression)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, convertCELErrorToValidationError(fldPath, expression, err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return &compilationResult, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func compileUserCELExpression(compiler authenticationcel.Compiler, expression authenticationcel.ExpressionAccessor, fldPath *field.Path) (*authenticationcel.CompilationResult, *field.Error) {
 | 
				
			||||||
 | 
						compilationResult, err := compiler.CompileUserExpression(expression)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, convertCELErrorToValidationError(fldPath, expression, err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return &compilationResult, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// ValidateAuthorizationConfiguration validates a given AuthorizationConfiguration.
 | 
					// ValidateAuthorizationConfiguration validates a given AuthorizationConfiguration.
 | 
				
			||||||
func ValidateAuthorizationConfiguration(fldPath *field.Path, c *api.AuthorizationConfiguration, knownTypes sets.String, repeatableTypes sets.String) field.ErrorList {
 | 
					func ValidateAuthorizationConfiguration(fldPath *field.Path, c *api.AuthorizationConfiguration, knownTypes sets.String, repeatableTypes sets.String) field.ErrorList {
 | 
				
			||||||
	allErrs := field.ErrorList{}
 | 
						allErrs := field.ErrorList{}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -21,6 +21,7 @@ import (
 | 
				
			|||||||
	"crypto/elliptic"
 | 
						"crypto/elliptic"
 | 
				
			||||||
	"crypto/rand"
 | 
						"crypto/rand"
 | 
				
			||||||
	"encoding/pem"
 | 
						"encoding/pem"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
	"os"
 | 
						"os"
 | 
				
			||||||
	"testing"
 | 
						"testing"
 | 
				
			||||||
	"time"
 | 
						"time"
 | 
				
			||||||
@@ -32,6 +33,8 @@ import (
 | 
				
			|||||||
	"k8s.io/apimachinery/pkg/util/sets"
 | 
						"k8s.io/apimachinery/pkg/util/sets"
 | 
				
			||||||
	"k8s.io/apimachinery/pkg/util/validation/field"
 | 
						"k8s.io/apimachinery/pkg/util/validation/field"
 | 
				
			||||||
	api "k8s.io/apiserver/pkg/apis/apiserver"
 | 
						api "k8s.io/apiserver/pkg/apis/apiserver"
 | 
				
			||||||
 | 
						authenticationcel "k8s.io/apiserver/pkg/authentication/cel"
 | 
				
			||||||
 | 
						"k8s.io/apiserver/pkg/cel/environment"
 | 
				
			||||||
	"k8s.io/apiserver/pkg/features"
 | 
						"k8s.io/apiserver/pkg/features"
 | 
				
			||||||
	utilfeature "k8s.io/apiserver/pkg/util/feature"
 | 
						utilfeature "k8s.io/apiserver/pkg/util/feature"
 | 
				
			||||||
	certutil "k8s.io/client-go/util/cert"
 | 
						certutil "k8s.io/client-go/util/cert"
 | 
				
			||||||
@@ -39,7 +42,13 @@ import (
 | 
				
			|||||||
	"k8s.io/utils/pointer"
 | 
						"k8s.io/utils/pointer"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var (
 | 
				
			||||||
 | 
						compiler = authenticationcel.NewCompiler(environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion()))
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func TestValidateAuthenticationConfiguration(t *testing.T) {
 | 
					func TestValidateAuthenticationConfiguration(t *testing.T) {
 | 
				
			||||||
 | 
						defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.StructuredAuthenticationConfiguration, true)()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	testCases := []struct {
 | 
						testCases := []struct {
 | 
				
			||||||
		name string
 | 
							name string
 | 
				
			||||||
		in   *api.AuthenticationConfiguration
 | 
							in   *api.AuthenticationConfiguration
 | 
				
			||||||
@@ -133,7 +142,37 @@ func TestValidateAuthenticationConfiguration(t *testing.T) {
 | 
				
			|||||||
					},
 | 
										},
 | 
				
			||||||
				},
 | 
									},
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
			want: "jwt[0].claimMappings.username.claim: Required value: claim name is required",
 | 
								want: "jwt[0].claimMappings.username: Required value: claim or expression is required",
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "failed userValidationRule validation",
 | 
				
			||||||
 | 
								in: &api.AuthenticationConfiguration{
 | 
				
			||||||
 | 
									JWT: []api.JWTAuthenticator{
 | 
				
			||||||
 | 
										{
 | 
				
			||||||
 | 
											Issuer: api.Issuer{
 | 
				
			||||||
 | 
												URL:       "https://issuer-url",
 | 
				
			||||||
 | 
												Audiences: []string{"audience"},
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
											ClaimValidationRules: []api.ClaimValidationRule{
 | 
				
			||||||
 | 
												{
 | 
				
			||||||
 | 
													Claim:         "foo",
 | 
				
			||||||
 | 
													RequiredValue: "bar",
 | 
				
			||||||
 | 
												},
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
											ClaimMappings: api.ClaimMappings{
 | 
				
			||||||
 | 
												Username: api.PrefixedClaimOrExpression{
 | 
				
			||||||
 | 
													Claim:  "sub",
 | 
				
			||||||
 | 
													Prefix: pointer.String("prefix"),
 | 
				
			||||||
 | 
												},
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
											UserValidationRules: []api.UserValidationRule{
 | 
				
			||||||
 | 
												{Expression: "user.username == 'foo'"},
 | 
				
			||||||
 | 
												{Expression: "user.username == 'foo'"},
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								want: `jwt[0].userValidationRules[1].expression: Duplicate value: "user.username == 'foo'"`,
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		{
 | 
							{
 | 
				
			||||||
			name: "valid authentication configuration",
 | 
								name: "valid authentication configuration",
 | 
				
			||||||
@@ -313,31 +352,98 @@ func TestValidateCertificateAuthority(t *testing.T) {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func TestClaimValidationRules(t *testing.T) {
 | 
					func TestValidateClaimValidationRules(t *testing.T) {
 | 
				
			||||||
	fldPath := field.NewPath("issuer", "claimValidationRules")
 | 
						fldPath := field.NewPath("issuer", "claimValidationRules")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	testCases := []struct {
 | 
						testCases := []struct {
 | 
				
			||||||
		name                          string
 | 
							name                          string
 | 
				
			||||||
		in                            []api.ClaimValidationRule
 | 
							in                            []api.ClaimValidationRule
 | 
				
			||||||
 | 
							structuredAuthnFeatureEnabled bool
 | 
				
			||||||
		want                          string
 | 
							want                          string
 | 
				
			||||||
 | 
							wantCELMapper                 bool
 | 
				
			||||||
	}{
 | 
						}{
 | 
				
			||||||
		{
 | 
							{
 | 
				
			||||||
			name: "claim validation rule claim is empty",
 | 
								name:                          "claim and expression are empty, structured authn feature enabled",
 | 
				
			||||||
			in:   []api.ClaimValidationRule{{Claim: ""}},
 | 
								in:                            []api.ClaimValidationRule{{}},
 | 
				
			||||||
			want: "issuer.claimValidationRules[0].claim: Required value: claim name is required",
 | 
								structuredAuthnFeatureEnabled: true,
 | 
				
			||||||
 | 
								want:                          "issuer.claimValidationRules[0]: Required value: claim or expression is required",
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "claim and expression are set",
 | 
				
			||||||
 | 
								in: []api.ClaimValidationRule{
 | 
				
			||||||
 | 
									{Claim: "claim", Expression: "expression"},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								structuredAuthnFeatureEnabled: true,
 | 
				
			||||||
 | 
								want:                          `issuer.claimValidationRules[0]: Invalid value: "claim": claim and expression can't both be set`,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "message set when claim is set",
 | 
				
			||||||
 | 
								in: []api.ClaimValidationRule{
 | 
				
			||||||
 | 
									{Claim: "claim", Message: "message"},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								structuredAuthnFeatureEnabled: true,
 | 
				
			||||||
 | 
								want:                          `issuer.claimValidationRules[0].message: Invalid value: "message": message can't be set when claim is set`,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "requiredValue set when expression is set",
 | 
				
			||||||
 | 
								in: []api.ClaimValidationRule{
 | 
				
			||||||
 | 
									{Expression: "claims.foo == 'bar'", RequiredValue: "value"},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								structuredAuthnFeatureEnabled: true,
 | 
				
			||||||
 | 
								want:                          `issuer.claimValidationRules[0].requiredValue: Invalid value: "value": requiredValue can't be set when expression is set`,
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		{
 | 
							{
 | 
				
			||||||
			name: "duplicate claim",
 | 
								name: "duplicate claim",
 | 
				
			||||||
			in: []api.ClaimValidationRule{{
 | 
								in: []api.ClaimValidationRule{
 | 
				
			||||||
				Claim: "claim", RequiredValue: "value1"},
 | 
									{Claim: "claim"},
 | 
				
			||||||
				{Claim: "claim", RequiredValue: "value2"},
 | 
									{Claim: "claim"},
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
 | 
								structuredAuthnFeatureEnabled: true,
 | 
				
			||||||
			want:                          `issuer.claimValidationRules[1].claim: Duplicate value: "claim"`,
 | 
								want:                          `issuer.claimValidationRules[1].claim: Duplicate value: "claim"`,
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		{
 | 
							{
 | 
				
			||||||
			name: "valid claim validation rule",
 | 
								name: "duplicate expression",
 | 
				
			||||||
			in:   []api.ClaimValidationRule{{Claim: "claim", RequiredValue: "value"}},
 | 
								in: []api.ClaimValidationRule{
 | 
				
			||||||
 | 
									{Expression: "claims.foo == 'bar'"},
 | 
				
			||||||
 | 
									{Expression: "claims.foo == 'bar'"},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								structuredAuthnFeatureEnabled: true,
 | 
				
			||||||
 | 
								want:                          `issuer.claimValidationRules[1].expression: Duplicate value: "claims.foo == 'bar'"`,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "expression set when structured authn feature is disabled",
 | 
				
			||||||
 | 
								in: []api.ClaimValidationRule{
 | 
				
			||||||
 | 
									{Expression: "claims.foo == 'bar'"},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								structuredAuthnFeatureEnabled: false,
 | 
				
			||||||
 | 
								want:                          `issuer.claimValidationRules[0].expression: Invalid value: "claims.foo == 'bar'": expression is not supported when StructuredAuthenticationConfiguration feature gate is disabled`,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "CEL expression compilation error",
 | 
				
			||||||
 | 
								in: []api.ClaimValidationRule{
 | 
				
			||||||
 | 
									{Expression: "foo.bar"},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								structuredAuthnFeatureEnabled: true,
 | 
				
			||||||
 | 
								want: `issuer.claimValidationRules[0].expression: Invalid value: "foo.bar": compilation failed: ERROR: <input>:1:1: undeclared reference to 'foo' (in container '')
 | 
				
			||||||
 | 
					 | foo.bar
 | 
				
			||||||
 | 
					 | ^`,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "expression does not evaluate to bool",
 | 
				
			||||||
 | 
								in: []api.ClaimValidationRule{
 | 
				
			||||||
 | 
									{Expression: "claims.foo"},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								structuredAuthnFeatureEnabled: true,
 | 
				
			||||||
 | 
								want:                          `issuer.claimValidationRules[0].expression: Invalid value: "claims.foo": must evaluate to bool`,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "valid claim validation rule with expression",
 | 
				
			||||||
 | 
								in: []api.ClaimValidationRule{
 | 
				
			||||||
 | 
									{Expression: "claims.foo == 'bar'"},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								structuredAuthnFeatureEnabled: true,
 | 
				
			||||||
			want:                          "",
 | 
								want:                          "",
 | 
				
			||||||
 | 
								wantCELMapper:                 true,
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		{
 | 
							{
 | 
				
			||||||
			name: "valid claim validation rule with multiple rules",
 | 
								name: "valid claim validation rule with multiple rules",
 | 
				
			||||||
@@ -345,16 +451,21 @@ func TestClaimValidationRules(t *testing.T) {
 | 
				
			|||||||
				{Claim: "claim1", RequiredValue: "value1"},
 | 
									{Claim: "claim1", RequiredValue: "value1"},
 | 
				
			||||||
				{Claim: "claim2", RequiredValue: "value2"},
 | 
									{Claim: "claim2", RequiredValue: "value2"},
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
 | 
								structuredAuthnFeatureEnabled: true,
 | 
				
			||||||
			want:                          "",
 | 
								want:                          "",
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for _, tt := range testCases {
 | 
						for _, tt := range testCases {
 | 
				
			||||||
		t.Run(tt.name, func(t *testing.T) {
 | 
							t.Run(tt.name, func(t *testing.T) {
 | 
				
			||||||
			got := validateClaimValidationRules(tt.in, fldPath).ToAggregate()
 | 
								celMapper := &authenticationcel.CELMapper{}
 | 
				
			||||||
 | 
								got := validateClaimValidationRules(compiler, celMapper, tt.in, fldPath, tt.structuredAuthnFeatureEnabled).ToAggregate()
 | 
				
			||||||
			if d := cmp.Diff(tt.want, errString(got)); d != "" {
 | 
								if d := cmp.Diff(tt.want, errString(got)); d != "" {
 | 
				
			||||||
				t.Fatalf("ClaimValidationRules validation mismatch (-want +got):\n%s", d)
 | 
									t.Fatalf("ClaimValidationRules validation mismatch (-want +got):\n%s", d)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
								if tt.wantCELMapper && celMapper.ClaimValidationRules == nil {
 | 
				
			||||||
 | 
									t.Fatalf("ClaimValidationRules validation mismatch: CELMapper.ClaimValidationRules is nil")
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
		})
 | 
							})
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -365,50 +476,419 @@ func TestValidateClaimMappings(t *testing.T) {
 | 
				
			|||||||
	testCases := []struct {
 | 
						testCases := []struct {
 | 
				
			||||||
		name                          string
 | 
							name                          string
 | 
				
			||||||
		in                            api.ClaimMappings
 | 
							in                            api.ClaimMappings
 | 
				
			||||||
 | 
							structuredAuthnFeatureEnabled bool
 | 
				
			||||||
		want                          string
 | 
							want                          string
 | 
				
			||||||
 | 
							wantCELMapper                 bool
 | 
				
			||||||
	}{
 | 
						}{
 | 
				
			||||||
		{
 | 
							{
 | 
				
			||||||
			name: "username claim is empty",
 | 
								name: "username expression and claim are set",
 | 
				
			||||||
			in:   api.ClaimMappings{Username: api.PrefixedClaimOrExpression{Claim: "", Prefix: pointer.String("prefix")}},
 | 
					 | 
				
			||||||
			want: "issuer.claimMappings.username.claim: Required value: claim name is required",
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
		{
 | 
					 | 
				
			||||||
			name: "username prefix is empty",
 | 
					 | 
				
			||||||
			in:   api.ClaimMappings{Username: api.PrefixedClaimOrExpression{Claim: "claim"}},
 | 
					 | 
				
			||||||
			want: "issuer.claimMappings.username.prefix: Required value: prefix is required",
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
		{
 | 
					 | 
				
			||||||
			name: "groups prefix is empty",
 | 
					 | 
				
			||||||
			in: api.ClaimMappings{
 | 
								in: api.ClaimMappings{
 | 
				
			||||||
				Username: api.PrefixedClaimOrExpression{Claim: "claim", Prefix: pointer.String("prefix")},
 | 
									Username: api.PrefixedClaimOrExpression{
 | 
				
			||||||
				Groups:   api.PrefixedClaimOrExpression{Claim: "claim"},
 | 
										Claim:      "claim",
 | 
				
			||||||
 | 
										Expression: "claims.username",
 | 
				
			||||||
				},
 | 
									},
 | 
				
			||||||
			want: "issuer.claimMappings.groups.prefix: Required value: prefix is required when claim is set",
 | 
								},
 | 
				
			||||||
 | 
								structuredAuthnFeatureEnabled: true,
 | 
				
			||||||
 | 
								want:                          `issuer.claimMappings.username: Invalid value: "": claim and expression can't both be set`,
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		{
 | 
							{
 | 
				
			||||||
			name: "groups prefix set but claim is empty",
 | 
								name:                          "username expression and claim are empty",
 | 
				
			||||||
			in: api.ClaimMappings{
 | 
								in:                            api.ClaimMappings{Username: api.PrefixedClaimOrExpression{}},
 | 
				
			||||||
				Username: api.PrefixedClaimOrExpression{Claim: "claim", Prefix: pointer.String("prefix")},
 | 
								structuredAuthnFeatureEnabled: true,
 | 
				
			||||||
				Groups:   api.PrefixedClaimOrExpression{Prefix: pointer.String("prefix")},
 | 
								want:                          "issuer.claimMappings.username: Required value: claim or expression is required",
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
			want: "issuer.claimMappings.groups.claim: Required value: non-empty claim name is required when prefix is set",
 | 
							{
 | 
				
			||||||
 | 
								name: "username prefix set when expression is set",
 | 
				
			||||||
 | 
								in: api.ClaimMappings{
 | 
				
			||||||
 | 
									Username: api.PrefixedClaimOrExpression{
 | 
				
			||||||
 | 
										Expression: "claims.username",
 | 
				
			||||||
 | 
										Prefix:     pointer.String("prefix"),
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								structuredAuthnFeatureEnabled: true,
 | 
				
			||||||
 | 
								want:                          `issuer.claimMappings.username.prefix: Invalid value: "prefix": prefix can't be set when expression is set`,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "username prefix is nil when claim is set",
 | 
				
			||||||
 | 
								in: api.ClaimMappings{
 | 
				
			||||||
 | 
									Username: api.PrefixedClaimOrExpression{
 | 
				
			||||||
 | 
										Claim: "claim",
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								structuredAuthnFeatureEnabled: true,
 | 
				
			||||||
 | 
								want:                          `issuer.claimMappings.username.prefix: Required value: prefix is required when claim is set. It can be set to an empty string to disable prefixing`,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "username expression is invalid",
 | 
				
			||||||
 | 
								in: api.ClaimMappings{
 | 
				
			||||||
 | 
									Username: api.PrefixedClaimOrExpression{
 | 
				
			||||||
 | 
										Expression: "foo.bar",
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								structuredAuthnFeatureEnabled: true,
 | 
				
			||||||
 | 
								want: `issuer.claimMappings.username.expression: Invalid value: "foo.bar": compilation failed: ERROR: <input>:1:1: undeclared reference to 'foo' (in container '')
 | 
				
			||||||
 | 
					 | foo.bar
 | 
				
			||||||
 | 
					 | ^`,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "groups expression and claim are set",
 | 
				
			||||||
 | 
								in: api.ClaimMappings{
 | 
				
			||||||
 | 
									Username: api.PrefixedClaimOrExpression{
 | 
				
			||||||
 | 
										Claim:  "claim",
 | 
				
			||||||
 | 
										Prefix: pointer.String("prefix"),
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									Groups: api.PrefixedClaimOrExpression{
 | 
				
			||||||
 | 
										Claim:      "claim",
 | 
				
			||||||
 | 
										Expression: "claims.groups",
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								structuredAuthnFeatureEnabled: true,
 | 
				
			||||||
 | 
								want:                          `issuer.claimMappings.groups: Invalid value: "": claim and expression can't both be set`,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "groups prefix set when expression is set",
 | 
				
			||||||
 | 
								in: api.ClaimMappings{
 | 
				
			||||||
 | 
									Username: api.PrefixedClaimOrExpression{
 | 
				
			||||||
 | 
										Claim:  "claim",
 | 
				
			||||||
 | 
										Prefix: pointer.String("prefix"),
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									Groups: api.PrefixedClaimOrExpression{
 | 
				
			||||||
 | 
										Expression: "claims.groups",
 | 
				
			||||||
 | 
										Prefix:     pointer.String("prefix"),
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								structuredAuthnFeatureEnabled: true,
 | 
				
			||||||
 | 
								want:                          `issuer.claimMappings.groups.prefix: Invalid value: "prefix": prefix can't be set when expression is set`,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "groups prefix is nil when claim is set",
 | 
				
			||||||
 | 
								in: api.ClaimMappings{
 | 
				
			||||||
 | 
									Username: api.PrefixedClaimOrExpression{
 | 
				
			||||||
 | 
										Claim:  "claim",
 | 
				
			||||||
 | 
										Prefix: pointer.String("prefix"),
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									Groups: api.PrefixedClaimOrExpression{
 | 
				
			||||||
 | 
										Claim: "claim",
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								structuredAuthnFeatureEnabled: true,
 | 
				
			||||||
 | 
								want:                          `issuer.claimMappings.groups.prefix: Required value: prefix is required when claim is set. It can be set to an empty string to disable prefixing`,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "groups expression is invalid",
 | 
				
			||||||
 | 
								in: api.ClaimMappings{
 | 
				
			||||||
 | 
									Username: api.PrefixedClaimOrExpression{
 | 
				
			||||||
 | 
										Claim:  "claim",
 | 
				
			||||||
 | 
										Prefix: pointer.String("prefix"),
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									Groups: api.PrefixedClaimOrExpression{
 | 
				
			||||||
 | 
										Expression: "foo.bar",
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								structuredAuthnFeatureEnabled: true,
 | 
				
			||||||
 | 
								want: `issuer.claimMappings.groups.expression: Invalid value: "foo.bar": compilation failed: ERROR: <input>:1:1: undeclared reference to 'foo' (in container '')
 | 
				
			||||||
 | 
					 | foo.bar
 | 
				
			||||||
 | 
					 | ^`,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "uid claim and expression are set",
 | 
				
			||||||
 | 
								in: api.ClaimMappings{
 | 
				
			||||||
 | 
									Username: api.PrefixedClaimOrExpression{
 | 
				
			||||||
 | 
										Claim:  "claim",
 | 
				
			||||||
 | 
										Prefix: pointer.String("prefix"),
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									UID: api.ClaimOrExpression{
 | 
				
			||||||
 | 
										Claim:      "claim",
 | 
				
			||||||
 | 
										Expression: "claims.uid",
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								structuredAuthnFeatureEnabled: true,
 | 
				
			||||||
 | 
								want:                          `issuer.claimMappings.uid: Invalid value: "": claim and expression can't both be set`,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "uid expression is invalid",
 | 
				
			||||||
 | 
								in: api.ClaimMappings{
 | 
				
			||||||
 | 
									Username: api.PrefixedClaimOrExpression{
 | 
				
			||||||
 | 
										Claim:  "claim",
 | 
				
			||||||
 | 
										Prefix: pointer.String("prefix"),
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									UID: api.ClaimOrExpression{
 | 
				
			||||||
 | 
										Expression: "foo.bar",
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								structuredAuthnFeatureEnabled: true,
 | 
				
			||||||
 | 
								want: `issuer.claimMappings.uid.expression: Invalid value: "foo.bar": compilation failed: ERROR: <input>:1:1: undeclared reference to 'foo' (in container '')
 | 
				
			||||||
 | 
					 | foo.bar
 | 
				
			||||||
 | 
					 | ^`,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "extra mapping key is empty",
 | 
				
			||||||
 | 
								in: api.ClaimMappings{
 | 
				
			||||||
 | 
									Username: api.PrefixedClaimOrExpression{
 | 
				
			||||||
 | 
										Claim:  "claim",
 | 
				
			||||||
 | 
										Prefix: pointer.String("prefix"),
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									Extra: []api.ExtraMapping{
 | 
				
			||||||
 | 
										{Key: "", ValueExpression: "claims.extra"},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								structuredAuthnFeatureEnabled: true,
 | 
				
			||||||
 | 
								want:                          `issuer.claimMappings.extra[0].key: Required value`,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "extra mapping value expression is empty",
 | 
				
			||||||
 | 
								in: api.ClaimMappings{
 | 
				
			||||||
 | 
									Username: api.PrefixedClaimOrExpression{
 | 
				
			||||||
 | 
										Claim:  "claim",
 | 
				
			||||||
 | 
										Prefix: pointer.String("prefix"),
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									Extra: []api.ExtraMapping{
 | 
				
			||||||
 | 
										{Key: "example.org/foo", ValueExpression: ""},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								structuredAuthnFeatureEnabled: true,
 | 
				
			||||||
 | 
								want:                          `issuer.claimMappings.extra[0].valueExpression: Required value: valueExpression is required`,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "extra mapping value expression is invalid",
 | 
				
			||||||
 | 
								in: api.ClaimMappings{
 | 
				
			||||||
 | 
									Username: api.PrefixedClaimOrExpression{
 | 
				
			||||||
 | 
										Claim:  "claim",
 | 
				
			||||||
 | 
										Prefix: pointer.String("prefix"),
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									Extra: []api.ExtraMapping{
 | 
				
			||||||
 | 
										{Key: "example.org/foo", ValueExpression: "foo.bar"},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								structuredAuthnFeatureEnabled: true,
 | 
				
			||||||
 | 
								want: `issuer.claimMappings.extra[0].valueExpression: Invalid value: "foo.bar": compilation failed: ERROR: <input>:1:1: undeclared reference to 'foo' (in container '')
 | 
				
			||||||
 | 
					 | foo.bar
 | 
				
			||||||
 | 
					 | ^`,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "username expression is invalid when structured authn feature is disabled",
 | 
				
			||||||
 | 
								in: api.ClaimMappings{
 | 
				
			||||||
 | 
									Username: api.PrefixedClaimOrExpression{
 | 
				
			||||||
 | 
										Expression: "foo.bar",
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								structuredAuthnFeatureEnabled: false,
 | 
				
			||||||
 | 
								want: `[issuer.claimMappings.username.expression: Invalid value: "foo.bar": expression is not supported when StructuredAuthenticationConfiguration feature gate is disabled, issuer.claimMappings.username.expression: Invalid value: "foo.bar": compilation failed: ERROR: <input>:1:1: undeclared reference to 'foo' (in container '')
 | 
				
			||||||
 | 
					 | foo.bar
 | 
				
			||||||
 | 
					 | ^]`,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "groups expression is invalid when structured authn feature is disabled",
 | 
				
			||||||
 | 
								in: api.ClaimMappings{
 | 
				
			||||||
 | 
									Username: api.PrefixedClaimOrExpression{
 | 
				
			||||||
 | 
										Claim:  "claim",
 | 
				
			||||||
 | 
										Prefix: pointer.String("prefix"),
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									Groups: api.PrefixedClaimOrExpression{
 | 
				
			||||||
 | 
										Expression: "foo.bar",
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								structuredAuthnFeatureEnabled: false,
 | 
				
			||||||
 | 
								want: `[issuer.claimMappings.groups.expression: Invalid value: "foo.bar": expression is not supported when StructuredAuthenticationConfiguration feature gate is disabled, issuer.claimMappings.groups.expression: Invalid value: "foo.bar": compilation failed: ERROR: <input>:1:1: undeclared reference to 'foo' (in container '')
 | 
				
			||||||
 | 
					 | foo.bar
 | 
				
			||||||
 | 
					 | ^]`,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "uid expression is invalid when structured authn feature is disabled",
 | 
				
			||||||
 | 
								in: api.ClaimMappings{
 | 
				
			||||||
 | 
									Username: api.PrefixedClaimOrExpression{
 | 
				
			||||||
 | 
										Claim:  "claim",
 | 
				
			||||||
 | 
										Prefix: pointer.String("prefix"),
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									UID: api.ClaimOrExpression{
 | 
				
			||||||
 | 
										Expression: "foo.bar",
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								structuredAuthnFeatureEnabled: false,
 | 
				
			||||||
 | 
								want: `[issuer.claimMappings.uid: Invalid value: "": uid claim mapping is not supported when StructuredAuthenticationConfiguration feature gate is disabled, issuer.claimMappings.uid.expression: Invalid value: "foo.bar": compilation failed: ERROR: <input>:1:1: undeclared reference to 'foo' (in container '')
 | 
				
			||||||
 | 
					 | foo.bar
 | 
				
			||||||
 | 
					 | ^]`,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "uid claim is invalid when structured authn feature is disabled",
 | 
				
			||||||
 | 
								in: api.ClaimMappings{
 | 
				
			||||||
 | 
									Username: api.PrefixedClaimOrExpression{
 | 
				
			||||||
 | 
										Claim:  "claim",
 | 
				
			||||||
 | 
										Prefix: pointer.String("prefix"),
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									UID: api.ClaimOrExpression{
 | 
				
			||||||
 | 
										Claim: "claim",
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								structuredAuthnFeatureEnabled: false,
 | 
				
			||||||
 | 
								want:                          `issuer.claimMappings.uid: Invalid value: "": uid claim mapping is not supported when StructuredAuthenticationConfiguration feature gate is disabled`,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "extra mapping is invalid when structured authn feature is disabled",
 | 
				
			||||||
 | 
								in: api.ClaimMappings{
 | 
				
			||||||
 | 
									Username: api.PrefixedClaimOrExpression{
 | 
				
			||||||
 | 
										Claim:  "claim",
 | 
				
			||||||
 | 
										Prefix: pointer.String("prefix"),
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									Extra: []api.ExtraMapping{
 | 
				
			||||||
 | 
										{Key: "example.org/foo", ValueExpression: "claims.extra"},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								structuredAuthnFeatureEnabled: false,
 | 
				
			||||||
 | 
								want:                          `issuer.claimMappings.extra: Invalid value: "": extra claim mapping is not supported when StructuredAuthenticationConfiguration feature gate is disabled`,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "duplicate extra mapping key",
 | 
				
			||||||
 | 
								in: api.ClaimMappings{
 | 
				
			||||||
 | 
									Username: api.PrefixedClaimOrExpression{Expression: "claims.username"},
 | 
				
			||||||
 | 
									Groups:   api.PrefixedClaimOrExpression{Expression: "claims.groups"},
 | 
				
			||||||
 | 
									Extra: []api.ExtraMapping{
 | 
				
			||||||
 | 
										{Key: "example.org/foo", ValueExpression: "claims.extra"},
 | 
				
			||||||
 | 
										{Key: "example.org/foo", ValueExpression: "claims.extras"},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								structuredAuthnFeatureEnabled: true,
 | 
				
			||||||
 | 
								want:                          `issuer.claimMappings.extra[1].key: Duplicate value: "example.org/foo"`,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "extra mapping key is not domain prefix path",
 | 
				
			||||||
 | 
								in: api.ClaimMappings{
 | 
				
			||||||
 | 
									Username: api.PrefixedClaimOrExpression{Expression: "claims.username"},
 | 
				
			||||||
 | 
									Groups:   api.PrefixedClaimOrExpression{Expression: "claims.groups"},
 | 
				
			||||||
 | 
									Extra: []api.ExtraMapping{
 | 
				
			||||||
 | 
										{Key: "foo", ValueExpression: "claims.extra"},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								structuredAuthnFeatureEnabled: true,
 | 
				
			||||||
 | 
								want:                          `issuer.claimMappings.extra[0].key: Invalid value: "foo": must be a domain-prefixed path (such as "acme.io/foo")`,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "extra mapping key is not lower case",
 | 
				
			||||||
 | 
								in: api.ClaimMappings{
 | 
				
			||||||
 | 
									Username: api.PrefixedClaimOrExpression{Expression: "claims.username"},
 | 
				
			||||||
 | 
									Groups:   api.PrefixedClaimOrExpression{Expression: "claims.groups"},
 | 
				
			||||||
 | 
									Extra: []api.ExtraMapping{
 | 
				
			||||||
 | 
										{Key: "example.org/Foo", ValueExpression: "claims.extra"},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								structuredAuthnFeatureEnabled: true,
 | 
				
			||||||
 | 
								want:                          `issuer.claimMappings.extra[0].key: Invalid value: "example.org/Foo": key must be lowercase`,
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		{
 | 
							{
 | 
				
			||||||
			name: "valid claim mappings",
 | 
								name: "valid claim mappings",
 | 
				
			||||||
			in: api.ClaimMappings{
 | 
								in: api.ClaimMappings{
 | 
				
			||||||
				Username: api.PrefixedClaimOrExpression{Claim: "claim", Prefix: pointer.String("prefix")},
 | 
									Username: api.PrefixedClaimOrExpression{Expression: "claims.username"},
 | 
				
			||||||
				Groups:   api.PrefixedClaimOrExpression{Claim: "claim", Prefix: pointer.String("prefix")},
 | 
									Groups:   api.PrefixedClaimOrExpression{Expression: "claims.groups"},
 | 
				
			||||||
 | 
									UID:      api.ClaimOrExpression{Expression: "claims.uid"},
 | 
				
			||||||
 | 
									Extra: []api.ExtraMapping{
 | 
				
			||||||
 | 
										{Key: "example.org/foo", ValueExpression: "claims.extra"},
 | 
				
			||||||
				},
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								structuredAuthnFeatureEnabled: true,
 | 
				
			||||||
 | 
								wantCELMapper:                 true,
 | 
				
			||||||
			want:                          "",
 | 
								want:                          "",
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for _, tt := range testCases {
 | 
						for _, tt := range testCases {
 | 
				
			||||||
		t.Run(tt.name, func(t *testing.T) {
 | 
							t.Run(tt.name, func(t *testing.T) {
 | 
				
			||||||
			got := validateClaimMappings(tt.in, fldPath).ToAggregate()
 | 
								celMapper := &authenticationcel.CELMapper{}
 | 
				
			||||||
 | 
								got := validateClaimMappings(compiler, celMapper, tt.in, fldPath, tt.structuredAuthnFeatureEnabled).ToAggregate()
 | 
				
			||||||
			if d := cmp.Diff(tt.want, errString(got)); d != "" {
 | 
								if d := cmp.Diff(tt.want, errString(got)); d != "" {
 | 
				
			||||||
 | 
									fmt.Println(errString(got))
 | 
				
			||||||
				t.Fatalf("ClaimMappings validation mismatch (-want +got):\n%s", d)
 | 
									t.Fatalf("ClaimMappings validation mismatch (-want +got):\n%s", d)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
								if tt.wantCELMapper {
 | 
				
			||||||
 | 
									if len(tt.in.Username.Expression) > 0 && celMapper.Username == nil {
 | 
				
			||||||
 | 
										t.Fatalf("ClaimMappings validation mismatch: CELMapper.Username is nil")
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									if len(tt.in.Groups.Expression) > 0 && celMapper.Groups == nil {
 | 
				
			||||||
 | 
										t.Fatalf("ClaimMappings validation mismatch: CELMapper.Groups is nil")
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									if len(tt.in.UID.Expression) > 0 && celMapper.UID == nil {
 | 
				
			||||||
 | 
										t.Fatalf("ClaimMappings validation mismatch: CELMapper.UID is nil")
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									if len(tt.in.Extra) > 0 && celMapper.Extra == nil {
 | 
				
			||||||
 | 
										t.Fatalf("ClaimMappings validation mismatch: CELMapper.Extra is nil")
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestValidateUserValidationRules(t *testing.T) {
 | 
				
			||||||
 | 
						fldPath := field.NewPath("issuer", "userValidationRules")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						testCases := []struct {
 | 
				
			||||||
 | 
							name                          string
 | 
				
			||||||
 | 
							in                            []api.UserValidationRule
 | 
				
			||||||
 | 
							structuredAuthnFeatureEnabled bool
 | 
				
			||||||
 | 
							want                          string
 | 
				
			||||||
 | 
							wantCELMapper                 bool
 | 
				
			||||||
 | 
						}{
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name:                          "user info validation rule, expression is empty",
 | 
				
			||||||
 | 
								in:                            []api.UserValidationRule{{}},
 | 
				
			||||||
 | 
								structuredAuthnFeatureEnabled: true,
 | 
				
			||||||
 | 
								want:                          "issuer.userValidationRules[0].expression: Required value: expression is required",
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "duplicate expression",
 | 
				
			||||||
 | 
								in: []api.UserValidationRule{
 | 
				
			||||||
 | 
									{Expression: "user.username == 'foo'"},
 | 
				
			||||||
 | 
									{Expression: "user.username == 'foo'"},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								structuredAuthnFeatureEnabled: true,
 | 
				
			||||||
 | 
								want:                          `issuer.userValidationRules[1].expression: Duplicate value: "user.username == 'foo'"`,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "user validation rule is invalid when structured authn feature is disabled",
 | 
				
			||||||
 | 
								in: []api.UserValidationRule{
 | 
				
			||||||
 | 
									{Expression: "user.username == 'foo'"},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								structuredAuthnFeatureEnabled: false,
 | 
				
			||||||
 | 
								want:                          `issuer.userValidationRules: Invalid value: "": user validation rules are not supported when StructuredAuthenticationConfiguration feature gate is disabled`,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "expression is invalid",
 | 
				
			||||||
 | 
								in: []api.UserValidationRule{
 | 
				
			||||||
 | 
									{Expression: "foo.bar"},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								structuredAuthnFeatureEnabled: true,
 | 
				
			||||||
 | 
								want: `issuer.userValidationRules[0].expression: Invalid value: "foo.bar": compilation failed: ERROR: <input>:1:1: undeclared reference to 'foo' (in container '')
 | 
				
			||||||
 | 
					 | foo.bar
 | 
				
			||||||
 | 
					 | ^`,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "expression does not return bool",
 | 
				
			||||||
 | 
								in: []api.UserValidationRule{
 | 
				
			||||||
 | 
									{Expression: "user.username"},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								structuredAuthnFeatureEnabled: true,
 | 
				
			||||||
 | 
								want:                          `issuer.userValidationRules[0].expression: Invalid value: "user.username": must evaluate to bool`,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "valid user info validation rule",
 | 
				
			||||||
 | 
								in: []api.UserValidationRule{
 | 
				
			||||||
 | 
									{Expression: "user.username == 'foo'"},
 | 
				
			||||||
 | 
									{Expression: "!user.username.startsWith('system:')", Message: "username cannot used reserved system: prefix"},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								structuredAuthnFeatureEnabled: true,
 | 
				
			||||||
 | 
								want:                          "",
 | 
				
			||||||
 | 
								wantCELMapper:                 true,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, tt := range testCases {
 | 
				
			||||||
 | 
							t.Run(tt.name, func(t *testing.T) {
 | 
				
			||||||
 | 
								celMapper := &authenticationcel.CELMapper{}
 | 
				
			||||||
 | 
								got := validateUserValidationRules(compiler, celMapper, tt.in, fldPath, tt.structuredAuthnFeatureEnabled).ToAggregate()
 | 
				
			||||||
 | 
								if d := cmp.Diff(tt.want, errString(got)); d != "" {
 | 
				
			||||||
 | 
									t.Fatalf("UserValidationRules validation mismatch (-want +got):\n%s", d)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								if tt.wantCELMapper && celMapper.UserValidationRules == nil {
 | 
				
			||||||
 | 
									t.Fatalf("UserValidationRules validation mismatch: CELMapper.UserValidationRules is nil")
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
		})
 | 
							})
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -168,6 +168,12 @@ func (in *ClaimMappings) DeepCopyInto(out *ClaimMappings) {
 | 
				
			|||||||
	*out = *in
 | 
						*out = *in
 | 
				
			||||||
	in.Username.DeepCopyInto(&out.Username)
 | 
						in.Username.DeepCopyInto(&out.Username)
 | 
				
			||||||
	in.Groups.DeepCopyInto(&out.Groups)
 | 
						in.Groups.DeepCopyInto(&out.Groups)
 | 
				
			||||||
 | 
						out.UID = in.UID
 | 
				
			||||||
 | 
						if in.Extra != nil {
 | 
				
			||||||
 | 
							in, out := &in.Extra, &out.Extra
 | 
				
			||||||
 | 
							*out = make([]ExtraMapping, len(*in))
 | 
				
			||||||
 | 
							copy(*out, *in)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
	return
 | 
						return
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -181,6 +187,22 @@ func (in *ClaimMappings) DeepCopy() *ClaimMappings {
 | 
				
			|||||||
	return out
 | 
						return out
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 | 
				
			||||||
 | 
					func (in *ClaimOrExpression) DeepCopyInto(out *ClaimOrExpression) {
 | 
				
			||||||
 | 
						*out = *in
 | 
				
			||||||
 | 
						return
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClaimOrExpression.
 | 
				
			||||||
 | 
					func (in *ClaimOrExpression) DeepCopy() *ClaimOrExpression {
 | 
				
			||||||
 | 
						if in == nil {
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						out := new(ClaimOrExpression)
 | 
				
			||||||
 | 
						in.DeepCopyInto(out)
 | 
				
			||||||
 | 
						return out
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 | 
					// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 | 
				
			||||||
func (in *ClaimValidationRule) DeepCopyInto(out *ClaimValidationRule) {
 | 
					func (in *ClaimValidationRule) DeepCopyInto(out *ClaimValidationRule) {
 | 
				
			||||||
	*out = *in
 | 
						*out = *in
 | 
				
			||||||
@@ -267,6 +289,22 @@ func (in *EgressSelectorConfiguration) DeepCopyObject() runtime.Object {
 | 
				
			|||||||
	return nil
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 | 
				
			||||||
 | 
					func (in *ExtraMapping) DeepCopyInto(out *ExtraMapping) {
 | 
				
			||||||
 | 
						*out = *in
 | 
				
			||||||
 | 
						return
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExtraMapping.
 | 
				
			||||||
 | 
					func (in *ExtraMapping) DeepCopy() *ExtraMapping {
 | 
				
			||||||
 | 
						if in == nil {
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						out := new(ExtraMapping)
 | 
				
			||||||
 | 
						in.DeepCopyInto(out)
 | 
				
			||||||
 | 
						return out
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 | 
					// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 | 
				
			||||||
func (in *Issuer) DeepCopyInto(out *Issuer) {
 | 
					func (in *Issuer) DeepCopyInto(out *Issuer) {
 | 
				
			||||||
	*out = *in
 | 
						*out = *in
 | 
				
			||||||
@@ -298,6 +336,11 @@ func (in *JWTAuthenticator) DeepCopyInto(out *JWTAuthenticator) {
 | 
				
			|||||||
		copy(*out, *in)
 | 
							copy(*out, *in)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	in.ClaimMappings.DeepCopyInto(&out.ClaimMappings)
 | 
						in.ClaimMappings.DeepCopyInto(&out.ClaimMappings)
 | 
				
			||||||
 | 
						if in.UserValidationRules != nil {
 | 
				
			||||||
 | 
							in, out := &in.UserValidationRules, &out.UserValidationRules
 | 
				
			||||||
 | 
							*out = make([]UserValidationRule, len(*in))
 | 
				
			||||||
 | 
							copy(*out, *in)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
	return
 | 
						return
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -437,6 +480,22 @@ func (in *UDSTransport) DeepCopy() *UDSTransport {
 | 
				
			|||||||
	return out
 | 
						return out
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 | 
				
			||||||
 | 
					func (in *UserValidationRule) DeepCopyInto(out *UserValidationRule) {
 | 
				
			||||||
 | 
						*out = *in
 | 
				
			||||||
 | 
						return
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UserValidationRule.
 | 
				
			||||||
 | 
					func (in *UserValidationRule) DeepCopy() *UserValidationRule {
 | 
				
			||||||
 | 
						if in == nil {
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						out := new(UserValidationRule)
 | 
				
			||||||
 | 
						in.DeepCopyInto(out)
 | 
				
			||||||
 | 
						return out
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 | 
					// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 | 
				
			||||||
func (in *WebhookConfiguration) DeepCopyInto(out *WebhookConfiguration) {
 | 
					func (in *WebhookConfiguration) DeepCopyInto(out *WebhookConfiguration) {
 | 
				
			||||||
	*out = *in
 | 
						*out = *in
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										154
									
								
								staging/src/k8s.io/apiserver/pkg/authentication/cel/compile.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										154
									
								
								staging/src/k8s.io/apiserver/pkg/authentication/cel/compile.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,154 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					Copyright 2023 The Kubernetes Authors.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Licensed under the Apache License, Version 2.0 (the "License");
 | 
				
			||||||
 | 
					you may not use this file except in compliance with the License.
 | 
				
			||||||
 | 
					You may obtain a copy of the License at
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    http://www.apache.org/licenses/LICENSE-2.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Unless required by applicable law or agreed to in writing, software
 | 
				
			||||||
 | 
					distributed under the License is distributed on an "AS IS" BASIS,
 | 
				
			||||||
 | 
					WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
				
			||||||
 | 
					See the License for the specific language governing permissions and
 | 
				
			||||||
 | 
					limitations under the License.
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package cel
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/google/cel-go/cel"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"k8s.io/apimachinery/pkg/util/version"
 | 
				
			||||||
 | 
						apiservercel "k8s.io/apiserver/pkg/cel"
 | 
				
			||||||
 | 
						"k8s.io/apiserver/pkg/cel/environment"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const (
 | 
				
			||||||
 | 
						claimsVarName = "claims"
 | 
				
			||||||
 | 
						userVarName   = "user"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// compiler implements the Compiler interface.
 | 
				
			||||||
 | 
					type compiler struct {
 | 
				
			||||||
 | 
						// varEnvs is a map of CEL environments, keyed by the name of the CEL variable.
 | 
				
			||||||
 | 
						// The CEL variable is available to the expression.
 | 
				
			||||||
 | 
						// We have 2 environments, one for claims and one for user.
 | 
				
			||||||
 | 
						varEnvs map[string]*environment.EnvSet
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// NewCompiler returns a new Compiler.
 | 
				
			||||||
 | 
					func NewCompiler(env *environment.EnvSet) Compiler {
 | 
				
			||||||
 | 
						return &compiler{
 | 
				
			||||||
 | 
							varEnvs: mustBuildEnvs(env),
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// CompileClaimsExpression compiles the given expressionAccessor into a CEL program that can be evaluated.
 | 
				
			||||||
 | 
					// The claims CEL variable is available to the expression.
 | 
				
			||||||
 | 
					func (c compiler) CompileClaimsExpression(expressionAccessor ExpressionAccessor) (CompilationResult, error) {
 | 
				
			||||||
 | 
						return c.compile(expressionAccessor, claimsVarName)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// CompileUserExpression compiles the given expressionAccessor into a CEL program that can be evaluated.
 | 
				
			||||||
 | 
					// The user CEL variable is available to the expression.
 | 
				
			||||||
 | 
					func (c compiler) CompileUserExpression(expressionAccessor ExpressionAccessor) (CompilationResult, error) {
 | 
				
			||||||
 | 
						return c.compile(expressionAccessor, userVarName)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (c compiler) compile(expressionAccessor ExpressionAccessor, envVarName string) (CompilationResult, error) {
 | 
				
			||||||
 | 
						resultError := func(errorString string, errType apiservercel.ErrorType) (CompilationResult, error) {
 | 
				
			||||||
 | 
							return CompilationResult{}, &apiservercel.Error{
 | 
				
			||||||
 | 
								Type:   errType,
 | 
				
			||||||
 | 
								Detail: errorString,
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						env, err := c.varEnvs[envVarName].Env(environment.StoredExpressions)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return resultError(fmt.Sprintf("unexpected error loading CEL environment: %v", err), apiservercel.ErrorTypeInternal)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ast, issues := env.Compile(expressionAccessor.GetExpression())
 | 
				
			||||||
 | 
						if issues != nil {
 | 
				
			||||||
 | 
							return resultError("compilation failed: "+issues.String(), apiservercel.ErrorTypeInvalid)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						found := false
 | 
				
			||||||
 | 
						returnTypes := expressionAccessor.ReturnTypes()
 | 
				
			||||||
 | 
						for _, returnType := range returnTypes {
 | 
				
			||||||
 | 
							if ast.OutputType() == returnType || cel.AnyType == returnType {
 | 
				
			||||||
 | 
								found = true
 | 
				
			||||||
 | 
								break
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if !found {
 | 
				
			||||||
 | 
							var reason string
 | 
				
			||||||
 | 
							if len(returnTypes) == 1 {
 | 
				
			||||||
 | 
								reason = fmt.Sprintf("must evaluate to %v", returnTypes[0].String())
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								reason = fmt.Sprintf("must evaluate to one of %v", returnTypes)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							return resultError(reason, apiservercel.ErrorTypeInvalid)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if _, err = cel.AstToCheckedExpr(ast); err != nil {
 | 
				
			||||||
 | 
							// should be impossible since env.Compile returned no issues
 | 
				
			||||||
 | 
							return resultError("unexpected compilation error: "+err.Error(), apiservercel.ErrorTypeInternal)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						prog, err := env.Program(ast)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return resultError("program instantiation failed: "+err.Error(), apiservercel.ErrorTypeInternal)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return CompilationResult{
 | 
				
			||||||
 | 
							Program:            prog,
 | 
				
			||||||
 | 
							ExpressionAccessor: expressionAccessor,
 | 
				
			||||||
 | 
						}, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func buildUserType() *apiservercel.DeclType {
 | 
				
			||||||
 | 
						field := func(name string, declType *apiservercel.DeclType, required bool) *apiservercel.DeclField {
 | 
				
			||||||
 | 
							return apiservercel.NewDeclField(name, declType, required, nil, nil)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						fields := func(fields ...*apiservercel.DeclField) map[string]*apiservercel.DeclField {
 | 
				
			||||||
 | 
							result := make(map[string]*apiservercel.DeclField, len(fields))
 | 
				
			||||||
 | 
							for _, f := range fields {
 | 
				
			||||||
 | 
								result[f.Name] = f
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return result
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return apiservercel.NewObjectType("kubernetes.UserInfo", fields(
 | 
				
			||||||
 | 
							field("username", apiservercel.StringType, false),
 | 
				
			||||||
 | 
							field("uid", apiservercel.StringType, false),
 | 
				
			||||||
 | 
							field("groups", apiservercel.NewListType(apiservercel.StringType, -1), false),
 | 
				
			||||||
 | 
							field("extra", apiservercel.NewMapType(apiservercel.StringType, apiservercel.NewListType(apiservercel.StringType, -1), -1), false),
 | 
				
			||||||
 | 
						))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func mustBuildEnvs(baseEnv *environment.EnvSet) map[string]*environment.EnvSet {
 | 
				
			||||||
 | 
						buildEnvSet := func(envOpts []cel.EnvOption, declTypes []*apiservercel.DeclType) *environment.EnvSet {
 | 
				
			||||||
 | 
							env, err := baseEnv.Extend(environment.VersionedOptions{
 | 
				
			||||||
 | 
								IntroducedVersion: version.MajorMinor(1, 0),
 | 
				
			||||||
 | 
								EnvOptions:        envOpts,
 | 
				
			||||||
 | 
								DeclTypes:         declTypes,
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								panic(fmt.Sprintf("environment misconfigured: %v", err))
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return env
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						userType := buildUserType()
 | 
				
			||||||
 | 
						claimsType := apiservercel.NewMapType(apiservercel.StringType, apiservercel.AnyType, -1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						envs := make(map[string]*environment.EnvSet, 2) // build two environments, one for claims and one for user
 | 
				
			||||||
 | 
						envs[claimsVarName] = buildEnvSet([]cel.EnvOption{cel.Variable(claimsVarName, claimsType.CelType())}, []*apiservercel.DeclType{claimsType})
 | 
				
			||||||
 | 
						envs[userVarName] = buildEnvSet([]cel.EnvOption{cel.Variable(userVarName, userType.CelType())}, []*apiservercel.DeclType{userType})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return envs
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,263 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					Copyright 2023 The Kubernetes Authors.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Licensed under the Apache License, Version 2.0 (the "License");
 | 
				
			||||||
 | 
					you may not use this file except in compliance with the License.
 | 
				
			||||||
 | 
					You may obtain a copy of the License at
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    http://www.apache.org/licenses/LICENSE-2.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Unless required by applicable law or agreed to in writing, software
 | 
				
			||||||
 | 
					distributed under the License is distributed on an "AS IS" BASIS,
 | 
				
			||||||
 | 
					WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
				
			||||||
 | 
					See the License for the specific language governing permissions and
 | 
				
			||||||
 | 
					limitations under the License.
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package cel
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"reflect"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
						"testing"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						authenticationv1 "k8s.io/api/authentication/v1"
 | 
				
			||||||
 | 
						apiservercel "k8s.io/apiserver/pkg/cel"
 | 
				
			||||||
 | 
						"k8s.io/apiserver/pkg/cel/environment"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestCompileClaimsExpression(t *testing.T) {
 | 
				
			||||||
 | 
						testCases := []struct {
 | 
				
			||||||
 | 
							name                string
 | 
				
			||||||
 | 
							expressionAccessors []ExpressionAccessor
 | 
				
			||||||
 | 
						}{
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "valid ClaimMappingCondition",
 | 
				
			||||||
 | 
								expressionAccessors: []ExpressionAccessor{
 | 
				
			||||||
 | 
									&ClaimMappingExpression{
 | 
				
			||||||
 | 
										Expression: "claims.foo",
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "valid ClaimValidationCondition",
 | 
				
			||||||
 | 
								expressionAccessors: []ExpressionAccessor{
 | 
				
			||||||
 | 
									&ClaimValidationCondition{
 | 
				
			||||||
 | 
										Expression: "claims.foo == 'bar'",
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "valid ExtraMapppingCondition",
 | 
				
			||||||
 | 
								expressionAccessors: []ExpressionAccessor{
 | 
				
			||||||
 | 
									&ExtraMappingExpression{
 | 
				
			||||||
 | 
										Expression: "claims.foo",
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						compiler := NewCompiler(environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion()))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, tc := range testCases {
 | 
				
			||||||
 | 
							t.Run(tc.name, func(t *testing.T) {
 | 
				
			||||||
 | 
								for _, expressionAccessor := range tc.expressionAccessors {
 | 
				
			||||||
 | 
									_, err := compiler.CompileClaimsExpression(expressionAccessor)
 | 
				
			||||||
 | 
									if err != nil {
 | 
				
			||||||
 | 
										t.Errorf("unexpected error: %v", err)
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestCompileUserExpression(t *testing.T) {
 | 
				
			||||||
 | 
						testCases := []struct {
 | 
				
			||||||
 | 
							name                string
 | 
				
			||||||
 | 
							expressionAccessors []ExpressionAccessor
 | 
				
			||||||
 | 
						}{
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "valid UserValidationCondition",
 | 
				
			||||||
 | 
								expressionAccessors: []ExpressionAccessor{
 | 
				
			||||||
 | 
									&ExtraMappingExpression{
 | 
				
			||||||
 | 
										Expression: "user.username == 'bar'",
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						compiler := NewCompiler(environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion()))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, tc := range testCases {
 | 
				
			||||||
 | 
							t.Run(tc.name, func(t *testing.T) {
 | 
				
			||||||
 | 
								for _, expressionAccessor := range tc.expressionAccessors {
 | 
				
			||||||
 | 
									_, err := compiler.CompileUserExpression(expressionAccessor)
 | 
				
			||||||
 | 
									if err != nil {
 | 
				
			||||||
 | 
										t.Errorf("unexpected error: %v", err)
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestCompileClaimsExpressionError(t *testing.T) {
 | 
				
			||||||
 | 
						testCases := []struct {
 | 
				
			||||||
 | 
							name                string
 | 
				
			||||||
 | 
							expressionAccessors []ExpressionAccessor
 | 
				
			||||||
 | 
							wantErr             string
 | 
				
			||||||
 | 
						}{
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "invalid ClaimValidationCondition",
 | 
				
			||||||
 | 
								expressionAccessors: []ExpressionAccessor{
 | 
				
			||||||
 | 
									&ClaimValidationCondition{
 | 
				
			||||||
 | 
										Expression: "claims.foo",
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								wantErr: "must evaluate to bool",
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "UserValidationCondition with wrong env",
 | 
				
			||||||
 | 
								expressionAccessors: []ExpressionAccessor{
 | 
				
			||||||
 | 
									&UserValidationCondition{
 | 
				
			||||||
 | 
										Expression: "user.username == 'foo'",
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								wantErr: `compilation failed: ERROR: <input>:1:1: undeclared reference to 'user' (in container '')`,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "invalid ClaimMappingCondition",
 | 
				
			||||||
 | 
								expressionAccessors: []ExpressionAccessor{
 | 
				
			||||||
 | 
									&ClaimMappingExpression{
 | 
				
			||||||
 | 
										Expression: "claims + 1",
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								wantErr: `compilation failed: ERROR: <input>:1:8: found no matching overload for '_+_' applied to '(map(string, any), int)'`,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						compiler := NewCompiler(environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion()))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, tc := range testCases {
 | 
				
			||||||
 | 
							t.Run(tc.name, func(t *testing.T) {
 | 
				
			||||||
 | 
								for _, expressionAccessor := range tc.expressionAccessors {
 | 
				
			||||||
 | 
									_, err := compiler.CompileClaimsExpression(expressionAccessor)
 | 
				
			||||||
 | 
									if err == nil {
 | 
				
			||||||
 | 
										t.Errorf("expected error but got nil")
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									if !strings.Contains(err.Error(), tc.wantErr) {
 | 
				
			||||||
 | 
										t.Errorf("expected error to contain %q but got %q", tc.wantErr, err.Error())
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestCompileUserExpressionError(t *testing.T) {
 | 
				
			||||||
 | 
						testCases := []struct {
 | 
				
			||||||
 | 
							name                string
 | 
				
			||||||
 | 
							expressionAccessors []ExpressionAccessor
 | 
				
			||||||
 | 
							wantErr             string
 | 
				
			||||||
 | 
						}{
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "invalid UserValidationCondition",
 | 
				
			||||||
 | 
								expressionAccessors: []ExpressionAccessor{
 | 
				
			||||||
 | 
									&UserValidationCondition{
 | 
				
			||||||
 | 
										Expression: "user.username",
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								wantErr: "must evaluate to bool",
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "ClamMappingCondition with wrong env",
 | 
				
			||||||
 | 
								expressionAccessors: []ExpressionAccessor{
 | 
				
			||||||
 | 
									&ClaimMappingExpression{
 | 
				
			||||||
 | 
										Expression: "claims.foo",
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								wantErr: `compilation failed: ERROR: <input>:1:1: undeclared reference to 'claims' (in container '')`,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "ExtraMappingCondition with wrong env",
 | 
				
			||||||
 | 
								expressionAccessors: []ExpressionAccessor{
 | 
				
			||||||
 | 
									&ExtraMappingExpression{
 | 
				
			||||||
 | 
										Expression: "claims.foo",
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								wantErr: `compilation failed: ERROR: <input>:1:1: undeclared reference to 'claims' (in container '')`,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "ClaimValidationCondition with wrong env",
 | 
				
			||||||
 | 
								expressionAccessors: []ExpressionAccessor{
 | 
				
			||||||
 | 
									&ClaimValidationCondition{
 | 
				
			||||||
 | 
										Expression: "claims.foo == 'bar'",
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								wantErr: `compilation failed: ERROR: <input>:1:1: undeclared reference to 'claims' (in container '')`,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "UserValidationCondition expression with unknown field",
 | 
				
			||||||
 | 
								expressionAccessors: []ExpressionAccessor{
 | 
				
			||||||
 | 
									&UserValidationCondition{
 | 
				
			||||||
 | 
										Expression: "user.unknown == 'foo'",
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								wantErr: `compilation failed: ERROR: <input>:1:5: undefined field 'unknown'`,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						compiler := NewCompiler(environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion()))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, tc := range testCases {
 | 
				
			||||||
 | 
							t.Run(tc.name, func(t *testing.T) {
 | 
				
			||||||
 | 
								for _, expressionAccessor := range tc.expressionAccessors {
 | 
				
			||||||
 | 
									_, err := compiler.CompileUserExpression(expressionAccessor)
 | 
				
			||||||
 | 
									if err == nil {
 | 
				
			||||||
 | 
										t.Errorf("expected error but got nil")
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									if !strings.Contains(err.Error(), tc.wantErr) {
 | 
				
			||||||
 | 
										t.Errorf("expected error to contain %q but got %q", tc.wantErr, err.Error())
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestBuildUserType(t *testing.T) {
 | 
				
			||||||
 | 
						userDeclType := buildUserType()
 | 
				
			||||||
 | 
						userType := reflect.TypeOf(authenticationv1.UserInfo{})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if len(userDeclType.Fields) != userType.NumField() {
 | 
				
			||||||
 | 
							t.Errorf("expected %d fields, got %d", userType.NumField(), len(userDeclType.Fields))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for i := 0; i < userType.NumField(); i++ {
 | 
				
			||||||
 | 
							field := userType.Field(i)
 | 
				
			||||||
 | 
							jsonTagParts := strings.Split(field.Tag.Get("json"), ",")
 | 
				
			||||||
 | 
							if len(jsonTagParts) < 1 {
 | 
				
			||||||
 | 
								t.Fatal("expected json tag to be present")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							fieldName := jsonTagParts[0]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							declField, ok := userDeclType.Fields[fieldName]
 | 
				
			||||||
 | 
							if !ok {
 | 
				
			||||||
 | 
								t.Errorf("expected field %q to be present", field.Name)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if nativeTypeToCELType(t, field.Type).CelType().Equal(declField.Type.CelType()).Value() != true {
 | 
				
			||||||
 | 
								t.Errorf("expected field %q to have type %v, got %v", field.Name, field.Type, declField.Type)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func nativeTypeToCELType(t *testing.T, nativeType reflect.Type) *apiservercel.DeclType {
 | 
				
			||||||
 | 
						switch nativeType {
 | 
				
			||||||
 | 
						case reflect.TypeOf(""):
 | 
				
			||||||
 | 
							return apiservercel.StringType
 | 
				
			||||||
 | 
						case reflect.TypeOf([]string{}):
 | 
				
			||||||
 | 
							return apiservercel.NewListType(apiservercel.StringType, -1)
 | 
				
			||||||
 | 
						case reflect.TypeOf(map[string]authenticationv1.ExtraValue{}):
 | 
				
			||||||
 | 
							return apiservercel.NewMapType(apiservercel.StringType, apiservercel.AnyType, -1)
 | 
				
			||||||
 | 
						default:
 | 
				
			||||||
 | 
							t.Fatalf("unsupported type %v", nativeType)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										147
									
								
								staging/src/k8s.io/apiserver/pkg/authentication/cel/interface.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										147
									
								
								staging/src/k8s.io/apiserver/pkg/authentication/cel/interface.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,147 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					Copyright 2023 The Kubernetes Authors.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Licensed under the Apache License, Version 2.0 (the "License");
 | 
				
			||||||
 | 
					you may not use this file except in compliance with the License.
 | 
				
			||||||
 | 
					You may obtain a copy of the License at
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    http://www.apache.org/licenses/LICENSE-2.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Unless required by applicable law or agreed to in writing, software
 | 
				
			||||||
 | 
					distributed under the License is distributed on an "AS IS" BASIS,
 | 
				
			||||||
 | 
					WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
				
			||||||
 | 
					See the License for the specific language governing permissions and
 | 
				
			||||||
 | 
					limitations under the License.
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Package cel contains the CEL related interfaces and structs for authentication.
 | 
				
			||||||
 | 
					package cel
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"context"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						celgo "github.com/google/cel-go/cel"
 | 
				
			||||||
 | 
						"github.com/google/cel-go/common/types/ref"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ExpressionAccessor is an interface that provides access to a CEL expression.
 | 
				
			||||||
 | 
					type ExpressionAccessor interface {
 | 
				
			||||||
 | 
						GetExpression() string
 | 
				
			||||||
 | 
						ReturnTypes() []*celgo.Type
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// CompilationResult represents a compiled validations expression.
 | 
				
			||||||
 | 
					type CompilationResult struct {
 | 
				
			||||||
 | 
						Program            celgo.Program
 | 
				
			||||||
 | 
						ExpressionAccessor ExpressionAccessor
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// EvaluationResult contains the minimal required fields and metadata of a cel evaluation
 | 
				
			||||||
 | 
					type EvaluationResult struct {
 | 
				
			||||||
 | 
						EvalResult         ref.Val
 | 
				
			||||||
 | 
						ExpressionAccessor ExpressionAccessor
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Compiler provides a CEL expression compiler configured with the desired authentication related CEL variables.
 | 
				
			||||||
 | 
					type Compiler interface {
 | 
				
			||||||
 | 
						CompileClaimsExpression(expressionAccessor ExpressionAccessor) (CompilationResult, error)
 | 
				
			||||||
 | 
						CompileUserExpression(expressionAccessor ExpressionAccessor) (CompilationResult, error)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ClaimsMapper provides a CEL expression mapper configured with the claims CEL variable.
 | 
				
			||||||
 | 
					type ClaimsMapper interface {
 | 
				
			||||||
 | 
						// EvalClaimMapping evaluates the given claim mapping expression and returns a EvaluationResult.
 | 
				
			||||||
 | 
						// This is used for username, groups and uid claim mapping that contains a single expression.
 | 
				
			||||||
 | 
						EvalClaimMapping(ctx context.Context, claims *unstructured.Unstructured) (EvaluationResult, error)
 | 
				
			||||||
 | 
						// EvalClaimMappings evaluates the given expressions and returns a list of EvaluationResult.
 | 
				
			||||||
 | 
						// This is used for extra claim mapping and claim validation that contains a list of expressions.
 | 
				
			||||||
 | 
						EvalClaimMappings(ctx context.Context, claims *unstructured.Unstructured) ([]EvaluationResult, error)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// UserMapper provides a CEL expression mapper configured with the user CEL variable.
 | 
				
			||||||
 | 
					type UserMapper interface {
 | 
				
			||||||
 | 
						// EvalUser evaluates the given user expressions and returns a list of EvaluationResult.
 | 
				
			||||||
 | 
						// This is used for user validation that contains a list of expressions.
 | 
				
			||||||
 | 
						EvalUser(ctx context.Context, userInfo *unstructured.Unstructured) ([]EvaluationResult, error)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var _ ExpressionAccessor = &ClaimMappingExpression{}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ClaimMappingExpression is a CEL expression that maps a claim.
 | 
				
			||||||
 | 
					type ClaimMappingExpression struct {
 | 
				
			||||||
 | 
						Expression string
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetExpression returns the CEL expression.
 | 
				
			||||||
 | 
					func (v *ClaimMappingExpression) GetExpression() string {
 | 
				
			||||||
 | 
						return v.Expression
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ReturnTypes returns the CEL expression return types.
 | 
				
			||||||
 | 
					func (v *ClaimMappingExpression) ReturnTypes() []*celgo.Type {
 | 
				
			||||||
 | 
						// return types is only used for validation. The claims variable that's available
 | 
				
			||||||
 | 
						// to the claim mapping expressions is a map[string]interface{}, so we can't
 | 
				
			||||||
 | 
						// really know what the return type is during compilation. Strict type checking
 | 
				
			||||||
 | 
						// is done during evaluation.
 | 
				
			||||||
 | 
						return []*celgo.Type{celgo.AnyType}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var _ ExpressionAccessor = &ClaimValidationCondition{}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ClaimValidationCondition is a CEL expression that validates a claim.
 | 
				
			||||||
 | 
					type ClaimValidationCondition struct {
 | 
				
			||||||
 | 
						Expression string
 | 
				
			||||||
 | 
						Message    string
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetExpression returns the CEL expression.
 | 
				
			||||||
 | 
					func (v *ClaimValidationCondition) GetExpression() string {
 | 
				
			||||||
 | 
						return v.Expression
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ReturnTypes returns the CEL expression return types.
 | 
				
			||||||
 | 
					func (v *ClaimValidationCondition) ReturnTypes() []*celgo.Type {
 | 
				
			||||||
 | 
						return []*celgo.Type{celgo.BoolType}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var _ ExpressionAccessor = &ExtraMappingExpression{}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ExtraMappingExpression is a CEL expression that maps an extra to a list of values.
 | 
				
			||||||
 | 
					type ExtraMappingExpression struct {
 | 
				
			||||||
 | 
						Key        string
 | 
				
			||||||
 | 
						Expression string
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetExpression returns the CEL expression.
 | 
				
			||||||
 | 
					func (v *ExtraMappingExpression) GetExpression() string {
 | 
				
			||||||
 | 
						return v.Expression
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ReturnTypes returns the CEL expression return types.
 | 
				
			||||||
 | 
					func (v *ExtraMappingExpression) ReturnTypes() []*celgo.Type {
 | 
				
			||||||
 | 
						// return types is only used for validation. The claims variable that's available
 | 
				
			||||||
 | 
						// to the claim mapping expressions is a map[string]interface{}, so we can't
 | 
				
			||||||
 | 
						// really know what the return type is during compilation. Strict type checking
 | 
				
			||||||
 | 
						// is done during evaluation.
 | 
				
			||||||
 | 
						return []*celgo.Type{celgo.AnyType}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var _ ExpressionAccessor = &UserValidationCondition{}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// UserValidationCondition is a CEL expression that validates a User.
 | 
				
			||||||
 | 
					type UserValidationCondition struct {
 | 
				
			||||||
 | 
						Expression string
 | 
				
			||||||
 | 
						Message    string
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetExpression returns the CEL expression.
 | 
				
			||||||
 | 
					func (v *UserValidationCondition) GetExpression() string {
 | 
				
			||||||
 | 
						return v.Expression
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ReturnTypes returns the CEL expression return types.
 | 
				
			||||||
 | 
					func (v *UserValidationCondition) ReturnTypes() []*celgo.Type {
 | 
				
			||||||
 | 
						return []*celgo.Type{celgo.BoolType}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,97 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					Copyright 2023 The Kubernetes Authors.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Licensed under the Apache License, Version 2.0 (the "License");
 | 
				
			||||||
 | 
					you may not use this file except in compliance with the License.
 | 
				
			||||||
 | 
					You may obtain a copy of the License at
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    http://www.apache.org/licenses/LICENSE-2.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Unless required by applicable law or agreed to in writing, software
 | 
				
			||||||
 | 
					distributed under the License is distributed on an "AS IS" BASIS,
 | 
				
			||||||
 | 
					WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
				
			||||||
 | 
					See the License for the specific language governing permissions and
 | 
				
			||||||
 | 
					limitations under the License.
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package cel
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"context"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var _ ClaimsMapper = &mapper{}
 | 
				
			||||||
 | 
					var _ UserMapper = &mapper{}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// mapper implements the ClaimsMapper and UserMapper interface.
 | 
				
			||||||
 | 
					type mapper struct {
 | 
				
			||||||
 | 
						compilationResults []CompilationResult
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// CELMapper is a struct that holds the compiled expressions for
 | 
				
			||||||
 | 
					// username, groups, uid, extra, claimValidation and userValidation
 | 
				
			||||||
 | 
					type CELMapper struct {
 | 
				
			||||||
 | 
						Username             ClaimsMapper
 | 
				
			||||||
 | 
						Groups               ClaimsMapper
 | 
				
			||||||
 | 
						UID                  ClaimsMapper
 | 
				
			||||||
 | 
						Extra                ClaimsMapper
 | 
				
			||||||
 | 
						ClaimValidationRules ClaimsMapper
 | 
				
			||||||
 | 
						UserValidationRules  UserMapper
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// NewClaimsMapper returns a new ClaimsMapper.
 | 
				
			||||||
 | 
					func NewClaimsMapper(compilationResults []CompilationResult) ClaimsMapper {
 | 
				
			||||||
 | 
						return &mapper{
 | 
				
			||||||
 | 
							compilationResults: compilationResults,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// NewUserMapper returns a new UserMapper.
 | 
				
			||||||
 | 
					func NewUserMapper(compilationResults []CompilationResult) UserMapper {
 | 
				
			||||||
 | 
						return &mapper{
 | 
				
			||||||
 | 
							compilationResults: compilationResults,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// EvalClaimMapping evaluates the given claim mapping expression and returns a EvaluationResult.
 | 
				
			||||||
 | 
					func (m *mapper) EvalClaimMapping(ctx context.Context, claims *unstructured.Unstructured) (EvaluationResult, error) {
 | 
				
			||||||
 | 
						results, err := m.eval(ctx, map[string]interface{}{claimsVarName: claims.Object})
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return EvaluationResult{}, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if len(results) != 1 {
 | 
				
			||||||
 | 
							return EvaluationResult{}, fmt.Errorf("expected 1 evaluation result, got %d", len(results))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return results[0], nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// EvalClaimMappings evaluates the given expressions and returns a list of EvaluationResult.
 | 
				
			||||||
 | 
					func (m *mapper) EvalClaimMappings(ctx context.Context, claims *unstructured.Unstructured) ([]EvaluationResult, error) {
 | 
				
			||||||
 | 
						return m.eval(ctx, map[string]interface{}{claimsVarName: claims.Object})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// EvalUser evaluates the given user expressions and returns a list of EvaluationResult.
 | 
				
			||||||
 | 
					func (m *mapper) EvalUser(ctx context.Context, userInfo *unstructured.Unstructured) ([]EvaluationResult, error) {
 | 
				
			||||||
 | 
						return m.eval(ctx, map[string]interface{}{userVarName: userInfo.Object})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (m *mapper) eval(ctx context.Context, input map[string]interface{}) ([]EvaluationResult, error) {
 | 
				
			||||||
 | 
						evaluations := make([]EvaluationResult, len(m.compilationResults))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for i, compilationResult := range m.compilationResults {
 | 
				
			||||||
 | 
							var evaluation = &evaluations[i]
 | 
				
			||||||
 | 
							evaluation.ExpressionAccessor = compilationResult.ExpressionAccessor
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							evalResult, _, err := compilationResult.Program.ContextEval(ctx, input)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return nil, fmt.Errorf("expression '%s' resulted in error: %w", compilationResult.ExpressionAccessor.GetExpression(), err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							evaluation.EvalResult = evalResult
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return evaluations, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -35,18 +35,25 @@ import (
 | 
				
			|||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
	"io/ioutil"
 | 
						"io/ioutil"
 | 
				
			||||||
	"net/http"
 | 
						"net/http"
 | 
				
			||||||
 | 
						"reflect"
 | 
				
			||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
	"sync"
 | 
						"sync"
 | 
				
			||||||
	"sync/atomic"
 | 
						"sync/atomic"
 | 
				
			||||||
	"time"
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/coreos/go-oidc"
 | 
						"github.com/coreos/go-oidc"
 | 
				
			||||||
 | 
						celgo "github.com/google/cel-go/cel"
 | 
				
			||||||
 | 
						"github.com/google/cel-go/common/types/ref"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						authenticationv1 "k8s.io/api/authentication/v1"
 | 
				
			||||||
 | 
						"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
 | 
				
			||||||
 | 
						"k8s.io/apimachinery/pkg/runtime"
 | 
				
			||||||
	"k8s.io/apimachinery/pkg/util/net"
 | 
						"k8s.io/apimachinery/pkg/util/net"
 | 
				
			||||||
	"k8s.io/apimachinery/pkg/util/wait"
 | 
						"k8s.io/apimachinery/pkg/util/wait"
 | 
				
			||||||
	"k8s.io/apiserver/pkg/apis/apiserver"
 | 
						"k8s.io/apiserver/pkg/apis/apiserver"
 | 
				
			||||||
	apiservervalidation "k8s.io/apiserver/pkg/apis/apiserver/validation"
 | 
						apiservervalidation "k8s.io/apiserver/pkg/apis/apiserver/validation"
 | 
				
			||||||
	"k8s.io/apiserver/pkg/authentication/authenticator"
 | 
						"k8s.io/apiserver/pkg/authentication/authenticator"
 | 
				
			||||||
 | 
						authenticationcel "k8s.io/apiserver/pkg/authentication/cel"
 | 
				
			||||||
	"k8s.io/apiserver/pkg/authentication/user"
 | 
						"k8s.io/apiserver/pkg/authentication/user"
 | 
				
			||||||
	certutil "k8s.io/client-go/util/cert"
 | 
						certutil "k8s.io/client-go/util/cert"
 | 
				
			||||||
	"k8s.io/klog/v2"
 | 
						"k8s.io/klog/v2"
 | 
				
			||||||
@@ -165,6 +172,13 @@ type Authenticator struct {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	// resolver is used to resolve distributed claims.
 | 
						// resolver is used to resolve distributed claims.
 | 
				
			||||||
	resolver *claimResolver
 | 
						resolver *claimResolver
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// celMapper contains the compiled CEL expressions for
 | 
				
			||||||
 | 
						// username, groups, uid, extra, claimMapping and claimValidation
 | 
				
			||||||
 | 
						celMapper authenticationcel.CELMapper
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// requiredClaims contains the list of claims that must be present in the token.
 | 
				
			||||||
 | 
						requiredClaims map[string]string
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (a *Authenticator) setVerifier(v *oidc.IDTokenVerifier) {
 | 
					func (a *Authenticator) setVerifier(v *oidc.IDTokenVerifier) {
 | 
				
			||||||
@@ -197,7 +211,8 @@ var allowedSigningAlgs = map[string]bool{
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func New(opts Options) (*Authenticator, error) {
 | 
					func New(opts Options) (*Authenticator, error) {
 | 
				
			||||||
	if err := apiservervalidation.ValidateJWTAuthenticator(opts.JWTAuthenticator).ToAggregate(); err != nil {
 | 
						celMapper, fieldErr := apiservervalidation.CompileAndValidateJWTAuthenticator(opts.JWTAuthenticator)
 | 
				
			||||||
 | 
						if err := fieldErr.ToAggregate(); err != nil {
 | 
				
			||||||
		return nil, err
 | 
							return nil, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -262,10 +277,19 @@ func New(opts Options) (*Authenticator, error) {
 | 
				
			|||||||
		resolver = newClaimResolver(groupsClaim, client, verifierConfig)
 | 
							resolver = newClaimResolver(groupsClaim, client, verifierConfig)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						requiredClaims := make(map[string]string)
 | 
				
			||||||
 | 
						for _, claimValidationRule := range opts.JWTAuthenticator.ClaimValidationRules {
 | 
				
			||||||
 | 
							if len(claimValidationRule.Claim) > 0 {
 | 
				
			||||||
 | 
								requiredClaims[claimValidationRule.Claim] = claimValidationRule.RequiredValue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	authenticator := &Authenticator{
 | 
						authenticator := &Authenticator{
 | 
				
			||||||
		jwtAuthenticator: opts.JWTAuthenticator,
 | 
							jwtAuthenticator: opts.JWTAuthenticator,
 | 
				
			||||||
		cancel:           cancel,
 | 
							cancel:           cancel,
 | 
				
			||||||
		resolver:         resolver,
 | 
							resolver:         resolver,
 | 
				
			||||||
 | 
							celMapper:        celMapper,
 | 
				
			||||||
 | 
							requiredClaims:   requiredClaims,
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if opts.KeySet != nil {
 | 
						if opts.KeySet != nil {
 | 
				
			||||||
@@ -521,10 +545,130 @@ func (a *Authenticator) AuthenticateToken(ctx context.Context, token string) (*a
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var claimsUnstructured *unstructured.Unstructured
 | 
				
			||||||
 | 
						// Convert the claims to unstructured so that we can evaluate the CEL expressions
 | 
				
			||||||
 | 
						// against the claims. This is done once here so that we don't have to convert
 | 
				
			||||||
 | 
						// the claims to unstructured multiple times in the CEL mapper for each mapping.
 | 
				
			||||||
 | 
						// Only perform this conversion if any of the mapping or validation rules contain
 | 
				
			||||||
 | 
						// CEL expressions.
 | 
				
			||||||
 | 
						// TODO(aramase): In the future when we look into making distributed claims work,
 | 
				
			||||||
 | 
						// we should see if we can skip this function and use a dynamic type resolver for
 | 
				
			||||||
 | 
						// both json.RawMessage and the distributed claim fetching.
 | 
				
			||||||
 | 
						if a.celMapper.Username != nil || a.celMapper.Groups != nil || a.celMapper.UID != nil || a.celMapper.Extra != nil || a.celMapper.ClaimValidationRules != nil {
 | 
				
			||||||
 | 
							if claimsUnstructured, err = convertObjectToUnstructured(&c); err != nil {
 | 
				
			||||||
 | 
								return nil, false, fmt.Errorf("oidc: could not convert claims to unstructured: %w", err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var username string
 | 
				
			||||||
 | 
						if username, err = a.getUsername(ctx, c, claimsUnstructured); err != nil {
 | 
				
			||||||
 | 
							return nil, false, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						info := &user.DefaultInfo{Name: username}
 | 
				
			||||||
 | 
						if info.Groups, err = a.getGroups(ctx, c, claimsUnstructured); err != nil {
 | 
				
			||||||
 | 
							return nil, false, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if info.UID, err = a.getUID(ctx, c, claimsUnstructured); err != nil {
 | 
				
			||||||
 | 
							return nil, false, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						extra, err := a.getExtra(ctx, claimsUnstructured)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, false, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if len(extra) > 0 {
 | 
				
			||||||
 | 
							info.Extra = extra
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// check to ensure all required claims are present in the ID token and have matching values.
 | 
				
			||||||
 | 
						for claim, value := range a.requiredClaims {
 | 
				
			||||||
 | 
							if !c.hasClaim(claim) {
 | 
				
			||||||
 | 
								return nil, false, fmt.Errorf("oidc: required claim %s not present in ID token", claim)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// NOTE: Only string values are supported as valid required claim values.
 | 
				
			||||||
 | 
							var claimValue string
 | 
				
			||||||
 | 
							if err := c.unmarshalClaim(claim, &claimValue); err != nil {
 | 
				
			||||||
 | 
								return nil, false, fmt.Errorf("oidc: parse claim %s: %w", claim, err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if claimValue != value {
 | 
				
			||||||
 | 
								return nil, false, fmt.Errorf("oidc: required claim %s value does not match. Got = %s, want = %s", claim, claimValue, value)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if a.celMapper.ClaimValidationRules != nil {
 | 
				
			||||||
 | 
							evalResult, err := a.celMapper.ClaimValidationRules.EvalClaimMappings(ctx, claimsUnstructured)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return nil, false, fmt.Errorf("oidc: error evaluating claim validation expression: %w", err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if err := checkValidationRulesEvaluation(evalResult, func(a authenticationcel.ExpressionAccessor) (string, error) {
 | 
				
			||||||
 | 
								claimValidationCondition, ok := a.(*authenticationcel.ClaimValidationCondition)
 | 
				
			||||||
 | 
								if !ok {
 | 
				
			||||||
 | 
									return "", fmt.Errorf("invalid type conversion, expected ClaimValidationCondition")
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								return claimValidationCondition.Message, nil
 | 
				
			||||||
 | 
							}); err != nil {
 | 
				
			||||||
 | 
								return nil, false, fmt.Errorf("oidc: error evaluating claim validation expression: %w", err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if a.celMapper.UserValidationRules != nil {
 | 
				
			||||||
 | 
							userInfo := &authenticationv1.UserInfo{
 | 
				
			||||||
 | 
								Extra:    make(map[string]authenticationv1.ExtraValue),
 | 
				
			||||||
 | 
								Groups:   info.GetGroups(),
 | 
				
			||||||
 | 
								UID:      info.GetUID(),
 | 
				
			||||||
 | 
								Username: info.GetName(),
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							// Convert the extra information in the user object
 | 
				
			||||||
 | 
							for key, val := range info.GetExtra() {
 | 
				
			||||||
 | 
								userInfo.Extra[key] = authenticationv1.ExtraValue(val)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Convert the user info to unstructured so that we can evaluate the CEL expressions
 | 
				
			||||||
 | 
							// against the user info. This is done once here so that we don't have to convert
 | 
				
			||||||
 | 
							// the user info to unstructured multiple times in the CEL mapper for each mapping.
 | 
				
			||||||
 | 
							userInfoUnstructured, err := convertObjectToUnstructured(userInfo)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return nil, false, fmt.Errorf("oidc: could not convert user info to unstructured: %v", err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							evalResult, err := a.celMapper.UserValidationRules.EvalUser(ctx, userInfoUnstructured)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return nil, false, fmt.Errorf("oidc: error evaluating user info validation rule: %w", err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if err := checkValidationRulesEvaluation(evalResult, func(a authenticationcel.ExpressionAccessor) (string, error) {
 | 
				
			||||||
 | 
								userValidationCondition, ok := a.(*authenticationcel.UserValidationCondition)
 | 
				
			||||||
 | 
								if !ok {
 | 
				
			||||||
 | 
									return "", fmt.Errorf("invalid type conversion, expected UserValidationCondition")
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								return userValidationCondition.Message, nil
 | 
				
			||||||
 | 
							}); err != nil {
 | 
				
			||||||
 | 
								return nil, false, fmt.Errorf("oidc: error evaluating user info validation rule: %w", err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return &authenticator.Response{User: info}, true, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (a *Authenticator) getUsername(ctx context.Context, c claims, claimsUnstructured *unstructured.Unstructured) (string, error) {
 | 
				
			||||||
 | 
						if a.celMapper.Username != nil {
 | 
				
			||||||
 | 
							evalResult, err := a.celMapper.Username.EvalClaimMapping(ctx, claimsUnstructured)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return "", fmt.Errorf("oidc: error evaluating username claim expression: %w", err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if evalResult.EvalResult.Type() != celgo.StringType {
 | 
				
			||||||
 | 
								return "", fmt.Errorf("oidc: error evaluating username claim expression: %w", fmt.Errorf("username claim expression must return a string"))
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							return evalResult.EvalResult.Value().(string), nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	var username string
 | 
						var username string
 | 
				
			||||||
	usernameClaim := a.jwtAuthenticator.ClaimMappings.Username.Claim
 | 
						usernameClaim := a.jwtAuthenticator.ClaimMappings.Username.Claim
 | 
				
			||||||
	if err := c.unmarshalClaim(usernameClaim, &username); err != nil {
 | 
						if err := c.unmarshalClaim(usernameClaim, &username); err != nil {
 | 
				
			||||||
		return nil, false, fmt.Errorf("oidc: parse username claims %q: %v", usernameClaim, err)
 | 
							return "", fmt.Errorf("oidc: parse username claims %q: %v", usernameClaim, err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if usernameClaim == "email" {
 | 
						if usernameClaim == "email" {
 | 
				
			||||||
@@ -533,24 +677,26 @@ func (a *Authenticator) AuthenticateToken(ctx context.Context, token string) (*a
 | 
				
			|||||||
		if hasEmailVerified := c.hasClaim("email_verified"); hasEmailVerified {
 | 
							if hasEmailVerified := c.hasClaim("email_verified"); hasEmailVerified {
 | 
				
			||||||
			var emailVerified bool
 | 
								var emailVerified bool
 | 
				
			||||||
			if err := c.unmarshalClaim("email_verified", &emailVerified); err != nil {
 | 
								if err := c.unmarshalClaim("email_verified", &emailVerified); err != nil {
 | 
				
			||||||
				return nil, false, fmt.Errorf("oidc: parse 'email_verified' claim: %v", err)
 | 
									return "", fmt.Errorf("oidc: parse 'email_verified' claim: %v", err)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			// If the email_verified claim is present we have to verify it is set to `true`.
 | 
								// If the email_verified claim is present we have to verify it is set to `true`.
 | 
				
			||||||
			if !emailVerified {
 | 
								if !emailVerified {
 | 
				
			||||||
				return nil, false, fmt.Errorf("oidc: email not verified")
 | 
									return "", fmt.Errorf("oidc: email not verified")
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	userNamePrefix := a.jwtAuthenticator.ClaimMappings.Username.Prefix
 | 
						userNamePrefix := a.jwtAuthenticator.ClaimMappings.Username.Prefix
 | 
				
			||||||
	if userNamePrefix != nil && *userNamePrefix != "" {
 | 
						if userNamePrefix != nil && *userNamePrefix != "" {
 | 
				
			||||||
		username = *userNamePrefix + username
 | 
							return *userNamePrefix + username, nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return username, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	info := &user.DefaultInfo{Name: username}
 | 
					func (a *Authenticator) getGroups(ctx context.Context, c claims, claimsUnstructured *unstructured.Unstructured) ([]string, error) {
 | 
				
			||||||
	groupsClaim := a.jwtAuthenticator.ClaimMappings.Groups.Claim
 | 
						groupsClaim := a.jwtAuthenticator.ClaimMappings.Groups.Claim
 | 
				
			||||||
	if groupsClaim != "" {
 | 
						if len(groupsClaim) > 0 {
 | 
				
			||||||
		if _, ok := c[groupsClaim]; ok {
 | 
							if _, ok := c[groupsClaim]; ok {
 | 
				
			||||||
			// Some admins want to use string claims like "role" as the group value.
 | 
								// Some admins want to use string claims like "role" as the group value.
 | 
				
			||||||
			// Allow the group claim to be a single string instead of an array.
 | 
								// Allow the group claim to be a single string instead of an array.
 | 
				
			||||||
@@ -558,39 +704,91 @@ func (a *Authenticator) AuthenticateToken(ctx context.Context, token string) (*a
 | 
				
			|||||||
			// See: https://github.com/kubernetes/kubernetes/issues/33290
 | 
								// See: https://github.com/kubernetes/kubernetes/issues/33290
 | 
				
			||||||
			var groups stringOrArray
 | 
								var groups stringOrArray
 | 
				
			||||||
			if err := c.unmarshalClaim(groupsClaim, &groups); err != nil {
 | 
								if err := c.unmarshalClaim(groupsClaim, &groups); err != nil {
 | 
				
			||||||
				return nil, false, fmt.Errorf("oidc: parse groups claim %q: %v", groupsClaim, err)
 | 
									return nil, fmt.Errorf("oidc: parse groups claim %q: %w", groupsClaim, err)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			info.Groups = []string(groups)
 | 
					
 | 
				
			||||||
 | 
								prefix := a.jwtAuthenticator.ClaimMappings.Groups.Prefix
 | 
				
			||||||
 | 
								if prefix != nil && *prefix != "" {
 | 
				
			||||||
 | 
									for i, group := range groups {
 | 
				
			||||||
 | 
										groups[i] = *prefix + group
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	groupsPrefix := a.jwtAuthenticator.ClaimMappings.Groups.Prefix
 | 
								return []string(groups), nil
 | 
				
			||||||
	if groupsPrefix != nil && *groupsPrefix != "" {
 | 
					 | 
				
			||||||
		for i, group := range info.Groups {
 | 
					 | 
				
			||||||
			info.Groups[i] = *groupsPrefix + group
 | 
					 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// check to ensure all required claims are present in the ID token and have matching values.
 | 
						if a.celMapper.Groups == nil {
 | 
				
			||||||
	for _, claimValidationRule := range a.jwtAuthenticator.ClaimValidationRules {
 | 
							return nil, nil
 | 
				
			||||||
		claim := claimValidationRule.Claim
 | 
					 | 
				
			||||||
		value := claimValidationRule.RequiredValue
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		if !c.hasClaim(claim) {
 | 
					 | 
				
			||||||
			return nil, false, fmt.Errorf("oidc: required claim %s not present in ID token", claim)
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// NOTE: Only string values are supported as valid required claim values.
 | 
						evalResult, err := a.celMapper.Groups.EvalClaimMapping(ctx, claimsUnstructured)
 | 
				
			||||||
		var claimValue string
 | 
						if err != nil {
 | 
				
			||||||
		if err := c.unmarshalClaim(claim, &claimValue); err != nil {
 | 
							return nil, fmt.Errorf("oidc: error evaluating group claim expression: %w", err)
 | 
				
			||||||
			return nil, false, fmt.Errorf("oidc: parse claim %s: %v", claim, err)
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		if claimValue != value {
 | 
					 | 
				
			||||||
			return nil, false, fmt.Errorf("oidc: required claim %s value does not match. Got = %s, want = %s", claim, claimValue, value)
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return &authenticator.Response{User: info}, true, nil
 | 
						groups, err := convertCELValueToStringList(evalResult.EvalResult)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, fmt.Errorf("oidc: error evaluating group claim expression: %w", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return groups, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (a *Authenticator) getUID(ctx context.Context, c claims, claimsUnstructured *unstructured.Unstructured) (string, error) {
 | 
				
			||||||
 | 
						uidClaim := a.jwtAuthenticator.ClaimMappings.UID.Claim
 | 
				
			||||||
 | 
						if len(uidClaim) > 0 {
 | 
				
			||||||
 | 
							var uid string
 | 
				
			||||||
 | 
							if err := c.unmarshalClaim(uidClaim, &uid); err != nil {
 | 
				
			||||||
 | 
								return "", fmt.Errorf("oidc: parse uid claim %q: %w", uidClaim, err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return uid, nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if a.celMapper.UID == nil {
 | 
				
			||||||
 | 
							return "", nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						evalResult, err := a.celMapper.UID.EvalClaimMapping(ctx, claimsUnstructured)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return "", fmt.Errorf("oidc: error evaluating uid claim expression: %w", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if evalResult.EvalResult.Type() != celgo.StringType {
 | 
				
			||||||
 | 
							return "", fmt.Errorf("oidc: error evaluating uid claim expression: %w", fmt.Errorf("uid claim expression must return a string"))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return evalResult.EvalResult.Value().(string), nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (a *Authenticator) getExtra(ctx context.Context, claimsUnstructured *unstructured.Unstructured) (map[string][]string, error) {
 | 
				
			||||||
 | 
						if a.celMapper.Extra == nil {
 | 
				
			||||||
 | 
							return nil, nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						evalResult, err := a.celMapper.Extra.EvalClaimMappings(ctx, claimsUnstructured)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						extra := make(map[string][]string, len(evalResult))
 | 
				
			||||||
 | 
						for _, result := range evalResult {
 | 
				
			||||||
 | 
							extraMapping, ok := result.ExpressionAccessor.(*authenticationcel.ExtraMappingExpression)
 | 
				
			||||||
 | 
							if !ok {
 | 
				
			||||||
 | 
								return nil, fmt.Errorf("oidc: error evaluating extra claim expression: %w", fmt.Errorf("invalid type conversion, expected ExtraMappingCondition"))
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							extraValues, err := convertCELValueToStringList(result.EvalResult)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return nil, fmt.Errorf("oidc: error evaluating extra claim expression: %s: %w", extraMapping.Expression, err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if len(extraValues) == 0 {
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							extra[extraMapping.Key] = extraValues
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return extra, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// getClaimJWT gets a distributed claim JWT from url, using the supplied access
 | 
					// getClaimJWT gets a distributed claim JWT from url, using the supplied access
 | 
				
			||||||
@@ -655,3 +853,94 @@ func (c claims) hasClaim(name string) bool {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
	return true
 | 
						return true
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// convertCELValueToStringList converts the CEL value to a string list.
 | 
				
			||||||
 | 
					// The CEL value needs to be either a string or a list of strings.
 | 
				
			||||||
 | 
					// "", [] are treated as not being present and will return nil.
 | 
				
			||||||
 | 
					// Empty string in a list of strings is treated as not being present and will be filtered out.
 | 
				
			||||||
 | 
					func convertCELValueToStringList(val ref.Val) ([]string, error) {
 | 
				
			||||||
 | 
						switch val.Type().TypeName() {
 | 
				
			||||||
 | 
						case celgo.StringType.TypeName():
 | 
				
			||||||
 | 
							out := val.Value().(string)
 | 
				
			||||||
 | 
							if len(out) == 0 {
 | 
				
			||||||
 | 
								return nil, nil
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return []string{out}, nil
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						case celgo.ListType(nil).TypeName():
 | 
				
			||||||
 | 
							var result []string
 | 
				
			||||||
 | 
							switch val.Value().(type) {
 | 
				
			||||||
 | 
							case []interface{}:
 | 
				
			||||||
 | 
								for _, v := range val.Value().([]interface{}) {
 | 
				
			||||||
 | 
									out, ok := v.(string)
 | 
				
			||||||
 | 
									if !ok {
 | 
				
			||||||
 | 
										return nil, fmt.Errorf("expression must return a string or a list of strings")
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									if len(out) == 0 {
 | 
				
			||||||
 | 
										continue
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									result = append(result, out)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							case []ref.Val:
 | 
				
			||||||
 | 
								for _, v := range val.Value().([]ref.Val) {
 | 
				
			||||||
 | 
									out, ok := v.Value().(string)
 | 
				
			||||||
 | 
									if !ok {
 | 
				
			||||||
 | 
										return nil, fmt.Errorf("expression must return a string or a list of strings")
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									if len(out) == 0 {
 | 
				
			||||||
 | 
										continue
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									result = append(result, out)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							default:
 | 
				
			||||||
 | 
								return nil, fmt.Errorf("expression must return a string or a list of strings")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if len(result) == 0 {
 | 
				
			||||||
 | 
								return nil, nil
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							return result, nil
 | 
				
			||||||
 | 
						case celgo.NullType.TypeName():
 | 
				
			||||||
 | 
							return nil, nil
 | 
				
			||||||
 | 
						default:
 | 
				
			||||||
 | 
							return nil, fmt.Errorf("expression must return a string or a list of strings")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// messageFunc is a function that returns a message for a validation rule.
 | 
				
			||||||
 | 
					type messageFunc func(authenticationcel.ExpressionAccessor) (string, error)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// checkValidationRulesEvaluation checks if the validation rules evaluation results
 | 
				
			||||||
 | 
					// are valid. If the validation rules evaluation results are not valid, it returns
 | 
				
			||||||
 | 
					// an error with an optional message that was set in the validation rule.
 | 
				
			||||||
 | 
					func checkValidationRulesEvaluation(results []authenticationcel.EvaluationResult, messageFn messageFunc) error {
 | 
				
			||||||
 | 
						for _, result := range results {
 | 
				
			||||||
 | 
							if result.EvalResult.Type() != celgo.BoolType {
 | 
				
			||||||
 | 
								return fmt.Errorf("validation expression must return a boolean")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if !result.EvalResult.Value().(bool) {
 | 
				
			||||||
 | 
								expression := result.ExpressionAccessor.GetExpression()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								message, err := messageFn(result.ExpressionAccessor)
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									return err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								return fmt.Errorf("validation expression '%s' failed: %s", expression, message)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func convertObjectToUnstructured(obj interface{}) (*unstructured.Unstructured, error) {
 | 
				
			||||||
 | 
						if obj == nil || reflect.ValueOf(obj).IsNil() {
 | 
				
			||||||
 | 
							return &unstructured.Unstructured{Object: nil}, nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						ret, err := runtime.DefaultUnstructuredConverter.ToUnstructured(obj)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return &unstructured.Unstructured{Object: ret}, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -38,7 +38,10 @@ import (
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	"k8s.io/apiserver/pkg/apis/apiserver"
 | 
						"k8s.io/apiserver/pkg/apis/apiserver"
 | 
				
			||||||
	"k8s.io/apiserver/pkg/authentication/user"
 | 
						"k8s.io/apiserver/pkg/authentication/user"
 | 
				
			||||||
 | 
						"k8s.io/apiserver/pkg/features"
 | 
				
			||||||
	"k8s.io/apiserver/pkg/server/dynamiccertificates"
 | 
						"k8s.io/apiserver/pkg/server/dynamiccertificates"
 | 
				
			||||||
 | 
						utilfeature "k8s.io/apiserver/pkg/util/feature"
 | 
				
			||||||
 | 
						featuregatetesting "k8s.io/component-base/featuregate/testing"
 | 
				
			||||||
	"k8s.io/klog/v2"
 | 
						"k8s.io/klog/v2"
 | 
				
			||||||
	"k8s.io/utils/pointer"
 | 
						"k8s.io/utils/pointer"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
@@ -288,6 +291,11 @@ func (c *claimsTest) run(t *testing.T) {
 | 
				
			|||||||
		t.Fatalf("wanted initialization error %q but got none", c.wantInitErr)
 | 
							t.Fatalf("wanted initialization error %q but got none", c.wantInitErr)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						claims := struct{}{}
 | 
				
			||||||
 | 
						if err := json.Unmarshal([]byte(c.claims), &claims); err != nil {
 | 
				
			||||||
 | 
							t.Fatalf("failed to unmarshal claims: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Sign and serialize the claims in a JWT.
 | 
						// Sign and serialize the claims in a JWT.
 | 
				
			||||||
	jws, err := signer.Sign([]byte(c.claims))
 | 
						jws, err := signer.Sign([]byte(c.claims))
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
@@ -333,6 +341,8 @@ func (c *claimsTest) run(t *testing.T) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func TestToken(t *testing.T) {
 | 
					func TestToken(t *testing.T) {
 | 
				
			||||||
 | 
						defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.StructuredAuthenticationConfiguration, true)()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	synchronizeTokenIDVerifierForTest = true
 | 
						synchronizeTokenIDVerifierForTest = true
 | 
				
			||||||
	tests := []claimsTest{
 | 
						tests := []claimsTest{
 | 
				
			||||||
		{
 | 
							{
 | 
				
			||||||
@@ -1801,7 +1811,7 @@ func TestToken(t *testing.T) {
 | 
				
			|||||||
			pubKeys: []*jose.JSONWebKey{
 | 
								pubKeys: []*jose.JSONWebKey{
 | 
				
			||||||
				loadRSAKey(t, "testdata/rsa_1.pem", jose.RS256),
 | 
									loadRSAKey(t, "testdata/rsa_1.pem", jose.RS256),
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
			wantInitErr: `claimMappings.username.claim: Required value: claim name is required`,
 | 
								wantInitErr: `claimMappings.username: Required value: claim or expression is required`,
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		{
 | 
							{
 | 
				
			||||||
			name: "invalid-sig-alg",
 | 
								name: "invalid-sig-alg",
 | 
				
			||||||
@@ -1910,6 +1920,813 @@ func TestToken(t *testing.T) {
 | 
				
			|||||||
			}`, valid.Unix()),
 | 
								}`, valid.Unix()),
 | 
				
			||||||
			wantErr: `oidc: verify token: oidc: expected audience "my-client" got ["my-wrong-client"]`,
 | 
								wantErr: `oidc: verify token: oidc: expected audience "my-client" got ["my-wrong-client"]`,
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "user validation rule fails for user.username",
 | 
				
			||||||
 | 
								options: Options{
 | 
				
			||||||
 | 
									JWTAuthenticator: apiserver.JWTAuthenticator{
 | 
				
			||||||
 | 
										Issuer: apiserver.Issuer{
 | 
				
			||||||
 | 
											URL:       "https://auth.example.com",
 | 
				
			||||||
 | 
											Audiences: []string{"my-client"},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										ClaimMappings: apiserver.ClaimMappings{
 | 
				
			||||||
 | 
											Username: apiserver.PrefixedClaimOrExpression{
 | 
				
			||||||
 | 
												Claim:  "username",
 | 
				
			||||||
 | 
												Prefix: pointer.String("system:"),
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										UserValidationRules: []apiserver.UserValidationRule{
 | 
				
			||||||
 | 
											{
 | 
				
			||||||
 | 
												Expression: "!user.username.startsWith('system:')",
 | 
				
			||||||
 | 
												Message:    "username cannot used reserved system: prefix",
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									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": "my-client",
 | 
				
			||||||
 | 
									"username": "jane",
 | 
				
			||||||
 | 
									"exp": %d
 | 
				
			||||||
 | 
								}`, valid.Unix()),
 | 
				
			||||||
 | 
								wantErr: `oidc: error evaluating user info validation rule: validation expression '!user.username.startsWith('system:')' failed: username cannot used reserved system: prefix`,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "user validation rule fails for user.groups",
 | 
				
			||||||
 | 
								options: Options{
 | 
				
			||||||
 | 
									JWTAuthenticator: apiserver.JWTAuthenticator{
 | 
				
			||||||
 | 
										Issuer: apiserver.Issuer{
 | 
				
			||||||
 | 
											URL:       "https://auth.example.com",
 | 
				
			||||||
 | 
											Audiences: []string{"my-client"},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										ClaimMappings: apiserver.ClaimMappings{
 | 
				
			||||||
 | 
											Username: apiserver.PrefixedClaimOrExpression{
 | 
				
			||||||
 | 
												Expression: "claims.username",
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
											Groups: apiserver.PrefixedClaimOrExpression{
 | 
				
			||||||
 | 
												Claim:  "groups",
 | 
				
			||||||
 | 
												Prefix: pointer.String("system:"),
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										UserValidationRules: []apiserver.UserValidationRule{
 | 
				
			||||||
 | 
											{
 | 
				
			||||||
 | 
												Expression: "user.groups.all(group, !group.startsWith('system:'))",
 | 
				
			||||||
 | 
												Message:    "groups cannot used reserved system: prefix",
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									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": "my-client",
 | 
				
			||||||
 | 
									"username": "jane",
 | 
				
			||||||
 | 
									"exp": %d,
 | 
				
			||||||
 | 
									"groups": ["team1", "team2"]
 | 
				
			||||||
 | 
								}`, valid.Unix()),
 | 
				
			||||||
 | 
								wantErr: `oidc: error evaluating user info validation rule: validation expression 'user.groups.all(group, !group.startsWith('system:'))' failed: groups cannot used reserved system: prefix`,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "claim validation rule with expression fails",
 | 
				
			||||||
 | 
								options: Options{
 | 
				
			||||||
 | 
									JWTAuthenticator: apiserver.JWTAuthenticator{
 | 
				
			||||||
 | 
										Issuer: apiserver.Issuer{
 | 
				
			||||||
 | 
											URL:       "https://auth.example.com",
 | 
				
			||||||
 | 
											Audiences: []string{"my-client"},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										ClaimMappings: apiserver.ClaimMappings{
 | 
				
			||||||
 | 
											Username: apiserver.PrefixedClaimOrExpression{
 | 
				
			||||||
 | 
												Claim:  "username",
 | 
				
			||||||
 | 
												Prefix: pointer.String(""),
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										ClaimValidationRules: []apiserver.ClaimValidationRule{
 | 
				
			||||||
 | 
											{
 | 
				
			||||||
 | 
												Expression: `claims.hd == "example.com"`,
 | 
				
			||||||
 | 
												Message:    "hd claim must be example.com",
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									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": "my-client",
 | 
				
			||||||
 | 
									"username": "jane",
 | 
				
			||||||
 | 
									"exp": %d
 | 
				
			||||||
 | 
								}`, valid.Unix()),
 | 
				
			||||||
 | 
								wantErr: `oidc: error evaluating claim validation expression: expression 'claims.hd == "example.com"' resulted in error: no such key: hd`,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "claim validation rule with expression",
 | 
				
			||||||
 | 
								options: Options{
 | 
				
			||||||
 | 
									JWTAuthenticator: apiserver.JWTAuthenticator{
 | 
				
			||||||
 | 
										Issuer: apiserver.Issuer{
 | 
				
			||||||
 | 
											URL:       "https://auth.example.com",
 | 
				
			||||||
 | 
											Audiences: []string{"my-client"},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										ClaimMappings: apiserver.ClaimMappings{
 | 
				
			||||||
 | 
											Username: apiserver.PrefixedClaimOrExpression{
 | 
				
			||||||
 | 
												Claim:  "username",
 | 
				
			||||||
 | 
												Prefix: pointer.String(""),
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										ClaimValidationRules: []apiserver.ClaimValidationRule{
 | 
				
			||||||
 | 
											{
 | 
				
			||||||
 | 
												Expression: `claims.hd == "example.com"`,
 | 
				
			||||||
 | 
												Message:    "hd claim must be example.com",
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									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": "my-client",
 | 
				
			||||||
 | 
									"username": "jane",
 | 
				
			||||||
 | 
									"exp": %d,
 | 
				
			||||||
 | 
									"hd": "example.com"
 | 
				
			||||||
 | 
								}`, valid.Unix()),
 | 
				
			||||||
 | 
								want: &user.DefaultInfo{
 | 
				
			||||||
 | 
									Name: "jane",
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "claim validation rule with expression and nested claims",
 | 
				
			||||||
 | 
								options: Options{
 | 
				
			||||||
 | 
									JWTAuthenticator: apiserver.JWTAuthenticator{
 | 
				
			||||||
 | 
										Issuer: apiserver.Issuer{
 | 
				
			||||||
 | 
											URL:       "https://auth.example.com",
 | 
				
			||||||
 | 
											Audiences: []string{"my-client"},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										ClaimMappings: apiserver.ClaimMappings{
 | 
				
			||||||
 | 
											Username: apiserver.PrefixedClaimOrExpression{
 | 
				
			||||||
 | 
												Claim:  "username",
 | 
				
			||||||
 | 
												Prefix: pointer.String(""),
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										ClaimValidationRules: []apiserver.ClaimValidationRule{
 | 
				
			||||||
 | 
											{
 | 
				
			||||||
 | 
												Expression: `claims.foo.bar == "baz"`,
 | 
				
			||||||
 | 
												Message:    "foo.bar claim must be 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": "my-client",
 | 
				
			||||||
 | 
									"username": "jane",
 | 
				
			||||||
 | 
									"exp": %d,
 | 
				
			||||||
 | 
									"hd": "example.com",
 | 
				
			||||||
 | 
									"foo": {
 | 
				
			||||||
 | 
										"bar": "baz"
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}`, valid.Unix()),
 | 
				
			||||||
 | 
								want: &user.DefaultInfo{
 | 
				
			||||||
 | 
									Name: "jane",
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "claim validation rule with mix of expression and claim",
 | 
				
			||||||
 | 
								options: Options{
 | 
				
			||||||
 | 
									JWTAuthenticator: apiserver.JWTAuthenticator{
 | 
				
			||||||
 | 
										Issuer: apiserver.Issuer{
 | 
				
			||||||
 | 
											URL:       "https://auth.example.com",
 | 
				
			||||||
 | 
											Audiences: []string{"my-client"},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										ClaimMappings: apiserver.ClaimMappings{
 | 
				
			||||||
 | 
											Username: apiserver.PrefixedClaimOrExpression{
 | 
				
			||||||
 | 
												Claim:  "username",
 | 
				
			||||||
 | 
												Prefix: pointer.String(""),
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										ClaimValidationRules: []apiserver.ClaimValidationRule{
 | 
				
			||||||
 | 
											{
 | 
				
			||||||
 | 
												Expression: `claims.foo.bar == "baz"`,
 | 
				
			||||||
 | 
												Message:    "foo.bar claim must be baz",
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
											{
 | 
				
			||||||
 | 
												Claim:         "hd",
 | 
				
			||||||
 | 
												RequiredValue: "example.com",
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									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": "my-client",
 | 
				
			||||||
 | 
									"username": "jane",
 | 
				
			||||||
 | 
									"exp": %d,
 | 
				
			||||||
 | 
									"hd": "example.com",
 | 
				
			||||||
 | 
									"foo": {
 | 
				
			||||||
 | 
										"bar": "baz"
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}`, valid.Unix()),
 | 
				
			||||||
 | 
								want: &user.DefaultInfo{
 | 
				
			||||||
 | 
									Name: "jane",
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "username claim mapping with expression",
 | 
				
			||||||
 | 
								options: Options{
 | 
				
			||||||
 | 
									JWTAuthenticator: apiserver.JWTAuthenticator{
 | 
				
			||||||
 | 
										Issuer: apiserver.Issuer{
 | 
				
			||||||
 | 
											URL:       "https://auth.example.com",
 | 
				
			||||||
 | 
											Audiences: []string{"my-client"},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										ClaimMappings: apiserver.ClaimMappings{
 | 
				
			||||||
 | 
											Username: apiserver.PrefixedClaimOrExpression{
 | 
				
			||||||
 | 
												Expression: "claims.username",
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									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": "my-client",
 | 
				
			||||||
 | 
									"username": "jane",
 | 
				
			||||||
 | 
									"exp": %d
 | 
				
			||||||
 | 
								}`, valid.Unix()),
 | 
				
			||||||
 | 
								want: &user.DefaultInfo{
 | 
				
			||||||
 | 
									Name: "jane",
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "username claim mapping with expression and nested claim",
 | 
				
			||||||
 | 
								options: Options{
 | 
				
			||||||
 | 
									JWTAuthenticator: apiserver.JWTAuthenticator{
 | 
				
			||||||
 | 
										Issuer: apiserver.Issuer{
 | 
				
			||||||
 | 
											URL:       "https://auth.example.com",
 | 
				
			||||||
 | 
											Audiences: []string{"my-client"},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										ClaimMappings: apiserver.ClaimMappings{
 | 
				
			||||||
 | 
											Username: apiserver.PrefixedClaimOrExpression{
 | 
				
			||||||
 | 
												Expression: "claims.foo.username",
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									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": "my-client",
 | 
				
			||||||
 | 
									"username": "jane",
 | 
				
			||||||
 | 
									"exp": %d,
 | 
				
			||||||
 | 
									"foo": {
 | 
				
			||||||
 | 
										"username": "jane"
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}`, valid.Unix()),
 | 
				
			||||||
 | 
								want: &user.DefaultInfo{
 | 
				
			||||||
 | 
									Name: "jane",
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "groups claim mapping with expression",
 | 
				
			||||||
 | 
								options: Options{
 | 
				
			||||||
 | 
									JWTAuthenticator: apiserver.JWTAuthenticator{
 | 
				
			||||||
 | 
										Issuer: apiserver.Issuer{
 | 
				
			||||||
 | 
											URL:       "https://auth.example.com",
 | 
				
			||||||
 | 
											Audiences: []string{"my-client"},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										ClaimMappings: apiserver.ClaimMappings{
 | 
				
			||||||
 | 
											Username: apiserver.PrefixedClaimOrExpression{
 | 
				
			||||||
 | 
												Expression: "claims.username",
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
											Groups: apiserver.PrefixedClaimOrExpression{
 | 
				
			||||||
 | 
												Expression: "claims.groups",
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									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": "my-client",
 | 
				
			||||||
 | 
									"username": "jane",
 | 
				
			||||||
 | 
									"groups": ["team1", "team2"],
 | 
				
			||||||
 | 
									"exp": %d
 | 
				
			||||||
 | 
								}`, valid.Unix()),
 | 
				
			||||||
 | 
								want: &user.DefaultInfo{
 | 
				
			||||||
 | 
									Name:   "jane",
 | 
				
			||||||
 | 
									Groups: []string{"team1", "team2"},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "groups claim with expression",
 | 
				
			||||||
 | 
								options: Options{
 | 
				
			||||||
 | 
									JWTAuthenticator: apiserver.JWTAuthenticator{
 | 
				
			||||||
 | 
										Issuer: apiserver.Issuer{
 | 
				
			||||||
 | 
											URL:       "https://auth.example.com",
 | 
				
			||||||
 | 
											Audiences: []string{"my-client"},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										ClaimMappings: apiserver.ClaimMappings{
 | 
				
			||||||
 | 
											Username: apiserver.PrefixedClaimOrExpression{
 | 
				
			||||||
 | 
												Claim:  "username",
 | 
				
			||||||
 | 
												Prefix: pointer.String("oidc:"),
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
											Groups: apiserver.PrefixedClaimOrExpression{
 | 
				
			||||||
 | 
												Expression: `(claims.roles.split(",") + claims.other_roles.split(",")).map(role, "groups:" + role)`,
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									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": "my-client",
 | 
				
			||||||
 | 
									"username": "jane",
 | 
				
			||||||
 | 
									"roles": "foo,bar",
 | 
				
			||||||
 | 
									"other_roles": "baz,qux",
 | 
				
			||||||
 | 
									"exp": %d
 | 
				
			||||||
 | 
								}`, valid.Unix()),
 | 
				
			||||||
 | 
								want: &user.DefaultInfo{
 | 
				
			||||||
 | 
									Name:   "oidc:jane",
 | 
				
			||||||
 | 
									Groups: []string{"groups:foo", "groups:bar", "groups:baz", "groups:qux"},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "uid claim mapping with expression",
 | 
				
			||||||
 | 
								options: Options{
 | 
				
			||||||
 | 
									JWTAuthenticator: apiserver.JWTAuthenticator{
 | 
				
			||||||
 | 
										Issuer: apiserver.Issuer{
 | 
				
			||||||
 | 
											URL:       "https://auth.example.com",
 | 
				
			||||||
 | 
											Audiences: []string{"my-client"},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										ClaimMappings: apiserver.ClaimMappings{
 | 
				
			||||||
 | 
											Username: apiserver.PrefixedClaimOrExpression{
 | 
				
			||||||
 | 
												Expression: "claims.username",
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
											Groups: apiserver.PrefixedClaimOrExpression{
 | 
				
			||||||
 | 
												Expression: "claims.groups",
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
											UID: apiserver.ClaimOrExpression{
 | 
				
			||||||
 | 
												Expression: "claims.uid",
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									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": "my-client",
 | 
				
			||||||
 | 
									"username": "jane",
 | 
				
			||||||
 | 
									"groups": ["team1", "team2"],
 | 
				
			||||||
 | 
									"exp": %d,
 | 
				
			||||||
 | 
									"uid": "1234"
 | 
				
			||||||
 | 
								}`, valid.Unix()),
 | 
				
			||||||
 | 
								want: &user.DefaultInfo{
 | 
				
			||||||
 | 
									Name:   "jane",
 | 
				
			||||||
 | 
									Groups: []string{"team1", "team2"},
 | 
				
			||||||
 | 
									UID:    "1234",
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "uid claim mapping with claim",
 | 
				
			||||||
 | 
								options: Options{
 | 
				
			||||||
 | 
									JWTAuthenticator: apiserver.JWTAuthenticator{
 | 
				
			||||||
 | 
										Issuer: apiserver.Issuer{
 | 
				
			||||||
 | 
											URL:       "https://auth.example.com",
 | 
				
			||||||
 | 
											Audiences: []string{"my-client"},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										ClaimMappings: apiserver.ClaimMappings{
 | 
				
			||||||
 | 
											Username: apiserver.PrefixedClaimOrExpression{
 | 
				
			||||||
 | 
												Expression: "claims.username",
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
											Groups: apiserver.PrefixedClaimOrExpression{
 | 
				
			||||||
 | 
												Expression: "claims.groups",
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
											UID: apiserver.ClaimOrExpression{
 | 
				
			||||||
 | 
												Claim: "uid",
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									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": "my-client",
 | 
				
			||||||
 | 
									"username": "jane",
 | 
				
			||||||
 | 
									"groups": ["team1", "team2"],
 | 
				
			||||||
 | 
									"exp": %d,
 | 
				
			||||||
 | 
									"uid": "1234"
 | 
				
			||||||
 | 
								}`, valid.Unix()),
 | 
				
			||||||
 | 
								want: &user.DefaultInfo{
 | 
				
			||||||
 | 
									Name:   "jane",
 | 
				
			||||||
 | 
									Groups: []string{"team1", "team2"},
 | 
				
			||||||
 | 
									UID:    "1234",
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "extra claim mapping with expression",
 | 
				
			||||||
 | 
								options: Options{
 | 
				
			||||||
 | 
									JWTAuthenticator: apiserver.JWTAuthenticator{
 | 
				
			||||||
 | 
										Issuer: apiserver.Issuer{
 | 
				
			||||||
 | 
											URL:       "https://auth.example.com",
 | 
				
			||||||
 | 
											Audiences: []string{"my-client"},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										ClaimMappings: apiserver.ClaimMappings{
 | 
				
			||||||
 | 
											Username: apiserver.PrefixedClaimOrExpression{
 | 
				
			||||||
 | 
												Expression: "claims.username",
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
											Groups: apiserver.PrefixedClaimOrExpression{
 | 
				
			||||||
 | 
												Expression: "claims.groups",
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
											UID: apiserver.ClaimOrExpression{
 | 
				
			||||||
 | 
												Expression: "claims.uid",
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
											Extra: []apiserver.ExtraMapping{
 | 
				
			||||||
 | 
												{
 | 
				
			||||||
 | 
													Key:             "example.org/foo",
 | 
				
			||||||
 | 
													ValueExpression: "claims.foo",
 | 
				
			||||||
 | 
												},
 | 
				
			||||||
 | 
												{
 | 
				
			||||||
 | 
													Key:             "example.org/bar",
 | 
				
			||||||
 | 
													ValueExpression: "claims.bar",
 | 
				
			||||||
 | 
												},
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									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": "my-client",
 | 
				
			||||||
 | 
									"username": "jane",
 | 
				
			||||||
 | 
									"groups": ["team1", "team2"],
 | 
				
			||||||
 | 
									"exp": %d,
 | 
				
			||||||
 | 
									"uid": "1234",
 | 
				
			||||||
 | 
									"foo": "bar",
 | 
				
			||||||
 | 
									"bar": [
 | 
				
			||||||
 | 
										"baz",
 | 
				
			||||||
 | 
										"qux"
 | 
				
			||||||
 | 
									]
 | 
				
			||||||
 | 
								}`, valid.Unix()),
 | 
				
			||||||
 | 
								want: &user.DefaultInfo{
 | 
				
			||||||
 | 
									Name:   "jane",
 | 
				
			||||||
 | 
									Groups: []string{"team1", "team2"},
 | 
				
			||||||
 | 
									UID:    "1234",
 | 
				
			||||||
 | 
									Extra: map[string][]string{
 | 
				
			||||||
 | 
										"example.org/foo": {"bar"},
 | 
				
			||||||
 | 
										"example.org/bar": {"baz", "qux"},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "extra claim mapping, value derived from claim value",
 | 
				
			||||||
 | 
								options: Options{
 | 
				
			||||||
 | 
									JWTAuthenticator: apiserver.JWTAuthenticator{
 | 
				
			||||||
 | 
										Issuer: apiserver.Issuer{
 | 
				
			||||||
 | 
											URL:       "https://auth.example.com",
 | 
				
			||||||
 | 
											Audiences: []string{"my-client"},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										ClaimMappings: apiserver.ClaimMappings{
 | 
				
			||||||
 | 
											Username: apiserver.PrefixedClaimOrExpression{
 | 
				
			||||||
 | 
												Expression: "claims.username",
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
											Extra: []apiserver.ExtraMapping{
 | 
				
			||||||
 | 
												{
 | 
				
			||||||
 | 
													Key:             "example.org/admin",
 | 
				
			||||||
 | 
													ValueExpression: `(has(claims.is_admin) && claims.is_admin) ? "true":""`,
 | 
				
			||||||
 | 
												},
 | 
				
			||||||
 | 
												{
 | 
				
			||||||
 | 
													Key:             "example.org/admin_1",
 | 
				
			||||||
 | 
													ValueExpression: `claims.?is_admin.orValue(false) == true ? "true":""`,
 | 
				
			||||||
 | 
												},
 | 
				
			||||||
 | 
												{
 | 
				
			||||||
 | 
													Key:             "example.org/non_existent",
 | 
				
			||||||
 | 
													ValueExpression: `claims.?non_existent.orValue("default") == "default" ? "true":""`,
 | 
				
			||||||
 | 
												},
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									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": "my-client",
 | 
				
			||||||
 | 
									"username": "jane",
 | 
				
			||||||
 | 
									"exp": %d,
 | 
				
			||||||
 | 
									"is_admin": true
 | 
				
			||||||
 | 
								}`, valid.Unix()),
 | 
				
			||||||
 | 
								want: &user.DefaultInfo{
 | 
				
			||||||
 | 
									Name: "jane",
 | 
				
			||||||
 | 
									Extra: map[string][]string{
 | 
				
			||||||
 | 
										"example.org/admin":        {"true"},
 | 
				
			||||||
 | 
										"example.org/admin_1":      {"true"},
 | 
				
			||||||
 | 
										"example.org/non_existent": {"true"},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "hardcoded extra claim mapping",
 | 
				
			||||||
 | 
								options: Options{
 | 
				
			||||||
 | 
									JWTAuthenticator: apiserver.JWTAuthenticator{
 | 
				
			||||||
 | 
										Issuer: apiserver.Issuer{
 | 
				
			||||||
 | 
											URL:       "https://auth.example.com",
 | 
				
			||||||
 | 
											Audiences: []string{"my-client"},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										ClaimMappings: apiserver.ClaimMappings{
 | 
				
			||||||
 | 
											Username: apiserver.PrefixedClaimOrExpression{
 | 
				
			||||||
 | 
												Expression: "claims.username",
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
											Extra: []apiserver.ExtraMapping{
 | 
				
			||||||
 | 
												{
 | 
				
			||||||
 | 
													Key:             "example.org/admin",
 | 
				
			||||||
 | 
													ValueExpression: `"true"`,
 | 
				
			||||||
 | 
												},
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									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": "my-client",
 | 
				
			||||||
 | 
									"username": "jane",
 | 
				
			||||||
 | 
									"exp": %d,
 | 
				
			||||||
 | 
									"is_admin": true
 | 
				
			||||||
 | 
								}`, valid.Unix()),
 | 
				
			||||||
 | 
								want: &user.DefaultInfo{
 | 
				
			||||||
 | 
									Name: "jane",
 | 
				
			||||||
 | 
									Extra: map[string][]string{
 | 
				
			||||||
 | 
										"example.org/admin": {"true"},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "extra claim mapping, multiple expressions for same key",
 | 
				
			||||||
 | 
								options: Options{
 | 
				
			||||||
 | 
									JWTAuthenticator: apiserver.JWTAuthenticator{
 | 
				
			||||||
 | 
										Issuer: apiserver.Issuer{
 | 
				
			||||||
 | 
											URL:       "https://auth.example.com",
 | 
				
			||||||
 | 
											Audiences: []string{"my-client"},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										ClaimMappings: apiserver.ClaimMappings{
 | 
				
			||||||
 | 
											Username: apiserver.PrefixedClaimOrExpression{
 | 
				
			||||||
 | 
												Expression: "claims.username",
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
											Groups: apiserver.PrefixedClaimOrExpression{
 | 
				
			||||||
 | 
												Expression: "claims.groups",
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
											UID: apiserver.ClaimOrExpression{
 | 
				
			||||||
 | 
												Expression: "claims.uid",
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
											Extra: []apiserver.ExtraMapping{
 | 
				
			||||||
 | 
												{
 | 
				
			||||||
 | 
													Key:             "example.org/foo",
 | 
				
			||||||
 | 
													ValueExpression: "claims.foo",
 | 
				
			||||||
 | 
												},
 | 
				
			||||||
 | 
												{
 | 
				
			||||||
 | 
													Key:             "example.org/bar",
 | 
				
			||||||
 | 
													ValueExpression: "claims.bar",
 | 
				
			||||||
 | 
												},
 | 
				
			||||||
 | 
												{
 | 
				
			||||||
 | 
													Key:             "example.org/foo",
 | 
				
			||||||
 | 
													ValueExpression: "claims.bar",
 | 
				
			||||||
 | 
												},
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									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": "my-client",
 | 
				
			||||||
 | 
									"username": "jane",
 | 
				
			||||||
 | 
									"groups": ["team1", "team2"],
 | 
				
			||||||
 | 
									"exp": %d,
 | 
				
			||||||
 | 
									"uid": "1234",
 | 
				
			||||||
 | 
									"foo": "bar",
 | 
				
			||||||
 | 
									"bar": [
 | 
				
			||||||
 | 
										"baz",
 | 
				
			||||||
 | 
										"qux"
 | 
				
			||||||
 | 
									]
 | 
				
			||||||
 | 
								}`, valid.Unix()),
 | 
				
			||||||
 | 
								wantInitErr: `claimMappings.extra[2].key: Duplicate value: "example.org/foo"`,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "extra claim mapping, empty string value for key",
 | 
				
			||||||
 | 
								options: Options{
 | 
				
			||||||
 | 
									JWTAuthenticator: apiserver.JWTAuthenticator{
 | 
				
			||||||
 | 
										Issuer: apiserver.Issuer{
 | 
				
			||||||
 | 
											URL:       "https://auth.example.com",
 | 
				
			||||||
 | 
											Audiences: []string{"my-client"},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										ClaimMappings: apiserver.ClaimMappings{
 | 
				
			||||||
 | 
											Username: apiserver.PrefixedClaimOrExpression{
 | 
				
			||||||
 | 
												Expression: "claims.username",
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
											Groups: apiserver.PrefixedClaimOrExpression{
 | 
				
			||||||
 | 
												Expression: "claims.groups",
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
											UID: apiserver.ClaimOrExpression{
 | 
				
			||||||
 | 
												Expression: "claims.uid",
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
											Extra: []apiserver.ExtraMapping{
 | 
				
			||||||
 | 
												{
 | 
				
			||||||
 | 
													Key:             "example.org/foo",
 | 
				
			||||||
 | 
													ValueExpression: "claims.foo",
 | 
				
			||||||
 | 
												},
 | 
				
			||||||
 | 
												{
 | 
				
			||||||
 | 
													Key:             "example.org/bar",
 | 
				
			||||||
 | 
													ValueExpression: "claims.bar",
 | 
				
			||||||
 | 
												},
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									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": "my-client",
 | 
				
			||||||
 | 
									"username": "jane",
 | 
				
			||||||
 | 
									"groups": ["team1", "team2"],
 | 
				
			||||||
 | 
									"exp": %d,
 | 
				
			||||||
 | 
									"uid": "1234",
 | 
				
			||||||
 | 
									"foo": "",
 | 
				
			||||||
 | 
									"bar": [
 | 
				
			||||||
 | 
										"baz",
 | 
				
			||||||
 | 
										"qux"
 | 
				
			||||||
 | 
									]
 | 
				
			||||||
 | 
								}`, valid.Unix()),
 | 
				
			||||||
 | 
								want: &user.DefaultInfo{
 | 
				
			||||||
 | 
									Name:   "jane",
 | 
				
			||||||
 | 
									Groups: []string{"team1", "team2"},
 | 
				
			||||||
 | 
									UID:    "1234",
 | 
				
			||||||
 | 
									Extra: map[string][]string{
 | 
				
			||||||
 | 
										"example.org/bar": {"baz", "qux"},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "extra claim mapping with user validation rule succeeds",
 | 
				
			||||||
 | 
								options: Options{
 | 
				
			||||||
 | 
									JWTAuthenticator: apiserver.JWTAuthenticator{
 | 
				
			||||||
 | 
										Issuer: apiserver.Issuer{
 | 
				
			||||||
 | 
											URL:       "https://auth.example.com",
 | 
				
			||||||
 | 
											Audiences: []string{"my-client"},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										ClaimMappings: apiserver.ClaimMappings{
 | 
				
			||||||
 | 
											Username: apiserver.PrefixedClaimOrExpression{
 | 
				
			||||||
 | 
												Expression: "claims.username",
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
											Groups: apiserver.PrefixedClaimOrExpression{
 | 
				
			||||||
 | 
												Expression: "claims.groups",
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
											UID: apiserver.ClaimOrExpression{
 | 
				
			||||||
 | 
												Expression: "claims.uid",
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
											Extra: []apiserver.ExtraMapping{
 | 
				
			||||||
 | 
												{
 | 
				
			||||||
 | 
													Key:             "example.org/foo",
 | 
				
			||||||
 | 
													ValueExpression: "'bar'",
 | 
				
			||||||
 | 
												},
 | 
				
			||||||
 | 
												{
 | 
				
			||||||
 | 
													Key:             "example.org/baz",
 | 
				
			||||||
 | 
													ValueExpression: "claims.baz",
 | 
				
			||||||
 | 
												},
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										UserValidationRules: []apiserver.UserValidationRule{
 | 
				
			||||||
 | 
											{
 | 
				
			||||||
 | 
												Expression: "'bar' in user.extra['example.org/foo'] && 'qux' in user.extra['example.org/baz']",
 | 
				
			||||||
 | 
												Message:    "example.org/foo must be bar and example.org/baz must be qux",
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									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": "my-client",
 | 
				
			||||||
 | 
									"username": "jane",
 | 
				
			||||||
 | 
									"groups": ["team1", "team2"],
 | 
				
			||||||
 | 
									"exp": %d,
 | 
				
			||||||
 | 
									"uid": "1234",
 | 
				
			||||||
 | 
									"baz": "qux"
 | 
				
			||||||
 | 
								}`, valid.Unix()),
 | 
				
			||||||
 | 
								want: &user.DefaultInfo{
 | 
				
			||||||
 | 
									Name:   "jane",
 | 
				
			||||||
 | 
									Groups: []string{"team1", "team2"},
 | 
				
			||||||
 | 
									UID:    "1234",
 | 
				
			||||||
 | 
									Extra: map[string][]string{
 | 
				
			||||||
 | 
										"example.org/foo": {"bar"},
 | 
				
			||||||
 | 
										"example.org/baz": {"qux"},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "groups expression returns null",
 | 
				
			||||||
 | 
								options: Options{
 | 
				
			||||||
 | 
									JWTAuthenticator: apiserver.JWTAuthenticator{
 | 
				
			||||||
 | 
										Issuer: apiserver.Issuer{
 | 
				
			||||||
 | 
											URL:       "https://auth.example.com",
 | 
				
			||||||
 | 
											Audiences: []string{"my-client"},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										ClaimMappings: apiserver.ClaimMappings{
 | 
				
			||||||
 | 
											Username: apiserver.PrefixedClaimOrExpression{
 | 
				
			||||||
 | 
												Expression: "claims.username",
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
											Groups: apiserver.PrefixedClaimOrExpression{
 | 
				
			||||||
 | 
												Expression: "claims.groups",
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									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": "my-client",
 | 
				
			||||||
 | 
									"username": "jane",
 | 
				
			||||||
 | 
									"groups": null,
 | 
				
			||||||
 | 
									"exp": %d,
 | 
				
			||||||
 | 
									"uid": "1234",
 | 
				
			||||||
 | 
									"baz": "qux"
 | 
				
			||||||
 | 
								}`, valid.Unix()),
 | 
				
			||||||
 | 
								want: &user.DefaultInfo{
 | 
				
			||||||
 | 
									Name: "jane",
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	for _, test := range tests {
 | 
						for _, test := range tests {
 | 
				
			||||||
		t.Run(test.name, test.run)
 | 
							t.Run(test.name, test.run)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -36,6 +36,7 @@ import (
 | 
				
			|||||||
	"github.com/stretchr/testify/assert"
 | 
						"github.com/stretchr/testify/assert"
 | 
				
			||||||
	"github.com/stretchr/testify/require"
 | 
						"github.com/stretchr/testify/require"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						authenticationv1 "k8s.io/api/authentication/v1"
 | 
				
			||||||
	rbacv1 "k8s.io/api/rbac/v1"
 | 
						rbacv1 "k8s.io/api/rbac/v1"
 | 
				
			||||||
	apierrors "k8s.io/apimachinery/pkg/api/errors"
 | 
						apierrors "k8s.io/apimachinery/pkg/api/errors"
 | 
				
			||||||
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | 
						metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | 
				
			||||||
@@ -99,6 +100,9 @@ var (
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// authenticationConfigFunc is a function that returns a string representation of an authentication config.
 | 
				
			||||||
 | 
					type authenticationConfigFunc func(t *testing.T, issuerURL, caCert string) string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func TestOIDC(t *testing.T) {
 | 
					func TestOIDC(t *testing.T) {
 | 
				
			||||||
	t.Log("Testing OIDC authenticator with --oidc-* flags")
 | 
						t.Log("Testing OIDC authenticator with --oidc-* flags")
 | 
				
			||||||
	runTests(t, false)
 | 
						runTests(t, false)
 | 
				
			||||||
@@ -114,7 +118,7 @@ func TestStructuredAuthenticationConfig(t *testing.T) {
 | 
				
			|||||||
func runTests(t *testing.T, useAuthenticationConfig bool) {
 | 
					func runTests(t *testing.T, useAuthenticationConfig bool) {
 | 
				
			||||||
	var tests = []struct {
 | 
						var tests = []struct {
 | 
				
			||||||
		name                    string
 | 
							name                    string
 | 
				
			||||||
		configureInfrastructure func(t *testing.T, useAuthenticationConfig bool) (
 | 
							configureInfrastructure func(t *testing.T, fn authenticationConfigFunc) (
 | 
				
			||||||
			oidcServer *utilsoidc.TestServer,
 | 
								oidcServer *utilsoidc.TestServer,
 | 
				
			||||||
			apiServer *kubeapiserverapptesting.TestServer,
 | 
								apiServer *kubeapiserverapptesting.TestServer,
 | 
				
			||||||
			signingPrivateKey *rsa.PrivateKey,
 | 
								signingPrivateKey *rsa.PrivateKey,
 | 
				
			||||||
@@ -129,27 +133,29 @@ func runTests(t *testing.T, useAuthenticationConfig bool) {
 | 
				
			|||||||
			certPath,
 | 
								certPath,
 | 
				
			||||||
			oidcServerURL,
 | 
								oidcServerURL,
 | 
				
			||||||
			oidcServerTokenURL string,
 | 
								oidcServerTokenURL string,
 | 
				
			||||||
		) *kubernetes.Clientset
 | 
							) kubernetes.Interface
 | 
				
			||||||
		asserErrFn func(t *testing.T, errorToCheck error)
 | 
							assertErrFn func(t *testing.T, errorToCheck error)
 | 
				
			||||||
	}{
 | 
						}{
 | 
				
			||||||
		{
 | 
							{
 | 
				
			||||||
			name:                    "ID token is ok",
 | 
								name:                    "ID token is ok",
 | 
				
			||||||
			configureInfrastructure: configureTestInfrastructure,
 | 
								configureInfrastructure: configureTestInfrastructure,
 | 
				
			||||||
			configureOIDCServerBehaviour: func(t *testing.T, oidcServer *utilsoidc.TestServer, signingPrivateKey *rsa.PrivateKey) {
 | 
								configureOIDCServerBehaviour: func(t *testing.T, oidcServer *utilsoidc.TestServer, signingPrivateKey *rsa.PrivateKey) {
 | 
				
			||||||
				idTokenLifetime := time.Second * 1200
 | 
									idTokenLifetime := time.Second * 1200
 | 
				
			||||||
				oidcServer.TokenHandler().EXPECT().Token().Times(1).DoAndReturn(utilsoidc.TokenHandlerBehaviourReturningPredefinedJWT(
 | 
									oidcServer.TokenHandler().EXPECT().Token().Times(1).DoAndReturn(utilsoidc.TokenHandlerBehaviorReturningPredefinedJWT(
 | 
				
			||||||
					t,
 | 
										t,
 | 
				
			||||||
					signingPrivateKey,
 | 
										signingPrivateKey,
 | 
				
			||||||
					oidcServer.URL(),
 | 
										map[string]interface{}{
 | 
				
			||||||
					defaultOIDCClientID,
 | 
											"iss": oidcServer.URL(),
 | 
				
			||||||
					defaultOIDCClaimedUsername,
 | 
											"sub": defaultOIDCClaimedUsername,
 | 
				
			||||||
 | 
											"aud": defaultOIDCClientID,
 | 
				
			||||||
 | 
											"exp": time.Now().Add(idTokenLifetime).Unix(),
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
					defaultStubAccessToken,
 | 
										defaultStubAccessToken,
 | 
				
			||||||
					defaultStubRefreshToken,
 | 
										defaultStubRefreshToken,
 | 
				
			||||||
					time.Now().Add(idTokenLifetime).Unix(),
 | 
					 | 
				
			||||||
				))
 | 
									))
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
			configureClient: configureClientFetchingOIDCCredentials,
 | 
								configureClient: configureClientFetchingOIDCCredentials,
 | 
				
			||||||
			asserErrFn: func(t *testing.T, errorToCheck error) {
 | 
								assertErrFn: func(t *testing.T, errorToCheck error) {
 | 
				
			||||||
				assert.NoError(t, errorToCheck)
 | 
									assert.NoError(t, errorToCheck)
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
@@ -160,7 +166,7 @@ func runTests(t *testing.T, useAuthenticationConfig bool) {
 | 
				
			|||||||
				configureOIDCServerToReturnExpiredIDToken(t, 2, oidcServer, signingPrivateKey)
 | 
									configureOIDCServerToReturnExpiredIDToken(t, 2, oidcServer, signingPrivateKey)
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
			configureClient: configureClientFetchingOIDCCredentials,
 | 
								configureClient: configureClientFetchingOIDCCredentials,
 | 
				
			||||||
			asserErrFn: func(t *testing.T, errorToCheck error) {
 | 
								assertErrFn: func(t *testing.T, errorToCheck error) {
 | 
				
			||||||
				assert.True(t, apierrors.IsUnauthorized(errorToCheck), errorToCheck)
 | 
									assert.True(t, apierrors.IsUnauthorized(errorToCheck), errorToCheck)
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
@@ -171,7 +177,7 @@ func runTests(t *testing.T, useAuthenticationConfig bool) {
 | 
				
			|||||||
				oidcServer.TokenHandler().EXPECT().Token().Times(2).Return(utilsoidc.Token{}, utilsoidc.ErrBadClientID)
 | 
									oidcServer.TokenHandler().EXPECT().Token().Times(2).Return(utilsoidc.Token{}, utilsoidc.ErrBadClientID)
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
			configureClient: configureClientWithEmptyIDToken,
 | 
								configureClient: configureClientWithEmptyIDToken,
 | 
				
			||||||
			asserErrFn: func(t *testing.T, errorToCheck error) {
 | 
								assertErrFn: func(t *testing.T, errorToCheck error) {
 | 
				
			||||||
				urlError, ok := errorToCheck.(*url.Error)
 | 
									urlError, ok := errorToCheck.(*url.Error)
 | 
				
			||||||
				require.True(t, ok)
 | 
									require.True(t, ok)
 | 
				
			||||||
				assert.Equal(
 | 
									assert.Equal(
 | 
				
			||||||
@@ -185,7 +191,7 @@ func runTests(t *testing.T, useAuthenticationConfig bool) {
 | 
				
			|||||||
			name:                         "client has wrong CA",
 | 
								name:                         "client has wrong CA",
 | 
				
			||||||
			configureInfrastructure:      configureTestInfrastructure,
 | 
								configureInfrastructure:      configureTestInfrastructure,
 | 
				
			||||||
			configureOIDCServerBehaviour: func(t *testing.T, oidcServer *utilsoidc.TestServer, _ *rsa.PrivateKey) {},
 | 
								configureOIDCServerBehaviour: func(t *testing.T, oidcServer *utilsoidc.TestServer, _ *rsa.PrivateKey) {},
 | 
				
			||||||
			configureClient: func(t *testing.T, restCfg *rest.Config, caCert []byte, _, oidcServerURL, oidcServerTokenURL string) *kubernetes.Clientset {
 | 
								configureClient: func(t *testing.T, restCfg *rest.Config, caCert []byte, _, oidcServerURL, oidcServerTokenURL string) kubernetes.Interface {
 | 
				
			||||||
				tempDir := t.TempDir()
 | 
									tempDir := t.TempDir()
 | 
				
			||||||
				certFilePath := filepath.Join(tempDir, "localhost_127.0.0.1_.crt")
 | 
									certFilePath := filepath.Join(tempDir, "localhost_127.0.0.1_.crt")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -194,7 +200,7 @@ func runTests(t *testing.T, useAuthenticationConfig bool) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
				return configureClientWithEmptyIDToken(t, restCfg, caCert, certFilePath, oidcServerURL, oidcServerTokenURL)
 | 
									return configureClientWithEmptyIDToken(t, restCfg, caCert, certFilePath, oidcServerURL, oidcServerTokenURL)
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
			asserErrFn: func(t *testing.T, errorToCheck error) {
 | 
								assertErrFn: func(t *testing.T, errorToCheck error) {
 | 
				
			||||||
				expectedErr := new(x509.UnknownAuthorityError)
 | 
									expectedErr := new(x509.UnknownAuthorityError)
 | 
				
			||||||
				assert.ErrorAs(t, errorToCheck, expectedErr)
 | 
									assert.ErrorAs(t, errorToCheck, expectedErr)
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
@@ -212,7 +218,7 @@ func runTests(t *testing.T, useAuthenticationConfig bool) {
 | 
				
			|||||||
				}, nil)
 | 
									}, nil)
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
			configureClient: configureClientFetchingOIDCCredentials,
 | 
								configureClient: configureClientFetchingOIDCCredentials,
 | 
				
			||||||
			asserErrFn: func(t *testing.T, errorToCheck error) {
 | 
								assertErrFn: func(t *testing.T, errorToCheck error) {
 | 
				
			||||||
				expectedError := new(apierrors.StatusError)
 | 
									expectedError := new(apierrors.StatusError)
 | 
				
			||||||
				assert.ErrorAs(t, errorToCheck, &expectedError)
 | 
									assert.ErrorAs(t, errorToCheck, &expectedError)
 | 
				
			||||||
				assert.Equal(
 | 
									assert.Equal(
 | 
				
			||||||
@@ -224,7 +230,7 @@ func runTests(t *testing.T, useAuthenticationConfig bool) {
 | 
				
			|||||||
		},
 | 
							},
 | 
				
			||||||
		{
 | 
							{
 | 
				
			||||||
			name: "ID token signature can not be verified due to wrong JWKs",
 | 
								name: "ID token signature can not be verified due to wrong JWKs",
 | 
				
			||||||
			configureInfrastructure: func(t *testing.T, useAuthenticationConfig bool) (
 | 
								configureInfrastructure: func(t *testing.T, fn authenticationConfigFunc) (
 | 
				
			||||||
				oidcServer *utilsoidc.TestServer,
 | 
									oidcServer *utilsoidc.TestServer,
 | 
				
			||||||
				apiServer *kubeapiserverapptesting.TestServer,
 | 
									apiServer *kubeapiserverapptesting.TestServer,
 | 
				
			||||||
				signingPrivateKey *rsa.PrivateKey,
 | 
									signingPrivateKey *rsa.PrivateKey,
 | 
				
			||||||
@@ -239,7 +245,21 @@ func runTests(t *testing.T, useAuthenticationConfig bool) {
 | 
				
			|||||||
				oidcServer = utilsoidc.BuildAndRunTestServer(t, caFilePath, caKeyFilePath)
 | 
									oidcServer = utilsoidc.BuildAndRunTestServer(t, caFilePath, caKeyFilePath)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				if useAuthenticationConfig {
 | 
									if useAuthenticationConfig {
 | 
				
			||||||
					authenticationConfig := generateAuthenticationConfig(t, oidcServer.URL(), defaultOIDCClientID, string(caCertContent), defaultOIDCUsernamePrefix)
 | 
										authenticationConfig := fmt.Sprintf(`
 | 
				
			||||||
 | 
					apiVersion: apiserver.config.k8s.io/v1alpha1
 | 
				
			||||||
 | 
					kind: AuthenticationConfiguration
 | 
				
			||||||
 | 
					jwt:
 | 
				
			||||||
 | 
					- issuer:
 | 
				
			||||||
 | 
					    url: %s
 | 
				
			||||||
 | 
					    audiences:
 | 
				
			||||||
 | 
					    - %s
 | 
				
			||||||
 | 
					    certificateAuthority: |
 | 
				
			||||||
 | 
					        %s
 | 
				
			||||||
 | 
					  claimMappings:
 | 
				
			||||||
 | 
					    username:
 | 
				
			||||||
 | 
					      claim: sub
 | 
				
			||||||
 | 
					      prefix: %s
 | 
				
			||||||
 | 
					`, oidcServer.URL(), defaultOIDCClientID, indentCertificateAuthority(string(caCertContent)), defaultOIDCUsernamePrefix)
 | 
				
			||||||
					apiServer = startTestAPIServerForOIDC(t, "", "", "", authenticationConfig)
 | 
										apiServer = startTestAPIServerForOIDC(t, "", "", "", authenticationConfig)
 | 
				
			||||||
				} else {
 | 
									} else {
 | 
				
			||||||
					apiServer = startTestAPIServerForOIDC(t, oidcServer.URL(), defaultOIDCClientID, caFilePath, "")
 | 
										apiServer = startTestAPIServerForOIDC(t, oidcServer.URL(), defaultOIDCClientID, caFilePath, "")
 | 
				
			||||||
@@ -250,24 +270,26 @@ func runTests(t *testing.T, useAuthenticationConfig bool) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
				anotherSigningPrivateKey, wantErr := rsa.GenerateKey(rand.Reader, rsaKeyBitSize)
 | 
									anotherSigningPrivateKey, wantErr := rsa.GenerateKey(rand.Reader, rsaKeyBitSize)
 | 
				
			||||||
				require.NoError(t, wantErr)
 | 
									require.NoError(t, wantErr)
 | 
				
			||||||
				oidcServer.JwksHandler().EXPECT().KeySet().AnyTimes().DoAndReturn(utilsoidc.DefaultJwksHandlerBehaviour(t, &anotherSigningPrivateKey.PublicKey))
 | 
									oidcServer.JwksHandler().EXPECT().KeySet().AnyTimes().DoAndReturn(utilsoidc.DefaultJwksHandlerBehavior(t, &anotherSigningPrivateKey.PublicKey))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				return oidcServer, apiServer, signingPrivateKey, caCertContent, caFilePath
 | 
									return oidcServer, apiServer, signingPrivateKey, caCertContent, caFilePath
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
			configureOIDCServerBehaviour: func(t *testing.T, oidcServer *utilsoidc.TestServer, signingPrivateKey *rsa.PrivateKey) {
 | 
								configureOIDCServerBehaviour: func(t *testing.T, oidcServer *utilsoidc.TestServer, signingPrivateKey *rsa.PrivateKey) {
 | 
				
			||||||
				oidcServer.TokenHandler().EXPECT().Token().Times(1).DoAndReturn(utilsoidc.TokenHandlerBehaviourReturningPredefinedJWT(
 | 
									oidcServer.TokenHandler().EXPECT().Token().Times(1).DoAndReturn(utilsoidc.TokenHandlerBehaviorReturningPredefinedJWT(
 | 
				
			||||||
					t,
 | 
										t,
 | 
				
			||||||
					signingPrivateKey,
 | 
										signingPrivateKey,
 | 
				
			||||||
					oidcServer.URL(),
 | 
										map[string]interface{}{
 | 
				
			||||||
					defaultOIDCClientID,
 | 
											"iss": oidcServer.URL(),
 | 
				
			||||||
					defaultOIDCClaimedUsername,
 | 
											"sub": defaultOIDCClaimedUsername,
 | 
				
			||||||
 | 
											"aud": defaultOIDCClientID,
 | 
				
			||||||
 | 
											"exp": time.Now().Add(time.Second * 1200).Unix(),
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
					defaultStubAccessToken,
 | 
										defaultStubAccessToken,
 | 
				
			||||||
					defaultStubRefreshToken,
 | 
										defaultStubRefreshToken,
 | 
				
			||||||
					time.Now().Add(time.Second*1200).Unix(),
 | 
					 | 
				
			||||||
				))
 | 
									))
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
			configureClient: configureClientFetchingOIDCCredentials,
 | 
								configureClient: configureClientFetchingOIDCCredentials,
 | 
				
			||||||
			asserErrFn: func(t *testing.T, errorToCheck error) {
 | 
								assertErrFn: func(t *testing.T, errorToCheck error) {
 | 
				
			||||||
				assert.True(t, apierrors.IsUnauthorized(errorToCheck), errorToCheck)
 | 
									assert.True(t, apierrors.IsUnauthorized(errorToCheck), errorToCheck)
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
@@ -275,7 +297,27 @@ func runTests(t *testing.T, useAuthenticationConfig bool) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	for _, tt := range tests {
 | 
						for _, tt := range tests {
 | 
				
			||||||
		t.Run(tt.name, func(t *testing.T) {
 | 
							t.Run(tt.name, func(t *testing.T) {
 | 
				
			||||||
			oidcServer, apiServer, signingPrivateKey, caCert, certPath := tt.configureInfrastructure(t, useAuthenticationConfig)
 | 
								fn := func(t *testing.T, issuerURL, caCert string) string { return "" }
 | 
				
			||||||
 | 
								if useAuthenticationConfig {
 | 
				
			||||||
 | 
									fn = func(t *testing.T, issuerURL, caCert string) string {
 | 
				
			||||||
 | 
										return fmt.Sprintf(`
 | 
				
			||||||
 | 
					apiVersion: apiserver.config.k8s.io/v1alpha1
 | 
				
			||||||
 | 
					kind: AuthenticationConfiguration
 | 
				
			||||||
 | 
					jwt:
 | 
				
			||||||
 | 
					- issuer:
 | 
				
			||||||
 | 
					    url: %s
 | 
				
			||||||
 | 
					    audiences:
 | 
				
			||||||
 | 
					    - %s
 | 
				
			||||||
 | 
					    certificateAuthority: |
 | 
				
			||||||
 | 
					        %s
 | 
				
			||||||
 | 
					  claimMappings:
 | 
				
			||||||
 | 
					    username:
 | 
				
			||||||
 | 
					      claim: sub
 | 
				
			||||||
 | 
					      prefix: %s
 | 
				
			||||||
 | 
					`, issuerURL, defaultOIDCClientID, indentCertificateAuthority(caCert), defaultOIDCUsernamePrefix)
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								oidcServer, apiServer, signingPrivateKey, caCert, certPath := tt.configureInfrastructure(t, fn)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			tt.configureOIDCServerBehaviour(t, oidcServer, signingPrivateKey)
 | 
								tt.configureOIDCServerBehaviour(t, oidcServer, signingPrivateKey)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -284,12 +326,10 @@ func runTests(t *testing.T, useAuthenticationConfig bool) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
			client := tt.configureClient(t, apiServer.ClientConfig, caCert, certPath, oidcServer.URL(), tokenURL)
 | 
								client := tt.configureClient(t, apiServer.ClientConfig, caCert, certPath, oidcServer.URL(), tokenURL)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
 | 
								ctx := testContext(t)
 | 
				
			||||||
			defer cancel()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			_, err = client.CoreV1().Pods(defaultNamespace).List(ctx, metav1.ListOptions{})
 | 
								_, err = client.CoreV1().Pods(defaultNamespace).List(ctx, metav1.ListOptions{})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			tt.asserErrFn(t, err)
 | 
								tt.assertErrFn(t, err)
 | 
				
			||||||
		})
 | 
							})
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -298,24 +338,26 @@ func TestUpdatingRefreshTokenInCaseOfExpiredIDToken(t *testing.T) {
 | 
				
			|||||||
	var tests = []struct {
 | 
						var tests = []struct {
 | 
				
			||||||
		name                            string
 | 
							name                            string
 | 
				
			||||||
		configureUpdatingTokenBehaviour func(t *testing.T, oidcServer *utilsoidc.TestServer, signingPrivateKey *rsa.PrivateKey)
 | 
							configureUpdatingTokenBehaviour func(t *testing.T, oidcServer *utilsoidc.TestServer, signingPrivateKey *rsa.PrivateKey)
 | 
				
			||||||
		asserErrFn                      func(t *testing.T, errorToCheck error)
 | 
							assertErrFn                     func(t *testing.T, errorToCheck error)
 | 
				
			||||||
	}{
 | 
						}{
 | 
				
			||||||
		{
 | 
							{
 | 
				
			||||||
			name: "cache returns stale client if refresh token is not updated in config",
 | 
								name: "cache returns stale client if refresh token is not updated in config",
 | 
				
			||||||
			configureUpdatingTokenBehaviour: func(t *testing.T, oidcServer *utilsoidc.TestServer, signingPrivateKey *rsa.PrivateKey) {
 | 
								configureUpdatingTokenBehaviour: func(t *testing.T, oidcServer *utilsoidc.TestServer, signingPrivateKey *rsa.PrivateKey) {
 | 
				
			||||||
				oidcServer.TokenHandler().EXPECT().Token().Times(1).DoAndReturn(utilsoidc.TokenHandlerBehaviourReturningPredefinedJWT(
 | 
									oidcServer.TokenHandler().EXPECT().Token().Times(1).DoAndReturn(utilsoidc.TokenHandlerBehaviorReturningPredefinedJWT(
 | 
				
			||||||
					t,
 | 
										t,
 | 
				
			||||||
					signingPrivateKey,
 | 
										signingPrivateKey,
 | 
				
			||||||
					oidcServer.URL(),
 | 
										map[string]interface{}{
 | 
				
			||||||
					defaultOIDCClientID,
 | 
											"iss": oidcServer.URL(),
 | 
				
			||||||
					defaultOIDCClaimedUsername,
 | 
											"sub": defaultOIDCClaimedUsername,
 | 
				
			||||||
 | 
											"aud": defaultOIDCClientID,
 | 
				
			||||||
 | 
											"exp": time.Now().Add(time.Second * 1200).Unix(),
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
					defaultStubAccessToken,
 | 
										defaultStubAccessToken,
 | 
				
			||||||
					defaultStubRefreshToken,
 | 
										defaultStubRefreshToken,
 | 
				
			||||||
					time.Now().Add(time.Second*1200).Unix(),
 | 
					 | 
				
			||||||
				))
 | 
									))
 | 
				
			||||||
				configureOIDCServerToReturnExpiredRefreshTokenErrorOnTryingToUpdateIDToken(oidcServer)
 | 
									configureOIDCServerToReturnExpiredRefreshTokenErrorOnTryingToUpdateIDToken(oidcServer)
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
			asserErrFn: func(t *testing.T, errorToCheck error) {
 | 
								assertErrFn: func(t *testing.T, errorToCheck error) {
 | 
				
			||||||
				urlError, ok := errorToCheck.(*url.Error)
 | 
									urlError, ok := errorToCheck.(*url.Error)
 | 
				
			||||||
				require.True(t, ok)
 | 
									require.True(t, ok)
 | 
				
			||||||
				assert.Equal(
 | 
									assert.Equal(
 | 
				
			||||||
@@ -327,7 +369,7 @@ func TestUpdatingRefreshTokenInCaseOfExpiredIDToken(t *testing.T) {
 | 
				
			|||||||
		},
 | 
							},
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	oidcServer, apiServer, signingPrivateKey, caCert, certPath := configureTestInfrastructure(t, false)
 | 
						oidcServer, apiServer, signingPrivateKey, caCert, certPath := configureTestInfrastructure(t, func(t *testing.T, _, _ string) string { return "" })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	tokenURL, err := oidcServer.TokenURL()
 | 
						tokenURL, err := oidcServer.TokenURL()
 | 
				
			||||||
	require.NoError(t, err)
 | 
						require.NoError(t, err)
 | 
				
			||||||
@@ -339,9 +381,7 @@ func TestUpdatingRefreshTokenInCaseOfExpiredIDToken(t *testing.T) {
 | 
				
			|||||||
			expiredClient := kubernetes.NewForConfigOrDie(clientConfig)
 | 
								expiredClient := kubernetes.NewForConfigOrDie(clientConfig)
 | 
				
			||||||
			configureOIDCServerToReturnExpiredRefreshTokenErrorOnTryingToUpdateIDToken(oidcServer)
 | 
								configureOIDCServerToReturnExpiredRefreshTokenErrorOnTryingToUpdateIDToken(oidcServer)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
 | 
								ctx := testContext(t)
 | 
				
			||||||
			defer cancel()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			_, err = expiredClient.CoreV1().Pods(defaultNamespace).List(ctx, metav1.ListOptions{})
 | 
								_, err = expiredClient.CoreV1().Pods(defaultNamespace).List(ctx, metav1.ListOptions{})
 | 
				
			||||||
			assert.Error(t, err)
 | 
								assert.Error(t, err)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -351,12 +391,347 @@ func TestUpdatingRefreshTokenInCaseOfExpiredIDToken(t *testing.T) {
 | 
				
			|||||||
			expectedOkClient := kubernetes.NewForConfigOrDie(clientConfig)
 | 
								expectedOkClient := kubernetes.NewForConfigOrDie(clientConfig)
 | 
				
			||||||
			_, err = expectedOkClient.CoreV1().Pods(defaultNamespace).List(ctx, metav1.ListOptions{})
 | 
								_, err = expectedOkClient.CoreV1().Pods(defaultNamespace).List(ctx, metav1.ListOptions{})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			tt.asserErrFn(t, err)
 | 
								tt.assertErrFn(t, err)
 | 
				
			||||||
		})
 | 
							})
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func configureTestInfrastructure(t *testing.T, useAuthenticationConfig bool) (
 | 
					func TestStructuredAuthenticationConfigCEL(t *testing.T) {
 | 
				
			||||||
 | 
						defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.StructuredAuthenticationConfiguration, true)()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						tests := []struct {
 | 
				
			||||||
 | 
							name                    string
 | 
				
			||||||
 | 
							authConfigFn            authenticationConfigFunc
 | 
				
			||||||
 | 
							configureInfrastructure func(t *testing.T, fn authenticationConfigFunc) (
 | 
				
			||||||
 | 
								oidcServer *utilsoidc.TestServer,
 | 
				
			||||||
 | 
								apiServer *kubeapiserverapptesting.TestServer,
 | 
				
			||||||
 | 
								signingPrivateKey *rsa.PrivateKey,
 | 
				
			||||||
 | 
								caCertContent []byte,
 | 
				
			||||||
 | 
								caFilePath string,
 | 
				
			||||||
 | 
							)
 | 
				
			||||||
 | 
							configureOIDCServerBehaviour func(t *testing.T, oidcServer *utilsoidc.TestServer, signingPrivateKey *rsa.PrivateKey)
 | 
				
			||||||
 | 
							configureClient              func(
 | 
				
			||||||
 | 
								t *testing.T,
 | 
				
			||||||
 | 
								restCfg *rest.Config,
 | 
				
			||||||
 | 
								caCert []byte,
 | 
				
			||||||
 | 
								certPath,
 | 
				
			||||||
 | 
								oidcServerURL,
 | 
				
			||||||
 | 
								oidcServerTokenURL string,
 | 
				
			||||||
 | 
							) kubernetes.Interface
 | 
				
			||||||
 | 
							assertErrFn func(t *testing.T, errorToCheck error)
 | 
				
			||||||
 | 
							wantUser    *authenticationv1.UserInfo
 | 
				
			||||||
 | 
						}{
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "username CEL expression 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:
 | 
				
			||||||
 | 
					    - %s
 | 
				
			||||||
 | 
					    certificateAuthority: |
 | 
				
			||||||
 | 
					        %s
 | 
				
			||||||
 | 
					  claimMappings:
 | 
				
			||||||
 | 
					    username:
 | 
				
			||||||
 | 
					      expression: "'k8s-' + claims.sub"
 | 
				
			||||||
 | 
					`, issuerURL, defaultOIDCClientID, indentCertificateAuthority(caCert))
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								configureInfrastructure: configureTestInfrastructure,
 | 
				
			||||||
 | 
								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": defaultOIDCClientID,
 | 
				
			||||||
 | 
											"exp": time.Now().Add(idTokenLifetime).Unix(),
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										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"},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "groups CEL expression 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:
 | 
				
			||||||
 | 
					    - %s
 | 
				
			||||||
 | 
					    certificateAuthority: |
 | 
				
			||||||
 | 
					        %s
 | 
				
			||||||
 | 
					  claimMappings:
 | 
				
			||||||
 | 
					    username:
 | 
				
			||||||
 | 
					      expression: "'k8s-' + claims.sub"
 | 
				
			||||||
 | 
					    groups:
 | 
				
			||||||
 | 
					      expression: '(claims.roles.split(",") + claims.other_roles.split(",")).map(role, "prefix:" + role)'
 | 
				
			||||||
 | 
					`, issuerURL, defaultOIDCClientID, indentCertificateAuthority(caCert))
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								configureInfrastructure: configureTestInfrastructure,
 | 
				
			||||||
 | 
								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":         defaultOIDCClientID,
 | 
				
			||||||
 | 
											"exp":         time.Now().Add(idTokenLifetime).Unix(),
 | 
				
			||||||
 | 
											"roles":       "foo,bar",
 | 
				
			||||||
 | 
											"other_roles": "baz,qux",
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										defaultStubAccessToken,
 | 
				
			||||||
 | 
										defaultStubRefreshToken,
 | 
				
			||||||
 | 
									))
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								configureClient: configureClientFetchingOIDCCredentials,
 | 
				
			||||||
 | 
								assertErrFn: func(t *testing.T, errorToCheck error) {
 | 
				
			||||||
 | 
									assert.NoError(t, errorToCheck)
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								wantUser: &authenticationv1.UserInfo{
 | 
				
			||||||
 | 
									Username: "k8s-john_doe",
 | 
				
			||||||
 | 
									Groups:   []string{"prefix:foo", "prefix:bar", "prefix:baz", "prefix:qux", "system:authenticated"},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "claim validation rule fails",
 | 
				
			||||||
 | 
								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:
 | 
				
			||||||
 | 
					    - %s
 | 
				
			||||||
 | 
					    certificateAuthority: |
 | 
				
			||||||
 | 
					        %s
 | 
				
			||||||
 | 
					  claimMappings:
 | 
				
			||||||
 | 
					    username:
 | 
				
			||||||
 | 
					      expression: "'k8s-' + claims.sub"
 | 
				
			||||||
 | 
					  claimValidationRules:
 | 
				
			||||||
 | 
					  - expression: 'claims.hd == "example.com"'
 | 
				
			||||||
 | 
					    message: "the hd claim must be set to example.com"
 | 
				
			||||||
 | 
					`, issuerURL, defaultOIDCClientID, indentCertificateAuthority(caCert))
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								configureInfrastructure: configureTestInfrastructure,
 | 
				
			||||||
 | 
								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": defaultOIDCClientID,
 | 
				
			||||||
 | 
											"exp": time.Now().Add(idTokenLifetime).Unix(),
 | 
				
			||||||
 | 
											"hd":  "notexample.com",
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										defaultStubAccessToken,
 | 
				
			||||||
 | 
										defaultStubRefreshToken,
 | 
				
			||||||
 | 
									))
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								configureClient: configureClientFetchingOIDCCredentials,
 | 
				
			||||||
 | 
								assertErrFn: func(t *testing.T, errorToCheck error) {
 | 
				
			||||||
 | 
									assert.True(t, apierrors.IsUnauthorized(errorToCheck), errorToCheck)
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "extra mapping CEL expressions are 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:
 | 
				
			||||||
 | 
					    - %s
 | 
				
			||||||
 | 
					    certificateAuthority: |
 | 
				
			||||||
 | 
					        %s
 | 
				
			||||||
 | 
					  claimMappings:
 | 
				
			||||||
 | 
					    username:
 | 
				
			||||||
 | 
					      expression: "'k8s-' + claims.sub"
 | 
				
			||||||
 | 
					    extra:
 | 
				
			||||||
 | 
					    - key: "example.org/foo"
 | 
				
			||||||
 | 
					      valueExpression: "'bar'"
 | 
				
			||||||
 | 
					    - key: "example.org/baz"
 | 
				
			||||||
 | 
					      valueExpression: "claims.baz"
 | 
				
			||||||
 | 
					  userValidationRules:
 | 
				
			||||||
 | 
					  - expression: "'bar' in user.extra['example.org/foo'] && 'qux' in user.extra['example.org/baz']"
 | 
				
			||||||
 | 
					    message: "example.org/foo must be bar and example.org/baz must be qux"
 | 
				
			||||||
 | 
					`, issuerURL, defaultOIDCClientID, indentCertificateAuthority(caCert))
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								configureInfrastructure: configureTestInfrastructure,
 | 
				
			||||||
 | 
								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": defaultOIDCClientID,
 | 
				
			||||||
 | 
											"exp": time.Now().Add(idTokenLifetime).Unix(),
 | 
				
			||||||
 | 
											"baz": "qux",
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										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"},
 | 
				
			||||||
 | 
									Extra: map[string]authenticationv1.ExtraValue{
 | 
				
			||||||
 | 
										"example.org/foo": {"bar"},
 | 
				
			||||||
 | 
										"example.org/baz": {"qux"},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "uid CEL expression 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:
 | 
				
			||||||
 | 
					    - %s
 | 
				
			||||||
 | 
					    certificateAuthority: |
 | 
				
			||||||
 | 
					        %s
 | 
				
			||||||
 | 
					  claimMappings:
 | 
				
			||||||
 | 
					    username:
 | 
				
			||||||
 | 
					      expression: "'k8s-' + claims.sub"
 | 
				
			||||||
 | 
					    uid:
 | 
				
			||||||
 | 
					      expression: "claims.uid"
 | 
				
			||||||
 | 
					`, issuerURL, defaultOIDCClientID, indentCertificateAuthority(caCert))
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								configureInfrastructure: configureTestInfrastructure,
 | 
				
			||||||
 | 
								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": defaultOIDCClientID,
 | 
				
			||||||
 | 
											"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",
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "user validation rule fails",
 | 
				
			||||||
 | 
								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:
 | 
				
			||||||
 | 
					    - %s
 | 
				
			||||||
 | 
					    certificateAuthority: |
 | 
				
			||||||
 | 
					        %s
 | 
				
			||||||
 | 
					  claimMappings:
 | 
				
			||||||
 | 
					    username:
 | 
				
			||||||
 | 
					      expression: "'k8s-' + claims.sub"
 | 
				
			||||||
 | 
					    groups:
 | 
				
			||||||
 | 
					      expression: '(claims.roles.split(",") + claims.other_roles.split(",")).map(role, "system:" + role)'
 | 
				
			||||||
 | 
					  userValidationRules:
 | 
				
			||||||
 | 
					  - expression: "user.groups.all(group, !group.startsWith('system:'))"
 | 
				
			||||||
 | 
					    message: "groups cannot used reserved system: prefix"
 | 
				
			||||||
 | 
					`, issuerURL, defaultOIDCClientID, indentCertificateAuthority(caCert))
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								configureInfrastructure: configureTestInfrastructure,
 | 
				
			||||||
 | 
								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":         defaultOIDCClientID,
 | 
				
			||||||
 | 
											"exp":         time.Now().Add(idTokenLifetime).Unix(),
 | 
				
			||||||
 | 
											"roles":       "foo,bar",
 | 
				
			||||||
 | 
											"other_roles": "baz,qux",
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										defaultStubAccessToken,
 | 
				
			||||||
 | 
										defaultStubRefreshToken,
 | 
				
			||||||
 | 
									))
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								configureClient: configureClientFetchingOIDCCredentials,
 | 
				
			||||||
 | 
								assertErrFn: func(t *testing.T, errorToCheck error) {
 | 
				
			||||||
 | 
									assert.True(t, apierrors.IsUnauthorized(errorToCheck), errorToCheck)
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								wantUser: nil,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, tt := range tests {
 | 
				
			||||||
 | 
							t.Run(tt.name, func(t *testing.T) {
 | 
				
			||||||
 | 
								oidcServer, apiServer, signingPrivateKey, caCert, certPath := tt.configureInfrastructure(t, tt.authConfigFn)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								tt.configureOIDCServerBehaviour(t, oidcServer, signingPrivateKey)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								tokenURL, err := oidcServer.TokenURL()
 | 
				
			||||||
 | 
								require.NoError(t, err)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								client := tt.configureClient(t, apiServer.ClientConfig, caCert, certPath, oidcServer.URL(), tokenURL)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								ctx := testContext(t)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if tt.wantUser != nil {
 | 
				
			||||||
 | 
									res, err := client.AuthenticationV1().SelfSubjectReviews().Create(ctx, &authenticationv1.SelfSubjectReview{}, metav1.CreateOptions{})
 | 
				
			||||||
 | 
									require.NoError(t, err)
 | 
				
			||||||
 | 
									assert.Equal(t, *tt.wantUser, res.Status.UserInfo)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								_, err = client.CoreV1().Pods(defaultNamespace).List(ctx, metav1.ListOptions{})
 | 
				
			||||||
 | 
								tt.assertErrFn(t, err)
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func configureTestInfrastructure(t *testing.T, fn authenticationConfigFunc) (
 | 
				
			||||||
	oidcServer *utilsoidc.TestServer,
 | 
						oidcServer *utilsoidc.TestServer,
 | 
				
			||||||
	apiServer *kubeapiserverapptesting.TestServer,
 | 
						apiServer *kubeapiserverapptesting.TestServer,
 | 
				
			||||||
	signingPrivateKey *rsa.PrivateKey,
 | 
						signingPrivateKey *rsa.PrivateKey,
 | 
				
			||||||
@@ -372,14 +747,14 @@ func configureTestInfrastructure(t *testing.T, useAuthenticationConfig bool) (
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	oidcServer = utilsoidc.BuildAndRunTestServer(t, caFilePath, caKeyFilePath)
 | 
						oidcServer = utilsoidc.BuildAndRunTestServer(t, caFilePath, caKeyFilePath)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if useAuthenticationConfig {
 | 
						authenticationConfig := fn(t, oidcServer.URL(), string(caCertContent))
 | 
				
			||||||
		authenticationConfig := generateAuthenticationConfig(t, oidcServer.URL(), defaultOIDCClientID, string(caCertContent), defaultOIDCUsernamePrefix)
 | 
						if len(authenticationConfig) > 0 {
 | 
				
			||||||
		apiServer = startTestAPIServerForOIDC(t, "", "", "", authenticationConfig)
 | 
							apiServer = startTestAPIServerForOIDC(t, "", "", "", authenticationConfig)
 | 
				
			||||||
	} else {
 | 
						} else {
 | 
				
			||||||
		apiServer = startTestAPIServerForOIDC(t, oidcServer.URL(), defaultOIDCClientID, caFilePath, "")
 | 
							apiServer = startTestAPIServerForOIDC(t, oidcServer.URL(), defaultOIDCClientID, caFilePath, "")
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	oidcServer.JwksHandler().EXPECT().KeySet().AnyTimes().DoAndReturn(utilsoidc.DefaultJwksHandlerBehaviour(t, &signingPrivateKey.PublicKey))
 | 
						oidcServer.JwksHandler().EXPECT().KeySet().AnyTimes().DoAndReturn(utilsoidc.DefaultJwksHandlerBehavior(t, &signingPrivateKey.PublicKey))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	adminClient := kubernetes.NewForConfigOrDie(apiServer.ClientConfig)
 | 
						adminClient := kubernetes.NewForConfigOrDie(apiServer.ClientConfig)
 | 
				
			||||||
	configureRBAC(t, adminClient, defaultRole, defaultRoleBinding)
 | 
						configureRBAC(t, adminClient, defaultRole, defaultRoleBinding)
 | 
				
			||||||
@@ -387,19 +762,19 @@ func configureTestInfrastructure(t *testing.T, useAuthenticationConfig bool) (
 | 
				
			|||||||
	return oidcServer, apiServer, signingPrivateKey, caCertContent, caFilePath
 | 
						return oidcServer, apiServer, signingPrivateKey, caCertContent, caFilePath
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func configureClientFetchingOIDCCredentials(t *testing.T, restCfg *rest.Config, caCert []byte, certPath, oidcServerURL, oidcServerTokenURL string) *kubernetes.Clientset {
 | 
					func configureClientFetchingOIDCCredentials(t *testing.T, restCfg *rest.Config, caCert []byte, certPath, oidcServerURL, oidcServerTokenURL string) kubernetes.Interface {
 | 
				
			||||||
	idToken, stubRefreshToken := fetchOIDCCredentials(t, oidcServerTokenURL, caCert)
 | 
						idToken, stubRefreshToken := fetchOIDCCredentials(t, oidcServerTokenURL, caCert)
 | 
				
			||||||
	clientConfig := configureClientConfigForOIDC(t, restCfg, defaultOIDCClientID, certPath, idToken, stubRefreshToken, oidcServerURL)
 | 
						clientConfig := configureClientConfigForOIDC(t, restCfg, defaultOIDCClientID, certPath, idToken, stubRefreshToken, oidcServerURL)
 | 
				
			||||||
	return kubernetes.NewForConfigOrDie(clientConfig)
 | 
						return kubernetes.NewForConfigOrDie(clientConfig)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func configureClientWithEmptyIDToken(t *testing.T, restCfg *rest.Config, _ []byte, certPath, oidcServerURL, _ string) *kubernetes.Clientset {
 | 
					func configureClientWithEmptyIDToken(t *testing.T, restCfg *rest.Config, _ []byte, certPath, oidcServerURL, _ string) kubernetes.Interface {
 | 
				
			||||||
	emptyIDToken, stubRefreshToken := "", defaultStubRefreshToken
 | 
						emptyIDToken, stubRefreshToken := "", defaultStubRefreshToken
 | 
				
			||||||
	clientConfig := configureClientConfigForOIDC(t, restCfg, defaultOIDCClientID, certPath, emptyIDToken, stubRefreshToken, oidcServerURL)
 | 
						clientConfig := configureClientConfigForOIDC(t, restCfg, defaultOIDCClientID, certPath, emptyIDToken, stubRefreshToken, oidcServerURL)
 | 
				
			||||||
	return kubernetes.NewForConfigOrDie(clientConfig)
 | 
						return kubernetes.NewForConfigOrDie(clientConfig)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func configureRBAC(t *testing.T, clientset *kubernetes.Clientset, role *rbacv1.Role, binding *rbacv1.RoleBinding) {
 | 
					func configureRBAC(t *testing.T, clientset kubernetes.Interface, role *rbacv1.Role, binding *rbacv1.RoleBinding) {
 | 
				
			||||||
	t.Helper()
 | 
						t.Helper()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
 | 
						ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
 | 
				
			||||||
@@ -500,15 +875,17 @@ func configureOIDCServerToReturnExpiredIDToken(t *testing.T, returningExpiredTok
 | 
				
			|||||||
	t.Helper()
 | 
						t.Helper()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	oidcServer.TokenHandler().EXPECT().Token().Times(returningExpiredTokenTimes).DoAndReturn(func() (utilsoidc.Token, error) {
 | 
						oidcServer.TokenHandler().EXPECT().Token().Times(returningExpiredTokenTimes).DoAndReturn(func() (utilsoidc.Token, error) {
 | 
				
			||||||
		token, err := utilsoidc.TokenHandlerBehaviourReturningPredefinedJWT(
 | 
							token, err := utilsoidc.TokenHandlerBehaviorReturningPredefinedJWT(
 | 
				
			||||||
			t,
 | 
								t,
 | 
				
			||||||
			signingPrivateKey,
 | 
								signingPrivateKey,
 | 
				
			||||||
			oidcServer.URL(),
 | 
								map[string]interface{}{
 | 
				
			||||||
			defaultOIDCClientID,
 | 
									"iss": oidcServer.URL(),
 | 
				
			||||||
			defaultOIDCClaimedUsername,
 | 
									"sub": defaultOIDCClaimedUsername,
 | 
				
			||||||
 | 
									"aud": defaultOIDCClientID,
 | 
				
			||||||
 | 
									"exp": time.Now().Add(-time.Millisecond).Unix(),
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
			defaultStubAccessToken,
 | 
								defaultStubAccessToken,
 | 
				
			||||||
			defaultStubRefreshToken,
 | 
								defaultStubRefreshToken,
 | 
				
			||||||
			time.Now().Add(-time.Millisecond).Unix(),
 | 
					 | 
				
			||||||
		)()
 | 
							)()
 | 
				
			||||||
		return token, err
 | 
							return token, err
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
@@ -548,26 +925,14 @@ func writeTempFile(t *testing.T, content string) string {
 | 
				
			|||||||
	return file.Name()
 | 
						return file.Name()
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func generateAuthenticationConfig(t *testing.T, issuerURL, clientID, caCert, usernamePrefix string) string {
 | 
					// indentCertificateAuthority indents the certificate authority to match
 | 
				
			||||||
	t.Helper()
 | 
					// the format of the generated authentication config.
 | 
				
			||||||
 | 
					func indentCertificateAuthority(caCert string) string {
 | 
				
			||||||
	// Indent the certificate authority to match the format of the generated
 | 
						return strings.ReplaceAll(caCert, "\n", "\n        ")
 | 
				
			||||||
	// authentication config.
 | 
					}
 | 
				
			||||||
	caCert = strings.ReplaceAll(caCert, "\n", "\n        ")
 | 
					
 | 
				
			||||||
 | 
					func testContext(t *testing.T) context.Context {
 | 
				
			||||||
	return fmt.Sprintf(`
 | 
						ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
 | 
				
			||||||
apiVersion: apiserver.config.k8s.io/v1alpha1
 | 
						t.Cleanup(cancel)
 | 
				
			||||||
kind: AuthenticationConfiguration
 | 
						return ctx
 | 
				
			||||||
jwt:
 | 
					 | 
				
			||||||
- issuer:
 | 
					 | 
				
			||||||
    url: %s
 | 
					 | 
				
			||||||
    audiences:
 | 
					 | 
				
			||||||
    - %s
 | 
					 | 
				
			||||||
    certificateAuthority: |
 | 
					 | 
				
			||||||
        %s
 | 
					 | 
				
			||||||
  claimMappings:
 | 
					 | 
				
			||||||
    username:
 | 
					 | 
				
			||||||
      claim: sub
 | 
					 | 
				
			||||||
      prefix: %s
 | 
					 | 
				
			||||||
`, issuerURL, clientID, string(caCert), usernamePrefix)
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -170,17 +170,12 @@ func BuildAndRunTestServer(t *testing.T, caPath, caKeyPath string) *TestServer {
 | 
				
			|||||||
	return oidcServer
 | 
						return oidcServer
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// TokenHandlerBehaviourReturningPredefinedJWT describes the scenario when signed JWT token is being created.
 | 
					// TokenHandlerBehaviorReturningPredefinedJWT describes the scenario when signed JWT token is being created.
 | 
				
			||||||
// This behaviour should being applied to the MockTokenHandler.
 | 
					// This behavior should being applied to the MockTokenHandler.
 | 
				
			||||||
func TokenHandlerBehaviourReturningPredefinedJWT(
 | 
					func TokenHandlerBehaviorReturningPredefinedJWT(
 | 
				
			||||||
	t *testing.T,
 | 
						t *testing.T,
 | 
				
			||||||
	rsaPrivateKey *rsa.PrivateKey,
 | 
						rsaPrivateKey *rsa.PrivateKey,
 | 
				
			||||||
	issClaim,
 | 
						claims map[string]interface{}, accessToken, refreshToken string,
 | 
				
			||||||
	audClaim,
 | 
					 | 
				
			||||||
	subClaim,
 | 
					 | 
				
			||||||
	accessToken,
 | 
					 | 
				
			||||||
	refreshToken string,
 | 
					 | 
				
			||||||
	expClaim int64,
 | 
					 | 
				
			||||||
) func() (Token, error) {
 | 
					) func() (Token, error) {
 | 
				
			||||||
	t.Helper()
 | 
						t.Helper()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -188,18 +183,7 @@ func TokenHandlerBehaviourReturningPredefinedJWT(
 | 
				
			|||||||
		signer, err := jose.NewSigner(jose.SigningKey{Algorithm: jose.RS256, Key: rsaPrivateKey}, nil)
 | 
							signer, err := jose.NewSigner(jose.SigningKey{Algorithm: jose.RS256, Key: rsaPrivateKey}, nil)
 | 
				
			||||||
		require.NoError(t, err)
 | 
							require.NoError(t, err)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		payload := struct {
 | 
							payloadJSON, err := json.Marshal(claims)
 | 
				
			||||||
			Iss string `json:"iss"`
 | 
					 | 
				
			||||||
			Aud string `json:"aud"`
 | 
					 | 
				
			||||||
			Sub string `json:"sub"`
 | 
					 | 
				
			||||||
			Exp int64  `json:"exp"`
 | 
					 | 
				
			||||||
		}{
 | 
					 | 
				
			||||||
			Iss: issClaim,
 | 
					 | 
				
			||||||
			Aud: audClaim,
 | 
					 | 
				
			||||||
			Sub: subClaim,
 | 
					 | 
				
			||||||
			Exp: expClaim,
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		payloadJSON, err := json.Marshal(payload)
 | 
					 | 
				
			||||||
		require.NoError(t, err)
 | 
							require.NoError(t, err)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		idTokenSignature, err := signer.Sign(payloadJSON)
 | 
							idTokenSignature, err := signer.Sign(payloadJSON)
 | 
				
			||||||
@@ -215,9 +199,9 @@ func TokenHandlerBehaviourReturningPredefinedJWT(
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// DefaultJwksHandlerBehaviour describes the scenario when JSON Web Key Set token is being returned.
 | 
					// DefaultJwksHandlerBehavior describes the scenario when JSON Web Key Set token is being returned.
 | 
				
			||||||
// This behaviour should being applied to the MockJWKsHandler.
 | 
					// This behavior should being applied to the MockJWKsHandler.
 | 
				
			||||||
func DefaultJwksHandlerBehaviour(t *testing.T, verificationPublicKey *rsa.PublicKey) func() jose.JSONWebKeySet {
 | 
					func DefaultJwksHandlerBehavior(t *testing.T, verificationPublicKey *rsa.PublicKey) func() jose.JSONWebKeySet {
 | 
				
			||||||
	t.Helper()
 | 
						t.Helper()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return func() jose.JSONWebKeySet {
 | 
						return func() jose.JSONWebKeySet {
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										1
									
								
								vendor/modules.txt
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								vendor/modules.txt
									
									
									
									
										vendored
									
									
								
							@@ -1501,6 +1501,7 @@ k8s.io/apiserver/pkg/audit
 | 
				
			|||||||
k8s.io/apiserver/pkg/audit/policy
 | 
					k8s.io/apiserver/pkg/audit/policy
 | 
				
			||||||
k8s.io/apiserver/pkg/authentication/authenticator
 | 
					k8s.io/apiserver/pkg/authentication/authenticator
 | 
				
			||||||
k8s.io/apiserver/pkg/authentication/authenticatorfactory
 | 
					k8s.io/apiserver/pkg/authentication/authenticatorfactory
 | 
				
			||||||
 | 
					k8s.io/apiserver/pkg/authentication/cel
 | 
				
			||||||
k8s.io/apiserver/pkg/authentication/group
 | 
					k8s.io/apiserver/pkg/authentication/group
 | 
				
			||||||
k8s.io/apiserver/pkg/authentication/request/anonymous
 | 
					k8s.io/apiserver/pkg/authentication/request/anonymous
 | 
				
			||||||
k8s.io/apiserver/pkg/authentication/request/bearertoken
 | 
					k8s.io/apiserver/pkg/authentication/request/bearertoken
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user