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.