Skip to content

OCPBUGS-55792: [release-4.18] config: add uid and extra claim mappings for external OIDC configuration #2311

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Large diffs are not rendered by default.

137 changes: 135 additions & 2 deletions config/v1/types_authentication.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
// +genclient
// +genclient:nonNamespaced
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// +openshift:validation:FeatureGateAwareXValidation:featureGate=ExternalOIDC,rule="!has(self.spec.oidcProviders) || self.spec.oidcProviders.all(p, !has(p.oidcClients) || p.oidcClients.all(specC, self.status.oidcClients.exists(statusC, statusC.componentNamespace == specC.componentNamespace && statusC.componentName == specC.componentName) || (has(oldSelf.spec.oidcProviders) && oldSelf.spec.oidcProviders.exists(oldP, oldP.name == p.name && has(oldP.oidcClients) && oldP.oidcClients.exists(oldC, oldC.componentNamespace == specC.componentNamespace && oldC.componentName == specC.componentName)))))",message="all oidcClients in the oidcProviders must match their componentName and componentNamespace to either a previously configured oidcClient or they must exist in the status.oidcClients"
// +openshift:validation:FeatureGateAwareXValidation:featureGate=ExternalOIDC;ExternalOIDCWithUIDAndExtraClaimMappings,rule="!has(self.spec.oidcProviders) || self.spec.oidcProviders.all(p, !has(p.oidcClients) || p.oidcClients.all(specC, self.status.oidcClients.exists(statusC, statusC.componentNamespace == specC.componentNamespace && statusC.componentName == specC.componentName) || (has(oldSelf.spec.oidcProviders) && oldSelf.spec.oidcProviders.exists(oldP, oldP.name == p.name && has(oldP.oidcClients) && oldP.oidcClients.exists(oldC, oldC.componentNamespace == specC.componentNamespace && oldC.componentName == specC.componentName)))))",message="all oidcClients in the oidcProviders must match their componentName and componentNamespace to either a previously configured oidcClient or they must exist in the status.oidcClients"

// Authentication specifies cluster-wide settings for authentication (like OAuth and
// webhook token authenticators). The canonical name of an instance is `cluster`.
Expand Down Expand Up @@ -91,6 +91,7 @@ type AuthenticationSpec struct {
// +listMapKey=name
// +kubebuilder:validation:MaxItems=1
// +openshift:enable:FeatureGate=ExternalOIDC
// +openshift:enable:FeatureGate=ExternalOIDCWithUIDAndExtraClaimMappings
OIDCProviders []OIDCProvider `json:"oidcProviders,omitempty"`
}

Expand Down Expand Up @@ -118,6 +119,7 @@ type AuthenticationStatus struct {
// +listMapKey=componentName
// +kubebuilder:validation:MaxItems=20
// +openshift:enable:FeatureGate=ExternalOIDC
// +openshift:enable:FeatureGate=ExternalOIDCWithUIDAndExtraClaimMappings
OIDCClients []OIDCClientStatus `json:"oidcClients"`
}

Expand All @@ -136,7 +138,7 @@ type AuthenticationList struct {
}

// +openshift:validation:FeatureGateAwareEnum:featureGate="",enum="";None;IntegratedOAuth
// +openshift:validation:FeatureGateAwareEnum:featureGate=ExternalOIDC,enum="";None;IntegratedOAuth;OIDC
// +openshift:validation:FeatureGateAwareEnum:featureGate=ExternalOIDC;ExternalOIDCWithUIDAndExtraClaimMappings,enum="";None;IntegratedOAuth;OIDC
type AuthenticationType string

