From 2a83d00373896296687eb850800d631a43bdbe6e Mon Sep 17 00:00:00 2001 From: Ryan Richard Date: Tue, 15 Jul 2025 14:38:54 -0700 Subject: [PATCH 1/7] add claimValidationRules, userValidationRules, and claims.extra to JWTAuthenticator CRD --- .../v1alpha1/types_jwtauthenticator.go.tmpl | 144 +++++- ...cierge.pinniped.dev_jwtauthenticators.yaml | 147 +++++- generated/1.26/README.adoc | 145 +++++- .../v1alpha1/types_jwtauthenticator.go | 144 +++++- .../v1alpha1/zz_generated.deepcopy.go | 65 ++- ...cierge.pinniped.dev_jwtauthenticators.yaml | 147 +++++- generated/1.27/README.adoc | 145 +++++- .../v1alpha1/types_jwtauthenticator.go | 144 +++++- .../v1alpha1/zz_generated.deepcopy.go | 65 ++- ...cierge.pinniped.dev_jwtauthenticators.yaml | 147 +++++- generated/1.28/README.adoc | 145 +++++- .../v1alpha1/types_jwtauthenticator.go | 144 +++++- .../v1alpha1/zz_generated.deepcopy.go | 65 ++- ...cierge.pinniped.dev_jwtauthenticators.yaml | 147 +++++- generated/1.29/README.adoc | 145 +++++- .../v1alpha1/types_jwtauthenticator.go | 144 +++++- .../v1alpha1/zz_generated.deepcopy.go | 65 ++- ...cierge.pinniped.dev_jwtauthenticators.yaml | 147 +++++- generated/1.30/README.adoc | 145 +++++- .../v1alpha1/types_jwtauthenticator.go | 144 +++++- .../v1alpha1/zz_generated.deepcopy.go | 65 ++- ...cierge.pinniped.dev_jwtauthenticators.yaml | 147 +++++- generated/1.31/README.adoc | 145 +++++- .../v1alpha1/types_jwtauthenticator.go | 144 +++++- .../v1alpha1/zz_generated.deepcopy.go | 65 ++- ...cierge.pinniped.dev_jwtauthenticators.yaml | 147 +++++- generated/1.32/README.adoc | 145 +++++- .../v1alpha1/types_jwtauthenticator.go | 144 +++++- .../v1alpha1/zz_generated.deepcopy.go | 65 ++- ...cierge.pinniped.dev_jwtauthenticators.yaml | 147 +++++- generated/1.33/README.adoc | 145 +++++- .../v1alpha1/types_jwtauthenticator.go | 144 +++++- .../v1alpha1/zz_generated.deepcopy.go | 65 ++- ...cierge.pinniped.dev_jwtauthenticators.yaml | 147 +++++- generated/latest/README.adoc | 145 +++++- .../v1alpha1/types_jwtauthenticator.go | 144 +++++- .../v1alpha1/zz_generated.deepcopy.go | 65 ++- hack/header.txt | 2 +- internal/certauthority/certauthority.go | 10 +- internal/certauthority/certauthority_test.go | 28 +- .../dynamiccertauthority.go | 6 +- .../dynamiccertauthority_test.go | 13 +- internal/clientcertissuer/issuer.go | 8 +- internal/clientcertissuer/issuer_test.go | 21 +- .../impersonator/impersonator_test.go | 2 +- .../convert_jwtauthenticator_type.go | 107 +++++ .../convert_jwtauthenticator_type_test.go | 149 ++++++ .../jwtcachefiller/jwtcachefiller.go | 111 +++-- .../jwtcachefiller/jwtcachefiller_test.go | 443 +++++++++++++++++- .../impersonator_config_test.go | 4 +- .../mocks/mockcachevalue/mockcachevalue.go | 2 +- .../mockcredentialrequest.go | 2 +- .../mockgithubclient/mockgithubclient.go | 2 +- internal/mocks/mockissuer/mockissuer.go | 10 +- internal/mocks/mockkeyset/mockkeyset.go | 2 +- .../mockkubecertagent/mockdynamiccert.go | 2 +- .../mockpodcommandexecutor.go | 2 +- internal/mocks/mockldapconn/mockldapconn.go | 2 +- .../mockoidcclientoptions.go | 2 +- .../mockresponsewriter/mockresponsewriter.go | 2 +- .../mocksecrethelper/mocksecrethelper.go | 2 +- .../mockupstreamoidcidentityprovider.go | 2 +- internal/registry/credentialrequest/rest.go | 106 ++++- .../registry/credentialrequest/rest_test.go | 269 +++++++---- internal/testutil/certs.go | 9 +- 65 files changed, 5486 insertions(+), 487 deletions(-) create mode 100644 internal/controller/authenticator/jwtcachefiller/convert_jwtauthenticator_type.go create mode 100644 internal/controller/authenticator/jwtcachefiller/convert_jwtauthenticator_type_test.go diff --git a/apis/concierge/authentication/v1alpha1/types_jwtauthenticator.go.tmpl b/apis/concierge/authentication/v1alpha1/types_jwtauthenticator.go.tmpl index a3b01c696..d5149642f 100644 --- a/apis/concierge/authentication/v1alpha1/types_jwtauthenticator.go.tmpl +++ b/apis/concierge/authentication/v1alpha1/types_jwtauthenticator.go.tmpl @@ -18,7 +18,7 @@ const ( JWTAuthenticatorPhaseError JWTAuthenticatorPhase = "Error" ) -// Status of a JWT authenticator. +// JWTAuthenticatorStatus is the status of a JWT authenticator. type JWTAuthenticatorStatus struct { // Represents the observations of the authenticator's current state. // +patchMergeKey=type @@ -26,46 +26,168 @@ type JWTAuthenticatorStatus struct { // +listType=map // +listMapKey=type Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"` + // Phase summarizes the overall status of the JWTAuthenticator. // +kubebuilder:default=Pending // +kubebuilder:validation:Enum=Pending;Ready;Error Phase JWTAuthenticatorPhase `json:"phase,omitempty"` } -// Spec for configuring a JWT authenticator. +// JWTAuthenticatorSpec is the spec for configuring a JWT authenticator. type JWTAuthenticatorSpec struct { - // Issuer is the OIDC issuer URL that will be used to discover public signing keys. Issuer is + // issuer is the OIDC issuer URL that will be used to discover public signing keys. Issuer is // also used to validate the "iss" JWT claim. // +kubebuilder:validation:MinLength=1 // +kubebuilder:validation:Pattern=`^https://` Issuer string `json:"issuer"` - // Audience is the required value of the "aud" JWT claim. + // audience is the required value of the "aud" JWT claim. // +kubebuilder:validation:MinLength=1 Audience string `json:"audience"` - // Claims allows customization of the claims that will be mapped to user identity + // claims allows customization of the claims that will be mapped to user identity // for Kubernetes access. // +optional Claims JWTTokenClaims `json:"claims"` - // TLS configuration for communicating with the OIDC provider. + // claimValidationRules are rules that are applied to validate token claims to authenticate users. + // This is similar to claimValidationRules from Kubernetes AuthenticationConfiguration as documented in + // https://kubernetes.io/docs/reference/access-authn-authz/authentication. + // This is an advanced configuration option. During an end-user login flow, mistakes in this + // configuration will cause the user's login to fail. + // +optional + ClaimValidationRules []ClaimValidationRule `json:"claimValidationRules,omitempty"` + + // 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. + // This is similar to claimValidationRules from Kubernetes AuthenticationConfiguration as documented in + // https://kubernetes.io/docs/reference/access-authn-authz/authentication. + // This is an advanced configuration option. During an end-user login flow, mistakes in this + // configuration will cause the user's login to fail. + // +optional + UserValidationRules []UserValidationRule `json:"userValidationRules,omitempty"` + + // tls is the configuration for communicating with the OIDC provider via TLS. // +optional TLS *TLSSpec `json:"tls,omitempty"` } +// ClaimValidationRule provides the configuration for a single claim validation rule. +type ClaimValidationRule struct { + // claim is the name of a required claim. + // Same as --oidc-required-claim flag. + // Only string claim keys are supported. + // Mutually exclusive with expression and message. + // +optional + Claim string `json:"claim,omitempty"` + + // requiredValue is the value of a required claim. + // Same as --oidc-required-claim flag. + // 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. + // Mutually exclusive with expression and message. + // +optional + 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.foo.bar'. + // 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"` +} + +// 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"` +} + // JWTTokenClaims allows customization of the claims that will be mapped to user identity // for Kubernetes access. type JWTTokenClaims struct { - // Groups is the name of the claim which should be read to extract the user's + // groups is the name of the claim which should be read to extract the user's // group membership from the JWT token. When not specified, it will default to "groups". // +optional Groups string `json:"groups"` - // Username is the name of the claim which should be read to extract the + // username is the name of the claim which should be read to extract the // username from the JWT token. When not specified, it will default to "username". // +optional Username string `json:"username"` + + // extra is similar to claimMappings.extra from Kubernetes AuthenticationConfiguration as documented in + // https://kubernetes.io/docs/reference/access-authn-authz/authentication. However, note that the + // Pinniped Concierge issues client certificates to users for the purpose of authenticating, and + // the Kubernetes API server does not have any mechanism for transmitting auth extras via client + // certificates. When configured, these extras will appear in client certificates issued by the + // Pinniped Supervisor in the x509 Subject field as Organizational Units (OU). However, when this + // client certificate is presented to Kubernetes for authentication, Kubernetes will ignore these + // extras. This is probably only useful if you are using a custom authenticating proxy in front + // of your Kubernetes API server which can translate these OUs into auth extras, as described by + // https://kubernetes.io/docs/reference/access-authn-authz/authentication/#authenticating-proxy. + // This is an advanced configuration option. During an end-user login flow, each of these CEL expressions + // must evaluate to either a string or an array of strings, or else the user's login will fail. + // +optional + Extra []ExtraMapping `json:"extra,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 to be unique. + // +required + 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.foo.bar'. + // + // Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ + // + // +required + ValueExpression string `json:"valueExpression"` } // JWTAuthenticator describes the configuration of a JWT authenticator. @@ -86,14 +208,14 @@ type JWTAuthenticator struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` - // Spec for configuring the authenticator. + // spec for configuring the authenticator. Spec JWTAuthenticatorSpec `json:"spec"` - // Status of the authenticator. + // status of the authenticator. Status JWTAuthenticatorStatus `json:"status,omitempty"` } -// List of JWTAuthenticator objects. +// JWTAuthenticatorList is a list of JWTAuthenticator objects. // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object type JWTAuthenticatorList struct { metav1.TypeMeta `json:",inline"` diff --git a/deploy/concierge/authentication.concierge.pinniped.dev_jwtauthenticators.yaml b/deploy/concierge/authentication.concierge.pinniped.dev_jwtauthenticators.yaml index 2ef6312a0..cbb567616 100644 --- a/deploy/concierge/authentication.concierge.pinniped.dev_jwtauthenticators.yaml +++ b/deploy/concierge/authentication.concierge.pinniped.dev_jwtauthenticators.yaml @@ -58,37 +58,133 @@ spec: metadata: type: object spec: - description: Spec for configuring the authenticator. + description: spec for configuring the authenticator. properties: audience: - description: Audience is the required value of the "aud" JWT claim. + description: audience is the required value of the "aud" JWT claim. minLength: 1 type: string + claimValidationRules: + description: |- + claimValidationRules are rules that are applied to validate token claims to authenticate users. + This is similar to claimValidationRules from Kubernetes AuthenticationConfiguration as documented in + https://kubernetes.io/docs/reference/access-authn-authz/authentication. + This is an advanced configuration option. During an end-user login flow, mistakes in this + configuration will cause the user's login to fail. + items: + description: ClaimValidationRule provides the configuration for + a single claim validation rule. + properties: + claim: + description: |- + claim is the name of a required claim. + Same as --oidc-required-claim flag. + Only string claim keys are supported. + Mutually exclusive with expression and message. + type: string + expression: + description: |- + 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.foo.bar'. + 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. + type: string + message: + description: |- + message customizes the returned error message when expression returns false. + message is a literal string. + Mutually exclusive with claim and requiredValue. + type: string + requiredValue: + description: |- + requiredValue is the value of a required claim. + Same as --oidc-required-claim flag. + 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. + Mutually exclusive with expression and message. + type: string + type: object + type: array claims: description: |- - Claims allows customization of the claims that will be mapped to user identity + claims allows customization of the claims that will be mapped to user identity for Kubernetes access. properties: + extra: + description: |- + extra is similar to claimMappings.extra from Kubernetes AuthenticationConfiguration as documented in + https://kubernetes.io/docs/reference/access-authn-authz/authentication. However, note that the + Pinniped Concierge issues client certificates to users for the purpose of authenticating, and + the Kubernetes API server does not have any mechanism for transmitting auth extras via client + certificates. When configured, these extras will appear in client certificates issued by the + Pinniped Supervisor in the x509 Subject field as Organizational Units (OU). However, when this + client certificate is presented to Kubernetes for authentication, Kubernetes will ignore these + extras. This is probably only useful if you are using a custom authenticating proxy in front + of your Kubernetes API server which can translate these OUs into auth extras, as described by + https://kubernetes.io/docs/reference/access-authn-authz/authentication/#authenticating-proxy. + This is an advanced configuration option. During an end-user login flow, each of these CEL expressions + must evaluate to either a string or an array of strings, or else the user's login will fail. + items: + description: ExtraMapping provides the configuration for a single + extra mapping. + properties: + key: + description: |- + 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 to be unique. + type: string + valueExpression: + description: |- + 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.foo.bar'. + + Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ + type: string + required: + - key + - valueExpression + type: object + type: array groups: description: |- - Groups is the name of the claim which should be read to extract the user's + groups is the name of the claim which should be read to extract the user's group membership from the JWT token. When not specified, it will default to "groups". type: string username: description: |- - Username is the name of the claim which should be read to extract the + username is the name of the claim which should be read to extract the username from the JWT token. When not specified, it will default to "username". type: string type: object issuer: description: |- - Issuer is the OIDC issuer URL that will be used to discover public signing keys. Issuer is + issuer is the OIDC issuer URL that will be used to discover public signing keys. Issuer is also used to validate the "iss" JWT claim. minLength: 1 pattern: ^https:// type: string tls: - description: TLS configuration for communicating with the OIDC provider. + description: tls is the configuration for communicating with the OIDC + provider via TLS. properties: certificateAuthorityData: description: X.509 Certificate Authority (base64-encoded PEM bundle). @@ -128,12 +224,47 @@ spec: - name type: object type: object + userValidationRules: + description: |- + 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. + This is similar to claimValidationRules from Kubernetes AuthenticationConfiguration as documented in + https://kubernetes.io/docs/reference/access-authn-authz/authentication. + This is an advanced configuration option. During an end-user login flow, mistakes in this + configuration will cause the user's login to fail. + items: + description: UserValidationRule provides the configuration for a + single user info validation rule. + properties: + expression: + description: |- + 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/ + type: string + message: + description: |- + message customizes the returned error message when rule returns false. + message is a literal string. + type: string + required: + - expression + type: object + type: array required: - audience - issuer type: object status: - description: Status of the authenticator. + description: status of the authenticator. properties: conditions: description: Represents the observations of the authenticator's current diff --git a/generated/1.26/README.adoc b/generated/1.26/README.adoc index 13eaefb76..14bb3c853 100644 --- a/generated/1.26/README.adoc +++ b/generated/1.26/README.adoc @@ -60,6 +60,79 @@ certificate bundle. + |=== +[id="{anchor_prefix}-go-pinniped-dev-generated-1-26-apis-concierge-authentication-v1alpha1-claimvalidationrule"] +==== ClaimValidationRule + +ClaimValidationRule provides the configuration for a single claim validation rule. + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-26-apis-concierge-authentication-v1alpha1-jwtauthenticatorspec[$$JWTAuthenticatorSpec$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description +| *`claim`* __string__ | claim is the name of a required claim. + +Same as --oidc-required-claim flag. + +Only string claim keys are supported. + +Mutually exclusive with expression and message. + +| *`requiredValue`* __string__ | requiredValue is the value of a required claim. + +Same as --oidc-required-claim flag. + +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. + +Mutually exclusive with expression and message. + +| *`expression`* __string__ | 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.foo.bar'. + +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. + +| *`message`* __string__ | message customizes the returned error message when expression returns false. + +message is a literal string. + +Mutually exclusive with claim and requiredValue. + +|=== + + +[id="{anchor_prefix}-go-pinniped-dev-generated-1-26-apis-concierge-authentication-v1alpha1-extramapping"] +==== ExtraMapping + +ExtraMapping provides the configuration for a single extra mapping. + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-26-apis-concierge-authentication-v1alpha1-jwttokenclaims[$$JWTTokenClaims$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description +| *`key`* __string__ | 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 to be unique. + +| *`valueExpression`* __string__ | 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.foo.bar'. + + +Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ + +|=== + + [id="{anchor_prefix}-go-pinniped-dev-generated-1-26-apis-concierge-authentication-v1alpha1-jwtauthenticator"] ==== JWTAuthenticator @@ -78,8 +151,8 @@ signature, existence of claims, etc.) and extract the username and groups from t | Field | Description | *`metadata`* __link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.26/#objectmeta-v1-meta[$$ObjectMeta$$]__ | Refer to Kubernetes API documentation for fields of `metadata`. -| *`spec`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-26-apis-concierge-authentication-v1alpha1-jwtauthenticatorspec[$$JWTAuthenticatorSpec$$]__ | Spec for configuring the authenticator. + -| *`status`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-26-apis-concierge-authentication-v1alpha1-jwtauthenticatorstatus[$$JWTAuthenticatorStatus$$]__ | Status of the authenticator. + +| *`spec`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-26-apis-concierge-authentication-v1alpha1-jwtauthenticatorspec[$$JWTAuthenticatorSpec$$]__ | spec for configuring the authenticator. + +| *`status`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-26-apis-concierge-authentication-v1alpha1-jwtauthenticatorstatus[$$JWTAuthenticatorStatus$$]__ | status of the authenticator. + |=== @@ -100,7 +173,7 @@ signature, existence of claims, etc.) and extract the username and groups from t [id="{anchor_prefix}-go-pinniped-dev-generated-1-26-apis-concierge-authentication-v1alpha1-jwtauthenticatorspec"] ==== JWTAuthenticatorSpec -Spec for configuring a JWT authenticator. +JWTAuthenticatorSpec is the spec for configuring a JWT authenticator. .Appears In: **** @@ -110,19 +183,32 @@ Spec for configuring a JWT authenticator. [cols="25a,75a", options="header"] |=== | Field | Description -| *`issuer`* __string__ | Issuer is the OIDC issuer URL that will be used to discover public signing keys. Issuer is + +| *`issuer`* __string__ | issuer is the OIDC issuer URL that will be used to discover public signing keys. Issuer is + also used to validate the "iss" JWT claim. + -| *`audience`* __string__ | Audience is the required value of the "aud" JWT claim. + -| *`claims`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-26-apis-concierge-authentication-v1alpha1-jwttokenclaims[$$JWTTokenClaims$$]__ | Claims allows customization of the claims that will be mapped to user identity + +| *`audience`* __string__ | audience is the required value of the "aud" JWT claim. + +| *`claims`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-26-apis-concierge-authentication-v1alpha1-jwttokenclaims[$$JWTTokenClaims$$]__ | claims allows customization of the claims that will be mapped to user identity + for Kubernetes access. + -| *`tls`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-26-apis-concierge-authentication-v1alpha1-tlsspec[$$TLSSpec$$]__ | TLS configuration for communicating with the OIDC provider. + +| *`claimValidationRules`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-26-apis-concierge-authentication-v1alpha1-claimvalidationrule[$$ClaimValidationRule$$] array__ | claimValidationRules are rules that are applied to validate token claims to authenticate users. + +This is similar to claimValidationRules from Kubernetes AuthenticationConfiguration as documented in + +https://kubernetes.io/docs/reference/access-authn-authz/authentication. + +This is an advanced configuration option. During an end-user login flow, mistakes in this + +configuration will cause the user's login to fail. + +| *`userValidationRules`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-26-apis-concierge-authentication-v1alpha1-uservalidationrule[$$UserValidationRule$$] array__ | 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. + +This is similar to claimValidationRules from Kubernetes AuthenticationConfiguration as documented in + +https://kubernetes.io/docs/reference/access-authn-authz/authentication. + +This is an advanced configuration option. During an end-user login flow, mistakes in this + +configuration will cause the user's login to fail. + +| *`tls`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-26-apis-concierge-authentication-v1alpha1-tlsspec[$$TLSSpec$$]__ | tls is the configuration for communicating with the OIDC provider via TLS. + |=== [id="{anchor_prefix}-go-pinniped-dev-generated-1-26-apis-concierge-authentication-v1alpha1-jwtauthenticatorstatus"] ==== JWTAuthenticatorStatus -Status of a JWT authenticator. +JWTAuthenticatorStatus is the status of a JWT authenticator. .Appears In: **** @@ -151,10 +237,22 @@ for Kubernetes access. [cols="25a,75a", options="header"] |=== | Field | Description -| *`groups`* __string__ | Groups is the name of the claim which should be read to extract the user's + +| *`groups`* __string__ | groups is the name of the claim which should be read to extract the user's + group membership from the JWT token. When not specified, it will default to "groups". + -| *`username`* __string__ | Username is the name of the claim which should be read to extract the + +| *`username`* __string__ | username is the name of the claim which should be read to extract the + username from the JWT token. When not specified, it will default to "username". + +| *`extra`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-26-apis-concierge-authentication-v1alpha1-extramapping[$$ExtraMapping$$] array__ | extra is similar to claimMappings.extra from Kubernetes AuthenticationConfiguration as documented in + +https://kubernetes.io/docs/reference/access-authn-authz/authentication. However, note that the + +Pinniped Concierge issues client certificates to users for the purpose of authenticating, and + +the Kubernetes API server does not have any mechanism for transmitting auth extras via client + +certificates. When configured, these extras will appear in client certificates issued by the + +Pinniped Supervisor in the x509 Subject field as Organizational Units (OU). However, when this + +client certificate is presented to Kubernetes for authentication, Kubernetes will ignore these + +extras. This is probably only useful if you are using a custom authenticating proxy in front + +of your Kubernetes API server which can translate these OUs into auth extras, as described by + +https://kubernetes.io/docs/reference/access-authn-authz/authentication/#authenticating-proxy. + +This is an advanced configuration option. During an end-user login flow, each of these CEL expressions + +must evaluate to either a string or an array of strings, or else the user's login will fail. + |=== @@ -178,6 +276,33 @@ Any changes to the CA bundle in the secret or configmap will be dynamically relo |=== +[id="{anchor_prefix}-go-pinniped-dev-generated-1-26-apis-concierge-authentication-v1alpha1-uservalidationrule"] +==== UserValidationRule + +UserValidationRule provides the configuration for a single user info validation rule. + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-26-apis-concierge-authentication-v1alpha1-jwtauthenticatorspec[$$JWTAuthenticatorSpec$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description +| *`expression`* __string__ | 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/ + +| *`message`* __string__ | message customizes the returned error message when rule returns false. + +message is a literal string. + +|=== + + [id="{anchor_prefix}-go-pinniped-dev-generated-1-26-apis-concierge-authentication-v1alpha1-webhookauthenticator"] ==== WebhookAuthenticator diff --git a/generated/1.26/apis/concierge/authentication/v1alpha1/types_jwtauthenticator.go b/generated/1.26/apis/concierge/authentication/v1alpha1/types_jwtauthenticator.go index a3b01c696..d5149642f 100644 --- a/generated/1.26/apis/concierge/authentication/v1alpha1/types_jwtauthenticator.go +++ b/generated/1.26/apis/concierge/authentication/v1alpha1/types_jwtauthenticator.go @@ -18,7 +18,7 @@ const ( JWTAuthenticatorPhaseError JWTAuthenticatorPhase = "Error" ) -// Status of a JWT authenticator. +// JWTAuthenticatorStatus is the status of a JWT authenticator. type JWTAuthenticatorStatus struct { // Represents the observations of the authenticator's current state. // +patchMergeKey=type @@ -26,46 +26,168 @@ type JWTAuthenticatorStatus struct { // +listType=map // +listMapKey=type Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"` + // Phase summarizes the overall status of the JWTAuthenticator. // +kubebuilder:default=Pending // +kubebuilder:validation:Enum=Pending;Ready;Error Phase JWTAuthenticatorPhase `json:"phase,omitempty"` } -// Spec for configuring a JWT authenticator. +// JWTAuthenticatorSpec is the spec for configuring a JWT authenticator. type JWTAuthenticatorSpec struct { - // Issuer is the OIDC issuer URL that will be used to discover public signing keys. Issuer is + // issuer is the OIDC issuer URL that will be used to discover public signing keys. Issuer is // also used to validate the "iss" JWT claim. // +kubebuilder:validation:MinLength=1 // +kubebuilder:validation:Pattern=`^https://` Issuer string `json:"issuer"` - // Audience is the required value of the "aud" JWT claim. + // audience is the required value of the "aud" JWT claim. // +kubebuilder:validation:MinLength=1 Audience string `json:"audience"` - // Claims allows customization of the claims that will be mapped to user identity + // claims allows customization of the claims that will be mapped to user identity // for Kubernetes access. // +optional Claims JWTTokenClaims `json:"claims"` - // TLS configuration for communicating with the OIDC provider. + // claimValidationRules are rules that are applied to validate token claims to authenticate users. + // This is similar to claimValidationRules from Kubernetes AuthenticationConfiguration as documented in + // https://kubernetes.io/docs/reference/access-authn-authz/authentication. + // This is an advanced configuration option. During an end-user login flow, mistakes in this + // configuration will cause the user's login to fail. + // +optional + ClaimValidationRules []ClaimValidationRule `json:"claimValidationRules,omitempty"` + + // 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. + // This is similar to claimValidationRules from Kubernetes AuthenticationConfiguration as documented in + // https://kubernetes.io/docs/reference/access-authn-authz/authentication. + // This is an advanced configuration option. During an end-user login flow, mistakes in this + // configuration will cause the user's login to fail. + // +optional + UserValidationRules []UserValidationRule `json:"userValidationRules,omitempty"` + + // tls is the configuration for communicating with the OIDC provider via TLS. // +optional TLS *TLSSpec `json:"tls,omitempty"` } +// ClaimValidationRule provides the configuration for a single claim validation rule. +type ClaimValidationRule struct { + // claim is the name of a required claim. + // Same as --oidc-required-claim flag. + // Only string claim keys are supported. + // Mutually exclusive with expression and message. + // +optional + Claim string `json:"claim,omitempty"` + + // requiredValue is the value of a required claim. + // Same as --oidc-required-claim flag. + // 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. + // Mutually exclusive with expression and message. + // +optional + 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.foo.bar'. + // 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"` +} + +// 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"` +} + // JWTTokenClaims allows customization of the claims that will be mapped to user identity // for Kubernetes access. type JWTTokenClaims struct { - // Groups is the name of the claim which should be read to extract the user's + // groups is the name of the claim which should be read to extract the user's // group membership from the JWT token. When not specified, it will default to "groups". // +optional Groups string `json:"groups"` - // Username is the name of the claim which should be read to extract the + // username is the name of the claim which should be read to extract the // username from the JWT token. When not specified, it will default to "username". // +optional Username string `json:"username"` + + // extra is similar to claimMappings.extra from Kubernetes AuthenticationConfiguration as documented in + // https://kubernetes.io/docs/reference/access-authn-authz/authentication. However, note that the + // Pinniped Concierge issues client certificates to users for the purpose of authenticating, and + // the Kubernetes API server does not have any mechanism for transmitting auth extras via client + // certificates. When configured, these extras will appear in client certificates issued by the + // Pinniped Supervisor in the x509 Subject field as Organizational Units (OU). However, when this + // client certificate is presented to Kubernetes for authentication, Kubernetes will ignore these + // extras. This is probably only useful if you are using a custom authenticating proxy in front + // of your Kubernetes API server which can translate these OUs into auth extras, as described by + // https://kubernetes.io/docs/reference/access-authn-authz/authentication/#authenticating-proxy. + // This is an advanced configuration option. During an end-user login flow, each of these CEL expressions + // must evaluate to either a string or an array of strings, or else the user's login will fail. + // +optional + Extra []ExtraMapping `json:"extra,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 to be unique. + // +required + 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.foo.bar'. + // + // Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ + // + // +required + ValueExpression string `json:"valueExpression"` } // JWTAuthenticator describes the configuration of a JWT authenticator. @@ -86,14 +208,14 @@ type JWTAuthenticator struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` - // Spec for configuring the authenticator. + // spec for configuring the authenticator. Spec JWTAuthenticatorSpec `json:"spec"` - // Status of the authenticator. + // status of the authenticator. Status JWTAuthenticatorStatus `json:"status,omitempty"` } -// List of JWTAuthenticator objects. +// JWTAuthenticatorList is a list of JWTAuthenticator objects. // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object type JWTAuthenticatorList struct { metav1.TypeMeta `json:",inline"` diff --git a/generated/1.26/apis/concierge/authentication/v1alpha1/zz_generated.deepcopy.go b/generated/1.26/apis/concierge/authentication/v1alpha1/zz_generated.deepcopy.go index 6b1235b88..cf8c0e61c 100644 --- a/generated/1.26/apis/concierge/authentication/v1alpha1/zz_generated.deepcopy.go +++ b/generated/1.26/apis/concierge/authentication/v1alpha1/zz_generated.deepcopy.go @@ -29,6 +29,38 @@ func (in *CertificateAuthorityDataSourceSpec) DeepCopy() *CertificateAuthorityDa return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ClaimValidationRule) DeepCopyInto(out *ClaimValidationRule) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClaimValidationRule. +func (in *ClaimValidationRule) DeepCopy() *ClaimValidationRule { + if in == nil { + return nil + } + out := new(ClaimValidationRule) + in.DeepCopyInto(out) + return out +} + +// 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. func (in *JWTAuthenticator) DeepCopyInto(out *JWTAuthenticator) { *out = *in @@ -93,7 +125,17 @@ func (in *JWTAuthenticatorList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *JWTAuthenticatorSpec) DeepCopyInto(out *JWTAuthenticatorSpec) { *out = *in - out.Claims = in.Claims + in.Claims.DeepCopyInto(&out.Claims) + if in.ClaimValidationRules != nil { + in, out := &in.ClaimValidationRules, &out.ClaimValidationRules + *out = make([]ClaimValidationRule, len(*in)) + copy(*out, *in) + } + if in.UserValidationRules != nil { + in, out := &in.UserValidationRules, &out.UserValidationRules + *out = make([]UserValidationRule, len(*in)) + copy(*out, *in) + } if in.TLS != nil { in, out := &in.TLS, &out.TLS *out = new(TLSSpec) @@ -138,6 +180,11 @@ func (in *JWTAuthenticatorStatus) DeepCopy() *JWTAuthenticatorStatus { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *JWTTokenClaims) DeepCopyInto(out *JWTTokenClaims) { *out = *in + if in.Extra != nil { + in, out := &in.Extra, &out.Extra + *out = make([]ExtraMapping, len(*in)) + copy(*out, *in) + } return } @@ -172,6 +219,22 @@ func (in *TLSSpec) DeepCopy() *TLSSpec { 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. func (in *WebhookAuthenticator) DeepCopyInto(out *WebhookAuthenticator) { *out = *in diff --git a/generated/1.26/crds/authentication.concierge.pinniped.dev_jwtauthenticators.yaml b/generated/1.26/crds/authentication.concierge.pinniped.dev_jwtauthenticators.yaml index 2ef6312a0..cbb567616 100644 --- a/generated/1.26/crds/authentication.concierge.pinniped.dev_jwtauthenticators.yaml +++ b/generated/1.26/crds/authentication.concierge.pinniped.dev_jwtauthenticators.yaml @@ -58,37 +58,133 @@ spec: metadata: type: object spec: - description: Spec for configuring the authenticator. + description: spec for configuring the authenticator. properties: audience: - description: Audience is the required value of the "aud" JWT claim. + description: audience is the required value of the "aud" JWT claim. minLength: 1 type: string + claimValidationRules: + description: |- + claimValidationRules are rules that are applied to validate token claims to authenticate users. + This is similar to claimValidationRules from Kubernetes AuthenticationConfiguration as documented in + https://kubernetes.io/docs/reference/access-authn-authz/authentication. + This is an advanced configuration option. During an end-user login flow, mistakes in this + configuration will cause the user's login to fail. + items: + description: ClaimValidationRule provides the configuration for + a single claim validation rule. + properties: + claim: + description: |- + claim is the name of a required claim. + Same as --oidc-required-claim flag. + Only string claim keys are supported. + Mutually exclusive with expression and message. + type: string + expression: + description: |- + 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.foo.bar'. + 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. + type: string + message: + description: |- + message customizes the returned error message when expression returns false. + message is a literal string. + Mutually exclusive with claim and requiredValue. + type: string + requiredValue: + description: |- + requiredValue is the value of a required claim. + Same as --oidc-required-claim flag. + 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. + Mutually exclusive with expression and message. + type: string + type: object + type: array claims: description: |- - Claims allows customization of the claims that will be mapped to user identity + claims allows customization of the claims that will be mapped to user identity for Kubernetes access. properties: + extra: + description: |- + extra is similar to claimMappings.extra from Kubernetes AuthenticationConfiguration as documented in + https://kubernetes.io/docs/reference/access-authn-authz/authentication. However, note that the + Pinniped Concierge issues client certificates to users for the purpose of authenticating, and + the Kubernetes API server does not have any mechanism for transmitting auth extras via client + certificates. When configured, these extras will appear in client certificates issued by the + Pinniped Supervisor in the x509 Subject field as Organizational Units (OU). However, when this + client certificate is presented to Kubernetes for authentication, Kubernetes will ignore these + extras. This is probably only useful if you are using a custom authenticating proxy in front + of your Kubernetes API server which can translate these OUs into auth extras, as described by + https://kubernetes.io/docs/reference/access-authn-authz/authentication/#authenticating-proxy. + This is an advanced configuration option. During an end-user login flow, each of these CEL expressions + must evaluate to either a string or an array of strings, or else the user's login will fail. + items: + description: ExtraMapping provides the configuration for a single + extra mapping. + properties: + key: + description: |- + 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 to be unique. + type: string + valueExpression: + description: |- + 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.foo.bar'. + + Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ + type: string + required: + - key + - valueExpression + type: object + type: array groups: description: |- - Groups is the name of the claim which should be read to extract the user's + groups is the name of the claim which should be read to extract the user's group membership from the JWT token. When not specified, it will default to "groups". type: string username: description: |- - Username is the name of the claim which should be read to extract the + username is the name of the claim which should be read to extract the username from the JWT token. When not specified, it will default to "username". type: string type: object issuer: description: |- - Issuer is the OIDC issuer URL that will be used to discover public signing keys. Issuer is + issuer is the OIDC issuer URL that will be used to discover public signing keys. Issuer is also used to validate the "iss" JWT claim. minLength: 1 pattern: ^https:// type: string tls: - description: TLS configuration for communicating with the OIDC provider. + description: tls is the configuration for communicating with the OIDC + provider via TLS. properties: certificateAuthorityData: description: X.509 Certificate Authority (base64-encoded PEM bundle). @@ -128,12 +224,47 @@ spec: - name type: object type: object + userValidationRules: + description: |- + 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. + This is similar to claimValidationRules from Kubernetes AuthenticationConfiguration as documented in + https://kubernetes.io/docs/reference/access-authn-authz/authentication. + This is an advanced configuration option. During an end-user login flow, mistakes in this + configuration will cause the user's login to fail. + items: + description: UserValidationRule provides the configuration for a + single user info validation rule. + properties: + expression: + description: |- + 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/ + type: string + message: + description: |- + message customizes the returned error message when rule returns false. + message is a literal string. + type: string + required: + - expression + type: object + type: array required: - audience - issuer type: object status: - description: Status of the authenticator. + description: status of the authenticator. properties: conditions: description: Represents the observations of the authenticator's current diff --git a/generated/1.27/README.adoc b/generated/1.27/README.adoc index 2c68468cc..ed8964abf 100644 --- a/generated/1.27/README.adoc +++ b/generated/1.27/README.adoc @@ -60,6 +60,79 @@ certificate bundle. + |=== +[id="{anchor_prefix}-go-pinniped-dev-generated-1-27-apis-concierge-authentication-v1alpha1-claimvalidationrule"] +==== ClaimValidationRule + +ClaimValidationRule provides the configuration for a single claim validation rule. + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-27-apis-concierge-authentication-v1alpha1-jwtauthenticatorspec[$$JWTAuthenticatorSpec$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description +| *`claim`* __string__ | claim is the name of a required claim. + +Same as --oidc-required-claim flag. + +Only string claim keys are supported. + +Mutually exclusive with expression and message. + +| *`requiredValue`* __string__ | requiredValue is the value of a required claim. + +Same as --oidc-required-claim flag. + +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. + +Mutually exclusive with expression and message. + +| *`expression`* __string__ | 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.foo.bar'. + +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. + +| *`message`* __string__ | message customizes the returned error message when expression returns false. + +message is a literal string. + +Mutually exclusive with claim and requiredValue. + +|=== + + +[id="{anchor_prefix}-go-pinniped-dev-generated-1-27-apis-concierge-authentication-v1alpha1-extramapping"] +==== ExtraMapping + +ExtraMapping provides the configuration for a single extra mapping. + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-27-apis-concierge-authentication-v1alpha1-jwttokenclaims[$$JWTTokenClaims$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description +| *`key`* __string__ | 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 to be unique. + +| *`valueExpression`* __string__ | 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.foo.bar'. + + +Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ + +|=== + + [id="{anchor_prefix}-go-pinniped-dev-generated-1-27-apis-concierge-authentication-v1alpha1-jwtauthenticator"] ==== JWTAuthenticator @@ -78,8 +151,8 @@ signature, existence of claims, etc.) and extract the username and groups from t | Field | Description | *`metadata`* __link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.27/#objectmeta-v1-meta[$$ObjectMeta$$]__ | Refer to Kubernetes API documentation for fields of `metadata`. -| *`spec`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-27-apis-concierge-authentication-v1alpha1-jwtauthenticatorspec[$$JWTAuthenticatorSpec$$]__ | Spec for configuring the authenticator. + -| *`status`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-27-apis-concierge-authentication-v1alpha1-jwtauthenticatorstatus[$$JWTAuthenticatorStatus$$]__ | Status of the authenticator. + +| *`spec`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-27-apis-concierge-authentication-v1alpha1-jwtauthenticatorspec[$$JWTAuthenticatorSpec$$]__ | spec for configuring the authenticator. + +| *`status`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-27-apis-concierge-authentication-v1alpha1-jwtauthenticatorstatus[$$JWTAuthenticatorStatus$$]__ | status of the authenticator. + |=== @@ -100,7 +173,7 @@ signature, existence of claims, etc.) and extract the username and groups from t [id="{anchor_prefix}-go-pinniped-dev-generated-1-27-apis-concierge-authentication-v1alpha1-jwtauthenticatorspec"] ==== JWTAuthenticatorSpec -Spec for configuring a JWT authenticator. +JWTAuthenticatorSpec is the spec for configuring a JWT authenticator. .Appears In: **** @@ -110,19 +183,32 @@ Spec for configuring a JWT authenticator. [cols="25a,75a", options="header"] |=== | Field | Description -| *`issuer`* __string__ | Issuer is the OIDC issuer URL that will be used to discover public signing keys. Issuer is + +| *`issuer`* __string__ | issuer is the OIDC issuer URL that will be used to discover public signing keys. Issuer is + also used to validate the "iss" JWT claim. + -| *`audience`* __string__ | Audience is the required value of the "aud" JWT claim. + -| *`claims`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-27-apis-concierge-authentication-v1alpha1-jwttokenclaims[$$JWTTokenClaims$$]__ | Claims allows customization of the claims that will be mapped to user identity + +| *`audience`* __string__ | audience is the required value of the "aud" JWT claim. + +| *`claims`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-27-apis-concierge-authentication-v1alpha1-jwttokenclaims[$$JWTTokenClaims$$]__ | claims allows customization of the claims that will be mapped to user identity + for Kubernetes access. + -| *`tls`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-27-apis-concierge-authentication-v1alpha1-tlsspec[$$TLSSpec$$]__ | TLS configuration for communicating with the OIDC provider. + +| *`claimValidationRules`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-27-apis-concierge-authentication-v1alpha1-claimvalidationrule[$$ClaimValidationRule$$] array__ | claimValidationRules are rules that are applied to validate token claims to authenticate users. + +This is similar to claimValidationRules from Kubernetes AuthenticationConfiguration as documented in + +https://kubernetes.io/docs/reference/access-authn-authz/authentication. + +This is an advanced configuration option. During an end-user login flow, mistakes in this + +configuration will cause the user's login to fail. + +| *`userValidationRules`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-27-apis-concierge-authentication-v1alpha1-uservalidationrule[$$UserValidationRule$$] array__ | 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. + +This is similar to claimValidationRules from Kubernetes AuthenticationConfiguration as documented in + +https://kubernetes.io/docs/reference/access-authn-authz/authentication. + +This is an advanced configuration option. During an end-user login flow, mistakes in this + +configuration will cause the user's login to fail. + +| *`tls`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-27-apis-concierge-authentication-v1alpha1-tlsspec[$$TLSSpec$$]__ | tls is the configuration for communicating with the OIDC provider via TLS. + |=== [id="{anchor_prefix}-go-pinniped-dev-generated-1-27-apis-concierge-authentication-v1alpha1-jwtauthenticatorstatus"] ==== JWTAuthenticatorStatus -Status of a JWT authenticator. +JWTAuthenticatorStatus is the status of a JWT authenticator. .Appears In: **** @@ -151,10 +237,22 @@ for Kubernetes access. [cols="25a,75a", options="header"] |=== | Field | Description -| *`groups`* __string__ | Groups is the name of the claim which should be read to extract the user's + +| *`groups`* __string__ | groups is the name of the claim which should be read to extract the user's + group membership from the JWT token. When not specified, it will default to "groups". + -| *`username`* __string__ | Username is the name of the claim which should be read to extract the + +| *`username`* __string__ | username is the name of the claim which should be read to extract the + username from the JWT token. When not specified, it will default to "username". + +| *`extra`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-27-apis-concierge-authentication-v1alpha1-extramapping[$$ExtraMapping$$] array__ | extra is similar to claimMappings.extra from Kubernetes AuthenticationConfiguration as documented in + +https://kubernetes.io/docs/reference/access-authn-authz/authentication. However, note that the + +Pinniped Concierge issues client certificates to users for the purpose of authenticating, and + +the Kubernetes API server does not have any mechanism for transmitting auth extras via client + +certificates. When configured, these extras will appear in client certificates issued by the + +Pinniped Supervisor in the x509 Subject field as Organizational Units (OU). However, when this + +client certificate is presented to Kubernetes for authentication, Kubernetes will ignore these + +extras. This is probably only useful if you are using a custom authenticating proxy in front + +of your Kubernetes API server which can translate these OUs into auth extras, as described by + +https://kubernetes.io/docs/reference/access-authn-authz/authentication/#authenticating-proxy. + +This is an advanced configuration option. During an end-user login flow, each of these CEL expressions + +must evaluate to either a string or an array of strings, or else the user's login will fail. + |=== @@ -178,6 +276,33 @@ Any changes to the CA bundle in the secret or configmap will be dynamically relo |=== +[id="{anchor_prefix}-go-pinniped-dev-generated-1-27-apis-concierge-authentication-v1alpha1-uservalidationrule"] +==== UserValidationRule + +UserValidationRule provides the configuration for a single user info validation rule. + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-27-apis-concierge-authentication-v1alpha1-jwtauthenticatorspec[$$JWTAuthenticatorSpec$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description +| *`expression`* __string__ | 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/ + +| *`message`* __string__ | message customizes the returned error message when rule returns false. + +message is a literal string. + +|=== + + [id="{anchor_prefix}-go-pinniped-dev-generated-1-27-apis-concierge-authentication-v1alpha1-webhookauthenticator"] ==== WebhookAuthenticator diff --git a/generated/1.27/apis/concierge/authentication/v1alpha1/types_jwtauthenticator.go b/generated/1.27/apis/concierge/authentication/v1alpha1/types_jwtauthenticator.go index a3b01c696..d5149642f 100644 --- a/generated/1.27/apis/concierge/authentication/v1alpha1/types_jwtauthenticator.go +++ b/generated/1.27/apis/concierge/authentication/v1alpha1/types_jwtauthenticator.go @@ -18,7 +18,7 @@ const ( JWTAuthenticatorPhaseError JWTAuthenticatorPhase = "Error" ) -// Status of a JWT authenticator. +// JWTAuthenticatorStatus is the status of a JWT authenticator. type JWTAuthenticatorStatus struct { // Represents the observations of the authenticator's current state. // +patchMergeKey=type @@ -26,46 +26,168 @@ type JWTAuthenticatorStatus struct { // +listType=map // +listMapKey=type Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"` + // Phase summarizes the overall status of the JWTAuthenticator. // +kubebuilder:default=Pending // +kubebuilder:validation:Enum=Pending;Ready;Error Phase JWTAuthenticatorPhase `json:"phase,omitempty"` } -// Spec for configuring a JWT authenticator. +// JWTAuthenticatorSpec is the spec for configuring a JWT authenticator. type JWTAuthenticatorSpec struct { - // Issuer is the OIDC issuer URL that will be used to discover public signing keys. Issuer is + // issuer is the OIDC issuer URL that will be used to discover public signing keys. Issuer is // also used to validate the "iss" JWT claim. // +kubebuilder:validation:MinLength=1 // +kubebuilder:validation:Pattern=`^https://` Issuer string `json:"issuer"` - // Audience is the required value of the "aud" JWT claim. + // audience is the required value of the "aud" JWT claim. // +kubebuilder:validation:MinLength=1 Audience string `json:"audience"` - // Claims allows customization of the claims that will be mapped to user identity + // claims allows customization of the claims that will be mapped to user identity // for Kubernetes access. // +optional Claims JWTTokenClaims `json:"claims"` - // TLS configuration for communicating with the OIDC provider. + // claimValidationRules are rules that are applied to validate token claims to authenticate users. + // This is similar to claimValidationRules from Kubernetes AuthenticationConfiguration as documented in + // https://kubernetes.io/docs/reference/access-authn-authz/authentication. + // This is an advanced configuration option. During an end-user login flow, mistakes in this + // configuration will cause the user's login to fail. + // +optional + ClaimValidationRules []ClaimValidationRule `json:"claimValidationRules,omitempty"` + + // 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. + // This is similar to claimValidationRules from Kubernetes AuthenticationConfiguration as documented in + // https://kubernetes.io/docs/reference/access-authn-authz/authentication. + // This is an advanced configuration option. During an end-user login flow, mistakes in this + // configuration will cause the user's login to fail. + // +optional + UserValidationRules []UserValidationRule `json:"userValidationRules,omitempty"` + + // tls is the configuration for communicating with the OIDC provider via TLS. // +optional TLS *TLSSpec `json:"tls,omitempty"` } +// ClaimValidationRule provides the configuration for a single claim validation rule. +type ClaimValidationRule struct { + // claim is the name of a required claim. + // Same as --oidc-required-claim flag. + // Only string claim keys are supported. + // Mutually exclusive with expression and message. + // +optional + Claim string `json:"claim,omitempty"` + + // requiredValue is the value of a required claim. + // Same as --oidc-required-claim flag. + // 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. + // Mutually exclusive with expression and message. + // +optional + 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.foo.bar'. + // 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"` +} + +// 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"` +} + // JWTTokenClaims allows customization of the claims that will be mapped to user identity // for Kubernetes access. type JWTTokenClaims struct { - // Groups is the name of the claim which should be read to extract the user's + // groups is the name of the claim which should be read to extract the user's // group membership from the JWT token. When not specified, it will default to "groups". // +optional Groups string `json:"groups"` - // Username is the name of the claim which should be read to extract the + // username is the name of the claim which should be read to extract the // username from the JWT token. When not specified, it will default to "username". // +optional Username string `json:"username"` + + // extra is similar to claimMappings.extra from Kubernetes AuthenticationConfiguration as documented in + // https://kubernetes.io/docs/reference/access-authn-authz/authentication. However, note that the + // Pinniped Concierge issues client certificates to users for the purpose of authenticating, and + // the Kubernetes API server does not have any mechanism for transmitting auth extras via client + // certificates. When configured, these extras will appear in client certificates issued by the + // Pinniped Supervisor in the x509 Subject field as Organizational Units (OU). However, when this + // client certificate is presented to Kubernetes for authentication, Kubernetes will ignore these + // extras. This is probably only useful if you are using a custom authenticating proxy in front + // of your Kubernetes API server which can translate these OUs into auth extras, as described by + // https://kubernetes.io/docs/reference/access-authn-authz/authentication/#authenticating-proxy. + // This is an advanced configuration option. During an end-user login flow, each of these CEL expressions + // must evaluate to either a string or an array of strings, or else the user's login will fail. + // +optional + Extra []ExtraMapping `json:"extra,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 to be unique. + // +required + 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.foo.bar'. + // + // Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ + // + // +required + ValueExpression string `json:"valueExpression"` } // JWTAuthenticator describes the configuration of a JWT authenticator. @@ -86,14 +208,14 @@ type JWTAuthenticator struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` - // Spec for configuring the authenticator. + // spec for configuring the authenticator. Spec JWTAuthenticatorSpec `json:"spec"` - // Status of the authenticator. + // status of the authenticator. Status JWTAuthenticatorStatus `json:"status,omitempty"` } -// List of JWTAuthenticator objects. +// JWTAuthenticatorList is a list of JWTAuthenticator objects. // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object type JWTAuthenticatorList struct { metav1.TypeMeta `json:",inline"` diff --git a/generated/1.27/apis/concierge/authentication/v1alpha1/zz_generated.deepcopy.go b/generated/1.27/apis/concierge/authentication/v1alpha1/zz_generated.deepcopy.go index 6b1235b88..cf8c0e61c 100644 --- a/generated/1.27/apis/concierge/authentication/v1alpha1/zz_generated.deepcopy.go +++ b/generated/1.27/apis/concierge/authentication/v1alpha1/zz_generated.deepcopy.go @@ -29,6 +29,38 @@ func (in *CertificateAuthorityDataSourceSpec) DeepCopy() *CertificateAuthorityDa return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ClaimValidationRule) DeepCopyInto(out *ClaimValidationRule) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClaimValidationRule. +func (in *ClaimValidationRule) DeepCopy() *ClaimValidationRule { + if in == nil { + return nil + } + out := new(ClaimValidationRule) + in.DeepCopyInto(out) + return out +} + +// 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. func (in *JWTAuthenticator) DeepCopyInto(out *JWTAuthenticator) { *out = *in @@ -93,7 +125,17 @@ func (in *JWTAuthenticatorList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *JWTAuthenticatorSpec) DeepCopyInto(out *JWTAuthenticatorSpec) { *out = *in - out.Claims = in.Claims + in.Claims.DeepCopyInto(&out.Claims) + if in.ClaimValidationRules != nil { + in, out := &in.ClaimValidationRules, &out.ClaimValidationRules + *out = make([]ClaimValidationRule, len(*in)) + copy(*out, *in) + } + if in.UserValidationRules != nil { + in, out := &in.UserValidationRules, &out.UserValidationRules + *out = make([]UserValidationRule, len(*in)) + copy(*out, *in) + } if in.TLS != nil { in, out := &in.TLS, &out.TLS *out = new(TLSSpec) @@ -138,6 +180,11 @@ func (in *JWTAuthenticatorStatus) DeepCopy() *JWTAuthenticatorStatus { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *JWTTokenClaims) DeepCopyInto(out *JWTTokenClaims) { *out = *in + if in.Extra != nil { + in, out := &in.Extra, &out.Extra + *out = make([]ExtraMapping, len(*in)) + copy(*out, *in) + } return } @@ -172,6 +219,22 @@ func (in *TLSSpec) DeepCopy() *TLSSpec { 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. func (in *WebhookAuthenticator) DeepCopyInto(out *WebhookAuthenticator) { *out = *in diff --git a/generated/1.27/crds/authentication.concierge.pinniped.dev_jwtauthenticators.yaml b/generated/1.27/crds/authentication.concierge.pinniped.dev_jwtauthenticators.yaml index 2ef6312a0..cbb567616 100644 --- a/generated/1.27/crds/authentication.concierge.pinniped.dev_jwtauthenticators.yaml +++ b/generated/1.27/crds/authentication.concierge.pinniped.dev_jwtauthenticators.yaml @@ -58,37 +58,133 @@ spec: metadata: type: object spec: - description: Spec for configuring the authenticator. + description: spec for configuring the authenticator. properties: audience: - description: Audience is the required value of the "aud" JWT claim. + description: audience is the required value of the "aud" JWT claim. minLength: 1 type: string + claimValidationRules: + description: |- + claimValidationRules are rules that are applied to validate token claims to authenticate users. + This is similar to claimValidationRules from Kubernetes AuthenticationConfiguration as documented in + https://kubernetes.io/docs/reference/access-authn-authz/authentication. + This is an advanced configuration option. During an end-user login flow, mistakes in this + configuration will cause the user's login to fail. + items: + description: ClaimValidationRule provides the configuration for + a single claim validation rule. + properties: + claim: + description: |- + claim is the name of a required claim. + Same as --oidc-required-claim flag. + Only string claim keys are supported. + Mutually exclusive with expression and message. + type: string + expression: + description: |- + 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.foo.bar'. + 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. + type: string + message: + description: |- + message customizes the returned error message when expression returns false. + message is a literal string. + Mutually exclusive with claim and requiredValue. + type: string + requiredValue: + description: |- + requiredValue is the value of a required claim. + Same as --oidc-required-claim flag. + 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. + Mutually exclusive with expression and message. + type: string + type: object + type: array claims: description: |- - Claims allows customization of the claims that will be mapped to user identity + claims allows customization of the claims that will be mapped to user identity for Kubernetes access. properties: + extra: + description: |- + extra is similar to claimMappings.extra from Kubernetes AuthenticationConfiguration as documented in + https://kubernetes.io/docs/reference/access-authn-authz/authentication. However, note that the + Pinniped Concierge issues client certificates to users for the purpose of authenticating, and + the Kubernetes API server does not have any mechanism for transmitting auth extras via client + certificates. When configured, these extras will appear in client certificates issued by the + Pinniped Supervisor in the x509 Subject field as Organizational Units (OU). However, when this + client certificate is presented to Kubernetes for authentication, Kubernetes will ignore these + extras. This is probably only useful if you are using a custom authenticating proxy in front + of your Kubernetes API server which can translate these OUs into auth extras, as described by + https://kubernetes.io/docs/reference/access-authn-authz/authentication/#authenticating-proxy. + This is an advanced configuration option. During an end-user login flow, each of these CEL expressions + must evaluate to either a string or an array of strings, or else the user's login will fail. + items: + description: ExtraMapping provides the configuration for a single + extra mapping. + properties: + key: + description: |- + 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 to be unique. + type: string + valueExpression: + description: |- + 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.foo.bar'. + + Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ + type: string + required: + - key + - valueExpression + type: object + type: array groups: description: |- - Groups is the name of the claim which should be read to extract the user's + groups is the name of the claim which should be read to extract the user's group membership from the JWT token. When not specified, it will default to "groups". type: string username: description: |- - Username is the name of the claim which should be read to extract the + username is the name of the claim which should be read to extract the username from the JWT token. When not specified, it will default to "username". type: string type: object issuer: description: |- - Issuer is the OIDC issuer URL that will be used to discover public signing keys. Issuer is + issuer is the OIDC issuer URL that will be used to discover public signing keys. Issuer is also used to validate the "iss" JWT claim. minLength: 1 pattern: ^https:// type: string tls: - description: TLS configuration for communicating with the OIDC provider. + description: tls is the configuration for communicating with the OIDC + provider via TLS. properties: certificateAuthorityData: description: X.509 Certificate Authority (base64-encoded PEM bundle). @@ -128,12 +224,47 @@ spec: - name type: object type: object + userValidationRules: + description: |- + 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. + This is similar to claimValidationRules from Kubernetes AuthenticationConfiguration as documented in + https://kubernetes.io/docs/reference/access-authn-authz/authentication. + This is an advanced configuration option. During an end-user login flow, mistakes in this + configuration will cause the user's login to fail. + items: + description: UserValidationRule provides the configuration for a + single user info validation rule. + properties: + expression: + description: |- + 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/ + type: string + message: + description: |- + message customizes the returned error message when rule returns false. + message is a literal string. + type: string + required: + - expression + type: object + type: array required: - audience - issuer type: object status: - description: Status of the authenticator. + description: status of the authenticator. properties: conditions: description: Represents the observations of the authenticator's current diff --git a/generated/1.28/README.adoc b/generated/1.28/README.adoc index d69c92ae6..d813352df 100644 --- a/generated/1.28/README.adoc +++ b/generated/1.28/README.adoc @@ -60,6 +60,79 @@ certificate bundle. + |=== +[id="{anchor_prefix}-go-pinniped-dev-generated-1-28-apis-concierge-authentication-v1alpha1-claimvalidationrule"] +==== ClaimValidationRule + +ClaimValidationRule provides the configuration for a single claim validation rule. + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-28-apis-concierge-authentication-v1alpha1-jwtauthenticatorspec[$$JWTAuthenticatorSpec$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description +| *`claim`* __string__ | claim is the name of a required claim. + +Same as --oidc-required-claim flag. + +Only string claim keys are supported. + +Mutually exclusive with expression and message. + +| *`requiredValue`* __string__ | requiredValue is the value of a required claim. + +Same as --oidc-required-claim flag. + +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. + +Mutually exclusive with expression and message. + +| *`expression`* __string__ | 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.foo.bar'. + +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. + +| *`message`* __string__ | message customizes the returned error message when expression returns false. + +message is a literal string. + +Mutually exclusive with claim and requiredValue. + +|=== + + +[id="{anchor_prefix}-go-pinniped-dev-generated-1-28-apis-concierge-authentication-v1alpha1-extramapping"] +==== ExtraMapping + +ExtraMapping provides the configuration for a single extra mapping. + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-28-apis-concierge-authentication-v1alpha1-jwttokenclaims[$$JWTTokenClaims$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description +| *`key`* __string__ | 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 to be unique. + +| *`valueExpression`* __string__ | 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.foo.bar'. + + +Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ + +|=== + + [id="{anchor_prefix}-go-pinniped-dev-generated-1-28-apis-concierge-authentication-v1alpha1-jwtauthenticator"] ==== JWTAuthenticator @@ -78,8 +151,8 @@ signature, existence of claims, etc.) and extract the username and groups from t | Field | Description | *`metadata`* __link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.28/#objectmeta-v1-meta[$$ObjectMeta$$]__ | Refer to Kubernetes API documentation for fields of `metadata`. -| *`spec`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-28-apis-concierge-authentication-v1alpha1-jwtauthenticatorspec[$$JWTAuthenticatorSpec$$]__ | Spec for configuring the authenticator. + -| *`status`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-28-apis-concierge-authentication-v1alpha1-jwtauthenticatorstatus[$$JWTAuthenticatorStatus$$]__ | Status of the authenticator. + +| *`spec`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-28-apis-concierge-authentication-v1alpha1-jwtauthenticatorspec[$$JWTAuthenticatorSpec$$]__ | spec for configuring the authenticator. + +| *`status`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-28-apis-concierge-authentication-v1alpha1-jwtauthenticatorstatus[$$JWTAuthenticatorStatus$$]__ | status of the authenticator. + |=== @@ -100,7 +173,7 @@ signature, existence of claims, etc.) and extract the username and groups from t [id="{anchor_prefix}-go-pinniped-dev-generated-1-28-apis-concierge-authentication-v1alpha1-jwtauthenticatorspec"] ==== JWTAuthenticatorSpec -Spec for configuring a JWT authenticator. +JWTAuthenticatorSpec is the spec for configuring a JWT authenticator. .Appears In: **** @@ -110,19 +183,32 @@ Spec for configuring a JWT authenticator. [cols="25a,75a", options="header"] |=== | Field | Description -| *`issuer`* __string__ | Issuer is the OIDC issuer URL that will be used to discover public signing keys. Issuer is + +| *`issuer`* __string__ | issuer is the OIDC issuer URL that will be used to discover public signing keys. Issuer is + also used to validate the "iss" JWT claim. + -| *`audience`* __string__ | Audience is the required value of the "aud" JWT claim. + -| *`claims`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-28-apis-concierge-authentication-v1alpha1-jwttokenclaims[$$JWTTokenClaims$$]__ | Claims allows customization of the claims that will be mapped to user identity + +| *`audience`* __string__ | audience is the required value of the "aud" JWT claim. + +| *`claims`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-28-apis-concierge-authentication-v1alpha1-jwttokenclaims[$$JWTTokenClaims$$]__ | claims allows customization of the claims that will be mapped to user identity + for Kubernetes access. + -| *`tls`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-28-apis-concierge-authentication-v1alpha1-tlsspec[$$TLSSpec$$]__ | TLS configuration for communicating with the OIDC provider. + +| *`claimValidationRules`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-28-apis-concierge-authentication-v1alpha1-claimvalidationrule[$$ClaimValidationRule$$] array__ | claimValidationRules are rules that are applied to validate token claims to authenticate users. + +This is similar to claimValidationRules from Kubernetes AuthenticationConfiguration as documented in + +https://kubernetes.io/docs/reference/access-authn-authz/authentication. + +This is an advanced configuration option. During an end-user login flow, mistakes in this + +configuration will cause the user's login to fail. + +| *`userValidationRules`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-28-apis-concierge-authentication-v1alpha1-uservalidationrule[$$UserValidationRule$$] array__ | 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. + +This is similar to claimValidationRules from Kubernetes AuthenticationConfiguration as documented in + +https://kubernetes.io/docs/reference/access-authn-authz/authentication. + +This is an advanced configuration option. During an end-user login flow, mistakes in this + +configuration will cause the user's login to fail. + +| *`tls`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-28-apis-concierge-authentication-v1alpha1-tlsspec[$$TLSSpec$$]__ | tls is the configuration for communicating with the OIDC provider via TLS. + |=== [id="{anchor_prefix}-go-pinniped-dev-generated-1-28-apis-concierge-authentication-v1alpha1-jwtauthenticatorstatus"] ==== JWTAuthenticatorStatus -Status of a JWT authenticator. +JWTAuthenticatorStatus is the status of a JWT authenticator. .Appears In: **** @@ -151,10 +237,22 @@ for Kubernetes access. [cols="25a,75a", options="header"] |=== | Field | Description -| *`groups`* __string__ | Groups is the name of the claim which should be read to extract the user's + +| *`groups`* __string__ | groups is the name of the claim which should be read to extract the user's + group membership from the JWT token. When not specified, it will default to "groups". + -| *`username`* __string__ | Username is the name of the claim which should be read to extract the + +| *`username`* __string__ | username is the name of the claim which should be read to extract the + username from the JWT token. When not specified, it will default to "username". + +| *`extra`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-28-apis-concierge-authentication-v1alpha1-extramapping[$$ExtraMapping$$] array__ | extra is similar to claimMappings.extra from Kubernetes AuthenticationConfiguration as documented in + +https://kubernetes.io/docs/reference/access-authn-authz/authentication. However, note that the + +Pinniped Concierge issues client certificates to users for the purpose of authenticating, and + +the Kubernetes API server does not have any mechanism for transmitting auth extras via client + +certificates. When configured, these extras will appear in client certificates issued by the + +Pinniped Supervisor in the x509 Subject field as Organizational Units (OU). However, when this + +client certificate is presented to Kubernetes for authentication, Kubernetes will ignore these + +extras. This is probably only useful if you are using a custom authenticating proxy in front + +of your Kubernetes API server which can translate these OUs into auth extras, as described by + +https://kubernetes.io/docs/reference/access-authn-authz/authentication/#authenticating-proxy. + +This is an advanced configuration option. During an end-user login flow, each of these CEL expressions + +must evaluate to either a string or an array of strings, or else the user's login will fail. + |=== @@ -178,6 +276,33 @@ Any changes to the CA bundle in the secret or configmap will be dynamically relo |=== +[id="{anchor_prefix}-go-pinniped-dev-generated-1-28-apis-concierge-authentication-v1alpha1-uservalidationrule"] +==== UserValidationRule + +UserValidationRule provides the configuration for a single user info validation rule. + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-28-apis-concierge-authentication-v1alpha1-jwtauthenticatorspec[$$JWTAuthenticatorSpec$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description +| *`expression`* __string__ | 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/ + +| *`message`* __string__ | message customizes the returned error message when rule returns false. + +message is a literal string. + +|=== + + [id="{anchor_prefix}-go-pinniped-dev-generated-1-28-apis-concierge-authentication-v1alpha1-webhookauthenticator"] ==== WebhookAuthenticator diff --git a/generated/1.28/apis/concierge/authentication/v1alpha1/types_jwtauthenticator.go b/generated/1.28/apis/concierge/authentication/v1alpha1/types_jwtauthenticator.go index a3b01c696..d5149642f 100644 --- a/generated/1.28/apis/concierge/authentication/v1alpha1/types_jwtauthenticator.go +++ b/generated/1.28/apis/concierge/authentication/v1alpha1/types_jwtauthenticator.go @@ -18,7 +18,7 @@ const ( JWTAuthenticatorPhaseError JWTAuthenticatorPhase = "Error" ) -// Status of a JWT authenticator. +// JWTAuthenticatorStatus is the status of a JWT authenticator. type JWTAuthenticatorStatus struct { // Represents the observations of the authenticator's current state. // +patchMergeKey=type @@ -26,46 +26,168 @@ type JWTAuthenticatorStatus struct { // +listType=map // +listMapKey=type Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"` + // Phase summarizes the overall status of the JWTAuthenticator. // +kubebuilder:default=Pending // +kubebuilder:validation:Enum=Pending;Ready;Error Phase JWTAuthenticatorPhase `json:"phase,omitempty"` } -// Spec for configuring a JWT authenticator. +// JWTAuthenticatorSpec is the spec for configuring a JWT authenticator. type JWTAuthenticatorSpec struct { - // Issuer is the OIDC issuer URL that will be used to discover public signing keys. Issuer is + // issuer is the OIDC issuer URL that will be used to discover public signing keys. Issuer is // also used to validate the "iss" JWT claim. // +kubebuilder:validation:MinLength=1 // +kubebuilder:validation:Pattern=`^https://` Issuer string `json:"issuer"` - // Audience is the required value of the "aud" JWT claim. + // audience is the required value of the "aud" JWT claim. // +kubebuilder:validation:MinLength=1 Audience string `json:"audience"` - // Claims allows customization of the claims that will be mapped to user identity + // claims allows customization of the claims that will be mapped to user identity // for Kubernetes access. // +optional Claims JWTTokenClaims `json:"claims"` - // TLS configuration for communicating with the OIDC provider. + // claimValidationRules are rules that are applied to validate token claims to authenticate users. + // This is similar to claimValidationRules from Kubernetes AuthenticationConfiguration as documented in + // https://kubernetes.io/docs/reference/access-authn-authz/authentication. + // This is an advanced configuration option. During an end-user login flow, mistakes in this + // configuration will cause the user's login to fail. + // +optional + ClaimValidationRules []ClaimValidationRule `json:"claimValidationRules,omitempty"` + + // 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. + // This is similar to claimValidationRules from Kubernetes AuthenticationConfiguration as documented in + // https://kubernetes.io/docs/reference/access-authn-authz/authentication. + // This is an advanced configuration option. During an end-user login flow, mistakes in this + // configuration will cause the user's login to fail. + // +optional + UserValidationRules []UserValidationRule `json:"userValidationRules,omitempty"` + + // tls is the configuration for communicating with the OIDC provider via TLS. // +optional TLS *TLSSpec `json:"tls,omitempty"` } +// ClaimValidationRule provides the configuration for a single claim validation rule. +type ClaimValidationRule struct { + // claim is the name of a required claim. + // Same as --oidc-required-claim flag. + // Only string claim keys are supported. + // Mutually exclusive with expression and message. + // +optional + Claim string `json:"claim,omitempty"` + + // requiredValue is the value of a required claim. + // Same as --oidc-required-claim flag. + // 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. + // Mutually exclusive with expression and message. + // +optional + 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.foo.bar'. + // 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"` +} + +// 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"` +} + // JWTTokenClaims allows customization of the claims that will be mapped to user identity // for Kubernetes access. type JWTTokenClaims struct { - // Groups is the name of the claim which should be read to extract the user's + // groups is the name of the claim which should be read to extract the user's // group membership from the JWT token. When not specified, it will default to "groups". // +optional Groups string `json:"groups"` - // Username is the name of the claim which should be read to extract the + // username is the name of the claim which should be read to extract the // username from the JWT token. When not specified, it will default to "username". // +optional Username string `json:"username"` + + // extra is similar to claimMappings.extra from Kubernetes AuthenticationConfiguration as documented in + // https://kubernetes.io/docs/reference/access-authn-authz/authentication. However, note that the + // Pinniped Concierge issues client certificates to users for the purpose of authenticating, and + // the Kubernetes API server does not have any mechanism for transmitting auth extras via client + // certificates. When configured, these extras will appear in client certificates issued by the + // Pinniped Supervisor in the x509 Subject field as Organizational Units (OU). However, when this + // client certificate is presented to Kubernetes for authentication, Kubernetes will ignore these + // extras. This is probably only useful if you are using a custom authenticating proxy in front + // of your Kubernetes API server which can translate these OUs into auth extras, as described by + // https://kubernetes.io/docs/reference/access-authn-authz/authentication/#authenticating-proxy. + // This is an advanced configuration option. During an end-user login flow, each of these CEL expressions + // must evaluate to either a string or an array of strings, or else the user's login will fail. + // +optional + Extra []ExtraMapping `json:"extra,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 to be unique. + // +required + 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.foo.bar'. + // + // Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ + // + // +required + ValueExpression string `json:"valueExpression"` } // JWTAuthenticator describes the configuration of a JWT authenticator. @@ -86,14 +208,14 @@ type JWTAuthenticator struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` - // Spec for configuring the authenticator. + // spec for configuring the authenticator. Spec JWTAuthenticatorSpec `json:"spec"` - // Status of the authenticator. + // status of the authenticator. Status JWTAuthenticatorStatus `json:"status,omitempty"` } -// List of JWTAuthenticator objects. +// JWTAuthenticatorList is a list of JWTAuthenticator objects. // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object type JWTAuthenticatorList struct { metav1.TypeMeta `json:",inline"` diff --git a/generated/1.28/apis/concierge/authentication/v1alpha1/zz_generated.deepcopy.go b/generated/1.28/apis/concierge/authentication/v1alpha1/zz_generated.deepcopy.go index 6b1235b88..cf8c0e61c 100644 --- a/generated/1.28/apis/concierge/authentication/v1alpha1/zz_generated.deepcopy.go +++ b/generated/1.28/apis/concierge/authentication/v1alpha1/zz_generated.deepcopy.go @@ -29,6 +29,38 @@ func (in *CertificateAuthorityDataSourceSpec) DeepCopy() *CertificateAuthorityDa return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ClaimValidationRule) DeepCopyInto(out *ClaimValidationRule) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClaimValidationRule. +func (in *ClaimValidationRule) DeepCopy() *ClaimValidationRule { + if in == nil { + return nil + } + out := new(ClaimValidationRule) + in.DeepCopyInto(out) + return out +} + +// 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. func (in *JWTAuthenticator) DeepCopyInto(out *JWTAuthenticator) { *out = *in @@ -93,7 +125,17 @@ func (in *JWTAuthenticatorList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *JWTAuthenticatorSpec) DeepCopyInto(out *JWTAuthenticatorSpec) { *out = *in - out.Claims = in.Claims + in.Claims.DeepCopyInto(&out.Claims) + if in.ClaimValidationRules != nil { + in, out := &in.ClaimValidationRules, &out.ClaimValidationRules + *out = make([]ClaimValidationRule, len(*in)) + copy(*out, *in) + } + if in.UserValidationRules != nil { + in, out := &in.UserValidationRules, &out.UserValidationRules + *out = make([]UserValidationRule, len(*in)) + copy(*out, *in) + } if in.TLS != nil { in, out := &in.TLS, &out.TLS *out = new(TLSSpec) @@ -138,6 +180,11 @@ func (in *JWTAuthenticatorStatus) DeepCopy() *JWTAuthenticatorStatus { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *JWTTokenClaims) DeepCopyInto(out *JWTTokenClaims) { *out = *in + if in.Extra != nil { + in, out := &in.Extra, &out.Extra + *out = make([]ExtraMapping, len(*in)) + copy(*out, *in) + } return } @@ -172,6 +219,22 @@ func (in *TLSSpec) DeepCopy() *TLSSpec { 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. func (in *WebhookAuthenticator) DeepCopyInto(out *WebhookAuthenticator) { *out = *in diff --git a/generated/1.28/crds/authentication.concierge.pinniped.dev_jwtauthenticators.yaml b/generated/1.28/crds/authentication.concierge.pinniped.dev_jwtauthenticators.yaml index 2ef6312a0..cbb567616 100644 --- a/generated/1.28/crds/authentication.concierge.pinniped.dev_jwtauthenticators.yaml +++ b/generated/1.28/crds/authentication.concierge.pinniped.dev_jwtauthenticators.yaml @@ -58,37 +58,133 @@ spec: metadata: type: object spec: - description: Spec for configuring the authenticator. + description: spec for configuring the authenticator. properties: audience: - description: Audience is the required value of the "aud" JWT claim. + description: audience is the required value of the "aud" JWT claim. minLength: 1 type: string + claimValidationRules: + description: |- + claimValidationRules are rules that are applied to validate token claims to authenticate users. + This is similar to claimValidationRules from Kubernetes AuthenticationConfiguration as documented in + https://kubernetes.io/docs/reference/access-authn-authz/authentication. + This is an advanced configuration option. During an end-user login flow, mistakes in this + configuration will cause the user's login to fail. + items: + description: ClaimValidationRule provides the configuration for + a single claim validation rule. + properties: + claim: + description: |- + claim is the name of a required claim. + Same as --oidc-required-claim flag. + Only string claim keys are supported. + Mutually exclusive with expression and message. + type: string + expression: + description: |- + 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.foo.bar'. + 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. + type: string + message: + description: |- + message customizes the returned error message when expression returns false. + message is a literal string. + Mutually exclusive with claim and requiredValue. + type: string + requiredValue: + description: |- + requiredValue is the value of a required claim. + Same as --oidc-required-claim flag. + 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. + Mutually exclusive with expression and message. + type: string + type: object + type: array claims: description: |- - Claims allows customization of the claims that will be mapped to user identity + claims allows customization of the claims that will be mapped to user identity for Kubernetes access. properties: + extra: + description: |- + extra is similar to claimMappings.extra from Kubernetes AuthenticationConfiguration as documented in + https://kubernetes.io/docs/reference/access-authn-authz/authentication. However, note that the + Pinniped Concierge issues client certificates to users for the purpose of authenticating, and + the Kubernetes API server does not have any mechanism for transmitting auth extras via client + certificates. When configured, these extras will appear in client certificates issued by the + Pinniped Supervisor in the x509 Subject field as Organizational Units (OU). However, when this + client certificate is presented to Kubernetes for authentication, Kubernetes will ignore these + extras. This is probably only useful if you are using a custom authenticating proxy in front + of your Kubernetes API server which can translate these OUs into auth extras, as described by + https://kubernetes.io/docs/reference/access-authn-authz/authentication/#authenticating-proxy. + This is an advanced configuration option. During an end-user login flow, each of these CEL expressions + must evaluate to either a string or an array of strings, or else the user's login will fail. + items: + description: ExtraMapping provides the configuration for a single + extra mapping. + properties: + key: + description: |- + 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 to be unique. + type: string + valueExpression: + description: |- + 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.foo.bar'. + + Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ + type: string + required: + - key + - valueExpression + type: object + type: array groups: description: |- - Groups is the name of the claim which should be read to extract the user's + groups is the name of the claim which should be read to extract the user's group membership from the JWT token. When not specified, it will default to "groups". type: string username: description: |- - Username is the name of the claim which should be read to extract the + username is the name of the claim which should be read to extract the username from the JWT token. When not specified, it will default to "username". type: string type: object issuer: description: |- - Issuer is the OIDC issuer URL that will be used to discover public signing keys. Issuer is + issuer is the OIDC issuer URL that will be used to discover public signing keys. Issuer is also used to validate the "iss" JWT claim. minLength: 1 pattern: ^https:// type: string tls: - description: TLS configuration for communicating with the OIDC provider. + description: tls is the configuration for communicating with the OIDC + provider via TLS. properties: certificateAuthorityData: description: X.509 Certificate Authority (base64-encoded PEM bundle). @@ -128,12 +224,47 @@ spec: - name type: object type: object + userValidationRules: + description: |- + 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. + This is similar to claimValidationRules from Kubernetes AuthenticationConfiguration as documented in + https://kubernetes.io/docs/reference/access-authn-authz/authentication. + This is an advanced configuration option. During an end-user login flow, mistakes in this + configuration will cause the user's login to fail. + items: + description: UserValidationRule provides the configuration for a + single user info validation rule. + properties: + expression: + description: |- + 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/ + type: string + message: + description: |- + message customizes the returned error message when rule returns false. + message is a literal string. + type: string + required: + - expression + type: object + type: array required: - audience - issuer type: object status: - description: Status of the authenticator. + description: status of the authenticator. properties: conditions: description: Represents the observations of the authenticator's current diff --git a/generated/1.29/README.adoc b/generated/1.29/README.adoc index 7cf6c60e9..d935f9896 100644 --- a/generated/1.29/README.adoc +++ b/generated/1.29/README.adoc @@ -60,6 +60,79 @@ certificate bundle. + |=== +[id="{anchor_prefix}-go-pinniped-dev-generated-1-29-apis-concierge-authentication-v1alpha1-claimvalidationrule"] +==== ClaimValidationRule + +ClaimValidationRule provides the configuration for a single claim validation rule. + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-29-apis-concierge-authentication-v1alpha1-jwtauthenticatorspec[$$JWTAuthenticatorSpec$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description +| *`claim`* __string__ | claim is the name of a required claim. + +Same as --oidc-required-claim flag. + +Only string claim keys are supported. + +Mutually exclusive with expression and message. + +| *`requiredValue`* __string__ | requiredValue is the value of a required claim. + +Same as --oidc-required-claim flag. + +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. + +Mutually exclusive with expression and message. + +| *`expression`* __string__ | 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.foo.bar'. + +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. + +| *`message`* __string__ | message customizes the returned error message when expression returns false. + +message is a literal string. + +Mutually exclusive with claim and requiredValue. + +|=== + + +[id="{anchor_prefix}-go-pinniped-dev-generated-1-29-apis-concierge-authentication-v1alpha1-extramapping"] +==== ExtraMapping + +ExtraMapping provides the configuration for a single extra mapping. + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-29-apis-concierge-authentication-v1alpha1-jwttokenclaims[$$JWTTokenClaims$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description +| *`key`* __string__ | 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 to be unique. + +| *`valueExpression`* __string__ | 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.foo.bar'. + + +Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ + +|=== + + [id="{anchor_prefix}-go-pinniped-dev-generated-1-29-apis-concierge-authentication-v1alpha1-jwtauthenticator"] ==== JWTAuthenticator @@ -78,8 +151,8 @@ signature, existence of claims, etc.) and extract the username and groups from t | Field | Description | *`metadata`* __link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#objectmeta-v1-meta[$$ObjectMeta$$]__ | Refer to Kubernetes API documentation for fields of `metadata`. -| *`spec`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-29-apis-concierge-authentication-v1alpha1-jwtauthenticatorspec[$$JWTAuthenticatorSpec$$]__ | Spec for configuring the authenticator. + -| *`status`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-29-apis-concierge-authentication-v1alpha1-jwtauthenticatorstatus[$$JWTAuthenticatorStatus$$]__ | Status of the authenticator. + +| *`spec`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-29-apis-concierge-authentication-v1alpha1-jwtauthenticatorspec[$$JWTAuthenticatorSpec$$]__ | spec for configuring the authenticator. + +| *`status`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-29-apis-concierge-authentication-v1alpha1-jwtauthenticatorstatus[$$JWTAuthenticatorStatus$$]__ | status of the authenticator. + |=== @@ -100,7 +173,7 @@ signature, existence of claims, etc.) and extract the username and groups from t [id="{anchor_prefix}-go-pinniped-dev-generated-1-29-apis-concierge-authentication-v1alpha1-jwtauthenticatorspec"] ==== JWTAuthenticatorSpec -Spec for configuring a JWT authenticator. +JWTAuthenticatorSpec is the spec for configuring a JWT authenticator. .Appears In: **** @@ -110,19 +183,32 @@ Spec for configuring a JWT authenticator. [cols="25a,75a", options="header"] |=== | Field | Description -| *`issuer`* __string__ | Issuer is the OIDC issuer URL that will be used to discover public signing keys. Issuer is + +| *`issuer`* __string__ | issuer is the OIDC issuer URL that will be used to discover public signing keys. Issuer is + also used to validate the "iss" JWT claim. + -| *`audience`* __string__ | Audience is the required value of the "aud" JWT claim. + -| *`claims`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-29-apis-concierge-authentication-v1alpha1-jwttokenclaims[$$JWTTokenClaims$$]__ | Claims allows customization of the claims that will be mapped to user identity + +| *`audience`* __string__ | audience is the required value of the "aud" JWT claim. + +| *`claims`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-29-apis-concierge-authentication-v1alpha1-jwttokenclaims[$$JWTTokenClaims$$]__ | claims allows customization of the claims that will be mapped to user identity + for Kubernetes access. + -| *`tls`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-29-apis-concierge-authentication-v1alpha1-tlsspec[$$TLSSpec$$]__ | TLS configuration for communicating with the OIDC provider. + +| *`claimValidationRules`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-29-apis-concierge-authentication-v1alpha1-claimvalidationrule[$$ClaimValidationRule$$] array__ | claimValidationRules are rules that are applied to validate token claims to authenticate users. + +This is similar to claimValidationRules from Kubernetes AuthenticationConfiguration as documented in + +https://kubernetes.io/docs/reference/access-authn-authz/authentication. + +This is an advanced configuration option. During an end-user login flow, mistakes in this + +configuration will cause the user's login to fail. + +| *`userValidationRules`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-29-apis-concierge-authentication-v1alpha1-uservalidationrule[$$UserValidationRule$$] array__ | 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. + +This is similar to claimValidationRules from Kubernetes AuthenticationConfiguration as documented in + +https://kubernetes.io/docs/reference/access-authn-authz/authentication. + +This is an advanced configuration option. During an end-user login flow, mistakes in this + +configuration will cause the user's login to fail. + +| *`tls`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-29-apis-concierge-authentication-v1alpha1-tlsspec[$$TLSSpec$$]__ | tls is the configuration for communicating with the OIDC provider via TLS. + |=== [id="{anchor_prefix}-go-pinniped-dev-generated-1-29-apis-concierge-authentication-v1alpha1-jwtauthenticatorstatus"] ==== JWTAuthenticatorStatus -Status of a JWT authenticator. +JWTAuthenticatorStatus is the status of a JWT authenticator. .Appears In: **** @@ -151,10 +237,22 @@ for Kubernetes access. [cols="25a,75a", options="header"] |=== | Field | Description -| *`groups`* __string__ | Groups is the name of the claim which should be read to extract the user's + +| *`groups`* __string__ | groups is the name of the claim which should be read to extract the user's + group membership from the JWT token. When not specified, it will default to "groups". + -| *`username`* __string__ | Username is the name of the claim which should be read to extract the + +| *`username`* __string__ | username is the name of the claim which should be read to extract the + username from the JWT token. When not specified, it will default to "username". + +| *`extra`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-29-apis-concierge-authentication-v1alpha1-extramapping[$$ExtraMapping$$] array__ | extra is similar to claimMappings.extra from Kubernetes AuthenticationConfiguration as documented in + +https://kubernetes.io/docs/reference/access-authn-authz/authentication. However, note that the + +Pinniped Concierge issues client certificates to users for the purpose of authenticating, and + +the Kubernetes API server does not have any mechanism for transmitting auth extras via client + +certificates. When configured, these extras will appear in client certificates issued by the + +Pinniped Supervisor in the x509 Subject field as Organizational Units (OU). However, when this + +client certificate is presented to Kubernetes for authentication, Kubernetes will ignore these + +extras. This is probably only useful if you are using a custom authenticating proxy in front + +of your Kubernetes API server which can translate these OUs into auth extras, as described by + +https://kubernetes.io/docs/reference/access-authn-authz/authentication/#authenticating-proxy. + +This is an advanced configuration option. During an end-user login flow, each of these CEL expressions + +must evaluate to either a string or an array of strings, or else the user's login will fail. + |=== @@ -178,6 +276,33 @@ Any changes to the CA bundle in the secret or configmap will be dynamically relo |=== +[id="{anchor_prefix}-go-pinniped-dev-generated-1-29-apis-concierge-authentication-v1alpha1-uservalidationrule"] +==== UserValidationRule + +UserValidationRule provides the configuration for a single user info validation rule. + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-29-apis-concierge-authentication-v1alpha1-jwtauthenticatorspec[$$JWTAuthenticatorSpec$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description +| *`expression`* __string__ | 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/ + +| *`message`* __string__ | message customizes the returned error message when rule returns false. + +message is a literal string. + +|=== + + [id="{anchor_prefix}-go-pinniped-dev-generated-1-29-apis-concierge-authentication-v1alpha1-webhookauthenticator"] ==== WebhookAuthenticator diff --git a/generated/1.29/apis/concierge/authentication/v1alpha1/types_jwtauthenticator.go b/generated/1.29/apis/concierge/authentication/v1alpha1/types_jwtauthenticator.go index a3b01c696..d5149642f 100644 --- a/generated/1.29/apis/concierge/authentication/v1alpha1/types_jwtauthenticator.go +++ b/generated/1.29/apis/concierge/authentication/v1alpha1/types_jwtauthenticator.go @@ -18,7 +18,7 @@ const ( JWTAuthenticatorPhaseError JWTAuthenticatorPhase = "Error" ) -// Status of a JWT authenticator. +// JWTAuthenticatorStatus is the status of a JWT authenticator. type JWTAuthenticatorStatus struct { // Represents the observations of the authenticator's current state. // +patchMergeKey=type @@ -26,46 +26,168 @@ type JWTAuthenticatorStatus struct { // +listType=map // +listMapKey=type Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"` + // Phase summarizes the overall status of the JWTAuthenticator. // +kubebuilder:default=Pending // +kubebuilder:validation:Enum=Pending;Ready;Error Phase JWTAuthenticatorPhase `json:"phase,omitempty"` } -// Spec for configuring a JWT authenticator. +// JWTAuthenticatorSpec is the spec for configuring a JWT authenticator. type JWTAuthenticatorSpec struct { - // Issuer is the OIDC issuer URL that will be used to discover public signing keys. Issuer is + // issuer is the OIDC issuer URL that will be used to discover public signing keys. Issuer is // also used to validate the "iss" JWT claim. // +kubebuilder:validation:MinLength=1 // +kubebuilder:validation:Pattern=`^https://` Issuer string `json:"issuer"` - // Audience is the required value of the "aud" JWT claim. + // audience is the required value of the "aud" JWT claim. // +kubebuilder:validation:MinLength=1 Audience string `json:"audience"` - // Claims allows customization of the claims that will be mapped to user identity + // claims allows customization of the claims that will be mapped to user identity // for Kubernetes access. // +optional Claims JWTTokenClaims `json:"claims"` - // TLS configuration for communicating with the OIDC provider. + // claimValidationRules are rules that are applied to validate token claims to authenticate users. + // This is similar to claimValidationRules from Kubernetes AuthenticationConfiguration as documented in + // https://kubernetes.io/docs/reference/access-authn-authz/authentication. + // This is an advanced configuration option. During an end-user login flow, mistakes in this + // configuration will cause the user's login to fail. + // +optional + ClaimValidationRules []ClaimValidationRule `json:"claimValidationRules,omitempty"` + + // 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. + // This is similar to claimValidationRules from Kubernetes AuthenticationConfiguration as documented in + // https://kubernetes.io/docs/reference/access-authn-authz/authentication. + // This is an advanced configuration option. During an end-user login flow, mistakes in this + // configuration will cause the user's login to fail. + // +optional + UserValidationRules []UserValidationRule `json:"userValidationRules,omitempty"` + + // tls is the configuration for communicating with the OIDC provider via TLS. // +optional TLS *TLSSpec `json:"tls,omitempty"` } +// ClaimValidationRule provides the configuration for a single claim validation rule. +type ClaimValidationRule struct { + // claim is the name of a required claim. + // Same as --oidc-required-claim flag. + // Only string claim keys are supported. + // Mutually exclusive with expression and message. + // +optional + Claim string `json:"claim,omitempty"` + + // requiredValue is the value of a required claim. + // Same as --oidc-required-claim flag. + // 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. + // Mutually exclusive with expression and message. + // +optional + 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.foo.bar'. + // 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"` +} + +// 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"` +} + // JWTTokenClaims allows customization of the claims that will be mapped to user identity // for Kubernetes access. type JWTTokenClaims struct { - // Groups is the name of the claim which should be read to extract the user's + // groups is the name of the claim which should be read to extract the user's // group membership from the JWT token. When not specified, it will default to "groups". // +optional Groups string `json:"groups"` - // Username is the name of the claim which should be read to extract the + // username is the name of the claim which should be read to extract the // username from the JWT token. When not specified, it will default to "username". // +optional Username string `json:"username"` + + // extra is similar to claimMappings.extra from Kubernetes AuthenticationConfiguration as documented in + // https://kubernetes.io/docs/reference/access-authn-authz/authentication. However, note that the + // Pinniped Concierge issues client certificates to users for the purpose of authenticating, and + // the Kubernetes API server does not have any mechanism for transmitting auth extras via client + // certificates. When configured, these extras will appear in client certificates issued by the + // Pinniped Supervisor in the x509 Subject field as Organizational Units (OU). However, when this + // client certificate is presented to Kubernetes for authentication, Kubernetes will ignore these + // extras. This is probably only useful if you are using a custom authenticating proxy in front + // of your Kubernetes API server which can translate these OUs into auth extras, as described by + // https://kubernetes.io/docs/reference/access-authn-authz/authentication/#authenticating-proxy. + // This is an advanced configuration option. During an end-user login flow, each of these CEL expressions + // must evaluate to either a string or an array of strings, or else the user's login will fail. + // +optional + Extra []ExtraMapping `json:"extra,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 to be unique. + // +required + 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.foo.bar'. + // + // Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ + // + // +required + ValueExpression string `json:"valueExpression"` } // JWTAuthenticator describes the configuration of a JWT authenticator. @@ -86,14 +208,14 @@ type JWTAuthenticator struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` - // Spec for configuring the authenticator. + // spec for configuring the authenticator. Spec JWTAuthenticatorSpec `json:"spec"` - // Status of the authenticator. + // status of the authenticator. Status JWTAuthenticatorStatus `json:"status,omitempty"` } -// List of JWTAuthenticator objects. +// JWTAuthenticatorList is a list of JWTAuthenticator objects. // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object type JWTAuthenticatorList struct { metav1.TypeMeta `json:",inline"` diff --git a/generated/1.29/apis/concierge/authentication/v1alpha1/zz_generated.deepcopy.go b/generated/1.29/apis/concierge/authentication/v1alpha1/zz_generated.deepcopy.go index 6b1235b88..cf8c0e61c 100644 --- a/generated/1.29/apis/concierge/authentication/v1alpha1/zz_generated.deepcopy.go +++ b/generated/1.29/apis/concierge/authentication/v1alpha1/zz_generated.deepcopy.go @@ -29,6 +29,38 @@ func (in *CertificateAuthorityDataSourceSpec) DeepCopy() *CertificateAuthorityDa return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ClaimValidationRule) DeepCopyInto(out *ClaimValidationRule) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClaimValidationRule. +func (in *ClaimValidationRule) DeepCopy() *ClaimValidationRule { + if in == nil { + return nil + } + out := new(ClaimValidationRule) + in.DeepCopyInto(out) + return out +} + +// 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. func (in *JWTAuthenticator) DeepCopyInto(out *JWTAuthenticator) { *out = *in @@ -93,7 +125,17 @@ func (in *JWTAuthenticatorList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *JWTAuthenticatorSpec) DeepCopyInto(out *JWTAuthenticatorSpec) { *out = *in - out.Claims = in.Claims + in.Claims.DeepCopyInto(&out.Claims) + if in.ClaimValidationRules != nil { + in, out := &in.ClaimValidationRules, &out.ClaimValidationRules + *out = make([]ClaimValidationRule, len(*in)) + copy(*out, *in) + } + if in.UserValidationRules != nil { + in, out := &in.UserValidationRules, &out.UserValidationRules + *out = make([]UserValidationRule, len(*in)) + copy(*out, *in) + } if in.TLS != nil { in, out := &in.TLS, &out.TLS *out = new(TLSSpec) @@ -138,6 +180,11 @@ func (in *JWTAuthenticatorStatus) DeepCopy() *JWTAuthenticatorStatus { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *JWTTokenClaims) DeepCopyInto(out *JWTTokenClaims) { *out = *in + if in.Extra != nil { + in, out := &in.Extra, &out.Extra + *out = make([]ExtraMapping, len(*in)) + copy(*out, *in) + } return } @@ -172,6 +219,22 @@ func (in *TLSSpec) DeepCopy() *TLSSpec { 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. func (in *WebhookAuthenticator) DeepCopyInto(out *WebhookAuthenticator) { *out = *in diff --git a/generated/1.29/crds/authentication.concierge.pinniped.dev_jwtauthenticators.yaml b/generated/1.29/crds/authentication.concierge.pinniped.dev_jwtauthenticators.yaml index 2ef6312a0..cbb567616 100644 --- a/generated/1.29/crds/authentication.concierge.pinniped.dev_jwtauthenticators.yaml +++ b/generated/1.29/crds/authentication.concierge.pinniped.dev_jwtauthenticators.yaml @@ -58,37 +58,133 @@ spec: metadata: type: object spec: - description: Spec for configuring the authenticator. + description: spec for configuring the authenticator. properties: audience: - description: Audience is the required value of the "aud" JWT claim. + description: audience is the required value of the "aud" JWT claim. minLength: 1 type: string + claimValidationRules: + description: |- + claimValidationRules are rules that are applied to validate token claims to authenticate users. + This is similar to claimValidationRules from Kubernetes AuthenticationConfiguration as documented in + https://kubernetes.io/docs/reference/access-authn-authz/authentication. + This is an advanced configuration option. During an end-user login flow, mistakes in this + configuration will cause the user's login to fail. + items: + description: ClaimValidationRule provides the configuration for + a single claim validation rule. + properties: + claim: + description: |- + claim is the name of a required claim. + Same as --oidc-required-claim flag. + Only string claim keys are supported. + Mutually exclusive with expression and message. + type: string + expression: + description: |- + 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.foo.bar'. + 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. + type: string + message: + description: |- + message customizes the returned error message when expression returns false. + message is a literal string. + Mutually exclusive with claim and requiredValue. + type: string + requiredValue: + description: |- + requiredValue is the value of a required claim. + Same as --oidc-required-claim flag. + 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. + Mutually exclusive with expression and message. + type: string + type: object + type: array claims: description: |- - Claims allows customization of the claims that will be mapped to user identity + claims allows customization of the claims that will be mapped to user identity for Kubernetes access. properties: + extra: + description: |- + extra is similar to claimMappings.extra from Kubernetes AuthenticationConfiguration as documented in + https://kubernetes.io/docs/reference/access-authn-authz/authentication. However, note that the + Pinniped Concierge issues client certificates to users for the purpose of authenticating, and + the Kubernetes API server does not have any mechanism for transmitting auth extras via client + certificates. When configured, these extras will appear in client certificates issued by the + Pinniped Supervisor in the x509 Subject field as Organizational Units (OU). However, when this + client certificate is presented to Kubernetes for authentication, Kubernetes will ignore these + extras. This is probably only useful if you are using a custom authenticating proxy in front + of your Kubernetes API server which can translate these OUs into auth extras, as described by + https://kubernetes.io/docs/reference/access-authn-authz/authentication/#authenticating-proxy. + This is an advanced configuration option. During an end-user login flow, each of these CEL expressions + must evaluate to either a string or an array of strings, or else the user's login will fail. + items: + description: ExtraMapping provides the configuration for a single + extra mapping. + properties: + key: + description: |- + 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 to be unique. + type: string + valueExpression: + description: |- + 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.foo.bar'. + + Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ + type: string + required: + - key + - valueExpression + type: object + type: array groups: description: |- - Groups is the name of the claim which should be read to extract the user's + groups is the name of the claim which should be read to extract the user's group membership from the JWT token. When not specified, it will default to "groups". type: string username: description: |- - Username is the name of the claim which should be read to extract the + username is the name of the claim which should be read to extract the username from the JWT token. When not specified, it will default to "username". type: string type: object issuer: description: |- - Issuer is the OIDC issuer URL that will be used to discover public signing keys. Issuer is + issuer is the OIDC issuer URL that will be used to discover public signing keys. Issuer is also used to validate the "iss" JWT claim. minLength: 1 pattern: ^https:// type: string tls: - description: TLS configuration for communicating with the OIDC provider. + description: tls is the configuration for communicating with the OIDC + provider via TLS. properties: certificateAuthorityData: description: X.509 Certificate Authority (base64-encoded PEM bundle). @@ -128,12 +224,47 @@ spec: - name type: object type: object + userValidationRules: + description: |- + 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. + This is similar to claimValidationRules from Kubernetes AuthenticationConfiguration as documented in + https://kubernetes.io/docs/reference/access-authn-authz/authentication. + This is an advanced configuration option. During an end-user login flow, mistakes in this + configuration will cause the user's login to fail. + items: + description: UserValidationRule provides the configuration for a + single user info validation rule. + properties: + expression: + description: |- + 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/ + type: string + message: + description: |- + message customizes the returned error message when rule returns false. + message is a literal string. + type: string + required: + - expression + type: object + type: array required: - audience - issuer type: object status: - description: Status of the authenticator. + description: status of the authenticator. properties: conditions: description: Represents the observations of the authenticator's current diff --git a/generated/1.30/README.adoc b/generated/1.30/README.adoc index b05b1b5c8..2615c5333 100644 --- a/generated/1.30/README.adoc +++ b/generated/1.30/README.adoc @@ -60,6 +60,79 @@ certificate bundle. + |=== +[id="{anchor_prefix}-go-pinniped-dev-generated-1-30-apis-concierge-authentication-v1alpha1-claimvalidationrule"] +==== ClaimValidationRule + +ClaimValidationRule provides the configuration for a single claim validation rule. + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-30-apis-concierge-authentication-v1alpha1-jwtauthenticatorspec[$$JWTAuthenticatorSpec$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description +| *`claim`* __string__ | claim is the name of a required claim. + +Same as --oidc-required-claim flag. + +Only string claim keys are supported. + +Mutually exclusive with expression and message. + +| *`requiredValue`* __string__ | requiredValue is the value of a required claim. + +Same as --oidc-required-claim flag. + +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. + +Mutually exclusive with expression and message. + +| *`expression`* __string__ | 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.foo.bar'. + +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. + +| *`message`* __string__ | message customizes the returned error message when expression returns false. + +message is a literal string. + +Mutually exclusive with claim and requiredValue. + +|=== + + +[id="{anchor_prefix}-go-pinniped-dev-generated-1-30-apis-concierge-authentication-v1alpha1-extramapping"] +==== ExtraMapping + +ExtraMapping provides the configuration for a single extra mapping. + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-30-apis-concierge-authentication-v1alpha1-jwttokenclaims[$$JWTTokenClaims$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description +| *`key`* __string__ | 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 to be unique. + +| *`valueExpression`* __string__ | 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.foo.bar'. + + +Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ + +|=== + + [id="{anchor_prefix}-go-pinniped-dev-generated-1-30-apis-concierge-authentication-v1alpha1-jwtauthenticator"] ==== JWTAuthenticator @@ -78,8 +151,8 @@ signature, existence of claims, etc.) and extract the username and groups from t | Field | Description | *`metadata`* __link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.3/#objectmeta-v1-meta[$$ObjectMeta$$]__ | Refer to Kubernetes API documentation for fields of `metadata`. -| *`spec`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-30-apis-concierge-authentication-v1alpha1-jwtauthenticatorspec[$$JWTAuthenticatorSpec$$]__ | Spec for configuring the authenticator. + -| *`status`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-30-apis-concierge-authentication-v1alpha1-jwtauthenticatorstatus[$$JWTAuthenticatorStatus$$]__ | Status of the authenticator. + +| *`spec`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-30-apis-concierge-authentication-v1alpha1-jwtauthenticatorspec[$$JWTAuthenticatorSpec$$]__ | spec for configuring the authenticator. + +| *`status`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-30-apis-concierge-authentication-v1alpha1-jwtauthenticatorstatus[$$JWTAuthenticatorStatus$$]__ | status of the authenticator. + |=== @@ -100,7 +173,7 @@ signature, existence of claims, etc.) and extract the username and groups from t [id="{anchor_prefix}-go-pinniped-dev-generated-1-30-apis-concierge-authentication-v1alpha1-jwtauthenticatorspec"] ==== JWTAuthenticatorSpec -Spec for configuring a JWT authenticator. +JWTAuthenticatorSpec is the spec for configuring a JWT authenticator. .Appears In: **** @@ -110,19 +183,32 @@ Spec for configuring a JWT authenticator. [cols="25a,75a", options="header"] |=== | Field | Description -| *`issuer`* __string__ | Issuer is the OIDC issuer URL that will be used to discover public signing keys. Issuer is + +| *`issuer`* __string__ | issuer is the OIDC issuer URL that will be used to discover public signing keys. Issuer is + also used to validate the "iss" JWT claim. + -| *`audience`* __string__ | Audience is the required value of the "aud" JWT claim. + -| *`claims`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-30-apis-concierge-authentication-v1alpha1-jwttokenclaims[$$JWTTokenClaims$$]__ | Claims allows customization of the claims that will be mapped to user identity + +| *`audience`* __string__ | audience is the required value of the "aud" JWT claim. + +| *`claims`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-30-apis-concierge-authentication-v1alpha1-jwttokenclaims[$$JWTTokenClaims$$]__ | claims allows customization of the claims that will be mapped to user identity + for Kubernetes access. + -| *`tls`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-30-apis-concierge-authentication-v1alpha1-tlsspec[$$TLSSpec$$]__ | TLS configuration for communicating with the OIDC provider. + +| *`claimValidationRules`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-30-apis-concierge-authentication-v1alpha1-claimvalidationrule[$$ClaimValidationRule$$] array__ | claimValidationRules are rules that are applied to validate token claims to authenticate users. + +This is similar to claimValidationRules from Kubernetes AuthenticationConfiguration as documented in + +https://kubernetes.io/docs/reference/access-authn-authz/authentication. + +This is an advanced configuration option. During an end-user login flow, mistakes in this + +configuration will cause the user's login to fail. + +| *`userValidationRules`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-30-apis-concierge-authentication-v1alpha1-uservalidationrule[$$UserValidationRule$$] array__ | 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. + +This is similar to claimValidationRules from Kubernetes AuthenticationConfiguration as documented in + +https://kubernetes.io/docs/reference/access-authn-authz/authentication. + +This is an advanced configuration option. During an end-user login flow, mistakes in this + +configuration will cause the user's login to fail. + +| *`tls`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-30-apis-concierge-authentication-v1alpha1-tlsspec[$$TLSSpec$$]__ | tls is the configuration for communicating with the OIDC provider via TLS. + |=== [id="{anchor_prefix}-go-pinniped-dev-generated-1-30-apis-concierge-authentication-v1alpha1-jwtauthenticatorstatus"] ==== JWTAuthenticatorStatus -Status of a JWT authenticator. +JWTAuthenticatorStatus is the status of a JWT authenticator. .Appears In: **** @@ -151,10 +237,22 @@ for Kubernetes access. [cols="25a,75a", options="header"] |=== | Field | Description -| *`groups`* __string__ | Groups is the name of the claim which should be read to extract the user's + +| *`groups`* __string__ | groups is the name of the claim which should be read to extract the user's + group membership from the JWT token. When not specified, it will default to "groups". + -| *`username`* __string__ | Username is the name of the claim which should be read to extract the + +| *`username`* __string__ | username is the name of the claim which should be read to extract the + username from the JWT token. When not specified, it will default to "username". + +| *`extra`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-30-apis-concierge-authentication-v1alpha1-extramapping[$$ExtraMapping$$] array__ | extra is similar to claimMappings.extra from Kubernetes AuthenticationConfiguration as documented in + +https://kubernetes.io/docs/reference/access-authn-authz/authentication. However, note that the + +Pinniped Concierge issues client certificates to users for the purpose of authenticating, and + +the Kubernetes API server does not have any mechanism for transmitting auth extras via client + +certificates. When configured, these extras will appear in client certificates issued by the + +Pinniped Supervisor in the x509 Subject field as Organizational Units (OU). However, when this + +client certificate is presented to Kubernetes for authentication, Kubernetes will ignore these + +extras. This is probably only useful if you are using a custom authenticating proxy in front + +of your Kubernetes API server which can translate these OUs into auth extras, as described by + +https://kubernetes.io/docs/reference/access-authn-authz/authentication/#authenticating-proxy. + +This is an advanced configuration option. During an end-user login flow, each of these CEL expressions + +must evaluate to either a string or an array of strings, or else the user's login will fail. + |=== @@ -178,6 +276,33 @@ Any changes to the CA bundle in the secret or configmap will be dynamically relo |=== +[id="{anchor_prefix}-go-pinniped-dev-generated-1-30-apis-concierge-authentication-v1alpha1-uservalidationrule"] +==== UserValidationRule + +UserValidationRule provides the configuration for a single user info validation rule. + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-30-apis-concierge-authentication-v1alpha1-jwtauthenticatorspec[$$JWTAuthenticatorSpec$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description +| *`expression`* __string__ | 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/ + +| *`message`* __string__ | message customizes the returned error message when rule returns false. + +message is a literal string. + +|=== + + [id="{anchor_prefix}-go-pinniped-dev-generated-1-30-apis-concierge-authentication-v1alpha1-webhookauthenticator"] ==== WebhookAuthenticator diff --git a/generated/1.30/apis/concierge/authentication/v1alpha1/types_jwtauthenticator.go b/generated/1.30/apis/concierge/authentication/v1alpha1/types_jwtauthenticator.go index a3b01c696..d5149642f 100644 --- a/generated/1.30/apis/concierge/authentication/v1alpha1/types_jwtauthenticator.go +++ b/generated/1.30/apis/concierge/authentication/v1alpha1/types_jwtauthenticator.go @@ -18,7 +18,7 @@ const ( JWTAuthenticatorPhaseError JWTAuthenticatorPhase = "Error" ) -// Status of a JWT authenticator. +// JWTAuthenticatorStatus is the status of a JWT authenticator. type JWTAuthenticatorStatus struct { // Represents the observations of the authenticator's current state. // +patchMergeKey=type @@ -26,46 +26,168 @@ type JWTAuthenticatorStatus struct { // +listType=map // +listMapKey=type Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"` + // Phase summarizes the overall status of the JWTAuthenticator. // +kubebuilder:default=Pending // +kubebuilder:validation:Enum=Pending;Ready;Error Phase JWTAuthenticatorPhase `json:"phase,omitempty"` } -// Spec for configuring a JWT authenticator. +// JWTAuthenticatorSpec is the spec for configuring a JWT authenticator. type JWTAuthenticatorSpec struct { - // Issuer is the OIDC issuer URL that will be used to discover public signing keys. Issuer is + // issuer is the OIDC issuer URL that will be used to discover public signing keys. Issuer is // also used to validate the "iss" JWT claim. // +kubebuilder:validation:MinLength=1 // +kubebuilder:validation:Pattern=`^https://` Issuer string `json:"issuer"` - // Audience is the required value of the "aud" JWT claim. + // audience is the required value of the "aud" JWT claim. // +kubebuilder:validation:MinLength=1 Audience string `json:"audience"` - // Claims allows customization of the claims that will be mapped to user identity + // claims allows customization of the claims that will be mapped to user identity // for Kubernetes access. // +optional Claims JWTTokenClaims `json:"claims"` - // TLS configuration for communicating with the OIDC provider. + // claimValidationRules are rules that are applied to validate token claims to authenticate users. + // This is similar to claimValidationRules from Kubernetes AuthenticationConfiguration as documented in + // https://kubernetes.io/docs/reference/access-authn-authz/authentication. + // This is an advanced configuration option. During an end-user login flow, mistakes in this + // configuration will cause the user's login to fail. + // +optional + ClaimValidationRules []ClaimValidationRule `json:"claimValidationRules,omitempty"` + + // 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. + // This is similar to claimValidationRules from Kubernetes AuthenticationConfiguration as documented in + // https://kubernetes.io/docs/reference/access-authn-authz/authentication. + // This is an advanced configuration option. During an end-user login flow, mistakes in this + // configuration will cause the user's login to fail. + // +optional + UserValidationRules []UserValidationRule `json:"userValidationRules,omitempty"` + + // tls is the configuration for communicating with the OIDC provider via TLS. // +optional TLS *TLSSpec `json:"tls,omitempty"` } +// ClaimValidationRule provides the configuration for a single claim validation rule. +type ClaimValidationRule struct { + // claim is the name of a required claim. + // Same as --oidc-required-claim flag. + // Only string claim keys are supported. + // Mutually exclusive with expression and message. + // +optional + Claim string `json:"claim,omitempty"` + + // requiredValue is the value of a required claim. + // Same as --oidc-required-claim flag. + // 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. + // Mutually exclusive with expression and message. + // +optional + 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.foo.bar'. + // 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"` +} + +// 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"` +} + // JWTTokenClaims allows customization of the claims that will be mapped to user identity // for Kubernetes access. type JWTTokenClaims struct { - // Groups is the name of the claim which should be read to extract the user's + // groups is the name of the claim which should be read to extract the user's // group membership from the JWT token. When not specified, it will default to "groups". // +optional Groups string `json:"groups"` - // Username is the name of the claim which should be read to extract the + // username is the name of the claim which should be read to extract the // username from the JWT token. When not specified, it will default to "username". // +optional Username string `json:"username"` + + // extra is similar to claimMappings.extra from Kubernetes AuthenticationConfiguration as documented in + // https://kubernetes.io/docs/reference/access-authn-authz/authentication. However, note that the + // Pinniped Concierge issues client certificates to users for the purpose of authenticating, and + // the Kubernetes API server does not have any mechanism for transmitting auth extras via client + // certificates. When configured, these extras will appear in client certificates issued by the + // Pinniped Supervisor in the x509 Subject field as Organizational Units (OU). However, when this + // client certificate is presented to Kubernetes for authentication, Kubernetes will ignore these + // extras. This is probably only useful if you are using a custom authenticating proxy in front + // of your Kubernetes API server which can translate these OUs into auth extras, as described by + // https://kubernetes.io/docs/reference/access-authn-authz/authentication/#authenticating-proxy. + // This is an advanced configuration option. During an end-user login flow, each of these CEL expressions + // must evaluate to either a string or an array of strings, or else the user's login will fail. + // +optional + Extra []ExtraMapping `json:"extra,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 to be unique. + // +required + 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.foo.bar'. + // + // Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ + // + // +required + ValueExpression string `json:"valueExpression"` } // JWTAuthenticator describes the configuration of a JWT authenticator. @@ -86,14 +208,14 @@ type JWTAuthenticator struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` - // Spec for configuring the authenticator. + // spec for configuring the authenticator. Spec JWTAuthenticatorSpec `json:"spec"` - // Status of the authenticator. + // status of the authenticator. Status JWTAuthenticatorStatus `json:"status,omitempty"` } -// List of JWTAuthenticator objects. +// JWTAuthenticatorList is a list of JWTAuthenticator objects. // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object type JWTAuthenticatorList struct { metav1.TypeMeta `json:",inline"` diff --git a/generated/1.30/apis/concierge/authentication/v1alpha1/zz_generated.deepcopy.go b/generated/1.30/apis/concierge/authentication/v1alpha1/zz_generated.deepcopy.go index 6b1235b88..cf8c0e61c 100644 --- a/generated/1.30/apis/concierge/authentication/v1alpha1/zz_generated.deepcopy.go +++ b/generated/1.30/apis/concierge/authentication/v1alpha1/zz_generated.deepcopy.go @@ -29,6 +29,38 @@ func (in *CertificateAuthorityDataSourceSpec) DeepCopy() *CertificateAuthorityDa return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ClaimValidationRule) DeepCopyInto(out *ClaimValidationRule) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClaimValidationRule. +func (in *ClaimValidationRule) DeepCopy() *ClaimValidationRule { + if in == nil { + return nil + } + out := new(ClaimValidationRule) + in.DeepCopyInto(out) + return out +} + +// 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. func (in *JWTAuthenticator) DeepCopyInto(out *JWTAuthenticator) { *out = *in @@ -93,7 +125,17 @@ func (in *JWTAuthenticatorList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *JWTAuthenticatorSpec) DeepCopyInto(out *JWTAuthenticatorSpec) { *out = *in - out.Claims = in.Claims + in.Claims.DeepCopyInto(&out.Claims) + if in.ClaimValidationRules != nil { + in, out := &in.ClaimValidationRules, &out.ClaimValidationRules + *out = make([]ClaimValidationRule, len(*in)) + copy(*out, *in) + } + if in.UserValidationRules != nil { + in, out := &in.UserValidationRules, &out.UserValidationRules + *out = make([]UserValidationRule, len(*in)) + copy(*out, *in) + } if in.TLS != nil { in, out := &in.TLS, &out.TLS *out = new(TLSSpec) @@ -138,6 +180,11 @@ func (in *JWTAuthenticatorStatus) DeepCopy() *JWTAuthenticatorStatus { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *JWTTokenClaims) DeepCopyInto(out *JWTTokenClaims) { *out = *in + if in.Extra != nil { + in, out := &in.Extra, &out.Extra + *out = make([]ExtraMapping, len(*in)) + copy(*out, *in) + } return } @@ -172,6 +219,22 @@ func (in *TLSSpec) DeepCopy() *TLSSpec { 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. func (in *WebhookAuthenticator) DeepCopyInto(out *WebhookAuthenticator) { *out = *in diff --git a/generated/1.30/crds/authentication.concierge.pinniped.dev_jwtauthenticators.yaml b/generated/1.30/crds/authentication.concierge.pinniped.dev_jwtauthenticators.yaml index 2ef6312a0..cbb567616 100644 --- a/generated/1.30/crds/authentication.concierge.pinniped.dev_jwtauthenticators.yaml +++ b/generated/1.30/crds/authentication.concierge.pinniped.dev_jwtauthenticators.yaml @@ -58,37 +58,133 @@ spec: metadata: type: object spec: - description: Spec for configuring the authenticator. + description: spec for configuring the authenticator. properties: audience: - description: Audience is the required value of the "aud" JWT claim. + description: audience is the required value of the "aud" JWT claim. minLength: 1 type: string + claimValidationRules: + description: |- + claimValidationRules are rules that are applied to validate token claims to authenticate users. + This is similar to claimValidationRules from Kubernetes AuthenticationConfiguration as documented in + https://kubernetes.io/docs/reference/access-authn-authz/authentication. + This is an advanced configuration option. During an end-user login flow, mistakes in this + configuration will cause the user's login to fail. + items: + description: ClaimValidationRule provides the configuration for + a single claim validation rule. + properties: + claim: + description: |- + claim is the name of a required claim. + Same as --oidc-required-claim flag. + Only string claim keys are supported. + Mutually exclusive with expression and message. + type: string + expression: + description: |- + 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.foo.bar'. + 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. + type: string + message: + description: |- + message customizes the returned error message when expression returns false. + message is a literal string. + Mutually exclusive with claim and requiredValue. + type: string + requiredValue: + description: |- + requiredValue is the value of a required claim. + Same as --oidc-required-claim flag. + 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. + Mutually exclusive with expression and message. + type: string + type: object + type: array claims: description: |- - Claims allows customization of the claims that will be mapped to user identity + claims allows customization of the claims that will be mapped to user identity for Kubernetes access. properties: + extra: + description: |- + extra is similar to claimMappings.extra from Kubernetes AuthenticationConfiguration as documented in + https://kubernetes.io/docs/reference/access-authn-authz/authentication. However, note that the + Pinniped Concierge issues client certificates to users for the purpose of authenticating, and + the Kubernetes API server does not have any mechanism for transmitting auth extras via client + certificates. When configured, these extras will appear in client certificates issued by the + Pinniped Supervisor in the x509 Subject field as Organizational Units (OU). However, when this + client certificate is presented to Kubernetes for authentication, Kubernetes will ignore these + extras. This is probably only useful if you are using a custom authenticating proxy in front + of your Kubernetes API server which can translate these OUs into auth extras, as described by + https://kubernetes.io/docs/reference/access-authn-authz/authentication/#authenticating-proxy. + This is an advanced configuration option. During an end-user login flow, each of these CEL expressions + must evaluate to either a string or an array of strings, or else the user's login will fail. + items: + description: ExtraMapping provides the configuration for a single + extra mapping. + properties: + key: + description: |- + 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 to be unique. + type: string + valueExpression: + description: |- + 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.foo.bar'. + + Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ + type: string + required: + - key + - valueExpression + type: object + type: array groups: description: |- - Groups is the name of the claim which should be read to extract the user's + groups is the name of the claim which should be read to extract the user's group membership from the JWT token. When not specified, it will default to "groups". type: string username: description: |- - Username is the name of the claim which should be read to extract the + username is the name of the claim which should be read to extract the username from the JWT token. When not specified, it will default to "username". type: string type: object issuer: description: |- - Issuer is the OIDC issuer URL that will be used to discover public signing keys. Issuer is + issuer is the OIDC issuer URL that will be used to discover public signing keys. Issuer is also used to validate the "iss" JWT claim. minLength: 1 pattern: ^https:// type: string tls: - description: TLS configuration for communicating with the OIDC provider. + description: tls is the configuration for communicating with the OIDC + provider via TLS. properties: certificateAuthorityData: description: X.509 Certificate Authority (base64-encoded PEM bundle). @@ -128,12 +224,47 @@ spec: - name type: object type: object + userValidationRules: + description: |- + 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. + This is similar to claimValidationRules from Kubernetes AuthenticationConfiguration as documented in + https://kubernetes.io/docs/reference/access-authn-authz/authentication. + This is an advanced configuration option. During an end-user login flow, mistakes in this + configuration will cause the user's login to fail. + items: + description: UserValidationRule provides the configuration for a + single user info validation rule. + properties: + expression: + description: |- + 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/ + type: string + message: + description: |- + message customizes the returned error message when rule returns false. + message is a literal string. + type: string + required: + - expression + type: object + type: array required: - audience - issuer type: object status: - description: Status of the authenticator. + description: status of the authenticator. properties: conditions: description: Represents the observations of the authenticator's current diff --git a/generated/1.31/README.adoc b/generated/1.31/README.adoc index 3efbeb1b6..870693cb6 100644 --- a/generated/1.31/README.adoc +++ b/generated/1.31/README.adoc @@ -60,6 +60,79 @@ certificate bundle. + |=== +[id="{anchor_prefix}-go-pinniped-dev-generated-1-31-apis-concierge-authentication-v1alpha1-claimvalidationrule"] +==== ClaimValidationRule + +ClaimValidationRule provides the configuration for a single claim validation rule. + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-31-apis-concierge-authentication-v1alpha1-jwtauthenticatorspec[$$JWTAuthenticatorSpec$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description +| *`claim`* __string__ | claim is the name of a required claim. + +Same as --oidc-required-claim flag. + +Only string claim keys are supported. + +Mutually exclusive with expression and message. + +| *`requiredValue`* __string__ | requiredValue is the value of a required claim. + +Same as --oidc-required-claim flag. + +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. + +Mutually exclusive with expression and message. + +| *`expression`* __string__ | 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.foo.bar'. + +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. + +| *`message`* __string__ | message customizes the returned error message when expression returns false. + +message is a literal string. + +Mutually exclusive with claim and requiredValue. + +|=== + + +[id="{anchor_prefix}-go-pinniped-dev-generated-1-31-apis-concierge-authentication-v1alpha1-extramapping"] +==== ExtraMapping + +ExtraMapping provides the configuration for a single extra mapping. + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-31-apis-concierge-authentication-v1alpha1-jwttokenclaims[$$JWTTokenClaims$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description +| *`key`* __string__ | 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 to be unique. + +| *`valueExpression`* __string__ | 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.foo.bar'. + + +Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ + +|=== + + [id="{anchor_prefix}-go-pinniped-dev-generated-1-31-apis-concierge-authentication-v1alpha1-jwtauthenticator"] ==== JWTAuthenticator @@ -78,8 +151,8 @@ signature, existence of claims, etc.) and extract the username and groups from t | Field | Description | *`metadata`* __link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.31/#objectmeta-v1-meta[$$ObjectMeta$$]__ | Refer to Kubernetes API documentation for fields of `metadata`. -| *`spec`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-31-apis-concierge-authentication-v1alpha1-jwtauthenticatorspec[$$JWTAuthenticatorSpec$$]__ | Spec for configuring the authenticator. + -| *`status`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-31-apis-concierge-authentication-v1alpha1-jwtauthenticatorstatus[$$JWTAuthenticatorStatus$$]__ | Status of the authenticator. + +| *`spec`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-31-apis-concierge-authentication-v1alpha1-jwtauthenticatorspec[$$JWTAuthenticatorSpec$$]__ | spec for configuring the authenticator. + +| *`status`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-31-apis-concierge-authentication-v1alpha1-jwtauthenticatorstatus[$$JWTAuthenticatorStatus$$]__ | status of the authenticator. + |=== @@ -100,7 +173,7 @@ signature, existence of claims, etc.) and extract the username and groups from t [id="{anchor_prefix}-go-pinniped-dev-generated-1-31-apis-concierge-authentication-v1alpha1-jwtauthenticatorspec"] ==== JWTAuthenticatorSpec -Spec for configuring a JWT authenticator. +JWTAuthenticatorSpec is the spec for configuring a JWT authenticator. .Appears In: **** @@ -110,19 +183,32 @@ Spec for configuring a JWT authenticator. [cols="25a,75a", options="header"] |=== | Field | Description -| *`issuer`* __string__ | Issuer is the OIDC issuer URL that will be used to discover public signing keys. Issuer is + +| *`issuer`* __string__ | issuer is the OIDC issuer URL that will be used to discover public signing keys. Issuer is + also used to validate the "iss" JWT claim. + -| *`audience`* __string__ | Audience is the required value of the "aud" JWT claim. + -| *`claims`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-31-apis-concierge-authentication-v1alpha1-jwttokenclaims[$$JWTTokenClaims$$]__ | Claims allows customization of the claims that will be mapped to user identity + +| *`audience`* __string__ | audience is the required value of the "aud" JWT claim. + +| *`claims`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-31-apis-concierge-authentication-v1alpha1-jwttokenclaims[$$JWTTokenClaims$$]__ | claims allows customization of the claims that will be mapped to user identity + for Kubernetes access. + -| *`tls`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-31-apis-concierge-authentication-v1alpha1-tlsspec[$$TLSSpec$$]__ | TLS configuration for communicating with the OIDC provider. + +| *`claimValidationRules`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-31-apis-concierge-authentication-v1alpha1-claimvalidationrule[$$ClaimValidationRule$$] array__ | claimValidationRules are rules that are applied to validate token claims to authenticate users. + +This is similar to claimValidationRules from Kubernetes AuthenticationConfiguration as documented in + +https://kubernetes.io/docs/reference/access-authn-authz/authentication. + +This is an advanced configuration option. During an end-user login flow, mistakes in this + +configuration will cause the user's login to fail. + +| *`userValidationRules`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-31-apis-concierge-authentication-v1alpha1-uservalidationrule[$$UserValidationRule$$] array__ | 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. + +This is similar to claimValidationRules from Kubernetes AuthenticationConfiguration as documented in + +https://kubernetes.io/docs/reference/access-authn-authz/authentication. + +This is an advanced configuration option. During an end-user login flow, mistakes in this + +configuration will cause the user's login to fail. + +| *`tls`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-31-apis-concierge-authentication-v1alpha1-tlsspec[$$TLSSpec$$]__ | tls is the configuration for communicating with the OIDC provider via TLS. + |=== [id="{anchor_prefix}-go-pinniped-dev-generated-1-31-apis-concierge-authentication-v1alpha1-jwtauthenticatorstatus"] ==== JWTAuthenticatorStatus -Status of a JWT authenticator. +JWTAuthenticatorStatus is the status of a JWT authenticator. .Appears In: **** @@ -151,10 +237,22 @@ for Kubernetes access. [cols="25a,75a", options="header"] |=== | Field | Description -| *`groups`* __string__ | Groups is the name of the claim which should be read to extract the user's + +| *`groups`* __string__ | groups is the name of the claim which should be read to extract the user's + group membership from the JWT token. When not specified, it will default to "groups". + -| *`username`* __string__ | Username is the name of the claim which should be read to extract the + +| *`username`* __string__ | username is the name of the claim which should be read to extract the + username from the JWT token. When not specified, it will default to "username". + +| *`extra`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-31-apis-concierge-authentication-v1alpha1-extramapping[$$ExtraMapping$$] array__ | extra is similar to claimMappings.extra from Kubernetes AuthenticationConfiguration as documented in + +https://kubernetes.io/docs/reference/access-authn-authz/authentication. However, note that the + +Pinniped Concierge issues client certificates to users for the purpose of authenticating, and + +the Kubernetes API server does not have any mechanism for transmitting auth extras via client + +certificates. When configured, these extras will appear in client certificates issued by the + +Pinniped Supervisor in the x509 Subject field as Organizational Units (OU). However, when this + +client certificate is presented to Kubernetes for authentication, Kubernetes will ignore these + +extras. This is probably only useful if you are using a custom authenticating proxy in front + +of your Kubernetes API server which can translate these OUs into auth extras, as described by + +https://kubernetes.io/docs/reference/access-authn-authz/authentication/#authenticating-proxy. + +This is an advanced configuration option. During an end-user login flow, each of these CEL expressions + +must evaluate to either a string or an array of strings, or else the user's login will fail. + |=== @@ -178,6 +276,33 @@ Any changes to the CA bundle in the secret or configmap will be dynamically relo |=== +[id="{anchor_prefix}-go-pinniped-dev-generated-1-31-apis-concierge-authentication-v1alpha1-uservalidationrule"] +==== UserValidationRule + +UserValidationRule provides the configuration for a single user info validation rule. + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-31-apis-concierge-authentication-v1alpha1-jwtauthenticatorspec[$$JWTAuthenticatorSpec$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description +| *`expression`* __string__ | 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/ + +| *`message`* __string__ | message customizes the returned error message when rule returns false. + +message is a literal string. + +|=== + + [id="{anchor_prefix}-go-pinniped-dev-generated-1-31-apis-concierge-authentication-v1alpha1-webhookauthenticator"] ==== WebhookAuthenticator diff --git a/generated/1.31/apis/concierge/authentication/v1alpha1/types_jwtauthenticator.go b/generated/1.31/apis/concierge/authentication/v1alpha1/types_jwtauthenticator.go index a3b01c696..d5149642f 100644 --- a/generated/1.31/apis/concierge/authentication/v1alpha1/types_jwtauthenticator.go +++ b/generated/1.31/apis/concierge/authentication/v1alpha1/types_jwtauthenticator.go @@ -18,7 +18,7 @@ const ( JWTAuthenticatorPhaseError JWTAuthenticatorPhase = "Error" ) -// Status of a JWT authenticator. +// JWTAuthenticatorStatus is the status of a JWT authenticator. type JWTAuthenticatorStatus struct { // Represents the observations of the authenticator's current state. // +patchMergeKey=type @@ -26,46 +26,168 @@ type JWTAuthenticatorStatus struct { // +listType=map // +listMapKey=type Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"` + // Phase summarizes the overall status of the JWTAuthenticator. // +kubebuilder:default=Pending // +kubebuilder:validation:Enum=Pending;Ready;Error Phase JWTAuthenticatorPhase `json:"phase,omitempty"` } -// Spec for configuring a JWT authenticator. +// JWTAuthenticatorSpec is the spec for configuring a JWT authenticator. type JWTAuthenticatorSpec struct { - // Issuer is the OIDC issuer URL that will be used to discover public signing keys. Issuer is + // issuer is the OIDC issuer URL that will be used to discover public signing keys. Issuer is // also used to validate the "iss" JWT claim. // +kubebuilder:validation:MinLength=1 // +kubebuilder:validation:Pattern=`^https://` Issuer string `json:"issuer"` - // Audience is the required value of the "aud" JWT claim. + // audience is the required value of the "aud" JWT claim. // +kubebuilder:validation:MinLength=1 Audience string `json:"audience"` - // Claims allows customization of the claims that will be mapped to user identity + // claims allows customization of the claims that will be mapped to user identity // for Kubernetes access. // +optional Claims JWTTokenClaims `json:"claims"` - // TLS configuration for communicating with the OIDC provider. + // claimValidationRules are rules that are applied to validate token claims to authenticate users. + // This is similar to claimValidationRules from Kubernetes AuthenticationConfiguration as documented in + // https://kubernetes.io/docs/reference/access-authn-authz/authentication. + // This is an advanced configuration option. During an end-user login flow, mistakes in this + // configuration will cause the user's login to fail. + // +optional + ClaimValidationRules []ClaimValidationRule `json:"claimValidationRules,omitempty"` + + // 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. + // This is similar to claimValidationRules from Kubernetes AuthenticationConfiguration as documented in + // https://kubernetes.io/docs/reference/access-authn-authz/authentication. + // This is an advanced configuration option. During an end-user login flow, mistakes in this + // configuration will cause the user's login to fail. + // +optional + UserValidationRules []UserValidationRule `json:"userValidationRules,omitempty"` + + // tls is the configuration for communicating with the OIDC provider via TLS. // +optional TLS *TLSSpec `json:"tls,omitempty"` } +// ClaimValidationRule provides the configuration for a single claim validation rule. +type ClaimValidationRule struct { + // claim is the name of a required claim. + // Same as --oidc-required-claim flag. + // Only string claim keys are supported. + // Mutually exclusive with expression and message. + // +optional + Claim string `json:"claim,omitempty"` + + // requiredValue is the value of a required claim. + // Same as --oidc-required-claim flag. + // 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. + // Mutually exclusive with expression and message. + // +optional + 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.foo.bar'. + // 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"` +} + +// 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"` +} + // JWTTokenClaims allows customization of the claims that will be mapped to user identity // for Kubernetes access. type JWTTokenClaims struct { - // Groups is the name of the claim which should be read to extract the user's + // groups is the name of the claim which should be read to extract the user's // group membership from the JWT token. When not specified, it will default to "groups". // +optional Groups string `json:"groups"` - // Username is the name of the claim which should be read to extract the + // username is the name of the claim which should be read to extract the // username from the JWT token. When not specified, it will default to "username". // +optional Username string `json:"username"` + + // extra is similar to claimMappings.extra from Kubernetes AuthenticationConfiguration as documented in + // https://kubernetes.io/docs/reference/access-authn-authz/authentication. However, note that the + // Pinniped Concierge issues client certificates to users for the purpose of authenticating, and + // the Kubernetes API server does not have any mechanism for transmitting auth extras via client + // certificates. When configured, these extras will appear in client certificates issued by the + // Pinniped Supervisor in the x509 Subject field as Organizational Units (OU). However, when this + // client certificate is presented to Kubernetes for authentication, Kubernetes will ignore these + // extras. This is probably only useful if you are using a custom authenticating proxy in front + // of your Kubernetes API server which can translate these OUs into auth extras, as described by + // https://kubernetes.io/docs/reference/access-authn-authz/authentication/#authenticating-proxy. + // This is an advanced configuration option. During an end-user login flow, each of these CEL expressions + // must evaluate to either a string or an array of strings, or else the user's login will fail. + // +optional + Extra []ExtraMapping `json:"extra,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 to be unique. + // +required + 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.foo.bar'. + // + // Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ + // + // +required + ValueExpression string `json:"valueExpression"` } // JWTAuthenticator describes the configuration of a JWT authenticator. @@ -86,14 +208,14 @@ type JWTAuthenticator struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` - // Spec for configuring the authenticator. + // spec for configuring the authenticator. Spec JWTAuthenticatorSpec `json:"spec"` - // Status of the authenticator. + // status of the authenticator. Status JWTAuthenticatorStatus `json:"status,omitempty"` } -// List of JWTAuthenticator objects. +// JWTAuthenticatorList is a list of JWTAuthenticator objects. // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object type JWTAuthenticatorList struct { metav1.TypeMeta `json:",inline"` diff --git a/generated/1.31/apis/concierge/authentication/v1alpha1/zz_generated.deepcopy.go b/generated/1.31/apis/concierge/authentication/v1alpha1/zz_generated.deepcopy.go index 6b1235b88..cf8c0e61c 100644 --- a/generated/1.31/apis/concierge/authentication/v1alpha1/zz_generated.deepcopy.go +++ b/generated/1.31/apis/concierge/authentication/v1alpha1/zz_generated.deepcopy.go @@ -29,6 +29,38 @@ func (in *CertificateAuthorityDataSourceSpec) DeepCopy() *CertificateAuthorityDa return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ClaimValidationRule) DeepCopyInto(out *ClaimValidationRule) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClaimValidationRule. +func (in *ClaimValidationRule) DeepCopy() *ClaimValidationRule { + if in == nil { + return nil + } + out := new(ClaimValidationRule) + in.DeepCopyInto(out) + return out +} + +// 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. func (in *JWTAuthenticator) DeepCopyInto(out *JWTAuthenticator) { *out = *in @@ -93,7 +125,17 @@ func (in *JWTAuthenticatorList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *JWTAuthenticatorSpec) DeepCopyInto(out *JWTAuthenticatorSpec) { *out = *in - out.Claims = in.Claims + in.Claims.DeepCopyInto(&out.Claims) + if in.ClaimValidationRules != nil { + in, out := &in.ClaimValidationRules, &out.ClaimValidationRules + *out = make([]ClaimValidationRule, len(*in)) + copy(*out, *in) + } + if in.UserValidationRules != nil { + in, out := &in.UserValidationRules, &out.UserValidationRules + *out = make([]UserValidationRule, len(*in)) + copy(*out, *in) + } if in.TLS != nil { in, out := &in.TLS, &out.TLS *out = new(TLSSpec) @@ -138,6 +180,11 @@ func (in *JWTAuthenticatorStatus) DeepCopy() *JWTAuthenticatorStatus { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *JWTTokenClaims) DeepCopyInto(out *JWTTokenClaims) { *out = *in + if in.Extra != nil { + in, out := &in.Extra, &out.Extra + *out = make([]ExtraMapping, len(*in)) + copy(*out, *in) + } return } @@ -172,6 +219,22 @@ func (in *TLSSpec) DeepCopy() *TLSSpec { 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. func (in *WebhookAuthenticator) DeepCopyInto(out *WebhookAuthenticator) { *out = *in diff --git a/generated/1.31/crds/authentication.concierge.pinniped.dev_jwtauthenticators.yaml b/generated/1.31/crds/authentication.concierge.pinniped.dev_jwtauthenticators.yaml index 2ef6312a0..cbb567616 100644 --- a/generated/1.31/crds/authentication.concierge.pinniped.dev_jwtauthenticators.yaml +++ b/generated/1.31/crds/authentication.concierge.pinniped.dev_jwtauthenticators.yaml @@ -58,37 +58,133 @@ spec: metadata: type: object spec: - description: Spec for configuring the authenticator. + description: spec for configuring the authenticator. properties: audience: - description: Audience is the required value of the "aud" JWT claim. + description: audience is the required value of the "aud" JWT claim. minLength: 1 type: string + claimValidationRules: + description: |- + claimValidationRules are rules that are applied to validate token claims to authenticate users. + This is similar to claimValidationRules from Kubernetes AuthenticationConfiguration as documented in + https://kubernetes.io/docs/reference/access-authn-authz/authentication. + This is an advanced configuration option. During an end-user login flow, mistakes in this + configuration will cause the user's login to fail. + items: + description: ClaimValidationRule provides the configuration for + a single claim validation rule. + properties: + claim: + description: |- + claim is the name of a required claim. + Same as --oidc-required-claim flag. + Only string claim keys are supported. + Mutually exclusive with expression and message. + type: string + expression: + description: |- + 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.foo.bar'. + 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. + type: string + message: + description: |- + message customizes the returned error message when expression returns false. + message is a literal string. + Mutually exclusive with claim and requiredValue. + type: string + requiredValue: + description: |- + requiredValue is the value of a required claim. + Same as --oidc-required-claim flag. + 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. + Mutually exclusive with expression and message. + type: string + type: object + type: array claims: description: |- - Claims allows customization of the claims that will be mapped to user identity + claims allows customization of the claims that will be mapped to user identity for Kubernetes access. properties: + extra: + description: |- + extra is similar to claimMappings.extra from Kubernetes AuthenticationConfiguration as documented in + https://kubernetes.io/docs/reference/access-authn-authz/authentication. However, note that the + Pinniped Concierge issues client certificates to users for the purpose of authenticating, and + the Kubernetes API server does not have any mechanism for transmitting auth extras via client + certificates. When configured, these extras will appear in client certificates issued by the + Pinniped Supervisor in the x509 Subject field as Organizational Units (OU). However, when this + client certificate is presented to Kubernetes for authentication, Kubernetes will ignore these + extras. This is probably only useful if you are using a custom authenticating proxy in front + of your Kubernetes API server which can translate these OUs into auth extras, as described by + https://kubernetes.io/docs/reference/access-authn-authz/authentication/#authenticating-proxy. + This is an advanced configuration option. During an end-user login flow, each of these CEL expressions + must evaluate to either a string or an array of strings, or else the user's login will fail. + items: + description: ExtraMapping provides the configuration for a single + extra mapping. + properties: + key: + description: |- + 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 to be unique. + type: string + valueExpression: + description: |- + 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.foo.bar'. + + Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ + type: string + required: + - key + - valueExpression + type: object + type: array groups: description: |- - Groups is the name of the claim which should be read to extract the user's + groups is the name of the claim which should be read to extract the user's group membership from the JWT token. When not specified, it will default to "groups". type: string username: description: |- - Username is the name of the claim which should be read to extract the + username is the name of the claim which should be read to extract the username from the JWT token. When not specified, it will default to "username". type: string type: object issuer: description: |- - Issuer is the OIDC issuer URL that will be used to discover public signing keys. Issuer is + issuer is the OIDC issuer URL that will be used to discover public signing keys. Issuer is also used to validate the "iss" JWT claim. minLength: 1 pattern: ^https:// type: string tls: - description: TLS configuration for communicating with the OIDC provider. + description: tls is the configuration for communicating with the OIDC + provider via TLS. properties: certificateAuthorityData: description: X.509 Certificate Authority (base64-encoded PEM bundle). @@ -128,12 +224,47 @@ spec: - name type: object type: object + userValidationRules: + description: |- + 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. + This is similar to claimValidationRules from Kubernetes AuthenticationConfiguration as documented in + https://kubernetes.io/docs/reference/access-authn-authz/authentication. + This is an advanced configuration option. During an end-user login flow, mistakes in this + configuration will cause the user's login to fail. + items: + description: UserValidationRule provides the configuration for a + single user info validation rule. + properties: + expression: + description: |- + 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/ + type: string + message: + description: |- + message customizes the returned error message when rule returns false. + message is a literal string. + type: string + required: + - expression + type: object + type: array required: - audience - issuer type: object status: - description: Status of the authenticator. + description: status of the authenticator. properties: conditions: description: Represents the observations of the authenticator's current diff --git a/generated/1.32/README.adoc b/generated/1.32/README.adoc index 5c9bed6a3..3e4990deb 100644 --- a/generated/1.32/README.adoc +++ b/generated/1.32/README.adoc @@ -60,6 +60,79 @@ certificate bundle. + |=== +[id="{anchor_prefix}-go-pinniped-dev-generated-1-32-apis-concierge-authentication-v1alpha1-claimvalidationrule"] +==== ClaimValidationRule + +ClaimValidationRule provides the configuration for a single claim validation rule. + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-32-apis-concierge-authentication-v1alpha1-jwtauthenticatorspec[$$JWTAuthenticatorSpec$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description +| *`claim`* __string__ | claim is the name of a required claim. + +Same as --oidc-required-claim flag. + +Only string claim keys are supported. + +Mutually exclusive with expression and message. + +| *`requiredValue`* __string__ | requiredValue is the value of a required claim. + +Same as --oidc-required-claim flag. + +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. + +Mutually exclusive with expression and message. + +| *`expression`* __string__ | 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.foo.bar'. + +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. + +| *`message`* __string__ | message customizes the returned error message when expression returns false. + +message is a literal string. + +Mutually exclusive with claim and requiredValue. + +|=== + + +[id="{anchor_prefix}-go-pinniped-dev-generated-1-32-apis-concierge-authentication-v1alpha1-extramapping"] +==== ExtraMapping + +ExtraMapping provides the configuration for a single extra mapping. + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-32-apis-concierge-authentication-v1alpha1-jwttokenclaims[$$JWTTokenClaims$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description +| *`key`* __string__ | 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 to be unique. + +| *`valueExpression`* __string__ | 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.foo.bar'. + + +Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ + +|=== + + [id="{anchor_prefix}-go-pinniped-dev-generated-1-32-apis-concierge-authentication-v1alpha1-jwtauthenticator"] ==== JWTAuthenticator @@ -78,8 +151,8 @@ signature, existence of claims, etc.) and extract the username and groups from t | Field | Description | *`metadata`* __link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.32/#objectmeta-v1-meta[$$ObjectMeta$$]__ | Refer to Kubernetes API documentation for fields of `metadata`. -| *`spec`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-32-apis-concierge-authentication-v1alpha1-jwtauthenticatorspec[$$JWTAuthenticatorSpec$$]__ | Spec for configuring the authenticator. + -| *`status`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-32-apis-concierge-authentication-v1alpha1-jwtauthenticatorstatus[$$JWTAuthenticatorStatus$$]__ | Status of the authenticator. + +| *`spec`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-32-apis-concierge-authentication-v1alpha1-jwtauthenticatorspec[$$JWTAuthenticatorSpec$$]__ | spec for configuring the authenticator. + +| *`status`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-32-apis-concierge-authentication-v1alpha1-jwtauthenticatorstatus[$$JWTAuthenticatorStatus$$]__ | status of the authenticator. + |=== @@ -100,7 +173,7 @@ signature, existence of claims, etc.) and extract the username and groups from t [id="{anchor_prefix}-go-pinniped-dev-generated-1-32-apis-concierge-authentication-v1alpha1-jwtauthenticatorspec"] ==== JWTAuthenticatorSpec -Spec for configuring a JWT authenticator. +JWTAuthenticatorSpec is the spec for configuring a JWT authenticator. .Appears In: **** @@ -110,19 +183,32 @@ Spec for configuring a JWT authenticator. [cols="25a,75a", options="header"] |=== | Field | Description -| *`issuer`* __string__ | Issuer is the OIDC issuer URL that will be used to discover public signing keys. Issuer is + +| *`issuer`* __string__ | issuer is the OIDC issuer URL that will be used to discover public signing keys. Issuer is + also used to validate the "iss" JWT claim. + -| *`audience`* __string__ | Audience is the required value of the "aud" JWT claim. + -| *`claims`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-32-apis-concierge-authentication-v1alpha1-jwttokenclaims[$$JWTTokenClaims$$]__ | Claims allows customization of the claims that will be mapped to user identity + +| *`audience`* __string__ | audience is the required value of the "aud" JWT claim. + +| *`claims`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-32-apis-concierge-authentication-v1alpha1-jwttokenclaims[$$JWTTokenClaims$$]__ | claims allows customization of the claims that will be mapped to user identity + for Kubernetes access. + -| *`tls`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-32-apis-concierge-authentication-v1alpha1-tlsspec[$$TLSSpec$$]__ | TLS configuration for communicating with the OIDC provider. + +| *`claimValidationRules`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-32-apis-concierge-authentication-v1alpha1-claimvalidationrule[$$ClaimValidationRule$$] array__ | claimValidationRules are rules that are applied to validate token claims to authenticate users. + +This is similar to claimValidationRules from Kubernetes AuthenticationConfiguration as documented in + +https://kubernetes.io/docs/reference/access-authn-authz/authentication. + +This is an advanced configuration option. During an end-user login flow, mistakes in this + +configuration will cause the user's login to fail. + +| *`userValidationRules`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-32-apis-concierge-authentication-v1alpha1-uservalidationrule[$$UserValidationRule$$] array__ | 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. + +This is similar to claimValidationRules from Kubernetes AuthenticationConfiguration as documented in + +https://kubernetes.io/docs/reference/access-authn-authz/authentication. + +This is an advanced configuration option. During an end-user login flow, mistakes in this + +configuration will cause the user's login to fail. + +| *`tls`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-32-apis-concierge-authentication-v1alpha1-tlsspec[$$TLSSpec$$]__ | tls is the configuration for communicating with the OIDC provider via TLS. + |=== [id="{anchor_prefix}-go-pinniped-dev-generated-1-32-apis-concierge-authentication-v1alpha1-jwtauthenticatorstatus"] ==== JWTAuthenticatorStatus -Status of a JWT authenticator. +JWTAuthenticatorStatus is the status of a JWT authenticator. .Appears In: **** @@ -151,10 +237,22 @@ for Kubernetes access. [cols="25a,75a", options="header"] |=== | Field | Description -| *`groups`* __string__ | Groups is the name of the claim which should be read to extract the user's + +| *`groups`* __string__ | groups is the name of the claim which should be read to extract the user's + group membership from the JWT token. When not specified, it will default to "groups". + -| *`username`* __string__ | Username is the name of the claim which should be read to extract the + +| *`username`* __string__ | username is the name of the claim which should be read to extract the + username from the JWT token. When not specified, it will default to "username". + +| *`extra`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-32-apis-concierge-authentication-v1alpha1-extramapping[$$ExtraMapping$$] array__ | extra is similar to claimMappings.extra from Kubernetes AuthenticationConfiguration as documented in + +https://kubernetes.io/docs/reference/access-authn-authz/authentication. However, note that the + +Pinniped Concierge issues client certificates to users for the purpose of authenticating, and + +the Kubernetes API server does not have any mechanism for transmitting auth extras via client + +certificates. When configured, these extras will appear in client certificates issued by the + +Pinniped Supervisor in the x509 Subject field as Organizational Units (OU). However, when this + +client certificate is presented to Kubernetes for authentication, Kubernetes will ignore these + +extras. This is probably only useful if you are using a custom authenticating proxy in front + +of your Kubernetes API server which can translate these OUs into auth extras, as described by + +https://kubernetes.io/docs/reference/access-authn-authz/authentication/#authenticating-proxy. + +This is an advanced configuration option. During an end-user login flow, each of these CEL expressions + +must evaluate to either a string or an array of strings, or else the user's login will fail. + |=== @@ -178,6 +276,33 @@ Any changes to the CA bundle in the secret or configmap will be dynamically relo |=== +[id="{anchor_prefix}-go-pinniped-dev-generated-1-32-apis-concierge-authentication-v1alpha1-uservalidationrule"] +==== UserValidationRule + +UserValidationRule provides the configuration for a single user info validation rule. + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-32-apis-concierge-authentication-v1alpha1-jwtauthenticatorspec[$$JWTAuthenticatorSpec$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description +| *`expression`* __string__ | 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/ + +| *`message`* __string__ | message customizes the returned error message when rule returns false. + +message is a literal string. + +|=== + + [id="{anchor_prefix}-go-pinniped-dev-generated-1-32-apis-concierge-authentication-v1alpha1-webhookauthenticator"] ==== WebhookAuthenticator diff --git a/generated/1.32/apis/concierge/authentication/v1alpha1/types_jwtauthenticator.go b/generated/1.32/apis/concierge/authentication/v1alpha1/types_jwtauthenticator.go index a3b01c696..d5149642f 100644 --- a/generated/1.32/apis/concierge/authentication/v1alpha1/types_jwtauthenticator.go +++ b/generated/1.32/apis/concierge/authentication/v1alpha1/types_jwtauthenticator.go @@ -18,7 +18,7 @@ const ( JWTAuthenticatorPhaseError JWTAuthenticatorPhase = "Error" ) -// Status of a JWT authenticator. +// JWTAuthenticatorStatus is the status of a JWT authenticator. type JWTAuthenticatorStatus struct { // Represents the observations of the authenticator's current state. // +patchMergeKey=type @@ -26,46 +26,168 @@ type JWTAuthenticatorStatus struct { // +listType=map // +listMapKey=type Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"` + // Phase summarizes the overall status of the JWTAuthenticator. // +kubebuilder:default=Pending // +kubebuilder:validation:Enum=Pending;Ready;Error Phase JWTAuthenticatorPhase `json:"phase,omitempty"` } -// Spec for configuring a JWT authenticator. +// JWTAuthenticatorSpec is the spec for configuring a JWT authenticator. type JWTAuthenticatorSpec struct { - // Issuer is the OIDC issuer URL that will be used to discover public signing keys. Issuer is + // issuer is the OIDC issuer URL that will be used to discover public signing keys. Issuer is // also used to validate the "iss" JWT claim. // +kubebuilder:validation:MinLength=1 // +kubebuilder:validation:Pattern=`^https://` Issuer string `json:"issuer"` - // Audience is the required value of the "aud" JWT claim. + // audience is the required value of the "aud" JWT claim. // +kubebuilder:validation:MinLength=1 Audience string `json:"audience"` - // Claims allows customization of the claims that will be mapped to user identity + // claims allows customization of the claims that will be mapped to user identity // for Kubernetes access. // +optional Claims JWTTokenClaims `json:"claims"` - // TLS configuration for communicating with the OIDC provider. + // claimValidationRules are rules that are applied to validate token claims to authenticate users. + // This is similar to claimValidationRules from Kubernetes AuthenticationConfiguration as documented in + // https://kubernetes.io/docs/reference/access-authn-authz/authentication. + // This is an advanced configuration option. During an end-user login flow, mistakes in this + // configuration will cause the user's login to fail. + // +optional + ClaimValidationRules []ClaimValidationRule `json:"claimValidationRules,omitempty"` + + // 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. + // This is similar to claimValidationRules from Kubernetes AuthenticationConfiguration as documented in + // https://kubernetes.io/docs/reference/access-authn-authz/authentication. + // This is an advanced configuration option. During an end-user login flow, mistakes in this + // configuration will cause the user's login to fail. + // +optional + UserValidationRules []UserValidationRule `json:"userValidationRules,omitempty"` + + // tls is the configuration for communicating with the OIDC provider via TLS. // +optional TLS *TLSSpec `json:"tls,omitempty"` } +// ClaimValidationRule provides the configuration for a single claim validation rule. +type ClaimValidationRule struct { + // claim is the name of a required claim. + // Same as --oidc-required-claim flag. + // Only string claim keys are supported. + // Mutually exclusive with expression and message. + // +optional + Claim string `json:"claim,omitempty"` + + // requiredValue is the value of a required claim. + // Same as --oidc-required-claim flag. + // 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. + // Mutually exclusive with expression and message. + // +optional + 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.foo.bar'. + // 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"` +} + +// 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"` +} + // JWTTokenClaims allows customization of the claims that will be mapped to user identity // for Kubernetes access. type JWTTokenClaims struct { - // Groups is the name of the claim which should be read to extract the user's + // groups is the name of the claim which should be read to extract the user's // group membership from the JWT token. When not specified, it will default to "groups". // +optional Groups string `json:"groups"` - // Username is the name of the claim which should be read to extract the + // username is the name of the claim which should be read to extract the // username from the JWT token. When not specified, it will default to "username". // +optional Username string `json:"username"` + + // extra is similar to claimMappings.extra from Kubernetes AuthenticationConfiguration as documented in + // https://kubernetes.io/docs/reference/access-authn-authz/authentication. However, note that the + // Pinniped Concierge issues client certificates to users for the purpose of authenticating, and + // the Kubernetes API server does not have any mechanism for transmitting auth extras via client + // certificates. When configured, these extras will appear in client certificates issued by the + // Pinniped Supervisor in the x509 Subject field as Organizational Units (OU). However, when this + // client certificate is presented to Kubernetes for authentication, Kubernetes will ignore these + // extras. This is probably only useful if you are using a custom authenticating proxy in front + // of your Kubernetes API server which can translate these OUs into auth extras, as described by + // https://kubernetes.io/docs/reference/access-authn-authz/authentication/#authenticating-proxy. + // This is an advanced configuration option. During an end-user login flow, each of these CEL expressions + // must evaluate to either a string or an array of strings, or else the user's login will fail. + // +optional + Extra []ExtraMapping `json:"extra,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 to be unique. + // +required + 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.foo.bar'. + // + // Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ + // + // +required + ValueExpression string `json:"valueExpression"` } // JWTAuthenticator describes the configuration of a JWT authenticator. @@ -86,14 +208,14 @@ type JWTAuthenticator struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` - // Spec for configuring the authenticator. + // spec for configuring the authenticator. Spec JWTAuthenticatorSpec `json:"spec"` - // Status of the authenticator. + // status of the authenticator. Status JWTAuthenticatorStatus `json:"status,omitempty"` } -// List of JWTAuthenticator objects. +// JWTAuthenticatorList is a list of JWTAuthenticator objects. // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object type JWTAuthenticatorList struct { metav1.TypeMeta `json:",inline"` diff --git a/generated/1.32/apis/concierge/authentication/v1alpha1/zz_generated.deepcopy.go b/generated/1.32/apis/concierge/authentication/v1alpha1/zz_generated.deepcopy.go index 6b1235b88..cf8c0e61c 100644 --- a/generated/1.32/apis/concierge/authentication/v1alpha1/zz_generated.deepcopy.go +++ b/generated/1.32/apis/concierge/authentication/v1alpha1/zz_generated.deepcopy.go @@ -29,6 +29,38 @@ func (in *CertificateAuthorityDataSourceSpec) DeepCopy() *CertificateAuthorityDa return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ClaimValidationRule) DeepCopyInto(out *ClaimValidationRule) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClaimValidationRule. +func (in *ClaimValidationRule) DeepCopy() *ClaimValidationRule { + if in == nil { + return nil + } + out := new(ClaimValidationRule) + in.DeepCopyInto(out) + return out +} + +// 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. func (in *JWTAuthenticator) DeepCopyInto(out *JWTAuthenticator) { *out = *in @@ -93,7 +125,17 @@ func (in *JWTAuthenticatorList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *JWTAuthenticatorSpec) DeepCopyInto(out *JWTAuthenticatorSpec) { *out = *in - out.Claims = in.Claims + in.Claims.DeepCopyInto(&out.Claims) + if in.ClaimValidationRules != nil { + in, out := &in.ClaimValidationRules, &out.ClaimValidationRules + *out = make([]ClaimValidationRule, len(*in)) + copy(*out, *in) + } + if in.UserValidationRules != nil { + in, out := &in.UserValidationRules, &out.UserValidationRules + *out = make([]UserValidationRule, len(*in)) + copy(*out, *in) + } if in.TLS != nil { in, out := &in.TLS, &out.TLS *out = new(TLSSpec) @@ -138,6 +180,11 @@ func (in *JWTAuthenticatorStatus) DeepCopy() *JWTAuthenticatorStatus { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *JWTTokenClaims) DeepCopyInto(out *JWTTokenClaims) { *out = *in + if in.Extra != nil { + in, out := &in.Extra, &out.Extra + *out = make([]ExtraMapping, len(*in)) + copy(*out, *in) + } return } @@ -172,6 +219,22 @@ func (in *TLSSpec) DeepCopy() *TLSSpec { 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. func (in *WebhookAuthenticator) DeepCopyInto(out *WebhookAuthenticator) { *out = *in diff --git a/generated/1.32/crds/authentication.concierge.pinniped.dev_jwtauthenticators.yaml b/generated/1.32/crds/authentication.concierge.pinniped.dev_jwtauthenticators.yaml index 2ef6312a0..cbb567616 100644 --- a/generated/1.32/crds/authentication.concierge.pinniped.dev_jwtauthenticators.yaml +++ b/generated/1.32/crds/authentication.concierge.pinniped.dev_jwtauthenticators.yaml @@ -58,37 +58,133 @@ spec: metadata: type: object spec: - description: Spec for configuring the authenticator. + description: spec for configuring the authenticator. properties: audience: - description: Audience is the required value of the "aud" JWT claim. + description: audience is the required value of the "aud" JWT claim. minLength: 1 type: string + claimValidationRules: + description: |- + claimValidationRules are rules that are applied to validate token claims to authenticate users. + This is similar to claimValidationRules from Kubernetes AuthenticationConfiguration as documented in + https://kubernetes.io/docs/reference/access-authn-authz/authentication. + This is an advanced configuration option. During an end-user login flow, mistakes in this + configuration will cause the user's login to fail. + items: + description: ClaimValidationRule provides the configuration for + a single claim validation rule. + properties: + claim: + description: |- + claim is the name of a required claim. + Same as --oidc-required-claim flag. + Only string claim keys are supported. + Mutually exclusive with expression and message. + type: string + expression: + description: |- + 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.foo.bar'. + 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. + type: string + message: + description: |- + message customizes the returned error message when expression returns false. + message is a literal string. + Mutually exclusive with claim and requiredValue. + type: string + requiredValue: + description: |- + requiredValue is the value of a required claim. + Same as --oidc-required-claim flag. + 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. + Mutually exclusive with expression and message. + type: string + type: object + type: array claims: description: |- - Claims allows customization of the claims that will be mapped to user identity + claims allows customization of the claims that will be mapped to user identity for Kubernetes access. properties: + extra: + description: |- + extra is similar to claimMappings.extra from Kubernetes AuthenticationConfiguration as documented in + https://kubernetes.io/docs/reference/access-authn-authz/authentication. However, note that the + Pinniped Concierge issues client certificates to users for the purpose of authenticating, and + the Kubernetes API server does not have any mechanism for transmitting auth extras via client + certificates. When configured, these extras will appear in client certificates issued by the + Pinniped Supervisor in the x509 Subject field as Organizational Units (OU). However, when this + client certificate is presented to Kubernetes for authentication, Kubernetes will ignore these + extras. This is probably only useful if you are using a custom authenticating proxy in front + of your Kubernetes API server which can translate these OUs into auth extras, as described by + https://kubernetes.io/docs/reference/access-authn-authz/authentication/#authenticating-proxy. + This is an advanced configuration option. During an end-user login flow, each of these CEL expressions + must evaluate to either a string or an array of strings, or else the user's login will fail. + items: + description: ExtraMapping provides the configuration for a single + extra mapping. + properties: + key: + description: |- + 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 to be unique. + type: string + valueExpression: + description: |- + 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.foo.bar'. + + Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ + type: string + required: + - key + - valueExpression + type: object + type: array groups: description: |- - Groups is the name of the claim which should be read to extract the user's + groups is the name of the claim which should be read to extract the user's group membership from the JWT token. When not specified, it will default to "groups". type: string username: description: |- - Username is the name of the claim which should be read to extract the + username is the name of the claim which should be read to extract the username from the JWT token. When not specified, it will default to "username". type: string type: object issuer: description: |- - Issuer is the OIDC issuer URL that will be used to discover public signing keys. Issuer is + issuer is the OIDC issuer URL that will be used to discover public signing keys. Issuer is also used to validate the "iss" JWT claim. minLength: 1 pattern: ^https:// type: string tls: - description: TLS configuration for communicating with the OIDC provider. + description: tls is the configuration for communicating with the OIDC + provider via TLS. properties: certificateAuthorityData: description: X.509 Certificate Authority (base64-encoded PEM bundle). @@ -128,12 +224,47 @@ spec: - name type: object type: object + userValidationRules: + description: |- + 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. + This is similar to claimValidationRules from Kubernetes AuthenticationConfiguration as documented in + https://kubernetes.io/docs/reference/access-authn-authz/authentication. + This is an advanced configuration option. During an end-user login flow, mistakes in this + configuration will cause the user's login to fail. + items: + description: UserValidationRule provides the configuration for a + single user info validation rule. + properties: + expression: + description: |- + 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/ + type: string + message: + description: |- + message customizes the returned error message when rule returns false. + message is a literal string. + type: string + required: + - expression + type: object + type: array required: - audience - issuer type: object status: - description: Status of the authenticator. + description: status of the authenticator. properties: conditions: description: Represents the observations of the authenticator's current diff --git a/generated/1.33/README.adoc b/generated/1.33/README.adoc index e9f7df956..5c4577bd0 100644 --- a/generated/1.33/README.adoc +++ b/generated/1.33/README.adoc @@ -60,6 +60,79 @@ certificate bundle. + |=== +[id="{anchor_prefix}-go-pinniped-dev-generated-1-33-apis-concierge-authentication-v1alpha1-claimvalidationrule"] +==== ClaimValidationRule + +ClaimValidationRule provides the configuration for a single claim validation rule. + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-33-apis-concierge-authentication-v1alpha1-jwtauthenticatorspec[$$JWTAuthenticatorSpec$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description +| *`claim`* __string__ | claim is the name of a required claim. + +Same as --oidc-required-claim flag. + +Only string claim keys are supported. + +Mutually exclusive with expression and message. + +| *`requiredValue`* __string__ | requiredValue is the value of a required claim. + +Same as --oidc-required-claim flag. + +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. + +Mutually exclusive with expression and message. + +| *`expression`* __string__ | 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.foo.bar'. + +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. + +| *`message`* __string__ | message customizes the returned error message when expression returns false. + +message is a literal string. + +Mutually exclusive with claim and requiredValue. + +|=== + + +[id="{anchor_prefix}-go-pinniped-dev-generated-1-33-apis-concierge-authentication-v1alpha1-extramapping"] +==== ExtraMapping + +ExtraMapping provides the configuration for a single extra mapping. + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-33-apis-concierge-authentication-v1alpha1-jwttokenclaims[$$JWTTokenClaims$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description +| *`key`* __string__ | 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 to be unique. + +| *`valueExpression`* __string__ | 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.foo.bar'. + + +Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ + +|=== + + [id="{anchor_prefix}-go-pinniped-dev-generated-1-33-apis-concierge-authentication-v1alpha1-jwtauthenticator"] ==== JWTAuthenticator @@ -78,8 +151,8 @@ signature, existence of claims, etc.) and extract the username and groups from t | Field | Description | *`metadata`* __link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.33/#objectmeta-v1-meta[$$ObjectMeta$$]__ | Refer to Kubernetes API documentation for fields of `metadata`. -| *`spec`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-33-apis-concierge-authentication-v1alpha1-jwtauthenticatorspec[$$JWTAuthenticatorSpec$$]__ | Spec for configuring the authenticator. + -| *`status`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-33-apis-concierge-authentication-v1alpha1-jwtauthenticatorstatus[$$JWTAuthenticatorStatus$$]__ | Status of the authenticator. + +| *`spec`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-33-apis-concierge-authentication-v1alpha1-jwtauthenticatorspec[$$JWTAuthenticatorSpec$$]__ | spec for configuring the authenticator. + +| *`status`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-33-apis-concierge-authentication-v1alpha1-jwtauthenticatorstatus[$$JWTAuthenticatorStatus$$]__ | status of the authenticator. + |=== @@ -100,7 +173,7 @@ signature, existence of claims, etc.) and extract the username and groups from t [id="{anchor_prefix}-go-pinniped-dev-generated-1-33-apis-concierge-authentication-v1alpha1-jwtauthenticatorspec"] ==== JWTAuthenticatorSpec -Spec for configuring a JWT authenticator. +JWTAuthenticatorSpec is the spec for configuring a JWT authenticator. .Appears In: **** @@ -110,19 +183,32 @@ Spec for configuring a JWT authenticator. [cols="25a,75a", options="header"] |=== | Field | Description -| *`issuer`* __string__ | Issuer is the OIDC issuer URL that will be used to discover public signing keys. Issuer is + +| *`issuer`* __string__ | issuer is the OIDC issuer URL that will be used to discover public signing keys. Issuer is + also used to validate the "iss" JWT claim. + -| *`audience`* __string__ | Audience is the required value of the "aud" JWT claim. + -| *`claims`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-33-apis-concierge-authentication-v1alpha1-jwttokenclaims[$$JWTTokenClaims$$]__ | Claims allows customization of the claims that will be mapped to user identity + +| *`audience`* __string__ | audience is the required value of the "aud" JWT claim. + +| *`claims`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-33-apis-concierge-authentication-v1alpha1-jwttokenclaims[$$JWTTokenClaims$$]__ | claims allows customization of the claims that will be mapped to user identity + for Kubernetes access. + -| *`tls`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-33-apis-concierge-authentication-v1alpha1-tlsspec[$$TLSSpec$$]__ | TLS configuration for communicating with the OIDC provider. + +| *`claimValidationRules`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-33-apis-concierge-authentication-v1alpha1-claimvalidationrule[$$ClaimValidationRule$$] array__ | claimValidationRules are rules that are applied to validate token claims to authenticate users. + +This is similar to claimValidationRules from Kubernetes AuthenticationConfiguration as documented in + +https://kubernetes.io/docs/reference/access-authn-authz/authentication. + +This is an advanced configuration option. During an end-user login flow, mistakes in this + +configuration will cause the user's login to fail. + +| *`userValidationRules`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-33-apis-concierge-authentication-v1alpha1-uservalidationrule[$$UserValidationRule$$] array__ | 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. + +This is similar to claimValidationRules from Kubernetes AuthenticationConfiguration as documented in + +https://kubernetes.io/docs/reference/access-authn-authz/authentication. + +This is an advanced configuration option. During an end-user login flow, mistakes in this + +configuration will cause the user's login to fail. + +| *`tls`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-33-apis-concierge-authentication-v1alpha1-tlsspec[$$TLSSpec$$]__ | tls is the configuration for communicating with the OIDC provider via TLS. + |=== [id="{anchor_prefix}-go-pinniped-dev-generated-1-33-apis-concierge-authentication-v1alpha1-jwtauthenticatorstatus"] ==== JWTAuthenticatorStatus -Status of a JWT authenticator. +JWTAuthenticatorStatus is the status of a JWT authenticator. .Appears In: **** @@ -151,10 +237,22 @@ for Kubernetes access. [cols="25a,75a", options="header"] |=== | Field | Description -| *`groups`* __string__ | Groups is the name of the claim which should be read to extract the user's + +| *`groups`* __string__ | groups is the name of the claim which should be read to extract the user's + group membership from the JWT token. When not specified, it will default to "groups". + -| *`username`* __string__ | Username is the name of the claim which should be read to extract the + +| *`username`* __string__ | username is the name of the claim which should be read to extract the + username from the JWT token. When not specified, it will default to "username". + +| *`extra`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-33-apis-concierge-authentication-v1alpha1-extramapping[$$ExtraMapping$$] array__ | extra is similar to claimMappings.extra from Kubernetes AuthenticationConfiguration as documented in + +https://kubernetes.io/docs/reference/access-authn-authz/authentication. However, note that the + +Pinniped Concierge issues client certificates to users for the purpose of authenticating, and + +the Kubernetes API server does not have any mechanism for transmitting auth extras via client + +certificates. When configured, these extras will appear in client certificates issued by the + +Pinniped Supervisor in the x509 Subject field as Organizational Units (OU). However, when this + +client certificate is presented to Kubernetes for authentication, Kubernetes will ignore these + +extras. This is probably only useful if you are using a custom authenticating proxy in front + +of your Kubernetes API server which can translate these OUs into auth extras, as described by + +https://kubernetes.io/docs/reference/access-authn-authz/authentication/#authenticating-proxy. + +This is an advanced configuration option. During an end-user login flow, each of these CEL expressions + +must evaluate to either a string or an array of strings, or else the user's login will fail. + |=== @@ -178,6 +276,33 @@ Any changes to the CA bundle in the secret or configmap will be dynamically relo |=== +[id="{anchor_prefix}-go-pinniped-dev-generated-1-33-apis-concierge-authentication-v1alpha1-uservalidationrule"] +==== UserValidationRule + +UserValidationRule provides the configuration for a single user info validation rule. + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-33-apis-concierge-authentication-v1alpha1-jwtauthenticatorspec[$$JWTAuthenticatorSpec$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description +| *`expression`* __string__ | 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/ + +| *`message`* __string__ | message customizes the returned error message when rule returns false. + +message is a literal string. + +|=== + + [id="{anchor_prefix}-go-pinniped-dev-generated-1-33-apis-concierge-authentication-v1alpha1-webhookauthenticator"] ==== WebhookAuthenticator diff --git a/generated/1.33/apis/concierge/authentication/v1alpha1/types_jwtauthenticator.go b/generated/1.33/apis/concierge/authentication/v1alpha1/types_jwtauthenticator.go index a3b01c696..d5149642f 100644 --- a/generated/1.33/apis/concierge/authentication/v1alpha1/types_jwtauthenticator.go +++ b/generated/1.33/apis/concierge/authentication/v1alpha1/types_jwtauthenticator.go @@ -18,7 +18,7 @@ const ( JWTAuthenticatorPhaseError JWTAuthenticatorPhase = "Error" ) -// Status of a JWT authenticator. +// JWTAuthenticatorStatus is the status of a JWT authenticator. type JWTAuthenticatorStatus struct { // Represents the observations of the authenticator's current state. // +patchMergeKey=type @@ -26,46 +26,168 @@ type JWTAuthenticatorStatus struct { // +listType=map // +listMapKey=type Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"` + // Phase summarizes the overall status of the JWTAuthenticator. // +kubebuilder:default=Pending // +kubebuilder:validation:Enum=Pending;Ready;Error Phase JWTAuthenticatorPhase `json:"phase,omitempty"` } -// Spec for configuring a JWT authenticator. +// JWTAuthenticatorSpec is the spec for configuring a JWT authenticator. type JWTAuthenticatorSpec struct { - // Issuer is the OIDC issuer URL that will be used to discover public signing keys. Issuer is + // issuer is the OIDC issuer URL that will be used to discover public signing keys. Issuer is // also used to validate the "iss" JWT claim. // +kubebuilder:validation:MinLength=1 // +kubebuilder:validation:Pattern=`^https://` Issuer string `json:"issuer"` - // Audience is the required value of the "aud" JWT claim. + // audience is the required value of the "aud" JWT claim. // +kubebuilder:validation:MinLength=1 Audience string `json:"audience"` - // Claims allows customization of the claims that will be mapped to user identity + // claims allows customization of the claims that will be mapped to user identity // for Kubernetes access. // +optional Claims JWTTokenClaims `json:"claims"` - // TLS configuration for communicating with the OIDC provider. + // claimValidationRules are rules that are applied to validate token claims to authenticate users. + // This is similar to claimValidationRules from Kubernetes AuthenticationConfiguration as documented in + // https://kubernetes.io/docs/reference/access-authn-authz/authentication. + // This is an advanced configuration option. During an end-user login flow, mistakes in this + // configuration will cause the user's login to fail. + // +optional + ClaimValidationRules []ClaimValidationRule `json:"claimValidationRules,omitempty"` + + // 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. + // This is similar to claimValidationRules from Kubernetes AuthenticationConfiguration as documented in + // https://kubernetes.io/docs/reference/access-authn-authz/authentication. + // This is an advanced configuration option. During an end-user login flow, mistakes in this + // configuration will cause the user's login to fail. + // +optional + UserValidationRules []UserValidationRule `json:"userValidationRules,omitempty"` + + // tls is the configuration for communicating with the OIDC provider via TLS. // +optional TLS *TLSSpec `json:"tls,omitempty"` } +// ClaimValidationRule provides the configuration for a single claim validation rule. +type ClaimValidationRule struct { + // claim is the name of a required claim. + // Same as --oidc-required-claim flag. + // Only string claim keys are supported. + // Mutually exclusive with expression and message. + // +optional + Claim string `json:"claim,omitempty"` + + // requiredValue is the value of a required claim. + // Same as --oidc-required-claim flag. + // 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. + // Mutually exclusive with expression and message. + // +optional + 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.foo.bar'. + // 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"` +} + +// 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"` +} + // JWTTokenClaims allows customization of the claims that will be mapped to user identity // for Kubernetes access. type JWTTokenClaims struct { - // Groups is the name of the claim which should be read to extract the user's + // groups is the name of the claim which should be read to extract the user's // group membership from the JWT token. When not specified, it will default to "groups". // +optional Groups string `json:"groups"` - // Username is the name of the claim which should be read to extract the + // username is the name of the claim which should be read to extract the // username from the JWT token. When not specified, it will default to "username". // +optional Username string `json:"username"` + + // extra is similar to claimMappings.extra from Kubernetes AuthenticationConfiguration as documented in + // https://kubernetes.io/docs/reference/access-authn-authz/authentication. However, note that the + // Pinniped Concierge issues client certificates to users for the purpose of authenticating, and + // the Kubernetes API server does not have any mechanism for transmitting auth extras via client + // certificates. When configured, these extras will appear in client certificates issued by the + // Pinniped Supervisor in the x509 Subject field as Organizational Units (OU). However, when this + // client certificate is presented to Kubernetes for authentication, Kubernetes will ignore these + // extras. This is probably only useful if you are using a custom authenticating proxy in front + // of your Kubernetes API server which can translate these OUs into auth extras, as described by + // https://kubernetes.io/docs/reference/access-authn-authz/authentication/#authenticating-proxy. + // This is an advanced configuration option. During an end-user login flow, each of these CEL expressions + // must evaluate to either a string or an array of strings, or else the user's login will fail. + // +optional + Extra []ExtraMapping `json:"extra,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 to be unique. + // +required + 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.foo.bar'. + // + // Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ + // + // +required + ValueExpression string `json:"valueExpression"` } // JWTAuthenticator describes the configuration of a JWT authenticator. @@ -86,14 +208,14 @@ type JWTAuthenticator struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` - // Spec for configuring the authenticator. + // spec for configuring the authenticator. Spec JWTAuthenticatorSpec `json:"spec"` - // Status of the authenticator. + // status of the authenticator. Status JWTAuthenticatorStatus `json:"status,omitempty"` } -// List of JWTAuthenticator objects. +// JWTAuthenticatorList is a list of JWTAuthenticator objects. // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object type JWTAuthenticatorList struct { metav1.TypeMeta `json:",inline"` diff --git a/generated/1.33/apis/concierge/authentication/v1alpha1/zz_generated.deepcopy.go b/generated/1.33/apis/concierge/authentication/v1alpha1/zz_generated.deepcopy.go index 6b1235b88..cf8c0e61c 100644 --- a/generated/1.33/apis/concierge/authentication/v1alpha1/zz_generated.deepcopy.go +++ b/generated/1.33/apis/concierge/authentication/v1alpha1/zz_generated.deepcopy.go @@ -29,6 +29,38 @@ func (in *CertificateAuthorityDataSourceSpec) DeepCopy() *CertificateAuthorityDa return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ClaimValidationRule) DeepCopyInto(out *ClaimValidationRule) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClaimValidationRule. +func (in *ClaimValidationRule) DeepCopy() *ClaimValidationRule { + if in == nil { + return nil + } + out := new(ClaimValidationRule) + in.DeepCopyInto(out) + return out +} + +// 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. func (in *JWTAuthenticator) DeepCopyInto(out *JWTAuthenticator) { *out = *in @@ -93,7 +125,17 @@ func (in *JWTAuthenticatorList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *JWTAuthenticatorSpec) DeepCopyInto(out *JWTAuthenticatorSpec) { *out = *in - out.Claims = in.Claims + in.Claims.DeepCopyInto(&out.Claims) + if in.ClaimValidationRules != nil { + in, out := &in.ClaimValidationRules, &out.ClaimValidationRules + *out = make([]ClaimValidationRule, len(*in)) + copy(*out, *in) + } + if in.UserValidationRules != nil { + in, out := &in.UserValidationRules, &out.UserValidationRules + *out = make([]UserValidationRule, len(*in)) + copy(*out, *in) + } if in.TLS != nil { in, out := &in.TLS, &out.TLS *out = new(TLSSpec) @@ -138,6 +180,11 @@ func (in *JWTAuthenticatorStatus) DeepCopy() *JWTAuthenticatorStatus { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *JWTTokenClaims) DeepCopyInto(out *JWTTokenClaims) { *out = *in + if in.Extra != nil { + in, out := &in.Extra, &out.Extra + *out = make([]ExtraMapping, len(*in)) + copy(*out, *in) + } return } @@ -172,6 +219,22 @@ func (in *TLSSpec) DeepCopy() *TLSSpec { 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. func (in *WebhookAuthenticator) DeepCopyInto(out *WebhookAuthenticator) { *out = *in diff --git a/generated/1.33/crds/authentication.concierge.pinniped.dev_jwtauthenticators.yaml b/generated/1.33/crds/authentication.concierge.pinniped.dev_jwtauthenticators.yaml index 2ef6312a0..cbb567616 100644 --- a/generated/1.33/crds/authentication.concierge.pinniped.dev_jwtauthenticators.yaml +++ b/generated/1.33/crds/authentication.concierge.pinniped.dev_jwtauthenticators.yaml @@ -58,37 +58,133 @@ spec: metadata: type: object spec: - description: Spec for configuring the authenticator. + description: spec for configuring the authenticator. properties: audience: - description: Audience is the required value of the "aud" JWT claim. + description: audience is the required value of the "aud" JWT claim. minLength: 1 type: string + claimValidationRules: + description: |- + claimValidationRules are rules that are applied to validate token claims to authenticate users. + This is similar to claimValidationRules from Kubernetes AuthenticationConfiguration as documented in + https://kubernetes.io/docs/reference/access-authn-authz/authentication. + This is an advanced configuration option. During an end-user login flow, mistakes in this + configuration will cause the user's login to fail. + items: + description: ClaimValidationRule provides the configuration for + a single claim validation rule. + properties: + claim: + description: |- + claim is the name of a required claim. + Same as --oidc-required-claim flag. + Only string claim keys are supported. + Mutually exclusive with expression and message. + type: string + expression: + description: |- + 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.foo.bar'. + 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. + type: string + message: + description: |- + message customizes the returned error message when expression returns false. + message is a literal string. + Mutually exclusive with claim and requiredValue. + type: string + requiredValue: + description: |- + requiredValue is the value of a required claim. + Same as --oidc-required-claim flag. + 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. + Mutually exclusive with expression and message. + type: string + type: object + type: array claims: description: |- - Claims allows customization of the claims that will be mapped to user identity + claims allows customization of the claims that will be mapped to user identity for Kubernetes access. properties: + extra: + description: |- + extra is similar to claimMappings.extra from Kubernetes AuthenticationConfiguration as documented in + https://kubernetes.io/docs/reference/access-authn-authz/authentication. However, note that the + Pinniped Concierge issues client certificates to users for the purpose of authenticating, and + the Kubernetes API server does not have any mechanism for transmitting auth extras via client + certificates. When configured, these extras will appear in client certificates issued by the + Pinniped Supervisor in the x509 Subject field as Organizational Units (OU). However, when this + client certificate is presented to Kubernetes for authentication, Kubernetes will ignore these + extras. This is probably only useful if you are using a custom authenticating proxy in front + of your Kubernetes API server which can translate these OUs into auth extras, as described by + https://kubernetes.io/docs/reference/access-authn-authz/authentication/#authenticating-proxy. + This is an advanced configuration option. During an end-user login flow, each of these CEL expressions + must evaluate to either a string or an array of strings, or else the user's login will fail. + items: + description: ExtraMapping provides the configuration for a single + extra mapping. + properties: + key: + description: |- + 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 to be unique. + type: string + valueExpression: + description: |- + 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.foo.bar'. + + Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ + type: string + required: + - key + - valueExpression + type: object + type: array groups: description: |- - Groups is the name of the claim which should be read to extract the user's + groups is the name of the claim which should be read to extract the user's group membership from the JWT token. When not specified, it will default to "groups". type: string username: description: |- - Username is the name of the claim which should be read to extract the + username is the name of the claim which should be read to extract the username from the JWT token. When not specified, it will default to "username". type: string type: object issuer: description: |- - Issuer is the OIDC issuer URL that will be used to discover public signing keys. Issuer is + issuer is the OIDC issuer URL that will be used to discover public signing keys. Issuer is also used to validate the "iss" JWT claim. minLength: 1 pattern: ^https:// type: string tls: - description: TLS configuration for communicating with the OIDC provider. + description: tls is the configuration for communicating with the OIDC + provider via TLS. properties: certificateAuthorityData: description: X.509 Certificate Authority (base64-encoded PEM bundle). @@ -128,12 +224,47 @@ spec: - name type: object type: object + userValidationRules: + description: |- + 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. + This is similar to claimValidationRules from Kubernetes AuthenticationConfiguration as documented in + https://kubernetes.io/docs/reference/access-authn-authz/authentication. + This is an advanced configuration option. During an end-user login flow, mistakes in this + configuration will cause the user's login to fail. + items: + description: UserValidationRule provides the configuration for a + single user info validation rule. + properties: + expression: + description: |- + 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/ + type: string + message: + description: |- + message customizes the returned error message when rule returns false. + message is a literal string. + type: string + required: + - expression + type: object + type: array required: - audience - issuer type: object status: - description: Status of the authenticator. + description: status of the authenticator. properties: conditions: description: Represents the observations of the authenticator's current diff --git a/generated/latest/README.adoc b/generated/latest/README.adoc index e9f7df956..5c4577bd0 100644 --- a/generated/latest/README.adoc +++ b/generated/latest/README.adoc @@ -60,6 +60,79 @@ certificate bundle. + |=== +[id="{anchor_prefix}-go-pinniped-dev-generated-1-33-apis-concierge-authentication-v1alpha1-claimvalidationrule"] +==== ClaimValidationRule + +ClaimValidationRule provides the configuration for a single claim validation rule. + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-33-apis-concierge-authentication-v1alpha1-jwtauthenticatorspec[$$JWTAuthenticatorSpec$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description +| *`claim`* __string__ | claim is the name of a required claim. + +Same as --oidc-required-claim flag. + +Only string claim keys are supported. + +Mutually exclusive with expression and message. + +| *`requiredValue`* __string__ | requiredValue is the value of a required claim. + +Same as --oidc-required-claim flag. + +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. + +Mutually exclusive with expression and message. + +| *`expression`* __string__ | 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.foo.bar'. + +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. + +| *`message`* __string__ | message customizes the returned error message when expression returns false. + +message is a literal string. + +Mutually exclusive with claim and requiredValue. + +|=== + + +[id="{anchor_prefix}-go-pinniped-dev-generated-1-33-apis-concierge-authentication-v1alpha1-extramapping"] +==== ExtraMapping + +ExtraMapping provides the configuration for a single extra mapping. + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-33-apis-concierge-authentication-v1alpha1-jwttokenclaims[$$JWTTokenClaims$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description +| *`key`* __string__ | 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 to be unique. + +| *`valueExpression`* __string__ | 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.foo.bar'. + + +Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ + +|=== + + [id="{anchor_prefix}-go-pinniped-dev-generated-1-33-apis-concierge-authentication-v1alpha1-jwtauthenticator"] ==== JWTAuthenticator @@ -78,8 +151,8 @@ signature, existence of claims, etc.) and extract the username and groups from t | Field | Description | *`metadata`* __link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.33/#objectmeta-v1-meta[$$ObjectMeta$$]__ | Refer to Kubernetes API documentation for fields of `metadata`. -| *`spec`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-33-apis-concierge-authentication-v1alpha1-jwtauthenticatorspec[$$JWTAuthenticatorSpec$$]__ | Spec for configuring the authenticator. + -| *`status`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-33-apis-concierge-authentication-v1alpha1-jwtauthenticatorstatus[$$JWTAuthenticatorStatus$$]__ | Status of the authenticator. + +| *`spec`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-33-apis-concierge-authentication-v1alpha1-jwtauthenticatorspec[$$JWTAuthenticatorSpec$$]__ | spec for configuring the authenticator. + +| *`status`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-33-apis-concierge-authentication-v1alpha1-jwtauthenticatorstatus[$$JWTAuthenticatorStatus$$]__ | status of the authenticator. + |=== @@ -100,7 +173,7 @@ signature, existence of claims, etc.) and extract the username and groups from t [id="{anchor_prefix}-go-pinniped-dev-generated-1-33-apis-concierge-authentication-v1alpha1-jwtauthenticatorspec"] ==== JWTAuthenticatorSpec -Spec for configuring a JWT authenticator. +JWTAuthenticatorSpec is the spec for configuring a JWT authenticator. .Appears In: **** @@ -110,19 +183,32 @@ Spec for configuring a JWT authenticator. [cols="25a,75a", options="header"] |=== | Field | Description -| *`issuer`* __string__ | Issuer is the OIDC issuer URL that will be used to discover public signing keys. Issuer is + +| *`issuer`* __string__ | issuer is the OIDC issuer URL that will be used to discover public signing keys. Issuer is + also used to validate the "iss" JWT claim. + -| *`audience`* __string__ | Audience is the required value of the "aud" JWT claim. + -| *`claims`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-33-apis-concierge-authentication-v1alpha1-jwttokenclaims[$$JWTTokenClaims$$]__ | Claims allows customization of the claims that will be mapped to user identity + +| *`audience`* __string__ | audience is the required value of the "aud" JWT claim. + +| *`claims`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-33-apis-concierge-authentication-v1alpha1-jwttokenclaims[$$JWTTokenClaims$$]__ | claims allows customization of the claims that will be mapped to user identity + for Kubernetes access. + -| *`tls`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-33-apis-concierge-authentication-v1alpha1-tlsspec[$$TLSSpec$$]__ | TLS configuration for communicating with the OIDC provider. + +| *`claimValidationRules`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-33-apis-concierge-authentication-v1alpha1-claimvalidationrule[$$ClaimValidationRule$$] array__ | claimValidationRules are rules that are applied to validate token claims to authenticate users. + +This is similar to claimValidationRules from Kubernetes AuthenticationConfiguration as documented in + +https://kubernetes.io/docs/reference/access-authn-authz/authentication. + +This is an advanced configuration option. During an end-user login flow, mistakes in this + +configuration will cause the user's login to fail. + +| *`userValidationRules`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-33-apis-concierge-authentication-v1alpha1-uservalidationrule[$$UserValidationRule$$] array__ | 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. + +This is similar to claimValidationRules from Kubernetes AuthenticationConfiguration as documented in + +https://kubernetes.io/docs/reference/access-authn-authz/authentication. + +This is an advanced configuration option. During an end-user login flow, mistakes in this + +configuration will cause the user's login to fail. + +| *`tls`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-33-apis-concierge-authentication-v1alpha1-tlsspec[$$TLSSpec$$]__ | tls is the configuration for communicating with the OIDC provider via TLS. + |=== [id="{anchor_prefix}-go-pinniped-dev-generated-1-33-apis-concierge-authentication-v1alpha1-jwtauthenticatorstatus"] ==== JWTAuthenticatorStatus -Status of a JWT authenticator. +JWTAuthenticatorStatus is the status of a JWT authenticator. .Appears In: **** @@ -151,10 +237,22 @@ for Kubernetes access. [cols="25a,75a", options="header"] |=== | Field | Description -| *`groups`* __string__ | Groups is the name of the claim which should be read to extract the user's + +| *`groups`* __string__ | groups is the name of the claim which should be read to extract the user's + group membership from the JWT token. When not specified, it will default to "groups". + -| *`username`* __string__ | Username is the name of the claim which should be read to extract the + +| *`username`* __string__ | username is the name of the claim which should be read to extract the + username from the JWT token. When not specified, it will default to "username". + +| *`extra`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-33-apis-concierge-authentication-v1alpha1-extramapping[$$ExtraMapping$$] array__ | extra is similar to claimMappings.extra from Kubernetes AuthenticationConfiguration as documented in + +https://kubernetes.io/docs/reference/access-authn-authz/authentication. However, note that the + +Pinniped Concierge issues client certificates to users for the purpose of authenticating, and + +the Kubernetes API server does not have any mechanism for transmitting auth extras via client + +certificates. When configured, these extras will appear in client certificates issued by the + +Pinniped Supervisor in the x509 Subject field as Organizational Units (OU). However, when this + +client certificate is presented to Kubernetes for authentication, Kubernetes will ignore these + +extras. This is probably only useful if you are using a custom authenticating proxy in front + +of your Kubernetes API server which can translate these OUs into auth extras, as described by + +https://kubernetes.io/docs/reference/access-authn-authz/authentication/#authenticating-proxy. + +This is an advanced configuration option. During an end-user login flow, each of these CEL expressions + +must evaluate to either a string or an array of strings, or else the user's login will fail. + |=== @@ -178,6 +276,33 @@ Any changes to the CA bundle in the secret or configmap will be dynamically relo |=== +[id="{anchor_prefix}-go-pinniped-dev-generated-1-33-apis-concierge-authentication-v1alpha1-uservalidationrule"] +==== UserValidationRule + +UserValidationRule provides the configuration for a single user info validation rule. + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-33-apis-concierge-authentication-v1alpha1-jwtauthenticatorspec[$$JWTAuthenticatorSpec$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description +| *`expression`* __string__ | 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/ + +| *`message`* __string__ | message customizes the returned error message when rule returns false. + +message is a literal string. + +|=== + + [id="{anchor_prefix}-go-pinniped-dev-generated-1-33-apis-concierge-authentication-v1alpha1-webhookauthenticator"] ==== WebhookAuthenticator diff --git a/generated/latest/apis/concierge/authentication/v1alpha1/types_jwtauthenticator.go b/generated/latest/apis/concierge/authentication/v1alpha1/types_jwtauthenticator.go index a3b01c696..d5149642f 100644 --- a/generated/latest/apis/concierge/authentication/v1alpha1/types_jwtauthenticator.go +++ b/generated/latest/apis/concierge/authentication/v1alpha1/types_jwtauthenticator.go @@ -18,7 +18,7 @@ const ( JWTAuthenticatorPhaseError JWTAuthenticatorPhase = "Error" ) -// Status of a JWT authenticator. +// JWTAuthenticatorStatus is the status of a JWT authenticator. type JWTAuthenticatorStatus struct { // Represents the observations of the authenticator's current state. // +patchMergeKey=type @@ -26,46 +26,168 @@ type JWTAuthenticatorStatus struct { // +listType=map // +listMapKey=type Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"` + // Phase summarizes the overall status of the JWTAuthenticator. // +kubebuilder:default=Pending // +kubebuilder:validation:Enum=Pending;Ready;Error Phase JWTAuthenticatorPhase `json:"phase,omitempty"` } -// Spec for configuring a JWT authenticator. +// JWTAuthenticatorSpec is the spec for configuring a JWT authenticator. type JWTAuthenticatorSpec struct { - // Issuer is the OIDC issuer URL that will be used to discover public signing keys. Issuer is + // issuer is the OIDC issuer URL that will be used to discover public signing keys. Issuer is // also used to validate the "iss" JWT claim. // +kubebuilder:validation:MinLength=1 // +kubebuilder:validation:Pattern=`^https://` Issuer string `json:"issuer"` - // Audience is the required value of the "aud" JWT claim. + // audience is the required value of the "aud" JWT claim. // +kubebuilder:validation:MinLength=1 Audience string `json:"audience"` - // Claims allows customization of the claims that will be mapped to user identity + // claims allows customization of the claims that will be mapped to user identity // for Kubernetes access. // +optional Claims JWTTokenClaims `json:"claims"` - // TLS configuration for communicating with the OIDC provider. + // claimValidationRules are rules that are applied to validate token claims to authenticate users. + // This is similar to claimValidationRules from Kubernetes AuthenticationConfiguration as documented in + // https://kubernetes.io/docs/reference/access-authn-authz/authentication. + // This is an advanced configuration option. During an end-user login flow, mistakes in this + // configuration will cause the user's login to fail. + // +optional + ClaimValidationRules []ClaimValidationRule `json:"claimValidationRules,omitempty"` + + // 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. + // This is similar to claimValidationRules from Kubernetes AuthenticationConfiguration as documented in + // https://kubernetes.io/docs/reference/access-authn-authz/authentication. + // This is an advanced configuration option. During an end-user login flow, mistakes in this + // configuration will cause the user's login to fail. + // +optional + UserValidationRules []UserValidationRule `json:"userValidationRules,omitempty"` + + // tls is the configuration for communicating with the OIDC provider via TLS. // +optional TLS *TLSSpec `json:"tls,omitempty"` } +// ClaimValidationRule provides the configuration for a single claim validation rule. +type ClaimValidationRule struct { + // claim is the name of a required claim. + // Same as --oidc-required-claim flag. + // Only string claim keys are supported. + // Mutually exclusive with expression and message. + // +optional + Claim string `json:"claim,omitempty"` + + // requiredValue is the value of a required claim. + // Same as --oidc-required-claim flag. + // 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. + // Mutually exclusive with expression and message. + // +optional + 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.foo.bar'. + // 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"` +} + +// 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"` +} + // JWTTokenClaims allows customization of the claims that will be mapped to user identity // for Kubernetes access. type JWTTokenClaims struct { - // Groups is the name of the claim which should be read to extract the user's + // groups is the name of the claim which should be read to extract the user's // group membership from the JWT token. When not specified, it will default to "groups". // +optional Groups string `json:"groups"` - // Username is the name of the claim which should be read to extract the + // username is the name of the claim which should be read to extract the // username from the JWT token. When not specified, it will default to "username". // +optional Username string `json:"username"` + + // extra is similar to claimMappings.extra from Kubernetes AuthenticationConfiguration as documented in + // https://kubernetes.io/docs/reference/access-authn-authz/authentication. However, note that the + // Pinniped Concierge issues client certificates to users for the purpose of authenticating, and + // the Kubernetes API server does not have any mechanism for transmitting auth extras via client + // certificates. When configured, these extras will appear in client certificates issued by the + // Pinniped Supervisor in the x509 Subject field as Organizational Units (OU). However, when this + // client certificate is presented to Kubernetes for authentication, Kubernetes will ignore these + // extras. This is probably only useful if you are using a custom authenticating proxy in front + // of your Kubernetes API server which can translate these OUs into auth extras, as described by + // https://kubernetes.io/docs/reference/access-authn-authz/authentication/#authenticating-proxy. + // This is an advanced configuration option. During an end-user login flow, each of these CEL expressions + // must evaluate to either a string or an array of strings, or else the user's login will fail. + // +optional + Extra []ExtraMapping `json:"extra,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 to be unique. + // +required + 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.foo.bar'. + // + // Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ + // + // +required + ValueExpression string `json:"valueExpression"` } // JWTAuthenticator describes the configuration of a JWT authenticator. @@ -86,14 +208,14 @@ type JWTAuthenticator struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` - // Spec for configuring the authenticator. + // spec for configuring the authenticator. Spec JWTAuthenticatorSpec `json:"spec"` - // Status of the authenticator. + // status of the authenticator. Status JWTAuthenticatorStatus `json:"status,omitempty"` } -// List of JWTAuthenticator objects. +// JWTAuthenticatorList is a list of JWTAuthenticator objects. // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object type JWTAuthenticatorList struct { metav1.TypeMeta `json:",inline"` diff --git a/generated/latest/apis/concierge/authentication/v1alpha1/zz_generated.deepcopy.go b/generated/latest/apis/concierge/authentication/v1alpha1/zz_generated.deepcopy.go index 6b1235b88..cf8c0e61c 100644 --- a/generated/latest/apis/concierge/authentication/v1alpha1/zz_generated.deepcopy.go +++ b/generated/latest/apis/concierge/authentication/v1alpha1/zz_generated.deepcopy.go @@ -29,6 +29,38 @@ func (in *CertificateAuthorityDataSourceSpec) DeepCopy() *CertificateAuthorityDa return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ClaimValidationRule) DeepCopyInto(out *ClaimValidationRule) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClaimValidationRule. +func (in *ClaimValidationRule) DeepCopy() *ClaimValidationRule { + if in == nil { + return nil + } + out := new(ClaimValidationRule) + in.DeepCopyInto(out) + return out +} + +// 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. func (in *JWTAuthenticator) DeepCopyInto(out *JWTAuthenticator) { *out = *in @@ -93,7 +125,17 @@ func (in *JWTAuthenticatorList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *JWTAuthenticatorSpec) DeepCopyInto(out *JWTAuthenticatorSpec) { *out = *in - out.Claims = in.Claims + in.Claims.DeepCopyInto(&out.Claims) + if in.ClaimValidationRules != nil { + in, out := &in.ClaimValidationRules, &out.ClaimValidationRules + *out = make([]ClaimValidationRule, len(*in)) + copy(*out, *in) + } + if in.UserValidationRules != nil { + in, out := &in.UserValidationRules, &out.UserValidationRules + *out = make([]UserValidationRule, len(*in)) + copy(*out, *in) + } if in.TLS != nil { in, out := &in.TLS, &out.TLS *out = new(TLSSpec) @@ -138,6 +180,11 @@ func (in *JWTAuthenticatorStatus) DeepCopy() *JWTAuthenticatorStatus { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *JWTTokenClaims) DeepCopyInto(out *JWTTokenClaims) { *out = *in + if in.Extra != nil { + in, out := &in.Extra, &out.Extra + *out = make([]ExtraMapping, len(*in)) + copy(*out, *in) + } return } @@ -172,6 +219,22 @@ func (in *TLSSpec) DeepCopy() *TLSSpec { 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. func (in *WebhookAuthenticator) DeepCopyInto(out *WebhookAuthenticator) { *out = *in diff --git a/hack/header.txt b/hack/header.txt index 17cfca1de..c45500693 100644 --- a/hack/header.txt +++ b/hack/header.txt @@ -1,2 +1,2 @@ -Copyright 2020-2024 the Pinniped contributors. All Rights Reserved. +Copyright 2020-2025 the Pinniped contributors. All Rights Reserved. SPDX-License-Identifier: Apache-2.0 diff --git a/internal/certauthority/certauthority.go b/internal/certauthority/certauthority.go index 76ef5d97f..292e0243c 100644 --- a/internal/certauthority/certauthority.go +++ b/internal/certauthority/certauthority.go @@ -1,4 +1,4 @@ -// Copyright 2020-2024 the Pinniped contributors. All Rights Reserved. +// Copyright 2020-2025 the Pinniped contributors. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 // Package certauthority implements a simple x509 certificate authority suitable for use in an aggregated API service. @@ -170,8 +170,8 @@ func (c *CA) Pool() *x509.CertPool { // IssueClientCert issues a new client certificate with username and groups included in the Kube-style // certificate subject for the given identity and duration. -func (c *CA) IssueClientCert(username string, groups []string, ttl time.Duration) (*tls.Certificate, error) { - return c.issueCert(x509.ExtKeyUsageClientAuth, pkix.Name{CommonName: username, Organization: groups}, nil, nil, ttl) +func (c *CA) IssueClientCert(username string, groups []string, extras []string, ttl time.Duration) (*tls.Certificate, error) { + return c.issueCert(x509.ExtKeyUsageClientAuth, pkix.Name{CommonName: username, Organization: groups, OrganizationalUnit: extras}, nil, nil, ttl) } // IssueServerCert issues a new server certificate for the given identity and duration. @@ -182,8 +182,8 @@ func (c *CA) IssueServerCert(dnsNames []string, ips []net.IP, ttl time.Duration) // IssueClientCertPEM is similar to IssueClientCert, but returns the new cert as a pair of PEM-formatted byte slices // for the certificate and private key, along with the notBefore and notAfter values. -func (c *CA) IssueClientCertPEM(username string, groups []string, ttl time.Duration) (*cert.PEM, error) { - return toPEM(c.IssueClientCert(username, groups, ttl)) +func (c *CA) IssueClientCertPEM(username string, groups []string, extras []string, ttl time.Duration) (*cert.PEM, error) { + return toPEM(c.IssueClientCert(username, groups, extras, ttl)) } // IssueServerCertPEM is similar to IssueServerCert, but returns the new cert as a pair of PEM-formatted byte slices diff --git a/internal/certauthority/certauthority_test.go b/internal/certauthority/certauthority_test.go index b59eeb0f5..03e91f39e 100644 --- a/internal/certauthority/certauthority_test.go +++ b/internal/certauthority/certauthority_test.go @@ -1,4 +1,4 @@ -// Copyright 2020-2024 the Pinniped contributors. All Rights Reserved. +// Copyright 2020-2025 the Pinniped contributors. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 package certauthority @@ -326,7 +326,7 @@ func TestIssue(t *testing.T) { require.Equal(t, now.Add(-5*time.Minute), got.Leaf.NotBefore) // always back-dated require.Equal(t, now.Add(10*time.Minute), got.Leaf.NotAfter) } - got, err = tt.ca.IssueClientCert("test-user", []string{"group1", "group2"}, 10*time.Minute) + got, err = tt.ca.IssueClientCert("test-user", []string{"group1", "group2"}, []string{"extra1=val1", "extra2=val2"}, 10*time.Minute) if tt.wantErr != "" { require.EqualError(t, err, tt.wantErr) require.Nil(t, got) @@ -378,28 +378,29 @@ func TestIssueMethods(t *testing.T) { t.Run("client certs", func(t *testing.T) { user := "test-username" groups := []string{"group1", "group2"} + extras := []string{"extra1=val1", "extra2=val2"} - clientCert, err := ca.IssueClientCert(user, groups, ttl) + clientCert, err := ca.IssueClientCert(user, groups, extras, ttl) require.NoError(t, err) certPEM, keyPEM, err := ToPEM(clientCert) require.NoError(t, err) - validateClientCert(t, ca.Bundle(), certPEM, keyPEM, user, groups, ttl) + validateClientCert(t, ca.Bundle(), certPEM, keyPEM, user, groups, extras, ttl) - pem, err := ca.IssueClientCertPEM(user, groups, ttl) + pem, err := ca.IssueClientCertPEM(user, groups, extras, ttl) require.NoError(t, err) - validateClientCert(t, ca.Bundle(), pem.CertPEM, pem.KeyPEM, user, groups, ttl) + validateClientCert(t, ca.Bundle(), pem.CertPEM, pem.KeyPEM, user, groups, extras, ttl) - pem, err = ca.IssueClientCertPEM(user, nil, ttl) + pem, err = ca.IssueClientCertPEM(user, nil, nil, ttl) require.NoError(t, err) - validateClientCert(t, ca.Bundle(), pem.CertPEM, pem.KeyPEM, user, nil, ttl) + validateClientCert(t, ca.Bundle(), pem.CertPEM, pem.KeyPEM, user, nil, nil, ttl) - pem, err = ca.IssueClientCertPEM(user, []string{}, ttl) + pem, err = ca.IssueClientCertPEM(user, []string{}, []string{}, ttl) require.NoError(t, err) - validateClientCert(t, ca.Bundle(), pem.CertPEM, pem.KeyPEM, user, nil, ttl) + validateClientCert(t, ca.Bundle(), pem.CertPEM, pem.KeyPEM, user, nil, nil, ttl) - pem, err = ca.IssueClientCertPEM("", []string{}, ttl) + pem, err = ca.IssueClientCertPEM("", []string{}, []string{}, ttl) require.NoError(t, err) - validateClientCert(t, ca.Bundle(), pem.CertPEM, pem.KeyPEM, "", nil, ttl) + validateClientCert(t, ca.Bundle(), pem.CertPEM, pem.KeyPEM, "", nil, nil, ttl) }) t.Run("server certs", func(t *testing.T) { @@ -434,13 +435,14 @@ func TestIssueMethods(t *testing.T) { }) } -func validateClientCert(t *testing.T, caBundle []byte, certPEM []byte, keyPEM []byte, expectedUser string, expectedGroups []string, expectedTTL time.Duration) { +func validateClientCert(t *testing.T, caBundle []byte, certPEM []byte, keyPEM []byte, expectedUser string, expectedGroups []string, expectedExtras []string, expectedTTL time.Duration) { const fudgeFactor = 10 * time.Second v := testutil.ValidateClientCertificate(t, string(caBundle), string(certPEM)) v.RequireLifetime(time.Now(), time.Now().Add(expectedTTL), certBackdate+fudgeFactor) v.RequireMatchesPrivateKey(string(keyPEM)) v.RequireCommonName(expectedUser) v.RequireOrganizations(expectedGroups) + v.RequireOrganizationalUnits(expectedExtras) v.RequireEmptyDNSNames() v.RequireEmptyIPs() } diff --git a/internal/certauthority/dynamiccertauthority/dynamiccertauthority.go b/internal/certauthority/dynamiccertauthority/dynamiccertauthority.go index 197112797..191eabce3 100644 --- a/internal/certauthority/dynamiccertauthority/dynamiccertauthority.go +++ b/internal/certauthority/dynamiccertauthority/dynamiccertauthority.go @@ -1,4 +1,4 @@ -// Copyright 2020-2024 the Pinniped contributors. All Rights Reserved. +// Copyright 2020-2025 the Pinniped contributors. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 // Package dynamiccertauthority implements a x509 certificate authority capable of issuing @@ -34,7 +34,7 @@ func (c *ca) Name() string { // IssueClientCertPEM issues a new client certificate for the given identity and duration, returning it as a // pair of PEM-formatted byte slices for the certificate and private key, along with the notBefore and notAfter values. -func (c *ca) IssueClientCertPEM(username string, groups []string, ttl time.Duration) (*cert.PEM, error) { +func (c *ca) IssueClientCertPEM(username string, groups []string, extras []string, ttl time.Duration) (*cert.PEM, error) { caCrtPEM, caKeyPEM := c.provider.CurrentCertKeyContent() // in the future we could split dynamiccert.Private into two interfaces (Private and PrivateRead) // and have this code take PrivateRead as input. We would then add ourselves as a listener to @@ -44,5 +44,5 @@ func (c *ca) IssueClientCertPEM(username string, groups []string, ttl time.Durat return nil, err } - return ca.IssueClientCertPEM(username, groups, ttl) + return ca.IssueClientCertPEM(username, groups, extras, ttl) } diff --git a/internal/certauthority/dynamiccertauthority/dynamiccertauthority_test.go b/internal/certauthority/dynamiccertauthority/dynamiccertauthority_test.go index 34935e845..fc4ecec9b 100644 --- a/internal/certauthority/dynamiccertauthority/dynamiccertauthority_test.go +++ b/internal/certauthority/dynamiccertauthority/dynamiccertauthority_test.go @@ -1,4 +1,4 @@ -// Copyright 2020-2024 the Pinniped contributors. All Rights Reserved. +// Copyright 2020-2025 the Pinniped contributors. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 package dynamiccertauthority @@ -91,8 +91,7 @@ func TestCAIssuePEM(t *testing.T) { } for _, step := range steps { t.Run(step.name, func(t *testing.T) { - // Can't run these steps in parallel, because each one depends on the previous steps being - // run. + // Can't run these steps in parallel, because each one depends on the previous steps being run. pem, err := issuePEM(provider, ca, step.caCrtPEM, step.caKeyPEM) @@ -108,6 +107,7 @@ func TestCAIssuePEM(t *testing.T) { crtAssertions := testutil.ValidateClientCertificate(t, string(caCrtPEM), string(pem.CertPEM)) crtAssertions.RequireCommonName("some-username") crtAssertions.RequireOrganizations([]string{"some-group1", "some-group2"}) + crtAssertions.RequireOrganizationalUnits([]string{"extra1=val1", "extra2=val2"}) crtAssertions.RequireLifetime(time.Now(), time.Now().Add(time.Hour*24), time.Minute*10) crtAssertions.RequireMatchesPrivateKey(string(pem.KeyPEM)) } @@ -124,5 +124,10 @@ func issuePEM(provider dynamiccert.Provider, ca clientcertissuer.ClientCertIssue } // otherwise check to see if there is an issuing error - return ca.IssueClientCertPEM("some-username", []string{"some-group1", "some-group2"}, time.Hour*24) + return ca.IssueClientCertPEM( + "some-username", + []string{"some-group1", "some-group2"}, + []string{"extra1=val1", "extra2=val2"}, + time.Hour*24, + ) } diff --git a/internal/clientcertissuer/issuer.go b/internal/clientcertissuer/issuer.go index 3d7901051..bc4d813e5 100644 --- a/internal/clientcertissuer/issuer.go +++ b/internal/clientcertissuer/issuer.go @@ -1,4 +1,4 @@ -// Copyright 2021-2024 the Pinniped contributors. All Rights Reserved. +// Copyright 2021-2025 the Pinniped contributors. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 package clientcertissuer @@ -18,7 +18,7 @@ const defaultCertIssuerErr = constable.Error("failed to issue cert") type ClientCertIssuer interface { Name() string - IssueClientCertPEM(username string, groups []string, ttl time.Duration) (pem *cert.PEM, err error) + IssueClientCertPEM(username string, groups []string, extras []string, ttl time.Duration) (pem *cert.PEM, err error) } var _ ClientCertIssuer = ClientCertIssuers{} @@ -38,11 +38,11 @@ func (c ClientCertIssuers) Name() string { return strings.Join(names, ",") } -func (c ClientCertIssuers) IssueClientCertPEM(username string, groups []string, ttl time.Duration) (*cert.PEM, error) { +func (c ClientCertIssuers) IssueClientCertPEM(username string, groups []string, extras []string, ttl time.Duration) (*cert.PEM, error) { errs := make([]error, 0, len(c)) for _, issuer := range c { - pem, err := issuer.IssueClientCertPEM(username, groups, ttl) + pem, err := issuer.IssueClientCertPEM(username, groups, extras, ttl) if err == nil { return pem, nil } diff --git a/internal/clientcertissuer/issuer_test.go b/internal/clientcertissuer/issuer_test.go index a14e08607..8b5cbeda8 100644 --- a/internal/clientcertissuer/issuer_test.go +++ b/internal/clientcertissuer/issuer_test.go @@ -1,4 +1,4 @@ -// Copyright 2023-2024 the Pinniped contributors. All Rights Reserved. +// Copyright 2023-2025 the Pinniped contributors. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 package clientcertissuer @@ -67,6 +67,11 @@ func TestName(t *testing.T) { func TestIssueClientCertPEM(t *testing.T) { ctrl := gomock.NewController(t) + username := "test-username" + groups := []string{"group1", "group2"} + extras := []string{"extra1=val1", "extra2=val2"} + ttl := 32 * time.Second + tests := []struct { name string buildIssuerMocks func() ClientCertIssuers @@ -85,7 +90,7 @@ func TestIssueClientCertPEM(t *testing.T) { errClientCertIssuer := mockissuer.NewMockClientCertIssuer(ctrl) errClientCertIssuer.EXPECT().Name().Return("error cert issuer") errClientCertIssuer.EXPECT(). - IssueClientCertPEM("username", []string{"group1", "group2"}, 32*time.Second). + IssueClientCertPEM(username, groups, extras, ttl). Return(nil, errors.New("error from wrapped cert issuer")) return ClientCertIssuers{errClientCertIssuer} }, @@ -96,7 +101,7 @@ func TestIssueClientCertPEM(t *testing.T) { buildIssuerMocks: func() ClientCertIssuers { validClientCertIssuer := mockissuer.NewMockClientCertIssuer(ctrl) validClientCertIssuer.EXPECT(). - IssueClientCertPEM("username", []string{"group1", "group2"}, 32*time.Second). + IssueClientCertPEM(username, groups, extras, ttl). Return(&cert.PEM{CertPEM: []byte("cert"), KeyPEM: []byte("key")}, nil) return ClientCertIssuers{validClientCertIssuer} }, @@ -109,12 +114,12 @@ func TestIssueClientCertPEM(t *testing.T) { errClientCertIssuer := mockissuer.NewMockClientCertIssuer(ctrl) errClientCertIssuer.EXPECT().Name().Return("error cert issuer") errClientCertIssuer.EXPECT(). - IssueClientCertPEM("username", []string{"group1", "group2"}, 32*time.Second). + IssueClientCertPEM(username, groups, extras, ttl). Return(nil, errors.New("error from wrapped cert issuer")) validClientCertIssuer := mockissuer.NewMockClientCertIssuer(ctrl) validClientCertIssuer.EXPECT(). - IssueClientCertPEM("username", []string{"group1", "group2"}, 32*time.Second). + IssueClientCertPEM(username, groups, extras, ttl). Return(&cert.PEM{CertPEM: []byte("cert"), KeyPEM: []byte("key")}, nil) return ClientCertIssuers{ errClientCertIssuer, @@ -130,13 +135,13 @@ func TestIssueClientCertPEM(t *testing.T) { err1ClientCertIssuer := mockissuer.NewMockClientCertIssuer(ctrl) err1ClientCertIssuer.EXPECT().Name().Return("error1 cert issuer") err1ClientCertIssuer.EXPECT(). - IssueClientCertPEM("username", []string{"group1", "group2"}, 32*time.Second). + IssueClientCertPEM(username, groups, extras, ttl). Return(nil, errors.New("error1 from wrapped cert issuer")) err2ClientCertIssuer := mockissuer.NewMockClientCertIssuer(ctrl) err2ClientCertIssuer.EXPECT().Name().Return("error2 cert issuer") err2ClientCertIssuer.EXPECT(). - IssueClientCertPEM("username", []string{"group1", "group2"}, 32*time.Second). + IssueClientCertPEM(username, groups, extras, ttl). Return(nil, errors.New("error2 from wrapped cert issuer")) return ClientCertIssuers{ @@ -154,7 +159,7 @@ func TestIssueClientCertPEM(t *testing.T) { t.Parallel() pem, err := testcase.buildIssuerMocks(). - IssueClientCertPEM("username", []string{"group1", "group2"}, 32*time.Second) + IssueClientCertPEM(username, groups, extras, ttl) if testcase.wantErrorMessage != "" { require.ErrorContains(t, err, testcase.wantErrorMessage) diff --git a/internal/concierge/impersonator/impersonator_test.go b/internal/concierge/impersonator/impersonator_test.go index 3750c317d..eaa377512 100644 --- a/internal/concierge/impersonator/impersonator_test.go +++ b/internal/concierge/impersonator/impersonator_test.go @@ -2579,7 +2579,7 @@ type clientCert struct { func newClientCert(t *testing.T, ca *certauthority.CA, username string, groups []string) clientCert { t.Helper() - clientCertPEM, err := ca.IssueClientCertPEM(username, groups, time.Hour) + clientCertPEM, err := ca.IssueClientCertPEM(username, groups, nil, time.Hour) require.NoError(t, err) return clientCert{ certPEM: clientCertPEM.CertPEM, diff --git a/internal/controller/authenticator/jwtcachefiller/convert_jwtauthenticator_type.go b/internal/controller/authenticator/jwtcachefiller/convert_jwtauthenticator_type.go new file mode 100644 index 000000000..9650487cb --- /dev/null +++ b/internal/controller/authenticator/jwtcachefiller/convert_jwtauthenticator_type.go @@ -0,0 +1,107 @@ +// Copyright 2025 the Pinniped contributors. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package jwtcachefiller + +import ( + "k8s.io/apiserver/pkg/apis/apiserver" + "k8s.io/utils/ptr" + + authenticationv1alpha1 "go.pinniped.dev/generated/latest/apis/concierge/authentication/v1alpha1" +) + +// convertJWTAuthenticatorSpecType converts a Pinniped CRD spec type into the very similar +// Kubernetes library apiserver.JWTAuthenticator type. It applies a default value for username and group claims, +// but is otherwise a straight conversion. The Pinniped type includes TLS configuration which does not need +// to be converted because that is applied elsewhere. +func convertJWTAuthenticatorSpecType(spec *authenticationv1alpha1.JWTAuthenticatorSpec) apiserver.JWTAuthenticator { + usernameClaim := spec.Claims.Username + if usernameClaim == "" { + usernameClaim = defaultUsernameClaim + } + + groupsClaim := spec.Claims.Groups + if groupsClaim == "" { + groupsClaim = defaultGroupsClaim + } + + var aud []string + if len(spec.Audience) > 0 { + aud = []string{spec.Audience} + } + + jwtAuthenticator := apiserver.JWTAuthenticator{ + Issuer: apiserver.Issuer{ + URL: spec.Issuer, + Audiences: aud, + }, + ClaimMappings: apiserver.ClaimMappings{ + Username: apiserver.PrefixedClaimOrExpression{ + Claim: usernameClaim, + Prefix: ptr.To(""), + }, + Groups: apiserver.PrefixedClaimOrExpression{ + Claim: groupsClaim, + Prefix: ptr.To(""), + }, + Extra: convertExtraType(spec.Claims.Extra), + }, + ClaimValidationRules: convertClaimValidationRulesType(spec.ClaimValidationRules), + UserValidationRules: convertUserValidationRules(spec.UserValidationRules), + } + + return jwtAuthenticator +} + +func convertUserValidationRules(rules []authenticationv1alpha1.UserValidationRule) []apiserver.UserValidationRule { + if len(rules) == 0 { + return nil + } + + apiServerRules := make([]apiserver.UserValidationRule, len(rules)) + + for i := range rules { + apiServerRules[i] = apiserver.UserValidationRule{ + Expression: rules[i].Expression, + Message: rules[i].Message, + } + } + + return apiServerRules +} + +func convertClaimValidationRulesType(rules []authenticationv1alpha1.ClaimValidationRule) []apiserver.ClaimValidationRule { + if len(rules) == 0 { + return nil + } + + apiServerRules := make([]apiserver.ClaimValidationRule, len(rules)) + + for i := range rules { + apiServerRules[i] = apiserver.ClaimValidationRule{ + Claim: rules[i].Claim, + RequiredValue: rules[i].RequiredValue, + Expression: rules[i].Expression, + Message: rules[i].Message, + } + } + + return apiServerRules +} + +func convertExtraType(extras []authenticationv1alpha1.ExtraMapping) []apiserver.ExtraMapping { + if len(extras) == 0 { + return nil + } + + apiServerExtras := make([]apiserver.ExtraMapping, len(extras)) + + for i := range extras { + apiServerExtras[i] = apiserver.ExtraMapping{ + Key: extras[i].Key, + ValueExpression: extras[i].ValueExpression, + } + } + + return apiServerExtras +} diff --git a/internal/controller/authenticator/jwtcachefiller/convert_jwtauthenticator_type_test.go b/internal/controller/authenticator/jwtcachefiller/convert_jwtauthenticator_type_test.go new file mode 100644 index 000000000..fc92334a8 --- /dev/null +++ b/internal/controller/authenticator/jwtcachefiller/convert_jwtauthenticator_type_test.go @@ -0,0 +1,149 @@ +// Copyright 2025 the Pinniped contributors. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package jwtcachefiller + +import ( + "testing" + + "github.com/stretchr/testify/require" + "k8s.io/apiserver/pkg/apis/apiserver" + "k8s.io/utils/ptr" + + authenticationv1alpha1 "go.pinniped.dev/generated/latest/apis/concierge/authentication/v1alpha1" +) + +func Test_convertJWTAuthenticatorSpecType(t *testing.T) { + t.Parallel() + tests := []struct { + name string + spec *authenticationv1alpha1.JWTAuthenticatorSpec + want apiserver.JWTAuthenticator + }{ + { + name: "defaults the username and groups claims", + spec: &authenticationv1alpha1.JWTAuthenticatorSpec{ + Issuer: "https://example.com", + }, + want: apiserver.JWTAuthenticator{ + Issuer: apiserver.Issuer{ + URL: "https://example.com", + }, + ClaimMappings: apiserver.ClaimMappings{ + Username: apiserver.PrefixedClaimOrExpression{ + Claim: "username", + Prefix: ptr.To(""), + }, + Groups: apiserver.PrefixedClaimOrExpression{ + Claim: "groups", + Prefix: ptr.To(""), + }, + }, + }, + }, + { + name: "converts every field except for TLS", + spec: &authenticationv1alpha1.JWTAuthenticatorSpec{ + Issuer: "https://example.com", + Audience: "example-aud", + Claims: authenticationv1alpha1.JWTTokenClaims{ + Username: "some-username-claim", + Groups: "some-groups-claim", + Extra: []authenticationv1alpha1.ExtraMapping{ + { + Key: "key1", + ValueExpression: "expr1", + }, + { + Key: "key2", + ValueExpression: "expr2", + }, + }, + }, + ClaimValidationRules: []authenticationv1alpha1.ClaimValidationRule{ + { + Claim: "claim-claim1", + RequiredValue: "claim-value1", + Expression: "claim-expr1", + Message: "claim-msg1", + }, + { + Claim: "claim-claim2", + RequiredValue: "claim-value2", + Expression: "claim-expr2", + Message: "claim-msg2", + }, + }, + UserValidationRules: []authenticationv1alpha1.UserValidationRule{ + { + Expression: "user-expr1", + Message: "user-msg1", + }, + { + Expression: "user-expr2", + Message: "user-msg2", + }, + }, + TLS: &authenticationv1alpha1.TLSSpec{ + CertificateAuthorityData: "CA bundle value - does not need to be converted", + }, + }, + want: apiserver.JWTAuthenticator{ + Issuer: apiserver.Issuer{ + URL: "https://example.com", + Audiences: []string{"example-aud"}, + }, + ClaimMappings: apiserver.ClaimMappings{ + Username: apiserver.PrefixedClaimOrExpression{ + Claim: "some-username-claim", + Prefix: ptr.To(""), + }, + Groups: apiserver.PrefixedClaimOrExpression{ + Claim: "some-groups-claim", + Prefix: ptr.To(""), + }, + Extra: []apiserver.ExtraMapping{ + { + Key: "key1", + ValueExpression: "expr1", + }, + { + Key: "key2", + ValueExpression: "expr2", + }, + }, + }, + ClaimValidationRules: []apiserver.ClaimValidationRule{ + { + Claim: "claim-claim1", + RequiredValue: "claim-value1", + Expression: "claim-expr1", + Message: "claim-msg1", + }, + { + Claim: "claim-claim2", + RequiredValue: "claim-value2", + Expression: "claim-expr2", + Message: "claim-msg2", + }, + }, + UserValidationRules: []apiserver.UserValidationRule{ + { + Expression: "user-expr1", + Message: "user-msg1", + }, + { + Expression: "user-expr2", + Message: "user-msg2", + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + require.Equal(t, tt.want, convertJWTAuthenticatorSpecType(tt.spec)) + }) + } +} diff --git a/internal/controller/authenticator/jwtcachefiller/jwtcachefiller.go b/internal/controller/authenticator/jwtcachefiller/jwtcachefiller.go index 3570b8255..1469b69e6 100644 --- a/internal/controller/authenticator/jwtcachefiller/jwtcachefiller.go +++ b/internal/controller/authenticator/jwtcachefiller/jwtcachefiller.go @@ -1,4 +1,4 @@ -// Copyright 2020-2024 the Pinniped contributors. All Rights Reserved. +// Copyright 2020-2025 the Pinniped contributors. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 // Package jwtcachefiller implements a controller for filling an authncache.Cache with each @@ -23,12 +23,14 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" utilerrors "k8s.io/apimachinery/pkg/util/errors" + "k8s.io/apimachinery/pkg/util/validation/field" "k8s.io/apiserver/pkg/apis/apiserver" + "k8s.io/apiserver/pkg/apis/apiserver/validation" "k8s.io/apiserver/pkg/authentication/authenticator" + authenticationcel "k8s.io/apiserver/pkg/authentication/cel" "k8s.io/apiserver/plugin/pkg/authenticator/token/oidc" corev1informers "k8s.io/client-go/informers/core/v1" "k8s.io/utils/clock" - "k8s.io/utils/ptr" authenticationv1alpha1 "go.pinniped.dev/generated/latest/apis/concierge/authentication/v1alpha1" oidcapi "go.pinniped.dev/generated/latest/apis/supervisor/oidc" @@ -108,11 +110,13 @@ type tokenAuthenticatorCloser interface { type cachedJWTAuthenticator struct { authenticator.Token - issuer string - audience string - claims authenticationv1alpha1.JWTTokenClaims - caBundleHash tlsconfigutil.CABundleHash - cancel context.CancelFunc + issuer string + audience string + claims authenticationv1alpha1.JWTTokenClaims + userValidationRules []authenticationv1alpha1.UserValidationRule + claimValidationRules []authenticationv1alpha1.ClaimValidationRule + caBundleHash tlsconfigutil.CABundleHash + cancel context.CancelFunc } func (c *cachedJWTAuthenticator) Close() { @@ -353,7 +357,9 @@ func (c *jwtCacheFillerController) havePreviouslyValidated( // If any spec field has changed, then we need a new in-memory authenticator. if authenticatorFromCache.issuer == spec.Issuer && authenticatorFromCache.audience == spec.Audience && - authenticatorFromCache.claims == spec.Claims && + equality.Semantic.DeepEqual(authenticatorFromCache.claims, spec.Claims) && + equality.Semantic.DeepEqual(authenticatorFromCache.userValidationRules, spec.UserValidationRules) && + equality.Semantic.DeepEqual(authenticatorFromCache.claimValidationRules, spec.ClaimValidationRules) && tlsBundleOk && // if there was any error while validating the latest CA bundle, then do not consider it previously validated authenticatorFromCache.caBundleHash.Equal(caBundleHash) { return true, true @@ -657,33 +663,46 @@ func (c *jwtCacheFillerController) newCachedJWTAuthenticator( return nil, conditions, nil } - usernameClaim := spec.Claims.Username - if usernameClaim == "" { - usernameClaim = defaultUsernameClaim + apiServerJWTAuthenticator := convertJWTAuthenticatorSpecType(spec) + + // Reuse the validation code from Kubernetes structured auth config. Most importantly, + // this validates the claim mapping extras, claim validation rules, and user validation + // rules for us. + errList := validation.ValidateAuthenticationConfiguration( + authenticationcel.NewDefaultCompiler(), + &apiserver.AuthenticationConfiguration{JWT: []apiserver.JWTAuthenticator{apiServerJWTAuthenticator}}, + []string{}, + ) + + // In addition to all the validations checked by ValidateAuthenticationConfiguration(), + // we do not want to allow equal signs in key names. This is because we want to be able to + // add the keyname to a client certificate's OU as "keyName=value". + for i, mapping := range spec.Claims.Extra { + if strings.Contains(mapping.Key, "=") { + // Use the same field path that ValidateAuthenticationConfiguration() would build, for consistency. + fieldPath := field.NewPath("jwt").Index(0).Child("claimMappings").Child("extra").Index(i) + errList = append(errList, field.Invalid(fieldPath, "", "Pinniped does not allow extra key names to contain equals sign")) + } } - groupsClaim := spec.Claims.Groups - if groupsClaim == "" { - groupsClaim = defaultGroupsClaim + + if errList != nil { + rewriteJWTAuthenticatorErrorPrefixes(errList) + + errText := "could not initialize jwt authenticator" + err := errList.ToAggregate() + msg := fmt.Sprintf("%s: %s", errText, err.Error()) + conditions = append(conditions, &metav1.Condition{ + Type: typeAuthenticatorValid, + Status: metav1.ConditionFalse, + Reason: reasonInvalidAuthenticator, + Message: msg, + }) + return nil, conditions, nil } cancelCtx, cancel := context.WithCancel(context.Background()) oidcAuthenticator, err := oidc.New(cancelCtx, oidc.Options{ - JWTAuthenticator: apiserver.JWTAuthenticator{ - Issuer: apiserver.Issuer{ - URL: spec.Issuer, - Audiences: []string{spec.Audience}, - }, - ClaimMappings: apiserver.ClaimMappings{ - Username: apiserver.PrefixedClaimOrExpression{ - Claim: usernameClaim, - Prefix: ptr.To(""), - }, - Groups: apiserver.PrefixedClaimOrExpression{ - Claim: groupsClaim, - Prefix: ptr.To(""), - }, - }, - }, + JWTAuthenticator: apiServerJWTAuthenticator, KeySet: keySet, SupportedSigningAlgs: defaultSupportedSigningAlgos(), Client: client, @@ -706,17 +725,37 @@ func (c *jwtCacheFillerController) newCachedJWTAuthenticator( // resync err, lots of possible issues that may or may not be machine related return nil, conditions, fmt.Errorf("%s: %w", errText, err) } + conditions = append(conditions, successfulAuthenticatorValidCondition()) + return &cachedJWTAuthenticator{ - Token: oidcAuthenticator, - issuer: spec.Issuer, - audience: spec.Audience, - claims: spec.Claims, - caBundleHash: caBundleHash, - cancel: cancel, + Token: oidcAuthenticator, + issuer: spec.Issuer, + audience: spec.Audience, + claims: spec.Claims, + userValidationRules: spec.UserValidationRules, + claimValidationRules: spec.ClaimValidationRules, + caBundleHash: caBundleHash, + cancel: cancel, }, conditions, nil } +// We don't have any control over the error prefixes created by ValidateAuthenticationConfiguration(), but we +// can rewrite them to make them more consistent with our JWTAuthenticator CRD field names where they don't agree. +func rewriteJWTAuthenticatorErrorPrefixes(errList field.ErrorList) { + // ValidateAuthenticationConfiguration() will prefix all our errors with "jwt[0]." because we always pass it + // exactly one jwtAuthenticator to validate. + undesirablePrefix := fmt.Sprintf("%s.", field.NewPath("jwt").Index(0).String()) + + for _, err := range errList { + err.Field = strings.TrimPrefix(err.Field, undesirablePrefix) + if strings.HasPrefix(err.Field, "claimMappings.") { + // Pinniped's CRD calls this field "claims". Otherwise, our field names are the same. + err.Field = strings.Replace(err.Field, "claimMappings.", "claims.", 1) + } + } +} + func (c *jwtCacheFillerController) updateStatus( ctx context.Context, original *authenticationv1alpha1.JWTAuthenticator, diff --git a/internal/controller/authenticator/jwtcachefiller/jwtcachefiller_test.go b/internal/controller/authenticator/jwtcachefiller/jwtcachefiller_test.go index 0c26d97f4..a31f1a59d 100644 --- a/internal/controller/authenticator/jwtcachefiller/jwtcachefiller_test.go +++ b/internal/controller/authenticator/jwtcachefiller/jwtcachefiller_test.go @@ -360,6 +360,79 @@ func TestController(t *testing.T) { Groups: customGroupsClaim, }, } + someJWTAuthenticatorSpecWithEveryOptionalValue := &authenticationv1alpha1.JWTAuthenticatorSpec{ + Issuer: goodIssuer, + Audience: goodAudience, + TLS: goodOIDCIssuerServerTLSSpec, + Claims: authenticationv1alpha1.JWTTokenClaims{ + Username: "my-custom-username-claim", + Groups: customGroupsClaim, + Extra: []authenticationv1alpha1.ExtraMapping{ + { + Key: "example.com/key-name", // must be a domain and path + ValueExpression: `"extra-value"`, // needs to be a valid CEL expression that returns a string or list of strings + }, + }, + }, + ClaimValidationRules: []authenticationv1alpha1.ClaimValidationRule{ + { + Claim: "iss", // note: can't specify this and Expression/Message at the same time + RequiredValue: goodIssuer, + }, + }, + UserValidationRules: []authenticationv1alpha1.UserValidationRule{ + { + Expression: "true", // must be a valid CEL expression that returns a boolean + Message: "user-msg1", + }, + }, + } + invalidClaimsExtraJWTAuthenticatorSpec := &authenticationv1alpha1.JWTAuthenticatorSpec{ + Issuer: goodIssuer, + Audience: goodAudience, + TLS: goodOIDCIssuerServerTLSSpec, + Claims: authenticationv1alpha1.JWTTokenClaims{ + Extra: []authenticationv1alpha1.ExtraMapping{ + { + Key: "this-is-an-invalid-key", // this should cause a validation error in the Kubernetes library validator + ValueExpression: `"extra-value"`, + }, + }, + }, + } + invalidClaimValidationRulesJWTAuthenticatorSpec := &authenticationv1alpha1.JWTAuthenticatorSpec{ + Issuer: goodIssuer, + Audience: goodAudience, + TLS: goodOIDCIssuerServerTLSSpec, + ClaimValidationRules: []authenticationv1alpha1.ClaimValidationRule{ + { + Expression: "this is an invalid cel expression", + }, + }, + } + invalidUserValidationRulesJWTAuthenticatorSpec := &authenticationv1alpha1.JWTAuthenticatorSpec{ + Issuer: goodIssuer, + Audience: goodAudience, + TLS: goodOIDCIssuerServerTLSSpec, + UserValidationRules: []authenticationv1alpha1.UserValidationRule{ + { + Expression: "this is an invalid cel expression", + }, + }, + } + invalidClaimsExtraContainsEqualSignJWTAuthenticatorSpec := &authenticationv1alpha1.JWTAuthenticatorSpec{ + Issuer: goodIssuer, + Audience: goodAudience, + TLS: goodOIDCIssuerServerTLSSpec, + Claims: authenticationv1alpha1.JWTTokenClaims{ + Extra: []authenticationv1alpha1.ExtraMapping{ + { + Key: "example.com/key=contains-equals-sign", // this should cause a validation error in our own code + ValueExpression: `"extra-value"`, + }, + }, + }, + } otherJWTAuthenticatorSpec := &authenticationv1alpha1.JWTAuthenticatorSpec{ Issuer: someOtherIssuer, Audience: goodAudience, @@ -549,6 +622,16 @@ func TestController(t *testing.T) { Message: "unable to validate; see other conditions for details", } } + sadAuthenticatorValid := func(msg string, time metav1.Time, observedGeneration int64) metav1.Condition { + return metav1.Condition{ + Type: "AuthenticatorValid", + Status: "False", + ObservedGeneration: observedGeneration, + LastTransitionTime: time, + Reason: "InvalidAuthenticator", + Message: msg, + } + } // NOTE: we can't reach this error the way our code is written. // We check many things and fail early, resulting in an "Unknown" Authenticator status. // The only possible fail for the Authenticator itself would require us to allow more @@ -717,6 +800,7 @@ func TestController(t *testing.T) { wantActions func() []coretesting.Action wantUsernameClaim string wantGroupsClaim string + wantExtras map[string][]string wantNamesOfJWTAuthenticatorsInCache []string skipTestingCachedAuthenticator bool }{ @@ -1108,6 +1192,43 @@ func TestController(t *testing.T) { wantGroupsClaim: someJWTAuthenticatorSpecWithGroupsClaim.Claims.Groups, wantNamesOfJWTAuthenticatorsInCache: []string{"test-name"}, }, + { + name: "Sync: JWTAuthenticator with every optional value: loop will complete successfully and update status conditions", + jwtAuthenticators: []runtime.Object{ + &authenticationv1alpha1.JWTAuthenticator{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-name", + }, + Spec: *someJWTAuthenticatorSpecWithEveryOptionalValue, + }, + }, + wantLogLines: []string{ + fmt.Sprintf(`{"level":"debug","timestamp":"2099-08-08T13:57:36.123456Z","logger":"jwtcachefiller-controller","caller":"jwtcachefiller/jwtcachefiller.go:$jwtcachefiller.(*jwtCacheFillerController).updateStatus","message":"jwtauthenticator status successfully updated","jwtAuthenticator":"test-name","issuer":"%s","phase":"Ready"}`, goodIssuer), + fmt.Sprintf(`{"level":"info","timestamp":"2099-08-08T13:57:36.123456Z","logger":"jwtcachefiller-controller","caller":"jwtcachefiller/jwtcachefiller.go:$jwtcachefiller.(*jwtCacheFillerController).syncIndividualJWTAuthenticator","message":"added or updated jwt authenticator in cache","jwtAuthenticator":"test-name","issuer":"%s","isOverwrite":false}`, goodIssuer), + }, + wantActions: func() []coretesting.Action { + updateStatusAction := coretesting.NewUpdateAction(jwtAuthenticatorsGVR, "", &authenticationv1alpha1.JWTAuthenticator{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-name", + }, + Spec: *someJWTAuthenticatorSpecWithEveryOptionalValue, + Status: authenticationv1alpha1.JWTAuthenticatorStatus{ + Conditions: allHappyConditionsSuccess(goodIssuer, frozenMetav1Now, 0), + Phase: "Ready", + }, + }) + updateStatusAction.Subresource = "status" + return []coretesting.Action{ + coretesting.NewListAction(jwtAuthenticatorsGVR, jwtAUthenticatorGVK, "", metav1.ListOptions{}), + coretesting.NewWatchAction(jwtAuthenticatorsGVR, "", metav1.ListOptions{Watch: true}), + updateStatusAction, + } + }, + wantUsernameClaim: "my-custom-username-claim", + wantGroupsClaim: someJWTAuthenticatorSpecWithGroupsClaim.Claims.Groups, + wantExtras: map[string][]string{"example.com/key-name": {"extra-value"}}, + wantNamesOfJWTAuthenticatorsInCache: []string{"test-name"}, + }, { name: "Sync: JWTAuthenticator with new spec.tls fields: loop will close previous instance of JWTAuthenticator and complete successfully and update status conditions", cache: func(t *testing.T, cache *authncache.Cache, wantClose bool) { @@ -1302,6 +1423,114 @@ func TestController(t *testing.T) { wantNamesOfJWTAuthenticatorsInCache: []string{"test-name"}, wantClose: true, }, + { + name: "Sync: JWTAuthenticator with new spec.userValidationRules field: loop will close previous instance of JWTAuthenticator and complete successfully and update status conditions", + cache: func(t *testing.T, cache *authncache.Cache, wantClose bool) { + oldCA, err := base64.StdEncoding.DecodeString(someJWTAuthenticatorSpec.TLS.CertificateAuthorityData) + require.NoError(t, err) + cacheValue := newCacheValue(t, *someJWTAuthenticatorSpec, string(oldCA), wantClose) + cacheValue.userValidationRules = []authenticationv1alpha1.UserValidationRule{ + { + Expression: "true", + Message: "some old rule", + }, + } + cache.Store( + authncache.Key{ + Name: "test-name", + Kind: "JWTAuthenticator", + APIGroup: authenticationv1alpha1.SchemeGroupVersion.Group, + }, + cacheValue, + ) + }, + jwtAuthenticators: []runtime.Object{ + &authenticationv1alpha1.JWTAuthenticator{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-name", + }, + Spec: *someJWTAuthenticatorSpec, + }, + }, + wantLogLines: []string{ + fmt.Sprintf(`{"level":"debug","timestamp":"2099-08-08T13:57:36.123456Z","logger":"jwtcachefiller-controller","caller":"jwtcachefiller/jwtcachefiller.go:$jwtcachefiller.(*jwtCacheFillerController).updateStatus","message":"jwtauthenticator status successfully updated","jwtAuthenticator":"test-name","issuer":"%s","phase":"Ready"}`, goodIssuer), + fmt.Sprintf(`{"level":"info","timestamp":"2099-08-08T13:57:36.123456Z","logger":"jwtcachefiller-controller","caller":"jwtcachefiller/jwtcachefiller.go:$jwtcachefiller.(*jwtCacheFillerController).syncIndividualJWTAuthenticator","message":"added or updated jwt authenticator in cache","jwtAuthenticator":"test-name","issuer":"%s","isOverwrite":true}`, goodIssuer), + }, + wantActions: func() []coretesting.Action { + updateStatusAction := coretesting.NewUpdateAction(jwtAuthenticatorsGVR, "", &authenticationv1alpha1.JWTAuthenticator{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-name", + }, + Spec: *someJWTAuthenticatorSpec, + Status: authenticationv1alpha1.JWTAuthenticatorStatus{ + Conditions: allHappyConditionsSuccess(goodIssuer, frozenMetav1Now, 0), + Phase: "Ready", + }, + }) + updateStatusAction.Subresource = "status" + return []coretesting.Action{ + coretesting.NewListAction(jwtAuthenticatorsGVR, jwtAUthenticatorGVK, "", metav1.ListOptions{}), + coretesting.NewWatchAction(jwtAuthenticatorsGVR, "", metav1.ListOptions{Watch: true}), + updateStatusAction, + } + }, + wantNamesOfJWTAuthenticatorsInCache: []string{"test-name"}, + wantClose: true, + }, + { + name: "Sync: JWTAuthenticator with new spec.claimValidationRules field: loop will close previous instance of JWTAuthenticator and complete successfully and update status conditions", + cache: func(t *testing.T, cache *authncache.Cache, wantClose bool) { + oldCA, err := base64.StdEncoding.DecodeString(someJWTAuthenticatorSpec.TLS.CertificateAuthorityData) + require.NoError(t, err) + cacheValue := newCacheValue(t, *someJWTAuthenticatorSpec, string(oldCA), wantClose) + cacheValue.claimValidationRules = []authenticationv1alpha1.ClaimValidationRule{ + { + Claim: "iss", + RequiredValue: "some old value", + }, + } + cache.Store( + authncache.Key{ + Name: "test-name", + Kind: "JWTAuthenticator", + APIGroup: authenticationv1alpha1.SchemeGroupVersion.Group, + }, + cacheValue, + ) + }, + jwtAuthenticators: []runtime.Object{ + &authenticationv1alpha1.JWTAuthenticator{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-name", + }, + Spec: *someJWTAuthenticatorSpec, + }, + }, + wantLogLines: []string{ + fmt.Sprintf(`{"level":"debug","timestamp":"2099-08-08T13:57:36.123456Z","logger":"jwtcachefiller-controller","caller":"jwtcachefiller/jwtcachefiller.go:$jwtcachefiller.(*jwtCacheFillerController).updateStatus","message":"jwtauthenticator status successfully updated","jwtAuthenticator":"test-name","issuer":"%s","phase":"Ready"}`, goodIssuer), + fmt.Sprintf(`{"level":"info","timestamp":"2099-08-08T13:57:36.123456Z","logger":"jwtcachefiller-controller","caller":"jwtcachefiller/jwtcachefiller.go:$jwtcachefiller.(*jwtCacheFillerController).syncIndividualJWTAuthenticator","message":"added or updated jwt authenticator in cache","jwtAuthenticator":"test-name","issuer":"%s","isOverwrite":true}`, goodIssuer), + }, + wantActions: func() []coretesting.Action { + updateStatusAction := coretesting.NewUpdateAction(jwtAuthenticatorsGVR, "", &authenticationv1alpha1.JWTAuthenticator{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-name", + }, + Spec: *someJWTAuthenticatorSpec, + Status: authenticationv1alpha1.JWTAuthenticatorStatus{ + Conditions: allHappyConditionsSuccess(goodIssuer, frozenMetav1Now, 0), + Phase: "Ready", + }, + }) + updateStatusAction.Subresource = "status" + return []coretesting.Action{ + coretesting.NewListAction(jwtAuthenticatorsGVR, jwtAUthenticatorGVK, "", metav1.ListOptions{}), + coretesting.NewWatchAction(jwtAuthenticatorsGVR, "", metav1.ListOptions{Watch: true}), + updateStatusAction, + } + }, + wantNamesOfJWTAuthenticatorsInCache: []string{"test-name"}, + wantClose: true, + }, { name: "Sync: previously cached authenticator gets new spec fields, but status update fails: loop will leave it in the cache and avoid calling close", cache: func(t *testing.T, cache *authncache.Cache, wantClose bool) { @@ -2280,6 +2509,196 @@ func TestController(t *testing.T) { }, wantSyncErr: testutil.WantExactErrorString("error for JWTAuthenticator test-name: could not fetch keys: fetching keys oidc: get keys failed: 404 Not Found 404 page not found\n"), }, + { + name: "newCachedJWTAuthenticator: validateAuthenticationConfiguration: for any error in claims.extra: loop will fail sync, will write failed and unknown status conditions, but will not enqueue a resync due to user config error", + jwtAuthenticators: []runtime.Object{ + &authenticationv1alpha1.JWTAuthenticator{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-name", + }, + Spec: *invalidClaimsExtraJWTAuthenticatorSpec, + }, + }, + wantLogLines: []string{ + fmt.Sprintf(`{"level":"info","timestamp":"2099-08-08T13:57:36.123456Z","logger":"jwtcachefiller-controller","caller":"jwtcachefiller/jwtcachefiller.go:$jwtcachefiller.(*jwtCacheFillerController).syncIndividualJWTAuthenticator","message":"invalid jwt authenticator","jwtAuthenticator":"test-name","issuer":"%s","removedFromCache":false}`, invalidClaimsExtraJWTAuthenticatorSpec.Issuer), + fmt.Sprintf(`{"level":"debug","timestamp":"2099-08-08T13:57:36.123456Z","logger":"jwtcachefiller-controller","caller":"jwtcachefiller/jwtcachefiller.go:$jwtcachefiller.(*jwtCacheFillerController).updateStatus","message":"jwtauthenticator status successfully updated","jwtAuthenticator":"test-name","issuer":"%s","phase":"Error"}`, invalidClaimsExtraJWTAuthenticatorSpec.Issuer), + }, + wantActions: func() []coretesting.Action { + updateStatusAction := coretesting.NewUpdateAction(jwtAuthenticatorsGVR, "", &authenticationv1alpha1.JWTAuthenticator{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-name", + }, + Spec: *invalidClaimsExtraJWTAuthenticatorSpec, + Status: authenticationv1alpha1.JWTAuthenticatorStatus{ + Conditions: conditionstestutil.Replace( + allHappyConditionsSuccess(goodIssuer, frozenMetav1Now, 0), + []metav1.Condition{ + sadReadyCondition(frozenMetav1Now, 0), + happyIssuerURLValid(frozenMetav1Now, 0), + happyDiscoveryURLValid(frozenMetav1Now, 0), + sadAuthenticatorValid( + `could not initialize jwt authenticator: claims.extra[0].key: Invalid value: "this-is-an-invalid-key": must be a domain-prefixed path (such as "acme.io/foo")`, + frozenMetav1Now, + 0, + ), + happyJWKSURLValid(frozenMetav1Now, 0), + happyJWKSFetch(frozenMetav1Now, 0), + }, + ), + Phase: "Error", + }, + }) + updateStatusAction.Subresource = "status" + return []coretesting.Action{ + coretesting.NewListAction(jwtAuthenticatorsGVR, jwtAUthenticatorGVK, "", metav1.ListOptions{}), + coretesting.NewWatchAction(jwtAuthenticatorsGVR, "", metav1.ListOptions{Watch: true}), + updateStatusAction, + } + }, + }, + { + name: "newCachedJWTAuthenticator: validateAuthenticationConfiguration: for any error in claimValidationRules: loop will fail sync, will write failed and unknown status conditions, but will not enqueue a resync due to user config error", + jwtAuthenticators: []runtime.Object{ + &authenticationv1alpha1.JWTAuthenticator{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-name", + }, + Spec: *invalidClaimValidationRulesJWTAuthenticatorSpec, + }, + }, + wantLogLines: []string{ + fmt.Sprintf(`{"level":"info","timestamp":"2099-08-08T13:57:36.123456Z","logger":"jwtcachefiller-controller","caller":"jwtcachefiller/jwtcachefiller.go:$jwtcachefiller.(*jwtCacheFillerController).syncIndividualJWTAuthenticator","message":"invalid jwt authenticator","jwtAuthenticator":"test-name","issuer":"%s","removedFromCache":false}`, invalidClaimValidationRulesJWTAuthenticatorSpec.Issuer), + fmt.Sprintf(`{"level":"debug","timestamp":"2099-08-08T13:57:36.123456Z","logger":"jwtcachefiller-controller","caller":"jwtcachefiller/jwtcachefiller.go:$jwtcachefiller.(*jwtCacheFillerController).updateStatus","message":"jwtauthenticator status successfully updated","jwtAuthenticator":"test-name","issuer":"%s","phase":"Error"}`, invalidClaimValidationRulesJWTAuthenticatorSpec.Issuer), + }, + wantActions: func() []coretesting.Action { + updateStatusAction := coretesting.NewUpdateAction(jwtAuthenticatorsGVR, "", &authenticationv1alpha1.JWTAuthenticator{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-name", + }, + Spec: *invalidClaimValidationRulesJWTAuthenticatorSpec, + Status: authenticationv1alpha1.JWTAuthenticatorStatus{ + Conditions: conditionstestutil.Replace( + allHappyConditionsSuccess(goodIssuer, frozenMetav1Now, 0), + []metav1.Condition{ + sadReadyCondition(frozenMetav1Now, 0), + happyIssuerURLValid(frozenMetav1Now, 0), + happyDiscoveryURLValid(frozenMetav1Now, 0), + sadAuthenticatorValid( + "could not initialize jwt authenticator: claimValidationRules[0].expression: Invalid value: \"this is an invalid cel expression\": "+ + "compilation failed: ERROR: :1:6: Syntax error: mismatched input 'is' expecting \n | this is an invalid cel expression\n | .....^", + frozenMetav1Now, + 0, + ), + happyJWKSURLValid(frozenMetav1Now, 0), + happyJWKSFetch(frozenMetav1Now, 0), + }, + ), + Phase: "Error", + }, + }) + updateStatusAction.Subresource = "status" + return []coretesting.Action{ + coretesting.NewListAction(jwtAuthenticatorsGVR, jwtAUthenticatorGVK, "", metav1.ListOptions{}), + coretesting.NewWatchAction(jwtAuthenticatorsGVR, "", metav1.ListOptions{Watch: true}), + updateStatusAction, + } + }, + }, + { + name: "newCachedJWTAuthenticator: validateAuthenticationConfiguration: for any error in userValidationRules: loop will fail sync, will write failed and unknown status conditions, but will not enqueue a resync due to user config error", + jwtAuthenticators: []runtime.Object{ + &authenticationv1alpha1.JWTAuthenticator{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-name", + }, + Spec: *invalidUserValidationRulesJWTAuthenticatorSpec, + }, + }, + wantLogLines: []string{ + fmt.Sprintf(`{"level":"info","timestamp":"2099-08-08T13:57:36.123456Z","logger":"jwtcachefiller-controller","caller":"jwtcachefiller/jwtcachefiller.go:$jwtcachefiller.(*jwtCacheFillerController).syncIndividualJWTAuthenticator","message":"invalid jwt authenticator","jwtAuthenticator":"test-name","issuer":"%s","removedFromCache":false}`, invalidUserValidationRulesJWTAuthenticatorSpec.Issuer), + fmt.Sprintf(`{"level":"debug","timestamp":"2099-08-08T13:57:36.123456Z","logger":"jwtcachefiller-controller","caller":"jwtcachefiller/jwtcachefiller.go:$jwtcachefiller.(*jwtCacheFillerController).updateStatus","message":"jwtauthenticator status successfully updated","jwtAuthenticator":"test-name","issuer":"%s","phase":"Error"}`, invalidUserValidationRulesJWTAuthenticatorSpec.Issuer), + }, + wantActions: func() []coretesting.Action { + updateStatusAction := coretesting.NewUpdateAction(jwtAuthenticatorsGVR, "", &authenticationv1alpha1.JWTAuthenticator{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-name", + }, + Spec: *invalidUserValidationRulesJWTAuthenticatorSpec, + Status: authenticationv1alpha1.JWTAuthenticatorStatus{ + Conditions: conditionstestutil.Replace( + allHappyConditionsSuccess(goodIssuer, frozenMetav1Now, 0), + []metav1.Condition{ + sadReadyCondition(frozenMetav1Now, 0), + happyIssuerURLValid(frozenMetav1Now, 0), + happyDiscoveryURLValid(frozenMetav1Now, 0), + sadAuthenticatorValid( + "could not initialize jwt authenticator: userValidationRules[0].expression: Invalid value: \"this is an invalid cel expression\": "+ + "compilation failed: ERROR: :1:6: Syntax error: mismatched input 'is' expecting \n | this is an invalid cel expression\n | .....^", + frozenMetav1Now, + 0, + ), + happyJWKSURLValid(frozenMetav1Now, 0), + happyJWKSFetch(frozenMetav1Now, 0), + }, + ), + Phase: "Error", + }, + }) + updateStatusAction.Subresource = "status" + return []coretesting.Action{ + coretesting.NewListAction(jwtAuthenticatorsGVR, jwtAUthenticatorGVK, "", metav1.ListOptions{}), + coretesting.NewWatchAction(jwtAuthenticatorsGVR, "", metav1.ListOptions{Watch: true}), + updateStatusAction, + } + }, + }, + { + name: "newCachedJWTAuthenticator: when any claims.extra[].key contains an equals sign: loop will fail sync, will write failed and unknown status conditions, but will not enqueue a resync due to user config error", + jwtAuthenticators: []runtime.Object{ + &authenticationv1alpha1.JWTAuthenticator{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-name", + }, + Spec: *invalidClaimsExtraContainsEqualSignJWTAuthenticatorSpec, + }, + }, + wantLogLines: []string{ + fmt.Sprintf(`{"level":"info","timestamp":"2099-08-08T13:57:36.123456Z","logger":"jwtcachefiller-controller","caller":"jwtcachefiller/jwtcachefiller.go:$jwtcachefiller.(*jwtCacheFillerController).syncIndividualJWTAuthenticator","message":"invalid jwt authenticator","jwtAuthenticator":"test-name","issuer":"%s","removedFromCache":false}`, invalidClaimsExtraContainsEqualSignJWTAuthenticatorSpec.Issuer), + fmt.Sprintf(`{"level":"debug","timestamp":"2099-08-08T13:57:36.123456Z","logger":"jwtcachefiller-controller","caller":"jwtcachefiller/jwtcachefiller.go:$jwtcachefiller.(*jwtCacheFillerController).updateStatus","message":"jwtauthenticator status successfully updated","jwtAuthenticator":"test-name","issuer":"%s","phase":"Error"}`, invalidClaimsExtraContainsEqualSignJWTAuthenticatorSpec.Issuer), + }, + wantActions: func() []coretesting.Action { + updateStatusAction := coretesting.NewUpdateAction(jwtAuthenticatorsGVR, "", &authenticationv1alpha1.JWTAuthenticator{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-name", + }, + Spec: *invalidClaimsExtraContainsEqualSignJWTAuthenticatorSpec, + Status: authenticationv1alpha1.JWTAuthenticatorStatus{ + Conditions: conditionstestutil.Replace( + allHappyConditionsSuccess(goodIssuer, frozenMetav1Now, 0), + []metav1.Condition{ + sadReadyCondition(frozenMetav1Now, 0), + happyIssuerURLValid(frozenMetav1Now, 0), + happyDiscoveryURLValid(frozenMetav1Now, 0), + sadAuthenticatorValid( + `could not initialize jwt authenticator: claims.extra[0]: Invalid value: "": Pinniped does not allow extra key names to contain equals sign`, + frozenMetav1Now, + 0, + ), + happyJWKSURLValid(frozenMetav1Now, 0), + happyJWKSFetch(frozenMetav1Now, 0), + }, + ), + Phase: "Error", + }, + }) + updateStatusAction.Subresource = "status" + return []coretesting.Action{ + coretesting.NewListAction(jwtAuthenticatorsGVR, jwtAUthenticatorGVK, "", metav1.ListOptions{}), + coretesting.NewWatchAction(jwtAuthenticatorsGVR, "", metav1.ListOptions{Watch: true}), + updateStatusAction, + } + }, + }, { name: "updateStatus: called with matching original and updated conditions: will not make request to update conditions", jwtAuthenticators: []runtime.Object{ @@ -2512,6 +2931,7 @@ func TestController(t *testing.T) { goodUsername, tt.wantUsernameClaim, tt.wantGroupsClaim, + tt.wantExtras, goodIssuer, ) { t.Run(test.name, func(t *testing.T) { @@ -2593,6 +3013,7 @@ func testTableForAuthenticateTokenTests( goodUsername string, expectedUsernameClaim string, expectedGroupsClaim string, + expectedExtras map[string][]string, issuer string, ) []struct { name string @@ -2616,7 +3037,8 @@ func testTableForAuthenticateTokenTests( name: "good token without groups and with EC signature", wantResponse: &authenticator.Response{ User: &user.DefaultInfo{ - Name: goodUsername, + Name: goodUsername, + Extra: expectedExtras, }, }, wantAuthenticated: true, @@ -2630,7 +3052,8 @@ func testTableForAuthenticateTokenTests( }, wantResponse: &authenticator.Response{ User: &user.DefaultInfo{ - Name: goodUsername, + Name: goodUsername, + Extra: expectedExtras, }, }, wantAuthenticated: true, @@ -2644,6 +3067,7 @@ func testTableForAuthenticateTokenTests( User: &user.DefaultInfo{ Name: goodUsername, Groups: []string{group0, group1}, + Extra: expectedExtras, }, }, wantAuthenticated: true, @@ -2657,6 +3081,7 @@ func testTableForAuthenticateTokenTests( User: &user.DefaultInfo{ Name: goodUsername, Groups: []string{"some-distributed-group-1", "some-distributed-group-2"}, + Extra: expectedExtras, }, }, wantAuthenticated: true, @@ -2684,6 +3109,7 @@ func testTableForAuthenticateTokenTests( User: &user.DefaultInfo{ Name: goodUsername, Groups: []string{group0}, + Extra: expectedExtras, }, }, wantAuthenticated: true, @@ -2695,7 +3121,8 @@ func testTableForAuthenticateTokenTests( }, wantResponse: &authenticator.Response{ User: &user.DefaultInfo{ - Name: goodUsername, + Name: goodUsername, + Extra: expectedExtras, }, }, wantAuthenticated: true, @@ -2828,10 +3255,12 @@ func newCacheValue(t *testing.T, spec authenticationv1alpha1.JWTAuthenticatorSpe }) return &cachedJWTAuthenticator{ - issuer: spec.Issuer, - audience: spec.Audience, - claims: spec.Claims, - caBundleHash: tlsconfigutil.NewCABundleHash([]byte(caBundle)), + issuer: spec.Issuer, + audience: spec.Audience, + claims: spec.Claims, + userValidationRules: spec.UserValidationRules, + claimValidationRules: spec.ClaimValidationRules, + caBundleHash: tlsconfigutil.NewCABundleHash([]byte(caBundle)), cancel: func() { wasClosed = true }, diff --git a/internal/controller/impersonatorconfig/impersonator_config_test.go b/internal/controller/impersonatorconfig/impersonator_config_test.go index 6d0587e93..c0a3f7bd7 100644 --- a/internal/controller/impersonatorconfig/impersonator_config_test.go +++ b/internal/controller/impersonatorconfig/impersonator_config_test.go @@ -1,4 +1,4 @@ -// Copyright 2021-2024 the Pinniped contributors. All Rights Reserved. +// Copyright 2021-2025 the Pinniped contributors. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 package impersonatorconfig @@ -1145,7 +1145,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) { mTLSClientCertCAPrivateKeyPEM, err = mTLSClientCertCA.PrivateKeyToPEM() r.NoError(err) mTLSClientCertCASecret = newSigningKeySecret(mTLSClientCertCASecretName, mTLSClientCertCACertPEM, mTLSClientCertCAPrivateKeyPEM) - validClientCert, err = mTLSClientCertCA.IssueClientCert("username", nil, time.Hour) + validClientCert, err = mTLSClientCertCA.IssueClientCert("username", nil, nil, time.Hour) r.NoError(err) externalCA = newCA() diff --git a/internal/mocks/mockcachevalue/mockcachevalue.go b/internal/mocks/mockcachevalue/mockcachevalue.go index e20c8729e..5aa817f7a 100644 --- a/internal/mocks/mockcachevalue/mockcachevalue.go +++ b/internal/mocks/mockcachevalue/mockcachevalue.go @@ -1,4 +1,4 @@ -// Copyright 2020-2024 the Pinniped contributors. All Rights Reserved. +// Copyright 2020-2025 the Pinniped contributors. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 // diff --git a/internal/mocks/mockcredentialrequest/mockcredentialrequest.go b/internal/mocks/mockcredentialrequest/mockcredentialrequest.go index e597ab854..fbe51490b 100644 --- a/internal/mocks/mockcredentialrequest/mockcredentialrequest.go +++ b/internal/mocks/mockcredentialrequest/mockcredentialrequest.go @@ -1,4 +1,4 @@ -// Copyright 2020-2024 the Pinniped contributors. All Rights Reserved. +// Copyright 2020-2025 the Pinniped contributors. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 // diff --git a/internal/mocks/mockgithubclient/mockgithubclient.go b/internal/mocks/mockgithubclient/mockgithubclient.go index a87748866..7f7ee6f20 100644 --- a/internal/mocks/mockgithubclient/mockgithubclient.go +++ b/internal/mocks/mockgithubclient/mockgithubclient.go @@ -1,4 +1,4 @@ -// Copyright 2020-2024 the Pinniped contributors. All Rights Reserved. +// Copyright 2020-2025 the Pinniped contributors. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 // diff --git a/internal/mocks/mockissuer/mockissuer.go b/internal/mocks/mockissuer/mockissuer.go index 4a22d01de..28f711fe7 100644 --- a/internal/mocks/mockissuer/mockissuer.go +++ b/internal/mocks/mockissuer/mockissuer.go @@ -1,4 +1,4 @@ -// Copyright 2020-2024 the Pinniped contributors. All Rights Reserved. +// Copyright 2020-2025 the Pinniped contributors. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 // @@ -46,18 +46,18 @@ func (m *MockClientCertIssuer) EXPECT() *MockClientCertIssuerMockRecorder { } // IssueClientCertPEM mocks base method. -func (m *MockClientCertIssuer) IssueClientCertPEM(username string, groups []string, ttl time.Duration) (*cert.PEM, error) { +func (m *MockClientCertIssuer) IssueClientCertPEM(username string, groups, extras []string, ttl time.Duration) (*cert.PEM, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "IssueClientCertPEM", username, groups, ttl) + ret := m.ctrl.Call(m, "IssueClientCertPEM", username, groups, extras, ttl) ret0, _ := ret[0].(*cert.PEM) ret1, _ := ret[1].(error) return ret0, ret1 } // IssueClientCertPEM indicates an expected call of IssueClientCertPEM. -func (mr *MockClientCertIssuerMockRecorder) IssueClientCertPEM(username, groups, ttl any) *gomock.Call { +func (mr *MockClientCertIssuerMockRecorder) IssueClientCertPEM(username, groups, extras, ttl any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IssueClientCertPEM", reflect.TypeOf((*MockClientCertIssuer)(nil).IssueClientCertPEM), username, groups, ttl) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IssueClientCertPEM", reflect.TypeOf((*MockClientCertIssuer)(nil).IssueClientCertPEM), username, groups, extras, ttl) } // Name mocks base method. diff --git a/internal/mocks/mockkeyset/mockkeyset.go b/internal/mocks/mockkeyset/mockkeyset.go index 254f32e39..a444be834 100644 --- a/internal/mocks/mockkeyset/mockkeyset.go +++ b/internal/mocks/mockkeyset/mockkeyset.go @@ -1,4 +1,4 @@ -// Copyright 2020-2024 the Pinniped contributors. All Rights Reserved. +// Copyright 2020-2025 the Pinniped contributors. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 // diff --git a/internal/mocks/mockkubecertagent/mockdynamiccert.go b/internal/mocks/mockkubecertagent/mockdynamiccert.go index 9a5913412..c417a4f74 100644 --- a/internal/mocks/mockkubecertagent/mockdynamiccert.go +++ b/internal/mocks/mockkubecertagent/mockdynamiccert.go @@ -1,4 +1,4 @@ -// Copyright 2020-2024 the Pinniped contributors. All Rights Reserved. +// Copyright 2020-2025 the Pinniped contributors. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 // diff --git a/internal/mocks/mockkubecertagent/mockpodcommandexecutor.go b/internal/mocks/mockkubecertagent/mockpodcommandexecutor.go index de863157b..66b4e93fb 100644 --- a/internal/mocks/mockkubecertagent/mockpodcommandexecutor.go +++ b/internal/mocks/mockkubecertagent/mockpodcommandexecutor.go @@ -1,4 +1,4 @@ -// Copyright 2020-2024 the Pinniped contributors. All Rights Reserved. +// Copyright 2020-2025 the Pinniped contributors. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 // diff --git a/internal/mocks/mockldapconn/mockldapconn.go b/internal/mocks/mockldapconn/mockldapconn.go index 3896a9a2f..90a4e1264 100644 --- a/internal/mocks/mockldapconn/mockldapconn.go +++ b/internal/mocks/mockldapconn/mockldapconn.go @@ -1,4 +1,4 @@ -// Copyright 2020-2024 the Pinniped contributors. All Rights Reserved. +// Copyright 2020-2025 the Pinniped contributors. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 // diff --git a/internal/mocks/mockoidcclientoptions/mockoidcclientoptions.go b/internal/mocks/mockoidcclientoptions/mockoidcclientoptions.go index 771f61178..ce4438664 100644 --- a/internal/mocks/mockoidcclientoptions/mockoidcclientoptions.go +++ b/internal/mocks/mockoidcclientoptions/mockoidcclientoptions.go @@ -1,4 +1,4 @@ -// Copyright 2020-2024 the Pinniped contributors. All Rights Reserved. +// Copyright 2020-2025 the Pinniped contributors. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 // diff --git a/internal/mocks/mockresponsewriter/mockresponsewriter.go b/internal/mocks/mockresponsewriter/mockresponsewriter.go index 85c890f1d..1d14f37ac 100644 --- a/internal/mocks/mockresponsewriter/mockresponsewriter.go +++ b/internal/mocks/mockresponsewriter/mockresponsewriter.go @@ -1,4 +1,4 @@ -// Copyright 2020-2024 the Pinniped contributors. All Rights Reserved. +// Copyright 2020-2025 the Pinniped contributors. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 // diff --git a/internal/mocks/mocksecrethelper/mocksecrethelper.go b/internal/mocks/mocksecrethelper/mocksecrethelper.go index 3c6c7a9d7..65acf06ca 100644 --- a/internal/mocks/mocksecrethelper/mocksecrethelper.go +++ b/internal/mocks/mocksecrethelper/mocksecrethelper.go @@ -1,4 +1,4 @@ -// Copyright 2020-2024 the Pinniped contributors. All Rights Reserved. +// Copyright 2020-2025 the Pinniped contributors. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 // diff --git a/internal/mocks/mockupstreamoidcidentityprovider/mockupstreamoidcidentityprovider.go b/internal/mocks/mockupstreamoidcidentityprovider/mockupstreamoidcidentityprovider.go index 5b42ce463..d41a01b6b 100644 --- a/internal/mocks/mockupstreamoidcidentityprovider/mockupstreamoidcidentityprovider.go +++ b/internal/mocks/mockupstreamoidcidentityprovider/mockupstreamoidcidentityprovider.go @@ -1,4 +1,4 @@ -// Copyright 2020-2024 the Pinniped contributors. All Rights Reserved. +// Copyright 2020-2025 the Pinniped contributors. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 // diff --git a/internal/registry/credentialrequest/rest.go b/internal/registry/credentialrequest/rest.go index e6f7a93c5..ef67426fe 100644 --- a/internal/registry/credentialrequest/rest.go +++ b/internal/registry/credentialrequest/rest.go @@ -9,6 +9,9 @@ import ( "crypto/sha256" "errors" "fmt" + "slices" + "sort" + "strings" "time" apierrors "k8s.io/apimachinery/pkg/api/errors" @@ -16,6 +19,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" + utilvalidation "k8s.io/apimachinery/pkg/util/validation" "k8s.io/apimachinery/pkg/util/validation/field" "k8s.io/apiserver/pkg/authentication/user" genericapirequest "k8s.io/apiserver/pkg/endpoints/request" @@ -158,7 +162,12 @@ func (r *REST) Create(ctx context.Context, obj runtime.Object, createValidation return authenticationFailedResponse(), nil } - pem, err := r.issuer.IssueClientCertPEM(userInfo.GetName(), userInfo.GetGroups(), clientCertificateTTL) + pem, err := r.issuer.IssueClientCertPEM( + userInfo.GetName(), + userInfo.GetGroups(), + extrasAsKeyValues(userInfo.GetExtra()), + clientCertificateTTL, + ) if err != nil { r.auditLogger.Audit(auditevent.TokenCredentialRequestUnexpectedError, &plog.AuditParams{ ReqCtx: ctx, @@ -179,6 +188,7 @@ func (r *REST) Create(ctx context.Context, obj runtime.Object, createValidation PIIKeysAndValues: []any{ "username", userInfo.GetName(), "groups", userInfo.GetGroups(), + "extras", userInfo.GetExtra(), }, KeysAndValues: []any{ "issuedClientCert", map[string]string{ @@ -200,6 +210,18 @@ func (r *REST) Create(ctx context.Context, obj runtime.Object, createValidation }, nil } +func extrasAsKeyValues(extras map[string][]string) []string { + var kvExtras []string + for k, v := range extras { + for _, vv := range v { + // Note that this will result in a key getting repeated if it has multiple values. + kvExtras = append(kvExtras, fmt.Sprintf("%s=%s", k, vv)) + } + } + slices.Sort(kvExtras) + return kvExtras +} + func validateRequest(ctx context.Context, obj runtime.Object, createValidation rest.ValidateObjectFunc, options *metav1.CreateOptions) (*loginapi.TokenCredentialRequest, error) { credentialRequest, ok := obj.(*loginapi.TokenCredentialRequest) if !ok { @@ -245,23 +267,81 @@ func validateUserInfo(userInfo user.Info) error { return errors.New("UIDs are not supported") } - // certs cannot assert extras, but starting in K8s 1.32 the authenticator will always provide this information - if len(userInfo.GetExtra()) == 0 { // it's ok for this to be empty... - return nil + allErrs := validateExtraKeys(userInfo.GetExtra()) + if allErrs != nil { + return fmt.Errorf("authenticator returned illegal userInfo extra key(s): %w", allErrs.ToAggregate()) } - // ... but if it's not empty, should have only exactly this one key. - if len(userInfo.GetExtra()) > 1 { - return errors.New("extra may have only one key 'authentication.kubernetes.io/credential-id'") - } - - _, ok := userInfo.GetExtra()["authentication.kubernetes.io/credential-id"] - if !ok { - return errors.New("extra may have only one key 'authentication.kubernetes.io/credential-id'") - } return nil } +func validateExtraKeys(extras map[string][]string) field.ErrorList { + // Prevent WebhookAuthenticators from returning illegal extras. + // + // JWTAuthenticators are already effectively prevented from returning illegal extras because we validate + // the extra key names that are configured on the JWTAuthenticator CRD, but it shouldn't hurt to check again + // here for JWTAuthenticators too. + // + // These validations are inspired by those done in k8s.io/apiserver@v0.33.2/pkg/apis/apiserver/validation/validation.go. + // + // Keys 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. + // k8s.io, kubernetes.io and their subdomains are reserved for Kubernetes use and cannot be used. + // Keys must be lowercase. + var allErrs field.ErrorList + + // Sort the keys for stable order of error messages. + keys := make([]string, 0, len(extras)) + for k := range extras { + keys = append(keys, k) + } + sort.Strings(keys) + + for _, extraKey := range keys { + path := field.NewPath(fmt.Sprintf("userInfo extra key %q", extraKey)) + + // This is a special key that is always added by authenticators starting in K8s 1.32, so always allow it. + if extraKey == "authentication.kubernetes.io/credential-id" { + continue + } + + // Noe that IsDomainPrefixedPath also checks for empty keys. + allErrs = append(allErrs, utilvalidation.IsDomainPrefixedPath(path, extraKey)...) + + // Cannot use reserved prefixes. + if isKubernetesDomainPrefix(extraKey) { + allErrs = append(allErrs, field.Invalid(path, extraKey, "k8s.io, kubernetes.io and their subdomains are reserved for Kubernetes use")) + } + + // We can't allow equals signs in the key name, because we need to be able to encode the key names and values + // into the client cert as OU "keyName=value". + if strings.Contains(extraKey, "=") { + allErrs = append(allErrs, field.Invalid(path, extraKey, "Pinniped does not allow extra key names to contain equals sign")) + } + } + + return allErrs +} + +func isKubernetesDomainPrefix(key string) bool { + domainPrefix := getDomainPrefix(key) + if domainPrefix == "kubernetes.io" || strings.HasSuffix(domainPrefix, ".kubernetes.io") { + return true + } + if domainPrefix == "k8s.io" || strings.HasSuffix(domainPrefix, ".k8s.io") { + return true + } + return false +} + +func getDomainPrefix(key string) string { + if parts := strings.SplitN(key, "/", 2); len(parts) == 2 { + return parts[0] + } + return "" +} + func authenticationFailedResponse() *loginapi.TokenCredentialRequest { m := "authentication failed" return &loginapi.TokenCredentialRequest{ diff --git a/internal/registry/credentialrequest/rest_test.go b/internal/registry/credentialrequest/rest_test.go index 45b82a630..948a605ab 100644 --- a/internal/registry/credentialrequest/rest_test.go +++ b/internal/registry/credentialrequest/rest_test.go @@ -92,7 +92,7 @@ func TestCreate(t *testing.T) { ctrl.Finish() }) - it("CreateSucceedsWhenGivenATokenAndTheWebhookAuthenticatesTheToken", func() { + it("CreateSucceedsWhenGivenATokenAndTheAuthenticatorAuthenticatesTheToken", func() { req := validCredentialRequest() requestAuthenticator := mockcredentialrequest.NewMockTokenCredentialRequestAuthenticator(ctrl) @@ -106,6 +106,7 @@ func TestCreate(t *testing.T) { clientCertIssuer.EXPECT().IssueClientCertPEM( "test-user", []string{"test-group-1", "test-group-2"}, + nil, 5*time.Minute, ).Return(&cert.PEM{ CertPEM: []byte("test-cert"), @@ -150,6 +151,7 @@ func TestCreate(t *testing.T) { "personalInfo": map[string]any{ "username": "test-user", "groups": []any{"test-group-1", "test-group-2"}, + "extras": map[string]any{}, }, }), } @@ -167,7 +169,7 @@ func TestCreate(t *testing.T) { clientCertIssuer := mockissuer.NewMockClientCertIssuer(ctrl) clientCertIssuer.EXPECT(). - IssueClientCertPEM(gomock.Any(), gomock.Any(), gomock.Any()). + IssueClientCertPEM(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). Return(nil, fmt.Errorf("some certificate authority error")) storage := NewREST(requestAuthenticator, clientCertIssuer, schema.GroupResource{}, auditLogger) @@ -193,7 +195,7 @@ func TestCreate(t *testing.T) { } }) - it("CreateSucceedsWithAnUnauthenticatedStatusWhenGivenATokenAndTheWebhookReturnsNilUser", func() { + it("CreateSucceedsWithAnUnauthenticatedStatusWhenGivenATokenAndTheAuthenticatorReturnsNilUser", func() { req := validCredentialRequest() requestAuthenticator := mockcredentialrequest.NewMockTokenCredentialRequestAuthenticator(ctrl) @@ -222,12 +224,12 @@ func TestCreate(t *testing.T) { } }) - it("CreateSucceedsWithAnUnauthenticatedStatusWhenWebhookFails", func() { + it("CreateSucceedsWithAnUnauthenticatedStatusWhenAuthenticatorFails", func() { req := validCredentialRequest() requestAuthenticator := mockcredentialrequest.NewMockTokenCredentialRequestAuthenticator(ctrl) requestAuthenticator.EXPECT().AuthenticateTokenCredentialRequest(gomock.Any(), req). - Return(nil, errors.New("some webhook error")) + Return(nil, errors.New("some authentication error")) storage := NewREST(requestAuthenticator, nil, schema.GroupResource{}, auditLogger) @@ -248,12 +250,12 @@ func TestCreate(t *testing.T) { "name": "fake-authenticator-name", }, "reason": "authenticator returned an error", - "err": "some webhook error", + "err": "some authentication error", }), } }) - it("CreateSucceedsWithAnUnauthenticatedStatusWhenWebhookReturnsAnEmptyUsername", func() { + it("CreateSucceedsWithAnUnauthenticatedStatusWhenAuthenticatorReturnsAnEmptyUsername", func() { req := validCredentialRequest() requestAuthenticator := mockcredentialrequest.NewMockTokenCredentialRequestAuthenticator(ctrl) @@ -289,7 +291,7 @@ func TestCreate(t *testing.T) { } }) - it("CreateSucceedsWithAnUnauthenticatedStatusWhenWebhookReturnsAUserWithUID", func() { + it("CreateSucceedsWithAnUnauthenticatedStatusWhenAuthenticatorReturnsAUserWithUID", func() { req := validCredentialRequest() requestAuthenticator := mockcredentialrequest.NewMockTokenCredentialRequestAuthenticator(ctrl) @@ -329,47 +331,7 @@ func TestCreate(t *testing.T) { } }) - it("CreateSucceedsWithAnUnauthenticatedStatusWhenWebhookReturnsAUserWithExtra", func() { - req := validCredentialRequest() - - requestAuthenticator := mockcredentialrequest.NewMockTokenCredentialRequestAuthenticator(ctrl) - requestAuthenticator.EXPECT().AuthenticateTokenCredentialRequest(gomock.Any(), req). - Return(&user.DefaultInfo{ - Name: "test-user", - Groups: []string{"test-group-1", "test-group-2"}, - Extra: map[string][]string{"test-key": {"test-val-1", "test-val-2"}}, - }, nil) - - storage := NewREST(requestAuthenticator, nil, schema.GroupResource{}, auditLogger) - - response, err := callCreate(storage, req) - - requireSuccessfulResponseWithAuthenticationFailureMessage(t, err, response) - - wantAuditLog = []testutil.WantedAuditLog{ - testutil.WantAuditLog("TokenCredentialRequest Token Received", map[string]any{ - "auditID": "fake-audit-id", - "tokenID": tokenToHash(req.Spec.Token), - }), - testutil.WantAuditLog("TokenCredentialRequest Unsupported UserInfo", map[string]any{ - "auditID": "fake-audit-id", - "authenticator": map[string]any{ - "apiGroup": "fake-api-group.com", - "kind": "FakeAuthenticatorKind", - "name": "fake-authenticator-name", - }, - "reason": "unsupported value in userInfo returned by authenticator", - "err": "extra may have only one key 'authentication.kubernetes.io/credential-id'", - "userInfoExtrasCount": float64(1), - "personalInfo": map[string]any{ - "userInfoName": "test-user", - "userInfoUID": "", - }, - }), - } - }) - - it("CreateSucceedsWithAnUnauthenticatedStatusWhenWebhookReturnsAUserWithTooManyExtra", func() { + it("CreateSucceedsWhenAuthenticatorReturnsAUserWithExtras", func() { req := validCredentialRequest() requestAuthenticator := mockcredentialrequest.NewMockTokenCredentialRequestAuthenticator(ctrl) @@ -378,55 +340,22 @@ func TestCreate(t *testing.T) { Name: "test-user", Groups: []string{"test-group-1", "test-group-2"}, Extra: map[string][]string{ - "test-key": {"test-val-1", "test-val-2"}, - "authentication.kubernetes.io/credential-id": {"some-value"}, + "authentication.kubernetes.io/credential-id": {"test-val-1", "test-val-2"}, + "example.com/extra1": {"test-val-a"}, + "example.com/extra2": {"test-val-b"}, }, }, nil) - storage := NewREST(requestAuthenticator, nil, schema.GroupResource{}, auditLogger) - - response, err := callCreate(storage, req) - - requireSuccessfulResponseWithAuthenticationFailureMessage(t, err, response) - - wantAuditLog = []testutil.WantedAuditLog{ - testutil.WantAuditLog("TokenCredentialRequest Token Received", map[string]any{ - "auditID": "fake-audit-id", - "tokenID": tokenToHash(req.Spec.Token), - }), - testutil.WantAuditLog("TokenCredentialRequest Unsupported UserInfo", map[string]any{ - "auditID": "fake-audit-id", - "authenticator": map[string]any{ - "apiGroup": "fake-api-group.com", - "kind": "FakeAuthenticatorKind", - "name": "fake-authenticator-name", - }, - "reason": "unsupported value in userInfo returned by authenticator", - "err": "extra may have only one key 'authentication.kubernetes.io/credential-id'", - "userInfoExtrasCount": float64(2), - "personalInfo": map[string]any{ - "userInfoName": "test-user", - "userInfoUID": "", - }, - }), - } - }) - - it("CreateSucceedsWhenWebhookReturnsAUserWithValidExtra", func() { - req := validCredentialRequest() - - requestAuthenticator := mockcredentialrequest.NewMockTokenCredentialRequestAuthenticator(ctrl) - requestAuthenticator.EXPECT().AuthenticateTokenCredentialRequest(gomock.Any(), req). - Return(&user.DefaultInfo{ - Name: "test-user", - Groups: []string{"test-group-1", "test-group-2"}, - Extra: map[string][]string{"authentication.kubernetes.io/credential-id": {"test-val-1", "test-val-2"}}, - }, nil) - clientCertIssuer := mockissuer.NewMockClientCertIssuer(ctrl) clientCertIssuer.EXPECT().IssueClientCertPEM( "test-user", []string{"test-group-1", "test-group-2"}, + []string{ + "authentication.kubernetes.io/credential-id=test-val-1", + "authentication.kubernetes.io/credential-id=test-val-2", + "example.com/extra1=test-val-a", + "example.com/extra2=test-val-b", + }, 5*time.Minute, ).Return(&cert.PEM{ CertPEM: []byte("test-cert"), @@ -471,6 +400,51 @@ func TestCreate(t *testing.T) { "personalInfo": map[string]any{ "username": "test-user", "groups": []any{"test-group-1", "test-group-2"}, + "extras": map[string]any{ + "authentication.kubernetes.io/credential-id": []any{"test-val-1", "test-val-2"}, + "example.com/extra1": []any{"test-val-a"}, + "example.com/extra2": []any{"test-val-b"}, + }, + }, + }), + } + }) + + it("CreateSucceedsWithAnUnauthenticatedStatusWhenWebhookReturnsAUserWithInvalidExtraKey", func() { + req := validCredentialRequest() + + requestAuthenticator := mockcredentialrequest.NewMockTokenCredentialRequestAuthenticator(ctrl) + requestAuthenticator.EXPECT().AuthenticateTokenCredentialRequest(gomock.Any(), req). + Return(&user.DefaultInfo{ + Name: "test-user", + Groups: []string{"test-group-1", "test-group-2"}, + Extra: map[string][]string{"key-name": {"test-val-1", "test-val-2"}}, + }, nil) + + storage := NewREST(requestAuthenticator, nil, schema.GroupResource{}, auditLogger) + + response, err := callCreate(storage, req) + + requireSuccessfulResponseWithAuthenticationFailureMessage(t, err, response) + + wantAuditLog = []testutil.WantedAuditLog{ + testutil.WantAuditLog("TokenCredentialRequest Token Received", map[string]any{ + "auditID": "fake-audit-id", + "tokenID": tokenToHash(req.Spec.Token), + }), + testutil.WantAuditLog("TokenCredentialRequest Unsupported UserInfo", map[string]any{ + "auditID": "fake-audit-id", + "authenticator": map[string]any{ + "apiGroup": "fake-api-group.com", + "kind": "FakeAuthenticatorKind", + "name": "fake-authenticator-name", + }, + "reason": "unsupported value in userInfo returned by authenticator", + "err": `authenticator returned illegal userInfo extra key(s): userInfo extra key "key-name": Invalid value: "key-name": must be a domain-prefixed path (such as "acme.io/foo")`, + "userInfoExtrasCount": float64(1), + "personalInfo": map[string]any{ + "userInfoName": "test-user", + "userInfoUID": "", }, }), } @@ -552,6 +526,7 @@ func TestCreate(t *testing.T) { "personalInfo": map[string]any{ "username": "test-user", "groups": []any{}, + "extras": map[string]any{}, }, }), } @@ -606,6 +581,7 @@ func TestCreate(t *testing.T) { "personalInfo": map[string]any{ "username": "test-user", "groups": []any{}, + "extras": map[string]any{}, }, }), } @@ -697,7 +673,7 @@ func requireSuccessfulResponseWithAuthenticationFailureMessage(t *testing.T, err func successfulIssuer(ctrl *gomock.Controller, fakeNow time.Time) clientcertissuer.ClientCertIssuer { clientCertIssuer := mockissuer.NewMockClientCertIssuer(ctrl) clientCertIssuer.EXPECT(). - IssueClientCertPEM(gomock.Any(), gomock.Any(), gomock.Any()). + IssueClientCertPEM(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). Return(&cert.PEM{ CertPEM: []byte("test-cert"), KeyPEM: []byte("test-key"), @@ -706,3 +682,112 @@ func successfulIssuer(ctrl *gomock.Controller, fakeNow time.Time) clientcertissu }, nil) return clientCertIssuer } + +func TestValidateExtraKeys(t *testing.T) { + tests := []struct { + name string + extras map[string][]string + wantErr string + }{ + { + name: "allowed extras keys cause no error", + extras: map[string][]string{ + "foo.com/example": {"foo"}, + "x.y.z.foo.io/some-path": {"bar"}, + "authentication.kubernetes.io/credential-id": {"baz"}, + }, + }, + { + name: "empty extras keys cause error", + extras: map[string][]string{ + "": {"foo"}, + "foo.io/some-path": {"bar"}, + }, + wantErr: `userInfo extra key "": Required value`, + }, + { + name: "extras keys not prefixed by domain name cause error", + extras: map[string][]string{ + "foo": {"foo"}, + "foo.io/some-path": {"bar"}, + }, + wantErr: `userInfo extra key "foo": Invalid value: "foo": must be a domain-prefixed path (such as "acme.io/foo")`, + }, + { + name: "extras keys with upper case chars cause error", + extras: map[string][]string{ + "fooBar.com/value": {"foo"}, + "foo.io/some-path": {"bar"}, + }, + wantErr: `userInfo extra key "fooBar.com/value": Invalid value: "fooBar.com": ` + + `a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', ` + + `and must start and end with an alphanumeric character (e.g. 'example.com', regex used for validation is ` + + `'[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*')`, + }, + { + name: "extras keys with unexpected chars in domain name cause error", + extras: map[string][]string{ + "foobar🦭.com/path": {"foo"}, + "foo.io/some-path": {"bar"}, + }, + wantErr: `userInfo extra key "foobar🦭.com/path": Invalid value: "foobar🦭.com": ` + + `a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', ` + + `and must start and end with an alphanumeric character (e.g. 'example.com', regex used for validation is ` + + `'[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*')`, + }, + { + name: "extras keys with unexpected chars in path cause error", + extras: map[string][]string{ + "foobar.com/🦭": {"foo"}, + "foo.io/some-path": {"bar"}, + }, + wantErr: `userInfo extra key "foobar.com/🦭": Invalid value: "🦭": ` + + `Invalid path (regex used for validation is '[A-Za-z0-9/\-._~%!$&'()*+,;=:]+')`, + }, + { + name: "extras keys with k8s.io cause error", + extras: map[string][]string{ + "k8s.io/some-path": {"foo"}, + "sub.k8s.io/some-path": {"foo"}, + "foo.io/some-path": {"bar"}, + }, + wantErr: `[` + + `userInfo extra key "k8s.io/some-path": Invalid value: "k8s.io/some-path": k8s.io, kubernetes.io and their subdomains are reserved for Kubernetes use, ` + + `userInfo extra key "sub.k8s.io/some-path": Invalid value: "sub.k8s.io/some-path": k8s.io, kubernetes.io and their subdomains are reserved for Kubernetes use` + + `]`, + }, + { + name: "extras keys with kubernetes.io cause error", + extras: map[string][]string{ + "kubernetes.io/some-path": {"foo"}, + "sub.kubernetes.io/some-path": {"foo"}, + "foo.io/some-path": {"bar"}, + }, + wantErr: `[` + + `userInfo extra key "kubernetes.io/some-path": Invalid value: "kubernetes.io/some-path": k8s.io, kubernetes.io and their subdomains are reserved for Kubernetes use, ` + + `userInfo extra key "sub.kubernetes.io/some-path": Invalid value: "sub.kubernetes.io/some-path": k8s.io, kubernetes.io and their subdomains are reserved for Kubernetes use` + + `]`, + }, + { + name: "extras keys with equals sign cause error", + extras: map[string][]string{ + "foobar.com/some=path": {"foo"}, + "foo.io/some-path": {"bar"}, + }, + wantErr: `userInfo extra key "foobar.com/some=path": Invalid value: "foobar.com/some=path": Pinniped does not allow extra key names to contain equals sign`, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + err := validateExtraKeys(tt.extras) + + if tt.wantErr == "" { + require.Nilf(t, err, "wanted no error but got %s", err.ToAggregate()) + } else { + require.EqualError(t, err.ToAggregate(), tt.wantErr) + } + }) + } +} diff --git a/internal/testutil/certs.go b/internal/testutil/certs.go index 9d6b41d3c..62fad0800 100644 --- a/internal/testutil/certs.go +++ b/internal/testutil/certs.go @@ -1,4 +1,4 @@ -// Copyright 2020-2021 the Pinniped contributors. All Rights Reserved. +// Copyright 2020-2025 the Pinniped contributors. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 package testutil @@ -122,11 +122,18 @@ func (v *ValidCert) RequireCommonName(commonName string) { require.Equal(v.t, commonName, v.parsed.Subject.CommonName) } +// RequireOrganizations asserts that the certificate contains the provided orgs in the subject. func (v *ValidCert) RequireOrganizations(orgs []string) { v.t.Helper() require.Equal(v.t, orgs, v.parsed.Subject.Organization) } +// RequireOrganizationalUnits asserts that the certificate contains the provided organizational units in the subject. +func (v *ValidCert) RequireOrganizationalUnits(ous []string) { + v.t.Helper() + require.Equal(v.t, ous, v.parsed.Subject.OrganizationalUnit) +} + // CreateCertificate creates a certificate with the provided time bounds, and returns the PEM // representation of the certificate and its private key. The returned certificate is capable of // signing child certificates. From 64e5e20010cb2864c495521c8e9a1882e411e5f3 Mon Sep 17 00:00:00 2001 From: Ryan Richard Date: Wed, 16 Jul 2025 14:28:37 -0700 Subject: [PATCH 2/7] add usernameExpression and groupsExpression to JWTAuthenticator CRD --- .../v1alpha1/types_jwtauthenticator.go.tmpl | 59 +++++- ...cierge.pinniped.dev_jwtauthenticators.yaml | 55 +++++- generated/1.26/README.adoc | 51 ++++- .../v1alpha1/types_jwtauthenticator.go | 59 +++++- ...cierge.pinniped.dev_jwtauthenticators.yaml | 55 +++++- generated/1.27/README.adoc | 51 ++++- .../v1alpha1/types_jwtauthenticator.go | 59 +++++- ...cierge.pinniped.dev_jwtauthenticators.yaml | 55 +++++- generated/1.28/README.adoc | 51 ++++- .../v1alpha1/types_jwtauthenticator.go | 59 +++++- ...cierge.pinniped.dev_jwtauthenticators.yaml | 55 +++++- generated/1.29/README.adoc | 51 ++++- .../v1alpha1/types_jwtauthenticator.go | 59 +++++- ...cierge.pinniped.dev_jwtauthenticators.yaml | 55 +++++- generated/1.30/README.adoc | 51 ++++- .../v1alpha1/types_jwtauthenticator.go | 59 +++++- ...cierge.pinniped.dev_jwtauthenticators.yaml | 55 +++++- generated/1.31/README.adoc | 51 ++++- .../v1alpha1/types_jwtauthenticator.go | 59 +++++- ...cierge.pinniped.dev_jwtauthenticators.yaml | 55 +++++- generated/1.32/README.adoc | 51 ++++- .../v1alpha1/types_jwtauthenticator.go | 59 +++++- ...cierge.pinniped.dev_jwtauthenticators.yaml | 55 +++++- generated/1.33/README.adoc | 51 ++++- .../v1alpha1/types_jwtauthenticator.go | 59 +++++- ...cierge.pinniped.dev_jwtauthenticators.yaml | 55 +++++- generated/latest/README.adoc | 51 ++++- .../v1alpha1/types_jwtauthenticator.go | 59 +++++- .../convert_jwtauthenticator_type.go | 78 +++++--- .../convert_jwtauthenticator_type_test.go | 29 ++- .../jwtcachefiller/jwtcachefiller_test.go | 177 ++++++++++++++++-- 31 files changed, 1700 insertions(+), 128 deletions(-) diff --git a/apis/concierge/authentication/v1alpha1/types_jwtauthenticator.go.tmpl b/apis/concierge/authentication/v1alpha1/types_jwtauthenticator.go.tmpl index d5149642f..8d416bcbf 100644 --- a/apis/concierge/authentication/v1alpha1/types_jwtauthenticator.go.tmpl +++ b/apis/concierge/authentication/v1alpha1/types_jwtauthenticator.go.tmpl @@ -137,15 +137,66 @@ type UserValidationRule struct { // JWTTokenClaims allows customization of the claims that will be mapped to user identity // for Kubernetes access. type JWTTokenClaims struct { + // username is the name of the claim which should be read to extract the + // username from the JWT token. When not specified, it will default to "username", + // unless usernameExpression is specified. + // + // Mutually exclusive with usernameExpression. Use either username or usernameExpression to + // determine the user's username from the JWT token. + // +optional + Username string `json:"username"` + + // usernameExpression represents an expression which will be evaluated by CEL. + // + // The expression's result will become the user's username. + // + // The expression must produce a non-empty string value. + // If the expression uses 'claims.email', then 'claims.email_verified' must be used in + // the expression or extra[*].valueExpression or claimValidationRules[*].expression. + // An example claim validation rule expression that matches the validation automatically + // applied when username.claim is set to 'email' is 'claims.?email_verified.orValue(true) == true'. + // By explicitly comparing the value to true, we let type-checking see the result will be a boolean, + // and to make sure a non-boolean email_verified claim will be caught at runtime. + // + // 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.foo.bar'. + // + // Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ + // + // Mutually exclusive with username. Use either username or usernameExpression to + // determine the user's username from the JWT token. + // +optional + UsernameExpression string `json:"usernameExpression,omitempty"` + // groups is the name of the claim which should be read to extract the user's - // group membership from the JWT token. When not specified, it will default to "groups". + // group membership from the JWT token. When not specified, it will default to "groups", + // unless groupsExpression is specified. + // + // Mutually exclusive with groupsExpression. Use either groups or groupsExpression to + // determine the user's group membership from the JWT token. // +optional Groups string `json:"groups"` - // username is the name of the claim which should be read to extract the - // username from the JWT token. When not specified, it will default to "username". + // groupsExpression represents an expression which will be evaluated by CEL. + // + // The expression's result will become the user's group memberships. + // + // The expression must produce a string or string array value. + // "", [], and null values are treated as the group mapping not being present. + // + // 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.foo.bar'. + // + // Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ + // + // Mutually exclusive with groups. Use either groups or groupsExpression to + // determine the user's group membership from the JWT token. // +optional - Username string `json:"username"` + GroupsExpression string `json:"groupsExpression,omitempty"` // extra is similar to claimMappings.extra from Kubernetes AuthenticationConfiguration as documented in // https://kubernetes.io/docs/reference/access-authn-authz/authentication. However, note that the diff --git a/deploy/concierge/authentication.concierge.pinniped.dev_jwtauthenticators.yaml b/deploy/concierge/authentication.concierge.pinniped.dev_jwtauthenticators.yaml index cbb567616..057c8d7b8 100644 --- a/deploy/concierge/authentication.concierge.pinniped.dev_jwtauthenticators.yaml +++ b/deploy/concierge/authentication.concierge.pinniped.dev_jwtauthenticators.yaml @@ -167,12 +167,63 @@ spec: groups: description: |- groups is the name of the claim which should be read to extract the user's - group membership from the JWT token. When not specified, it will default to "groups". + group membership from the JWT token. When not specified, it will default to "groups", + unless groupsExpression is specified. + + Mutually exclusive with groupsExpression. Use either groups or groupsExpression to + determine the user's group membership from the JWT token. + type: string + groupsExpression: + description: |- + groupsExpression represents an expression which will be evaluated by CEL. + + The expression's result will become the user's group memberships. + + The expression must produce a string or string array value. + "", [], and null values are treated as the group mapping not being present. + + 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.foo.bar'. + + Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ + + Mutually exclusive with groups. Use either groups or groupsExpression to + determine the user's group membership from the JWT token. type: string username: description: |- username is the name of the claim which should be read to extract the - username from the JWT token. When not specified, it will default to "username". + username from the JWT token. When not specified, it will default to "username", + unless usernameExpression is specified. + + Mutually exclusive with usernameExpression. Use either username or usernameExpression to + determine the user's username from the JWT token. + type: string + usernameExpression: + description: |- + usernameExpression represents an expression which will be evaluated by CEL. + + The expression's result will become the user's username. + + The expression must produce a non-empty string value. + If the expression uses 'claims.email', then 'claims.email_verified' must be used in + the expression or extra[*].valueExpression or claimValidationRules[*].expression. + An example claim validation rule expression that matches the validation automatically + applied when username.claim is set to 'email' is 'claims.?email_verified.orValue(true) == true'. + By explicitly comparing the value to true, we let type-checking see the result will be a boolean, + and to make sure a non-boolean email_verified claim will be caught at runtime. + + 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.foo.bar'. + + Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ + + Mutually exclusive with username. Use either username or usernameExpression to + determine the user's username from the JWT token. type: string type: object issuer: diff --git a/generated/1.26/README.adoc b/generated/1.26/README.adoc index 14bb3c853..0983f1dc9 100644 --- a/generated/1.26/README.adoc +++ b/generated/1.26/README.adoc @@ -237,10 +237,55 @@ for Kubernetes access. [cols="25a,75a", options="header"] |=== | Field | Description -| *`groups`* __string__ | groups is the name of the claim which should be read to extract the user's + -group membership from the JWT token. When not specified, it will default to "groups". + | *`username`* __string__ | username is the name of the claim which should be read to extract the + -username from the JWT token. When not specified, it will default to "username". + +username from the JWT token. When not specified, it will default to "username", + +unless usernameExpression is specified. + + +Mutually exclusive with usernameExpression. Use either username or usernameExpression to + +determine the user's username from the JWT token. + +| *`usernameExpression`* __string__ | usernameExpression represents an expression which will be evaluated by CEL. + + +The expression's result will become the user's username. + + +The expression must produce a non-empty string value. + +If the expression uses 'claims.email', then 'claims.email_verified' must be used in + +the expression or extra[*].valueExpression or claimValidationRules[*].expression. + +An example claim validation rule expression that matches the validation automatically + +applied when username.claim is set to 'email' is 'claims.?email_verified.orValue(true) == true'. + +By explicitly comparing the value to true, we let type-checking see the result will be a boolean, + +and to make sure a non-boolean email_verified claim will be caught at runtime. + + +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.foo.bar'. + + +Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ + + +Mutually exclusive with username. Use either username or usernameExpression to + +determine the user's username from the JWT token. + +| *`groups`* __string__ | groups is the name of the claim which should be read to extract the user's + +group membership from the JWT token. When not specified, it will default to "groups", + +unless groupsExpression is specified. + + +Mutually exclusive with groupsExpression. Use either groups or groupsExpression to + +determine the user's group membership from the JWT token. + +| *`groupsExpression`* __string__ | groupsExpression represents an expression which will be evaluated by CEL. + + +The expression's result will become the user's group memberships. + + +The expression must produce a string or string array value. + +"", [], and null values are treated as the group mapping not being present. + + +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.foo.bar'. + + +Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ + + +Mutually exclusive with groups. Use either groups or groupsExpression to + +determine the user's group membership from the JWT token. + | *`extra`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-26-apis-concierge-authentication-v1alpha1-extramapping[$$ExtraMapping$$] array__ | extra is similar to claimMappings.extra from Kubernetes AuthenticationConfiguration as documented in + https://kubernetes.io/docs/reference/access-authn-authz/authentication. However, note that the + Pinniped Concierge issues client certificates to users for the purpose of authenticating, and + diff --git a/generated/1.26/apis/concierge/authentication/v1alpha1/types_jwtauthenticator.go b/generated/1.26/apis/concierge/authentication/v1alpha1/types_jwtauthenticator.go index d5149642f..8d416bcbf 100644 --- a/generated/1.26/apis/concierge/authentication/v1alpha1/types_jwtauthenticator.go +++ b/generated/1.26/apis/concierge/authentication/v1alpha1/types_jwtauthenticator.go @@ -137,15 +137,66 @@ type UserValidationRule struct { // JWTTokenClaims allows customization of the claims that will be mapped to user identity // for Kubernetes access. type JWTTokenClaims struct { + // username is the name of the claim which should be read to extract the + // username from the JWT token. When not specified, it will default to "username", + // unless usernameExpression is specified. + // + // Mutually exclusive with usernameExpression. Use either username or usernameExpression to + // determine the user's username from the JWT token. + // +optional + Username string `json:"username"` + + // usernameExpression represents an expression which will be evaluated by CEL. + // + // The expression's result will become the user's username. + // + // The expression must produce a non-empty string value. + // If the expression uses 'claims.email', then 'claims.email_verified' must be used in + // the expression or extra[*].valueExpression or claimValidationRules[*].expression. + // An example claim validation rule expression that matches the validation automatically + // applied when username.claim is set to 'email' is 'claims.?email_verified.orValue(true) == true'. + // By explicitly comparing the value to true, we let type-checking see the result will be a boolean, + // and to make sure a non-boolean email_verified claim will be caught at runtime. + // + // 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.foo.bar'. + // + // Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ + // + // Mutually exclusive with username. Use either username or usernameExpression to + // determine the user's username from the JWT token. + // +optional + UsernameExpression string `json:"usernameExpression,omitempty"` + // groups is the name of the claim which should be read to extract the user's - // group membership from the JWT token. When not specified, it will default to "groups". + // group membership from the JWT token. When not specified, it will default to "groups", + // unless groupsExpression is specified. + // + // Mutually exclusive with groupsExpression. Use either groups or groupsExpression to + // determine the user's group membership from the JWT token. // +optional Groups string `json:"groups"` - // username is the name of the claim which should be read to extract the - // username from the JWT token. When not specified, it will default to "username". + // groupsExpression represents an expression which will be evaluated by CEL. + // + // The expression's result will become the user's group memberships. + // + // The expression must produce a string or string array value. + // "", [], and null values are treated as the group mapping not being present. + // + // 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.foo.bar'. + // + // Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ + // + // Mutually exclusive with groups. Use either groups or groupsExpression to + // determine the user's group membership from the JWT token. // +optional - Username string `json:"username"` + GroupsExpression string `json:"groupsExpression,omitempty"` // extra is similar to claimMappings.extra from Kubernetes AuthenticationConfiguration as documented in // https://kubernetes.io/docs/reference/access-authn-authz/authentication. However, note that the diff --git a/generated/1.26/crds/authentication.concierge.pinniped.dev_jwtauthenticators.yaml b/generated/1.26/crds/authentication.concierge.pinniped.dev_jwtauthenticators.yaml index cbb567616..057c8d7b8 100644 --- a/generated/1.26/crds/authentication.concierge.pinniped.dev_jwtauthenticators.yaml +++ b/generated/1.26/crds/authentication.concierge.pinniped.dev_jwtauthenticators.yaml @@ -167,12 +167,63 @@ spec: groups: description: |- groups is the name of the claim which should be read to extract the user's - group membership from the JWT token. When not specified, it will default to "groups". + group membership from the JWT token. When not specified, it will default to "groups", + unless groupsExpression is specified. + + Mutually exclusive with groupsExpression. Use either groups or groupsExpression to + determine the user's group membership from the JWT token. + type: string + groupsExpression: + description: |- + groupsExpression represents an expression which will be evaluated by CEL. + + The expression's result will become the user's group memberships. + + The expression must produce a string or string array value. + "", [], and null values are treated as the group mapping not being present. + + 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.foo.bar'. + + Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ + + Mutually exclusive with groups. Use either groups or groupsExpression to + determine the user's group membership from the JWT token. type: string username: description: |- username is the name of the claim which should be read to extract the - username from the JWT token. When not specified, it will default to "username". + username from the JWT token. When not specified, it will default to "username", + unless usernameExpression is specified. + + Mutually exclusive with usernameExpression. Use either username or usernameExpression to + determine the user's username from the JWT token. + type: string + usernameExpression: + description: |- + usernameExpression represents an expression which will be evaluated by CEL. + + The expression's result will become the user's username. + + The expression must produce a non-empty string value. + If the expression uses 'claims.email', then 'claims.email_verified' must be used in + the expression or extra[*].valueExpression or claimValidationRules[*].expression. + An example claim validation rule expression that matches the validation automatically + applied when username.claim is set to 'email' is 'claims.?email_verified.orValue(true) == true'. + By explicitly comparing the value to true, we let type-checking see the result will be a boolean, + and to make sure a non-boolean email_verified claim will be caught at runtime. + + 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.foo.bar'. + + Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ + + Mutually exclusive with username. Use either username or usernameExpression to + determine the user's username from the JWT token. type: string type: object issuer: diff --git a/generated/1.27/README.adoc b/generated/1.27/README.adoc index ed8964abf..ba630e361 100644 --- a/generated/1.27/README.adoc +++ b/generated/1.27/README.adoc @@ -237,10 +237,55 @@ for Kubernetes access. [cols="25a,75a", options="header"] |=== | Field | Description -| *`groups`* __string__ | groups is the name of the claim which should be read to extract the user's + -group membership from the JWT token. When not specified, it will default to "groups". + | *`username`* __string__ | username is the name of the claim which should be read to extract the + -username from the JWT token. When not specified, it will default to "username". + +username from the JWT token. When not specified, it will default to "username", + +unless usernameExpression is specified. + + +Mutually exclusive with usernameExpression. Use either username or usernameExpression to + +determine the user's username from the JWT token. + +| *`usernameExpression`* __string__ | usernameExpression represents an expression which will be evaluated by CEL. + + +The expression's result will become the user's username. + + +The expression must produce a non-empty string value. + +If the expression uses 'claims.email', then 'claims.email_verified' must be used in + +the expression or extra[*].valueExpression or claimValidationRules[*].expression. + +An example claim validation rule expression that matches the validation automatically + +applied when username.claim is set to 'email' is 'claims.?email_verified.orValue(true) == true'. + +By explicitly comparing the value to true, we let type-checking see the result will be a boolean, + +and to make sure a non-boolean email_verified claim will be caught at runtime. + + +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.foo.bar'. + + +Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ + + +Mutually exclusive with username. Use either username or usernameExpression to + +determine the user's username from the JWT token. + +| *`groups`* __string__ | groups is the name of the claim which should be read to extract the user's + +group membership from the JWT token. When not specified, it will default to "groups", + +unless groupsExpression is specified. + + +Mutually exclusive with groupsExpression. Use either groups or groupsExpression to + +determine the user's group membership from the JWT token. + +| *`groupsExpression`* __string__ | groupsExpression represents an expression which will be evaluated by CEL. + + +The expression's result will become the user's group memberships. + + +The expression must produce a string or string array value. + +"", [], and null values are treated as the group mapping not being present. + + +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.foo.bar'. + + +Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ + + +Mutually exclusive with groups. Use either groups or groupsExpression to + +determine the user's group membership from the JWT token. + | *`extra`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-27-apis-concierge-authentication-v1alpha1-extramapping[$$ExtraMapping$$] array__ | extra is similar to claimMappings.extra from Kubernetes AuthenticationConfiguration as documented in + https://kubernetes.io/docs/reference/access-authn-authz/authentication. However, note that the + Pinniped Concierge issues client certificates to users for the purpose of authenticating, and + diff --git a/generated/1.27/apis/concierge/authentication/v1alpha1/types_jwtauthenticator.go b/generated/1.27/apis/concierge/authentication/v1alpha1/types_jwtauthenticator.go index d5149642f..8d416bcbf 100644 --- a/generated/1.27/apis/concierge/authentication/v1alpha1/types_jwtauthenticator.go +++ b/generated/1.27/apis/concierge/authentication/v1alpha1/types_jwtauthenticator.go @@ -137,15 +137,66 @@ type UserValidationRule struct { // JWTTokenClaims allows customization of the claims that will be mapped to user identity // for Kubernetes access. type JWTTokenClaims struct { + // username is the name of the claim which should be read to extract the + // username from the JWT token. When not specified, it will default to "username", + // unless usernameExpression is specified. + // + // Mutually exclusive with usernameExpression. Use either username or usernameExpression to + // determine the user's username from the JWT token. + // +optional + Username string `json:"username"` + + // usernameExpression represents an expression which will be evaluated by CEL. + // + // The expression's result will become the user's username. + // + // The expression must produce a non-empty string value. + // If the expression uses 'claims.email', then 'claims.email_verified' must be used in + // the expression or extra[*].valueExpression or claimValidationRules[*].expression. + // An example claim validation rule expression that matches the validation automatically + // applied when username.claim is set to 'email' is 'claims.?email_verified.orValue(true) == true'. + // By explicitly comparing the value to true, we let type-checking see the result will be a boolean, + // and to make sure a non-boolean email_verified claim will be caught at runtime. + // + // 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.foo.bar'. + // + // Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ + // + // Mutually exclusive with username. Use either username or usernameExpression to + // determine the user's username from the JWT token. + // +optional + UsernameExpression string `json:"usernameExpression,omitempty"` + // groups is the name of the claim which should be read to extract the user's - // group membership from the JWT token. When not specified, it will default to "groups". + // group membership from the JWT token. When not specified, it will default to "groups", + // unless groupsExpression is specified. + // + // Mutually exclusive with groupsExpression. Use either groups or groupsExpression to + // determine the user's group membership from the JWT token. // +optional Groups string `json:"groups"` - // username is the name of the claim which should be read to extract the - // username from the JWT token. When not specified, it will default to "username". + // groupsExpression represents an expression which will be evaluated by CEL. + // + // The expression's result will become the user's group memberships. + // + // The expression must produce a string or string array value. + // "", [], and null values are treated as the group mapping not being present. + // + // 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.foo.bar'. + // + // Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ + // + // Mutually exclusive with groups. Use either groups or groupsExpression to + // determine the user's group membership from the JWT token. // +optional - Username string `json:"username"` + GroupsExpression string `json:"groupsExpression,omitempty"` // extra is similar to claimMappings.extra from Kubernetes AuthenticationConfiguration as documented in // https://kubernetes.io/docs/reference/access-authn-authz/authentication. However, note that the diff --git a/generated/1.27/crds/authentication.concierge.pinniped.dev_jwtauthenticators.yaml b/generated/1.27/crds/authentication.concierge.pinniped.dev_jwtauthenticators.yaml index cbb567616..057c8d7b8 100644 --- a/generated/1.27/crds/authentication.concierge.pinniped.dev_jwtauthenticators.yaml +++ b/generated/1.27/crds/authentication.concierge.pinniped.dev_jwtauthenticators.yaml @@ -167,12 +167,63 @@ spec: groups: description: |- groups is the name of the claim which should be read to extract the user's - group membership from the JWT token. When not specified, it will default to "groups". + group membership from the JWT token. When not specified, it will default to "groups", + unless groupsExpression is specified. + + Mutually exclusive with groupsExpression. Use either groups or groupsExpression to + determine the user's group membership from the JWT token. + type: string + groupsExpression: + description: |- + groupsExpression represents an expression which will be evaluated by CEL. + + The expression's result will become the user's group memberships. + + The expression must produce a string or string array value. + "", [], and null values are treated as the group mapping not being present. + + 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.foo.bar'. + + Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ + + Mutually exclusive with groups. Use either groups or groupsExpression to + determine the user's group membership from the JWT token. type: string username: description: |- username is the name of the claim which should be read to extract the - username from the JWT token. When not specified, it will default to "username". + username from the JWT token. When not specified, it will default to "username", + unless usernameExpression is specified. + + Mutually exclusive with usernameExpression. Use either username or usernameExpression to + determine the user's username from the JWT token. + type: string + usernameExpression: + description: |- + usernameExpression represents an expression which will be evaluated by CEL. + + The expression's result will become the user's username. + + The expression must produce a non-empty string value. + If the expression uses 'claims.email', then 'claims.email_verified' must be used in + the expression or extra[*].valueExpression or claimValidationRules[*].expression. + An example claim validation rule expression that matches the validation automatically + applied when username.claim is set to 'email' is 'claims.?email_verified.orValue(true) == true'. + By explicitly comparing the value to true, we let type-checking see the result will be a boolean, + and to make sure a non-boolean email_verified claim will be caught at runtime. + + 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.foo.bar'. + + Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ + + Mutually exclusive with username. Use either username or usernameExpression to + determine the user's username from the JWT token. type: string type: object issuer: diff --git a/generated/1.28/README.adoc b/generated/1.28/README.adoc index d813352df..42d652eeb 100644 --- a/generated/1.28/README.adoc +++ b/generated/1.28/README.adoc @@ -237,10 +237,55 @@ for Kubernetes access. [cols="25a,75a", options="header"] |=== | Field | Description -| *`groups`* __string__ | groups is the name of the claim which should be read to extract the user's + -group membership from the JWT token. When not specified, it will default to "groups". + | *`username`* __string__ | username is the name of the claim which should be read to extract the + -username from the JWT token. When not specified, it will default to "username". + +username from the JWT token. When not specified, it will default to "username", + +unless usernameExpression is specified. + + +Mutually exclusive with usernameExpression. Use either username or usernameExpression to + +determine the user's username from the JWT token. + +| *`usernameExpression`* __string__ | usernameExpression represents an expression which will be evaluated by CEL. + + +The expression's result will become the user's username. + + +The expression must produce a non-empty string value. + +If the expression uses 'claims.email', then 'claims.email_verified' must be used in + +the expression or extra[*].valueExpression or claimValidationRules[*].expression. + +An example claim validation rule expression that matches the validation automatically + +applied when username.claim is set to 'email' is 'claims.?email_verified.orValue(true) == true'. + +By explicitly comparing the value to true, we let type-checking see the result will be a boolean, + +and to make sure a non-boolean email_verified claim will be caught at runtime. + + +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.foo.bar'. + + +Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ + + +Mutually exclusive with username. Use either username or usernameExpression to + +determine the user's username from the JWT token. + +| *`groups`* __string__ | groups is the name of the claim which should be read to extract the user's + +group membership from the JWT token. When not specified, it will default to "groups", + +unless groupsExpression is specified. + + +Mutually exclusive with groupsExpression. Use either groups or groupsExpression to + +determine the user's group membership from the JWT token. + +| *`groupsExpression`* __string__ | groupsExpression represents an expression which will be evaluated by CEL. + + +The expression's result will become the user's group memberships. + + +The expression must produce a string or string array value. + +"", [], and null values are treated as the group mapping not being present. + + +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.foo.bar'. + + +Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ + + +Mutually exclusive with groups. Use either groups or groupsExpression to + +determine the user's group membership from the JWT token. + | *`extra`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-28-apis-concierge-authentication-v1alpha1-extramapping[$$ExtraMapping$$] array__ | extra is similar to claimMappings.extra from Kubernetes AuthenticationConfiguration as documented in + https://kubernetes.io/docs/reference/access-authn-authz/authentication. However, note that the + Pinniped Concierge issues client certificates to users for the purpose of authenticating, and + diff --git a/generated/1.28/apis/concierge/authentication/v1alpha1/types_jwtauthenticator.go b/generated/1.28/apis/concierge/authentication/v1alpha1/types_jwtauthenticator.go index d5149642f..8d416bcbf 100644 --- a/generated/1.28/apis/concierge/authentication/v1alpha1/types_jwtauthenticator.go +++ b/generated/1.28/apis/concierge/authentication/v1alpha1/types_jwtauthenticator.go @@ -137,15 +137,66 @@ type UserValidationRule struct { // JWTTokenClaims allows customization of the claims that will be mapped to user identity // for Kubernetes access. type JWTTokenClaims struct { + // username is the name of the claim which should be read to extract the + // username from the JWT token. When not specified, it will default to "username", + // unless usernameExpression is specified. + // + // Mutually exclusive with usernameExpression. Use either username or usernameExpression to + // determine the user's username from the JWT token. + // +optional + Username string `json:"username"` + + // usernameExpression represents an expression which will be evaluated by CEL. + // + // The expression's result will become the user's username. + // + // The expression must produce a non-empty string value. + // If the expression uses 'claims.email', then 'claims.email_verified' must be used in + // the expression or extra[*].valueExpression or claimValidationRules[*].expression. + // An example claim validation rule expression that matches the validation automatically + // applied when username.claim is set to 'email' is 'claims.?email_verified.orValue(true) == true'. + // By explicitly comparing the value to true, we let type-checking see the result will be a boolean, + // and to make sure a non-boolean email_verified claim will be caught at runtime. + // + // 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.foo.bar'. + // + // Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ + // + // Mutually exclusive with username. Use either username or usernameExpression to + // determine the user's username from the JWT token. + // +optional + UsernameExpression string `json:"usernameExpression,omitempty"` + // groups is the name of the claim which should be read to extract the user's - // group membership from the JWT token. When not specified, it will default to "groups". + // group membership from the JWT token. When not specified, it will default to "groups", + // unless groupsExpression is specified. + // + // Mutually exclusive with groupsExpression. Use either groups or groupsExpression to + // determine the user's group membership from the JWT token. // +optional Groups string `json:"groups"` - // username is the name of the claim which should be read to extract the - // username from the JWT token. When not specified, it will default to "username". + // groupsExpression represents an expression which will be evaluated by CEL. + // + // The expression's result will become the user's group memberships. + // + // The expression must produce a string or string array value. + // "", [], and null values are treated as the group mapping not being present. + // + // 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.foo.bar'. + // + // Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ + // + // Mutually exclusive with groups. Use either groups or groupsExpression to + // determine the user's group membership from the JWT token. // +optional - Username string `json:"username"` + GroupsExpression string `json:"groupsExpression,omitempty"` // extra is similar to claimMappings.extra from Kubernetes AuthenticationConfiguration as documented in // https://kubernetes.io/docs/reference/access-authn-authz/authentication. However, note that the diff --git a/generated/1.28/crds/authentication.concierge.pinniped.dev_jwtauthenticators.yaml b/generated/1.28/crds/authentication.concierge.pinniped.dev_jwtauthenticators.yaml index cbb567616..057c8d7b8 100644 --- a/generated/1.28/crds/authentication.concierge.pinniped.dev_jwtauthenticators.yaml +++ b/generated/1.28/crds/authentication.concierge.pinniped.dev_jwtauthenticators.yaml @@ -167,12 +167,63 @@ spec: groups: description: |- groups is the name of the claim which should be read to extract the user's - group membership from the JWT token. When not specified, it will default to "groups". + group membership from the JWT token. When not specified, it will default to "groups", + unless groupsExpression is specified. + + Mutually exclusive with groupsExpression. Use either groups or groupsExpression to + determine the user's group membership from the JWT token. + type: string + groupsExpression: + description: |- + groupsExpression represents an expression which will be evaluated by CEL. + + The expression's result will become the user's group memberships. + + The expression must produce a string or string array value. + "", [], and null values are treated as the group mapping not being present. + + 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.foo.bar'. + + Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ + + Mutually exclusive with groups. Use either groups or groupsExpression to + determine the user's group membership from the JWT token. type: string username: description: |- username is the name of the claim which should be read to extract the - username from the JWT token. When not specified, it will default to "username". + username from the JWT token. When not specified, it will default to "username", + unless usernameExpression is specified. + + Mutually exclusive with usernameExpression. Use either username or usernameExpression to + determine the user's username from the JWT token. + type: string + usernameExpression: + description: |- + usernameExpression represents an expression which will be evaluated by CEL. + + The expression's result will become the user's username. + + The expression must produce a non-empty string value. + If the expression uses 'claims.email', then 'claims.email_verified' must be used in + the expression or extra[*].valueExpression or claimValidationRules[*].expression. + An example claim validation rule expression that matches the validation automatically + applied when username.claim is set to 'email' is 'claims.?email_verified.orValue(true) == true'. + By explicitly comparing the value to true, we let type-checking see the result will be a boolean, + and to make sure a non-boolean email_verified claim will be caught at runtime. + + 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.foo.bar'. + + Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ + + Mutually exclusive with username. Use either username or usernameExpression to + determine the user's username from the JWT token. type: string type: object issuer: diff --git a/generated/1.29/README.adoc b/generated/1.29/README.adoc index d935f9896..2b5102cf5 100644 --- a/generated/1.29/README.adoc +++ b/generated/1.29/README.adoc @@ -237,10 +237,55 @@ for Kubernetes access. [cols="25a,75a", options="header"] |=== | Field | Description -| *`groups`* __string__ | groups is the name of the claim which should be read to extract the user's + -group membership from the JWT token. When not specified, it will default to "groups". + | *`username`* __string__ | username is the name of the claim which should be read to extract the + -username from the JWT token. When not specified, it will default to "username". + +username from the JWT token. When not specified, it will default to "username", + +unless usernameExpression is specified. + + +Mutually exclusive with usernameExpression. Use either username or usernameExpression to + +determine the user's username from the JWT token. + +| *`usernameExpression`* __string__ | usernameExpression represents an expression which will be evaluated by CEL. + + +The expression's result will become the user's username. + + +The expression must produce a non-empty string value. + +If the expression uses 'claims.email', then 'claims.email_verified' must be used in + +the expression or extra[*].valueExpression or claimValidationRules[*].expression. + +An example claim validation rule expression that matches the validation automatically + +applied when username.claim is set to 'email' is 'claims.?email_verified.orValue(true) == true'. + +By explicitly comparing the value to true, we let type-checking see the result will be a boolean, + +and to make sure a non-boolean email_verified claim will be caught at runtime. + + +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.foo.bar'. + + +Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ + + +Mutually exclusive with username. Use either username or usernameExpression to + +determine the user's username from the JWT token. + +| *`groups`* __string__ | groups is the name of the claim which should be read to extract the user's + +group membership from the JWT token. When not specified, it will default to "groups", + +unless groupsExpression is specified. + + +Mutually exclusive with groupsExpression. Use either groups or groupsExpression to + +determine the user's group membership from the JWT token. + +| *`groupsExpression`* __string__ | groupsExpression represents an expression which will be evaluated by CEL. + + +The expression's result will become the user's group memberships. + + +The expression must produce a string or string array value. + +"", [], and null values are treated as the group mapping not being present. + + +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.foo.bar'. + + +Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ + + +Mutually exclusive with groups. Use either groups or groupsExpression to + +determine the user's group membership from the JWT token. + | *`extra`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-29-apis-concierge-authentication-v1alpha1-extramapping[$$ExtraMapping$$] array__ | extra is similar to claimMappings.extra from Kubernetes AuthenticationConfiguration as documented in + https://kubernetes.io/docs/reference/access-authn-authz/authentication. However, note that the + Pinniped Concierge issues client certificates to users for the purpose of authenticating, and + diff --git a/generated/1.29/apis/concierge/authentication/v1alpha1/types_jwtauthenticator.go b/generated/1.29/apis/concierge/authentication/v1alpha1/types_jwtauthenticator.go index d5149642f..8d416bcbf 100644 --- a/generated/1.29/apis/concierge/authentication/v1alpha1/types_jwtauthenticator.go +++ b/generated/1.29/apis/concierge/authentication/v1alpha1/types_jwtauthenticator.go @@ -137,15 +137,66 @@ type UserValidationRule struct { // JWTTokenClaims allows customization of the claims that will be mapped to user identity // for Kubernetes access. type JWTTokenClaims struct { + // username is the name of the claim which should be read to extract the + // username from the JWT token. When not specified, it will default to "username", + // unless usernameExpression is specified. + // + // Mutually exclusive with usernameExpression. Use either username or usernameExpression to + // determine the user's username from the JWT token. + // +optional + Username string `json:"username"` + + // usernameExpression represents an expression which will be evaluated by CEL. + // + // The expression's result will become the user's username. + // + // The expression must produce a non-empty string value. + // If the expression uses 'claims.email', then 'claims.email_verified' must be used in + // the expression or extra[*].valueExpression or claimValidationRules[*].expression. + // An example claim validation rule expression that matches the validation automatically + // applied when username.claim is set to 'email' is 'claims.?email_verified.orValue(true) == true'. + // By explicitly comparing the value to true, we let type-checking see the result will be a boolean, + // and to make sure a non-boolean email_verified claim will be caught at runtime. + // + // 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.foo.bar'. + // + // Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ + // + // Mutually exclusive with username. Use either username or usernameExpression to + // determine the user's username from the JWT token. + // +optional + UsernameExpression string `json:"usernameExpression,omitempty"` + // groups is the name of the claim which should be read to extract the user's - // group membership from the JWT token. When not specified, it will default to "groups". + // group membership from the JWT token. When not specified, it will default to "groups", + // unless groupsExpression is specified. + // + // Mutually exclusive with groupsExpression. Use either groups or groupsExpression to + // determine the user's group membership from the JWT token. // +optional Groups string `json:"groups"` - // username is the name of the claim which should be read to extract the - // username from the JWT token. When not specified, it will default to "username". + // groupsExpression represents an expression which will be evaluated by CEL. + // + // The expression's result will become the user's group memberships. + // + // The expression must produce a string or string array value. + // "", [], and null values are treated as the group mapping not being present. + // + // 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.foo.bar'. + // + // Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ + // + // Mutually exclusive with groups. Use either groups or groupsExpression to + // determine the user's group membership from the JWT token. // +optional - Username string `json:"username"` + GroupsExpression string `json:"groupsExpression,omitempty"` // extra is similar to claimMappings.extra from Kubernetes AuthenticationConfiguration as documented in // https://kubernetes.io/docs/reference/access-authn-authz/authentication. However, note that the diff --git a/generated/1.29/crds/authentication.concierge.pinniped.dev_jwtauthenticators.yaml b/generated/1.29/crds/authentication.concierge.pinniped.dev_jwtauthenticators.yaml index cbb567616..057c8d7b8 100644 --- a/generated/1.29/crds/authentication.concierge.pinniped.dev_jwtauthenticators.yaml +++ b/generated/1.29/crds/authentication.concierge.pinniped.dev_jwtauthenticators.yaml @@ -167,12 +167,63 @@ spec: groups: description: |- groups is the name of the claim which should be read to extract the user's - group membership from the JWT token. When not specified, it will default to "groups". + group membership from the JWT token. When not specified, it will default to "groups", + unless groupsExpression is specified. + + Mutually exclusive with groupsExpression. Use either groups or groupsExpression to + determine the user's group membership from the JWT token. + type: string + groupsExpression: + description: |- + groupsExpression represents an expression which will be evaluated by CEL. + + The expression's result will become the user's group memberships. + + The expression must produce a string or string array value. + "", [], and null values are treated as the group mapping not being present. + + 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.foo.bar'. + + Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ + + Mutually exclusive with groups. Use either groups or groupsExpression to + determine the user's group membership from the JWT token. type: string username: description: |- username is the name of the claim which should be read to extract the - username from the JWT token. When not specified, it will default to "username". + username from the JWT token. When not specified, it will default to "username", + unless usernameExpression is specified. + + Mutually exclusive with usernameExpression. Use either username or usernameExpression to + determine the user's username from the JWT token. + type: string + usernameExpression: + description: |- + usernameExpression represents an expression which will be evaluated by CEL. + + The expression's result will become the user's username. + + The expression must produce a non-empty string value. + If the expression uses 'claims.email', then 'claims.email_verified' must be used in + the expression or extra[*].valueExpression or claimValidationRules[*].expression. + An example claim validation rule expression that matches the validation automatically + applied when username.claim is set to 'email' is 'claims.?email_verified.orValue(true) == true'. + By explicitly comparing the value to true, we let type-checking see the result will be a boolean, + and to make sure a non-boolean email_verified claim will be caught at runtime. + + 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.foo.bar'. + + Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ + + Mutually exclusive with username. Use either username or usernameExpression to + determine the user's username from the JWT token. type: string type: object issuer: diff --git a/generated/1.30/README.adoc b/generated/1.30/README.adoc index 2615c5333..3c64b7b2d 100644 --- a/generated/1.30/README.adoc +++ b/generated/1.30/README.adoc @@ -237,10 +237,55 @@ for Kubernetes access. [cols="25a,75a", options="header"] |=== | Field | Description -| *`groups`* __string__ | groups is the name of the claim which should be read to extract the user's + -group membership from the JWT token. When not specified, it will default to "groups". + | *`username`* __string__ | username is the name of the claim which should be read to extract the + -username from the JWT token. When not specified, it will default to "username". + +username from the JWT token. When not specified, it will default to "username", + +unless usernameExpression is specified. + + +Mutually exclusive with usernameExpression. Use either username or usernameExpression to + +determine the user's username from the JWT token. + +| *`usernameExpression`* __string__ | usernameExpression represents an expression which will be evaluated by CEL. + + +The expression's result will become the user's username. + + +The expression must produce a non-empty string value. + +If the expression uses 'claims.email', then 'claims.email_verified' must be used in + +the expression or extra[*].valueExpression or claimValidationRules[*].expression. + +An example claim validation rule expression that matches the validation automatically + +applied when username.claim is set to 'email' is 'claims.?email_verified.orValue(true) == true'. + +By explicitly comparing the value to true, we let type-checking see the result will be a boolean, + +and to make sure a non-boolean email_verified claim will be caught at runtime. + + +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.foo.bar'. + + +Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ + + +Mutually exclusive with username. Use either username or usernameExpression to + +determine the user's username from the JWT token. + +| *`groups`* __string__ | groups is the name of the claim which should be read to extract the user's + +group membership from the JWT token. When not specified, it will default to "groups", + +unless groupsExpression is specified. + + +Mutually exclusive with groupsExpression. Use either groups or groupsExpression to + +determine the user's group membership from the JWT token. + +| *`groupsExpression`* __string__ | groupsExpression represents an expression which will be evaluated by CEL. + + +The expression's result will become the user's group memberships. + + +The expression must produce a string or string array value. + +"", [], and null values are treated as the group mapping not being present. + + +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.foo.bar'. + + +Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ + + +Mutually exclusive with groups. Use either groups or groupsExpression to + +determine the user's group membership from the JWT token. + | *`extra`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-30-apis-concierge-authentication-v1alpha1-extramapping[$$ExtraMapping$$] array__ | extra is similar to claimMappings.extra from Kubernetes AuthenticationConfiguration as documented in + https://kubernetes.io/docs/reference/access-authn-authz/authentication. However, note that the + Pinniped Concierge issues client certificates to users for the purpose of authenticating, and + diff --git a/generated/1.30/apis/concierge/authentication/v1alpha1/types_jwtauthenticator.go b/generated/1.30/apis/concierge/authentication/v1alpha1/types_jwtauthenticator.go index d5149642f..8d416bcbf 100644 --- a/generated/1.30/apis/concierge/authentication/v1alpha1/types_jwtauthenticator.go +++ b/generated/1.30/apis/concierge/authentication/v1alpha1/types_jwtauthenticator.go @@ -137,15 +137,66 @@ type UserValidationRule struct { // JWTTokenClaims allows customization of the claims that will be mapped to user identity // for Kubernetes access. type JWTTokenClaims struct { + // username is the name of the claim which should be read to extract the + // username from the JWT token. When not specified, it will default to "username", + // unless usernameExpression is specified. + // + // Mutually exclusive with usernameExpression. Use either username or usernameExpression to + // determine the user's username from the JWT token. + // +optional + Username string `json:"username"` + + // usernameExpression represents an expression which will be evaluated by CEL. + // + // The expression's result will become the user's username. + // + // The expression must produce a non-empty string value. + // If the expression uses 'claims.email', then 'claims.email_verified' must be used in + // the expression or extra[*].valueExpression or claimValidationRules[*].expression. + // An example claim validation rule expression that matches the validation automatically + // applied when username.claim is set to 'email' is 'claims.?email_verified.orValue(true) == true'. + // By explicitly comparing the value to true, we let type-checking see the result will be a boolean, + // and to make sure a non-boolean email_verified claim will be caught at runtime. + // + // 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.foo.bar'. + // + // Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ + // + // Mutually exclusive with username. Use either username or usernameExpression to + // determine the user's username from the JWT token. + // +optional + UsernameExpression string `json:"usernameExpression,omitempty"` + // groups is the name of the claim which should be read to extract the user's - // group membership from the JWT token. When not specified, it will default to "groups". + // group membership from the JWT token. When not specified, it will default to "groups", + // unless groupsExpression is specified. + // + // Mutually exclusive with groupsExpression. Use either groups or groupsExpression to + // determine the user's group membership from the JWT token. // +optional Groups string `json:"groups"` - // username is the name of the claim which should be read to extract the - // username from the JWT token. When not specified, it will default to "username". + // groupsExpression represents an expression which will be evaluated by CEL. + // + // The expression's result will become the user's group memberships. + // + // The expression must produce a string or string array value. + // "", [], and null values are treated as the group mapping not being present. + // + // 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.foo.bar'. + // + // Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ + // + // Mutually exclusive with groups. Use either groups or groupsExpression to + // determine the user's group membership from the JWT token. // +optional - Username string `json:"username"` + GroupsExpression string `json:"groupsExpression,omitempty"` // extra is similar to claimMappings.extra from Kubernetes AuthenticationConfiguration as documented in // https://kubernetes.io/docs/reference/access-authn-authz/authentication. However, note that the diff --git a/generated/1.30/crds/authentication.concierge.pinniped.dev_jwtauthenticators.yaml b/generated/1.30/crds/authentication.concierge.pinniped.dev_jwtauthenticators.yaml index cbb567616..057c8d7b8 100644 --- a/generated/1.30/crds/authentication.concierge.pinniped.dev_jwtauthenticators.yaml +++ b/generated/1.30/crds/authentication.concierge.pinniped.dev_jwtauthenticators.yaml @@ -167,12 +167,63 @@ spec: groups: description: |- groups is the name of the claim which should be read to extract the user's - group membership from the JWT token. When not specified, it will default to "groups". + group membership from the JWT token. When not specified, it will default to "groups", + unless groupsExpression is specified. + + Mutually exclusive with groupsExpression. Use either groups or groupsExpression to + determine the user's group membership from the JWT token. + type: string + groupsExpression: + description: |- + groupsExpression represents an expression which will be evaluated by CEL. + + The expression's result will become the user's group memberships. + + The expression must produce a string or string array value. + "", [], and null values are treated as the group mapping not being present. + + 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.foo.bar'. + + Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ + + Mutually exclusive with groups. Use either groups or groupsExpression to + determine the user's group membership from the JWT token. type: string username: description: |- username is the name of the claim which should be read to extract the - username from the JWT token. When not specified, it will default to "username". + username from the JWT token. When not specified, it will default to "username", + unless usernameExpression is specified. + + Mutually exclusive with usernameExpression. Use either username or usernameExpression to + determine the user's username from the JWT token. + type: string + usernameExpression: + description: |- + usernameExpression represents an expression which will be evaluated by CEL. + + The expression's result will become the user's username. + + The expression must produce a non-empty string value. + If the expression uses 'claims.email', then 'claims.email_verified' must be used in + the expression or extra[*].valueExpression or claimValidationRules[*].expression. + An example claim validation rule expression that matches the validation automatically + applied when username.claim is set to 'email' is 'claims.?email_verified.orValue(true) == true'. + By explicitly comparing the value to true, we let type-checking see the result will be a boolean, + and to make sure a non-boolean email_verified claim will be caught at runtime. + + 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.foo.bar'. + + Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ + + Mutually exclusive with username. Use either username or usernameExpression to + determine the user's username from the JWT token. type: string type: object issuer: diff --git a/generated/1.31/README.adoc b/generated/1.31/README.adoc index 870693cb6..d05ca7c5d 100644 --- a/generated/1.31/README.adoc +++ b/generated/1.31/README.adoc @@ -237,10 +237,55 @@ for Kubernetes access. [cols="25a,75a", options="header"] |=== | Field | Description -| *`groups`* __string__ | groups is the name of the claim which should be read to extract the user's + -group membership from the JWT token. When not specified, it will default to "groups". + | *`username`* __string__ | username is the name of the claim which should be read to extract the + -username from the JWT token. When not specified, it will default to "username". + +username from the JWT token. When not specified, it will default to "username", + +unless usernameExpression is specified. + + +Mutually exclusive with usernameExpression. Use either username or usernameExpression to + +determine the user's username from the JWT token. + +| *`usernameExpression`* __string__ | usernameExpression represents an expression which will be evaluated by CEL. + + +The expression's result will become the user's username. + + +The expression must produce a non-empty string value. + +If the expression uses 'claims.email', then 'claims.email_verified' must be used in + +the expression or extra[*].valueExpression or claimValidationRules[*].expression. + +An example claim validation rule expression that matches the validation automatically + +applied when username.claim is set to 'email' is 'claims.?email_verified.orValue(true) == true'. + +By explicitly comparing the value to true, we let type-checking see the result will be a boolean, + +and to make sure a non-boolean email_verified claim will be caught at runtime. + + +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.foo.bar'. + + +Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ + + +Mutually exclusive with username. Use either username or usernameExpression to + +determine the user's username from the JWT token. + +| *`groups`* __string__ | groups is the name of the claim which should be read to extract the user's + +group membership from the JWT token. When not specified, it will default to "groups", + +unless groupsExpression is specified. + + +Mutually exclusive with groupsExpression. Use either groups or groupsExpression to + +determine the user's group membership from the JWT token. + +| *`groupsExpression`* __string__ | groupsExpression represents an expression which will be evaluated by CEL. + + +The expression's result will become the user's group memberships. + + +The expression must produce a string or string array value. + +"", [], and null values are treated as the group mapping not being present. + + +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.foo.bar'. + + +Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ + + +Mutually exclusive with groups. Use either groups or groupsExpression to + +determine the user's group membership from the JWT token. + | *`extra`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-31-apis-concierge-authentication-v1alpha1-extramapping[$$ExtraMapping$$] array__ | extra is similar to claimMappings.extra from Kubernetes AuthenticationConfiguration as documented in + https://kubernetes.io/docs/reference/access-authn-authz/authentication. However, note that the + Pinniped Concierge issues client certificates to users for the purpose of authenticating, and + diff --git a/generated/1.31/apis/concierge/authentication/v1alpha1/types_jwtauthenticator.go b/generated/1.31/apis/concierge/authentication/v1alpha1/types_jwtauthenticator.go index d5149642f..8d416bcbf 100644 --- a/generated/1.31/apis/concierge/authentication/v1alpha1/types_jwtauthenticator.go +++ b/generated/1.31/apis/concierge/authentication/v1alpha1/types_jwtauthenticator.go @@ -137,15 +137,66 @@ type UserValidationRule struct { // JWTTokenClaims allows customization of the claims that will be mapped to user identity // for Kubernetes access. type JWTTokenClaims struct { + // username is the name of the claim which should be read to extract the + // username from the JWT token. When not specified, it will default to "username", + // unless usernameExpression is specified. + // + // Mutually exclusive with usernameExpression. Use either username or usernameExpression to + // determine the user's username from the JWT token. + // +optional + Username string `json:"username"` + + // usernameExpression represents an expression which will be evaluated by CEL. + // + // The expression's result will become the user's username. + // + // The expression must produce a non-empty string value. + // If the expression uses 'claims.email', then 'claims.email_verified' must be used in + // the expression or extra[*].valueExpression or claimValidationRules[*].expression. + // An example claim validation rule expression that matches the validation automatically + // applied when username.claim is set to 'email' is 'claims.?email_verified.orValue(true) == true'. + // By explicitly comparing the value to true, we let type-checking see the result will be a boolean, + // and to make sure a non-boolean email_verified claim will be caught at runtime. + // + // 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.foo.bar'. + // + // Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ + // + // Mutually exclusive with username. Use either username or usernameExpression to + // determine the user's username from the JWT token. + // +optional + UsernameExpression string `json:"usernameExpression,omitempty"` + // groups is the name of the claim which should be read to extract the user's - // group membership from the JWT token. When not specified, it will default to "groups". + // group membership from the JWT token. When not specified, it will default to "groups", + // unless groupsExpression is specified. + // + // Mutually exclusive with groupsExpression. Use either groups or groupsExpression to + // determine the user's group membership from the JWT token. // +optional Groups string `json:"groups"` - // username is the name of the claim which should be read to extract the - // username from the JWT token. When not specified, it will default to "username". + // groupsExpression represents an expression which will be evaluated by CEL. + // + // The expression's result will become the user's group memberships. + // + // The expression must produce a string or string array value. + // "", [], and null values are treated as the group mapping not being present. + // + // 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.foo.bar'. + // + // Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ + // + // Mutually exclusive with groups. Use either groups or groupsExpression to + // determine the user's group membership from the JWT token. // +optional - Username string `json:"username"` + GroupsExpression string `json:"groupsExpression,omitempty"` // extra is similar to claimMappings.extra from Kubernetes AuthenticationConfiguration as documented in // https://kubernetes.io/docs/reference/access-authn-authz/authentication. However, note that the diff --git a/generated/1.31/crds/authentication.concierge.pinniped.dev_jwtauthenticators.yaml b/generated/1.31/crds/authentication.concierge.pinniped.dev_jwtauthenticators.yaml index cbb567616..057c8d7b8 100644 --- a/generated/1.31/crds/authentication.concierge.pinniped.dev_jwtauthenticators.yaml +++ b/generated/1.31/crds/authentication.concierge.pinniped.dev_jwtauthenticators.yaml @@ -167,12 +167,63 @@ spec: groups: description: |- groups is the name of the claim which should be read to extract the user's - group membership from the JWT token. When not specified, it will default to "groups". + group membership from the JWT token. When not specified, it will default to "groups", + unless groupsExpression is specified. + + Mutually exclusive with groupsExpression. Use either groups or groupsExpression to + determine the user's group membership from the JWT token. + type: string + groupsExpression: + description: |- + groupsExpression represents an expression which will be evaluated by CEL. + + The expression's result will become the user's group memberships. + + The expression must produce a string or string array value. + "", [], and null values are treated as the group mapping not being present. + + 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.foo.bar'. + + Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ + + Mutually exclusive with groups. Use either groups or groupsExpression to + determine the user's group membership from the JWT token. type: string username: description: |- username is the name of the claim which should be read to extract the - username from the JWT token. When not specified, it will default to "username". + username from the JWT token. When not specified, it will default to "username", + unless usernameExpression is specified. + + Mutually exclusive with usernameExpression. Use either username or usernameExpression to + determine the user's username from the JWT token. + type: string + usernameExpression: + description: |- + usernameExpression represents an expression which will be evaluated by CEL. + + The expression's result will become the user's username. + + The expression must produce a non-empty string value. + If the expression uses 'claims.email', then 'claims.email_verified' must be used in + the expression or extra[*].valueExpression or claimValidationRules[*].expression. + An example claim validation rule expression that matches the validation automatically + applied when username.claim is set to 'email' is 'claims.?email_verified.orValue(true) == true'. + By explicitly comparing the value to true, we let type-checking see the result will be a boolean, + and to make sure a non-boolean email_verified claim will be caught at runtime. + + 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.foo.bar'. + + Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ + + Mutually exclusive with username. Use either username or usernameExpression to + determine the user's username from the JWT token. type: string type: object issuer: diff --git a/generated/1.32/README.adoc b/generated/1.32/README.adoc index 3e4990deb..ab808e660 100644 --- a/generated/1.32/README.adoc +++ b/generated/1.32/README.adoc @@ -237,10 +237,55 @@ for Kubernetes access. [cols="25a,75a", options="header"] |=== | Field | Description -| *`groups`* __string__ | groups is the name of the claim which should be read to extract the user's + -group membership from the JWT token. When not specified, it will default to "groups". + | *`username`* __string__ | username is the name of the claim which should be read to extract the + -username from the JWT token. When not specified, it will default to "username". + +username from the JWT token. When not specified, it will default to "username", + +unless usernameExpression is specified. + + +Mutually exclusive with usernameExpression. Use either username or usernameExpression to + +determine the user's username from the JWT token. + +| *`usernameExpression`* __string__ | usernameExpression represents an expression which will be evaluated by CEL. + + +The expression's result will become the user's username. + + +The expression must produce a non-empty string value. + +If the expression uses 'claims.email', then 'claims.email_verified' must be used in + +the expression or extra[*].valueExpression or claimValidationRules[*].expression. + +An example claim validation rule expression that matches the validation automatically + +applied when username.claim is set to 'email' is 'claims.?email_verified.orValue(true) == true'. + +By explicitly comparing the value to true, we let type-checking see the result will be a boolean, + +and to make sure a non-boolean email_verified claim will be caught at runtime. + + +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.foo.bar'. + + +Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ + + +Mutually exclusive with username. Use either username or usernameExpression to + +determine the user's username from the JWT token. + +| *`groups`* __string__ | groups is the name of the claim which should be read to extract the user's + +group membership from the JWT token. When not specified, it will default to "groups", + +unless groupsExpression is specified. + + +Mutually exclusive with groupsExpression. Use either groups or groupsExpression to + +determine the user's group membership from the JWT token. + +| *`groupsExpression`* __string__ | groupsExpression represents an expression which will be evaluated by CEL. + + +The expression's result will become the user's group memberships. + + +The expression must produce a string or string array value. + +"", [], and null values are treated as the group mapping not being present. + + +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.foo.bar'. + + +Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ + + +Mutually exclusive with groups. Use either groups or groupsExpression to + +determine the user's group membership from the JWT token. + | *`extra`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-32-apis-concierge-authentication-v1alpha1-extramapping[$$ExtraMapping$$] array__ | extra is similar to claimMappings.extra from Kubernetes AuthenticationConfiguration as documented in + https://kubernetes.io/docs/reference/access-authn-authz/authentication. However, note that the + Pinniped Concierge issues client certificates to users for the purpose of authenticating, and + diff --git a/generated/1.32/apis/concierge/authentication/v1alpha1/types_jwtauthenticator.go b/generated/1.32/apis/concierge/authentication/v1alpha1/types_jwtauthenticator.go index d5149642f..8d416bcbf 100644 --- a/generated/1.32/apis/concierge/authentication/v1alpha1/types_jwtauthenticator.go +++ b/generated/1.32/apis/concierge/authentication/v1alpha1/types_jwtauthenticator.go @@ -137,15 +137,66 @@ type UserValidationRule struct { // JWTTokenClaims allows customization of the claims that will be mapped to user identity // for Kubernetes access. type JWTTokenClaims struct { + // username is the name of the claim which should be read to extract the + // username from the JWT token. When not specified, it will default to "username", + // unless usernameExpression is specified. + // + // Mutually exclusive with usernameExpression. Use either username or usernameExpression to + // determine the user's username from the JWT token. + // +optional + Username string `json:"username"` + + // usernameExpression represents an expression which will be evaluated by CEL. + // + // The expression's result will become the user's username. + // + // The expression must produce a non-empty string value. + // If the expression uses 'claims.email', then 'claims.email_verified' must be used in + // the expression or extra[*].valueExpression or claimValidationRules[*].expression. + // An example claim validation rule expression that matches the validation automatically + // applied when username.claim is set to 'email' is 'claims.?email_verified.orValue(true) == true'. + // By explicitly comparing the value to true, we let type-checking see the result will be a boolean, + // and to make sure a non-boolean email_verified claim will be caught at runtime. + // + // 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.foo.bar'. + // + // Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ + // + // Mutually exclusive with username. Use either username or usernameExpression to + // determine the user's username from the JWT token. + // +optional + UsernameExpression string `json:"usernameExpression,omitempty"` + // groups is the name of the claim which should be read to extract the user's - // group membership from the JWT token. When not specified, it will default to "groups". + // group membership from the JWT token. When not specified, it will default to "groups", + // unless groupsExpression is specified. + // + // Mutually exclusive with groupsExpression. Use either groups or groupsExpression to + // determine the user's group membership from the JWT token. // +optional Groups string `json:"groups"` - // username is the name of the claim which should be read to extract the - // username from the JWT token. When not specified, it will default to "username". + // groupsExpression represents an expression which will be evaluated by CEL. + // + // The expression's result will become the user's group memberships. + // + // The expression must produce a string or string array value. + // "", [], and null values are treated as the group mapping not being present. + // + // 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.foo.bar'. + // + // Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ + // + // Mutually exclusive with groups. Use either groups or groupsExpression to + // determine the user's group membership from the JWT token. // +optional - Username string `json:"username"` + GroupsExpression string `json:"groupsExpression,omitempty"` // extra is similar to claimMappings.extra from Kubernetes AuthenticationConfiguration as documented in // https://kubernetes.io/docs/reference/access-authn-authz/authentication. However, note that the diff --git a/generated/1.32/crds/authentication.concierge.pinniped.dev_jwtauthenticators.yaml b/generated/1.32/crds/authentication.concierge.pinniped.dev_jwtauthenticators.yaml index cbb567616..057c8d7b8 100644 --- a/generated/1.32/crds/authentication.concierge.pinniped.dev_jwtauthenticators.yaml +++ b/generated/1.32/crds/authentication.concierge.pinniped.dev_jwtauthenticators.yaml @@ -167,12 +167,63 @@ spec: groups: description: |- groups is the name of the claim which should be read to extract the user's - group membership from the JWT token. When not specified, it will default to "groups". + group membership from the JWT token. When not specified, it will default to "groups", + unless groupsExpression is specified. + + Mutually exclusive with groupsExpression. Use either groups or groupsExpression to + determine the user's group membership from the JWT token. + type: string + groupsExpression: + description: |- + groupsExpression represents an expression which will be evaluated by CEL. + + The expression's result will become the user's group memberships. + + The expression must produce a string or string array value. + "", [], and null values are treated as the group mapping not being present. + + 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.foo.bar'. + + Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ + + Mutually exclusive with groups. Use either groups or groupsExpression to + determine the user's group membership from the JWT token. type: string username: description: |- username is the name of the claim which should be read to extract the - username from the JWT token. When not specified, it will default to "username". + username from the JWT token. When not specified, it will default to "username", + unless usernameExpression is specified. + + Mutually exclusive with usernameExpression. Use either username or usernameExpression to + determine the user's username from the JWT token. + type: string + usernameExpression: + description: |- + usernameExpression represents an expression which will be evaluated by CEL. + + The expression's result will become the user's username. + + The expression must produce a non-empty string value. + If the expression uses 'claims.email', then 'claims.email_verified' must be used in + the expression or extra[*].valueExpression or claimValidationRules[*].expression. + An example claim validation rule expression that matches the validation automatically + applied when username.claim is set to 'email' is 'claims.?email_verified.orValue(true) == true'. + By explicitly comparing the value to true, we let type-checking see the result will be a boolean, + and to make sure a non-boolean email_verified claim will be caught at runtime. + + 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.foo.bar'. + + Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ + + Mutually exclusive with username. Use either username or usernameExpression to + determine the user's username from the JWT token. type: string type: object issuer: diff --git a/generated/1.33/README.adoc b/generated/1.33/README.adoc index 5c4577bd0..5796e1366 100644 --- a/generated/1.33/README.adoc +++ b/generated/1.33/README.adoc @@ -237,10 +237,55 @@ for Kubernetes access. [cols="25a,75a", options="header"] |=== | Field | Description -| *`groups`* __string__ | groups is the name of the claim which should be read to extract the user's + -group membership from the JWT token. When not specified, it will default to "groups". + | *`username`* __string__ | username is the name of the claim which should be read to extract the + -username from the JWT token. When not specified, it will default to "username". + +username from the JWT token. When not specified, it will default to "username", + +unless usernameExpression is specified. + + +Mutually exclusive with usernameExpression. Use either username or usernameExpression to + +determine the user's username from the JWT token. + +| *`usernameExpression`* __string__ | usernameExpression represents an expression which will be evaluated by CEL. + + +The expression's result will become the user's username. + + +The expression must produce a non-empty string value. + +If the expression uses 'claims.email', then 'claims.email_verified' must be used in + +the expression or extra[*].valueExpression or claimValidationRules[*].expression. + +An example claim validation rule expression that matches the validation automatically + +applied when username.claim is set to 'email' is 'claims.?email_verified.orValue(true) == true'. + +By explicitly comparing the value to true, we let type-checking see the result will be a boolean, + +and to make sure a non-boolean email_verified claim will be caught at runtime. + + +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.foo.bar'. + + +Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ + + +Mutually exclusive with username. Use either username or usernameExpression to + +determine the user's username from the JWT token. + +| *`groups`* __string__ | groups is the name of the claim which should be read to extract the user's + +group membership from the JWT token. When not specified, it will default to "groups", + +unless groupsExpression is specified. + + +Mutually exclusive with groupsExpression. Use either groups or groupsExpression to + +determine the user's group membership from the JWT token. + +| *`groupsExpression`* __string__ | groupsExpression represents an expression which will be evaluated by CEL. + + +The expression's result will become the user's group memberships. + + +The expression must produce a string or string array value. + +"", [], and null values are treated as the group mapping not being present. + + +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.foo.bar'. + + +Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ + + +Mutually exclusive with groups. Use either groups or groupsExpression to + +determine the user's group membership from the JWT token. + | *`extra`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-33-apis-concierge-authentication-v1alpha1-extramapping[$$ExtraMapping$$] array__ | extra is similar to claimMappings.extra from Kubernetes AuthenticationConfiguration as documented in + https://kubernetes.io/docs/reference/access-authn-authz/authentication. However, note that the + Pinniped Concierge issues client certificates to users for the purpose of authenticating, and + diff --git a/generated/1.33/apis/concierge/authentication/v1alpha1/types_jwtauthenticator.go b/generated/1.33/apis/concierge/authentication/v1alpha1/types_jwtauthenticator.go index d5149642f..8d416bcbf 100644 --- a/generated/1.33/apis/concierge/authentication/v1alpha1/types_jwtauthenticator.go +++ b/generated/1.33/apis/concierge/authentication/v1alpha1/types_jwtauthenticator.go @@ -137,15 +137,66 @@ type UserValidationRule struct { // JWTTokenClaims allows customization of the claims that will be mapped to user identity // for Kubernetes access. type JWTTokenClaims struct { + // username is the name of the claim which should be read to extract the + // username from the JWT token. When not specified, it will default to "username", + // unless usernameExpression is specified. + // + // Mutually exclusive with usernameExpression. Use either username or usernameExpression to + // determine the user's username from the JWT token. + // +optional + Username string `json:"username"` + + // usernameExpression represents an expression which will be evaluated by CEL. + // + // The expression's result will become the user's username. + // + // The expression must produce a non-empty string value. + // If the expression uses 'claims.email', then 'claims.email_verified' must be used in + // the expression or extra[*].valueExpression or claimValidationRules[*].expression. + // An example claim validation rule expression that matches the validation automatically + // applied when username.claim is set to 'email' is 'claims.?email_verified.orValue(true) == true'. + // By explicitly comparing the value to true, we let type-checking see the result will be a boolean, + // and to make sure a non-boolean email_verified claim will be caught at runtime. + // + // 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.foo.bar'. + // + // Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ + // + // Mutually exclusive with username. Use either username or usernameExpression to + // determine the user's username from the JWT token. + // +optional + UsernameExpression string `json:"usernameExpression,omitempty"` + // groups is the name of the claim which should be read to extract the user's - // group membership from the JWT token. When not specified, it will default to "groups". + // group membership from the JWT token. When not specified, it will default to "groups", + // unless groupsExpression is specified. + // + // Mutually exclusive with groupsExpression. Use either groups or groupsExpression to + // determine the user's group membership from the JWT token. // +optional Groups string `json:"groups"` - // username is the name of the claim which should be read to extract the - // username from the JWT token. When not specified, it will default to "username". + // groupsExpression represents an expression which will be evaluated by CEL. + // + // The expression's result will become the user's group memberships. + // + // The expression must produce a string or string array value. + // "", [], and null values are treated as the group mapping not being present. + // + // 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.foo.bar'. + // + // Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ + // + // Mutually exclusive with groups. Use either groups or groupsExpression to + // determine the user's group membership from the JWT token. // +optional - Username string `json:"username"` + GroupsExpression string `json:"groupsExpression,omitempty"` // extra is similar to claimMappings.extra from Kubernetes AuthenticationConfiguration as documented in // https://kubernetes.io/docs/reference/access-authn-authz/authentication. However, note that the diff --git a/generated/1.33/crds/authentication.concierge.pinniped.dev_jwtauthenticators.yaml b/generated/1.33/crds/authentication.concierge.pinniped.dev_jwtauthenticators.yaml index cbb567616..057c8d7b8 100644 --- a/generated/1.33/crds/authentication.concierge.pinniped.dev_jwtauthenticators.yaml +++ b/generated/1.33/crds/authentication.concierge.pinniped.dev_jwtauthenticators.yaml @@ -167,12 +167,63 @@ spec: groups: description: |- groups is the name of the claim which should be read to extract the user's - group membership from the JWT token. When not specified, it will default to "groups". + group membership from the JWT token. When not specified, it will default to "groups", + unless groupsExpression is specified. + + Mutually exclusive with groupsExpression. Use either groups or groupsExpression to + determine the user's group membership from the JWT token. + type: string + groupsExpression: + description: |- + groupsExpression represents an expression which will be evaluated by CEL. + + The expression's result will become the user's group memberships. + + The expression must produce a string or string array value. + "", [], and null values are treated as the group mapping not being present. + + 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.foo.bar'. + + Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ + + Mutually exclusive with groups. Use either groups or groupsExpression to + determine the user's group membership from the JWT token. type: string username: description: |- username is the name of the claim which should be read to extract the - username from the JWT token. When not specified, it will default to "username". + username from the JWT token. When not specified, it will default to "username", + unless usernameExpression is specified. + + Mutually exclusive with usernameExpression. Use either username or usernameExpression to + determine the user's username from the JWT token. + type: string + usernameExpression: + description: |- + usernameExpression represents an expression which will be evaluated by CEL. + + The expression's result will become the user's username. + + The expression must produce a non-empty string value. + If the expression uses 'claims.email', then 'claims.email_verified' must be used in + the expression or extra[*].valueExpression or claimValidationRules[*].expression. + An example claim validation rule expression that matches the validation automatically + applied when username.claim is set to 'email' is 'claims.?email_verified.orValue(true) == true'. + By explicitly comparing the value to true, we let type-checking see the result will be a boolean, + and to make sure a non-boolean email_verified claim will be caught at runtime. + + 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.foo.bar'. + + Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ + + Mutually exclusive with username. Use either username or usernameExpression to + determine the user's username from the JWT token. type: string type: object issuer: diff --git a/generated/latest/README.adoc b/generated/latest/README.adoc index 5c4577bd0..5796e1366 100644 --- a/generated/latest/README.adoc +++ b/generated/latest/README.adoc @@ -237,10 +237,55 @@ for Kubernetes access. [cols="25a,75a", options="header"] |=== | Field | Description -| *`groups`* __string__ | groups is the name of the claim which should be read to extract the user's + -group membership from the JWT token. When not specified, it will default to "groups". + | *`username`* __string__ | username is the name of the claim which should be read to extract the + -username from the JWT token. When not specified, it will default to "username". + +username from the JWT token. When not specified, it will default to "username", + +unless usernameExpression is specified. + + +Mutually exclusive with usernameExpression. Use either username or usernameExpression to + +determine the user's username from the JWT token. + +| *`usernameExpression`* __string__ | usernameExpression represents an expression which will be evaluated by CEL. + + +The expression's result will become the user's username. + + +The expression must produce a non-empty string value. + +If the expression uses 'claims.email', then 'claims.email_verified' must be used in + +the expression or extra[*].valueExpression or claimValidationRules[*].expression. + +An example claim validation rule expression that matches the validation automatically + +applied when username.claim is set to 'email' is 'claims.?email_verified.orValue(true) == true'. + +By explicitly comparing the value to true, we let type-checking see the result will be a boolean, + +and to make sure a non-boolean email_verified claim will be caught at runtime. + + +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.foo.bar'. + + +Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ + + +Mutually exclusive with username. Use either username or usernameExpression to + +determine the user's username from the JWT token. + +| *`groups`* __string__ | groups is the name of the claim which should be read to extract the user's + +group membership from the JWT token. When not specified, it will default to "groups", + +unless groupsExpression is specified. + + +Mutually exclusive with groupsExpression. Use either groups or groupsExpression to + +determine the user's group membership from the JWT token. + +| *`groupsExpression`* __string__ | groupsExpression represents an expression which will be evaluated by CEL. + + +The expression's result will become the user's group memberships. + + +The expression must produce a string or string array value. + +"", [], and null values are treated as the group mapping not being present. + + +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.foo.bar'. + + +Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ + + +Mutually exclusive with groups. Use either groups or groupsExpression to + +determine the user's group membership from the JWT token. + | *`extra`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-33-apis-concierge-authentication-v1alpha1-extramapping[$$ExtraMapping$$] array__ | extra is similar to claimMappings.extra from Kubernetes AuthenticationConfiguration as documented in + https://kubernetes.io/docs/reference/access-authn-authz/authentication. However, note that the + Pinniped Concierge issues client certificates to users for the purpose of authenticating, and + diff --git a/generated/latest/apis/concierge/authentication/v1alpha1/types_jwtauthenticator.go b/generated/latest/apis/concierge/authentication/v1alpha1/types_jwtauthenticator.go index d5149642f..8d416bcbf 100644 --- a/generated/latest/apis/concierge/authentication/v1alpha1/types_jwtauthenticator.go +++ b/generated/latest/apis/concierge/authentication/v1alpha1/types_jwtauthenticator.go @@ -137,15 +137,66 @@ type UserValidationRule struct { // JWTTokenClaims allows customization of the claims that will be mapped to user identity // for Kubernetes access. type JWTTokenClaims struct { + // username is the name of the claim which should be read to extract the + // username from the JWT token. When not specified, it will default to "username", + // unless usernameExpression is specified. + // + // Mutually exclusive with usernameExpression. Use either username or usernameExpression to + // determine the user's username from the JWT token. + // +optional + Username string `json:"username"` + + // usernameExpression represents an expression which will be evaluated by CEL. + // + // The expression's result will become the user's username. + // + // The expression must produce a non-empty string value. + // If the expression uses 'claims.email', then 'claims.email_verified' must be used in + // the expression or extra[*].valueExpression or claimValidationRules[*].expression. + // An example claim validation rule expression that matches the validation automatically + // applied when username.claim is set to 'email' is 'claims.?email_verified.orValue(true) == true'. + // By explicitly comparing the value to true, we let type-checking see the result will be a boolean, + // and to make sure a non-boolean email_verified claim will be caught at runtime. + // + // 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.foo.bar'. + // + // Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ + // + // Mutually exclusive with username. Use either username or usernameExpression to + // determine the user's username from the JWT token. + // +optional + UsernameExpression string `json:"usernameExpression,omitempty"` + // groups is the name of the claim which should be read to extract the user's - // group membership from the JWT token. When not specified, it will default to "groups". + // group membership from the JWT token. When not specified, it will default to "groups", + // unless groupsExpression is specified. + // + // Mutually exclusive with groupsExpression. Use either groups or groupsExpression to + // determine the user's group membership from the JWT token. // +optional Groups string `json:"groups"` - // username is the name of the claim which should be read to extract the - // username from the JWT token. When not specified, it will default to "username". + // groupsExpression represents an expression which will be evaluated by CEL. + // + // The expression's result will become the user's group memberships. + // + // The expression must produce a string or string array value. + // "", [], and null values are treated as the group mapping not being present. + // + // 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.foo.bar'. + // + // Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ + // + // Mutually exclusive with groups. Use either groups or groupsExpression to + // determine the user's group membership from the JWT token. // +optional - Username string `json:"username"` + GroupsExpression string `json:"groupsExpression,omitempty"` // extra is similar to claimMappings.extra from Kubernetes AuthenticationConfiguration as documented in // https://kubernetes.io/docs/reference/access-authn-authz/authentication. However, note that the diff --git a/internal/controller/authenticator/jwtcachefiller/convert_jwtauthenticator_type.go b/internal/controller/authenticator/jwtcachefiller/convert_jwtauthenticator_type.go index 9650487cb..5c9fb4ff4 100644 --- a/internal/controller/authenticator/jwtcachefiller/convert_jwtauthenticator_type.go +++ b/internal/controller/authenticator/jwtcachefiller/convert_jwtauthenticator_type.go @@ -15,45 +15,65 @@ import ( // but is otherwise a straight conversion. The Pinniped type includes TLS configuration which does not need // to be converted because that is applied elsewhere. func convertJWTAuthenticatorSpecType(spec *authenticationv1alpha1.JWTAuthenticatorSpec) apiserver.JWTAuthenticator { - usernameClaim := spec.Claims.Username - if usernameClaim == "" { - usernameClaim = defaultUsernameClaim - } - - groupsClaim := spec.Claims.Groups - if groupsClaim == "" { - groupsClaim = defaultGroupsClaim + return apiserver.JWTAuthenticator{ + Issuer: convertIssuerType(spec), + ClaimMappings: convertClaimMappingsType(spec.Claims), + ClaimValidationRules: convertClaimValidationRulesType(spec.ClaimValidationRules), + UserValidationRules: convertUserValidationRulesType(spec.UserValidationRules), } +} +func convertIssuerType(spec *authenticationv1alpha1.JWTAuthenticatorSpec) apiserver.Issuer { var aud []string if len(spec.Audience) > 0 { aud = []string{spec.Audience} } - jwtAuthenticator := apiserver.JWTAuthenticator{ - Issuer: apiserver.Issuer{ - URL: spec.Issuer, - Audiences: aud, - }, - ClaimMappings: apiserver.ClaimMappings{ - Username: apiserver.PrefixedClaimOrExpression{ - Claim: usernameClaim, - Prefix: ptr.To(""), - }, - Groups: apiserver.PrefixedClaimOrExpression{ - Claim: groupsClaim, - Prefix: ptr.To(""), - }, - Extra: convertExtraType(spec.Claims.Extra), - }, - ClaimValidationRules: convertClaimValidationRulesType(spec.ClaimValidationRules), - UserValidationRules: convertUserValidationRules(spec.UserValidationRules), + return apiserver.Issuer{ + URL: spec.Issuer, + Audiences: aud, } - - return jwtAuthenticator } -func convertUserValidationRules(rules []authenticationv1alpha1.UserValidationRule) []apiserver.UserValidationRule { +func convertClaimMappingsType(claims authenticationv1alpha1.JWTTokenClaims) apiserver.ClaimMappings { + usernameClaim := claims.Username + if usernameClaim == "" && claims.UsernameExpression == "" { + usernameClaim = defaultUsernameClaim + } + + var usernamePrefix *string + if usernameClaim != "" { + // Must be set only when username claim name is set. + usernamePrefix = ptr.To("") + } + + groupsClaim := claims.Groups + if groupsClaim == "" && claims.GroupsExpression == "" { + groupsClaim = defaultGroupsClaim + } + + var groupsPrefix *string + if groupsClaim != "" { + // Must be set only when groups claim name is set. + groupsPrefix = ptr.To("") + } + + return apiserver.ClaimMappings{ + Username: apiserver.PrefixedClaimOrExpression{ + Claim: usernameClaim, + Prefix: usernamePrefix, + Expression: claims.UsernameExpression, + }, + Groups: apiserver.PrefixedClaimOrExpression{ + Claim: groupsClaim, + Prefix: groupsPrefix, + Expression: claims.GroupsExpression, + }, + Extra: convertExtraType(claims.Extra), + } +} + +func convertUserValidationRulesType(rules []authenticationv1alpha1.UserValidationRule) []apiserver.UserValidationRule { if len(rules) == 0 { return nil } diff --git a/internal/controller/authenticator/jwtcachefiller/convert_jwtauthenticator_type_test.go b/internal/controller/authenticator/jwtcachefiller/convert_jwtauthenticator_type_test.go index fc92334a8..bbbbadb5c 100644 --- a/internal/controller/authenticator/jwtcachefiller/convert_jwtauthenticator_type_test.go +++ b/internal/controller/authenticator/jwtcachefiller/convert_jwtauthenticator_type_test.go @@ -21,7 +21,7 @@ func Test_convertJWTAuthenticatorSpecType(t *testing.T) { want apiserver.JWTAuthenticator }{ { - name: "defaults the username and groups claims", + name: "defaults the username and groups claims when the usernameExpression and groupExpression are not set", spec: &authenticationv1alpha1.JWTAuthenticatorSpec{ Issuer: "https://example.com", }, @@ -41,6 +41,33 @@ func Test_convertJWTAuthenticatorSpecType(t *testing.T) { }, }, }, + { + name: "does not default the username and groups claims an prefixes when the usernameExpression and groupExpression are set", + spec: &authenticationv1alpha1.JWTAuthenticatorSpec{ + Issuer: "https://example.com", + Claims: authenticationv1alpha1.JWTTokenClaims{ + UsernameExpression: `"foo"`, + GroupsExpression: `["foo"]`, + }, + }, + want: apiserver.JWTAuthenticator{ + Issuer: apiserver.Issuer{ + URL: "https://example.com", + }, + ClaimMappings: apiserver.ClaimMappings{ + Username: apiserver.PrefixedClaimOrExpression{ + Claim: "", + Prefix: nil, + Expression: `"foo"`, + }, + Groups: apiserver.PrefixedClaimOrExpression{ + Claim: "", + Prefix: nil, + Expression: `["foo"]`, + }, + }, + }, + }, { name: "converts every field except for TLS", spec: &authenticationv1alpha1.JWTAuthenticatorSpec{ diff --git a/internal/controller/authenticator/jwtcachefiller/jwtcachefiller_test.go b/internal/controller/authenticator/jwtcachefiller/jwtcachefiller_test.go index a31f1a59d..4ea127c3d 100644 --- a/internal/controller/authenticator/jwtcachefiller/jwtcachefiller_test.go +++ b/internal/controller/authenticator/jwtcachefiller/jwtcachefiller_test.go @@ -360,13 +360,13 @@ func TestController(t *testing.T) { Groups: customGroupsClaim, }, } - someJWTAuthenticatorSpecWithEveryOptionalValue := &authenticationv1alpha1.JWTAuthenticatorSpec{ + someJWTAuthenticatorSpecWithManyOptionalValues := &authenticationv1alpha1.JWTAuthenticatorSpec{ Issuer: goodIssuer, Audience: goodAudience, TLS: goodOIDCIssuerServerTLSSpec, Claims: authenticationv1alpha1.JWTTokenClaims{ - Username: "my-custom-username-claim", - Groups: customGroupsClaim, + Username: "my-custom-username-claim", // note: can't specify this and usernameExpression at the same time + Groups: customGroupsClaim, // note: can't specify this and groupsExpression at the same time Extra: []authenticationv1alpha1.ExtraMapping{ { Key: "example.com/key-name", // must be a domain and path @@ -387,6 +387,15 @@ func TestController(t *testing.T) { }, }, } + someJWTAuthenticatorSpecWithUsernameAndGroupExpressions := &authenticationv1alpha1.JWTAuthenticatorSpec{ + Issuer: goodIssuer, + Audience: goodAudience, + TLS: goodOIDCIssuerServerTLSSpec, + Claims: authenticationv1alpha1.JWTTokenClaims{ + UsernameExpression: "claims.otherUsernameClaim", + GroupsExpression: "has(claims.otherGroupsClaim) ? claims.otherGroupsClaim : []", // handles the case where the claim does not exist in the JWT + }, + } invalidClaimsExtraJWTAuthenticatorSpec := &authenticationv1alpha1.JWTAuthenticatorSpec{ Issuer: goodIssuer, Audience: goodAudience, @@ -420,6 +429,17 @@ func TestController(t *testing.T) { }, }, } + invalidClaimsMutualExclusiveRulesBothSetJWTAuthenticatorSpec := &authenticationv1alpha1.JWTAuthenticatorSpec{ + Issuer: goodIssuer, + Audience: goodAudience, + TLS: goodOIDCIssuerServerTLSSpec, + Claims: authenticationv1alpha1.JWTTokenClaims{ + Username: "user", + UsernameExpression: `"user"`, + Groups: "groups", + GroupsExpression: `["group1"]`, + }, + } invalidClaimsExtraContainsEqualSignJWTAuthenticatorSpec := &authenticationv1alpha1.JWTAuthenticatorSpec{ Issuer: goodIssuer, Audience: goodAudience, @@ -1193,13 +1213,13 @@ func TestController(t *testing.T) { wantNamesOfJWTAuthenticatorsInCache: []string{"test-name"}, }, { - name: "Sync: JWTAuthenticator with every optional value: loop will complete successfully and update status conditions", + name: "Sync: JWTAuthenticator with many optional values: loop will complete successfully and update status conditions", jwtAuthenticators: []runtime.Object{ &authenticationv1alpha1.JWTAuthenticator{ ObjectMeta: metav1.ObjectMeta{ Name: "test-name", }, - Spec: *someJWTAuthenticatorSpecWithEveryOptionalValue, + Spec: *someJWTAuthenticatorSpecWithManyOptionalValues, }, }, wantLogLines: []string{ @@ -1211,7 +1231,7 @@ func TestController(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ Name: "test-name", }, - Spec: *someJWTAuthenticatorSpecWithEveryOptionalValue, + Spec: *someJWTAuthenticatorSpecWithManyOptionalValues, Status: authenticationv1alpha1.JWTAuthenticatorStatus{ Conditions: allHappyConditionsSuccess(goodIssuer, frozenMetav1Now, 0), Phase: "Ready", @@ -1229,6 +1249,42 @@ func TestController(t *testing.T) { wantExtras: map[string][]string{"example.com/key-name": {"extra-value"}}, wantNamesOfJWTAuthenticatorsInCache: []string{"test-name"}, }, + { + name: "Sync: JWTAuthenticator with usernameExpression and groupsExpression values: loop will complete successfully and update status conditions", + jwtAuthenticators: []runtime.Object{ + &authenticationv1alpha1.JWTAuthenticator{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-name", + }, + Spec: *someJWTAuthenticatorSpecWithUsernameAndGroupExpressions, + }, + }, + wantLogLines: []string{ + fmt.Sprintf(`{"level":"debug","timestamp":"2099-08-08T13:57:36.123456Z","logger":"jwtcachefiller-controller","caller":"jwtcachefiller/jwtcachefiller.go:$jwtcachefiller.(*jwtCacheFillerController).updateStatus","message":"jwtauthenticator status successfully updated","jwtAuthenticator":"test-name","issuer":"%s","phase":"Ready"}`, goodIssuer), + fmt.Sprintf(`{"level":"info","timestamp":"2099-08-08T13:57:36.123456Z","logger":"jwtcachefiller-controller","caller":"jwtcachefiller/jwtcachefiller.go:$jwtcachefiller.(*jwtCacheFillerController).syncIndividualJWTAuthenticator","message":"added or updated jwt authenticator in cache","jwtAuthenticator":"test-name","issuer":"%s","isOverwrite":false}`, goodIssuer), + }, + wantActions: func() []coretesting.Action { + updateStatusAction := coretesting.NewUpdateAction(jwtAuthenticatorsGVR, "", &authenticationv1alpha1.JWTAuthenticator{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-name", + }, + Spec: *someJWTAuthenticatorSpecWithUsernameAndGroupExpressions, + Status: authenticationv1alpha1.JWTAuthenticatorStatus{ + Conditions: allHappyConditionsSuccess(goodIssuer, frozenMetav1Now, 0), + Phase: "Ready", + }, + }) + updateStatusAction.Subresource = "status" + return []coretesting.Action{ + coretesting.NewListAction(jwtAuthenticatorsGVR, jwtAUthenticatorGVK, "", metav1.ListOptions{}), + coretesting.NewWatchAction(jwtAuthenticatorsGVR, "", metav1.ListOptions{Watch: true}), + updateStatusAction, + } + }, + wantUsernameClaim: "otherUsernameClaim", + wantGroupsClaim: "otherGroupsClaim", + wantNamesOfJWTAuthenticatorsInCache: []string{"test-name"}, + }, { name: "Sync: JWTAuthenticator with new spec.tls fields: loop will close previous instance of JWTAuthenticator and complete successfully and update status conditions", cache: func(t *testing.T, cache *authncache.Cache, wantClose bool) { @@ -2652,6 +2708,53 @@ func TestController(t *testing.T) { } }, }, + { + name: "newCachedJWTAuthenticator: validateAuthenticationConfiguration: when username and usernameExpression and/or groups and groupsExpression are both set: loop will fail sync, will write failed and unknown status conditions, but will not enqueue a resync due to user config error", + jwtAuthenticators: []runtime.Object{ + &authenticationv1alpha1.JWTAuthenticator{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-name", + }, + Spec: *invalidClaimsMutualExclusiveRulesBothSetJWTAuthenticatorSpec, + }, + }, + wantLogLines: []string{ + fmt.Sprintf(`{"level":"info","timestamp":"2099-08-08T13:57:36.123456Z","logger":"jwtcachefiller-controller","caller":"jwtcachefiller/jwtcachefiller.go:$jwtcachefiller.(*jwtCacheFillerController).syncIndividualJWTAuthenticator","message":"invalid jwt authenticator","jwtAuthenticator":"test-name","issuer":"%s","removedFromCache":false}`, invalidClaimsMutualExclusiveRulesBothSetJWTAuthenticatorSpec.Issuer), + fmt.Sprintf(`{"level":"debug","timestamp":"2099-08-08T13:57:36.123456Z","logger":"jwtcachefiller-controller","caller":"jwtcachefiller/jwtcachefiller.go:$jwtcachefiller.(*jwtCacheFillerController).updateStatus","message":"jwtauthenticator status successfully updated","jwtAuthenticator":"test-name","issuer":"%s","phase":"Error"}`, invalidClaimsMutualExclusiveRulesBothSetJWTAuthenticatorSpec.Issuer), + }, + wantActions: func() []coretesting.Action { + updateStatusAction := coretesting.NewUpdateAction(jwtAuthenticatorsGVR, "", &authenticationv1alpha1.JWTAuthenticator{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-name", + }, + Spec: *invalidClaimsMutualExclusiveRulesBothSetJWTAuthenticatorSpec, + Status: authenticationv1alpha1.JWTAuthenticatorStatus{ + Conditions: conditionstestutil.Replace( + allHappyConditionsSuccess(goodIssuer, frozenMetav1Now, 0), + []metav1.Condition{ + sadReadyCondition(frozenMetav1Now, 0), + happyIssuerURLValid(frozenMetav1Now, 0), + happyDiscoveryURLValid(frozenMetav1Now, 0), + sadAuthenticatorValid( + `could not initialize jwt authenticator: [claims.username: Invalid value: "": claim and expression can't both be set, claims.groups: Invalid value: "": claim and expression can't both be set]`, + frozenMetav1Now, + 0, + ), + happyJWKSURLValid(frozenMetav1Now, 0), + happyJWKSFetch(frozenMetav1Now, 0), + }, + ), + Phase: "Error", + }, + }) + updateStatusAction.Subresource = "status" + return []coretesting.Action{ + coretesting.NewListAction(jwtAuthenticatorsGVR, jwtAUthenticatorGVK, "", metav1.ListOptions{}), + coretesting.NewWatchAction(jwtAuthenticatorsGVR, "", metav1.ListOptions{Watch: true}), + updateStatusAction, + } + }, + }, { name: "newCachedJWTAuthenticator: when any claims.extra[].key contains an equals sign: loop will fail sync, will write failed and unknown status conditions, but will not enqueue a resync due to user config error", jwtAuthenticators: []runtime.Object{ @@ -2903,6 +3006,15 @@ func TestController(t *testing.T) { cachedAuthenticator, ok := temp.(tokenAuthenticatorCloser) require.True(t, ok) + usernameClaimIsCelExpression := false + if temp.(*cachedJWTAuthenticator).claims.UsernameExpression != "" { + usernameClaimIsCelExpression = true + } + groupsClaimIsCelExpression := false + if temp.(*cachedJWTAuthenticator).claims.GroupsExpression != "" { + groupsClaimIsCelExpression = true + } + // Schedule it to be closed at the end of the test. t.Cleanup(cachedAuthenticator.Close) @@ -2930,13 +3042,19 @@ func TestController(t *testing.T) { group1, goodUsername, tt.wantUsernameClaim, + usernameClaimIsCelExpression, tt.wantGroupsClaim, + groupsClaimIsCelExpression, tt.wantExtras, goodIssuer, ) { t.Run(test.name, func(t *testing.T) { t.Parallel() + if test.skip != nil { + test.skip(t) // give the test a chance to skip itself if it wants to + } + wellKnownClaims := josejwt.Claims{ Issuer: goodIssuer, Subject: goodSubject, @@ -3012,7 +3130,9 @@ func testTableForAuthenticateTokenTests( group1 string, goodUsername string, expectedUsernameClaim string, + usernameClaimIsCelExpression bool, expectedGroupsClaim string, + groupsClaimIsCelExpression bool, expectedExtras map[string][]string, issuer string, ) []struct { @@ -3023,7 +3143,22 @@ func testTableForAuthenticateTokenTests( wantAuthenticated bool wantErr testutil.RequireErrorStringFunc distributedGroupsClaimURL string + skip func(t *testing.T) } { + expectedErrForBadTokenWithGroupsAsMap := func() testutil.RequireErrorStringFunc { + if groupsClaimIsCelExpression { + return testutil.WantExactErrorString(`oidc: error evaluating group claim expression: expression must return a string or a list of strings`) + } + return testutil.WantExactErrorString(`oidc: parse groups claim "` + expectedGroupsClaim + `": json: cannot unmarshal object into Go value of type string`) + } + + expectedErrForTokenDoesNotHaveUsernameClaim := func() testutil.RequireErrorStringFunc { + if usernameClaimIsCelExpression { + return testutil.WantMatchingErrorString(`oidc: error evaluating username claim expression: expression '.+' resulted in error: no such key: ` + expectedUsernameClaim) + } + return testutil.WantExactErrorString(`oidc: parse username claims "` + expectedUsernameClaim + `": claim not present`) + } + tests := []struct { name string jwtClaims func(wellKnownClaims *josejwt.Claims, groups *any, username *string) @@ -3032,6 +3167,7 @@ func testTableForAuthenticateTokenTests( wantAuthenticated bool wantErr testutil.RequireErrorStringFunc distributedGroupsClaimURL string + skip func(t *testing.T) }{ { name: "good token without groups and with EC signature", @@ -3085,13 +3221,23 @@ func testTableForAuthenticateTokenTests( }, }, wantAuthenticated: true, + skip: func(t *testing.T) { + if groupsClaimIsCelExpression { + t.Skip("skipping test because Kubernetes does not support using a CEL expression for groups mapping with distributed claims") + } + }, }, { name: "distributed groups returns a 404", jwtClaims: func(claims *josejwt.Claims, groups *any, username *string) { }, distributedGroupsClaimURL: issuer + "/not_found_claim_source", - wantErr: testutil.WantMatchingErrorString(`oidc: could not expand distributed claims: while getting distributed claim "` + expectedGroupsClaim + `": error while getting distributed claim JWT: 404 Not Found`), + wantErr: testutil.WantExactErrorString(`oidc: could not expand distributed claims: while getting distributed claim "` + expectedGroupsClaim + `": error while getting distributed claim JWT: 404 Not Found`), + skip: func(t *testing.T) { + if groupsClaimIsCelExpression { + t.Skip("skipping test because Kubernetes does not support using a CEL expression for groups mapping with distributed claims") + } + }, }, { name: "distributed groups doesn't return the right claim", @@ -3099,6 +3245,11 @@ func testTableForAuthenticateTokenTests( }, distributedGroupsClaimURL: issuer + "/wrong_claim_source", wantErr: testutil.WantMatchingErrorString(`oidc: could not expand distributed claims: jwt returned by distributed claim endpoint "` + issuer + `/wrong_claim_source" did not contain claim: `), + skip: func(t *testing.T) { + if groupsClaimIsCelExpression { + t.Skip("skipping test because Kubernetes does not support using a CEL expression for groups mapping with distributed claims") + } + }, }, { name: "good token with groups as string", @@ -3132,7 +3283,7 @@ func testTableForAuthenticateTokenTests( jwtClaims: func(_ *josejwt.Claims, groups *any, username *string) { *groups = map[string]string{"not an array": "or a string"} }, - wantErr: testutil.WantMatchingErrorString(`oidc: parse groups claim "` + expectedGroupsClaim + `": json: cannot unmarshal object into Go value of type string`), + wantErr: expectedErrForBadTokenWithGroupsAsMap(), }, { name: "bad token with wrong issuer", @@ -3147,14 +3298,14 @@ func testTableForAuthenticateTokenTests( jwtClaims: func(claims *josejwt.Claims, _ *any, username *string) { claims.Audience = nil }, - wantErr: testutil.WantMatchingErrorString(`oidc: verify token: oidc: expected audience "some-audience" got \[\]`), + wantErr: testutil.WantExactErrorString(`oidc: verify token: oidc: expected audience "some-audience" got []`), }, { name: "bad token with wrong audience", jwtClaims: func(claims *josejwt.Claims, _ *any, username *string) { claims.Audience = []string{"wrong-audience"} }, - wantErr: testutil.WantMatchingErrorString(`oidc: verify token: oidc: expected audience "some-audience" got \["wrong-audience"\]`), + wantErr: testutil.WantExactErrorString(`oidc: verify token: oidc: expected audience "some-audience" got ["wrong-audience"]`), }, { name: "bad token with nbf in the future", @@ -3182,7 +3333,7 @@ func testTableForAuthenticateTokenTests( jwtClaims: func(claims *josejwt.Claims, _ *any, username *string) { *username = "" }, - wantErr: testutil.WantMatchingErrorString(`oidc: parse username claims "` + expectedUsernameClaim + `": claim not present`), + wantErr: expectedErrForTokenDoesNotHaveUsernameClaim(), }, { name: "signing key is wrong", @@ -3192,7 +3343,7 @@ func testTableForAuthenticateTokenTests( require.NoError(t, err) *algo = jose.ES256 }, - wantErr: testutil.WantMatchingErrorString(`oidc: verify token: failed to verify signature: failed to verify id token signature`), + wantErr: testutil.WantExactErrorString(`oidc: verify token: failed to verify signature: failed to verify id token signature`), }, { name: "signing algo is unsupported", @@ -3202,7 +3353,7 @@ func testTableForAuthenticateTokenTests( require.NoError(t, err) *algo = jose.ES384 }, - wantErr: testutil.WantMatchingErrorString(`oidc: verify token: oidc: id token signed with unsupported algorithm, expected \["RS256" "ES256"\] got "ES384"`), + wantErr: testutil.WantExactErrorString(`oidc: verify token: oidc: id token signed with unsupported algorithm, expected ["RS256" "ES256"] got "ES384"`), }, } From 52622d5e4c5d3054588822bb35a54a96204f9259 Mon Sep 17 00:00:00 2001 From: Ryan Richard Date: Thu, 17 Jul 2025 10:52:29 -0700 Subject: [PATCH 3/7] fix pre-existing integration tests for new JWTAuthenticator features --- test/integration/audit_test.go | 21 +++++++++++++++++++++ test/integration/kube_api_discovery_test.go | 5 +++-- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/test/integration/audit_test.go b/test/integration/audit_test.go index 9e9a0bfe0..adead2aad 100644 --- a/test/integration/audit_test.go +++ b/test/integration/audit_test.go @@ -209,6 +209,7 @@ func TestAuditLogsDuringLogin_Disruptive(t *testing.T) { "personalInfo": map[string]any{ "username": "redacted", "groups": []any{"redacted 2 values"}, + "extras": map[string]any{"redacted": "redacted 1 keys"}, }, }, }, allConciergeTCRLogs) @@ -342,6 +343,25 @@ func TestAuditLogsDuringLogin_Disruptive(t *testing.T) { for _, log := range allConciergeTCRLogs { require.NotEmpty(t, log["issuedClientCert"]) delete(log, "issuedClientCert") + + // The value at the extras key "authentication.kubernetes.io/credential-id" will be a JWT ID, + // which is hard to predict, so just assert that it is there without worrying about its exact value. + require.Contains(t, log, "personalInfo") + personalInfo, ok := log["personalInfo"].(map[string]any) + require.True(t, ok) + require.NotNil(t, personalInfo["extras"]) + extras, ok := personalInfo["extras"].(map[string]any) + require.True(t, ok) + require.Contains(t, extras, "authentication.kubernetes.io/credential-id") + require.Len(t, extras, 1) // should be the only key + id := extras["authentication.kubernetes.io/credential-id"] + idValues, ok := id.([]any) + require.True(t, ok) + require.Len(t, idValues, 1) + require.Regexp(t, "JTI=.+", idValues[0]) + // Now that we have made assertions about all the expected extras, + // delete it so we can compare the rest using equals below. + delete(personalInfo, "extras") } // All values in the personalInfo map should not be redacted anymore. @@ -357,6 +377,7 @@ func TestAuditLogsDuringLogin_Disruptive(t *testing.T) { "personalInfo": map[string]any{ "username": expectedUsername, "groups": expectedGroups, + // note: also has an "extras" key, which we deleted from the actual value above }, }, }, allConciergeTCRLogs) diff --git a/test/integration/kube_api_discovery_test.go b/test/integration/kube_api_discovery_test.go index ded484662..260da7711 100644 --- a/test/integration/kube_api_discovery_test.go +++ b/test/integration/kube_api_discovery_test.go @@ -451,8 +451,9 @@ func TestGetAPIResourceList(t *testing.T) { //nolint:gocyclo // each t.Run is pr } } - // manually update this value whenever you add additional fields to an API resource and then run the generator - totalExpectedAPIFields := 310 + // Manually update this value whenever you add additional fields to an API resource and then run the generator. + // This is to ensure that this test checked every field in our whole API surface area. + totalExpectedAPIFields := 323 // Because we are parsing text from `kubectl explain` and because the format of that text can change // over time, make a rudimentary assertion that this test exercised the whole tree of all fields of all From cc4a148c70f5fac5dc7027d4e9dd1e47bb822be2 Mon Sep 17 00:00:00 2001 From: Ryan Richard Date: Fri, 18 Jul 2025 10:17:25 -0700 Subject: [PATCH 4/7] add new login integration tests for new JWTAuthenticator features --- hack/prepare-for-integration-tests.sh | 5 +- test/integration/cli_test.go | 4 +- .../concierge_credentialrequest_test.go | 474 +++++++++++++----- test/testlib/client.go | 29 +- test/testlib/env.go | 15 +- 5 files changed, 375 insertions(+), 152 deletions(-) diff --git a/hack/prepare-for-integration-tests.sh b/hack/prepare-for-integration-tests.sh index 8dc3f2a28..35c15dc93 100755 --- a/hack/prepare-for-integration-tests.sh +++ b/hack/prepare-for-integration-tests.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -# Copyright 2020-2024 the Pinniped contributors. All Rights Reserved. +# Copyright 2020-2025 the Pinniped contributors. All Rights Reserved. # SPDX-License-Identifier: Apache-2.0 # @@ -535,6 +535,9 @@ export PINNIPED_TEST_CLI_OIDC_CLIENT_ID=pinniped-cli export PINNIPED_TEST_CLI_OIDC_CALLBACK_URL=http://127.0.0.1:48095/callback export PINNIPED_TEST_CLI_OIDC_USERNAME=pinny@example.com export PINNIPED_TEST_CLI_OIDC_PASSWORD=${dex_test_password} +export PINNIPED_TEST_CLI_OIDC_USERNAME_CLAIM=email +export PINNIPED_TEST_CLI_OIDC_GROUPS_CLAIM=groups +export PINNIPED_TEST_CLI_OIDC_EXPECTED_GROUPS= # Dex's local user store does not let us configure groups. export PINNIPED_TEST_SUPERVISOR_UPSTREAM_OIDC_ISSUER=https://dex.tools.svc.cluster.local/dex export PINNIPED_TEST_SUPERVISOR_UPSTREAM_OIDC_ISSUER_CA_BUNDLE="${test_ca_bundle_pem}" export PINNIPED_TEST_SUPERVISOR_UPSTREAM_OIDC_ADDITIONAL_SCOPES="offline_access,email" diff --git a/test/integration/cli_test.go b/test/integration/cli_test.go index 45439c30f..3b5e178e7 100644 --- a/test/integration/cli_test.go +++ b/test/integration/cli_test.go @@ -222,7 +222,7 @@ func TestCLILoginOIDC_Browser(t *testing.T) { cacheKey := oidcclient.SessionCacheKey{ Issuer: env.CLIUpstreamOIDC.Issuer, ClientID: env.CLIUpstreamOIDC.ClientID, - Scopes: []string{"email", "offline_access", "openid", "profile"}, + Scopes: []string{"email", "groups", "offline_access", "openid", "profile"}, // in alphabetical order RedirectURI: strings.ReplaceAll(env.CLIUpstreamOIDC.CallbackURL, "127.0.0.1", "localhost"), } cached := cache.GetToken(cacheKey) @@ -413,7 +413,7 @@ func oidcLoginCommand(ctx context.Context, t *testing.T, pinnipedExe string, ses cmd := exec.CommandContext(ctx, pinnipedExe, "login", "oidc", "--issuer", env.CLIUpstreamOIDC.Issuer, "--client-id", env.CLIUpstreamOIDC.ClientID, - "--scopes", "offline_access,openid,email,profile", + "--scopes", "offline_access,openid,email,profile,groups", "--listen-port", callbackURL.Port(), "--session-cache", sessionCachePath, "--credential-cache", t.TempDir()+"/credentials.yaml", diff --git a/test/integration/concierge_credentialrequest_test.go b/test/integration/concierge_credentialrequest_test.go index 01f5dd98c..91a3a90b9 100644 --- a/test/integration/concierge_credentialrequest_test.go +++ b/test/integration/concierge_credentialrequest_test.go @@ -1,4 +1,4 @@ -// Copyright 2020-2024 the Pinniped contributors. All Rights Reserved. +// Copyright 2020-2025 the Pinniped contributors. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 package integration @@ -6,7 +6,9 @@ package integration import ( "context" "crypto/x509" + "encoding/base64" "encoding/pem" + "fmt" "testing" "time" @@ -23,12 +25,306 @@ import ( "go.pinniped.dev/test/testlib" ) +// TestCredentialRequest_Browser cannot run in parallel because runPinnipedLoginOIDC uses a fixed port +// for its localhost listener via --listen-port=env.CLIUpstreamOIDC.CallbackURL.Port() per oidcLoginCommand. +// Since ports are global to the process, tests using oidcLoginCommand must be run serially. +func TestCredentialRequest_Browser(t *testing.T) { + env := testlib.IntegrationEnv(t).WithCapability(testlib.ClusterSigningKeyIsAvailable) + + ctx, cancel := context.WithTimeout(context.Background(), 6*time.Minute) + t.Cleanup(cancel) + + jwtAuthenticatorTypedLocalObjectReference := func(a *authenticationv1alpha1.JWTAuthenticator) corev1.TypedLocalObjectReference { + return corev1.TypedLocalObjectReference{ + APIGroup: &authenticationv1alpha1.SchemeGroupVersion.Group, + Kind: "JWTAuthenticator", + Name: a.Name, + } + } + + expectedExtras := func(t *testing.T, jwt string) []string { + // Dex tokens do not include a jti claim, so check if it exists. + claims := getJWTClaims(t, jwt) + _, ok := claims["jti"] + if !ok { + return []string{} + } + + // Okta tokens contain a jti, so use it to make the expected value. + jti := getJWTClaimAsString(t, jwt, "jti") + require.NotEmpty(t, jti) + return []string{ + // The Kubernetes jwtAuthenticator will automatically add this extra when there is a jti claim. + fmt.Sprintf("authentication.kubernetes.io/credential-id=JTI=%s", jti), + } + } + + tests := []struct { + name string + authenticator func(context.Context, *testing.T) corev1.TypedLocalObjectReference + token func(t *testing.T) (tokenToSubmit string, wantUsername string, wantGroups []string, wantExtras []string) + }{ + { + name: "webhook", + authenticator: func(ctx context.Context, t *testing.T) corev1.TypedLocalObjectReference { + return testlib.CreateTestWebhookAuthenticator(ctx, t, &env.TestWebhook, authenticationv1alpha1.WebhookAuthenticatorPhaseReady) + }, + token: func(t *testing.T) (string, string, []string, []string) { + return env.TestUser.Token, env.TestUser.ExpectedUsername, env.TestUser.ExpectedGroups, []string{} + }, + }, + { + name: "minimal jwt authenticator", + authenticator: func(ctx context.Context, t *testing.T) corev1.TypedLocalObjectReference { + authenticator := testlib.CreateTestJWTAuthenticator(ctx, t, authenticationv1alpha1.JWTAuthenticatorSpec{ + Issuer: env.CLIUpstreamOIDC.Issuer, + Audience: env.CLIUpstreamOIDC.ClientID, + Claims: authenticationv1alpha1.JWTTokenClaims{ + Username: env.CLIUpstreamOIDC.UsernameClaim, + Groups: env.CLIUpstreamOIDC.GroupsClaim, + }, + TLS: tlsSpecForCLIUpstreamOIDC(t), + }, authenticationv1alpha1.JWTAuthenticatorPhaseReady) + + return jwtAuthenticatorTypedLocalObjectReference(authenticator) + }, + token: func(t *testing.T) (string, string, []string, []string) { + pinnipedExe := testlib.PinnipedCLIPath(t) + credOutput, _ := runPinnipedLoginOIDC(ctx, t, pinnipedExe) + token := credOutput.Status.Token + + // Sanity check that the JWT contains the expected username claim. + username := getJWTClaimAsString(t, token, env.CLIUpstreamOIDC.UsernameClaim) + require.Equal(t, env.CLIUpstreamOIDC.Username, username) + + // Sanity check that the JWT contains the expected groups claim. + // Dex doesn't return groups, so only check where we are expecting groups. + if len(env.CLIUpstreamOIDC.ExpectedGroups) > 0 { + groups := getJWTClaimAsStringSlice(t, token, env.CLIUpstreamOIDC.GroupsClaim) + t.Logf("found groups in JWT token: %#v", groups) + require.ElementsMatch(t, groups, env.CLIUpstreamOIDC.ExpectedGroups) + } + + return token, env.CLIUpstreamOIDC.Username, env.CLIUpstreamOIDC.ExpectedGroups, expectedExtras(t, token) + }, + }, + { + name: "jwt authenticator with username and groups CEL expressions and additional extras and validation rules which allow auth", + authenticator: func(ctx context.Context, t *testing.T) corev1.TypedLocalObjectReference { + authenticator := testlib.CreateTestJWTAuthenticator(ctx, t, authenticationv1alpha1.JWTAuthenticatorSpec{ + Issuer: env.CLIUpstreamOIDC.Issuer, + Audience: env.CLIUpstreamOIDC.ClientID, + Claims: authenticationv1alpha1.JWTTokenClaims{ + UsernameExpression: "claims.sub", + GroupsExpression: `["group1", "group2"]`, + Extra: []authenticationv1alpha1.ExtraMapping{ + { + Key: "example.com/sub", + ValueExpression: "claims.sub", + }, + { + Key: "example.com/const", + ValueExpression: `"some-value"`, + }, + }, + }, + ClaimValidationRules: []authenticationv1alpha1.ClaimValidationRule{ + { + Claim: env.CLIUpstreamOIDC.UsernameClaim, + RequiredValue: env.CLIUpstreamOIDC.Username, + }, + { + Expression: fmt.Sprintf("claims.%s == '%s'", env.CLIUpstreamOIDC.UsernameClaim, env.CLIUpstreamOIDC.Username), + Message: "only one specific user is allowed", + }, + }, + UserValidationRules: []authenticationv1alpha1.UserValidationRule{ + { + Expression: "!user.username.startsWith('system:')", + Message: "username cannot used reserved system: prefix", + }, + }, + TLS: tlsSpecForCLIUpstreamOIDC(t), + }, authenticationv1alpha1.JWTAuthenticatorPhaseReady) + + return jwtAuthenticatorTypedLocalObjectReference(authenticator) + }, + token: func(t *testing.T) (string, string, []string, []string) { + pinnipedExe := testlib.PinnipedCLIPath(t) + credOutput, _ := runPinnipedLoginOIDC(ctx, t, pinnipedExe) + token := credOutput.Status.Token + + subject := getJWTClaimAsString(t, token, "sub") + require.NotEmpty(t, subject) + + wantExtras := expectedExtras(t, token) + wantExtras = append(wantExtras, fmt.Sprintf("example.com/sub=%s", subject)) + wantExtras = append(wantExtras, "example.com/const=some-value") + + return token, subject, []string{"group1", "group2"}, wantExtras + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + authenticatorRefToSubmit := test.authenticator(ctx, t) + tokenToSubmit, wantUsername, wantGroups, wantExtras := test.token(t) + + var response *loginv1alpha1.TokenCredentialRequest + testlib.RequireEventually(t, func(requireEventually *require.Assertions) { + var err error + response, err = testlib.CreateTokenCredentialRequest(ctx, t, + loginv1alpha1.TokenCredentialRequestSpec{Token: tokenToSubmit, Authenticator: authenticatorRefToSubmit}, + ) + requireEventually.NoError(err, "the request should never fail at the HTTP level") + requireEventually.NotNil(response) + requireEventually.NotNil(response.Status.Credential, "the response should contain a credential") + requireEventually.Emptyf(response.Status.Message, "value is: %q", safeDerefStringPtr(response.Status.Message)) + requireEventually.NotNil(response.Status.Credential) + requireEventually.Empty(response.Spec) + requireEventually.Empty(response.Status.Credential.Token) + requireEventually.NotEmpty(response.Status.Credential.ClientCertificateData) + requireEventually.Equal(wantUsername, getCommonName(t, response.Status.Credential.ClientCertificateData)) + requireEventually.ElementsMatch(wantGroups, getOrganizations(t, response.Status.Credential.ClientCertificateData)) + requireEventually.ElementsMatch(wantExtras, getOrganizationalUnits(t, response.Status.Credential.ClientCertificateData)) + requireEventually.NotEmpty(response.Status.Credential.ClientKeyData) + requireEventually.NotNil(response.Status.Credential.ExpirationTimestamp) + requireEventually.InDelta(5*time.Minute, time.Until(response.Status.Credential.ExpirationTimestamp.Time), float64(time.Minute)) + }, 10*time.Second, 500*time.Millisecond) + + // Create a client using the certificate from the CredentialRequest. + clientWithCertFromCredentialRequest := testlib.NewClientsetWithCertAndKey( + t, + response.Status.Credential.ClientCertificateData, + response.Status.Credential.ClientKeyData, + ) + + t.Run( + "access as user", + testlib.AccessAsUserTest(ctx, wantUsername, clientWithCertFromCredentialRequest), + ) + for _, group := range wantGroups { + t.Run( + "access as group "+group, + testlib.AccessAsGroupTest(ctx, group, clientWithCertFromCredentialRequest), + ) + } + }) + } +} + +// This test cannot run in parallel because runPinnipedLoginOIDC uses a fixed port +// for its localhost listener via --listen-port=env.CLIUpstreamOIDC.CallbackURL.Port() per oidcLoginCommand. +// Since ports are global to the process, tests using oidcLoginCommand must be run serially. +func TestCredentialRequest_JWTAuthenticatorRulesToDisallowLogin_Browser(t *testing.T) { + env := testlib.IntegrationEnv(t).WithCapability(testlib.AnonymousAuthenticationSupported) + + basicSpec := &authenticationv1alpha1.JWTAuthenticatorSpec{ + Issuer: env.CLIUpstreamOIDC.Issuer, + Audience: env.CLIUpstreamOIDC.ClientID, + Claims: authenticationv1alpha1.JWTTokenClaims{ + Username: env.CLIUpstreamOIDC.UsernameClaim, + Groups: env.CLIUpstreamOIDC.GroupsClaim, + }, + TLS: tlsSpecForCLIUpstreamOIDC(t), + } + + tests := []struct { + name string + authenticator func(context.Context, *testing.T) *authenticationv1alpha1.JWTAuthenticator + wantSuccessfulAuth bool + }{ + { + // Sanity check to make sure that the basic JWTAuthenticator spec works before adding rules which should cause auth failure. + name: "JWTAuthenticator successful login", + authenticator: func(ctx context.Context, t *testing.T) *authenticationv1alpha1.JWTAuthenticator { + return testlib.CreateTestJWTAuthenticator(ctx, t, *basicSpec.DeepCopy(), authenticationv1alpha1.JWTAuthenticatorPhaseReady) + }, + wantSuccessfulAuth: true, + }, + { + name: "JWTAuthenticator ClaimValidationRules using CEL expression should be able to prevent login", + authenticator: func(ctx context.Context, t *testing.T) *authenticationv1alpha1.JWTAuthenticator { + spec := basicSpec.DeepCopy() + spec.ClaimValidationRules = []authenticationv1alpha1.ClaimValidationRule{ + { + // This should cause the login to fail for this specific user. + Expression: fmt.Sprintf("claims.%s != '%s'", env.CLIUpstreamOIDC.UsernameClaim, env.CLIUpstreamOIDC.Username), + Message: "one specific user is disallowed", + }, + } + return testlib.CreateTestJWTAuthenticator(ctx, t, *spec, authenticationv1alpha1.JWTAuthenticatorPhaseReady) + }, + }, + { + name: "JWTAuthenticator ClaimValidationRules using RequiredValue should be able to prevent login", + authenticator: func(ctx context.Context, t *testing.T) *authenticationv1alpha1.JWTAuthenticator { + spec := basicSpec.DeepCopy() + spec.ClaimValidationRules = []authenticationv1alpha1.ClaimValidationRule{ + { + Claim: "sub", + RequiredValue: "this-will-never-be-the-sub-value", + }, + } + return testlib.CreateTestJWTAuthenticator(ctx, t, *spec, authenticationv1alpha1.JWTAuthenticatorPhaseReady) + }, + }, + { + name: "JWTAuthenticator UserValidationRules CEL expressions should be able to prevent login", + authenticator: func(ctx context.Context, t *testing.T) *authenticationv1alpha1.JWTAuthenticator { + spec := basicSpec.DeepCopy() + spec.UserValidationRules = []authenticationv1alpha1.UserValidationRule{ + { + Expression: "false", + Message: "nobody is allowed to auth", + }, + } + return testlib.CreateTestJWTAuthenticator(ctx, t, *spec, authenticationv1alpha1.JWTAuthenticatorPhaseReady) + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), 6*time.Minute) + t.Cleanup(cancel) + + authenticator := test.authenticator(ctx, t) + + pinnipedExe := testlib.PinnipedCLIPath(t) + credOutput, _ := runPinnipedLoginOIDC(ctx, t, pinnipedExe) + + response, err := testlib.CreateTokenCredentialRequest(ctx, t, + loginv1alpha1.TokenCredentialRequestSpec{ + Token: credOutput.Status.Token, + Authenticator: corev1.TypedLocalObjectReference{ + APIGroup: &authenticationv1alpha1.SchemeGroupVersion.Group, + Kind: "JWTAuthenticator", + Name: authenticator.Name, + }, + }, + ) + require.NoError(t, err, testlib.Sdump(err)) + + if test.wantSuccessfulAuth { + require.NotEmpty(t, response.Status.Credential) + require.Empty(t, response.Status.Message) + } else { + require.Nil(t, response.Status.Credential) + require.NotNil(t, response.Status.Message) + require.Equal(t, "authentication failed", *response.Status.Message) + } + }) + } +} + // TCRs are non-mutating and safe to run in parallel with serial tests, see main_test.go. -func TestUnsuccessfulCredentialRequest_Parallel(t *testing.T) { +func TestCredentialRequest_ShouldFailWhenTheAuthenticatorDoesNotExist_Parallel(t *testing.T) { env := testlib.IntegrationEnv(t).WithCapability(testlib.AnonymousAuthenticationSupported) ctx, cancel := context.WithTimeout(context.Background(), time.Minute) - defer cancel() + t.Cleanup(cancel) response, err := testlib.CreateTokenCredentialRequest(ctx, t, loginv1alpha1.TokenCredentialRequestSpec{ @@ -46,110 +342,15 @@ func TestUnsuccessfulCredentialRequest_Parallel(t *testing.T) { require.Equal(t, "authentication failed", *response.Status.Message) } -// TestSuccessfulCredentialRequest_Browser cannot run in parallel because runPinnipedLoginOIDC uses a fixed port -// for its localhost listener via --listen-port=env.CLIUpstreamOIDC.CallbackURL.Port() per oidcLoginCommand. -// Since ports are global to the process, tests using oidcLoginCommand must be run serially. -func TestSuccessfulCredentialRequest_Browser(t *testing.T) { - env := testlib.IntegrationEnv(t).WithCapability(testlib.ClusterSigningKeyIsAvailable) - - ctx, cancel := context.WithTimeout(context.Background(), 6*time.Minute) - defer cancel() - - tests := []struct { - name string - authenticator func(context.Context, *testing.T) corev1.TypedLocalObjectReference - token func(t *testing.T) (token string, username string, groups []string) - }{ - { - name: "webhook", - authenticator: func(ctx context.Context, t *testing.T) corev1.TypedLocalObjectReference { - return testlib.CreateTestWebhookAuthenticator(ctx, t, &testlib.IntegrationEnv(t).TestWebhook, authenticationv1alpha1.WebhookAuthenticatorPhaseReady) - }, - token: func(t *testing.T) (string, string, []string) { - return testlib.IntegrationEnv(t).TestUser.Token, env.TestUser.ExpectedUsername, env.TestUser.ExpectedGroups - }, - }, - { - name: "jwt authenticator", - authenticator: func(ctx context.Context, t *testing.T) corev1.TypedLocalObjectReference { - authenticator := testlib.CreateTestJWTAuthenticatorForCLIUpstream(ctx, t) - return corev1.TypedLocalObjectReference{ - APIGroup: &authenticationv1alpha1.SchemeGroupVersion.Group, - Kind: "JWTAuthenticator", - Name: authenticator.Name, - } - }, - token: func(t *testing.T) (string, string, []string) { - pinnipedExe := testlib.PinnipedCLIPath(t) - credOutput, _ := runPinnipedLoginOIDC(ctx, t, pinnipedExe) - token := credOutput.Status.Token - - // By default, the JWTAuthenticator expects the username to be in the "username" claim and the - // groups to be in the "groups" claim. - // However, we are configuring Pinniped in the `CreateTestJWTAuthenticatorForCLIUpstream` method above - // to read the username from the "sub" claim of the token instead. - username, groups := getJWTSubAndGroupsClaims(t, token) - - return token, username, groups - }, - }, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - authenticator := test.authenticator(ctx, t) - token, username, groups := test.token(t) - - var response *loginv1alpha1.TokenCredentialRequest - testlib.RequireEventually(t, func(requireEventually *require.Assertions) { - var err error - response, err = testlib.CreateTokenCredentialRequest(ctx, t, - loginv1alpha1.TokenCredentialRequestSpec{Token: token, Authenticator: authenticator}, - ) - requireEventually.NoError(err, "the request should never fail at the HTTP level") - requireEventually.NotNil(response) - requireEventually.NotNil(response.Status.Credential, "the response should contain a credential") - requireEventually.Emptyf(response.Status.Message, "value is: %q", safeDerefStringPtr(response.Status.Message)) - requireEventually.NotNil(response.Status.Credential) - requireEventually.Empty(response.Spec) - requireEventually.Empty(response.Status.Credential.Token) - requireEventually.NotEmpty(response.Status.Credential.ClientCertificateData) - requireEventually.Equal(username, getCommonName(t, response.Status.Credential.ClientCertificateData)) - requireEventually.ElementsMatch(groups, getOrganizations(t, response.Status.Credential.ClientCertificateData)) - requireEventually.NotEmpty(response.Status.Credential.ClientKeyData) - requireEventually.NotNil(response.Status.Credential.ExpirationTimestamp) - requireEventually.InDelta(5*time.Minute, time.Until(response.Status.Credential.ExpirationTimestamp.Time), float64(time.Minute)) - }, 10*time.Second, 500*time.Millisecond) - - // Create a client using the certificate from the CredentialRequest. - clientWithCertFromCredentialRequest := testlib.NewClientsetWithCertAndKey( - t, - response.Status.Credential.ClientCertificateData, - response.Status.Credential.ClientKeyData, - ) - - t.Run( - "access as user", - testlib.AccessAsUserTest(ctx, username, clientWithCertFromCredentialRequest), - ) - for _, group := range groups { - t.Run( - "access as group "+group, - testlib.AccessAsGroupTest(ctx, group, clientWithCertFromCredentialRequest), - ) - } - }) - } -} - // TCRs are non-mutating and safe to run in parallel with serial tests, see main_test.go. -func TestFailedCredentialRequestWhenTheRequestIsValidButTheTokenDoesNotAuthenticateTheUser_Parallel(t *testing.T) { - _ = testlib.IntegrationEnv(t).WithCapability(testlib.ClusterSigningKeyIsAvailable) +func TestCredentialRequest_ShouldFailWhenTheRequestIsValidButTheTokenDoesNotAuthenticateTheUser_Parallel(t *testing.T) { + env := testlib.IntegrationEnv(t).WithCapability(testlib.AnonymousAuthenticationSupported) - // Create a testWebhook so we have a legitimate authenticator to pass to the - // TokenCredentialRequest API. + // Create a testWebhook so we have a legitimate authenticator to pass to the TokenCredentialRequest API. ctx, cancel := context.WithTimeout(context.Background(), time.Minute) - defer cancel() - testWebhook := testlib.CreateTestWebhookAuthenticator(ctx, t, &testlib.IntegrationEnv(t).TestWebhook, authenticationv1alpha1.WebhookAuthenticatorPhaseReady) + t.Cleanup(cancel) + + testWebhook := testlib.CreateTestWebhookAuthenticator(ctx, t, &env.TestWebhook, authenticationv1alpha1.WebhookAuthenticatorPhaseReady) response, err := testlib.CreateTokenCredentialRequest(context.Background(), t, loginv1alpha1.TokenCredentialRequestSpec{Token: "not a good token", Authenticator: testWebhook}, @@ -164,13 +365,13 @@ func TestFailedCredentialRequestWhenTheRequestIsValidButTheTokenDoesNotAuthentic // TCRs are non-mutating and safe to run in parallel with serial tests, see main_test.go. func TestCredentialRequest_ShouldFailWhenRequestDoesNotIncludeToken_Parallel(t *testing.T) { - _ = testlib.IntegrationEnv(t).WithCapability(testlib.ClusterSigningKeyIsAvailable) + env := testlib.IntegrationEnv(t).WithCapability(testlib.AnonymousAuthenticationSupported) - // Create a testWebhook so we have a legitimate authenticator to pass to the - // TokenCredentialRequest API. + // Create a testWebhook so we have a legitimate authenticator to pass to the TokenCredentialRequest API. ctx, cancel := context.WithTimeout(context.Background(), time.Minute) - defer cancel() - testWebhook := testlib.CreateTestWebhookAuthenticator(ctx, t, &testlib.IntegrationEnv(t).TestWebhook, authenticationv1alpha1.WebhookAuthenticatorPhaseReady) + t.Cleanup(cancel) + + testWebhook := testlib.CreateTestWebhookAuthenticator(ctx, t, &env.TestWebhook, authenticationv1alpha1.WebhookAuthenticatorPhaseReady) response, err := testlib.CreateTokenCredentialRequest(context.Background(), t, loginv1alpha1.TokenCredentialRequestSpec{Token: "", Authenticator: testWebhook}, @@ -210,6 +411,16 @@ func getOrganizations(t *testing.T, certPEM string) []string { return cert.Subject.Organization } +func getOrganizationalUnits(t *testing.T, certPEM string) []string { + t.Helper() + + pemBlock, _ := pem.Decode([]byte(certPEM)) + cert, err := x509.ParseCertificate(pemBlock.Bytes) + require.NoError(t, err) + + return cert.Subject.OrganizationalUnit +} + func safeDerefStringPtr(s *string) string { if s == nil { return "" @@ -217,18 +428,51 @@ func safeDerefStringPtr(s *string) string { return *s } -func getJWTSubAndGroupsClaims(t *testing.T, jwtToken string) (string, []string) { +func getJWTClaimAsString(t *testing.T, jwtToken string, claimName string) string { + t.Helper() + claims := getJWTClaims(t, jwtToken) + require.Contains(t, claims, claimName) + val := claims[claimName] + strVal, ok := val.(string) + require.Truef(t, ok, "expected value of claim %q to be a string, but it was: %#v", claimName, claims[claimName]) + return strVal +} + +func getJWTClaimAsStringSlice(t *testing.T, jwtToken string, claimName string) []string { + t.Helper() + claims := getJWTClaims(t, jwtToken) + require.Contains(t, claims, claimName) + val := claims[claimName] + anySliceVal, ok := val.([]any) + require.Truef(t, ok, "expected value of claim %q to be a []any, but it was: %#v", claimName, claims[claimName]) + strSliceVal := make([]string, len(anySliceVal)) + for i := range anySliceVal { + strSliceVal[i], ok = anySliceVal[i].(string) + require.Truef(t, ok, "expected every value of array at claim %q to be a string, but one element was: %#v", claimName, anySliceVal[i]) + } + return strSliceVal +} + +func getJWTClaims(t *testing.T, jwtToken string) map[string]any { t.Helper() token, err := josejwt.ParseSigned(jwtToken, []jose.SignatureAlgorithm{jose.ES256, jose.RS256}) require.NoError(t, err) - var claims struct { - Sub string `json:"sub"` - Groups []string `json:"groups"` - } + claims := map[string]any{} err = token.UnsafeClaimsWithoutVerification(&claims) require.NoError(t, err) - return claims.Sub, claims.Groups + return claims +} + +func tlsSpecForCLIUpstreamOIDC(t *testing.T) *authenticationv1alpha1.TLSSpec { + env := testlib.IntegrationEnv(t) + // If the test upstream does not have a CA bundle specified, then don't configure it. + if env.CLIUpstreamOIDC.CABundle != "" { + return &authenticationv1alpha1.TLSSpec{ + CertificateAuthorityData: base64.StdEncoding.EncodeToString([]byte(env.CLIUpstreamOIDC.CABundle)), + } + } + return nil } diff --git a/test/testlib/client.go b/test/testlib/client.go index 4f6db331c..6210c6be8 100644 --- a/test/testlib/client.go +++ b/test/testlib/client.go @@ -1,4 +1,4 @@ -// Copyright 2020-2024 the Pinniped contributors. All Rights Reserved. +// Copyright 2020-2025 the Pinniped contributors. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 package testlib @@ -6,7 +6,6 @@ package testlib import ( "context" "crypto/rand" - "encoding/base64" "encoding/hex" "fmt" "io" @@ -253,32 +252,6 @@ func WaitForWebhookAuthenticatorStatusConditions(ctx context.Context, t *testing }, 60*time.Second, 1*time.Second, "wanted WebhookAuthenticator conditions") } -// CreateTestJWTAuthenticatorForCLIUpstream creates and returns a test JWTAuthenticator which will be automatically -// deleted at the end of the current test's lifetime. -// -// CreateTestJWTAuthenticatorForCLIUpstream gets the OIDC issuer info from IntegrationEnv().CLIUpstreamOIDC. -func CreateTestJWTAuthenticatorForCLIUpstream(ctx context.Context, t *testing.T) *authenticationv1alpha1.JWTAuthenticator { - t.Helper() - testEnv := IntegrationEnv(t) - spec := authenticationv1alpha1.JWTAuthenticatorSpec{ - Issuer: testEnv.CLIUpstreamOIDC.Issuer, - Audience: testEnv.CLIUpstreamOIDC.ClientID, - // The default UsernameClaim is "username" but the upstreams that we use for - // integration tests won't necessarily have that claim, so use "sub" here. - Claims: authenticationv1alpha1.JWTTokenClaims{Username: "sub"}, - } - // If the test upstream does not have a CA bundle specified, then don't configure one in the - // JWTAuthenticator. Leaving TLSSpec set to nil will result in OIDC discovery using the OS's root - // CA store. - if testEnv.CLIUpstreamOIDC.CABundle != "" { - spec.TLS = &authenticationv1alpha1.TLSSpec{ - CertificateAuthorityData: base64.StdEncoding.EncodeToString([]byte(testEnv.CLIUpstreamOIDC.CABundle)), - } - } - authenticator := CreateTestJWTAuthenticator(ctx, t, spec, authenticationv1alpha1.JWTAuthenticatorPhaseReady) - return authenticator -} - // CreateTestJWTAuthenticator creates and returns a test JWTAuthenticator which will be automatically deleted // at the end of the current test's lifetime. func CreateTestJWTAuthenticator( diff --git a/test/testlib/env.go b/test/testlib/env.go index 01f10237e..662221201 100644 --- a/test/testlib/env.go +++ b/test/testlib/env.go @@ -304,12 +304,15 @@ func loadEnvVars(t *testing.T, result *TestEnv) { result.ShellContainerImage = needEnv(t, "PINNIPED_TEST_SHELL_CONTAINER_IMAGE") result.CLIUpstreamOIDC = TestOIDCUpstream{ - Issuer: needEnv(t, "PINNIPED_TEST_CLI_OIDC_ISSUER"), - CABundle: base64Decoded(t, os.Getenv("PINNIPED_TEST_CLI_OIDC_ISSUER_CA_BUNDLE")), - ClientID: needEnv(t, "PINNIPED_TEST_CLI_OIDC_CLIENT_ID"), - CallbackURL: needEnv(t, "PINNIPED_TEST_CLI_OIDC_CALLBACK_URL"), - Username: needEnv(t, "PINNIPED_TEST_CLI_OIDC_USERNAME"), - Password: needEnv(t, "PINNIPED_TEST_CLI_OIDC_PASSWORD"), + Issuer: needEnv(t, "PINNIPED_TEST_CLI_OIDC_ISSUER"), + CABundle: base64Decoded(t, os.Getenv("PINNIPED_TEST_CLI_OIDC_ISSUER_CA_BUNDLE")), + UsernameClaim: os.Getenv("PINNIPED_TEST_CLI_OIDC_USERNAME_CLAIM"), + GroupsClaim: os.Getenv("PINNIPED_TEST_CLI_OIDC_GROUPS_CLAIM"), + ClientID: needEnv(t, "PINNIPED_TEST_CLI_OIDC_CLIENT_ID"), + CallbackURL: needEnv(t, "PINNIPED_TEST_CLI_OIDC_CALLBACK_URL"), + Username: needEnv(t, "PINNIPED_TEST_CLI_OIDC_USERNAME"), + Password: needEnv(t, "PINNIPED_TEST_CLI_OIDC_PASSWORD"), + ExpectedGroups: filterEmpty(strings.Split(strings.ReplaceAll(os.Getenv("PINNIPED_TEST_CLI_OIDC_EXPECTED_GROUPS"), " ", ""), ",")), } result.SupervisorUpstreamOIDC = TestOIDCUpstream{ From 83696fd023204ddc6d05503b07825fd8c7d3dd5d Mon Sep 17 00:00:00 2001 From: Ryan Richard Date: Fri, 18 Jul 2025 12:22:06 -0700 Subject: [PATCH 5/7] improve errors and docs for JWTAuthenticator features, with int tests --- .../v1alpha1/types_jwtauthenticator.go.tmpl | 62 ++- ...cierge.pinniped.dev_jwtauthenticators.yaml | 61 ++- generated/1.26/README.adoc | 61 ++- .../v1alpha1/types_jwtauthenticator.go | 62 ++- ...cierge.pinniped.dev_jwtauthenticators.yaml | 61 ++- generated/1.27/README.adoc | 61 ++- .../v1alpha1/types_jwtauthenticator.go | 62 ++- ...cierge.pinniped.dev_jwtauthenticators.yaml | 61 ++- generated/1.28/README.adoc | 61 ++- .../v1alpha1/types_jwtauthenticator.go | 62 ++- ...cierge.pinniped.dev_jwtauthenticators.yaml | 61 ++- generated/1.29/README.adoc | 61 ++- .../v1alpha1/types_jwtauthenticator.go | 62 ++- ...cierge.pinniped.dev_jwtauthenticators.yaml | 61 ++- generated/1.30/README.adoc | 61 ++- .../v1alpha1/types_jwtauthenticator.go | 62 ++- ...cierge.pinniped.dev_jwtauthenticators.yaml | 61 ++- generated/1.31/README.adoc | 61 ++- .../v1alpha1/types_jwtauthenticator.go | 62 ++- ...cierge.pinniped.dev_jwtauthenticators.yaml | 61 ++- generated/1.32/README.adoc | 61 ++- .../v1alpha1/types_jwtauthenticator.go | 62 ++- ...cierge.pinniped.dev_jwtauthenticators.yaml | 61 ++- generated/1.33/README.adoc | 61 ++- .../v1alpha1/types_jwtauthenticator.go | 62 ++- ...cierge.pinniped.dev_jwtauthenticators.yaml | 61 ++- generated/latest/README.adoc | 61 ++- .../v1alpha1/types_jwtauthenticator.go | 62 ++- .../jwtcachefiller/jwtcachefiller.go | 38 +- .../jwtcachefiller/jwtcachefiller_test.go | 63 ++- .../concierge_jwtauthenticator_status_test.go | 378 ++++++++++++++++-- 31 files changed, 1787 insertions(+), 410 deletions(-) diff --git a/apis/concierge/authentication/v1alpha1/types_jwtauthenticator.go.tmpl b/apis/concierge/authentication/v1alpha1/types_jwtauthenticator.go.tmpl index 8d416bcbf..6c782b5ce 100644 --- a/apis/concierge/authentication/v1alpha1/types_jwtauthenticator.go.tmpl +++ b/apis/concierge/authentication/v1alpha1/types_jwtauthenticator.go.tmpl @@ -77,14 +77,12 @@ type JWTAuthenticatorSpec struct { // ClaimValidationRule provides the configuration for a single claim validation rule. type ClaimValidationRule struct { // claim is the name of a required claim. - // Same as --oidc-required-claim flag. // Only string claim keys are supported. // Mutually exclusive with expression and message. // +optional Claim string `json:"claim,omitempty"` // requiredValue is the value of a required claim. - // Same as --oidc-required-claim flag. // 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. // Mutually exclusive with expression and message. @@ -147,9 +145,14 @@ type JWTTokenClaims struct { Username string `json:"username"` // usernameExpression represents an expression which will be evaluated by CEL. - // // The expression's result will become the user's username. // + // usernameExpression is similar to claimMappings.username.expression from Kubernetes AuthenticationConfiguration + // as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication. + // This is an advanced configuration option. During an end-user login flow, each of these CEL expressions + // must evaluate to the expected type without errors, or else the user's login will fail. + // Additionally, mistakes in this configuration can cause the users to have unintended usernames. + // // The expression must produce a non-empty string value. // If the expression uses 'claims.email', then 'claims.email_verified' must be used in // the expression or extra[*].valueExpression or claimValidationRules[*].expression. @@ -180,9 +183,14 @@ type JWTTokenClaims struct { Groups string `json:"groups"` // groupsExpression represents an expression which will be evaluated by CEL. - // // The expression's result will become the user's group memberships. // + // groupsExpression is similar to claimMappings.groups.expression from Kubernetes AuthenticationConfiguration + // as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication. + // This is an advanced configuration option. During an end-user login flow, each of these CEL expressions + // must evaluate to one of the expected types without errors, or else the user's login will fail. + // Additionally, mistakes in this configuration can cause the users to have unintended group memberships. + // // The expression must produce a string or string array value. // "", [], and null values are treated as the group mapping not being present. // @@ -198,18 +206,45 @@ type JWTTokenClaims struct { // +optional GroupsExpression string `json:"groupsExpression,omitempty"` - // extra is similar to claimMappings.extra from Kubernetes AuthenticationConfiguration as documented in - // https://kubernetes.io/docs/reference/access-authn-authz/authentication. However, note that the - // Pinniped Concierge issues client certificates to users for the purpose of authenticating, and - // the Kubernetes API server does not have any mechanism for transmitting auth extras via client - // certificates. When configured, these extras will appear in client certificates issued by the - // Pinniped Supervisor in the x509 Subject field as Organizational Units (OU). However, when this - // client certificate is presented to Kubernetes for authentication, Kubernetes will ignore these - // extras. This is probably only useful if you are using a custom authenticating proxy in front - // of your Kubernetes API server which can translate these OUs into auth extras, as described by + // extra is similar to claimMappings.extra from Kubernetes AuthenticationConfiguration + // as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication. + // + // However, note that the Pinniped Concierge issues client certificates to users for the purpose + // of authenticating, and the Kubernetes API server does not have any mechanism for transmitting + // auth extras via client certificates. When configured, these extras will appear in client + // certificates issued by the Pinniped Supervisor in the x509 Subject field as Organizational + // Units (OU). However, when this client certificate is presented to Kubernetes for authentication, + // Kubernetes will ignore these extras. This is probably only useful if you are using a custom + // authenticating proxy in front of your Kubernetes API server which can translate these OUs into + // auth extras, as described by // https://kubernetes.io/docs/reference/access-authn-authz/authentication/#authenticating-proxy. // This is an advanced configuration option. During an end-user login flow, each of these CEL expressions // must evaluate to either a string or an array of strings, or else the user's login will fail. + // + // These keys must be a domain-prefixed path (such as "acme.io/foo") and must not contain an equals sign ("="). + // + // expression must produce a string or string array value. + // If the value is empty, the extra mapping will not be present. + // + // Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ + // + // hard-coded extra key/value + // - key: "acme.io/foo" + // valueExpression: "'bar'" + // This will result in an extra attribute - acme.io/foo: ["bar"] + // + // hard-coded key, value copying claim value + // - key: "acme.io/foo" + // valueExpression: "claims.some_claim" + // This will result in an extra attribute - acme.io/foo: [value of some_claim] + // + // hard-coded key, value derived from claim value + // - key: "acme.io/admin" + // valueExpression: '(has(claims.is_admin) && claims.is_admin) ? "true":""' + // This will result in: + // - if is_admin claim is present and true, extra attribute - acme.io/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"` } @@ -222,6 +257,7 @@ type ExtraMapping struct { // be valid HTTP Path characters as defined by RFC 3986. // key must be lowercase. // Required to be unique. + // Additionally, the key must not contain an equals sign ("="). // +required Key string `json:"key"` diff --git a/deploy/concierge/authentication.concierge.pinniped.dev_jwtauthenticators.yaml b/deploy/concierge/authentication.concierge.pinniped.dev_jwtauthenticators.yaml index 057c8d7b8..4fa9acc3e 100644 --- a/deploy/concierge/authentication.concierge.pinniped.dev_jwtauthenticators.yaml +++ b/deploy/concierge/authentication.concierge.pinniped.dev_jwtauthenticators.yaml @@ -78,7 +78,6 @@ spec: claim: description: |- claim is the name of a required claim. - Same as --oidc-required-claim flag. Only string claim keys are supported. Mutually exclusive with expression and message. type: string @@ -106,7 +105,6 @@ spec: requiredValue: description: |- requiredValue is the value of a required claim. - Same as --oidc-required-claim flag. 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. Mutually exclusive with expression and message. @@ -120,18 +118,44 @@ spec: properties: extra: description: |- - extra is similar to claimMappings.extra from Kubernetes AuthenticationConfiguration as documented in - https://kubernetes.io/docs/reference/access-authn-authz/authentication. However, note that the - Pinniped Concierge issues client certificates to users for the purpose of authenticating, and - the Kubernetes API server does not have any mechanism for transmitting auth extras via client - certificates. When configured, these extras will appear in client certificates issued by the - Pinniped Supervisor in the x509 Subject field as Organizational Units (OU). However, when this - client certificate is presented to Kubernetes for authentication, Kubernetes will ignore these - extras. This is probably only useful if you are using a custom authenticating proxy in front - of your Kubernetes API server which can translate these OUs into auth extras, as described by + extra is similar to claimMappings.extra from Kubernetes AuthenticationConfiguration + as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication. + + However, note that the Pinniped Concierge issues client certificates to users for the purpose + of authenticating, and the Kubernetes API server does not have any mechanism for transmitting + auth extras via client certificates. When configured, these extras will appear in client + certificates issued by the Pinniped Supervisor in the x509 Subject field as Organizational + Units (OU). However, when this client certificate is presented to Kubernetes for authentication, + Kubernetes will ignore these extras. This is probably only useful if you are using a custom + authenticating proxy in front of your Kubernetes API server which can translate these OUs into + auth extras, as described by https://kubernetes.io/docs/reference/access-authn-authz/authentication/#authenticating-proxy. This is an advanced configuration option. During an end-user login flow, each of these CEL expressions must evaluate to either a string or an array of strings, or else the user's login will fail. + + These keys must be a domain-prefixed path (such as "acme.io/foo") and must not contain an equals sign ("="). + + expression must produce a string or string array value. + If the value is empty, the extra mapping will not be present. + + Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ + + hard-coded extra key/value + - key: "acme.io/foo" + valueExpression: "'bar'" + This will result in an extra attribute - acme.io/foo: ["bar"] + + hard-coded key, value copying claim value + - key: "acme.io/foo" + valueExpression: "claims.some_claim" + This will result in an extra attribute - acme.io/foo: [value of some_claim] + + hard-coded key, value derived from claim value + - key: "acme.io/admin" + valueExpression: '(has(claims.is_admin) && claims.is_admin) ? "true":""' + This will result in: + - if is_admin claim is present and true, extra attribute - acme.io/admin: ["true"] + - if is_admin claim is present and false or is_admin claim is not present, no extra attribute will be added items: description: ExtraMapping provides the configuration for a single extra mapping. @@ -144,6 +168,7 @@ spec: be valid HTTP Path characters as defined by RFC 3986. key must be lowercase. Required to be unique. + Additionally, the key must not contain an equals sign ("="). type: string valueExpression: description: |- @@ -176,9 +201,14 @@ spec: groupsExpression: description: |- groupsExpression represents an expression which will be evaluated by CEL. - The expression's result will become the user's group memberships. + groupsExpression is similar to claimMappings.groups.expression from Kubernetes AuthenticationConfiguration + as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication. + This is an advanced configuration option. During an end-user login flow, each of these CEL expressions + must evaluate to one of the expected types without errors, or else the user's login will fail. + Additionally, mistakes in this configuration can cause the users to have unintended group memberships. + The expression must produce a string or string array value. "", [], and null values are treated as the group mapping not being present. @@ -204,9 +234,14 @@ spec: usernameExpression: description: |- usernameExpression represents an expression which will be evaluated by CEL. - The expression's result will become the user's username. + usernameExpression is similar to claimMappings.username.expression from Kubernetes AuthenticationConfiguration + as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication. + This is an advanced configuration option. During an end-user login flow, each of these CEL expressions + must evaluate to the expected type without errors, or else the user's login will fail. + Additionally, mistakes in this configuration can cause the users to have unintended usernames. + The expression must produce a non-empty string value. If the expression uses 'claims.email', then 'claims.email_verified' must be used in the expression or extra[*].valueExpression or claimValidationRules[*].expression. diff --git a/generated/1.26/README.adoc b/generated/1.26/README.adoc index 0983f1dc9..b80bc78a9 100644 --- a/generated/1.26/README.adoc +++ b/generated/1.26/README.adoc @@ -74,11 +74,9 @@ ClaimValidationRule provides the configuration for a single claim validation rul |=== | Field | Description | *`claim`* __string__ | claim is the name of a required claim. + -Same as --oidc-required-claim flag. + Only string claim keys are supported. + Mutually exclusive with expression and message. + | *`requiredValue`* __string__ | requiredValue is the value of a required claim. + -Same as --oidc-required-claim flag. + 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. + Mutually exclusive with expression and message. + @@ -119,6 +117,7 @@ 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 to be unique. + +Additionally, the key must not contain an equals sign ("="). + | *`valueExpression`* __string__ | 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. + @@ -244,9 +243,14 @@ unless usernameExpression is specified. + Mutually exclusive with usernameExpression. Use either username or usernameExpression to + determine the user's username from the JWT token. + | *`usernameExpression`* __string__ | usernameExpression represents an expression which will be evaluated by CEL. + - The expression's result will become the user's username. + +usernameExpression is similar to claimMappings.username.expression from Kubernetes AuthenticationConfiguration + +as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication. + +This is an advanced configuration option. During an end-user login flow, each of these CEL expressions + +must evaluate to the expected type without errors, or else the user's login will fail. + +Additionally, mistakes in this configuration can cause the users to have unintended usernames. + + The expression must produce a non-empty string value. + If the expression uses 'claims.email', then 'claims.email_verified' must be used in + the expression or extra[*].valueExpression or claimValidationRules[*].expression. + @@ -271,9 +275,14 @@ unless groupsExpression is specified. + Mutually exclusive with groupsExpression. Use either groups or groupsExpression to + determine the user's group membership from the JWT token. + | *`groupsExpression`* __string__ | groupsExpression represents an expression which will be evaluated by CEL. + - The expression's result will become the user's group memberships. + +groupsExpression is similar to claimMappings.groups.expression from Kubernetes AuthenticationConfiguration + +as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication. + +This is an advanced configuration option. During an end-user login flow, each of these CEL expressions + +must evaluate to one of the expected types without errors, or else the user's login will fail. + +Additionally, mistakes in this configuration can cause the users to have unintended group memberships. + + The expression must produce a string or string array value. + "", [], and null values are treated as the group mapping not being present. + @@ -286,18 +295,44 @@ Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ + Mutually exclusive with groups. Use either groups or groupsExpression to + determine the user's group membership from the JWT token. + -| *`extra`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-26-apis-concierge-authentication-v1alpha1-extramapping[$$ExtraMapping$$] array__ | extra is similar to claimMappings.extra from Kubernetes AuthenticationConfiguration as documented in + -https://kubernetes.io/docs/reference/access-authn-authz/authentication. However, note that the + -Pinniped Concierge issues client certificates to users for the purpose of authenticating, and + -the Kubernetes API server does not have any mechanism for transmitting auth extras via client + -certificates. When configured, these extras will appear in client certificates issued by the + -Pinniped Supervisor in the x509 Subject field as Organizational Units (OU). However, when this + -client certificate is presented to Kubernetes for authentication, Kubernetes will ignore these + -extras. This is probably only useful if you are using a custom authenticating proxy in front + -of your Kubernetes API server which can translate these OUs into auth extras, as described by + +| *`extra`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-26-apis-concierge-authentication-v1alpha1-extramapping[$$ExtraMapping$$] array__ | extra is similar to claimMappings.extra from Kubernetes AuthenticationConfiguration + +as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication. + + +However, note that the Pinniped Concierge issues client certificates to users for the purpose + +of authenticating, and the Kubernetes API server does not have any mechanism for transmitting + +auth extras via client certificates. When configured, these extras will appear in client + +certificates issued by the Pinniped Supervisor in the x509 Subject field as Organizational + +Units (OU). However, when this client certificate is presented to Kubernetes for authentication, + +Kubernetes will ignore these extras. This is probably only useful if you are using a custom + +authenticating proxy in front of your Kubernetes API server which can translate these OUs into + +auth extras, as described by + https://kubernetes.io/docs/reference/access-authn-authz/authentication/#authenticating-proxy. + This is an advanced configuration option. During an end-user login flow, each of these CEL expressions + must evaluate to either a string or an array of strings, or else the user's login will fail. + + +These keys must be a domain-prefixed path (such as "acme.io/foo") and must not contain an equals sign ("="). + + +expression must produce a string or string array value. + +If the value is empty, the extra mapping will not be present. + + +Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ + + +hard-coded extra key/value + +- key: "acme.io/foo" + +valueExpression: "'bar'" + +This will result in an extra attribute - acme.io/foo: ["bar"] + + +hard-coded key, value copying claim value + +- key: "acme.io/foo" + +valueExpression: "claims.some_claim" + +This will result in an extra attribute - acme.io/foo: [value of some_claim] + + +hard-coded key, value derived from claim value + +- key: "acme.io/admin" + +valueExpression: '(has(claims.is_admin) && claims.is_admin) ? "true":""' + +This will result in: + +- if is_admin claim is present and true, extra attribute - acme.io/admin: ["true"] + +- if is_admin claim is present and false or is_admin claim is not present, no extra attribute will be added + |=== diff --git a/generated/1.26/apis/concierge/authentication/v1alpha1/types_jwtauthenticator.go b/generated/1.26/apis/concierge/authentication/v1alpha1/types_jwtauthenticator.go index 8d416bcbf..6c782b5ce 100644 --- a/generated/1.26/apis/concierge/authentication/v1alpha1/types_jwtauthenticator.go +++ b/generated/1.26/apis/concierge/authentication/v1alpha1/types_jwtauthenticator.go @@ -77,14 +77,12 @@ type JWTAuthenticatorSpec struct { // ClaimValidationRule provides the configuration for a single claim validation rule. type ClaimValidationRule struct { // claim is the name of a required claim. - // Same as --oidc-required-claim flag. // Only string claim keys are supported. // Mutually exclusive with expression and message. // +optional Claim string `json:"claim,omitempty"` // requiredValue is the value of a required claim. - // Same as --oidc-required-claim flag. // 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. // Mutually exclusive with expression and message. @@ -147,9 +145,14 @@ type JWTTokenClaims struct { Username string `json:"username"` // usernameExpression represents an expression which will be evaluated by CEL. - // // The expression's result will become the user's username. // + // usernameExpression is similar to claimMappings.username.expression from Kubernetes AuthenticationConfiguration + // as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication. + // This is an advanced configuration option. During an end-user login flow, each of these CEL expressions + // must evaluate to the expected type without errors, or else the user's login will fail. + // Additionally, mistakes in this configuration can cause the users to have unintended usernames. + // // The expression must produce a non-empty string value. // If the expression uses 'claims.email', then 'claims.email_verified' must be used in // the expression or extra[*].valueExpression or claimValidationRules[*].expression. @@ -180,9 +183,14 @@ type JWTTokenClaims struct { Groups string `json:"groups"` // groupsExpression represents an expression which will be evaluated by CEL. - // // The expression's result will become the user's group memberships. // + // groupsExpression is similar to claimMappings.groups.expression from Kubernetes AuthenticationConfiguration + // as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication. + // This is an advanced configuration option. During an end-user login flow, each of these CEL expressions + // must evaluate to one of the expected types without errors, or else the user's login will fail. + // Additionally, mistakes in this configuration can cause the users to have unintended group memberships. + // // The expression must produce a string or string array value. // "", [], and null values are treated as the group mapping not being present. // @@ -198,18 +206,45 @@ type JWTTokenClaims struct { // +optional GroupsExpression string `json:"groupsExpression,omitempty"` - // extra is similar to claimMappings.extra from Kubernetes AuthenticationConfiguration as documented in - // https://kubernetes.io/docs/reference/access-authn-authz/authentication. However, note that the - // Pinniped Concierge issues client certificates to users for the purpose of authenticating, and - // the Kubernetes API server does not have any mechanism for transmitting auth extras via client - // certificates. When configured, these extras will appear in client certificates issued by the - // Pinniped Supervisor in the x509 Subject field as Organizational Units (OU). However, when this - // client certificate is presented to Kubernetes for authentication, Kubernetes will ignore these - // extras. This is probably only useful if you are using a custom authenticating proxy in front - // of your Kubernetes API server which can translate these OUs into auth extras, as described by + // extra is similar to claimMappings.extra from Kubernetes AuthenticationConfiguration + // as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication. + // + // However, note that the Pinniped Concierge issues client certificates to users for the purpose + // of authenticating, and the Kubernetes API server does not have any mechanism for transmitting + // auth extras via client certificates. When configured, these extras will appear in client + // certificates issued by the Pinniped Supervisor in the x509 Subject field as Organizational + // Units (OU). However, when this client certificate is presented to Kubernetes for authentication, + // Kubernetes will ignore these extras. This is probably only useful if you are using a custom + // authenticating proxy in front of your Kubernetes API server which can translate these OUs into + // auth extras, as described by // https://kubernetes.io/docs/reference/access-authn-authz/authentication/#authenticating-proxy. // This is an advanced configuration option. During an end-user login flow, each of these CEL expressions // must evaluate to either a string or an array of strings, or else the user's login will fail. + // + // These keys must be a domain-prefixed path (such as "acme.io/foo") and must not contain an equals sign ("="). + // + // expression must produce a string or string array value. + // If the value is empty, the extra mapping will not be present. + // + // Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ + // + // hard-coded extra key/value + // - key: "acme.io/foo" + // valueExpression: "'bar'" + // This will result in an extra attribute - acme.io/foo: ["bar"] + // + // hard-coded key, value copying claim value + // - key: "acme.io/foo" + // valueExpression: "claims.some_claim" + // This will result in an extra attribute - acme.io/foo: [value of some_claim] + // + // hard-coded key, value derived from claim value + // - key: "acme.io/admin" + // valueExpression: '(has(claims.is_admin) && claims.is_admin) ? "true":""' + // This will result in: + // - if is_admin claim is present and true, extra attribute - acme.io/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"` } @@ -222,6 +257,7 @@ type ExtraMapping struct { // be valid HTTP Path characters as defined by RFC 3986. // key must be lowercase. // Required to be unique. + // Additionally, the key must not contain an equals sign ("="). // +required Key string `json:"key"` diff --git a/generated/1.26/crds/authentication.concierge.pinniped.dev_jwtauthenticators.yaml b/generated/1.26/crds/authentication.concierge.pinniped.dev_jwtauthenticators.yaml index 057c8d7b8..4fa9acc3e 100644 --- a/generated/1.26/crds/authentication.concierge.pinniped.dev_jwtauthenticators.yaml +++ b/generated/1.26/crds/authentication.concierge.pinniped.dev_jwtauthenticators.yaml @@ -78,7 +78,6 @@ spec: claim: description: |- claim is the name of a required claim. - Same as --oidc-required-claim flag. Only string claim keys are supported. Mutually exclusive with expression and message. type: string @@ -106,7 +105,6 @@ spec: requiredValue: description: |- requiredValue is the value of a required claim. - Same as --oidc-required-claim flag. 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. Mutually exclusive with expression and message. @@ -120,18 +118,44 @@ spec: properties: extra: description: |- - extra is similar to claimMappings.extra from Kubernetes AuthenticationConfiguration as documented in - https://kubernetes.io/docs/reference/access-authn-authz/authentication. However, note that the - Pinniped Concierge issues client certificates to users for the purpose of authenticating, and - the Kubernetes API server does not have any mechanism for transmitting auth extras via client - certificates. When configured, these extras will appear in client certificates issued by the - Pinniped Supervisor in the x509 Subject field as Organizational Units (OU). However, when this - client certificate is presented to Kubernetes for authentication, Kubernetes will ignore these - extras. This is probably only useful if you are using a custom authenticating proxy in front - of your Kubernetes API server which can translate these OUs into auth extras, as described by + extra is similar to claimMappings.extra from Kubernetes AuthenticationConfiguration + as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication. + + However, note that the Pinniped Concierge issues client certificates to users for the purpose + of authenticating, and the Kubernetes API server does not have any mechanism for transmitting + auth extras via client certificates. When configured, these extras will appear in client + certificates issued by the Pinniped Supervisor in the x509 Subject field as Organizational + Units (OU). However, when this client certificate is presented to Kubernetes for authentication, + Kubernetes will ignore these extras. This is probably only useful if you are using a custom + authenticating proxy in front of your Kubernetes API server which can translate these OUs into + auth extras, as described by https://kubernetes.io/docs/reference/access-authn-authz/authentication/#authenticating-proxy. This is an advanced configuration option. During an end-user login flow, each of these CEL expressions must evaluate to either a string or an array of strings, or else the user's login will fail. + + These keys must be a domain-prefixed path (such as "acme.io/foo") and must not contain an equals sign ("="). + + expression must produce a string or string array value. + If the value is empty, the extra mapping will not be present. + + Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ + + hard-coded extra key/value + - key: "acme.io/foo" + valueExpression: "'bar'" + This will result in an extra attribute - acme.io/foo: ["bar"] + + hard-coded key, value copying claim value + - key: "acme.io/foo" + valueExpression: "claims.some_claim" + This will result in an extra attribute - acme.io/foo: [value of some_claim] + + hard-coded key, value derived from claim value + - key: "acme.io/admin" + valueExpression: '(has(claims.is_admin) && claims.is_admin) ? "true":""' + This will result in: + - if is_admin claim is present and true, extra attribute - acme.io/admin: ["true"] + - if is_admin claim is present and false or is_admin claim is not present, no extra attribute will be added items: description: ExtraMapping provides the configuration for a single extra mapping. @@ -144,6 +168,7 @@ spec: be valid HTTP Path characters as defined by RFC 3986. key must be lowercase. Required to be unique. + Additionally, the key must not contain an equals sign ("="). type: string valueExpression: description: |- @@ -176,9 +201,14 @@ spec: groupsExpression: description: |- groupsExpression represents an expression which will be evaluated by CEL. - The expression's result will become the user's group memberships. + groupsExpression is similar to claimMappings.groups.expression from Kubernetes AuthenticationConfiguration + as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication. + This is an advanced configuration option. During an end-user login flow, each of these CEL expressions + must evaluate to one of the expected types without errors, or else the user's login will fail. + Additionally, mistakes in this configuration can cause the users to have unintended group memberships. + The expression must produce a string or string array value. "", [], and null values are treated as the group mapping not being present. @@ -204,9 +234,14 @@ spec: usernameExpression: description: |- usernameExpression represents an expression which will be evaluated by CEL. - The expression's result will become the user's username. + usernameExpression is similar to claimMappings.username.expression from Kubernetes AuthenticationConfiguration + as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication. + This is an advanced configuration option. During an end-user login flow, each of these CEL expressions + must evaluate to the expected type without errors, or else the user's login will fail. + Additionally, mistakes in this configuration can cause the users to have unintended usernames. + The expression must produce a non-empty string value. If the expression uses 'claims.email', then 'claims.email_verified' must be used in the expression or extra[*].valueExpression or claimValidationRules[*].expression. diff --git a/generated/1.27/README.adoc b/generated/1.27/README.adoc index ba630e361..c485b28f2 100644 --- a/generated/1.27/README.adoc +++ b/generated/1.27/README.adoc @@ -74,11 +74,9 @@ ClaimValidationRule provides the configuration for a single claim validation rul |=== | Field | Description | *`claim`* __string__ | claim is the name of a required claim. + -Same as --oidc-required-claim flag. + Only string claim keys are supported. + Mutually exclusive with expression and message. + | *`requiredValue`* __string__ | requiredValue is the value of a required claim. + -Same as --oidc-required-claim flag. + 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. + Mutually exclusive with expression and message. + @@ -119,6 +117,7 @@ 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 to be unique. + +Additionally, the key must not contain an equals sign ("="). + | *`valueExpression`* __string__ | 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. + @@ -244,9 +243,14 @@ unless usernameExpression is specified. + Mutually exclusive with usernameExpression. Use either username or usernameExpression to + determine the user's username from the JWT token. + | *`usernameExpression`* __string__ | usernameExpression represents an expression which will be evaluated by CEL. + - The expression's result will become the user's username. + +usernameExpression is similar to claimMappings.username.expression from Kubernetes AuthenticationConfiguration + +as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication. + +This is an advanced configuration option. During an end-user login flow, each of these CEL expressions + +must evaluate to the expected type without errors, or else the user's login will fail. + +Additionally, mistakes in this configuration can cause the users to have unintended usernames. + + The expression must produce a non-empty string value. + If the expression uses 'claims.email', then 'claims.email_verified' must be used in + the expression or extra[*].valueExpression or claimValidationRules[*].expression. + @@ -271,9 +275,14 @@ unless groupsExpression is specified. + Mutually exclusive with groupsExpression. Use either groups or groupsExpression to + determine the user's group membership from the JWT token. + | *`groupsExpression`* __string__ | groupsExpression represents an expression which will be evaluated by CEL. + - The expression's result will become the user's group memberships. + +groupsExpression is similar to claimMappings.groups.expression from Kubernetes AuthenticationConfiguration + +as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication. + +This is an advanced configuration option. During an end-user login flow, each of these CEL expressions + +must evaluate to one of the expected types without errors, or else the user's login will fail. + +Additionally, mistakes in this configuration can cause the users to have unintended group memberships. + + The expression must produce a string or string array value. + "", [], and null values are treated as the group mapping not being present. + @@ -286,18 +295,44 @@ Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ + Mutually exclusive with groups. Use either groups or groupsExpression to + determine the user's group membership from the JWT token. + -| *`extra`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-27-apis-concierge-authentication-v1alpha1-extramapping[$$ExtraMapping$$] array__ | extra is similar to claimMappings.extra from Kubernetes AuthenticationConfiguration as documented in + -https://kubernetes.io/docs/reference/access-authn-authz/authentication. However, note that the + -Pinniped Concierge issues client certificates to users for the purpose of authenticating, and + -the Kubernetes API server does not have any mechanism for transmitting auth extras via client + -certificates. When configured, these extras will appear in client certificates issued by the + -Pinniped Supervisor in the x509 Subject field as Organizational Units (OU). However, when this + -client certificate is presented to Kubernetes for authentication, Kubernetes will ignore these + -extras. This is probably only useful if you are using a custom authenticating proxy in front + -of your Kubernetes API server which can translate these OUs into auth extras, as described by + +| *`extra`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-27-apis-concierge-authentication-v1alpha1-extramapping[$$ExtraMapping$$] array__ | extra is similar to claimMappings.extra from Kubernetes AuthenticationConfiguration + +as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication. + + +However, note that the Pinniped Concierge issues client certificates to users for the purpose + +of authenticating, and the Kubernetes API server does not have any mechanism for transmitting + +auth extras via client certificates. When configured, these extras will appear in client + +certificates issued by the Pinniped Supervisor in the x509 Subject field as Organizational + +Units (OU). However, when this client certificate is presented to Kubernetes for authentication, + +Kubernetes will ignore these extras. This is probably only useful if you are using a custom + +authenticating proxy in front of your Kubernetes API server which can translate these OUs into + +auth extras, as described by + https://kubernetes.io/docs/reference/access-authn-authz/authentication/#authenticating-proxy. + This is an advanced configuration option. During an end-user login flow, each of these CEL expressions + must evaluate to either a string or an array of strings, or else the user's login will fail. + + +These keys must be a domain-prefixed path (such as "acme.io/foo") and must not contain an equals sign ("="). + + +expression must produce a string or string array value. + +If the value is empty, the extra mapping will not be present. + + +Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ + + +hard-coded extra key/value + +- key: "acme.io/foo" + +valueExpression: "'bar'" + +This will result in an extra attribute - acme.io/foo: ["bar"] + + +hard-coded key, value copying claim value + +- key: "acme.io/foo" + +valueExpression: "claims.some_claim" + +This will result in an extra attribute - acme.io/foo: [value of some_claim] + + +hard-coded key, value derived from claim value + +- key: "acme.io/admin" + +valueExpression: '(has(claims.is_admin) && claims.is_admin) ? "true":""' + +This will result in: + +- if is_admin claim is present and true, extra attribute - acme.io/admin: ["true"] + +- if is_admin claim is present and false or is_admin claim is not present, no extra attribute will be added + |=== diff --git a/generated/1.27/apis/concierge/authentication/v1alpha1/types_jwtauthenticator.go b/generated/1.27/apis/concierge/authentication/v1alpha1/types_jwtauthenticator.go index 8d416bcbf..6c782b5ce 100644 --- a/generated/1.27/apis/concierge/authentication/v1alpha1/types_jwtauthenticator.go +++ b/generated/1.27/apis/concierge/authentication/v1alpha1/types_jwtauthenticator.go @@ -77,14 +77,12 @@ type JWTAuthenticatorSpec struct { // ClaimValidationRule provides the configuration for a single claim validation rule. type ClaimValidationRule struct { // claim is the name of a required claim. - // Same as --oidc-required-claim flag. // Only string claim keys are supported. // Mutually exclusive with expression and message. // +optional Claim string `json:"claim,omitempty"` // requiredValue is the value of a required claim. - // Same as --oidc-required-claim flag. // 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. // Mutually exclusive with expression and message. @@ -147,9 +145,14 @@ type JWTTokenClaims struct { Username string `json:"username"` // usernameExpression represents an expression which will be evaluated by CEL. - // // The expression's result will become the user's username. // + // usernameExpression is similar to claimMappings.username.expression from Kubernetes AuthenticationConfiguration + // as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication. + // This is an advanced configuration option. During an end-user login flow, each of these CEL expressions + // must evaluate to the expected type without errors, or else the user's login will fail. + // Additionally, mistakes in this configuration can cause the users to have unintended usernames. + // // The expression must produce a non-empty string value. // If the expression uses 'claims.email', then 'claims.email_verified' must be used in // the expression or extra[*].valueExpression or claimValidationRules[*].expression. @@ -180,9 +183,14 @@ type JWTTokenClaims struct { Groups string `json:"groups"` // groupsExpression represents an expression which will be evaluated by CEL. - // // The expression's result will become the user's group memberships. // + // groupsExpression is similar to claimMappings.groups.expression from Kubernetes AuthenticationConfiguration + // as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication. + // This is an advanced configuration option. During an end-user login flow, each of these CEL expressions + // must evaluate to one of the expected types without errors, or else the user's login will fail. + // Additionally, mistakes in this configuration can cause the users to have unintended group memberships. + // // The expression must produce a string or string array value. // "", [], and null values are treated as the group mapping not being present. // @@ -198,18 +206,45 @@ type JWTTokenClaims struct { // +optional GroupsExpression string `json:"groupsExpression,omitempty"` - // extra is similar to claimMappings.extra from Kubernetes AuthenticationConfiguration as documented in - // https://kubernetes.io/docs/reference/access-authn-authz/authentication. However, note that the - // Pinniped Concierge issues client certificates to users for the purpose of authenticating, and - // the Kubernetes API server does not have any mechanism for transmitting auth extras via client - // certificates. When configured, these extras will appear in client certificates issued by the - // Pinniped Supervisor in the x509 Subject field as Organizational Units (OU). However, when this - // client certificate is presented to Kubernetes for authentication, Kubernetes will ignore these - // extras. This is probably only useful if you are using a custom authenticating proxy in front - // of your Kubernetes API server which can translate these OUs into auth extras, as described by + // extra is similar to claimMappings.extra from Kubernetes AuthenticationConfiguration + // as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication. + // + // However, note that the Pinniped Concierge issues client certificates to users for the purpose + // of authenticating, and the Kubernetes API server does not have any mechanism for transmitting + // auth extras via client certificates. When configured, these extras will appear in client + // certificates issued by the Pinniped Supervisor in the x509 Subject field as Organizational + // Units (OU). However, when this client certificate is presented to Kubernetes for authentication, + // Kubernetes will ignore these extras. This is probably only useful if you are using a custom + // authenticating proxy in front of your Kubernetes API server which can translate these OUs into + // auth extras, as described by // https://kubernetes.io/docs/reference/access-authn-authz/authentication/#authenticating-proxy. // This is an advanced configuration option. During an end-user login flow, each of these CEL expressions // must evaluate to either a string or an array of strings, or else the user's login will fail. + // + // These keys must be a domain-prefixed path (such as "acme.io/foo") and must not contain an equals sign ("="). + // + // expression must produce a string or string array value. + // If the value is empty, the extra mapping will not be present. + // + // Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ + // + // hard-coded extra key/value + // - key: "acme.io/foo" + // valueExpression: "'bar'" + // This will result in an extra attribute - acme.io/foo: ["bar"] + // + // hard-coded key, value copying claim value + // - key: "acme.io/foo" + // valueExpression: "claims.some_claim" + // This will result in an extra attribute - acme.io/foo: [value of some_claim] + // + // hard-coded key, value derived from claim value + // - key: "acme.io/admin" + // valueExpression: '(has(claims.is_admin) && claims.is_admin) ? "true":""' + // This will result in: + // - if is_admin claim is present and true, extra attribute - acme.io/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"` } @@ -222,6 +257,7 @@ type ExtraMapping struct { // be valid HTTP Path characters as defined by RFC 3986. // key must be lowercase. // Required to be unique. + // Additionally, the key must not contain an equals sign ("="). // +required Key string `json:"key"` diff --git a/generated/1.27/crds/authentication.concierge.pinniped.dev_jwtauthenticators.yaml b/generated/1.27/crds/authentication.concierge.pinniped.dev_jwtauthenticators.yaml index 057c8d7b8..4fa9acc3e 100644 --- a/generated/1.27/crds/authentication.concierge.pinniped.dev_jwtauthenticators.yaml +++ b/generated/1.27/crds/authentication.concierge.pinniped.dev_jwtauthenticators.yaml @@ -78,7 +78,6 @@ spec: claim: description: |- claim is the name of a required claim. - Same as --oidc-required-claim flag. Only string claim keys are supported. Mutually exclusive with expression and message. type: string @@ -106,7 +105,6 @@ spec: requiredValue: description: |- requiredValue is the value of a required claim. - Same as --oidc-required-claim flag. 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. Mutually exclusive with expression and message. @@ -120,18 +118,44 @@ spec: properties: extra: description: |- - extra is similar to claimMappings.extra from Kubernetes AuthenticationConfiguration as documented in - https://kubernetes.io/docs/reference/access-authn-authz/authentication. However, note that the - Pinniped Concierge issues client certificates to users for the purpose of authenticating, and - the Kubernetes API server does not have any mechanism for transmitting auth extras via client - certificates. When configured, these extras will appear in client certificates issued by the - Pinniped Supervisor in the x509 Subject field as Organizational Units (OU). However, when this - client certificate is presented to Kubernetes for authentication, Kubernetes will ignore these - extras. This is probably only useful if you are using a custom authenticating proxy in front - of your Kubernetes API server which can translate these OUs into auth extras, as described by + extra is similar to claimMappings.extra from Kubernetes AuthenticationConfiguration + as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication. + + However, note that the Pinniped Concierge issues client certificates to users for the purpose + of authenticating, and the Kubernetes API server does not have any mechanism for transmitting + auth extras via client certificates. When configured, these extras will appear in client + certificates issued by the Pinniped Supervisor in the x509 Subject field as Organizational + Units (OU). However, when this client certificate is presented to Kubernetes for authentication, + Kubernetes will ignore these extras. This is probably only useful if you are using a custom + authenticating proxy in front of your Kubernetes API server which can translate these OUs into + auth extras, as described by https://kubernetes.io/docs/reference/access-authn-authz/authentication/#authenticating-proxy. This is an advanced configuration option. During an end-user login flow, each of these CEL expressions must evaluate to either a string or an array of strings, or else the user's login will fail. + + These keys must be a domain-prefixed path (such as "acme.io/foo") and must not contain an equals sign ("="). + + expression must produce a string or string array value. + If the value is empty, the extra mapping will not be present. + + Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ + + hard-coded extra key/value + - key: "acme.io/foo" + valueExpression: "'bar'" + This will result in an extra attribute - acme.io/foo: ["bar"] + + hard-coded key, value copying claim value + - key: "acme.io/foo" + valueExpression: "claims.some_claim" + This will result in an extra attribute - acme.io/foo: [value of some_claim] + + hard-coded key, value derived from claim value + - key: "acme.io/admin" + valueExpression: '(has(claims.is_admin) && claims.is_admin) ? "true":""' + This will result in: + - if is_admin claim is present and true, extra attribute - acme.io/admin: ["true"] + - if is_admin claim is present and false or is_admin claim is not present, no extra attribute will be added items: description: ExtraMapping provides the configuration for a single extra mapping. @@ -144,6 +168,7 @@ spec: be valid HTTP Path characters as defined by RFC 3986. key must be lowercase. Required to be unique. + Additionally, the key must not contain an equals sign ("="). type: string valueExpression: description: |- @@ -176,9 +201,14 @@ spec: groupsExpression: description: |- groupsExpression represents an expression which will be evaluated by CEL. - The expression's result will become the user's group memberships. + groupsExpression is similar to claimMappings.groups.expression from Kubernetes AuthenticationConfiguration + as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication. + This is an advanced configuration option. During an end-user login flow, each of these CEL expressions + must evaluate to one of the expected types without errors, or else the user's login will fail. + Additionally, mistakes in this configuration can cause the users to have unintended group memberships. + The expression must produce a string or string array value. "", [], and null values are treated as the group mapping not being present. @@ -204,9 +234,14 @@ spec: usernameExpression: description: |- usernameExpression represents an expression which will be evaluated by CEL. - The expression's result will become the user's username. + usernameExpression is similar to claimMappings.username.expression from Kubernetes AuthenticationConfiguration + as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication. + This is an advanced configuration option. During an end-user login flow, each of these CEL expressions + must evaluate to the expected type without errors, or else the user's login will fail. + Additionally, mistakes in this configuration can cause the users to have unintended usernames. + The expression must produce a non-empty string value. If the expression uses 'claims.email', then 'claims.email_verified' must be used in the expression or extra[*].valueExpression or claimValidationRules[*].expression. diff --git a/generated/1.28/README.adoc b/generated/1.28/README.adoc index 42d652eeb..92e119a98 100644 --- a/generated/1.28/README.adoc +++ b/generated/1.28/README.adoc @@ -74,11 +74,9 @@ ClaimValidationRule provides the configuration for a single claim validation rul |=== | Field | Description | *`claim`* __string__ | claim is the name of a required claim. + -Same as --oidc-required-claim flag. + Only string claim keys are supported. + Mutually exclusive with expression and message. + | *`requiredValue`* __string__ | requiredValue is the value of a required claim. + -Same as --oidc-required-claim flag. + 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. + Mutually exclusive with expression and message. + @@ -119,6 +117,7 @@ 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 to be unique. + +Additionally, the key must not contain an equals sign ("="). + | *`valueExpression`* __string__ | 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. + @@ -244,9 +243,14 @@ unless usernameExpression is specified. + Mutually exclusive with usernameExpression. Use either username or usernameExpression to + determine the user's username from the JWT token. + | *`usernameExpression`* __string__ | usernameExpression represents an expression which will be evaluated by CEL. + - The expression's result will become the user's username. + +usernameExpression is similar to claimMappings.username.expression from Kubernetes AuthenticationConfiguration + +as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication. + +This is an advanced configuration option. During an end-user login flow, each of these CEL expressions + +must evaluate to the expected type without errors, or else the user's login will fail. + +Additionally, mistakes in this configuration can cause the users to have unintended usernames. + + The expression must produce a non-empty string value. + If the expression uses 'claims.email', then 'claims.email_verified' must be used in + the expression or extra[*].valueExpression or claimValidationRules[*].expression. + @@ -271,9 +275,14 @@ unless groupsExpression is specified. + Mutually exclusive with groupsExpression. Use either groups or groupsExpression to + determine the user's group membership from the JWT token. + | *`groupsExpression`* __string__ | groupsExpression represents an expression which will be evaluated by CEL. + - The expression's result will become the user's group memberships. + +groupsExpression is similar to claimMappings.groups.expression from Kubernetes AuthenticationConfiguration + +as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication. + +This is an advanced configuration option. During an end-user login flow, each of these CEL expressions + +must evaluate to one of the expected types without errors, or else the user's login will fail. + +Additionally, mistakes in this configuration can cause the users to have unintended group memberships. + + The expression must produce a string or string array value. + "", [], and null values are treated as the group mapping not being present. + @@ -286,18 +295,44 @@ Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ + Mutually exclusive with groups. Use either groups or groupsExpression to + determine the user's group membership from the JWT token. + -| *`extra`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-28-apis-concierge-authentication-v1alpha1-extramapping[$$ExtraMapping$$] array__ | extra is similar to claimMappings.extra from Kubernetes AuthenticationConfiguration as documented in + -https://kubernetes.io/docs/reference/access-authn-authz/authentication. However, note that the + -Pinniped Concierge issues client certificates to users for the purpose of authenticating, and + -the Kubernetes API server does not have any mechanism for transmitting auth extras via client + -certificates. When configured, these extras will appear in client certificates issued by the + -Pinniped Supervisor in the x509 Subject field as Organizational Units (OU). However, when this + -client certificate is presented to Kubernetes for authentication, Kubernetes will ignore these + -extras. This is probably only useful if you are using a custom authenticating proxy in front + -of your Kubernetes API server which can translate these OUs into auth extras, as described by + +| *`extra`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-28-apis-concierge-authentication-v1alpha1-extramapping[$$ExtraMapping$$] array__ | extra is similar to claimMappings.extra from Kubernetes AuthenticationConfiguration + +as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication. + + +However, note that the Pinniped Concierge issues client certificates to users for the purpose + +of authenticating, and the Kubernetes API server does not have any mechanism for transmitting + +auth extras via client certificates. When configured, these extras will appear in client + +certificates issued by the Pinniped Supervisor in the x509 Subject field as Organizational + +Units (OU). However, when this client certificate is presented to Kubernetes for authentication, + +Kubernetes will ignore these extras. This is probably only useful if you are using a custom + +authenticating proxy in front of your Kubernetes API server which can translate these OUs into + +auth extras, as described by + https://kubernetes.io/docs/reference/access-authn-authz/authentication/#authenticating-proxy. + This is an advanced configuration option. During an end-user login flow, each of these CEL expressions + must evaluate to either a string or an array of strings, or else the user's login will fail. + + +These keys must be a domain-prefixed path (such as "acme.io/foo") and must not contain an equals sign ("="). + + +expression must produce a string or string array value. + +If the value is empty, the extra mapping will not be present. + + +Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ + + +hard-coded extra key/value + +- key: "acme.io/foo" + +valueExpression: "'bar'" + +This will result in an extra attribute - acme.io/foo: ["bar"] + + +hard-coded key, value copying claim value + +- key: "acme.io/foo" + +valueExpression: "claims.some_claim" + +This will result in an extra attribute - acme.io/foo: [value of some_claim] + + +hard-coded key, value derived from claim value + +- key: "acme.io/admin" + +valueExpression: '(has(claims.is_admin) && claims.is_admin) ? "true":""' + +This will result in: + +- if is_admin claim is present and true, extra attribute - acme.io/admin: ["true"] + +- if is_admin claim is present and false or is_admin claim is not present, no extra attribute will be added + |=== diff --git a/generated/1.28/apis/concierge/authentication/v1alpha1/types_jwtauthenticator.go b/generated/1.28/apis/concierge/authentication/v1alpha1/types_jwtauthenticator.go index 8d416bcbf..6c782b5ce 100644 --- a/generated/1.28/apis/concierge/authentication/v1alpha1/types_jwtauthenticator.go +++ b/generated/1.28/apis/concierge/authentication/v1alpha1/types_jwtauthenticator.go @@ -77,14 +77,12 @@ type JWTAuthenticatorSpec struct { // ClaimValidationRule provides the configuration for a single claim validation rule. type ClaimValidationRule struct { // claim is the name of a required claim. - // Same as --oidc-required-claim flag. // Only string claim keys are supported. // Mutually exclusive with expression and message. // +optional Claim string `json:"claim,omitempty"` // requiredValue is the value of a required claim. - // Same as --oidc-required-claim flag. // 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. // Mutually exclusive with expression and message. @@ -147,9 +145,14 @@ type JWTTokenClaims struct { Username string `json:"username"` // usernameExpression represents an expression which will be evaluated by CEL. - // // The expression's result will become the user's username. // + // usernameExpression is similar to claimMappings.username.expression from Kubernetes AuthenticationConfiguration + // as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication. + // This is an advanced configuration option. During an end-user login flow, each of these CEL expressions + // must evaluate to the expected type without errors, or else the user's login will fail. + // Additionally, mistakes in this configuration can cause the users to have unintended usernames. + // // The expression must produce a non-empty string value. // If the expression uses 'claims.email', then 'claims.email_verified' must be used in // the expression or extra[*].valueExpression or claimValidationRules[*].expression. @@ -180,9 +183,14 @@ type JWTTokenClaims struct { Groups string `json:"groups"` // groupsExpression represents an expression which will be evaluated by CEL. - // // The expression's result will become the user's group memberships. // + // groupsExpression is similar to claimMappings.groups.expression from Kubernetes AuthenticationConfiguration + // as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication. + // This is an advanced configuration option. During an end-user login flow, each of these CEL expressions + // must evaluate to one of the expected types without errors, or else the user's login will fail. + // Additionally, mistakes in this configuration can cause the users to have unintended group memberships. + // // The expression must produce a string or string array value. // "", [], and null values are treated as the group mapping not being present. // @@ -198,18 +206,45 @@ type JWTTokenClaims struct { // +optional GroupsExpression string `json:"groupsExpression,omitempty"` - // extra is similar to claimMappings.extra from Kubernetes AuthenticationConfiguration as documented in - // https://kubernetes.io/docs/reference/access-authn-authz/authentication. However, note that the - // Pinniped Concierge issues client certificates to users for the purpose of authenticating, and - // the Kubernetes API server does not have any mechanism for transmitting auth extras via client - // certificates. When configured, these extras will appear in client certificates issued by the - // Pinniped Supervisor in the x509 Subject field as Organizational Units (OU). However, when this - // client certificate is presented to Kubernetes for authentication, Kubernetes will ignore these - // extras. This is probably only useful if you are using a custom authenticating proxy in front - // of your Kubernetes API server which can translate these OUs into auth extras, as described by + // extra is similar to claimMappings.extra from Kubernetes AuthenticationConfiguration + // as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication. + // + // However, note that the Pinniped Concierge issues client certificates to users for the purpose + // of authenticating, and the Kubernetes API server does not have any mechanism for transmitting + // auth extras via client certificates. When configured, these extras will appear in client + // certificates issued by the Pinniped Supervisor in the x509 Subject field as Organizational + // Units (OU). However, when this client certificate is presented to Kubernetes for authentication, + // Kubernetes will ignore these extras. This is probably only useful if you are using a custom + // authenticating proxy in front of your Kubernetes API server which can translate these OUs into + // auth extras, as described by // https://kubernetes.io/docs/reference/access-authn-authz/authentication/#authenticating-proxy. // This is an advanced configuration option. During an end-user login flow, each of these CEL expressions // must evaluate to either a string or an array of strings, or else the user's login will fail. + // + // These keys must be a domain-prefixed path (such as "acme.io/foo") and must not contain an equals sign ("="). + // + // expression must produce a string or string array value. + // If the value is empty, the extra mapping will not be present. + // + // Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ + // + // hard-coded extra key/value + // - key: "acme.io/foo" + // valueExpression: "'bar'" + // This will result in an extra attribute - acme.io/foo: ["bar"] + // + // hard-coded key, value copying claim value + // - key: "acme.io/foo" + // valueExpression: "claims.some_claim" + // This will result in an extra attribute - acme.io/foo: [value of some_claim] + // + // hard-coded key, value derived from claim value + // - key: "acme.io/admin" + // valueExpression: '(has(claims.is_admin) && claims.is_admin) ? "true":""' + // This will result in: + // - if is_admin claim is present and true, extra attribute - acme.io/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"` } @@ -222,6 +257,7 @@ type ExtraMapping struct { // be valid HTTP Path characters as defined by RFC 3986. // key must be lowercase. // Required to be unique. + // Additionally, the key must not contain an equals sign ("="). // +required Key string `json:"key"` diff --git a/generated/1.28/crds/authentication.concierge.pinniped.dev_jwtauthenticators.yaml b/generated/1.28/crds/authentication.concierge.pinniped.dev_jwtauthenticators.yaml index 057c8d7b8..4fa9acc3e 100644 --- a/generated/1.28/crds/authentication.concierge.pinniped.dev_jwtauthenticators.yaml +++ b/generated/1.28/crds/authentication.concierge.pinniped.dev_jwtauthenticators.yaml @@ -78,7 +78,6 @@ spec: claim: description: |- claim is the name of a required claim. - Same as --oidc-required-claim flag. Only string claim keys are supported. Mutually exclusive with expression and message. type: string @@ -106,7 +105,6 @@ spec: requiredValue: description: |- requiredValue is the value of a required claim. - Same as --oidc-required-claim flag. 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. Mutually exclusive with expression and message. @@ -120,18 +118,44 @@ spec: properties: extra: description: |- - extra is similar to claimMappings.extra from Kubernetes AuthenticationConfiguration as documented in - https://kubernetes.io/docs/reference/access-authn-authz/authentication. However, note that the - Pinniped Concierge issues client certificates to users for the purpose of authenticating, and - the Kubernetes API server does not have any mechanism for transmitting auth extras via client - certificates. When configured, these extras will appear in client certificates issued by the - Pinniped Supervisor in the x509 Subject field as Organizational Units (OU). However, when this - client certificate is presented to Kubernetes for authentication, Kubernetes will ignore these - extras. This is probably only useful if you are using a custom authenticating proxy in front - of your Kubernetes API server which can translate these OUs into auth extras, as described by + extra is similar to claimMappings.extra from Kubernetes AuthenticationConfiguration + as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication. + + However, note that the Pinniped Concierge issues client certificates to users for the purpose + of authenticating, and the Kubernetes API server does not have any mechanism for transmitting + auth extras via client certificates. When configured, these extras will appear in client + certificates issued by the Pinniped Supervisor in the x509 Subject field as Organizational + Units (OU). However, when this client certificate is presented to Kubernetes for authentication, + Kubernetes will ignore these extras. This is probably only useful if you are using a custom + authenticating proxy in front of your Kubernetes API server which can translate these OUs into + auth extras, as described by https://kubernetes.io/docs/reference/access-authn-authz/authentication/#authenticating-proxy. This is an advanced configuration option. During an end-user login flow, each of these CEL expressions must evaluate to either a string or an array of strings, or else the user's login will fail. + + These keys must be a domain-prefixed path (such as "acme.io/foo") and must not contain an equals sign ("="). + + expression must produce a string or string array value. + If the value is empty, the extra mapping will not be present. + + Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ + + hard-coded extra key/value + - key: "acme.io/foo" + valueExpression: "'bar'" + This will result in an extra attribute - acme.io/foo: ["bar"] + + hard-coded key, value copying claim value + - key: "acme.io/foo" + valueExpression: "claims.some_claim" + This will result in an extra attribute - acme.io/foo: [value of some_claim] + + hard-coded key, value derived from claim value + - key: "acme.io/admin" + valueExpression: '(has(claims.is_admin) && claims.is_admin) ? "true":""' + This will result in: + - if is_admin claim is present and true, extra attribute - acme.io/admin: ["true"] + - if is_admin claim is present and false or is_admin claim is not present, no extra attribute will be added items: description: ExtraMapping provides the configuration for a single extra mapping. @@ -144,6 +168,7 @@ spec: be valid HTTP Path characters as defined by RFC 3986. key must be lowercase. Required to be unique. + Additionally, the key must not contain an equals sign ("="). type: string valueExpression: description: |- @@ -176,9 +201,14 @@ spec: groupsExpression: description: |- groupsExpression represents an expression which will be evaluated by CEL. - The expression's result will become the user's group memberships. + groupsExpression is similar to claimMappings.groups.expression from Kubernetes AuthenticationConfiguration + as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication. + This is an advanced configuration option. During an end-user login flow, each of these CEL expressions + must evaluate to one of the expected types without errors, or else the user's login will fail. + Additionally, mistakes in this configuration can cause the users to have unintended group memberships. + The expression must produce a string or string array value. "", [], and null values are treated as the group mapping not being present. @@ -204,9 +234,14 @@ spec: usernameExpression: description: |- usernameExpression represents an expression which will be evaluated by CEL. - The expression's result will become the user's username. + usernameExpression is similar to claimMappings.username.expression from Kubernetes AuthenticationConfiguration + as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication. + This is an advanced configuration option. During an end-user login flow, each of these CEL expressions + must evaluate to the expected type without errors, or else the user's login will fail. + Additionally, mistakes in this configuration can cause the users to have unintended usernames. + The expression must produce a non-empty string value. If the expression uses 'claims.email', then 'claims.email_verified' must be used in the expression or extra[*].valueExpression or claimValidationRules[*].expression. diff --git a/generated/1.29/README.adoc b/generated/1.29/README.adoc index 2b5102cf5..ddb30051b 100644 --- a/generated/1.29/README.adoc +++ b/generated/1.29/README.adoc @@ -74,11 +74,9 @@ ClaimValidationRule provides the configuration for a single claim validation rul |=== | Field | Description | *`claim`* __string__ | claim is the name of a required claim. + -Same as --oidc-required-claim flag. + Only string claim keys are supported. + Mutually exclusive with expression and message. + | *`requiredValue`* __string__ | requiredValue is the value of a required claim. + -Same as --oidc-required-claim flag. + 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. + Mutually exclusive with expression and message. + @@ -119,6 +117,7 @@ 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 to be unique. + +Additionally, the key must not contain an equals sign ("="). + | *`valueExpression`* __string__ | 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. + @@ -244,9 +243,14 @@ unless usernameExpression is specified. + Mutually exclusive with usernameExpression. Use either username or usernameExpression to + determine the user's username from the JWT token. + | *`usernameExpression`* __string__ | usernameExpression represents an expression which will be evaluated by CEL. + - The expression's result will become the user's username. + +usernameExpression is similar to claimMappings.username.expression from Kubernetes AuthenticationConfiguration + +as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication. + +This is an advanced configuration option. During an end-user login flow, each of these CEL expressions + +must evaluate to the expected type without errors, or else the user's login will fail. + +Additionally, mistakes in this configuration can cause the users to have unintended usernames. + + The expression must produce a non-empty string value. + If the expression uses 'claims.email', then 'claims.email_verified' must be used in + the expression or extra[*].valueExpression or claimValidationRules[*].expression. + @@ -271,9 +275,14 @@ unless groupsExpression is specified. + Mutually exclusive with groupsExpression. Use either groups or groupsExpression to + determine the user's group membership from the JWT token. + | *`groupsExpression`* __string__ | groupsExpression represents an expression which will be evaluated by CEL. + - The expression's result will become the user's group memberships. + +groupsExpression is similar to claimMappings.groups.expression from Kubernetes AuthenticationConfiguration + +as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication. + +This is an advanced configuration option. During an end-user login flow, each of these CEL expressions + +must evaluate to one of the expected types without errors, or else the user's login will fail. + +Additionally, mistakes in this configuration can cause the users to have unintended group memberships. + + The expression must produce a string or string array value. + "", [], and null values are treated as the group mapping not being present. + @@ -286,18 +295,44 @@ Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ + Mutually exclusive with groups. Use either groups or groupsExpression to + determine the user's group membership from the JWT token. + -| *`extra`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-29-apis-concierge-authentication-v1alpha1-extramapping[$$ExtraMapping$$] array__ | extra is similar to claimMappings.extra from Kubernetes AuthenticationConfiguration as documented in + -https://kubernetes.io/docs/reference/access-authn-authz/authentication. However, note that the + -Pinniped Concierge issues client certificates to users for the purpose of authenticating, and + -the Kubernetes API server does not have any mechanism for transmitting auth extras via client + -certificates. When configured, these extras will appear in client certificates issued by the + -Pinniped Supervisor in the x509 Subject field as Organizational Units (OU). However, when this + -client certificate is presented to Kubernetes for authentication, Kubernetes will ignore these + -extras. This is probably only useful if you are using a custom authenticating proxy in front + -of your Kubernetes API server which can translate these OUs into auth extras, as described by + +| *`extra`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-29-apis-concierge-authentication-v1alpha1-extramapping[$$ExtraMapping$$] array__ | extra is similar to claimMappings.extra from Kubernetes AuthenticationConfiguration + +as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication. + + +However, note that the Pinniped Concierge issues client certificates to users for the purpose + +of authenticating, and the Kubernetes API server does not have any mechanism for transmitting + +auth extras via client certificates. When configured, these extras will appear in client + +certificates issued by the Pinniped Supervisor in the x509 Subject field as Organizational + +Units (OU). However, when this client certificate is presented to Kubernetes for authentication, + +Kubernetes will ignore these extras. This is probably only useful if you are using a custom + +authenticating proxy in front of your Kubernetes API server which can translate these OUs into + +auth extras, as described by + https://kubernetes.io/docs/reference/access-authn-authz/authentication/#authenticating-proxy. + This is an advanced configuration option. During an end-user login flow, each of these CEL expressions + must evaluate to either a string or an array of strings, or else the user's login will fail. + + +These keys must be a domain-prefixed path (such as "acme.io/foo") and must not contain an equals sign ("="). + + +expression must produce a string or string array value. + +If the value is empty, the extra mapping will not be present. + + +Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ + + +hard-coded extra key/value + +- key: "acme.io/foo" + +valueExpression: "'bar'" + +This will result in an extra attribute - acme.io/foo: ["bar"] + + +hard-coded key, value copying claim value + +- key: "acme.io/foo" + +valueExpression: "claims.some_claim" + +This will result in an extra attribute - acme.io/foo: [value of some_claim] + + +hard-coded key, value derived from claim value + +- key: "acme.io/admin" + +valueExpression: '(has(claims.is_admin) && claims.is_admin) ? "true":""' + +This will result in: + +- if is_admin claim is present and true, extra attribute - acme.io/admin: ["true"] + +- if is_admin claim is present and false or is_admin claim is not present, no extra attribute will be added + |=== diff --git a/generated/1.29/apis/concierge/authentication/v1alpha1/types_jwtauthenticator.go b/generated/1.29/apis/concierge/authentication/v1alpha1/types_jwtauthenticator.go index 8d416bcbf..6c782b5ce 100644 --- a/generated/1.29/apis/concierge/authentication/v1alpha1/types_jwtauthenticator.go +++ b/generated/1.29/apis/concierge/authentication/v1alpha1/types_jwtauthenticator.go @@ -77,14 +77,12 @@ type JWTAuthenticatorSpec struct { // ClaimValidationRule provides the configuration for a single claim validation rule. type ClaimValidationRule struct { // claim is the name of a required claim. - // Same as --oidc-required-claim flag. // Only string claim keys are supported. // Mutually exclusive with expression and message. // +optional Claim string `json:"claim,omitempty"` // requiredValue is the value of a required claim. - // Same as --oidc-required-claim flag. // 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. // Mutually exclusive with expression and message. @@ -147,9 +145,14 @@ type JWTTokenClaims struct { Username string `json:"username"` // usernameExpression represents an expression which will be evaluated by CEL. - // // The expression's result will become the user's username. // + // usernameExpression is similar to claimMappings.username.expression from Kubernetes AuthenticationConfiguration + // as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication. + // This is an advanced configuration option. During an end-user login flow, each of these CEL expressions + // must evaluate to the expected type without errors, or else the user's login will fail. + // Additionally, mistakes in this configuration can cause the users to have unintended usernames. + // // The expression must produce a non-empty string value. // If the expression uses 'claims.email', then 'claims.email_verified' must be used in // the expression or extra[*].valueExpression or claimValidationRules[*].expression. @@ -180,9 +183,14 @@ type JWTTokenClaims struct { Groups string `json:"groups"` // groupsExpression represents an expression which will be evaluated by CEL. - // // The expression's result will become the user's group memberships. // + // groupsExpression is similar to claimMappings.groups.expression from Kubernetes AuthenticationConfiguration + // as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication. + // This is an advanced configuration option. During an end-user login flow, each of these CEL expressions + // must evaluate to one of the expected types without errors, or else the user's login will fail. + // Additionally, mistakes in this configuration can cause the users to have unintended group memberships. + // // The expression must produce a string or string array value. // "", [], and null values are treated as the group mapping not being present. // @@ -198,18 +206,45 @@ type JWTTokenClaims struct { // +optional GroupsExpression string `json:"groupsExpression,omitempty"` - // extra is similar to claimMappings.extra from Kubernetes AuthenticationConfiguration as documented in - // https://kubernetes.io/docs/reference/access-authn-authz/authentication. However, note that the - // Pinniped Concierge issues client certificates to users for the purpose of authenticating, and - // the Kubernetes API server does not have any mechanism for transmitting auth extras via client - // certificates. When configured, these extras will appear in client certificates issued by the - // Pinniped Supervisor in the x509 Subject field as Organizational Units (OU). However, when this - // client certificate is presented to Kubernetes for authentication, Kubernetes will ignore these - // extras. This is probably only useful if you are using a custom authenticating proxy in front - // of your Kubernetes API server which can translate these OUs into auth extras, as described by + // extra is similar to claimMappings.extra from Kubernetes AuthenticationConfiguration + // as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication. + // + // However, note that the Pinniped Concierge issues client certificates to users for the purpose + // of authenticating, and the Kubernetes API server does not have any mechanism for transmitting + // auth extras via client certificates. When configured, these extras will appear in client + // certificates issued by the Pinniped Supervisor in the x509 Subject field as Organizational + // Units (OU). However, when this client certificate is presented to Kubernetes for authentication, + // Kubernetes will ignore these extras. This is probably only useful if you are using a custom + // authenticating proxy in front of your Kubernetes API server which can translate these OUs into + // auth extras, as described by // https://kubernetes.io/docs/reference/access-authn-authz/authentication/#authenticating-proxy. // This is an advanced configuration option. During an end-user login flow, each of these CEL expressions // must evaluate to either a string or an array of strings, or else the user's login will fail. + // + // These keys must be a domain-prefixed path (such as "acme.io/foo") and must not contain an equals sign ("="). + // + // expression must produce a string or string array value. + // If the value is empty, the extra mapping will not be present. + // + // Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ + // + // hard-coded extra key/value + // - key: "acme.io/foo" + // valueExpression: "'bar'" + // This will result in an extra attribute - acme.io/foo: ["bar"] + // + // hard-coded key, value copying claim value + // - key: "acme.io/foo" + // valueExpression: "claims.some_claim" + // This will result in an extra attribute - acme.io/foo: [value of some_claim] + // + // hard-coded key, value derived from claim value + // - key: "acme.io/admin" + // valueExpression: '(has(claims.is_admin) && claims.is_admin) ? "true":""' + // This will result in: + // - if is_admin claim is present and true, extra attribute - acme.io/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"` } @@ -222,6 +257,7 @@ type ExtraMapping struct { // be valid HTTP Path characters as defined by RFC 3986. // key must be lowercase. // Required to be unique. + // Additionally, the key must not contain an equals sign ("="). // +required Key string `json:"key"` diff --git a/generated/1.29/crds/authentication.concierge.pinniped.dev_jwtauthenticators.yaml b/generated/1.29/crds/authentication.concierge.pinniped.dev_jwtauthenticators.yaml index 057c8d7b8..4fa9acc3e 100644 --- a/generated/1.29/crds/authentication.concierge.pinniped.dev_jwtauthenticators.yaml +++ b/generated/1.29/crds/authentication.concierge.pinniped.dev_jwtauthenticators.yaml @@ -78,7 +78,6 @@ spec: claim: description: |- claim is the name of a required claim. - Same as --oidc-required-claim flag. Only string claim keys are supported. Mutually exclusive with expression and message. type: string @@ -106,7 +105,6 @@ spec: requiredValue: description: |- requiredValue is the value of a required claim. - Same as --oidc-required-claim flag. 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. Mutually exclusive with expression and message. @@ -120,18 +118,44 @@ spec: properties: extra: description: |- - extra is similar to claimMappings.extra from Kubernetes AuthenticationConfiguration as documented in - https://kubernetes.io/docs/reference/access-authn-authz/authentication. However, note that the - Pinniped Concierge issues client certificates to users for the purpose of authenticating, and - the Kubernetes API server does not have any mechanism for transmitting auth extras via client - certificates. When configured, these extras will appear in client certificates issued by the - Pinniped Supervisor in the x509 Subject field as Organizational Units (OU). However, when this - client certificate is presented to Kubernetes for authentication, Kubernetes will ignore these - extras. This is probably only useful if you are using a custom authenticating proxy in front - of your Kubernetes API server which can translate these OUs into auth extras, as described by + extra is similar to claimMappings.extra from Kubernetes AuthenticationConfiguration + as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication. + + However, note that the Pinniped Concierge issues client certificates to users for the purpose + of authenticating, and the Kubernetes API server does not have any mechanism for transmitting + auth extras via client certificates. When configured, these extras will appear in client + certificates issued by the Pinniped Supervisor in the x509 Subject field as Organizational + Units (OU). However, when this client certificate is presented to Kubernetes for authentication, + Kubernetes will ignore these extras. This is probably only useful if you are using a custom + authenticating proxy in front of your Kubernetes API server which can translate these OUs into + auth extras, as described by https://kubernetes.io/docs/reference/access-authn-authz/authentication/#authenticating-proxy. This is an advanced configuration option. During an end-user login flow, each of these CEL expressions must evaluate to either a string or an array of strings, or else the user's login will fail. + + These keys must be a domain-prefixed path (such as "acme.io/foo") and must not contain an equals sign ("="). + + expression must produce a string or string array value. + If the value is empty, the extra mapping will not be present. + + Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ + + hard-coded extra key/value + - key: "acme.io/foo" + valueExpression: "'bar'" + This will result in an extra attribute - acme.io/foo: ["bar"] + + hard-coded key, value copying claim value + - key: "acme.io/foo" + valueExpression: "claims.some_claim" + This will result in an extra attribute - acme.io/foo: [value of some_claim] + + hard-coded key, value derived from claim value + - key: "acme.io/admin" + valueExpression: '(has(claims.is_admin) && claims.is_admin) ? "true":""' + This will result in: + - if is_admin claim is present and true, extra attribute - acme.io/admin: ["true"] + - if is_admin claim is present and false or is_admin claim is not present, no extra attribute will be added items: description: ExtraMapping provides the configuration for a single extra mapping. @@ -144,6 +168,7 @@ spec: be valid HTTP Path characters as defined by RFC 3986. key must be lowercase. Required to be unique. + Additionally, the key must not contain an equals sign ("="). type: string valueExpression: description: |- @@ -176,9 +201,14 @@ spec: groupsExpression: description: |- groupsExpression represents an expression which will be evaluated by CEL. - The expression's result will become the user's group memberships. + groupsExpression is similar to claimMappings.groups.expression from Kubernetes AuthenticationConfiguration + as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication. + This is an advanced configuration option. During an end-user login flow, each of these CEL expressions + must evaluate to one of the expected types without errors, or else the user's login will fail. + Additionally, mistakes in this configuration can cause the users to have unintended group memberships. + The expression must produce a string or string array value. "", [], and null values are treated as the group mapping not being present. @@ -204,9 +234,14 @@ spec: usernameExpression: description: |- usernameExpression represents an expression which will be evaluated by CEL. - The expression's result will become the user's username. + usernameExpression is similar to claimMappings.username.expression from Kubernetes AuthenticationConfiguration + as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication. + This is an advanced configuration option. During an end-user login flow, each of these CEL expressions + must evaluate to the expected type without errors, or else the user's login will fail. + Additionally, mistakes in this configuration can cause the users to have unintended usernames. + The expression must produce a non-empty string value. If the expression uses 'claims.email', then 'claims.email_verified' must be used in the expression or extra[*].valueExpression or claimValidationRules[*].expression. diff --git a/generated/1.30/README.adoc b/generated/1.30/README.adoc index 3c64b7b2d..171e02987 100644 --- a/generated/1.30/README.adoc +++ b/generated/1.30/README.adoc @@ -74,11 +74,9 @@ ClaimValidationRule provides the configuration for a single claim validation rul |=== | Field | Description | *`claim`* __string__ | claim is the name of a required claim. + -Same as --oidc-required-claim flag. + Only string claim keys are supported. + Mutually exclusive with expression and message. + | *`requiredValue`* __string__ | requiredValue is the value of a required claim. + -Same as --oidc-required-claim flag. + 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. + Mutually exclusive with expression and message. + @@ -119,6 +117,7 @@ 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 to be unique. + +Additionally, the key must not contain an equals sign ("="). + | *`valueExpression`* __string__ | 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. + @@ -244,9 +243,14 @@ unless usernameExpression is specified. + Mutually exclusive with usernameExpression. Use either username or usernameExpression to + determine the user's username from the JWT token. + | *`usernameExpression`* __string__ | usernameExpression represents an expression which will be evaluated by CEL. + - The expression's result will become the user's username. + +usernameExpression is similar to claimMappings.username.expression from Kubernetes AuthenticationConfiguration + +as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication. + +This is an advanced configuration option. During an end-user login flow, each of these CEL expressions + +must evaluate to the expected type without errors, or else the user's login will fail. + +Additionally, mistakes in this configuration can cause the users to have unintended usernames. + + The expression must produce a non-empty string value. + If the expression uses 'claims.email', then 'claims.email_verified' must be used in + the expression or extra[*].valueExpression or claimValidationRules[*].expression. + @@ -271,9 +275,14 @@ unless groupsExpression is specified. + Mutually exclusive with groupsExpression. Use either groups or groupsExpression to + determine the user's group membership from the JWT token. + | *`groupsExpression`* __string__ | groupsExpression represents an expression which will be evaluated by CEL. + - The expression's result will become the user's group memberships. + +groupsExpression is similar to claimMappings.groups.expression from Kubernetes AuthenticationConfiguration + +as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication. + +This is an advanced configuration option. During an end-user login flow, each of these CEL expressions + +must evaluate to one of the expected types without errors, or else the user's login will fail. + +Additionally, mistakes in this configuration can cause the users to have unintended group memberships. + + The expression must produce a string or string array value. + "", [], and null values are treated as the group mapping not being present. + @@ -286,18 +295,44 @@ Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ + Mutually exclusive with groups. Use either groups or groupsExpression to + determine the user's group membership from the JWT token. + -| *`extra`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-30-apis-concierge-authentication-v1alpha1-extramapping[$$ExtraMapping$$] array__ | extra is similar to claimMappings.extra from Kubernetes AuthenticationConfiguration as documented in + -https://kubernetes.io/docs/reference/access-authn-authz/authentication. However, note that the + -Pinniped Concierge issues client certificates to users for the purpose of authenticating, and + -the Kubernetes API server does not have any mechanism for transmitting auth extras via client + -certificates. When configured, these extras will appear in client certificates issued by the + -Pinniped Supervisor in the x509 Subject field as Organizational Units (OU). However, when this + -client certificate is presented to Kubernetes for authentication, Kubernetes will ignore these + -extras. This is probably only useful if you are using a custom authenticating proxy in front + -of your Kubernetes API server which can translate these OUs into auth extras, as described by + +| *`extra`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-30-apis-concierge-authentication-v1alpha1-extramapping[$$ExtraMapping$$] array__ | extra is similar to claimMappings.extra from Kubernetes AuthenticationConfiguration + +as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication. + + +However, note that the Pinniped Concierge issues client certificates to users for the purpose + +of authenticating, and the Kubernetes API server does not have any mechanism for transmitting + +auth extras via client certificates. When configured, these extras will appear in client + +certificates issued by the Pinniped Supervisor in the x509 Subject field as Organizational + +Units (OU). However, when this client certificate is presented to Kubernetes for authentication, + +Kubernetes will ignore these extras. This is probably only useful if you are using a custom + +authenticating proxy in front of your Kubernetes API server which can translate these OUs into + +auth extras, as described by + https://kubernetes.io/docs/reference/access-authn-authz/authentication/#authenticating-proxy. + This is an advanced configuration option. During an end-user login flow, each of these CEL expressions + must evaluate to either a string or an array of strings, or else the user's login will fail. + + +These keys must be a domain-prefixed path (such as "acme.io/foo") and must not contain an equals sign ("="). + + +expression must produce a string or string array value. + +If the value is empty, the extra mapping will not be present. + + +Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ + + +hard-coded extra key/value + +- key: "acme.io/foo" + +valueExpression: "'bar'" + +This will result in an extra attribute - acme.io/foo: ["bar"] + + +hard-coded key, value copying claim value + +- key: "acme.io/foo" + +valueExpression: "claims.some_claim" + +This will result in an extra attribute - acme.io/foo: [value of some_claim] + + +hard-coded key, value derived from claim value + +- key: "acme.io/admin" + +valueExpression: '(has(claims.is_admin) && claims.is_admin) ? "true":""' + +This will result in: + +- if is_admin claim is present and true, extra attribute - acme.io/admin: ["true"] + +- if is_admin claim is present and false or is_admin claim is not present, no extra attribute will be added + |=== diff --git a/generated/1.30/apis/concierge/authentication/v1alpha1/types_jwtauthenticator.go b/generated/1.30/apis/concierge/authentication/v1alpha1/types_jwtauthenticator.go index 8d416bcbf..6c782b5ce 100644 --- a/generated/1.30/apis/concierge/authentication/v1alpha1/types_jwtauthenticator.go +++ b/generated/1.30/apis/concierge/authentication/v1alpha1/types_jwtauthenticator.go @@ -77,14 +77,12 @@ type JWTAuthenticatorSpec struct { // ClaimValidationRule provides the configuration for a single claim validation rule. type ClaimValidationRule struct { // claim is the name of a required claim. - // Same as --oidc-required-claim flag. // Only string claim keys are supported. // Mutually exclusive with expression and message. // +optional Claim string `json:"claim,omitempty"` // requiredValue is the value of a required claim. - // Same as --oidc-required-claim flag. // 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. // Mutually exclusive with expression and message. @@ -147,9 +145,14 @@ type JWTTokenClaims struct { Username string `json:"username"` // usernameExpression represents an expression which will be evaluated by CEL. - // // The expression's result will become the user's username. // + // usernameExpression is similar to claimMappings.username.expression from Kubernetes AuthenticationConfiguration + // as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication. + // This is an advanced configuration option. During an end-user login flow, each of these CEL expressions + // must evaluate to the expected type without errors, or else the user's login will fail. + // Additionally, mistakes in this configuration can cause the users to have unintended usernames. + // // The expression must produce a non-empty string value. // If the expression uses 'claims.email', then 'claims.email_verified' must be used in // the expression or extra[*].valueExpression or claimValidationRules[*].expression. @@ -180,9 +183,14 @@ type JWTTokenClaims struct { Groups string `json:"groups"` // groupsExpression represents an expression which will be evaluated by CEL. - // // The expression's result will become the user's group memberships. // + // groupsExpression is similar to claimMappings.groups.expression from Kubernetes AuthenticationConfiguration + // as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication. + // This is an advanced configuration option. During an end-user login flow, each of these CEL expressions + // must evaluate to one of the expected types without errors, or else the user's login will fail. + // Additionally, mistakes in this configuration can cause the users to have unintended group memberships. + // // The expression must produce a string or string array value. // "", [], and null values are treated as the group mapping not being present. // @@ -198,18 +206,45 @@ type JWTTokenClaims struct { // +optional GroupsExpression string `json:"groupsExpression,omitempty"` - // extra is similar to claimMappings.extra from Kubernetes AuthenticationConfiguration as documented in - // https://kubernetes.io/docs/reference/access-authn-authz/authentication. However, note that the - // Pinniped Concierge issues client certificates to users for the purpose of authenticating, and - // the Kubernetes API server does not have any mechanism for transmitting auth extras via client - // certificates. When configured, these extras will appear in client certificates issued by the - // Pinniped Supervisor in the x509 Subject field as Organizational Units (OU). However, when this - // client certificate is presented to Kubernetes for authentication, Kubernetes will ignore these - // extras. This is probably only useful if you are using a custom authenticating proxy in front - // of your Kubernetes API server which can translate these OUs into auth extras, as described by + // extra is similar to claimMappings.extra from Kubernetes AuthenticationConfiguration + // as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication. + // + // However, note that the Pinniped Concierge issues client certificates to users for the purpose + // of authenticating, and the Kubernetes API server does not have any mechanism for transmitting + // auth extras via client certificates. When configured, these extras will appear in client + // certificates issued by the Pinniped Supervisor in the x509 Subject field as Organizational + // Units (OU). However, when this client certificate is presented to Kubernetes for authentication, + // Kubernetes will ignore these extras. This is probably only useful if you are using a custom + // authenticating proxy in front of your Kubernetes API server which can translate these OUs into + // auth extras, as described by // https://kubernetes.io/docs/reference/access-authn-authz/authentication/#authenticating-proxy. // This is an advanced configuration option. During an end-user login flow, each of these CEL expressions // must evaluate to either a string or an array of strings, or else the user's login will fail. + // + // These keys must be a domain-prefixed path (such as "acme.io/foo") and must not contain an equals sign ("="). + // + // expression must produce a string or string array value. + // If the value is empty, the extra mapping will not be present. + // + // Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ + // + // hard-coded extra key/value + // - key: "acme.io/foo" + // valueExpression: "'bar'" + // This will result in an extra attribute - acme.io/foo: ["bar"] + // + // hard-coded key, value copying claim value + // - key: "acme.io/foo" + // valueExpression: "claims.some_claim" + // This will result in an extra attribute - acme.io/foo: [value of some_claim] + // + // hard-coded key, value derived from claim value + // - key: "acme.io/admin" + // valueExpression: '(has(claims.is_admin) && claims.is_admin) ? "true":""' + // This will result in: + // - if is_admin claim is present and true, extra attribute - acme.io/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"` } @@ -222,6 +257,7 @@ type ExtraMapping struct { // be valid HTTP Path characters as defined by RFC 3986. // key must be lowercase. // Required to be unique. + // Additionally, the key must not contain an equals sign ("="). // +required Key string `json:"key"` diff --git a/generated/1.30/crds/authentication.concierge.pinniped.dev_jwtauthenticators.yaml b/generated/1.30/crds/authentication.concierge.pinniped.dev_jwtauthenticators.yaml index 057c8d7b8..4fa9acc3e 100644 --- a/generated/1.30/crds/authentication.concierge.pinniped.dev_jwtauthenticators.yaml +++ b/generated/1.30/crds/authentication.concierge.pinniped.dev_jwtauthenticators.yaml @@ -78,7 +78,6 @@ spec: claim: description: |- claim is the name of a required claim. - Same as --oidc-required-claim flag. Only string claim keys are supported. Mutually exclusive with expression and message. type: string @@ -106,7 +105,6 @@ spec: requiredValue: description: |- requiredValue is the value of a required claim. - Same as --oidc-required-claim flag. 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. Mutually exclusive with expression and message. @@ -120,18 +118,44 @@ spec: properties: extra: description: |- - extra is similar to claimMappings.extra from Kubernetes AuthenticationConfiguration as documented in - https://kubernetes.io/docs/reference/access-authn-authz/authentication. However, note that the - Pinniped Concierge issues client certificates to users for the purpose of authenticating, and - the Kubernetes API server does not have any mechanism for transmitting auth extras via client - certificates. When configured, these extras will appear in client certificates issued by the - Pinniped Supervisor in the x509 Subject field as Organizational Units (OU). However, when this - client certificate is presented to Kubernetes for authentication, Kubernetes will ignore these - extras. This is probably only useful if you are using a custom authenticating proxy in front - of your Kubernetes API server which can translate these OUs into auth extras, as described by + extra is similar to claimMappings.extra from Kubernetes AuthenticationConfiguration + as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication. + + However, note that the Pinniped Concierge issues client certificates to users for the purpose + of authenticating, and the Kubernetes API server does not have any mechanism for transmitting + auth extras via client certificates. When configured, these extras will appear in client + certificates issued by the Pinniped Supervisor in the x509 Subject field as Organizational + Units (OU). However, when this client certificate is presented to Kubernetes for authentication, + Kubernetes will ignore these extras. This is probably only useful if you are using a custom + authenticating proxy in front of your Kubernetes API server which can translate these OUs into + auth extras, as described by https://kubernetes.io/docs/reference/access-authn-authz/authentication/#authenticating-proxy. This is an advanced configuration option. During an end-user login flow, each of these CEL expressions must evaluate to either a string or an array of strings, or else the user's login will fail. + + These keys must be a domain-prefixed path (such as "acme.io/foo") and must not contain an equals sign ("="). + + expression must produce a string or string array value. + If the value is empty, the extra mapping will not be present. + + Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ + + hard-coded extra key/value + - key: "acme.io/foo" + valueExpression: "'bar'" + This will result in an extra attribute - acme.io/foo: ["bar"] + + hard-coded key, value copying claim value + - key: "acme.io/foo" + valueExpression: "claims.some_claim" + This will result in an extra attribute - acme.io/foo: [value of some_claim] + + hard-coded key, value derived from claim value + - key: "acme.io/admin" + valueExpression: '(has(claims.is_admin) && claims.is_admin) ? "true":""' + This will result in: + - if is_admin claim is present and true, extra attribute - acme.io/admin: ["true"] + - if is_admin claim is present and false or is_admin claim is not present, no extra attribute will be added items: description: ExtraMapping provides the configuration for a single extra mapping. @@ -144,6 +168,7 @@ spec: be valid HTTP Path characters as defined by RFC 3986. key must be lowercase. Required to be unique. + Additionally, the key must not contain an equals sign ("="). type: string valueExpression: description: |- @@ -176,9 +201,14 @@ spec: groupsExpression: description: |- groupsExpression represents an expression which will be evaluated by CEL. - The expression's result will become the user's group memberships. + groupsExpression is similar to claimMappings.groups.expression from Kubernetes AuthenticationConfiguration + as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication. + This is an advanced configuration option. During an end-user login flow, each of these CEL expressions + must evaluate to one of the expected types without errors, or else the user's login will fail. + Additionally, mistakes in this configuration can cause the users to have unintended group memberships. + The expression must produce a string or string array value. "", [], and null values are treated as the group mapping not being present. @@ -204,9 +234,14 @@ spec: usernameExpression: description: |- usernameExpression represents an expression which will be evaluated by CEL. - The expression's result will become the user's username. + usernameExpression is similar to claimMappings.username.expression from Kubernetes AuthenticationConfiguration + as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication. + This is an advanced configuration option. During an end-user login flow, each of these CEL expressions + must evaluate to the expected type without errors, or else the user's login will fail. + Additionally, mistakes in this configuration can cause the users to have unintended usernames. + The expression must produce a non-empty string value. If the expression uses 'claims.email', then 'claims.email_verified' must be used in the expression or extra[*].valueExpression or claimValidationRules[*].expression. diff --git a/generated/1.31/README.adoc b/generated/1.31/README.adoc index d05ca7c5d..09eda657f 100644 --- a/generated/1.31/README.adoc +++ b/generated/1.31/README.adoc @@ -74,11 +74,9 @@ ClaimValidationRule provides the configuration for a single claim validation rul |=== | Field | Description | *`claim`* __string__ | claim is the name of a required claim. + -Same as --oidc-required-claim flag. + Only string claim keys are supported. + Mutually exclusive with expression and message. + | *`requiredValue`* __string__ | requiredValue is the value of a required claim. + -Same as --oidc-required-claim flag. + 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. + Mutually exclusive with expression and message. + @@ -119,6 +117,7 @@ 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 to be unique. + +Additionally, the key must not contain an equals sign ("="). + | *`valueExpression`* __string__ | 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. + @@ -244,9 +243,14 @@ unless usernameExpression is specified. + Mutually exclusive with usernameExpression. Use either username or usernameExpression to + determine the user's username from the JWT token. + | *`usernameExpression`* __string__ | usernameExpression represents an expression which will be evaluated by CEL. + - The expression's result will become the user's username. + +usernameExpression is similar to claimMappings.username.expression from Kubernetes AuthenticationConfiguration + +as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication. + +This is an advanced configuration option. During an end-user login flow, each of these CEL expressions + +must evaluate to the expected type without errors, or else the user's login will fail. + +Additionally, mistakes in this configuration can cause the users to have unintended usernames. + + The expression must produce a non-empty string value. + If the expression uses 'claims.email', then 'claims.email_verified' must be used in + the expression or extra[*].valueExpression or claimValidationRules[*].expression. + @@ -271,9 +275,14 @@ unless groupsExpression is specified. + Mutually exclusive with groupsExpression. Use either groups or groupsExpression to + determine the user's group membership from the JWT token. + | *`groupsExpression`* __string__ | groupsExpression represents an expression which will be evaluated by CEL. + - The expression's result will become the user's group memberships. + +groupsExpression is similar to claimMappings.groups.expression from Kubernetes AuthenticationConfiguration + +as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication. + +This is an advanced configuration option. During an end-user login flow, each of these CEL expressions + +must evaluate to one of the expected types without errors, or else the user's login will fail. + +Additionally, mistakes in this configuration can cause the users to have unintended group memberships. + + The expression must produce a string or string array value. + "", [], and null values are treated as the group mapping not being present. + @@ -286,18 +295,44 @@ Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ + Mutually exclusive with groups. Use either groups or groupsExpression to + determine the user's group membership from the JWT token. + -| *`extra`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-31-apis-concierge-authentication-v1alpha1-extramapping[$$ExtraMapping$$] array__ | extra is similar to claimMappings.extra from Kubernetes AuthenticationConfiguration as documented in + -https://kubernetes.io/docs/reference/access-authn-authz/authentication. However, note that the + -Pinniped Concierge issues client certificates to users for the purpose of authenticating, and + -the Kubernetes API server does not have any mechanism for transmitting auth extras via client + -certificates. When configured, these extras will appear in client certificates issued by the + -Pinniped Supervisor in the x509 Subject field as Organizational Units (OU). However, when this + -client certificate is presented to Kubernetes for authentication, Kubernetes will ignore these + -extras. This is probably only useful if you are using a custom authenticating proxy in front + -of your Kubernetes API server which can translate these OUs into auth extras, as described by + +| *`extra`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-31-apis-concierge-authentication-v1alpha1-extramapping[$$ExtraMapping$$] array__ | extra is similar to claimMappings.extra from Kubernetes AuthenticationConfiguration + +as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication. + + +However, note that the Pinniped Concierge issues client certificates to users for the purpose + +of authenticating, and the Kubernetes API server does not have any mechanism for transmitting + +auth extras via client certificates. When configured, these extras will appear in client + +certificates issued by the Pinniped Supervisor in the x509 Subject field as Organizational + +Units (OU). However, when this client certificate is presented to Kubernetes for authentication, + +Kubernetes will ignore these extras. This is probably only useful if you are using a custom + +authenticating proxy in front of your Kubernetes API server which can translate these OUs into + +auth extras, as described by + https://kubernetes.io/docs/reference/access-authn-authz/authentication/#authenticating-proxy. + This is an advanced configuration option. During an end-user login flow, each of these CEL expressions + must evaluate to either a string or an array of strings, or else the user's login will fail. + + +These keys must be a domain-prefixed path (such as "acme.io/foo") and must not contain an equals sign ("="). + + +expression must produce a string or string array value. + +If the value is empty, the extra mapping will not be present. + + +Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ + + +hard-coded extra key/value + +- key: "acme.io/foo" + +valueExpression: "'bar'" + +This will result in an extra attribute - acme.io/foo: ["bar"] + + +hard-coded key, value copying claim value + +- key: "acme.io/foo" + +valueExpression: "claims.some_claim" + +This will result in an extra attribute - acme.io/foo: [value of some_claim] + + +hard-coded key, value derived from claim value + +- key: "acme.io/admin" + +valueExpression: '(has(claims.is_admin) && claims.is_admin) ? "true":""' + +This will result in: + +- if is_admin claim is present and true, extra attribute - acme.io/admin: ["true"] + +- if is_admin claim is present and false or is_admin claim is not present, no extra attribute will be added + |=== diff --git a/generated/1.31/apis/concierge/authentication/v1alpha1/types_jwtauthenticator.go b/generated/1.31/apis/concierge/authentication/v1alpha1/types_jwtauthenticator.go index 8d416bcbf..6c782b5ce 100644 --- a/generated/1.31/apis/concierge/authentication/v1alpha1/types_jwtauthenticator.go +++ b/generated/1.31/apis/concierge/authentication/v1alpha1/types_jwtauthenticator.go @@ -77,14 +77,12 @@ type JWTAuthenticatorSpec struct { // ClaimValidationRule provides the configuration for a single claim validation rule. type ClaimValidationRule struct { // claim is the name of a required claim. - // Same as --oidc-required-claim flag. // Only string claim keys are supported. // Mutually exclusive with expression and message. // +optional Claim string `json:"claim,omitempty"` // requiredValue is the value of a required claim. - // Same as --oidc-required-claim flag. // 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. // Mutually exclusive with expression and message. @@ -147,9 +145,14 @@ type JWTTokenClaims struct { Username string `json:"username"` // usernameExpression represents an expression which will be evaluated by CEL. - // // The expression's result will become the user's username. // + // usernameExpression is similar to claimMappings.username.expression from Kubernetes AuthenticationConfiguration + // as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication. + // This is an advanced configuration option. During an end-user login flow, each of these CEL expressions + // must evaluate to the expected type without errors, or else the user's login will fail. + // Additionally, mistakes in this configuration can cause the users to have unintended usernames. + // // The expression must produce a non-empty string value. // If the expression uses 'claims.email', then 'claims.email_verified' must be used in // the expression or extra[*].valueExpression or claimValidationRules[*].expression. @@ -180,9 +183,14 @@ type JWTTokenClaims struct { Groups string `json:"groups"` // groupsExpression represents an expression which will be evaluated by CEL. - // // The expression's result will become the user's group memberships. // + // groupsExpression is similar to claimMappings.groups.expression from Kubernetes AuthenticationConfiguration + // as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication. + // This is an advanced configuration option. During an end-user login flow, each of these CEL expressions + // must evaluate to one of the expected types without errors, or else the user's login will fail. + // Additionally, mistakes in this configuration can cause the users to have unintended group memberships. + // // The expression must produce a string or string array value. // "", [], and null values are treated as the group mapping not being present. // @@ -198,18 +206,45 @@ type JWTTokenClaims struct { // +optional GroupsExpression string `json:"groupsExpression,omitempty"` - // extra is similar to claimMappings.extra from Kubernetes AuthenticationConfiguration as documented in - // https://kubernetes.io/docs/reference/access-authn-authz/authentication. However, note that the - // Pinniped Concierge issues client certificates to users for the purpose of authenticating, and - // the Kubernetes API server does not have any mechanism for transmitting auth extras via client - // certificates. When configured, these extras will appear in client certificates issued by the - // Pinniped Supervisor in the x509 Subject field as Organizational Units (OU). However, when this - // client certificate is presented to Kubernetes for authentication, Kubernetes will ignore these - // extras. This is probably only useful if you are using a custom authenticating proxy in front - // of your Kubernetes API server which can translate these OUs into auth extras, as described by + // extra is similar to claimMappings.extra from Kubernetes AuthenticationConfiguration + // as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication. + // + // However, note that the Pinniped Concierge issues client certificates to users for the purpose + // of authenticating, and the Kubernetes API server does not have any mechanism for transmitting + // auth extras via client certificates. When configured, these extras will appear in client + // certificates issued by the Pinniped Supervisor in the x509 Subject field as Organizational + // Units (OU). However, when this client certificate is presented to Kubernetes for authentication, + // Kubernetes will ignore these extras. This is probably only useful if you are using a custom + // authenticating proxy in front of your Kubernetes API server which can translate these OUs into + // auth extras, as described by // https://kubernetes.io/docs/reference/access-authn-authz/authentication/#authenticating-proxy. // This is an advanced configuration option. During an end-user login flow, each of these CEL expressions // must evaluate to either a string or an array of strings, or else the user's login will fail. + // + // These keys must be a domain-prefixed path (such as "acme.io/foo") and must not contain an equals sign ("="). + // + // expression must produce a string or string array value. + // If the value is empty, the extra mapping will not be present. + // + // Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ + // + // hard-coded extra key/value + // - key: "acme.io/foo" + // valueExpression: "'bar'" + // This will result in an extra attribute - acme.io/foo: ["bar"] + // + // hard-coded key, value copying claim value + // - key: "acme.io/foo" + // valueExpression: "claims.some_claim" + // This will result in an extra attribute - acme.io/foo: [value of some_claim] + // + // hard-coded key, value derived from claim value + // - key: "acme.io/admin" + // valueExpression: '(has(claims.is_admin) && claims.is_admin) ? "true":""' + // This will result in: + // - if is_admin claim is present and true, extra attribute - acme.io/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"` } @@ -222,6 +257,7 @@ type ExtraMapping struct { // be valid HTTP Path characters as defined by RFC 3986. // key must be lowercase. // Required to be unique. + // Additionally, the key must not contain an equals sign ("="). // +required Key string `json:"key"` diff --git a/generated/1.31/crds/authentication.concierge.pinniped.dev_jwtauthenticators.yaml b/generated/1.31/crds/authentication.concierge.pinniped.dev_jwtauthenticators.yaml index 057c8d7b8..4fa9acc3e 100644 --- a/generated/1.31/crds/authentication.concierge.pinniped.dev_jwtauthenticators.yaml +++ b/generated/1.31/crds/authentication.concierge.pinniped.dev_jwtauthenticators.yaml @@ -78,7 +78,6 @@ spec: claim: description: |- claim is the name of a required claim. - Same as --oidc-required-claim flag. Only string claim keys are supported. Mutually exclusive with expression and message. type: string @@ -106,7 +105,6 @@ spec: requiredValue: description: |- requiredValue is the value of a required claim. - Same as --oidc-required-claim flag. 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. Mutually exclusive with expression and message. @@ -120,18 +118,44 @@ spec: properties: extra: description: |- - extra is similar to claimMappings.extra from Kubernetes AuthenticationConfiguration as documented in - https://kubernetes.io/docs/reference/access-authn-authz/authentication. However, note that the - Pinniped Concierge issues client certificates to users for the purpose of authenticating, and - the Kubernetes API server does not have any mechanism for transmitting auth extras via client - certificates. When configured, these extras will appear in client certificates issued by the - Pinniped Supervisor in the x509 Subject field as Organizational Units (OU). However, when this - client certificate is presented to Kubernetes for authentication, Kubernetes will ignore these - extras. This is probably only useful if you are using a custom authenticating proxy in front - of your Kubernetes API server which can translate these OUs into auth extras, as described by + extra is similar to claimMappings.extra from Kubernetes AuthenticationConfiguration + as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication. + + However, note that the Pinniped Concierge issues client certificates to users for the purpose + of authenticating, and the Kubernetes API server does not have any mechanism for transmitting + auth extras via client certificates. When configured, these extras will appear in client + certificates issued by the Pinniped Supervisor in the x509 Subject field as Organizational + Units (OU). However, when this client certificate is presented to Kubernetes for authentication, + Kubernetes will ignore these extras. This is probably only useful if you are using a custom + authenticating proxy in front of your Kubernetes API server which can translate these OUs into + auth extras, as described by https://kubernetes.io/docs/reference/access-authn-authz/authentication/#authenticating-proxy. This is an advanced configuration option. During an end-user login flow, each of these CEL expressions must evaluate to either a string or an array of strings, or else the user's login will fail. + + These keys must be a domain-prefixed path (such as "acme.io/foo") and must not contain an equals sign ("="). + + expression must produce a string or string array value. + If the value is empty, the extra mapping will not be present. + + Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ + + hard-coded extra key/value + - key: "acme.io/foo" + valueExpression: "'bar'" + This will result in an extra attribute - acme.io/foo: ["bar"] + + hard-coded key, value copying claim value + - key: "acme.io/foo" + valueExpression: "claims.some_claim" + This will result in an extra attribute - acme.io/foo: [value of some_claim] + + hard-coded key, value derived from claim value + - key: "acme.io/admin" + valueExpression: '(has(claims.is_admin) && claims.is_admin) ? "true":""' + This will result in: + - if is_admin claim is present and true, extra attribute - acme.io/admin: ["true"] + - if is_admin claim is present and false or is_admin claim is not present, no extra attribute will be added items: description: ExtraMapping provides the configuration for a single extra mapping. @@ -144,6 +168,7 @@ spec: be valid HTTP Path characters as defined by RFC 3986. key must be lowercase. Required to be unique. + Additionally, the key must not contain an equals sign ("="). type: string valueExpression: description: |- @@ -176,9 +201,14 @@ spec: groupsExpression: description: |- groupsExpression represents an expression which will be evaluated by CEL. - The expression's result will become the user's group memberships. + groupsExpression is similar to claimMappings.groups.expression from Kubernetes AuthenticationConfiguration + as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication. + This is an advanced configuration option. During an end-user login flow, each of these CEL expressions + must evaluate to one of the expected types without errors, or else the user's login will fail. + Additionally, mistakes in this configuration can cause the users to have unintended group memberships. + The expression must produce a string or string array value. "", [], and null values are treated as the group mapping not being present. @@ -204,9 +234,14 @@ spec: usernameExpression: description: |- usernameExpression represents an expression which will be evaluated by CEL. - The expression's result will become the user's username. + usernameExpression is similar to claimMappings.username.expression from Kubernetes AuthenticationConfiguration + as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication. + This is an advanced configuration option. During an end-user login flow, each of these CEL expressions + must evaluate to the expected type without errors, or else the user's login will fail. + Additionally, mistakes in this configuration can cause the users to have unintended usernames. + The expression must produce a non-empty string value. If the expression uses 'claims.email', then 'claims.email_verified' must be used in the expression or extra[*].valueExpression or claimValidationRules[*].expression. diff --git a/generated/1.32/README.adoc b/generated/1.32/README.adoc index ab808e660..d545aeb41 100644 --- a/generated/1.32/README.adoc +++ b/generated/1.32/README.adoc @@ -74,11 +74,9 @@ ClaimValidationRule provides the configuration for a single claim validation rul |=== | Field | Description | *`claim`* __string__ | claim is the name of a required claim. + -Same as --oidc-required-claim flag. + Only string claim keys are supported. + Mutually exclusive with expression and message. + | *`requiredValue`* __string__ | requiredValue is the value of a required claim. + -Same as --oidc-required-claim flag. + 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. + Mutually exclusive with expression and message. + @@ -119,6 +117,7 @@ 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 to be unique. + +Additionally, the key must not contain an equals sign ("="). + | *`valueExpression`* __string__ | 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. + @@ -244,9 +243,14 @@ unless usernameExpression is specified. + Mutually exclusive with usernameExpression. Use either username or usernameExpression to + determine the user's username from the JWT token. + | *`usernameExpression`* __string__ | usernameExpression represents an expression which will be evaluated by CEL. + - The expression's result will become the user's username. + +usernameExpression is similar to claimMappings.username.expression from Kubernetes AuthenticationConfiguration + +as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication. + +This is an advanced configuration option. During an end-user login flow, each of these CEL expressions + +must evaluate to the expected type without errors, or else the user's login will fail. + +Additionally, mistakes in this configuration can cause the users to have unintended usernames. + + The expression must produce a non-empty string value. + If the expression uses 'claims.email', then 'claims.email_verified' must be used in + the expression or extra[*].valueExpression or claimValidationRules[*].expression. + @@ -271,9 +275,14 @@ unless groupsExpression is specified. + Mutually exclusive with groupsExpression. Use either groups or groupsExpression to + determine the user's group membership from the JWT token. + | *`groupsExpression`* __string__ | groupsExpression represents an expression which will be evaluated by CEL. + - The expression's result will become the user's group memberships. + +groupsExpression is similar to claimMappings.groups.expression from Kubernetes AuthenticationConfiguration + +as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication. + +This is an advanced configuration option. During an end-user login flow, each of these CEL expressions + +must evaluate to one of the expected types without errors, or else the user's login will fail. + +Additionally, mistakes in this configuration can cause the users to have unintended group memberships. + + The expression must produce a string or string array value. + "", [], and null values are treated as the group mapping not being present. + @@ -286,18 +295,44 @@ Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ + Mutually exclusive with groups. Use either groups or groupsExpression to + determine the user's group membership from the JWT token. + -| *`extra`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-32-apis-concierge-authentication-v1alpha1-extramapping[$$ExtraMapping$$] array__ | extra is similar to claimMappings.extra from Kubernetes AuthenticationConfiguration as documented in + -https://kubernetes.io/docs/reference/access-authn-authz/authentication. However, note that the + -Pinniped Concierge issues client certificates to users for the purpose of authenticating, and + -the Kubernetes API server does not have any mechanism for transmitting auth extras via client + -certificates. When configured, these extras will appear in client certificates issued by the + -Pinniped Supervisor in the x509 Subject field as Organizational Units (OU). However, when this + -client certificate is presented to Kubernetes for authentication, Kubernetes will ignore these + -extras. This is probably only useful if you are using a custom authenticating proxy in front + -of your Kubernetes API server which can translate these OUs into auth extras, as described by + +| *`extra`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-32-apis-concierge-authentication-v1alpha1-extramapping[$$ExtraMapping$$] array__ | extra is similar to claimMappings.extra from Kubernetes AuthenticationConfiguration + +as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication. + + +However, note that the Pinniped Concierge issues client certificates to users for the purpose + +of authenticating, and the Kubernetes API server does not have any mechanism for transmitting + +auth extras via client certificates. When configured, these extras will appear in client + +certificates issued by the Pinniped Supervisor in the x509 Subject field as Organizational + +Units (OU). However, when this client certificate is presented to Kubernetes for authentication, + +Kubernetes will ignore these extras. This is probably only useful if you are using a custom + +authenticating proxy in front of your Kubernetes API server which can translate these OUs into + +auth extras, as described by + https://kubernetes.io/docs/reference/access-authn-authz/authentication/#authenticating-proxy. + This is an advanced configuration option. During an end-user login flow, each of these CEL expressions + must evaluate to either a string or an array of strings, or else the user's login will fail. + + +These keys must be a domain-prefixed path (such as "acme.io/foo") and must not contain an equals sign ("="). + + +expression must produce a string or string array value. + +If the value is empty, the extra mapping will not be present. + + +Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ + + +hard-coded extra key/value + +- key: "acme.io/foo" + +valueExpression: "'bar'" + +This will result in an extra attribute - acme.io/foo: ["bar"] + + +hard-coded key, value copying claim value + +- key: "acme.io/foo" + +valueExpression: "claims.some_claim" + +This will result in an extra attribute - acme.io/foo: [value of some_claim] + + +hard-coded key, value derived from claim value + +- key: "acme.io/admin" + +valueExpression: '(has(claims.is_admin) && claims.is_admin) ? "true":""' + +This will result in: + +- if is_admin claim is present and true, extra attribute - acme.io/admin: ["true"] + +- if is_admin claim is present and false or is_admin claim is not present, no extra attribute will be added + |=== diff --git a/generated/1.32/apis/concierge/authentication/v1alpha1/types_jwtauthenticator.go b/generated/1.32/apis/concierge/authentication/v1alpha1/types_jwtauthenticator.go index 8d416bcbf..6c782b5ce 100644 --- a/generated/1.32/apis/concierge/authentication/v1alpha1/types_jwtauthenticator.go +++ b/generated/1.32/apis/concierge/authentication/v1alpha1/types_jwtauthenticator.go @@ -77,14 +77,12 @@ type JWTAuthenticatorSpec struct { // ClaimValidationRule provides the configuration for a single claim validation rule. type ClaimValidationRule struct { // claim is the name of a required claim. - // Same as --oidc-required-claim flag. // Only string claim keys are supported. // Mutually exclusive with expression and message. // +optional Claim string `json:"claim,omitempty"` // requiredValue is the value of a required claim. - // Same as --oidc-required-claim flag. // 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. // Mutually exclusive with expression and message. @@ -147,9 +145,14 @@ type JWTTokenClaims struct { Username string `json:"username"` // usernameExpression represents an expression which will be evaluated by CEL. - // // The expression's result will become the user's username. // + // usernameExpression is similar to claimMappings.username.expression from Kubernetes AuthenticationConfiguration + // as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication. + // This is an advanced configuration option. During an end-user login flow, each of these CEL expressions + // must evaluate to the expected type without errors, or else the user's login will fail. + // Additionally, mistakes in this configuration can cause the users to have unintended usernames. + // // The expression must produce a non-empty string value. // If the expression uses 'claims.email', then 'claims.email_verified' must be used in // the expression or extra[*].valueExpression or claimValidationRules[*].expression. @@ -180,9 +183,14 @@ type JWTTokenClaims struct { Groups string `json:"groups"` // groupsExpression represents an expression which will be evaluated by CEL. - // // The expression's result will become the user's group memberships. // + // groupsExpression is similar to claimMappings.groups.expression from Kubernetes AuthenticationConfiguration + // as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication. + // This is an advanced configuration option. During an end-user login flow, each of these CEL expressions + // must evaluate to one of the expected types without errors, or else the user's login will fail. + // Additionally, mistakes in this configuration can cause the users to have unintended group memberships. + // // The expression must produce a string or string array value. // "", [], and null values are treated as the group mapping not being present. // @@ -198,18 +206,45 @@ type JWTTokenClaims struct { // +optional GroupsExpression string `json:"groupsExpression,omitempty"` - // extra is similar to claimMappings.extra from Kubernetes AuthenticationConfiguration as documented in - // https://kubernetes.io/docs/reference/access-authn-authz/authentication. However, note that the - // Pinniped Concierge issues client certificates to users for the purpose of authenticating, and - // the Kubernetes API server does not have any mechanism for transmitting auth extras via client - // certificates. When configured, these extras will appear in client certificates issued by the - // Pinniped Supervisor in the x509 Subject field as Organizational Units (OU). However, when this - // client certificate is presented to Kubernetes for authentication, Kubernetes will ignore these - // extras. This is probably only useful if you are using a custom authenticating proxy in front - // of your Kubernetes API server which can translate these OUs into auth extras, as described by + // extra is similar to claimMappings.extra from Kubernetes AuthenticationConfiguration + // as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication. + // + // However, note that the Pinniped Concierge issues client certificates to users for the purpose + // of authenticating, and the Kubernetes API server does not have any mechanism for transmitting + // auth extras via client certificates. When configured, these extras will appear in client + // certificates issued by the Pinniped Supervisor in the x509 Subject field as Organizational + // Units (OU). However, when this client certificate is presented to Kubernetes for authentication, + // Kubernetes will ignore these extras. This is probably only useful if you are using a custom + // authenticating proxy in front of your Kubernetes API server which can translate these OUs into + // auth extras, as described by // https://kubernetes.io/docs/reference/access-authn-authz/authentication/#authenticating-proxy. // This is an advanced configuration option. During an end-user login flow, each of these CEL expressions // must evaluate to either a string or an array of strings, or else the user's login will fail. + // + // These keys must be a domain-prefixed path (such as "acme.io/foo") and must not contain an equals sign ("="). + // + // expression must produce a string or string array value. + // If the value is empty, the extra mapping will not be present. + // + // Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ + // + // hard-coded extra key/value + // - key: "acme.io/foo" + // valueExpression: "'bar'" + // This will result in an extra attribute - acme.io/foo: ["bar"] + // + // hard-coded key, value copying claim value + // - key: "acme.io/foo" + // valueExpression: "claims.some_claim" + // This will result in an extra attribute - acme.io/foo: [value of some_claim] + // + // hard-coded key, value derived from claim value + // - key: "acme.io/admin" + // valueExpression: '(has(claims.is_admin) && claims.is_admin) ? "true":""' + // This will result in: + // - if is_admin claim is present and true, extra attribute - acme.io/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"` } @@ -222,6 +257,7 @@ type ExtraMapping struct { // be valid HTTP Path characters as defined by RFC 3986. // key must be lowercase. // Required to be unique. + // Additionally, the key must not contain an equals sign ("="). // +required Key string `json:"key"` diff --git a/generated/1.32/crds/authentication.concierge.pinniped.dev_jwtauthenticators.yaml b/generated/1.32/crds/authentication.concierge.pinniped.dev_jwtauthenticators.yaml index 057c8d7b8..4fa9acc3e 100644 --- a/generated/1.32/crds/authentication.concierge.pinniped.dev_jwtauthenticators.yaml +++ b/generated/1.32/crds/authentication.concierge.pinniped.dev_jwtauthenticators.yaml @@ -78,7 +78,6 @@ spec: claim: description: |- claim is the name of a required claim. - Same as --oidc-required-claim flag. Only string claim keys are supported. Mutually exclusive with expression and message. type: string @@ -106,7 +105,6 @@ spec: requiredValue: description: |- requiredValue is the value of a required claim. - Same as --oidc-required-claim flag. 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. Mutually exclusive with expression and message. @@ -120,18 +118,44 @@ spec: properties: extra: description: |- - extra is similar to claimMappings.extra from Kubernetes AuthenticationConfiguration as documented in - https://kubernetes.io/docs/reference/access-authn-authz/authentication. However, note that the - Pinniped Concierge issues client certificates to users for the purpose of authenticating, and - the Kubernetes API server does not have any mechanism for transmitting auth extras via client - certificates. When configured, these extras will appear in client certificates issued by the - Pinniped Supervisor in the x509 Subject field as Organizational Units (OU). However, when this - client certificate is presented to Kubernetes for authentication, Kubernetes will ignore these - extras. This is probably only useful if you are using a custom authenticating proxy in front - of your Kubernetes API server which can translate these OUs into auth extras, as described by + extra is similar to claimMappings.extra from Kubernetes AuthenticationConfiguration + as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication. + + However, note that the Pinniped Concierge issues client certificates to users for the purpose + of authenticating, and the Kubernetes API server does not have any mechanism for transmitting + auth extras via client certificates. When configured, these extras will appear in client + certificates issued by the Pinniped Supervisor in the x509 Subject field as Organizational + Units (OU). However, when this client certificate is presented to Kubernetes for authentication, + Kubernetes will ignore these extras. This is probably only useful if you are using a custom + authenticating proxy in front of your Kubernetes API server which can translate these OUs into + auth extras, as described by https://kubernetes.io/docs/reference/access-authn-authz/authentication/#authenticating-proxy. This is an advanced configuration option. During an end-user login flow, each of these CEL expressions must evaluate to either a string or an array of strings, or else the user's login will fail. + + These keys must be a domain-prefixed path (such as "acme.io/foo") and must not contain an equals sign ("="). + + expression must produce a string or string array value. + If the value is empty, the extra mapping will not be present. + + Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ + + hard-coded extra key/value + - key: "acme.io/foo" + valueExpression: "'bar'" + This will result in an extra attribute - acme.io/foo: ["bar"] + + hard-coded key, value copying claim value + - key: "acme.io/foo" + valueExpression: "claims.some_claim" + This will result in an extra attribute - acme.io/foo: [value of some_claim] + + hard-coded key, value derived from claim value + - key: "acme.io/admin" + valueExpression: '(has(claims.is_admin) && claims.is_admin) ? "true":""' + This will result in: + - if is_admin claim is present and true, extra attribute - acme.io/admin: ["true"] + - if is_admin claim is present and false or is_admin claim is not present, no extra attribute will be added items: description: ExtraMapping provides the configuration for a single extra mapping. @@ -144,6 +168,7 @@ spec: be valid HTTP Path characters as defined by RFC 3986. key must be lowercase. Required to be unique. + Additionally, the key must not contain an equals sign ("="). type: string valueExpression: description: |- @@ -176,9 +201,14 @@ spec: groupsExpression: description: |- groupsExpression represents an expression which will be evaluated by CEL. - The expression's result will become the user's group memberships. + groupsExpression is similar to claimMappings.groups.expression from Kubernetes AuthenticationConfiguration + as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication. + This is an advanced configuration option. During an end-user login flow, each of these CEL expressions + must evaluate to one of the expected types without errors, or else the user's login will fail. + Additionally, mistakes in this configuration can cause the users to have unintended group memberships. + The expression must produce a string or string array value. "", [], and null values are treated as the group mapping not being present. @@ -204,9 +234,14 @@ spec: usernameExpression: description: |- usernameExpression represents an expression which will be evaluated by CEL. - The expression's result will become the user's username. + usernameExpression is similar to claimMappings.username.expression from Kubernetes AuthenticationConfiguration + as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication. + This is an advanced configuration option. During an end-user login flow, each of these CEL expressions + must evaluate to the expected type without errors, or else the user's login will fail. + Additionally, mistakes in this configuration can cause the users to have unintended usernames. + The expression must produce a non-empty string value. If the expression uses 'claims.email', then 'claims.email_verified' must be used in the expression or extra[*].valueExpression or claimValidationRules[*].expression. diff --git a/generated/1.33/README.adoc b/generated/1.33/README.adoc index 5796e1366..f8805ee9c 100644 --- a/generated/1.33/README.adoc +++ b/generated/1.33/README.adoc @@ -74,11 +74,9 @@ ClaimValidationRule provides the configuration for a single claim validation rul |=== | Field | Description | *`claim`* __string__ | claim is the name of a required claim. + -Same as --oidc-required-claim flag. + Only string claim keys are supported. + Mutually exclusive with expression and message. + | *`requiredValue`* __string__ | requiredValue is the value of a required claim. + -Same as --oidc-required-claim flag. + 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. + Mutually exclusive with expression and message. + @@ -119,6 +117,7 @@ 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 to be unique. + +Additionally, the key must not contain an equals sign ("="). + | *`valueExpression`* __string__ | 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. + @@ -244,9 +243,14 @@ unless usernameExpression is specified. + Mutually exclusive with usernameExpression. Use either username or usernameExpression to + determine the user's username from the JWT token. + | *`usernameExpression`* __string__ | usernameExpression represents an expression which will be evaluated by CEL. + - The expression's result will become the user's username. + +usernameExpression is similar to claimMappings.username.expression from Kubernetes AuthenticationConfiguration + +as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication. + +This is an advanced configuration option. During an end-user login flow, each of these CEL expressions + +must evaluate to the expected type without errors, or else the user's login will fail. + +Additionally, mistakes in this configuration can cause the users to have unintended usernames. + + The expression must produce a non-empty string value. + If the expression uses 'claims.email', then 'claims.email_verified' must be used in + the expression or extra[*].valueExpression or claimValidationRules[*].expression. + @@ -271,9 +275,14 @@ unless groupsExpression is specified. + Mutually exclusive with groupsExpression. Use either groups or groupsExpression to + determine the user's group membership from the JWT token. + | *`groupsExpression`* __string__ | groupsExpression represents an expression which will be evaluated by CEL. + - The expression's result will become the user's group memberships. + +groupsExpression is similar to claimMappings.groups.expression from Kubernetes AuthenticationConfiguration + +as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication. + +This is an advanced configuration option. During an end-user login flow, each of these CEL expressions + +must evaluate to one of the expected types without errors, or else the user's login will fail. + +Additionally, mistakes in this configuration can cause the users to have unintended group memberships. + + The expression must produce a string or string array value. + "", [], and null values are treated as the group mapping not being present. + @@ -286,18 +295,44 @@ Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ + Mutually exclusive with groups. Use either groups or groupsExpression to + determine the user's group membership from the JWT token. + -| *`extra`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-33-apis-concierge-authentication-v1alpha1-extramapping[$$ExtraMapping$$] array__ | extra is similar to claimMappings.extra from Kubernetes AuthenticationConfiguration as documented in + -https://kubernetes.io/docs/reference/access-authn-authz/authentication. However, note that the + -Pinniped Concierge issues client certificates to users for the purpose of authenticating, and + -the Kubernetes API server does not have any mechanism for transmitting auth extras via client + -certificates. When configured, these extras will appear in client certificates issued by the + -Pinniped Supervisor in the x509 Subject field as Organizational Units (OU). However, when this + -client certificate is presented to Kubernetes for authentication, Kubernetes will ignore these + -extras. This is probably only useful if you are using a custom authenticating proxy in front + -of your Kubernetes API server which can translate these OUs into auth extras, as described by + +| *`extra`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-33-apis-concierge-authentication-v1alpha1-extramapping[$$ExtraMapping$$] array__ | extra is similar to claimMappings.extra from Kubernetes AuthenticationConfiguration + +as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication. + + +However, note that the Pinniped Concierge issues client certificates to users for the purpose + +of authenticating, and the Kubernetes API server does not have any mechanism for transmitting + +auth extras via client certificates. When configured, these extras will appear in client + +certificates issued by the Pinniped Supervisor in the x509 Subject field as Organizational + +Units (OU). However, when this client certificate is presented to Kubernetes for authentication, + +Kubernetes will ignore these extras. This is probably only useful if you are using a custom + +authenticating proxy in front of your Kubernetes API server which can translate these OUs into + +auth extras, as described by + https://kubernetes.io/docs/reference/access-authn-authz/authentication/#authenticating-proxy. + This is an advanced configuration option. During an end-user login flow, each of these CEL expressions + must evaluate to either a string or an array of strings, or else the user's login will fail. + + +These keys must be a domain-prefixed path (such as "acme.io/foo") and must not contain an equals sign ("="). + + +expression must produce a string or string array value. + +If the value is empty, the extra mapping will not be present. + + +Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ + + +hard-coded extra key/value + +- key: "acme.io/foo" + +valueExpression: "'bar'" + +This will result in an extra attribute - acme.io/foo: ["bar"] + + +hard-coded key, value copying claim value + +- key: "acme.io/foo" + +valueExpression: "claims.some_claim" + +This will result in an extra attribute - acme.io/foo: [value of some_claim] + + +hard-coded key, value derived from claim value + +- key: "acme.io/admin" + +valueExpression: '(has(claims.is_admin) && claims.is_admin) ? "true":""' + +This will result in: + +- if is_admin claim is present and true, extra attribute - acme.io/admin: ["true"] + +- if is_admin claim is present and false or is_admin claim is not present, no extra attribute will be added + |=== diff --git a/generated/1.33/apis/concierge/authentication/v1alpha1/types_jwtauthenticator.go b/generated/1.33/apis/concierge/authentication/v1alpha1/types_jwtauthenticator.go index 8d416bcbf..6c782b5ce 100644 --- a/generated/1.33/apis/concierge/authentication/v1alpha1/types_jwtauthenticator.go +++ b/generated/1.33/apis/concierge/authentication/v1alpha1/types_jwtauthenticator.go @@ -77,14 +77,12 @@ type JWTAuthenticatorSpec struct { // ClaimValidationRule provides the configuration for a single claim validation rule. type ClaimValidationRule struct { // claim is the name of a required claim. - // Same as --oidc-required-claim flag. // Only string claim keys are supported. // Mutually exclusive with expression and message. // +optional Claim string `json:"claim,omitempty"` // requiredValue is the value of a required claim. - // Same as --oidc-required-claim flag. // 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. // Mutually exclusive with expression and message. @@ -147,9 +145,14 @@ type JWTTokenClaims struct { Username string `json:"username"` // usernameExpression represents an expression which will be evaluated by CEL. - // // The expression's result will become the user's username. // + // usernameExpression is similar to claimMappings.username.expression from Kubernetes AuthenticationConfiguration + // as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication. + // This is an advanced configuration option. During an end-user login flow, each of these CEL expressions + // must evaluate to the expected type without errors, or else the user's login will fail. + // Additionally, mistakes in this configuration can cause the users to have unintended usernames. + // // The expression must produce a non-empty string value. // If the expression uses 'claims.email', then 'claims.email_verified' must be used in // the expression or extra[*].valueExpression or claimValidationRules[*].expression. @@ -180,9 +183,14 @@ type JWTTokenClaims struct { Groups string `json:"groups"` // groupsExpression represents an expression which will be evaluated by CEL. - // // The expression's result will become the user's group memberships. // + // groupsExpression is similar to claimMappings.groups.expression from Kubernetes AuthenticationConfiguration + // as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication. + // This is an advanced configuration option. During an end-user login flow, each of these CEL expressions + // must evaluate to one of the expected types without errors, or else the user's login will fail. + // Additionally, mistakes in this configuration can cause the users to have unintended group memberships. + // // The expression must produce a string or string array value. // "", [], and null values are treated as the group mapping not being present. // @@ -198,18 +206,45 @@ type JWTTokenClaims struct { // +optional GroupsExpression string `json:"groupsExpression,omitempty"` - // extra is similar to claimMappings.extra from Kubernetes AuthenticationConfiguration as documented in - // https://kubernetes.io/docs/reference/access-authn-authz/authentication. However, note that the - // Pinniped Concierge issues client certificates to users for the purpose of authenticating, and - // the Kubernetes API server does not have any mechanism for transmitting auth extras via client - // certificates. When configured, these extras will appear in client certificates issued by the - // Pinniped Supervisor in the x509 Subject field as Organizational Units (OU). However, when this - // client certificate is presented to Kubernetes for authentication, Kubernetes will ignore these - // extras. This is probably only useful if you are using a custom authenticating proxy in front - // of your Kubernetes API server which can translate these OUs into auth extras, as described by + // extra is similar to claimMappings.extra from Kubernetes AuthenticationConfiguration + // as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication. + // + // However, note that the Pinniped Concierge issues client certificates to users for the purpose + // of authenticating, and the Kubernetes API server does not have any mechanism for transmitting + // auth extras via client certificates. When configured, these extras will appear in client + // certificates issued by the Pinniped Supervisor in the x509 Subject field as Organizational + // Units (OU). However, when this client certificate is presented to Kubernetes for authentication, + // Kubernetes will ignore these extras. This is probably only useful if you are using a custom + // authenticating proxy in front of your Kubernetes API server which can translate these OUs into + // auth extras, as described by // https://kubernetes.io/docs/reference/access-authn-authz/authentication/#authenticating-proxy. // This is an advanced configuration option. During an end-user login flow, each of these CEL expressions // must evaluate to either a string or an array of strings, or else the user's login will fail. + // + // These keys must be a domain-prefixed path (such as "acme.io/foo") and must not contain an equals sign ("="). + // + // expression must produce a string or string array value. + // If the value is empty, the extra mapping will not be present. + // + // Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ + // + // hard-coded extra key/value + // - key: "acme.io/foo" + // valueExpression: "'bar'" + // This will result in an extra attribute - acme.io/foo: ["bar"] + // + // hard-coded key, value copying claim value + // - key: "acme.io/foo" + // valueExpression: "claims.some_claim" + // This will result in an extra attribute - acme.io/foo: [value of some_claim] + // + // hard-coded key, value derived from claim value + // - key: "acme.io/admin" + // valueExpression: '(has(claims.is_admin) && claims.is_admin) ? "true":""' + // This will result in: + // - if is_admin claim is present and true, extra attribute - acme.io/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"` } @@ -222,6 +257,7 @@ type ExtraMapping struct { // be valid HTTP Path characters as defined by RFC 3986. // key must be lowercase. // Required to be unique. + // Additionally, the key must not contain an equals sign ("="). // +required Key string `json:"key"` diff --git a/generated/1.33/crds/authentication.concierge.pinniped.dev_jwtauthenticators.yaml b/generated/1.33/crds/authentication.concierge.pinniped.dev_jwtauthenticators.yaml index 057c8d7b8..4fa9acc3e 100644 --- a/generated/1.33/crds/authentication.concierge.pinniped.dev_jwtauthenticators.yaml +++ b/generated/1.33/crds/authentication.concierge.pinniped.dev_jwtauthenticators.yaml @@ -78,7 +78,6 @@ spec: claim: description: |- claim is the name of a required claim. - Same as --oidc-required-claim flag. Only string claim keys are supported. Mutually exclusive with expression and message. type: string @@ -106,7 +105,6 @@ spec: requiredValue: description: |- requiredValue is the value of a required claim. - Same as --oidc-required-claim flag. 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. Mutually exclusive with expression and message. @@ -120,18 +118,44 @@ spec: properties: extra: description: |- - extra is similar to claimMappings.extra from Kubernetes AuthenticationConfiguration as documented in - https://kubernetes.io/docs/reference/access-authn-authz/authentication. However, note that the - Pinniped Concierge issues client certificates to users for the purpose of authenticating, and - the Kubernetes API server does not have any mechanism for transmitting auth extras via client - certificates. When configured, these extras will appear in client certificates issued by the - Pinniped Supervisor in the x509 Subject field as Organizational Units (OU). However, when this - client certificate is presented to Kubernetes for authentication, Kubernetes will ignore these - extras. This is probably only useful if you are using a custom authenticating proxy in front - of your Kubernetes API server which can translate these OUs into auth extras, as described by + extra is similar to claimMappings.extra from Kubernetes AuthenticationConfiguration + as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication. + + However, note that the Pinniped Concierge issues client certificates to users for the purpose + of authenticating, and the Kubernetes API server does not have any mechanism for transmitting + auth extras via client certificates. When configured, these extras will appear in client + certificates issued by the Pinniped Supervisor in the x509 Subject field as Organizational + Units (OU). However, when this client certificate is presented to Kubernetes for authentication, + Kubernetes will ignore these extras. This is probably only useful if you are using a custom + authenticating proxy in front of your Kubernetes API server which can translate these OUs into + auth extras, as described by https://kubernetes.io/docs/reference/access-authn-authz/authentication/#authenticating-proxy. This is an advanced configuration option. During an end-user login flow, each of these CEL expressions must evaluate to either a string or an array of strings, or else the user's login will fail. + + These keys must be a domain-prefixed path (such as "acme.io/foo") and must not contain an equals sign ("="). + + expression must produce a string or string array value. + If the value is empty, the extra mapping will not be present. + + Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ + + hard-coded extra key/value + - key: "acme.io/foo" + valueExpression: "'bar'" + This will result in an extra attribute - acme.io/foo: ["bar"] + + hard-coded key, value copying claim value + - key: "acme.io/foo" + valueExpression: "claims.some_claim" + This will result in an extra attribute - acme.io/foo: [value of some_claim] + + hard-coded key, value derived from claim value + - key: "acme.io/admin" + valueExpression: '(has(claims.is_admin) && claims.is_admin) ? "true":""' + This will result in: + - if is_admin claim is present and true, extra attribute - acme.io/admin: ["true"] + - if is_admin claim is present and false or is_admin claim is not present, no extra attribute will be added items: description: ExtraMapping provides the configuration for a single extra mapping. @@ -144,6 +168,7 @@ spec: be valid HTTP Path characters as defined by RFC 3986. key must be lowercase. Required to be unique. + Additionally, the key must not contain an equals sign ("="). type: string valueExpression: description: |- @@ -176,9 +201,14 @@ spec: groupsExpression: description: |- groupsExpression represents an expression which will be evaluated by CEL. - The expression's result will become the user's group memberships. + groupsExpression is similar to claimMappings.groups.expression from Kubernetes AuthenticationConfiguration + as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication. + This is an advanced configuration option. During an end-user login flow, each of these CEL expressions + must evaluate to one of the expected types without errors, or else the user's login will fail. + Additionally, mistakes in this configuration can cause the users to have unintended group memberships. + The expression must produce a string or string array value. "", [], and null values are treated as the group mapping not being present. @@ -204,9 +234,14 @@ spec: usernameExpression: description: |- usernameExpression represents an expression which will be evaluated by CEL. - The expression's result will become the user's username. + usernameExpression is similar to claimMappings.username.expression from Kubernetes AuthenticationConfiguration + as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication. + This is an advanced configuration option. During an end-user login flow, each of these CEL expressions + must evaluate to the expected type without errors, or else the user's login will fail. + Additionally, mistakes in this configuration can cause the users to have unintended usernames. + The expression must produce a non-empty string value. If the expression uses 'claims.email', then 'claims.email_verified' must be used in the expression or extra[*].valueExpression or claimValidationRules[*].expression. diff --git a/generated/latest/README.adoc b/generated/latest/README.adoc index 5796e1366..f8805ee9c 100644 --- a/generated/latest/README.adoc +++ b/generated/latest/README.adoc @@ -74,11 +74,9 @@ ClaimValidationRule provides the configuration for a single claim validation rul |=== | Field | Description | *`claim`* __string__ | claim is the name of a required claim. + -Same as --oidc-required-claim flag. + Only string claim keys are supported. + Mutually exclusive with expression and message. + | *`requiredValue`* __string__ | requiredValue is the value of a required claim. + -Same as --oidc-required-claim flag. + 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. + Mutually exclusive with expression and message. + @@ -119,6 +117,7 @@ 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 to be unique. + +Additionally, the key must not contain an equals sign ("="). + | *`valueExpression`* __string__ | 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. + @@ -244,9 +243,14 @@ unless usernameExpression is specified. + Mutually exclusive with usernameExpression. Use either username or usernameExpression to + determine the user's username from the JWT token. + | *`usernameExpression`* __string__ | usernameExpression represents an expression which will be evaluated by CEL. + - The expression's result will become the user's username. + +usernameExpression is similar to claimMappings.username.expression from Kubernetes AuthenticationConfiguration + +as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication. + +This is an advanced configuration option. During an end-user login flow, each of these CEL expressions + +must evaluate to the expected type without errors, or else the user's login will fail. + +Additionally, mistakes in this configuration can cause the users to have unintended usernames. + + The expression must produce a non-empty string value. + If the expression uses 'claims.email', then 'claims.email_verified' must be used in + the expression or extra[*].valueExpression or claimValidationRules[*].expression. + @@ -271,9 +275,14 @@ unless groupsExpression is specified. + Mutually exclusive with groupsExpression. Use either groups or groupsExpression to + determine the user's group membership from the JWT token. + | *`groupsExpression`* __string__ | groupsExpression represents an expression which will be evaluated by CEL. + - The expression's result will become the user's group memberships. + +groupsExpression is similar to claimMappings.groups.expression from Kubernetes AuthenticationConfiguration + +as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication. + +This is an advanced configuration option. During an end-user login flow, each of these CEL expressions + +must evaluate to one of the expected types without errors, or else the user's login will fail. + +Additionally, mistakes in this configuration can cause the users to have unintended group memberships. + + The expression must produce a string or string array value. + "", [], and null values are treated as the group mapping not being present. + @@ -286,18 +295,44 @@ Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ + Mutually exclusive with groups. Use either groups or groupsExpression to + determine the user's group membership from the JWT token. + -| *`extra`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-33-apis-concierge-authentication-v1alpha1-extramapping[$$ExtraMapping$$] array__ | extra is similar to claimMappings.extra from Kubernetes AuthenticationConfiguration as documented in + -https://kubernetes.io/docs/reference/access-authn-authz/authentication. However, note that the + -Pinniped Concierge issues client certificates to users for the purpose of authenticating, and + -the Kubernetes API server does not have any mechanism for transmitting auth extras via client + -certificates. When configured, these extras will appear in client certificates issued by the + -Pinniped Supervisor in the x509 Subject field as Organizational Units (OU). However, when this + -client certificate is presented to Kubernetes for authentication, Kubernetes will ignore these + -extras. This is probably only useful if you are using a custom authenticating proxy in front + -of your Kubernetes API server which can translate these OUs into auth extras, as described by + +| *`extra`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-33-apis-concierge-authentication-v1alpha1-extramapping[$$ExtraMapping$$] array__ | extra is similar to claimMappings.extra from Kubernetes AuthenticationConfiguration + +as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication. + + +However, note that the Pinniped Concierge issues client certificates to users for the purpose + +of authenticating, and the Kubernetes API server does not have any mechanism for transmitting + +auth extras via client certificates. When configured, these extras will appear in client + +certificates issued by the Pinniped Supervisor in the x509 Subject field as Organizational + +Units (OU). However, when this client certificate is presented to Kubernetes for authentication, + +Kubernetes will ignore these extras. This is probably only useful if you are using a custom + +authenticating proxy in front of your Kubernetes API server which can translate these OUs into + +auth extras, as described by + https://kubernetes.io/docs/reference/access-authn-authz/authentication/#authenticating-proxy. + This is an advanced configuration option. During an end-user login flow, each of these CEL expressions + must evaluate to either a string or an array of strings, or else the user's login will fail. + + +These keys must be a domain-prefixed path (such as "acme.io/foo") and must not contain an equals sign ("="). + + +expression must produce a string or string array value. + +If the value is empty, the extra mapping will not be present. + + +Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ + + +hard-coded extra key/value + +- key: "acme.io/foo" + +valueExpression: "'bar'" + +This will result in an extra attribute - acme.io/foo: ["bar"] + + +hard-coded key, value copying claim value + +- key: "acme.io/foo" + +valueExpression: "claims.some_claim" + +This will result in an extra attribute - acme.io/foo: [value of some_claim] + + +hard-coded key, value derived from claim value + +- key: "acme.io/admin" + +valueExpression: '(has(claims.is_admin) && claims.is_admin) ? "true":""' + +This will result in: + +- if is_admin claim is present and true, extra attribute - acme.io/admin: ["true"] + +- if is_admin claim is present and false or is_admin claim is not present, no extra attribute will be added + |=== diff --git a/generated/latest/apis/concierge/authentication/v1alpha1/types_jwtauthenticator.go b/generated/latest/apis/concierge/authentication/v1alpha1/types_jwtauthenticator.go index 8d416bcbf..6c782b5ce 100644 --- a/generated/latest/apis/concierge/authentication/v1alpha1/types_jwtauthenticator.go +++ b/generated/latest/apis/concierge/authentication/v1alpha1/types_jwtauthenticator.go @@ -77,14 +77,12 @@ type JWTAuthenticatorSpec struct { // ClaimValidationRule provides the configuration for a single claim validation rule. type ClaimValidationRule struct { // claim is the name of a required claim. - // Same as --oidc-required-claim flag. // Only string claim keys are supported. // Mutually exclusive with expression and message. // +optional Claim string `json:"claim,omitempty"` // requiredValue is the value of a required claim. - // Same as --oidc-required-claim flag. // 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. // Mutually exclusive with expression and message. @@ -147,9 +145,14 @@ type JWTTokenClaims struct { Username string `json:"username"` // usernameExpression represents an expression which will be evaluated by CEL. - // // The expression's result will become the user's username. // + // usernameExpression is similar to claimMappings.username.expression from Kubernetes AuthenticationConfiguration + // as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication. + // This is an advanced configuration option. During an end-user login flow, each of these CEL expressions + // must evaluate to the expected type without errors, or else the user's login will fail. + // Additionally, mistakes in this configuration can cause the users to have unintended usernames. + // // The expression must produce a non-empty string value. // If the expression uses 'claims.email', then 'claims.email_verified' must be used in // the expression or extra[*].valueExpression or claimValidationRules[*].expression. @@ -180,9 +183,14 @@ type JWTTokenClaims struct { Groups string `json:"groups"` // groupsExpression represents an expression which will be evaluated by CEL. - // // The expression's result will become the user's group memberships. // + // groupsExpression is similar to claimMappings.groups.expression from Kubernetes AuthenticationConfiguration + // as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication. + // This is an advanced configuration option. During an end-user login flow, each of these CEL expressions + // must evaluate to one of the expected types without errors, or else the user's login will fail. + // Additionally, mistakes in this configuration can cause the users to have unintended group memberships. + // // The expression must produce a string or string array value. // "", [], and null values are treated as the group mapping not being present. // @@ -198,18 +206,45 @@ type JWTTokenClaims struct { // +optional GroupsExpression string `json:"groupsExpression,omitempty"` - // extra is similar to claimMappings.extra from Kubernetes AuthenticationConfiguration as documented in - // https://kubernetes.io/docs/reference/access-authn-authz/authentication. However, note that the - // Pinniped Concierge issues client certificates to users for the purpose of authenticating, and - // the Kubernetes API server does not have any mechanism for transmitting auth extras via client - // certificates. When configured, these extras will appear in client certificates issued by the - // Pinniped Supervisor in the x509 Subject field as Organizational Units (OU). However, when this - // client certificate is presented to Kubernetes for authentication, Kubernetes will ignore these - // extras. This is probably only useful if you are using a custom authenticating proxy in front - // of your Kubernetes API server which can translate these OUs into auth extras, as described by + // extra is similar to claimMappings.extra from Kubernetes AuthenticationConfiguration + // as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication. + // + // However, note that the Pinniped Concierge issues client certificates to users for the purpose + // of authenticating, and the Kubernetes API server does not have any mechanism for transmitting + // auth extras via client certificates. When configured, these extras will appear in client + // certificates issued by the Pinniped Supervisor in the x509 Subject field as Organizational + // Units (OU). However, when this client certificate is presented to Kubernetes for authentication, + // Kubernetes will ignore these extras. This is probably only useful if you are using a custom + // authenticating proxy in front of your Kubernetes API server which can translate these OUs into + // auth extras, as described by // https://kubernetes.io/docs/reference/access-authn-authz/authentication/#authenticating-proxy. // This is an advanced configuration option. During an end-user login flow, each of these CEL expressions // must evaluate to either a string or an array of strings, or else the user's login will fail. + // + // These keys must be a domain-prefixed path (such as "acme.io/foo") and must not contain an equals sign ("="). + // + // expression must produce a string or string array value. + // If the value is empty, the extra mapping will not be present. + // + // Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ + // + // hard-coded extra key/value + // - key: "acme.io/foo" + // valueExpression: "'bar'" + // This will result in an extra attribute - acme.io/foo: ["bar"] + // + // hard-coded key, value copying claim value + // - key: "acme.io/foo" + // valueExpression: "claims.some_claim" + // This will result in an extra attribute - acme.io/foo: [value of some_claim] + // + // hard-coded key, value derived from claim value + // - key: "acme.io/admin" + // valueExpression: '(has(claims.is_admin) && claims.is_admin) ? "true":""' + // This will result in: + // - if is_admin claim is present and true, extra attribute - acme.io/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"` } @@ -222,6 +257,7 @@ type ExtraMapping struct { // be valid HTTP Path characters as defined by RFC 3986. // key must be lowercase. // Required to be unique. + // Additionally, the key must not contain an equals sign ("="). // +required Key string `json:"key"` diff --git a/internal/controller/authenticator/jwtcachefiller/jwtcachefiller.go b/internal/controller/authenticator/jwtcachefiller/jwtcachefiller.go index 1469b69e6..c99a62a0c 100644 --- a/internal/controller/authenticator/jwtcachefiller/jwtcachefiller.go +++ b/internal/controller/authenticator/jwtcachefiller/jwtcachefiller.go @@ -680,13 +680,18 @@ func (c *jwtCacheFillerController) newCachedJWTAuthenticator( for i, mapping := range spec.Claims.Extra { if strings.Contains(mapping.Key, "=") { // Use the same field path that ValidateAuthenticationConfiguration() would build, for consistency. - fieldPath := field.NewPath("jwt").Index(0).Child("claimMappings").Child("extra").Index(i) - errList = append(errList, field.Invalid(fieldPath, "", "Pinniped does not allow extra key names to contain equals sign")) + fieldPath := field.NewPath("jwt").Index(0). + Child("claimMappings"). + Child("extra").Index(i). + Child("key") + errList = append(errList, field.Invalid(fieldPath, mapping.Key, + "Pinniped does not allow extra key names to contain equals sign", + )) } } if errList != nil { - rewriteJWTAuthenticatorErrorPrefixes(errList) + rewriteJWTAuthenticatorErrors(errList) errText := "could not initialize jwt authenticator" err := errList.ToAggregate() @@ -740,18 +745,35 @@ func (c *jwtCacheFillerController) newCachedJWTAuthenticator( }, conditions, nil } -// We don't have any control over the error prefixes created by ValidateAuthenticationConfiguration(), but we +// We don't have any control over the error strings created by ValidateAuthenticationConfiguration(), but we // can rewrite them to make them more consistent with our JWTAuthenticator CRD field names where they don't agree. -func rewriteJWTAuthenticatorErrorPrefixes(errList field.ErrorList) { +func rewriteJWTAuthenticatorErrors(errList field.ErrorList) { // ValidateAuthenticationConfiguration() will prefix all our errors with "jwt[0]." because we always pass it // exactly one jwtAuthenticator to validate. undesirablePrefix := fmt.Sprintf("%s.", field.NewPath("jwt").Index(0).String()) + // Replace these to make the spec names consistent with how they are named in the JWTAuthenticator CRD. + replacements := []struct { + str string + repl string + }{ + // replace these more specific strings first + {str: "claimMappings.username.expression", repl: "claims.usernameExpression"}, + {str: "claimMappings.groups.expression", repl: "claims.groupsExpression"}, + + // then replace these less specific strings (substrings of the strings above) if they still exist + {str: "claimMappings.username", repl: "claims.username"}, + {str: "claimMappings.groups", repl: "claims.groups"}, + + // and also replace this one + {str: "claimMappings.extra", repl: "claims.extra"}, + } + for _, err := range errList { err.Field = strings.TrimPrefix(err.Field, undesirablePrefix) - if strings.HasPrefix(err.Field, "claimMappings.") { - // Pinniped's CRD calls this field "claims". Otherwise, our field names are the same. - err.Field = strings.Replace(err.Field, "claimMappings.", "claims.", 1) + for _, spec := range replacements { + err.Field = strings.ReplaceAll(err.Field, spec.str, spec.repl) + err.Detail = strings.ReplaceAll(err.Detail, spec.str, spec.repl) } } } diff --git a/internal/controller/authenticator/jwtcachefiller/jwtcachefiller_test.go b/internal/controller/authenticator/jwtcachefiller/jwtcachefiller_test.go index 4ea127c3d..877acfe65 100644 --- a/internal/controller/authenticator/jwtcachefiller/jwtcachefiller_test.go +++ b/internal/controller/authenticator/jwtcachefiller/jwtcachefiller_test.go @@ -440,12 +440,24 @@ func TestController(t *testing.T) { GroupsExpression: `["group1"]`, }, } + invalidClaimsUsernameExpressUsesClaimsDotEmailWrongJWTAuthenticatorSpec := &authenticationv1alpha1.JWTAuthenticatorSpec{ + Issuer: goodIssuer, + Audience: goodAudience, + TLS: goodOIDCIssuerServerTLSSpec, + Claims: authenticationv1alpha1.JWTTokenClaims{ + UsernameExpression: "claims.email", + }, + } invalidClaimsExtraContainsEqualSignJWTAuthenticatorSpec := &authenticationv1alpha1.JWTAuthenticatorSpec{ Issuer: goodIssuer, Audience: goodAudience, TLS: goodOIDCIssuerServerTLSSpec, Claims: authenticationv1alpha1.JWTTokenClaims{ Extra: []authenticationv1alpha1.ExtraMapping{ + { + Key: "example.com/legal-key", + ValueExpression: `"extra-value"`, + }, { Key: "example.com/key=contains-equals-sign", // this should cause a validation error in our own code ValueExpression: `"extra-value"`, @@ -2755,6 +2767,55 @@ func TestController(t *testing.T) { } }, }, + { + name: "newCachedJWTAuthenticator: validateAuthenticationConfiguration: when usernameExpression uses claims.email without using claims.email_verified elsewhere: loop will fail sync, will write failed and unknown status conditions, but will not enqueue a resync due to user config error", + jwtAuthenticators: []runtime.Object{ + &authenticationv1alpha1.JWTAuthenticator{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-name", + }, + Spec: *invalidClaimsUsernameExpressUsesClaimsDotEmailWrongJWTAuthenticatorSpec, + }, + }, + wantLogLines: []string{ + fmt.Sprintf(`{"level":"info","timestamp":"2099-08-08T13:57:36.123456Z","logger":"jwtcachefiller-controller","caller":"jwtcachefiller/jwtcachefiller.go:$jwtcachefiller.(*jwtCacheFillerController).syncIndividualJWTAuthenticator","message":"invalid jwt authenticator","jwtAuthenticator":"test-name","issuer":"%s","removedFromCache":false}`, invalidClaimsUsernameExpressUsesClaimsDotEmailWrongJWTAuthenticatorSpec.Issuer), + fmt.Sprintf(`{"level":"debug","timestamp":"2099-08-08T13:57:36.123456Z","logger":"jwtcachefiller-controller","caller":"jwtcachefiller/jwtcachefiller.go:$jwtcachefiller.(*jwtCacheFillerController).updateStatus","message":"jwtauthenticator status successfully updated","jwtAuthenticator":"test-name","issuer":"%s","phase":"Error"}`, invalidClaimsUsernameExpressUsesClaimsDotEmailWrongJWTAuthenticatorSpec.Issuer), + }, + wantActions: func() []coretesting.Action { + updateStatusAction := coretesting.NewUpdateAction(jwtAuthenticatorsGVR, "", &authenticationv1alpha1.JWTAuthenticator{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-name", + }, + Spec: *invalidClaimsUsernameExpressUsesClaimsDotEmailWrongJWTAuthenticatorSpec, + Status: authenticationv1alpha1.JWTAuthenticatorStatus{ + Conditions: conditionstestutil.Replace( + allHappyConditionsSuccess(goodIssuer, frozenMetav1Now, 0), + []metav1.Condition{ + sadReadyCondition(frozenMetav1Now, 0), + happyIssuerURLValid(frozenMetav1Now, 0), + happyDiscoveryURLValid(frozenMetav1Now, 0), + sadAuthenticatorValid( + `could not initialize jwt authenticator: claims.usernameExpression: Invalid value: "claims.email": `+ + `claims.email_verified must be used in claims.usernameExpression or claims.extra[*].valueExpression or `+ + `claimValidationRules[*].expression when claims.email is used in claims.usernameExpression`, + frozenMetav1Now, + 0, + ), + happyJWKSURLValid(frozenMetav1Now, 0), + happyJWKSFetch(frozenMetav1Now, 0), + }, + ), + Phase: "Error", + }, + }) + updateStatusAction.Subresource = "status" + return []coretesting.Action{ + coretesting.NewListAction(jwtAuthenticatorsGVR, jwtAUthenticatorGVK, "", metav1.ListOptions{}), + coretesting.NewWatchAction(jwtAuthenticatorsGVR, "", metav1.ListOptions{Watch: true}), + updateStatusAction, + } + }, + }, { name: "newCachedJWTAuthenticator: when any claims.extra[].key contains an equals sign: loop will fail sync, will write failed and unknown status conditions, but will not enqueue a resync due to user config error", jwtAuthenticators: []runtime.Object{ @@ -2783,7 +2844,7 @@ func TestController(t *testing.T) { happyIssuerURLValid(frozenMetav1Now, 0), happyDiscoveryURLValid(frozenMetav1Now, 0), sadAuthenticatorValid( - `could not initialize jwt authenticator: claims.extra[0]: Invalid value: "": Pinniped does not allow extra key names to contain equals sign`, + `could not initialize jwt authenticator: claims.extra[1].key: Invalid value: "example.com/key=contains-equals-sign": Pinniped does not allow extra key names to contain equals sign`, frozenMetav1Now, 0, ), diff --git a/test/integration/concierge_jwtauthenticator_status_test.go b/test/integration/concierge_jwtauthenticator_status_test.go index 2a34d5816..9049f48e0 100644 --- a/test/integration/concierge_jwtauthenticator_status_test.go +++ b/test/integration/concierge_jwtauthenticator_status_test.go @@ -1,4 +1,4 @@ -// Copyright 2024 the Pinniped contributors. All Rights Reserved. +// Copyright 2024-2025 the Pinniped contributors. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 package integration @@ -322,6 +322,308 @@ func TestConciergeJWTAuthenticatorStatus_Parallel(t *testing.T) { }, ), }, + { + name: "claims cannot use both username and usernameExpression", + spec: authenticationv1alpha1.JWTAuthenticatorSpec{ + Issuer: env.SupervisorUpstreamOIDC.Issuer, + Audience: "some-fake-audience", + TLS: &authenticationv1alpha1.TLSSpec{ + CertificateAuthorityData: base64.StdEncoding.EncodeToString([]byte(env.SupervisorUpstreamOIDC.CABundle)), + }, + Claims: authenticationv1alpha1.JWTTokenClaims{ + Username: "foo", + UsernameExpression: "bar", + }, + }, + wantPhase: authenticationv1alpha1.JWTAuthenticatorPhaseError, + wantConditions: replaceSomeConditions(t, + allSuccessfulJWTAuthenticatorConditions(len(env.SupervisorUpstreamOIDC.CABundle) != 0), + []metav1.Condition{ + { + Type: "Ready", + Status: "False", + Reason: "NotReady", + Message: "the JWTAuthenticator is not ready: see other conditions for details", + }, { + Type: "AuthenticatorValid", + Status: "False", + Reason: "InvalidAuthenticator", + Message: `could not initialize jwt authenticator: claims.username: Invalid value: "": claim and expression can't both be set`, + }, + }, + ), + }, + { + name: "claims cannot use both groups and groupsExpression", + spec: authenticationv1alpha1.JWTAuthenticatorSpec{ + Issuer: env.SupervisorUpstreamOIDC.Issuer, + Audience: "some-fake-audience", + TLS: &authenticationv1alpha1.TLSSpec{ + CertificateAuthorityData: base64.StdEncoding.EncodeToString([]byte(env.SupervisorUpstreamOIDC.CABundle)), + }, + Claims: authenticationv1alpha1.JWTTokenClaims{ + Groups: "foo", + GroupsExpression: "bar", + }, + }, + wantPhase: authenticationv1alpha1.JWTAuthenticatorPhaseError, + wantConditions: replaceSomeConditions(t, + allSuccessfulJWTAuthenticatorConditions(len(env.SupervisorUpstreamOIDC.CABundle) != 0), + []metav1.Condition{ + { + Type: "Ready", + Status: "False", + Reason: "NotReady", + Message: "the JWTAuthenticator is not ready: see other conditions for details", + }, { + Type: "AuthenticatorValid", + Status: "False", + Reason: "InvalidAuthenticator", + Message: `could not initialize jwt authenticator: claims.groups: Invalid value: "": claim and expression can't both be set`, + }, + }, + ), + }, + { + name: "username claim expression cannot use clams.email unless it also uses claims.email_verified elsewhere", + spec: authenticationv1alpha1.JWTAuthenticatorSpec{ + Issuer: env.SupervisorUpstreamOIDC.Issuer, + Audience: "some-fake-audience", + TLS: &authenticationv1alpha1.TLSSpec{ + CertificateAuthorityData: base64.StdEncoding.EncodeToString([]byte(env.SupervisorUpstreamOIDC.CABundle)), + }, + Claims: authenticationv1alpha1.JWTTokenClaims{ + UsernameExpression: "claims.email", + }, + }, + wantPhase: authenticationv1alpha1.JWTAuthenticatorPhaseError, + wantConditions: replaceSomeConditions(t, + allSuccessfulJWTAuthenticatorConditions(len(env.SupervisorUpstreamOIDC.CABundle) != 0), + []metav1.Condition{ + { + Type: "Ready", + Status: "False", + Reason: "NotReady", + Message: "the JWTAuthenticator is not ready: see other conditions for details", + }, { + Type: "AuthenticatorValid", + Status: "False", + Reason: "InvalidAuthenticator", + Message: `could not initialize jwt authenticator: claims.usernameExpression: Invalid value: "claims.email": ` + + `claims.email_verified must be used in claims.usernameExpression or claims.extra[*].valueExpression or ` + + `claimValidationRules[*].expression when claims.email is used in claims.usernameExpression`, + }, + }, + ), + }, + { + name: "username claim expression cannot use invalid CEL expression", + spec: authenticationv1alpha1.JWTAuthenticatorSpec{ + Issuer: env.SupervisorUpstreamOIDC.Issuer, + Audience: "some-fake-audience", + TLS: &authenticationv1alpha1.TLSSpec{ + CertificateAuthorityData: base64.StdEncoding.EncodeToString([]byte(env.SupervisorUpstreamOIDC.CABundle)), + }, + Claims: authenticationv1alpha1.JWTTokenClaims{ + UsernameExpression: "this is not a valid CEL expression", + }, + }, + wantPhase: authenticationv1alpha1.JWTAuthenticatorPhaseError, + wantConditions: replaceSomeConditions(t, + allSuccessfulJWTAuthenticatorConditions(len(env.SupervisorUpstreamOIDC.CABundle) != 0), + []metav1.Condition{ + { + Type: "Ready", + Status: "False", + Reason: "NotReady", + Message: "the JWTAuthenticator is not ready: see other conditions for details", + }, { + Type: "AuthenticatorValid", + Status: "False", + Reason: "InvalidAuthenticator", + Message: "could not initialize jwt authenticator: claims.usernameExpression: Invalid value: " + + "\"this is not a valid CEL expression\": compilation failed: ERROR: :1:6: Syntax error: mismatched input 'is' expecting \n" + + " | this is not a valid CEL expression\n" + + " | .....^", + }, + }, + ), + }, + { + name: "groups claim expression cannot use invalid CEL expression", + spec: authenticationv1alpha1.JWTAuthenticatorSpec{ + Issuer: env.SupervisorUpstreamOIDC.Issuer, + Audience: "some-fake-audience", + TLS: &authenticationv1alpha1.TLSSpec{ + CertificateAuthorityData: base64.StdEncoding.EncodeToString([]byte(env.SupervisorUpstreamOIDC.CABundle)), + }, + Claims: authenticationv1alpha1.JWTTokenClaims{ + GroupsExpression: "this is not a valid CEL expression", + }, + }, + wantPhase: authenticationv1alpha1.JWTAuthenticatorPhaseError, + wantConditions: replaceSomeConditions(t, + allSuccessfulJWTAuthenticatorConditions(len(env.SupervisorUpstreamOIDC.CABundle) != 0), + []metav1.Condition{ + { + Type: "Ready", + Status: "False", + Reason: "NotReady", + Message: "the JWTAuthenticator is not ready: see other conditions for details", + }, { + Type: "AuthenticatorValid", + Status: "False", + Reason: "InvalidAuthenticator", + Message: "could not initialize jwt authenticator: claims.groupsExpression: Invalid value: " + + "\"this is not a valid CEL expression\": compilation failed: ERROR: :1:6: Syntax error: mismatched input 'is' expecting \n" + + " | this is not a valid CEL expression\n" + + " | .....^", + }, + }, + ), + }, + { + name: "extra keys cannot have equal sign and must be domain-prefixed path", + spec: authenticationv1alpha1.JWTAuthenticatorSpec{ + Issuer: env.SupervisorUpstreamOIDC.Issuer, + Audience: "some-fake-audience", + TLS: &authenticationv1alpha1.TLSSpec{ + CertificateAuthorityData: base64.StdEncoding.EncodeToString([]byte(env.SupervisorUpstreamOIDC.CABundle)), + }, + Claims: authenticationv1alpha1.JWTTokenClaims{ + Extra: []authenticationv1alpha1.ExtraMapping{ + { + Key: "a=b", + ValueExpression: `"value"`, + }, + }, + }, + }, + wantPhase: authenticationv1alpha1.JWTAuthenticatorPhaseError, + wantConditions: replaceSomeConditions(t, + allSuccessfulJWTAuthenticatorConditions(len(env.SupervisorUpstreamOIDC.CABundle) != 0), + []metav1.Condition{ + { + Type: "Ready", + Status: "False", + Reason: "NotReady", + Message: "the JWTAuthenticator is not ready: see other conditions for details", + }, { + Type: "AuthenticatorValid", + Status: "False", + Reason: "InvalidAuthenticator", + Message: `could not initialize jwt authenticator: [` + + `claims.extra[0].key: Invalid value: "a=b": must be a domain-prefixed path (such as "acme.io/foo"), ` + + `claims.extra[0].key: Invalid value: "a=b": Pinniped does not allow extra key names to contain equals sign]`, + }, + }, + ), + }, + { + name: "claimValidationRules claim and requiredValue are mutually exclusive with expression and message", + spec: authenticationv1alpha1.JWTAuthenticatorSpec{ + Issuer: env.SupervisorUpstreamOIDC.Issuer, + Audience: "some-fake-audience", + TLS: &authenticationv1alpha1.TLSSpec{ + CertificateAuthorityData: base64.StdEncoding.EncodeToString([]byte(env.SupervisorUpstreamOIDC.CABundle)), + }, + ClaimValidationRules: []authenticationv1alpha1.ClaimValidationRule{ + { + Claim: "foo", + RequiredValue: "bar", + Expression: "baz", + Message: "bat", + }, + }, + }, + wantPhase: authenticationv1alpha1.JWTAuthenticatorPhaseError, + wantConditions: replaceSomeConditions(t, + allSuccessfulJWTAuthenticatorConditions(len(env.SupervisorUpstreamOIDC.CABundle) != 0), + []metav1.Condition{ + { + Type: "Ready", + Status: "False", + Reason: "NotReady", + Message: "the JWTAuthenticator is not ready: see other conditions for details", + }, { + Type: "AuthenticatorValid", + Status: "False", + Reason: "InvalidAuthenticator", + Message: `could not initialize jwt authenticator: claimValidationRules[0]: Invalid value: "foo": claim and expression can't both be set`, + }, + }, + ), + }, + { + name: "claimValidationRules cannot use invalid CEL expressions", + spec: authenticationv1alpha1.JWTAuthenticatorSpec{ + Issuer: env.SupervisorUpstreamOIDC.Issuer, + Audience: "some-fake-audience", + TLS: &authenticationv1alpha1.TLSSpec{ + CertificateAuthorityData: base64.StdEncoding.EncodeToString([]byte(env.SupervisorUpstreamOIDC.CABundle)), + }, + ClaimValidationRules: []authenticationv1alpha1.ClaimValidationRule{ + { + Expression: "this is not a valid CEL expression", + }, + }, + }, + wantPhase: authenticationv1alpha1.JWTAuthenticatorPhaseError, + wantConditions: replaceSomeConditions(t, + allSuccessfulJWTAuthenticatorConditions(len(env.SupervisorUpstreamOIDC.CABundle) != 0), + []metav1.Condition{ + { + Type: "Ready", + Status: "False", + Reason: "NotReady", + Message: "the JWTAuthenticator is not ready: see other conditions for details", + }, { + Type: "AuthenticatorValid", + Status: "False", + Reason: "InvalidAuthenticator", + Message: "could not initialize jwt authenticator: claimValidationRules[0].expression: Invalid value: " + + "\"this is not a valid CEL expression\": compilation failed: ERROR: :1:6: Syntax error: mismatched input 'is' expecting \n" + + " | this is not a valid CEL expression\n" + + " | .....^", + }, + }, + ), + }, + { + name: "userValidationRules must use valid CEL expressions", + spec: authenticationv1alpha1.JWTAuthenticatorSpec{ + Issuer: env.SupervisorUpstreamOIDC.Issuer, + Audience: "some-fake-audience", + TLS: &authenticationv1alpha1.TLSSpec{ + CertificateAuthorityData: base64.StdEncoding.EncodeToString([]byte(env.SupervisorUpstreamOIDC.CABundle)), + }, + UserValidationRules: []authenticationv1alpha1.UserValidationRule{ + { + Expression: "this is not a valid CEL expression", + }, + }, + }, + wantPhase: authenticationv1alpha1.JWTAuthenticatorPhaseError, + wantConditions: replaceSomeConditions(t, + allSuccessfulJWTAuthenticatorConditions(len(env.SupervisorUpstreamOIDC.CABundle) != 0), + []metav1.Condition{ + { + Type: "Ready", + Status: "False", + Reason: "NotReady", + Message: "the JWTAuthenticator is not ready: see other conditions for details", + }, { + Type: "AuthenticatorValid", + Status: "False", + Reason: "InvalidAuthenticator", + Message: "could not initialize jwt authenticator: userValidationRules[0].expression: Invalid value: " + + "\"this is not a valid CEL expression\": compilation failed: ERROR: :1:6: Syntax error: mismatched input 'is' expecting \n" + + " | this is not a valid CEL expression\n" + + " | .....^", + }, + }, + ), + }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { @@ -479,40 +781,42 @@ func allSuccessfulJWTAuthenticatorConditions(caBundleExists bool) []metav1.Condi if caBundleExists { tlsConfigValidMsg = "spec.tls is valid: using configured CA bundle" } - return []metav1.Condition{{ - Type: "AuthenticatorValid", - Status: "True", - Reason: "Success", - Message: "authenticator initialized", - }, { - Type: "DiscoveryURLValid", - Status: "True", - Reason: "Success", - Message: "discovery performed successfully", - }, { - Type: "IssuerURLValid", - Status: "True", - Reason: "Success", - Message: "issuer is a valid URL", - }, { - Type: "JWKSFetchValid", - Status: "True", - Reason: "Success", - Message: "successfully fetched jwks", - }, { - Type: "JWKSURLValid", - Status: "True", - Reason: "Success", - Message: "jwks_uri is a valid URL", - }, { - Type: "Ready", - Status: "True", - Reason: "Success", - Message: "the JWTAuthenticator is ready", - }, { - Type: "TLSConfigurationValid", - Status: "True", - Reason: "Success", - Message: tlsConfigValidMsg, - }} + return []metav1.Condition{ + { + Type: "AuthenticatorValid", + Status: "True", + Reason: "Success", + Message: "authenticator initialized", + }, { + Type: "DiscoveryURLValid", + Status: "True", + Reason: "Success", + Message: "discovery performed successfully", + }, { + Type: "IssuerURLValid", + Status: "True", + Reason: "Success", + Message: "issuer is a valid URL", + }, { + Type: "JWKSFetchValid", + Status: "True", + Reason: "Success", + Message: "successfully fetched jwks", + }, { + Type: "JWKSURLValid", + Status: "True", + Reason: "Success", + Message: "jwks_uri is a valid URL", + }, { + Type: "Ready", + Status: "True", + Reason: "Success", + Message: "the JWTAuthenticator is ready", + }, { + Type: "TLSConfigurationValid", + Status: "True", + Reason: "Success", + Message: tlsConfigValidMsg, + }, + } } From 3c281715250f6d0bae22647b934510d17dc3f1d4 Mon Sep 17 00:00:00 2001 From: Ryan Richard Date: Fri, 18 Jul 2025 12:49:58 -0700 Subject: [PATCH 6/7] account for change in err msg starting in Kube 1.34 beta version --- ...supervisor_federationdomain_status_test.go | 48 ++++++++++++++----- 1 file changed, 35 insertions(+), 13 deletions(-) diff --git a/test/integration/supervisor_federationdomain_status_test.go b/test/integration/supervisor_federationdomain_status_test.go index 745076347..89d26859b 100644 --- a/test/integration/supervisor_federationdomain_status_test.go +++ b/test/integration/supervisor_federationdomain_status_test.go @@ -555,6 +555,7 @@ func TestSupervisorFederationDomainStatus_Disruptive(t *testing.T) { } } +//nolint:gocyclo // we have a lot of "if" conditions here, but it's okay func TestSupervisorFederationDomainCRDValidations_Parallel(t *testing.T) { env := testlib.IntegrationEnv(t) fdClient := testlib.NewSupervisorClientset(t).ConfigV1alpha1().FederationDomains(env.SupervisorNamespace) @@ -566,7 +567,10 @@ func TestSupervisorFederationDomainCRDValidations_Parallel(t *testing.T) { // Certain non-CEL validation failures will prevent CEL validations from running, // and the Kubernetes API server will return this error message for those cases. - const couldNotRunCELValidationsErrMessage = `: Invalid value: "null": some validation rules were not checked because the object was invalid; correct the existing errors to complete validation` + const couldNotRunCELValidationsErrMessage = "some validation rules were not checked because the object was invalid; correct the existing errors to complete validation" + const oldCouldNotRunCELValidationsErrMessage = `: Invalid value: "null": ` + couldNotRunCELValidationsErrMessage + // Starting in beta version of Kube 1.34, they removed the quotes around the null in this message. + const newCouldNotRunCELValidationsErrMessage = `: Invalid value: null: ` + couldNotRunCELValidationsErrMessage tests := []struct { name string @@ -576,11 +580,14 @@ func TestSupervisorFederationDomainCRDValidations_Parallel(t *testing.T) { // optionally override wantErr for one or more specific versions of Kube, due to changing validation error text wantKube23OrOlderErrs []string wantKube24Through31InclusiveErrs []string - wantKube32OrNewerErrs []string + wantKube32Through33InclusiveErrs []string + wantKube34OrNewerErrs []string // These errors are appended to any other wanted errors when k8sAPIServerSupportsCEL is true wantCELErrorsForKube25Through28Inclusive []string + wantCELErrorsForKube29Through33Inclusive []string wantCELErrorsForKube29OrNewer []string + wantCELErrorsForKube34OrNewer []string }{ { name: "issuer cannot be empty", @@ -644,6 +651,7 @@ func TestSupervisorFederationDomainCRDValidations_Parallel(t *testing.T) { // For some unknown reason, Kubernetes versions 1.23 and older return errors *with indices* for this test case only. wantKube23OrOlderErrs: []string{`spec.identityProviders[0].transforms.constants[1]: Duplicate value: map[string]interface {}{"name":"notUnique"}`}, wantErrs: []string{`spec.identityProviders[0].transforms.constants[1]: Duplicate value: map[string]interface {}{"name":"notUnique"}`}, + wantKube34OrNewerErrs: []string{`spec.identityProviders[0].transforms.constants[1]: Duplicate value: {"name":"notUnique"}`}, }, { name: "IDP transform constant names cannot be empty", @@ -685,9 +693,10 @@ func TestSupervisorFederationDomainCRDValidations_Parallel(t *testing.T) { }, wantKube23OrOlderErrs: []string{`spec.identityProviders.transforms.constants.name: Invalid value: "12345678901234567890123456789012345678901234567890123456789012345": spec.identityProviders.transforms.constants.name in body should be at most 64 chars long`}, wantKube24Through31InclusiveErrs: []string{`spec.identityProviders[0].transforms.constants[0].name: Too long: may not be longer than 64`}, - wantKube32OrNewerErrs: []string{`spec.identityProviders[0].transforms.constants[0].name: Too long: may not be more than 64 bytes`}, - wantCELErrorsForKube25Through28Inclusive: []string{couldNotRunCELValidationsErrMessage}, - wantCELErrorsForKube29OrNewer: []string{couldNotRunCELValidationsErrMessage}, + wantErrs: []string{`spec.identityProviders[0].transforms.constants[0].name: Too long: may not be more than 64 bytes`}, + wantCELErrorsForKube25Through28Inclusive: []string{oldCouldNotRunCELValidationsErrMessage}, + wantCELErrorsForKube29Through33Inclusive: []string{oldCouldNotRunCELValidationsErrMessage}, + wantCELErrorsForKube34OrNewer: []string{newCouldNotRunCELValidationsErrMessage}, }, { name: "IDP transform constant names must be a legal CEL variable name", @@ -737,8 +746,9 @@ func TestSupervisorFederationDomainCRDValidations_Parallel(t *testing.T) { }, }, }, - wantErrs: []string{`spec.identityProviders[0].transforms.constants[0].type: Unsupported value: "this is invalid": supported values: "string", "stringList"`}, - wantCELErrorsForKube29OrNewer: []string{couldNotRunCELValidationsErrMessage}, // this should not be checked on kind 1.25, 1.26, 1.27, 1.28 + wantErrs: []string{`spec.identityProviders[0].transforms.constants[0].type: Unsupported value: "this is invalid": supported values: "string", "stringList"`}, + wantCELErrorsForKube29Through33Inclusive: []string{oldCouldNotRunCELValidationsErrMessage}, + wantCELErrorsForKube34OrNewer: []string{newCouldNotRunCELValidationsErrMessage}, }, { name: "IDP transform expression types must be one of the allowed enum strings", @@ -761,8 +771,9 @@ func TestSupervisorFederationDomainCRDValidations_Parallel(t *testing.T) { }, }, }, - wantErrs: []string{`spec.identityProviders[0].transforms.expressions[0].type: Unsupported value: "this is invalid": supported values: "policy/v1", "username/v1", "groups/v1"`}, - wantCELErrorsForKube29OrNewer: []string{couldNotRunCELValidationsErrMessage}, // this should not be checked on kind 1.25, 1.26, 1.27, 1.28 + wantErrs: []string{`spec.identityProviders[0].transforms.expressions[0].type: Unsupported value: "this is invalid": supported values: "policy/v1", "username/v1", "groups/v1"`}, + wantCELErrorsForKube29Through33Inclusive: []string{oldCouldNotRunCELValidationsErrMessage}, + wantCELErrorsForKube34OrNewer: []string{newCouldNotRunCELValidationsErrMessage}, }, { name: "IDP transform expressions cannot be empty", @@ -872,7 +883,7 @@ func TestSupervisorFederationDomainCRDValidations_Parallel(t *testing.T) { } }) - if len(tt.wantErrs) > 0 && len(tt.wantKube23OrOlderErrs) > 0 && len(tt.wantKube24Through31InclusiveErrs) > 0 && len(tt.wantKube32OrNewerErrs) > 0 { + if len(tt.wantErrs) > 0 && len(tt.wantKube23OrOlderErrs) > 0 && len(tt.wantKube24Through31InclusiveErrs) > 0 && len(tt.wantKube32Through33InclusiveErrs) > 0 && len(tt.wantKube34OrNewerErrs) > 0 { require.Fail(t, "test setup problem: wanted every possible kind of error, which would cause tt.wantErr to be unused") } @@ -900,16 +911,27 @@ func TestSupervisorFederationDomainCRDValidations_Parallel(t *testing.T) { // Also allow overriding with an exact expected error for these Kube versions. wantErr = tt.wantKube24Through31InclusiveErrs } - if minor >= 32 && len(tt.wantKube32OrNewerErrs) > 0 { + if minor >= 32 && minor <= 33 && len(tt.wantKube32Through33InclusiveErrs) > 0 { // Also allow overriding with an exact expected error for these Kube versions. - wantErr = tt.wantKube32OrNewerErrs + wantErr = tt.wantKube32Through33InclusiveErrs + } + if minor >= 34 && len(tt.wantKube34OrNewerErrs) > 0 { + // Also allow overriding with an exact expected error for these Kube versions. + wantErr = tt.wantKube34OrNewerErrs } if minor >= 25 && minor <= 28 && len(tt.wantCELErrorsForKube25Through28Inclusive) > 0 { wantErr = append(wantErr, tt.wantCELErrorsForKube25Through28Inclusive...) - } else if minor >= 29 && len(tt.wantCELErrorsForKube29OrNewer) > 0 { + } + if minor >= 29 && minor <= 33 && len(tt.wantCELErrorsForKube29Through33Inclusive) > 0 { + wantErr = append(wantErr, tt.wantCELErrorsForKube29Through33Inclusive...) + } + if minor >= 29 && len(tt.wantCELErrorsForKube29OrNewer) > 0 { wantErr = append(wantErr, tt.wantCELErrorsForKube29OrNewer...) } + if minor >= 34 && len(tt.wantCELErrorsForKube34OrNewer) > 0 { + wantErr = append(wantErr, tt.wantCELErrorsForKube34OrNewer...) + } // Did not want any error. if len(wantErr) == 0 { From 6d8ad5f7a921599735ac44256ebc430f9b3aa404 Mon Sep 17 00:00:00 2001 From: Ryan Richard Date: Mon, 21 Jul 2025 12:44:36 -0700 Subject: [PATCH 7/7] fix install-linter.sh for when there is no toolchain in go.mod --- hack/install-linter.sh | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/hack/install-linter.sh b/hack/install-linter.sh index 3e7aebaa9..962e195a4 100755 --- a/hack/install-linter.sh +++ b/hack/install-linter.sh @@ -5,7 +5,7 @@ set -euo pipefail -ROOT="$( cd "$( dirname "${BASH_SOURCE[0]}" )/.." && pwd )" +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" cd "${ROOT}" # Print the Go version. @@ -15,6 +15,17 @@ lint_version="v$(cat hack/lib/lint-version.txt)" # Find the toolchain version from our go.mod file. "go install" pays attention to $GOTOOLCHAIN. GOTOOLCHAIN=$(sed -rn 's/^toolchain (go[0-9\.]+)$/\1/p' go.mod) +if [[ -z "$GOTOOLCHAIN" ]]; then + # Did not find toolchain directive. The directive is not needed in a go.mod file when it would be the same + # version as the go directive, so it will not always be there. Try using go directive instead. + GOTOOLCHAIN=$(sed -rn 's/^go ([0-9]+\.[0-9]+\.[0-9]+)$/\1/p' go.mod) + if [[ -z "$GOTOOLCHAIN" ]]; then + echo "ERROR: Could not find Go patch version from go.mod file." + exit 1 + fi + GOTOOLCHAIN="go${GOTOOLCHAIN}" +fi + export GOTOOLCHAIN echo "Installing golangci-lint@${lint_version} using toolchain ${GOTOOLCHAIN}" @@ -22,6 +33,9 @@ echo "Installing golangci-lint@${lint_version} using toolchain ${GOTOOLCHAIN}" # Install the same version of the linter that the pipelines will use # so you can get the same results when running the linter locally. go install -v "github.com/golangci/golangci-lint/v2/cmd/golangci-lint@${lint_version}" + +echo "Finished installing golangci-lint@${lint_version} using toolchain ${GOTOOLCHAIN}" + golangci-lint --version echo "Finished. You may need to run 'rehash' in your current shell before using the new version (e.g. if you are using gvm)."