const (
Expand Down Expand Up @@ -268,6 +270,33 @@ type TokenClaimMappings struct {
// groups for the cluster identity.
// The referenced claim must use array of strings values.
Groups PrefixedClaimMapping `json:"groups,omitempty"`

// uid is an optional field for configuring the claim mapping
// used to construct the uid for the cluster identity.
//
// When using uid.claim to specify the claim it must be a single string value.
// When using uid.expression the expression must result in a single string value.
//
// When omitted, this means the user has no opinion and the platform
// is left to choose a default, which is subject to change over time.
// The current default is to use the 'sub' claim.
//
// +optional
// +openshift:enable:FeatureGate=ExternalOIDCWithUIDAndExtraClaimMappings
UID *TokenClaimOrExpressionMapping `json:"uid,omitempty"`

// extra is an optional field for configuring the mappings
// used to construct the extra attribute for the cluster identity.
// When omitted, no extra attributes will be present on the cluster identity.
// key values for extra mappings must be unique.
// A maximum of 64 extra attribute mappings may be provided.
//
// +optional
// +kubebuilder:validation:MaxItems=64
// +listType=map
// +listMapKey=key
// +openshift:enable:FeatureGate=ExternalOIDCWithUIDAndExtraClaimMappings
Extra []ExtraMapping `json:"extra,omitempty"`
}

type TokenClaimMapping struct {
Expand All @@ -278,6 +307,110 @@ type TokenClaimMapping struct {
Claim string `json:"claim"`
}

// TokenClaimOrExpressionMapping allows specifying either a JWT
// token claim or CEL expression to be used when mapping claims
// from an authentication token to cluster identities.
// +kubebuilder:validation:XValidation:rule="has(self.claim) ? !has(self.expression) : has(self.expression)",message="precisely one of claim or expression must be set"
type TokenClaimOrExpressionMapping struct {
// claim is an optional field for specifying the
// JWT token claim that is used in the mapping.
// The value of this claim will be assigned to
// the field in which this mapping is associated.
//
// Precisely one of claim or expression must be set.
// claim must not be specified when expression is set.
// When specified, claim must be at least 1 character in length
// and must not exceed 256 characters in length.
//
// +optional
// +kubebuilder:validation:MaxLength=256
// +kubebuilder:validation:MinLength=1
Claim string `json:"claim,omitempty"`

// expression is an optional field for specifying a
// CEL expression that produces a string value from
// JWT token claims.
//
// CEL expressions have access to the token claims
// through a CEL variable, 'claims'.
// 'claims' is a map of claim names to claim values.
// For example, the 'sub' claim value can be accessed as 'claims.sub'.
// Nested claims can be accessed using dot notation ('claims.foo.bar').
//
// Precisely one of claim or expression must be set.
// expression must not be specified when claim is set.
// When specified, expression must be at least 1 character in length
// and must not exceed 4096 characters in length.
//
// +optional
// +kubebuilder:validation:MaxLength=4096
// +kubebuilder:validation:MinLength=1
Expression string `json:"expression,omitempty"`
}

// ExtraMapping allows specifying a key and CEL expression
// to evaluate the keys' value. It is used to create additional
// mappings and attributes added to a cluster identity from
// a provided authentication token.
type ExtraMapping struct {
// key is a required field that specifies the string
// to use as the extra attribute key.
//
// key must be a domain-prefix path (e.g 'example.org/foo').
// key must not exceed 510 characters in length.
// key must contain the '/' character, separating the domain and path characters.
// key must not be empty.
//
// The domain portion of the key (string of characters prior to the '/') must be a valid RFC1123 subdomain.
// It must not exceed 253 characters in length.
// It must start and end with an alphanumeric character.
// It must only contain lower case alphanumeric characters and '-' or '.'.
// It must not use the reserved domains, or be subdomains of, "kubernetes.io", "k8s.io", and "openshift.io".
//
// The path portion of the key (string of characters after the '/') must not be empty and must consist of at least one
// alphanumeric character, percent-encoded octets, '-', '.', '_', '~', '!', '$', '&', ''', '(', ')', '*', '+', ',', ';', '=', and ':'.
// It must not exceed 256 characters in length.
//
// +required
// +kubebuilder:validation:MinLength=1
// +kubebuilder:validation:MaxLength=510
// +kubebuilder:validation:XValidation:rule="self.contains('/')",message="key must contain the '/' character"
//
// +kubebuilder:validation:XValidation:rule="self.split('/', 2)[0].matches(\"^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\")",message="the domain of the key must consist of only lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character"
// +kubebuilder:validation:XValidation:rule="self.split('/', 2)[0].size() <= 253",message="the domain of the key must not exceed 253 characters in length"
//
// +kubebuilder:validation:XValidation:rule="self.split('/', 2)[0] != 'kubernetes.io'",message="the domain 'kubernetes.io' is reserved for Kubernetes use"
// +kubebuilder:validation:XValidation:rule="!self.split('/', 2)[0].endsWith('.kubernetes.io')",message="the subdomains '*.kubernetes.io' are reserved for Kubernetes use"
// +kubebuilder:validation:XValidation:rule="self.split('/', 2)[0] != 'k8s.io'",message="the domain 'k8s.io' is reserved for Kubernetes use"
// +kubebuilder:validation:XValidation:rule="!self.split('/', 2)[0].endsWith('.k8s.io')",message="the subdomains '*.k8s.io' are reserved for Kubernetes use"
// +kubebuilder:validation:XValidation:rule="self.split('/', 2)[0] != 'openshift.io'",message="the domain 'openshift.io' is reserved for OpenShift use"
// +kubebuilder:validation:XValidation:rule="!self.split('/', 2)[0].endsWith('.openshift.io')",message="the subdomains '*.openshift.io' are reserved for OpenShift use"
//
// +kubebuilder:validation:XValidation:rule="self.split('/', 2)[1].matches('[A-Za-z0-9/\\\\-._~%!$&\\'()*+;=:]+')",message="the path of the key must not be empty and must consist of at least one alphanumeric character, percent-encoded octets, apostrophe, '-', '.', '_', '~', '!', '$', '&', '(', ')', '*', '+', ',', ';', '=', and ':'"
// +kubebuilder:validation:XValidation:rule="self.split('/', 2)[1].size() <= 256",message="the path of the key must not exceed 256 characters in length"
Key string `json:"key"`

// valueExpression is a required field to specify the CEL expression to extract
// the extra attribute value from a JWT token's claims.
// valueExpression must produce a string or string array value.
// "", [], and null are treated as the extra mapping not being present.
// Empty string values within an array are filtered out.
//
// CEL expressions have access to the token claims
// through a CEL variable, 'claims'.
// 'claims' is a map of claim names to claim values.
// For example, the 'sub' claim value can be accessed as 'claims.sub'.
// Nested claims can be accessed using dot notation ('claims.foo.bar').
//
// valueExpression must not exceed 4096 characters in length.
// valueExpression must not be empty.
//
// +required
// +kubebuilder:validation:MinLength=1
// +kubebuilder:validation:MaxLength=4096
ValueExpression string `json:"valueExpression"`
}

type OIDCClientConfig struct {
// ComponentName is the name of the component that is supposed to consume this
// client configuration
Expand Down
Loading