Merge pull request #2491 from vmware/jwtauthenticator_new_features
Some checks failed
CodeQL / Analyze (go) (push) Failing after 3m35s
CodeQL / Analyze (javascript) (push) Failing after 2m21s

add new features in JWTAuthenticator CRD
This commit is contained in:
Ryan Richard
2025-07-25 13:17:15 -07:00
committed by GitHub
75 changed files with 8994 additions and 765 deletions

View File

@@ -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,255 @@ 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.
// 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.
// 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
// group membership from the JWT token. When not specified, it will default to "groups".
// username is the name of the claim which should be read to extract the
// username from the JWT token. When not specified, it will default to "username",
// unless usernameExpression is specified.
//
// Mutually exclusive with usernameExpression. Use either username or usernameExpression to
// determine the user's username from the JWT token.
// +optional
Username string `json:"username"`
// usernameExpression represents an expression which will be evaluated by CEL.
// The expression's result will become the user's username.
//
// usernameExpression is similar to claimMappings.username.expression from Kubernetes AuthenticationConfiguration
// as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication.
// This is an advanced configuration option. During an end-user login flow, each of these CEL expressions
// must evaluate to the expected type without errors, or else the user's login will fail.
// Additionally, mistakes in this configuration can cause the users to have unintended usernames.
//
// The expression must produce a non-empty string value.
// If the expression uses 'claims.email', then 'claims.email_verified' must be used in
// the expression or extra[*].valueExpression or claimValidationRules[*].expression.
// An example claim validation rule expression that matches the validation automatically
// applied when username.claim is set to 'email' is 'claims.?email_verified.orValue(true) == true'.
// By explicitly comparing the value to true, we let type-checking see the result will be a boolean,
// and to make sure a non-boolean email_verified claim will be caught at runtime.
//
// CEL expressions have access to the contents of the token claims, organized into CEL variable:
// - 'claims' is a map of claim names to claim values.
// For example, a variable named 'sub' can be accessed as 'claims.sub'.
// Nested claims can be accessed using dot notation, e.g. 'claims.foo.bar'.
//
// Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/
//
// Mutually exclusive with username. Use either username or usernameExpression to
// determine the user's username from the JWT token.
// +optional
UsernameExpression string `json:"usernameExpression,omitempty"`
// groups is the name of the claim which should be read to extract the user's
// group membership from the JWT token. When not specified, it will default to "groups",
// unless groupsExpression is specified.
//
// Mutually exclusive with groupsExpression. Use either groups or groupsExpression to
// determine the user's group membership from the JWT token.
// +optional
Groups string `json:"groups"`
// Username is the name of the claim which should be read to extract the
// username from the JWT token. When not specified, it will default to "username".
// groupsExpression represents an expression which will be evaluated by CEL.
// The expression's result will become the user's group memberships.
//
// groupsExpression is similar to claimMappings.groups.expression from Kubernetes AuthenticationConfiguration
// as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication.
// This is an advanced configuration option. During an end-user login flow, each of these CEL expressions
// must evaluate to one of the expected types without errors, or else the user's login will fail.
// Additionally, mistakes in this configuration can cause the users to have unintended group memberships.
//
// The expression must produce a string or string array value.
// "", [], and null values are treated as the group mapping not being present.
//
// CEL expressions have access to the contents of the token claims, organized into CEL variable:
// - 'claims' is a map of claim names to claim values.
// For example, a variable named 'sub' can be accessed as 'claims.sub'.
// Nested claims can be accessed using dot notation, e.g. 'claims.foo.bar'.
//
// Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/
//
// Mutually exclusive with groups. Use either groups or groupsExpression to
// determine the user's group membership from the JWT token.
// +optional
Username string `json:"username"`
GroupsExpression string `json:"groupsExpression,omitempty"`
// extra is similar to claimMappings.extra from Kubernetes AuthenticationConfiguration
// as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication.
//
// However, note that the Pinniped Concierge issues client certificates to users for the purpose
// of authenticating, and the Kubernetes API server does not have any mechanism for transmitting
// auth extras via client certificates. When configured, these extras will appear in client
// certificates issued by the Pinniped Supervisor in the x509 Subject field as Organizational
// Units (OU). However, when this client certificate is presented to Kubernetes for authentication,
// Kubernetes will ignore these extras. This is probably only useful if you are using a custom
// authenticating proxy in front of your Kubernetes API server which can translate these OUs into
// auth extras, as described by
// https://kubernetes.io/docs/reference/access-authn-authz/authentication/#authenticating-proxy.
// This is an advanced configuration option. During an end-user login flow, each of these CEL expressions
// must evaluate to either a string or an array of strings, or else the user's login will fail.
//
// These keys must be a domain-prefixed path (such as "acme.io/foo") and must not contain an equals sign ("=").
//
// expression must produce a string or string array value.
// If the value is empty, the extra mapping will not be present.
//
// Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/
//
// hard-coded extra key/value
// - key: "acme.io/foo"
// valueExpression: "'bar'"
// This will result in an extra attribute - acme.io/foo: ["bar"]
//
// hard-coded key, value copying claim value
// - key: "acme.io/foo"
// valueExpression: "claims.some_claim"
// This will result in an extra attribute - acme.io/foo: [value of some_claim]
//
// hard-coded key, value derived from claim value
// - key: "acme.io/admin"
// valueExpression: '(has(claims.is_admin) && claims.is_admin) ? "true":""'
// This will result in:
// - if is_admin claim is present and true, extra attribute - acme.io/admin: ["true"]
// - if is_admin claim is present and false or is_admin claim is not present, no extra attribute will be added
//
// +optional
Extra []ExtraMapping `json:"extra,omitempty"`
}
// 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.
// Additionally, the key must not contain an equals sign ("=").
// +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 +295,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"`

View File

@@ -58,37 +58,219 @@ 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.
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.
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.
These keys must be a domain-prefixed path (such as "acme.io/foo") and must not contain an equals sign ("=").
expression must produce a string or string array value.
If the value is empty, the extra mapping will not be present.
Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/
hard-coded extra key/value
- key: "acme.io/foo"
valueExpression: "'bar'"
This will result in an extra attribute - acme.io/foo: ["bar"]
hard-coded key, value copying claim value
- key: "acme.io/foo"
valueExpression: "claims.some_claim"
This will result in an extra attribute - acme.io/foo: [value of some_claim]
hard-coded key, value derived from claim value
- key: "acme.io/admin"
valueExpression: '(has(claims.is_admin) && claims.is_admin) ? "true":""'
This will result in:
- if is_admin claim is present and true, extra attribute - acme.io/admin: ["true"]
- if is_admin claim is present and false or is_admin claim is not present, no extra attribute will be added
items:
description: ExtraMapping provides the configuration for a single
extra mapping.
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.
Additionally, the key must not contain an equals sign ("=").
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
group membership from the JWT token. When not specified, it will default to "groups".
groups is the name of the claim which should be read to extract the user's
group membership from the JWT token. When not specified, it will default to "groups",
unless groupsExpression is specified.
Mutually exclusive with groupsExpression. Use either groups or groupsExpression to
determine the user's group membership from the JWT token.
type: string
groupsExpression:
description: |-
groupsExpression represents an expression which will be evaluated by CEL.
The expression's result will become the user's group memberships.
groupsExpression is similar to claimMappings.groups.expression from Kubernetes AuthenticationConfiguration
as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication.
This is an advanced configuration option. During an end-user login flow, each of these CEL expressions
must evaluate to one of the expected types without errors, or else the user's login will fail.
Additionally, mistakes in this configuration can cause the users to have unintended group memberships.
The expression must produce a string or string array value.
"", [], and null values are treated as the group mapping not being present.
CEL expressions have access to the contents of the token claims, organized into CEL variable:
- 'claims' is a map of claim names to claim values.
For example, a variable named 'sub' can be accessed as 'claims.sub'.
Nested claims can be accessed using dot notation, e.g. 'claims.foo.bar'.
Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/
Mutually exclusive with groups. Use either groups or groupsExpression to
determine the user's group membership from the JWT token.
type: string
username:
description: |-
Username is the name of the claim which should be read to extract the
username from the JWT token. When not specified, it will default to "username".
username is the name of the claim which should be read to extract the
username from the JWT token. When not specified, it will default to "username",
unless usernameExpression is specified.
Mutually exclusive with usernameExpression. Use either username or usernameExpression to
determine the user's username from the JWT token.
type: string
usernameExpression:
description: |-
usernameExpression represents an expression which will be evaluated by CEL.
The expression's result will become the user's username.
usernameExpression is similar to claimMappings.username.expression from Kubernetes AuthenticationConfiguration
as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication.
This is an advanced configuration option. During an end-user login flow, each of these CEL expressions
must evaluate to the expected type without errors, or else the user's login will fail.
Additionally, mistakes in this configuration can cause the users to have unintended usernames.
The expression must produce a non-empty string value.
If the expression uses 'claims.email', then 'claims.email_verified' must be used in
the expression or extra[*].valueExpression or claimValidationRules[*].expression.
An example claim validation rule expression that matches the validation automatically
applied when username.claim is set to 'email' is 'claims.?email_verified.orValue(true) == true'.
By explicitly comparing the value to true, we let type-checking see the result will be a boolean,
and to make sure a non-boolean email_verified claim will be caught at runtime.
CEL expressions have access to the contents of the token claims, organized into CEL variable:
- 'claims' is a map of claim names to claim values.
For example, a variable named 'sub' can be accessed as 'claims.sub'.
Nested claims can be accessed using dot notation, e.g. 'claims.foo.bar'.
Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/
Mutually exclusive with username. Use either username or usernameExpression to
determine the user's username from the JWT token.
type: string
type: object
issuer:
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 +310,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

View File

@@ -60,6 +60,78 @@ 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. +
Only string claim keys are supported. +
Mutually exclusive with expression and message. +
| *`requiredValue`* __string__ | requiredValue is the value of a required claim. +
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. +
Additionally, the key must not contain an equals sign ("="). +
| *`valueExpression`* __string__ | valueExpression is a CEL expression to extract extra attribute value. +
valueExpression must produce a string or string array value. +
"", [], and null values are treated as the extra mapping not being present. +
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 +150,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 +172,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 +182,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 +236,103 @@ for Kubernetes access.
[cols="25a,75a", options="header"]
|===
| Field | Description
| *`groups`* __string__ | Groups is the name of the claim which should be read to extract the user's +
group membership from the JWT token. When not specified, it will default to "groups". +
| *`username`* __string__ | Username is the name of the claim which should be read to extract the +
username from the JWT token. When not specified, it will default to "username". +
| *`username`* __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", +
unless usernameExpression is specified. +
Mutually exclusive with usernameExpression. Use either username or usernameExpression to +
determine the user's username from the JWT token. +
| *`usernameExpression`* __string__ | usernameExpression represents an expression which will be evaluated by CEL. +
The expression's result will become the user's username. +
usernameExpression is similar to claimMappings.username.expression from Kubernetes AuthenticationConfiguration +
as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication. +
This is an advanced configuration option. During an end-user login flow, each of these CEL expressions +
must evaluate to the expected type without errors, or else the user's login will fail. +
Additionally, mistakes in this configuration can cause the users to have unintended usernames. +
The expression must produce a non-empty string value. +
If the expression uses 'claims.email', then 'claims.email_verified' must be used in +
the expression or extra[*].valueExpression or claimValidationRules[*].expression. +
An example claim validation rule expression that matches the validation automatically +
applied when username.claim is set to 'email' is 'claims.?email_verified.orValue(true) == true'. +
By explicitly comparing the value to true, we let type-checking see the result will be a boolean, +
and to make sure a non-boolean email_verified claim will be caught at runtime. +
CEL expressions have access to the contents of the token claims, organized into CEL variable: +
- 'claims' is a map of claim names to claim values. +
For example, a variable named 'sub' can be accessed as 'claims.sub'. +
Nested claims can be accessed using dot notation, e.g. 'claims.foo.bar'. +
Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ +
Mutually exclusive with username. Use either username or usernameExpression to +
determine the user's username from the JWT token. +
| *`groups`* __string__ | groups is the name of the claim which should be read to extract the user's +
group membership from the JWT token. When not specified, it will default to "groups", +
unless groupsExpression is specified. +
Mutually exclusive with groupsExpression. Use either groups or groupsExpression to +
determine the user's group membership from the JWT token. +
| *`groupsExpression`* __string__ | groupsExpression represents an expression which will be evaluated by CEL. +
The expression's result will become the user's group memberships. +
groupsExpression is similar to claimMappings.groups.expression from Kubernetes AuthenticationConfiguration +
as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication. +
This is an advanced configuration option. During an end-user login flow, each of these CEL expressions +
must evaluate to one of the expected types without errors, or else the user's login will fail. +
Additionally, mistakes in this configuration can cause the users to have unintended group memberships. +
The expression must produce a string or string array value. +
"", [], and null values are treated as the group mapping not being present. +
CEL expressions have access to the contents of the token claims, organized into CEL variable: +
- 'claims' is a map of claim names to claim values. +
For example, a variable named 'sub' can be accessed as 'claims.sub'. +
Nested claims can be accessed using dot notation, e.g. 'claims.foo.bar'. +
Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ +
Mutually exclusive with groups. Use either groups or groupsExpression to +
determine the user's group membership from the JWT token. +
| *`extra`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-26-apis-concierge-authentication-v1alpha1-extramapping[$$ExtraMapping$$] array__ | extra is similar to claimMappings.extra from Kubernetes AuthenticationConfiguration +
as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication. +
However, note that the Pinniped Concierge issues client certificates to users for the purpose +
of authenticating, and the Kubernetes API server does not have any mechanism for transmitting +
auth extras via client certificates. When configured, these extras will appear in client +
certificates issued by the Pinniped Supervisor in the x509 Subject field as Organizational +
Units (OU). However, when this client certificate is presented to Kubernetes for authentication, +
Kubernetes will ignore these extras. This is probably only useful if you are using a custom +
authenticating proxy in front of your Kubernetes API server which can translate these OUs into +
auth extras, as described by +
https://kubernetes.io/docs/reference/access-authn-authz/authentication/#authenticating-proxy. +
This is an advanced configuration option. During an end-user login flow, each of these CEL expressions +
must evaluate to either a string or an array of strings, or else the user's login will fail. +
These keys must be a domain-prefixed path (such as "acme.io/foo") and must not contain an equals sign ("="). +
expression must produce a string or string array value. +
If the value is empty, the extra mapping will not be present. +
Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ +
hard-coded extra key/value +
- key: "acme.io/foo" +
valueExpression: "'bar'" +
This will result in an extra attribute - acme.io/foo: ["bar"] +
hard-coded key, value copying claim value +
- key: "acme.io/foo" +
valueExpression: "claims.some_claim" +
This will result in an extra attribute - acme.io/foo: [value of some_claim] +
hard-coded key, value derived from claim value +
- key: "acme.io/admin" +
valueExpression: '(has(claims.is_admin) && claims.is_admin) ? "true":""' +
This will result in: +
- if is_admin claim is present and true, extra attribute - acme.io/admin: ["true"] +
- if is_admin claim is present and false or is_admin claim is not present, no extra attribute will be added +
|===
@@ -178,6 +356,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

View File

@@ -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,255 @@ 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.
// 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.
// 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
// group membership from the JWT token. When not specified, it will default to "groups".
// username is the name of the claim which should be read to extract the
// username from the JWT token. When not specified, it will default to "username",
// unless usernameExpression is specified.
//
// Mutually exclusive with usernameExpression. Use either username or usernameExpression to
// determine the user's username from the JWT token.
// +optional
Username string `json:"username"`
// usernameExpression represents an expression which will be evaluated by CEL.
// The expression's result will become the user's username.
//
// usernameExpression is similar to claimMappings.username.expression from Kubernetes AuthenticationConfiguration
// as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication.
// This is an advanced configuration option. During an end-user login flow, each of these CEL expressions
// must evaluate to the expected type without errors, or else the user's login will fail.
// Additionally, mistakes in this configuration can cause the users to have unintended usernames.
//
// The expression must produce a non-empty string value.
// If the expression uses 'claims.email', then 'claims.email_verified' must be used in
// the expression or extra[*].valueExpression or claimValidationRules[*].expression.
// An example claim validation rule expression that matches the validation automatically
// applied when username.claim is set to 'email' is 'claims.?email_verified.orValue(true) == true'.
// By explicitly comparing the value to true, we let type-checking see the result will be a boolean,
// and to make sure a non-boolean email_verified claim will be caught at runtime.
//
// CEL expressions have access to the contents of the token claims, organized into CEL variable:
// - 'claims' is a map of claim names to claim values.
// For example, a variable named 'sub' can be accessed as 'claims.sub'.
// Nested claims can be accessed using dot notation, e.g. 'claims.foo.bar'.
//
// Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/
//
// Mutually exclusive with username. Use either username or usernameExpression to
// determine the user's username from the JWT token.
// +optional
UsernameExpression string `json:"usernameExpression,omitempty"`
// groups is the name of the claim which should be read to extract the user's
// group membership from the JWT token. When not specified, it will default to "groups",
// unless groupsExpression is specified.
//
// Mutually exclusive with groupsExpression. Use either groups or groupsExpression to
// determine the user's group membership from the JWT token.
// +optional
Groups string `json:"groups"`
// Username is the name of the claim which should be read to extract the
// username from the JWT token. When not specified, it will default to "username".
// groupsExpression represents an expression which will be evaluated by CEL.
// The expression's result will become the user's group memberships.
//
// groupsExpression is similar to claimMappings.groups.expression from Kubernetes AuthenticationConfiguration
// as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication.
// This is an advanced configuration option. During an end-user login flow, each of these CEL expressions
// must evaluate to one of the expected types without errors, or else the user's login will fail.
// Additionally, mistakes in this configuration can cause the users to have unintended group memberships.
//
// The expression must produce a string or string array value.
// "", [], and null values are treated as the group mapping not being present.
//
// CEL expressions have access to the contents of the token claims, organized into CEL variable:
// - 'claims' is a map of claim names to claim values.
// For example, a variable named 'sub' can be accessed as 'claims.sub'.
// Nested claims can be accessed using dot notation, e.g. 'claims.foo.bar'.
//
// Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/
//
// Mutually exclusive with groups. Use either groups or groupsExpression to
// determine the user's group membership from the JWT token.
// +optional
Username string `json:"username"`
GroupsExpression string `json:"groupsExpression,omitempty"`
// extra is similar to claimMappings.extra from Kubernetes AuthenticationConfiguration
// as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication.
//
// However, note that the Pinniped Concierge issues client certificates to users for the purpose
// of authenticating, and the Kubernetes API server does not have any mechanism for transmitting
// auth extras via client certificates. When configured, these extras will appear in client
// certificates issued by the Pinniped Supervisor in the x509 Subject field as Organizational
// Units (OU). However, when this client certificate is presented to Kubernetes for authentication,
// Kubernetes will ignore these extras. This is probably only useful if you are using a custom
// authenticating proxy in front of your Kubernetes API server which can translate these OUs into
// auth extras, as described by
// https://kubernetes.io/docs/reference/access-authn-authz/authentication/#authenticating-proxy.
// This is an advanced configuration option. During an end-user login flow, each of these CEL expressions
// must evaluate to either a string or an array of strings, or else the user's login will fail.
//
// These keys must be a domain-prefixed path (such as "acme.io/foo") and must not contain an equals sign ("=").
//
// expression must produce a string or string array value.
// If the value is empty, the extra mapping will not be present.
//
// Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/
//
// hard-coded extra key/value
// - key: "acme.io/foo"
// valueExpression: "'bar'"
// This will result in an extra attribute - acme.io/foo: ["bar"]
//
// hard-coded key, value copying claim value
// - key: "acme.io/foo"
// valueExpression: "claims.some_claim"
// This will result in an extra attribute - acme.io/foo: [value of some_claim]
//
// hard-coded key, value derived from claim value
// - key: "acme.io/admin"
// valueExpression: '(has(claims.is_admin) && claims.is_admin) ? "true":""'
// This will result in:
// - if is_admin claim is present and true, extra attribute - acme.io/admin: ["true"]
// - if is_admin claim is present and false or is_admin claim is not present, no extra attribute will be added
//
// +optional
Extra []ExtraMapping `json:"extra,omitempty"`
}
// 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.
// Additionally, the key must not contain an equals sign ("=").
// +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 +295,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"`

View File

@@ -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

View File

@@ -58,37 +58,219 @@ 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.
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.
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.
These keys must be a domain-prefixed path (such as "acme.io/foo") and must not contain an equals sign ("=").
expression must produce a string or string array value.
If the value is empty, the extra mapping will not be present.
Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/
hard-coded extra key/value
- key: "acme.io/foo"
valueExpression: "'bar'"
This will result in an extra attribute - acme.io/foo: ["bar"]
hard-coded key, value copying claim value
- key: "acme.io/foo"
valueExpression: "claims.some_claim"
This will result in an extra attribute - acme.io/foo: [value of some_claim]
hard-coded key, value derived from claim value
- key: "acme.io/admin"
valueExpression: '(has(claims.is_admin) && claims.is_admin) ? "true":""'
This will result in:
- if is_admin claim is present and true, extra attribute - acme.io/admin: ["true"]
- if is_admin claim is present and false or is_admin claim is not present, no extra attribute will be added
items:
description: ExtraMapping provides the configuration for a single
extra mapping.
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.
Additionally, the key must not contain an equals sign ("=").
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
group membership from the JWT token. When not specified, it will default to "groups".
groups is the name of the claim which should be read to extract the user's
group membership from the JWT token. When not specified, it will default to "groups",
unless groupsExpression is specified.
Mutually exclusive with groupsExpression. Use either groups or groupsExpression to
determine the user's group membership from the JWT token.
type: string
groupsExpression:
description: |-
groupsExpression represents an expression which will be evaluated by CEL.
The expression's result will become the user's group memberships.
groupsExpression is similar to claimMappings.groups.expression from Kubernetes AuthenticationConfiguration
as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication.
This is an advanced configuration option. During an end-user login flow, each of these CEL expressions
must evaluate to one of the expected types without errors, or else the user's login will fail.
Additionally, mistakes in this configuration can cause the users to have unintended group memberships.
The expression must produce a string or string array value.
"", [], and null values are treated as the group mapping not being present.
CEL expressions have access to the contents of the token claims, organized into CEL variable:
- 'claims' is a map of claim names to claim values.
For example, a variable named 'sub' can be accessed as 'claims.sub'.
Nested claims can be accessed using dot notation, e.g. 'claims.foo.bar'.
Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/
Mutually exclusive with groups. Use either groups or groupsExpression to
determine the user's group membership from the JWT token.
type: string
username:
description: |-
Username is the name of the claim which should be read to extract the
username from the JWT token. When not specified, it will default to "username".
username is the name of the claim which should be read to extract the
username from the JWT token. When not specified, it will default to "username",
unless usernameExpression is specified.
Mutually exclusive with usernameExpression. Use either username or usernameExpression to
determine the user's username from the JWT token.
type: string
usernameExpression:
description: |-
usernameExpression represents an expression which will be evaluated by CEL.
The expression's result will become the user's username.
usernameExpression is similar to claimMappings.username.expression from Kubernetes AuthenticationConfiguration
as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication.
This is an advanced configuration option. During an end-user login flow, each of these CEL expressions
must evaluate to the expected type without errors, or else the user's login will fail.
Additionally, mistakes in this configuration can cause the users to have unintended usernames.
The expression must produce a non-empty string value.
If the expression uses 'claims.email', then 'claims.email_verified' must be used in
the expression or extra[*].valueExpression or claimValidationRules[*].expression.
An example claim validation rule expression that matches the validation automatically
applied when username.claim is set to 'email' is 'claims.?email_verified.orValue(true) == true'.
By explicitly comparing the value to true, we let type-checking see the result will be a boolean,
and to make sure a non-boolean email_verified claim will be caught at runtime.
CEL expressions have access to the contents of the token claims, organized into CEL variable:
- 'claims' is a map of claim names to claim values.
For example, a variable named 'sub' can be accessed as 'claims.sub'.
Nested claims can be accessed using dot notation, e.g. 'claims.foo.bar'.
Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/
Mutually exclusive with username. Use either username or usernameExpression to
determine the user's username from the JWT token.
type: string
type: object
issuer:
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 +310,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

View File

@@ -60,6 +60,78 @@ 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. +
Only string claim keys are supported. +
Mutually exclusive with expression and message. +
| *`requiredValue`* __string__ | requiredValue is the value of a required claim. +
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. +
Additionally, the key must not contain an equals sign ("="). +
| *`valueExpression`* __string__ | valueExpression is a CEL expression to extract extra attribute value. +
valueExpression must produce a string or string array value. +
"", [], and null values are treated as the extra mapping not being present. +
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 +150,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 +172,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 +182,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 +236,103 @@ for Kubernetes access.
[cols="25a,75a", options="header"]
|===
| Field | Description
| *`groups`* __string__ | Groups is the name of the claim which should be read to extract the user's +
group membership from the JWT token. When not specified, it will default to "groups". +
| *`username`* __string__ | Username is the name of the claim which should be read to extract the +
username from the JWT token. When not specified, it will default to "username". +
| *`username`* __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", +
unless usernameExpression is specified. +
Mutually exclusive with usernameExpression. Use either username or usernameExpression to +
determine the user's username from the JWT token. +
| *`usernameExpression`* __string__ | usernameExpression represents an expression which will be evaluated by CEL. +
The expression's result will become the user's username. +
usernameExpression is similar to claimMappings.username.expression from Kubernetes AuthenticationConfiguration +
as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication. +
This is an advanced configuration option. During an end-user login flow, each of these CEL expressions +
must evaluate to the expected type without errors, or else the user's login will fail. +
Additionally, mistakes in this configuration can cause the users to have unintended usernames. +
The expression must produce a non-empty string value. +
If the expression uses 'claims.email', then 'claims.email_verified' must be used in +
the expression or extra[*].valueExpression or claimValidationRules[*].expression. +
An example claim validation rule expression that matches the validation automatically +
applied when username.claim is set to 'email' is 'claims.?email_verified.orValue(true) == true'. +
By explicitly comparing the value to true, we let type-checking see the result will be a boolean, +
and to make sure a non-boolean email_verified claim will be caught at runtime. +
CEL expressions have access to the contents of the token claims, organized into CEL variable: +
- 'claims' is a map of claim names to claim values. +
For example, a variable named 'sub' can be accessed as 'claims.sub'. +
Nested claims can be accessed using dot notation, e.g. 'claims.foo.bar'. +
Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ +
Mutually exclusive with username. Use either username or usernameExpression to +
determine the user's username from the JWT token. +
| *`groups`* __string__ | groups is the name of the claim which should be read to extract the user's +
group membership from the JWT token. When not specified, it will default to "groups", +
unless groupsExpression is specified. +
Mutually exclusive with groupsExpression. Use either groups or groupsExpression to +
determine the user's group membership from the JWT token. +
| *`groupsExpression`* __string__ | groupsExpression represents an expression which will be evaluated by CEL. +
The expression's result will become the user's group memberships. +
groupsExpression is similar to claimMappings.groups.expression from Kubernetes AuthenticationConfiguration +
as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication. +
This is an advanced configuration option. During an end-user login flow, each of these CEL expressions +
must evaluate to one of the expected types without errors, or else the user's login will fail. +
Additionally, mistakes in this configuration can cause the users to have unintended group memberships. +
The expression must produce a string or string array value. +
"", [], and null values are treated as the group mapping not being present. +
CEL expressions have access to the contents of the token claims, organized into CEL variable: +
- 'claims' is a map of claim names to claim values. +
For example, a variable named 'sub' can be accessed as 'claims.sub'. +
Nested claims can be accessed using dot notation, e.g. 'claims.foo.bar'. +
Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ +
Mutually exclusive with groups. Use either groups or groupsExpression to +
determine the user's group membership from the JWT token. +
| *`extra`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-27-apis-concierge-authentication-v1alpha1-extramapping[$$ExtraMapping$$] array__ | extra is similar to claimMappings.extra from Kubernetes AuthenticationConfiguration +
as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication. +
However, note that the Pinniped Concierge issues client certificates to users for the purpose +
of authenticating, and the Kubernetes API server does not have any mechanism for transmitting +
auth extras via client certificates. When configured, these extras will appear in client +
certificates issued by the Pinniped Supervisor in the x509 Subject field as Organizational +
Units (OU). However, when this client certificate is presented to Kubernetes for authentication, +
Kubernetes will ignore these extras. This is probably only useful if you are using a custom +
authenticating proxy in front of your Kubernetes API server which can translate these OUs into +
auth extras, as described by +
https://kubernetes.io/docs/reference/access-authn-authz/authentication/#authenticating-proxy. +
This is an advanced configuration option. During an end-user login flow, each of these CEL expressions +
must evaluate to either a string or an array of strings, or else the user's login will fail. +
These keys must be a domain-prefixed path (such as "acme.io/foo") and must not contain an equals sign ("="). +
expression must produce a string or string array value. +
If the value is empty, the extra mapping will not be present. +
Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ +
hard-coded extra key/value +
- key: "acme.io/foo" +
valueExpression: "'bar'" +
This will result in an extra attribute - acme.io/foo: ["bar"] +
hard-coded key, value copying claim value +
- key: "acme.io/foo" +
valueExpression: "claims.some_claim" +
This will result in an extra attribute - acme.io/foo: [value of some_claim] +
hard-coded key, value derived from claim value +
- key: "acme.io/admin" +
valueExpression: '(has(claims.is_admin) && claims.is_admin) ? "true":""' +
This will result in: +
- if is_admin claim is present and true, extra attribute - acme.io/admin: ["true"] +
- if is_admin claim is present and false or is_admin claim is not present, no extra attribute will be added +
|===
@@ -178,6 +356,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

View File

@@ -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,255 @@ 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.
// 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.
// 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
// group membership from the JWT token. When not specified, it will default to "groups".
// username is the name of the claim which should be read to extract the
// username from the JWT token. When not specified, it will default to "username",
// unless usernameExpression is specified.
//
// Mutually exclusive with usernameExpression. Use either username or usernameExpression to
// determine the user's username from the JWT token.
// +optional
Username string `json:"username"`
// usernameExpression represents an expression which will be evaluated by CEL.
// The expression's result will become the user's username.
//
// usernameExpression is similar to claimMappings.username.expression from Kubernetes AuthenticationConfiguration
// as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication.
// This is an advanced configuration option. During an end-user login flow, each of these CEL expressions
// must evaluate to the expected type without errors, or else the user's login will fail.
// Additionally, mistakes in this configuration can cause the users to have unintended usernames.
//
// The expression must produce a non-empty string value.
// If the expression uses 'claims.email', then 'claims.email_verified' must be used in
// the expression or extra[*].valueExpression or claimValidationRules[*].expression.
// An example claim validation rule expression that matches the validation automatically
// applied when username.claim is set to 'email' is 'claims.?email_verified.orValue(true) == true'.
// By explicitly comparing the value to true, we let type-checking see the result will be a boolean,
// and to make sure a non-boolean email_verified claim will be caught at runtime.
//
// CEL expressions have access to the contents of the token claims, organized into CEL variable:
// - 'claims' is a map of claim names to claim values.
// For example, a variable named 'sub' can be accessed as 'claims.sub'.
// Nested claims can be accessed using dot notation, e.g. 'claims.foo.bar'.
//
// Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/
//
// Mutually exclusive with username. Use either username or usernameExpression to
// determine the user's username from the JWT token.
// +optional
UsernameExpression string `json:"usernameExpression,omitempty"`
// groups is the name of the claim which should be read to extract the user's
// group membership from the JWT token. When not specified, it will default to "groups",
// unless groupsExpression is specified.
//
// Mutually exclusive with groupsExpression. Use either groups or groupsExpression to
// determine the user's group membership from the JWT token.
// +optional
Groups string `json:"groups"`
// Username is the name of the claim which should be read to extract the
// username from the JWT token. When not specified, it will default to "username".
// groupsExpression represents an expression which will be evaluated by CEL.
// The expression's result will become the user's group memberships.
//
// groupsExpression is similar to claimMappings.groups.expression from Kubernetes AuthenticationConfiguration
// as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication.
// This is an advanced configuration option. During an end-user login flow, each of these CEL expressions
// must evaluate to one of the expected types without errors, or else the user's login will fail.
// Additionally, mistakes in this configuration can cause the users to have unintended group memberships.
//
// The expression must produce a string or string array value.
// "", [], and null values are treated as the group mapping not being present.
//
// CEL expressions have access to the contents of the token claims, organized into CEL variable:
// - 'claims' is a map of claim names to claim values.
// For example, a variable named 'sub' can be accessed as 'claims.sub'.
// Nested claims can be accessed using dot notation, e.g. 'claims.foo.bar'.
//
// Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/
//
// Mutually exclusive with groups. Use either groups or groupsExpression to
// determine the user's group membership from the JWT token.
// +optional
Username string `json:"username"`
GroupsExpression string `json:"groupsExpression,omitempty"`
// extra is similar to claimMappings.extra from Kubernetes AuthenticationConfiguration
// as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication.
//
// However, note that the Pinniped Concierge issues client certificates to users for the purpose
// of authenticating, and the Kubernetes API server does not have any mechanism for transmitting
// auth extras via client certificates. When configured, these extras will appear in client
// certificates issued by the Pinniped Supervisor in the x509 Subject field as Organizational
// Units (OU). However, when this client certificate is presented to Kubernetes for authentication,
// Kubernetes will ignore these extras. This is probably only useful if you are using a custom
// authenticating proxy in front of your Kubernetes API server which can translate these OUs into
// auth extras, as described by
// https://kubernetes.io/docs/reference/access-authn-authz/authentication/#authenticating-proxy.
// This is an advanced configuration option. During an end-user login flow, each of these CEL expressions
// must evaluate to either a string or an array of strings, or else the user's login will fail.
//
// These keys must be a domain-prefixed path (such as "acme.io/foo") and must not contain an equals sign ("=").
//
// expression must produce a string or string array value.
// If the value is empty, the extra mapping will not be present.
//
// Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/
//
// hard-coded extra key/value
// - key: "acme.io/foo"
// valueExpression: "'bar'"
// This will result in an extra attribute - acme.io/foo: ["bar"]
//
// hard-coded key, value copying claim value
// - key: "acme.io/foo"
// valueExpression: "claims.some_claim"
// This will result in an extra attribute - acme.io/foo: [value of some_claim]
//
// hard-coded key, value derived from claim value
// - key: "acme.io/admin"
// valueExpression: '(has(claims.is_admin) && claims.is_admin) ? "true":""'
// This will result in:
// - if is_admin claim is present and true, extra attribute - acme.io/admin: ["true"]
// - if is_admin claim is present and false or is_admin claim is not present, no extra attribute will be added
//
// +optional
Extra []ExtraMapping `json:"extra,omitempty"`
}
// 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.
// Additionally, the key must not contain an equals sign ("=").
// +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 +295,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"`

View File

@@ -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

View File

@@ -58,37 +58,219 @@ 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.
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.
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.
These keys must be a domain-prefixed path (such as "acme.io/foo") and must not contain an equals sign ("=").
expression must produce a string or string array value.
If the value is empty, the extra mapping will not be present.
Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/
hard-coded extra key/value
- key: "acme.io/foo"
valueExpression: "'bar'"
This will result in an extra attribute - acme.io/foo: ["bar"]
hard-coded key, value copying claim value
- key: "acme.io/foo"
valueExpression: "claims.some_claim"
This will result in an extra attribute - acme.io/foo: [value of some_claim]
hard-coded key, value derived from claim value
- key: "acme.io/admin"
valueExpression: '(has(claims.is_admin) && claims.is_admin) ? "true":""'
This will result in:
- if is_admin claim is present and true, extra attribute - acme.io/admin: ["true"]
- if is_admin claim is present and false or is_admin claim is not present, no extra attribute will be added
items:
description: ExtraMapping provides the configuration for a single
extra mapping.
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.
Additionally, the key must not contain an equals sign ("=").
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
group membership from the JWT token. When not specified, it will default to "groups".
groups is the name of the claim which should be read to extract the user's
group membership from the JWT token. When not specified, it will default to "groups",
unless groupsExpression is specified.
Mutually exclusive with groupsExpression. Use either groups or groupsExpression to
determine the user's group membership from the JWT token.
type: string
groupsExpression:
description: |-
groupsExpression represents an expression which will be evaluated by CEL.
The expression's result will become the user's group memberships.
groupsExpression is similar to claimMappings.groups.expression from Kubernetes AuthenticationConfiguration
as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication.
This is an advanced configuration option. During an end-user login flow, each of these CEL expressions
must evaluate to one of the expected types without errors, or else the user's login will fail.
Additionally, mistakes in this configuration can cause the users to have unintended group memberships.
The expression must produce a string or string array value.
"", [], and null values are treated as the group mapping not being present.
CEL expressions have access to the contents of the token claims, organized into CEL variable:
- 'claims' is a map of claim names to claim values.
For example, a variable named 'sub' can be accessed as 'claims.sub'.
Nested claims can be accessed using dot notation, e.g. 'claims.foo.bar'.
Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/
Mutually exclusive with groups. Use either groups or groupsExpression to
determine the user's group membership from the JWT token.
type: string
username:
description: |-
Username is the name of the claim which should be read to extract the
username from the JWT token. When not specified, it will default to "username".
username is the name of the claim which should be read to extract the
username from the JWT token. When not specified, it will default to "username",
unless usernameExpression is specified.
Mutually exclusive with usernameExpression. Use either username or usernameExpression to
determine the user's username from the JWT token.
type: string
usernameExpression:
description: |-
usernameExpression represents an expression which will be evaluated by CEL.
The expression's result will become the user's username.
usernameExpression is similar to claimMappings.username.expression from Kubernetes AuthenticationConfiguration
as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication.
This is an advanced configuration option. During an end-user login flow, each of these CEL expressions
must evaluate to the expected type without errors, or else the user's login will fail.
Additionally, mistakes in this configuration can cause the users to have unintended usernames.
The expression must produce a non-empty string value.
If the expression uses 'claims.email', then 'claims.email_verified' must be used in
the expression or extra[*].valueExpression or claimValidationRules[*].expression.
An example claim validation rule expression that matches the validation automatically
applied when username.claim is set to 'email' is 'claims.?email_verified.orValue(true) == true'.
By explicitly comparing the value to true, we let type-checking see the result will be a boolean,
and to make sure a non-boolean email_verified claim will be caught at runtime.
CEL expressions have access to the contents of the token claims, organized into CEL variable:
- 'claims' is a map of claim names to claim values.
For example, a variable named 'sub' can be accessed as 'claims.sub'.
Nested claims can be accessed using dot notation, e.g. 'claims.foo.bar'.
Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/
Mutually exclusive with username. Use either username or usernameExpression to
determine the user's username from the JWT token.
type: string
type: object
issuer:
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 +310,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

View File

@@ -60,6 +60,78 @@ 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. +
Only string claim keys are supported. +
Mutually exclusive with expression and message. +
| *`requiredValue`* __string__ | requiredValue is the value of a required claim. +
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. +
Additionally, the key must not contain an equals sign ("="). +
| *`valueExpression`* __string__ | valueExpression is a CEL expression to extract extra attribute value. +
valueExpression must produce a string or string array value. +
"", [], and null values are treated as the extra mapping not being present. +
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 +150,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 +172,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 +182,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 +236,103 @@ for Kubernetes access.
[cols="25a,75a", options="header"]
|===
| Field | Description
| *`groups`* __string__ | Groups is the name of the claim which should be read to extract the user's +
group membership from the JWT token. When not specified, it will default to "groups". +
| *`username`* __string__ | Username is the name of the claim which should be read to extract the +
username from the JWT token. When not specified, it will default to "username". +
| *`username`* __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", +
unless usernameExpression is specified. +
Mutually exclusive with usernameExpression. Use either username or usernameExpression to +
determine the user's username from the JWT token. +
| *`usernameExpression`* __string__ | usernameExpression represents an expression which will be evaluated by CEL. +
The expression's result will become the user's username. +
usernameExpression is similar to claimMappings.username.expression from Kubernetes AuthenticationConfiguration +
as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication. +
This is an advanced configuration option. During an end-user login flow, each of these CEL expressions +
must evaluate to the expected type without errors, or else the user's login will fail. +
Additionally, mistakes in this configuration can cause the users to have unintended usernames. +
The expression must produce a non-empty string value. +
If the expression uses 'claims.email', then 'claims.email_verified' must be used in +
the expression or extra[*].valueExpression or claimValidationRules[*].expression. +
An example claim validation rule expression that matches the validation automatically +
applied when username.claim is set to 'email' is 'claims.?email_verified.orValue(true) == true'. +
By explicitly comparing the value to true, we let type-checking see the result will be a boolean, +
and to make sure a non-boolean email_verified claim will be caught at runtime. +
CEL expressions have access to the contents of the token claims, organized into CEL variable: +
- 'claims' is a map of claim names to claim values. +
For example, a variable named 'sub' can be accessed as 'claims.sub'. +
Nested claims can be accessed using dot notation, e.g. 'claims.foo.bar'. +
Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ +
Mutually exclusive with username. Use either username or usernameExpression to +
determine the user's username from the JWT token. +
| *`groups`* __string__ | groups is the name of the claim which should be read to extract the user's +
group membership from the JWT token. When not specified, it will default to "groups", +
unless groupsExpression is specified. +
Mutually exclusive with groupsExpression. Use either groups or groupsExpression to +
determine the user's group membership from the JWT token. +
| *`groupsExpression`* __string__ | groupsExpression represents an expression which will be evaluated by CEL. +
The expression's result will become the user's group memberships. +
groupsExpression is similar to claimMappings.groups.expression from Kubernetes AuthenticationConfiguration +
as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication. +
This is an advanced configuration option. During an end-user login flow, each of these CEL expressions +
must evaluate to one of the expected types without errors, or else the user's login will fail. +
Additionally, mistakes in this configuration can cause the users to have unintended group memberships. +
The expression must produce a string or string array value. +
"", [], and null values are treated as the group mapping not being present. +
CEL expressions have access to the contents of the token claims, organized into CEL variable: +
- 'claims' is a map of claim names to claim values. +
For example, a variable named 'sub' can be accessed as 'claims.sub'. +
Nested claims can be accessed using dot notation, e.g. 'claims.foo.bar'. +
Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ +
Mutually exclusive with groups. Use either groups or groupsExpression to +
determine the user's group membership from the JWT token. +
| *`extra`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-28-apis-concierge-authentication-v1alpha1-extramapping[$$ExtraMapping$$] array__ | extra is similar to claimMappings.extra from Kubernetes AuthenticationConfiguration +
as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication. +
However, note that the Pinniped Concierge issues client certificates to users for the purpose +
of authenticating, and the Kubernetes API server does not have any mechanism for transmitting +
auth extras via client certificates. When configured, these extras will appear in client +
certificates issued by the Pinniped Supervisor in the x509 Subject field as Organizational +
Units (OU). However, when this client certificate is presented to Kubernetes for authentication, +
Kubernetes will ignore these extras. This is probably only useful if you are using a custom +
authenticating proxy in front of your Kubernetes API server which can translate these OUs into +
auth extras, as described by +
https://kubernetes.io/docs/reference/access-authn-authz/authentication/#authenticating-proxy. +
This is an advanced configuration option. During an end-user login flow, each of these CEL expressions +
must evaluate to either a string or an array of strings, or else the user's login will fail. +
These keys must be a domain-prefixed path (such as "acme.io/foo") and must not contain an equals sign ("="). +
expression must produce a string or string array value. +
If the value is empty, the extra mapping will not be present. +
Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ +
hard-coded extra key/value +
- key: "acme.io/foo" +
valueExpression: "'bar'" +
This will result in an extra attribute - acme.io/foo: ["bar"] +
hard-coded key, value copying claim value +
- key: "acme.io/foo" +
valueExpression: "claims.some_claim" +
This will result in an extra attribute - acme.io/foo: [value of some_claim] +
hard-coded key, value derived from claim value +
- key: "acme.io/admin" +
valueExpression: '(has(claims.is_admin) && claims.is_admin) ? "true":""' +
This will result in: +
- if is_admin claim is present and true, extra attribute - acme.io/admin: ["true"] +
- if is_admin claim is present and false or is_admin claim is not present, no extra attribute will be added +
|===
@@ -178,6 +356,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

View File

@@ -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,255 @@ 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.
// 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.
// 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
// group membership from the JWT token. When not specified, it will default to "groups".
// username is the name of the claim which should be read to extract the
// username from the JWT token. When not specified, it will default to "username",
// unless usernameExpression is specified.
//
// Mutually exclusive with usernameExpression. Use either username or usernameExpression to
// determine the user's username from the JWT token.
// +optional
Username string `json:"username"`
// usernameExpression represents an expression which will be evaluated by CEL.
// The expression's result will become the user's username.
//
// usernameExpression is similar to claimMappings.username.expression from Kubernetes AuthenticationConfiguration
// as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication.
// This is an advanced configuration option. During an end-user login flow, each of these CEL expressions
// must evaluate to the expected type without errors, or else the user's login will fail.
// Additionally, mistakes in this configuration can cause the users to have unintended usernames.
//
// The expression must produce a non-empty string value.
// If the expression uses 'claims.email', then 'claims.email_verified' must be used in
// the expression or extra[*].valueExpression or claimValidationRules[*].expression.
// An example claim validation rule expression that matches the validation automatically
// applied when username.claim is set to 'email' is 'claims.?email_verified.orValue(true) == true'.
// By explicitly comparing the value to true, we let type-checking see the result will be a boolean,
// and to make sure a non-boolean email_verified claim will be caught at runtime.
//
// CEL expressions have access to the contents of the token claims, organized into CEL variable:
// - 'claims' is a map of claim names to claim values.
// For example, a variable named 'sub' can be accessed as 'claims.sub'.
// Nested claims can be accessed using dot notation, e.g. 'claims.foo.bar'.
//
// Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/
//
// Mutually exclusive with username. Use either username or usernameExpression to
// determine the user's username from the JWT token.
// +optional
UsernameExpression string `json:"usernameExpression,omitempty"`
// groups is the name of the claim which should be read to extract the user's
// group membership from the JWT token. When not specified, it will default to "groups",
// unless groupsExpression is specified.
//
// Mutually exclusive with groupsExpression. Use either groups or groupsExpression to
// determine the user's group membership from the JWT token.
// +optional
Groups string `json:"groups"`
// Username is the name of the claim which should be read to extract the
// username from the JWT token. When not specified, it will default to "username".
// groupsExpression represents an expression which will be evaluated by CEL.
// The expression's result will become the user's group memberships.
//
// groupsExpression is similar to claimMappings.groups.expression from Kubernetes AuthenticationConfiguration
// as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication.
// This is an advanced configuration option. During an end-user login flow, each of these CEL expressions
// must evaluate to one of the expected types without errors, or else the user's login will fail.
// Additionally, mistakes in this configuration can cause the users to have unintended group memberships.
//
// The expression must produce a string or string array value.
// "", [], and null values are treated as the group mapping not being present.
//
// CEL expressions have access to the contents of the token claims, organized into CEL variable:
// - 'claims' is a map of claim names to claim values.
// For example, a variable named 'sub' can be accessed as 'claims.sub'.
// Nested claims can be accessed using dot notation, e.g. 'claims.foo.bar'.
//
// Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/
//
// Mutually exclusive with groups. Use either groups or groupsExpression to
// determine the user's group membership from the JWT token.
// +optional
Username string `json:"username"`
GroupsExpression string `json:"groupsExpression,omitempty"`
// extra is similar to claimMappings.extra from Kubernetes AuthenticationConfiguration
// as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication.
//
// However, note that the Pinniped Concierge issues client certificates to users for the purpose
// of authenticating, and the Kubernetes API server does not have any mechanism for transmitting
// auth extras via client certificates. When configured, these extras will appear in client
// certificates issued by the Pinniped Supervisor in the x509 Subject field as Organizational
// Units (OU). However, when this client certificate is presented to Kubernetes for authentication,
// Kubernetes will ignore these extras. This is probably only useful if you are using a custom
// authenticating proxy in front of your Kubernetes API server which can translate these OUs into
// auth extras, as described by
// https://kubernetes.io/docs/reference/access-authn-authz/authentication/#authenticating-proxy.
// This is an advanced configuration option. During an end-user login flow, each of these CEL expressions
// must evaluate to either a string or an array of strings, or else the user's login will fail.
//
// These keys must be a domain-prefixed path (such as "acme.io/foo") and must not contain an equals sign ("=").
//
// expression must produce a string or string array value.
// If the value is empty, the extra mapping will not be present.
//
// Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/
//
// hard-coded extra key/value
// - key: "acme.io/foo"
// valueExpression: "'bar'"
// This will result in an extra attribute - acme.io/foo: ["bar"]
//
// hard-coded key, value copying claim value
// - key: "acme.io/foo"
// valueExpression: "claims.some_claim"
// This will result in an extra attribute - acme.io/foo: [value of some_claim]
//
// hard-coded key, value derived from claim value
// - key: "acme.io/admin"
// valueExpression: '(has(claims.is_admin) && claims.is_admin) ? "true":""'
// This will result in:
// - if is_admin claim is present and true, extra attribute - acme.io/admin: ["true"]
// - if is_admin claim is present and false or is_admin claim is not present, no extra attribute will be added
//
// +optional
Extra []ExtraMapping `json:"extra,omitempty"`
}
// 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.
// Additionally, the key must not contain an equals sign ("=").
// +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 +295,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"`

View File

@@ -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

View File

@@ -58,37 +58,219 @@ 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.
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.
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.
These keys must be a domain-prefixed path (such as "acme.io/foo") and must not contain an equals sign ("=").
expression must produce a string or string array value.
If the value is empty, the extra mapping will not be present.
Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/
hard-coded extra key/value
- key: "acme.io/foo"
valueExpression: "'bar'"
This will result in an extra attribute - acme.io/foo: ["bar"]
hard-coded key, value copying claim value
- key: "acme.io/foo"
valueExpression: "claims.some_claim"
This will result in an extra attribute - acme.io/foo: [value of some_claim]
hard-coded key, value derived from claim value
- key: "acme.io/admin"
valueExpression: '(has(claims.is_admin) && claims.is_admin) ? "true":""'
This will result in:
- if is_admin claim is present and true, extra attribute - acme.io/admin: ["true"]
- if is_admin claim is present and false or is_admin claim is not present, no extra attribute will be added
items:
description: ExtraMapping provides the configuration for a single
extra mapping.
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.
Additionally, the key must not contain an equals sign ("=").
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
group membership from the JWT token. When not specified, it will default to "groups".
groups is the name of the claim which should be read to extract the user's
group membership from the JWT token. When not specified, it will default to "groups",
unless groupsExpression is specified.
Mutually exclusive with groupsExpression. Use either groups or groupsExpression to
determine the user's group membership from the JWT token.
type: string
groupsExpression:
description: |-
groupsExpression represents an expression which will be evaluated by CEL.
The expression's result will become the user's group memberships.
groupsExpression is similar to claimMappings.groups.expression from Kubernetes AuthenticationConfiguration
as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication.
This is an advanced configuration option. During an end-user login flow, each of these CEL expressions
must evaluate to one of the expected types without errors, or else the user's login will fail.
Additionally, mistakes in this configuration can cause the users to have unintended group memberships.
The expression must produce a string or string array value.
"", [], and null values are treated as the group mapping not being present.
CEL expressions have access to the contents of the token claims, organized into CEL variable:
- 'claims' is a map of claim names to claim values.
For example, a variable named 'sub' can be accessed as 'claims.sub'.
Nested claims can be accessed using dot notation, e.g. 'claims.foo.bar'.
Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/
Mutually exclusive with groups. Use either groups or groupsExpression to
determine the user's group membership from the JWT token.
type: string
username:
description: |-
Username is the name of the claim which should be read to extract the
username from the JWT token. When not specified, it will default to "username".
username is the name of the claim which should be read to extract the
username from the JWT token. When not specified, it will default to "username",
unless usernameExpression is specified.
Mutually exclusive with usernameExpression. Use either username or usernameExpression to
determine the user's username from the JWT token.
type: string
usernameExpression:
description: |-
usernameExpression represents an expression which will be evaluated by CEL.
The expression's result will become the user's username.
usernameExpression is similar to claimMappings.username.expression from Kubernetes AuthenticationConfiguration
as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication.
This is an advanced configuration option. During an end-user login flow, each of these CEL expressions
must evaluate to the expected type without errors, or else the user's login will fail.
Additionally, mistakes in this configuration can cause the users to have unintended usernames.
The expression must produce a non-empty string value.
If the expression uses 'claims.email', then 'claims.email_verified' must be used in
the expression or extra[*].valueExpression or claimValidationRules[*].expression.
An example claim validation rule expression that matches the validation automatically
applied when username.claim is set to 'email' is 'claims.?email_verified.orValue(true) == true'.
By explicitly comparing the value to true, we let type-checking see the result will be a boolean,
and to make sure a non-boolean email_verified claim will be caught at runtime.
CEL expressions have access to the contents of the token claims, organized into CEL variable:
- 'claims' is a map of claim names to claim values.
For example, a variable named 'sub' can be accessed as 'claims.sub'.
Nested claims can be accessed using dot notation, e.g. 'claims.foo.bar'.
Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/
Mutually exclusive with username. Use either username or usernameExpression to
determine the user's username from the JWT token.
type: string
type: object
issuer:
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 +310,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

View File

@@ -60,6 +60,78 @@ 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. +
Only string claim keys are supported. +
Mutually exclusive with expression and message. +
| *`requiredValue`* __string__ | requiredValue is the value of a required claim. +
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. +
Additionally, the key must not contain an equals sign ("="). +
| *`valueExpression`* __string__ | valueExpression is a CEL expression to extract extra attribute value. +
valueExpression must produce a string or string array value. +
"", [], and null values are treated as the extra mapping not being present. +
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 +150,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 +172,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 +182,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 +236,103 @@ for Kubernetes access.
[cols="25a,75a", options="header"]
|===
| Field | Description
| *`groups`* __string__ | Groups is the name of the claim which should be read to extract the user's +
group membership from the JWT token. When not specified, it will default to "groups". +
| *`username`* __string__ | Username is the name of the claim which should be read to extract the +
username from the JWT token. When not specified, it will default to "username". +
| *`username`* __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", +
unless usernameExpression is specified. +
Mutually exclusive with usernameExpression. Use either username or usernameExpression to +
determine the user's username from the JWT token. +
| *`usernameExpression`* __string__ | usernameExpression represents an expression which will be evaluated by CEL. +
The expression's result will become the user's username. +
usernameExpression is similar to claimMappings.username.expression from Kubernetes AuthenticationConfiguration +
as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication. +
This is an advanced configuration option. During an end-user login flow, each of these CEL expressions +
must evaluate to the expected type without errors, or else the user's login will fail. +
Additionally, mistakes in this configuration can cause the users to have unintended usernames. +
The expression must produce a non-empty string value. +
If the expression uses 'claims.email', then 'claims.email_verified' must be used in +
the expression or extra[*].valueExpression or claimValidationRules[*].expression. +
An example claim validation rule expression that matches the validation automatically +
applied when username.claim is set to 'email' is 'claims.?email_verified.orValue(true) == true'. +
By explicitly comparing the value to true, we let type-checking see the result will be a boolean, +
and to make sure a non-boolean email_verified claim will be caught at runtime. +
CEL expressions have access to the contents of the token claims, organized into CEL variable: +
- 'claims' is a map of claim names to claim values. +
For example, a variable named 'sub' can be accessed as 'claims.sub'. +
Nested claims can be accessed using dot notation, e.g. 'claims.foo.bar'. +
Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ +
Mutually exclusive with username. Use either username or usernameExpression to +
determine the user's username from the JWT token. +
| *`groups`* __string__ | groups is the name of the claim which should be read to extract the user's +
group membership from the JWT token. When not specified, it will default to "groups", +
unless groupsExpression is specified. +
Mutually exclusive with groupsExpression. Use either groups or groupsExpression to +
determine the user's group membership from the JWT token. +
| *`groupsExpression`* __string__ | groupsExpression represents an expression which will be evaluated by CEL. +
The expression's result will become the user's group memberships. +
groupsExpression is similar to claimMappings.groups.expression from Kubernetes AuthenticationConfiguration +
as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication. +
This is an advanced configuration option. During an end-user login flow, each of these CEL expressions +
must evaluate to one of the expected types without errors, or else the user's login will fail. +
Additionally, mistakes in this configuration can cause the users to have unintended group memberships. +
The expression must produce a string or string array value. +
"", [], and null values are treated as the group mapping not being present. +
CEL expressions have access to the contents of the token claims, organized into CEL variable: +
- 'claims' is a map of claim names to claim values. +
For example, a variable named 'sub' can be accessed as 'claims.sub'. +
Nested claims can be accessed using dot notation, e.g. 'claims.foo.bar'. +
Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ +
Mutually exclusive with groups. Use either groups or groupsExpression to +
determine the user's group membership from the JWT token. +
| *`extra`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-29-apis-concierge-authentication-v1alpha1-extramapping[$$ExtraMapping$$] array__ | extra is similar to claimMappings.extra from Kubernetes AuthenticationConfiguration +
as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication. +
However, note that the Pinniped Concierge issues client certificates to users for the purpose +
of authenticating, and the Kubernetes API server does not have any mechanism for transmitting +
auth extras via client certificates. When configured, these extras will appear in client +
certificates issued by the Pinniped Supervisor in the x509 Subject field as Organizational +
Units (OU). However, when this client certificate is presented to Kubernetes for authentication, +
Kubernetes will ignore these extras. This is probably only useful if you are using a custom +
authenticating proxy in front of your Kubernetes API server which can translate these OUs into +
auth extras, as described by +
https://kubernetes.io/docs/reference/access-authn-authz/authentication/#authenticating-proxy. +
This is an advanced configuration option. During an end-user login flow, each of these CEL expressions +
must evaluate to either a string or an array of strings, or else the user's login will fail. +
These keys must be a domain-prefixed path (such as "acme.io/foo") and must not contain an equals sign ("="). +
expression must produce a string or string array value. +
If the value is empty, the extra mapping will not be present. +
Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ +
hard-coded extra key/value +
- key: "acme.io/foo" +
valueExpression: "'bar'" +
This will result in an extra attribute - acme.io/foo: ["bar"] +
hard-coded key, value copying claim value +
- key: "acme.io/foo" +
valueExpression: "claims.some_claim" +
This will result in an extra attribute - acme.io/foo: [value of some_claim] +
hard-coded key, value derived from claim value +
- key: "acme.io/admin" +
valueExpression: '(has(claims.is_admin) && claims.is_admin) ? "true":""' +
This will result in: +
- if is_admin claim is present and true, extra attribute - acme.io/admin: ["true"] +
- if is_admin claim is present and false or is_admin claim is not present, no extra attribute will be added +
|===
@@ -178,6 +356,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

View File

@@ -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,255 @@ 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.
// 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.
// 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
// group membership from the JWT token. When not specified, it will default to "groups".
// username is the name of the claim which should be read to extract the
// username from the JWT token. When not specified, it will default to "username",
// unless usernameExpression is specified.
//
// Mutually exclusive with usernameExpression. Use either username or usernameExpression to
// determine the user's username from the JWT token.
// +optional
Username string `json:"username"`
// usernameExpression represents an expression which will be evaluated by CEL.
// The expression's result will become the user's username.
//
// usernameExpression is similar to claimMappings.username.expression from Kubernetes AuthenticationConfiguration
// as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication.
// This is an advanced configuration option. During an end-user login flow, each of these CEL expressions
// must evaluate to the expected type without errors, or else the user's login will fail.
// Additionally, mistakes in this configuration can cause the users to have unintended usernames.
//
// The expression must produce a non-empty string value.
// If the expression uses 'claims.email', then 'claims.email_verified' must be used in
// the expression or extra[*].valueExpression or claimValidationRules[*].expression.
// An example claim validation rule expression that matches the validation automatically
// applied when username.claim is set to 'email' is 'claims.?email_verified.orValue(true) == true'.
// By explicitly comparing the value to true, we let type-checking see the result will be a boolean,
// and to make sure a non-boolean email_verified claim will be caught at runtime.
//
// CEL expressions have access to the contents of the token claims, organized into CEL variable:
// - 'claims' is a map of claim names to claim values.
// For example, a variable named 'sub' can be accessed as 'claims.sub'.
// Nested claims can be accessed using dot notation, e.g. 'claims.foo.bar'.
//
// Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/
//
// Mutually exclusive with username. Use either username or usernameExpression to
// determine the user's username from the JWT token.
// +optional
UsernameExpression string `json:"usernameExpression,omitempty"`
// groups is the name of the claim which should be read to extract the user's
// group membership from the JWT token. When not specified, it will default to "groups",
// unless groupsExpression is specified.
//
// Mutually exclusive with groupsExpression. Use either groups or groupsExpression to
// determine the user's group membership from the JWT token.
// +optional
Groups string `json:"groups"`
// Username is the name of the claim which should be read to extract the
// username from the JWT token. When not specified, it will default to "username".
// groupsExpression represents an expression which will be evaluated by CEL.
// The expression's result will become the user's group memberships.
//
// groupsExpression is similar to claimMappings.groups.expression from Kubernetes AuthenticationConfiguration
// as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication.
// This is an advanced configuration option. During an end-user login flow, each of these CEL expressions
// must evaluate to one of the expected types without errors, or else the user's login will fail.
// Additionally, mistakes in this configuration can cause the users to have unintended group memberships.
//
// The expression must produce a string or string array value.
// "", [], and null values are treated as the group mapping not being present.
//
// CEL expressions have access to the contents of the token claims, organized into CEL variable:
// - 'claims' is a map of claim names to claim values.
// For example, a variable named 'sub' can be accessed as 'claims.sub'.
// Nested claims can be accessed using dot notation, e.g. 'claims.foo.bar'.
//
// Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/
//
// Mutually exclusive with groups. Use either groups or groupsExpression to
// determine the user's group membership from the JWT token.
// +optional
Username string `json:"username"`
GroupsExpression string `json:"groupsExpression,omitempty"`
// extra is similar to claimMappings.extra from Kubernetes AuthenticationConfiguration
// as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication.
//
// However, note that the Pinniped Concierge issues client certificates to users for the purpose
// of authenticating, and the Kubernetes API server does not have any mechanism for transmitting
// auth extras via client certificates. When configured, these extras will appear in client
// certificates issued by the Pinniped Supervisor in the x509 Subject field as Organizational
// Units (OU). However, when this client certificate is presented to Kubernetes for authentication,
// Kubernetes will ignore these extras. This is probably only useful if you are using a custom
// authenticating proxy in front of your Kubernetes API server which can translate these OUs into
// auth extras, as described by
// https://kubernetes.io/docs/reference/access-authn-authz/authentication/#authenticating-proxy.
// This is an advanced configuration option. During an end-user login flow, each of these CEL expressions
// must evaluate to either a string or an array of strings, or else the user's login will fail.
//
// These keys must be a domain-prefixed path (such as "acme.io/foo") and must not contain an equals sign ("=").
//
// expression must produce a string or string array value.
// If the value is empty, the extra mapping will not be present.
//
// Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/
//
// hard-coded extra key/value
// - key: "acme.io/foo"
// valueExpression: "'bar'"
// This will result in an extra attribute - acme.io/foo: ["bar"]
//
// hard-coded key, value copying claim value
// - key: "acme.io/foo"
// valueExpression: "claims.some_claim"
// This will result in an extra attribute - acme.io/foo: [value of some_claim]
//
// hard-coded key, value derived from claim value
// - key: "acme.io/admin"
// valueExpression: '(has(claims.is_admin) && claims.is_admin) ? "true":""'
// This will result in:
// - if is_admin claim is present and true, extra attribute - acme.io/admin: ["true"]
// - if is_admin claim is present and false or is_admin claim is not present, no extra attribute will be added
//
// +optional
Extra []ExtraMapping `json:"extra,omitempty"`
}
// 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.
// Additionally, the key must not contain an equals sign ("=").
// +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 +295,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"`

View File

@@ -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

View File

@@ -58,37 +58,219 @@ 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.
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.
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.
These keys must be a domain-prefixed path (such as "acme.io/foo") and must not contain an equals sign ("=").
expression must produce a string or string array value.
If the value is empty, the extra mapping will not be present.
Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/
hard-coded extra key/value
- key: "acme.io/foo"
valueExpression: "'bar'"
This will result in an extra attribute - acme.io/foo: ["bar"]
hard-coded key, value copying claim value
- key: "acme.io/foo"
valueExpression: "claims.some_claim"
This will result in an extra attribute - acme.io/foo: [value of some_claim]
hard-coded key, value derived from claim value
- key: "acme.io/admin"
valueExpression: '(has(claims.is_admin) && claims.is_admin) ? "true":""'
This will result in:
- if is_admin claim is present and true, extra attribute - acme.io/admin: ["true"]
- if is_admin claim is present and false or is_admin claim is not present, no extra attribute will be added
items:
description: ExtraMapping provides the configuration for a single
extra mapping.
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.
Additionally, the key must not contain an equals sign ("=").
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
group membership from the JWT token. When not specified, it will default to "groups".
groups is the name of the claim which should be read to extract the user's
group membership from the JWT token. When not specified, it will default to "groups",
unless groupsExpression is specified.
Mutually exclusive with groupsExpression. Use either groups or groupsExpression to
determine the user's group membership from the JWT token.
type: string
groupsExpression:
description: |-
groupsExpression represents an expression which will be evaluated by CEL.
The expression's result will become the user's group memberships.
groupsExpression is similar to claimMappings.groups.expression from Kubernetes AuthenticationConfiguration
as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication.
This is an advanced configuration option. During an end-user login flow, each of these CEL expressions
must evaluate to one of the expected types without errors, or else the user's login will fail.
Additionally, mistakes in this configuration can cause the users to have unintended group memberships.
The expression must produce a string or string array value.
"", [], and null values are treated as the group mapping not being present.
CEL expressions have access to the contents of the token claims, organized into CEL variable:
- 'claims' is a map of claim names to claim values.
For example, a variable named 'sub' can be accessed as 'claims.sub'.
Nested claims can be accessed using dot notation, e.g. 'claims.foo.bar'.
Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/
Mutually exclusive with groups. Use either groups or groupsExpression to
determine the user's group membership from the JWT token.
type: string
username:
description: |-
Username is the name of the claim which should be read to extract the
username from the JWT token. When not specified, it will default to "username".
username is the name of the claim which should be read to extract the
username from the JWT token. When not specified, it will default to "username",
unless usernameExpression is specified.
Mutually exclusive with usernameExpression. Use either username or usernameExpression to
determine the user's username from the JWT token.
type: string
usernameExpression:
description: |-
usernameExpression represents an expression which will be evaluated by CEL.
The expression's result will become the user's username.
usernameExpression is similar to claimMappings.username.expression from Kubernetes AuthenticationConfiguration
as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication.
This is an advanced configuration option. During an end-user login flow, each of these CEL expressions
must evaluate to the expected type without errors, or else the user's login will fail.
Additionally, mistakes in this configuration can cause the users to have unintended usernames.
The expression must produce a non-empty string value.
If the expression uses 'claims.email', then 'claims.email_verified' must be used in
the expression or extra[*].valueExpression or claimValidationRules[*].expression.
An example claim validation rule expression that matches the validation automatically
applied when username.claim is set to 'email' is 'claims.?email_verified.orValue(true) == true'.
By explicitly comparing the value to true, we let type-checking see the result will be a boolean,
and to make sure a non-boolean email_verified claim will be caught at runtime.
CEL expressions have access to the contents of the token claims, organized into CEL variable:
- 'claims' is a map of claim names to claim values.
For example, a variable named 'sub' can be accessed as 'claims.sub'.
Nested claims can be accessed using dot notation, e.g. 'claims.foo.bar'.
Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/
Mutually exclusive with username. Use either username or usernameExpression to
determine the user's username from the JWT token.
type: string
type: object
issuer:
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 +310,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

View File

@@ -60,6 +60,78 @@ 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. +
Only string claim keys are supported. +
Mutually exclusive with expression and message. +
| *`requiredValue`* __string__ | requiredValue is the value of a required claim. +
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. +
Additionally, the key must not contain an equals sign ("="). +
| *`valueExpression`* __string__ | valueExpression is a CEL expression to extract extra attribute value. +
valueExpression must produce a string or string array value. +
"", [], and null values are treated as the extra mapping not being present. +
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 +150,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 +172,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 +182,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 +236,103 @@ for Kubernetes access.
[cols="25a,75a", options="header"]
|===
| Field | Description
| *`groups`* __string__ | Groups is the name of the claim which should be read to extract the user's +
group membership from the JWT token. When not specified, it will default to "groups". +
| *`username`* __string__ | Username is the name of the claim which should be read to extract the +
username from the JWT token. When not specified, it will default to "username". +
| *`username`* __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", +
unless usernameExpression is specified. +
Mutually exclusive with usernameExpression. Use either username or usernameExpression to +
determine the user's username from the JWT token. +
| *`usernameExpression`* __string__ | usernameExpression represents an expression which will be evaluated by CEL. +
The expression's result will become the user's username. +
usernameExpression is similar to claimMappings.username.expression from Kubernetes AuthenticationConfiguration +
as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication. +
This is an advanced configuration option. During an end-user login flow, each of these CEL expressions +
must evaluate to the expected type without errors, or else the user's login will fail. +
Additionally, mistakes in this configuration can cause the users to have unintended usernames. +
The expression must produce a non-empty string value. +
If the expression uses 'claims.email', then 'claims.email_verified' must be used in +
the expression or extra[*].valueExpression or claimValidationRules[*].expression. +
An example claim validation rule expression that matches the validation automatically +
applied when username.claim is set to 'email' is 'claims.?email_verified.orValue(true) == true'. +
By explicitly comparing the value to true, we let type-checking see the result will be a boolean, +
and to make sure a non-boolean email_verified claim will be caught at runtime. +
CEL expressions have access to the contents of the token claims, organized into CEL variable: +
- 'claims' is a map of claim names to claim values. +
For example, a variable named 'sub' can be accessed as 'claims.sub'. +
Nested claims can be accessed using dot notation, e.g. 'claims.foo.bar'. +
Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ +
Mutually exclusive with username. Use either username or usernameExpression to +
determine the user's username from the JWT token. +
| *`groups`* __string__ | groups is the name of the claim which should be read to extract the user's +
group membership from the JWT token. When not specified, it will default to "groups", +
unless groupsExpression is specified. +
Mutually exclusive with groupsExpression. Use either groups or groupsExpression to +
determine the user's group membership from the JWT token. +
| *`groupsExpression`* __string__ | groupsExpression represents an expression which will be evaluated by CEL. +
The expression's result will become the user's group memberships. +
groupsExpression is similar to claimMappings.groups.expression from Kubernetes AuthenticationConfiguration +
as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication. +
This is an advanced configuration option. During an end-user login flow, each of these CEL expressions +
must evaluate to one of the expected types without errors, or else the user's login will fail. +
Additionally, mistakes in this configuration can cause the users to have unintended group memberships. +
The expression must produce a string or string array value. +
"", [], and null values are treated as the group mapping not being present. +
CEL expressions have access to the contents of the token claims, organized into CEL variable: +
- 'claims' is a map of claim names to claim values. +
For example, a variable named 'sub' can be accessed as 'claims.sub'. +
Nested claims can be accessed using dot notation, e.g. 'claims.foo.bar'. +
Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ +
Mutually exclusive with groups. Use either groups or groupsExpression to +
determine the user's group membership from the JWT token. +
| *`extra`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-30-apis-concierge-authentication-v1alpha1-extramapping[$$ExtraMapping$$] array__ | extra is similar to claimMappings.extra from Kubernetes AuthenticationConfiguration +
as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication. +
However, note that the Pinniped Concierge issues client certificates to users for the purpose +
of authenticating, and the Kubernetes API server does not have any mechanism for transmitting +
auth extras via client certificates. When configured, these extras will appear in client +
certificates issued by the Pinniped Supervisor in the x509 Subject field as Organizational +
Units (OU). However, when this client certificate is presented to Kubernetes for authentication, +
Kubernetes will ignore these extras. This is probably only useful if you are using a custom +
authenticating proxy in front of your Kubernetes API server which can translate these OUs into +
auth extras, as described by +
https://kubernetes.io/docs/reference/access-authn-authz/authentication/#authenticating-proxy. +
This is an advanced configuration option. During an end-user login flow, each of these CEL expressions +
must evaluate to either a string or an array of strings, or else the user's login will fail. +
These keys must be a domain-prefixed path (such as "acme.io/foo") and must not contain an equals sign ("="). +
expression must produce a string or string array value. +
If the value is empty, the extra mapping will not be present. +
Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ +
hard-coded extra key/value +
- key: "acme.io/foo" +
valueExpression: "'bar'" +
This will result in an extra attribute - acme.io/foo: ["bar"] +
hard-coded key, value copying claim value +
- key: "acme.io/foo" +
valueExpression: "claims.some_claim" +
This will result in an extra attribute - acme.io/foo: [value of some_claim] +
hard-coded key, value derived from claim value +
- key: "acme.io/admin" +
valueExpression: '(has(claims.is_admin) && claims.is_admin) ? "true":""' +
This will result in: +
- if is_admin claim is present and true, extra attribute - acme.io/admin: ["true"] +
- if is_admin claim is present and false or is_admin claim is not present, no extra attribute will be added +
|===
@@ -178,6 +356,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

View File

@@ -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,255 @@ 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.
// 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.
// 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
// group membership from the JWT token. When not specified, it will default to "groups".
// username is the name of the claim which should be read to extract the
// username from the JWT token. When not specified, it will default to "username",
// unless usernameExpression is specified.
//
// Mutually exclusive with usernameExpression. Use either username or usernameExpression to
// determine the user's username from the JWT token.
// +optional
Username string `json:"username"`
// usernameExpression represents an expression which will be evaluated by CEL.
// The expression's result will become the user's username.
//
// usernameExpression is similar to claimMappings.username.expression from Kubernetes AuthenticationConfiguration
// as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication.
// This is an advanced configuration option. During an end-user login flow, each of these CEL expressions
// must evaluate to the expected type without errors, or else the user's login will fail.
// Additionally, mistakes in this configuration can cause the users to have unintended usernames.
//
// The expression must produce a non-empty string value.
// If the expression uses 'claims.email', then 'claims.email_verified' must be used in
// the expression or extra[*].valueExpression or claimValidationRules[*].expression.
// An example claim validation rule expression that matches the validation automatically
// applied when username.claim is set to 'email' is 'claims.?email_verified.orValue(true) == true'.
// By explicitly comparing the value to true, we let type-checking see the result will be a boolean,
// and to make sure a non-boolean email_verified claim will be caught at runtime.
//
// CEL expressions have access to the contents of the token claims, organized into CEL variable:
// - 'claims' is a map of claim names to claim values.
// For example, a variable named 'sub' can be accessed as 'claims.sub'.
// Nested claims can be accessed using dot notation, e.g. 'claims.foo.bar'.
//
// Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/
//
// Mutually exclusive with username. Use either username or usernameExpression to
// determine the user's username from the JWT token.
// +optional
UsernameExpression string `json:"usernameExpression,omitempty"`
// groups is the name of the claim which should be read to extract the user's
// group membership from the JWT token. When not specified, it will default to "groups",
// unless groupsExpression is specified.
//
// Mutually exclusive with groupsExpression. Use either groups or groupsExpression to
// determine the user's group membership from the JWT token.
// +optional
Groups string `json:"groups"`
// Username is the name of the claim which should be read to extract the
// username from the JWT token. When not specified, it will default to "username".
// groupsExpression represents an expression which will be evaluated by CEL.
// The expression's result will become the user's group memberships.
//
// groupsExpression is similar to claimMappings.groups.expression from Kubernetes AuthenticationConfiguration
// as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication.
// This is an advanced configuration option. During an end-user login flow, each of these CEL expressions
// must evaluate to one of the expected types without errors, or else the user's login will fail.
// Additionally, mistakes in this configuration can cause the users to have unintended group memberships.
//
// The expression must produce a string or string array value.
// "", [], and null values are treated as the group mapping not being present.
//
// CEL expressions have access to the contents of the token claims, organized into CEL variable:
// - 'claims' is a map of claim names to claim values.
// For example, a variable named 'sub' can be accessed as 'claims.sub'.
// Nested claims can be accessed using dot notation, e.g. 'claims.foo.bar'.
//
// Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/
//
// Mutually exclusive with groups. Use either groups or groupsExpression to
// determine the user's group membership from the JWT token.
// +optional
Username string `json:"username"`
GroupsExpression string `json:"groupsExpression,omitempty"`
// extra is similar to claimMappings.extra from Kubernetes AuthenticationConfiguration
// as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication.
//
// However, note that the Pinniped Concierge issues client certificates to users for the purpose
// of authenticating, and the Kubernetes API server does not have any mechanism for transmitting
// auth extras via client certificates. When configured, these extras will appear in client
// certificates issued by the Pinniped Supervisor in the x509 Subject field as Organizational
// Units (OU). However, when this client certificate is presented to Kubernetes for authentication,
// Kubernetes will ignore these extras. This is probably only useful if you are using a custom
// authenticating proxy in front of your Kubernetes API server which can translate these OUs into
// auth extras, as described by
// https://kubernetes.io/docs/reference/access-authn-authz/authentication/#authenticating-proxy.
// This is an advanced configuration option. During an end-user login flow, each of these CEL expressions
// must evaluate to either a string or an array of strings, or else the user's login will fail.
//
// These keys must be a domain-prefixed path (such as "acme.io/foo") and must not contain an equals sign ("=").
//
// expression must produce a string or string array value.
// If the value is empty, the extra mapping will not be present.
//
// Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/
//
// hard-coded extra key/value
// - key: "acme.io/foo"
// valueExpression: "'bar'"
// This will result in an extra attribute - acme.io/foo: ["bar"]
//
// hard-coded key, value copying claim value
// - key: "acme.io/foo"
// valueExpression: "claims.some_claim"
// This will result in an extra attribute - acme.io/foo: [value of some_claim]
//
// hard-coded key, value derived from claim value
// - key: "acme.io/admin"
// valueExpression: '(has(claims.is_admin) && claims.is_admin) ? "true":""'
// This will result in:
// - if is_admin claim is present and true, extra attribute - acme.io/admin: ["true"]
// - if is_admin claim is present and false or is_admin claim is not present, no extra attribute will be added
//
// +optional
Extra []ExtraMapping `json:"extra,omitempty"`
}
// 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.
// Additionally, the key must not contain an equals sign ("=").
// +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 +295,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"`

View File

@@ -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

View File

@@ -58,37 +58,219 @@ 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.
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.
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.
These keys must be a domain-prefixed path (such as "acme.io/foo") and must not contain an equals sign ("=").
expression must produce a string or string array value.
If the value is empty, the extra mapping will not be present.
Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/
hard-coded extra key/value
- key: "acme.io/foo"
valueExpression: "'bar'"
This will result in an extra attribute - acme.io/foo: ["bar"]
hard-coded key, value copying claim value
- key: "acme.io/foo"
valueExpression: "claims.some_claim"
This will result in an extra attribute - acme.io/foo: [value of some_claim]
hard-coded key, value derived from claim value
- key: "acme.io/admin"
valueExpression: '(has(claims.is_admin) && claims.is_admin) ? "true":""'
This will result in:
- if is_admin claim is present and true, extra attribute - acme.io/admin: ["true"]
- if is_admin claim is present and false or is_admin claim is not present, no extra attribute will be added
items:
description: ExtraMapping provides the configuration for a single
extra mapping.
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.
Additionally, the key must not contain an equals sign ("=").
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
group membership from the JWT token. When not specified, it will default to "groups".
groups is the name of the claim which should be read to extract the user's
group membership from the JWT token. When not specified, it will default to "groups",
unless groupsExpression is specified.
Mutually exclusive with groupsExpression. Use either groups or groupsExpression to
determine the user's group membership from the JWT token.
type: string
groupsExpression:
description: |-
groupsExpression represents an expression which will be evaluated by CEL.
The expression's result will become the user's group memberships.
groupsExpression is similar to claimMappings.groups.expression from Kubernetes AuthenticationConfiguration
as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication.
This is an advanced configuration option. During an end-user login flow, each of these CEL expressions
must evaluate to one of the expected types without errors, or else the user's login will fail.
Additionally, mistakes in this configuration can cause the users to have unintended group memberships.
The expression must produce a string or string array value.
"", [], and null values are treated as the group mapping not being present.
CEL expressions have access to the contents of the token claims, organized into CEL variable:
- 'claims' is a map of claim names to claim values.
For example, a variable named 'sub' can be accessed as 'claims.sub'.
Nested claims can be accessed using dot notation, e.g. 'claims.foo.bar'.
Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/
Mutually exclusive with groups. Use either groups or groupsExpression to
determine the user's group membership from the JWT token.
type: string
username:
description: |-
Username is the name of the claim which should be read to extract the
username from the JWT token. When not specified, it will default to "username".
username is the name of the claim which should be read to extract the
username from the JWT token. When not specified, it will default to "username",
unless usernameExpression is specified.
Mutually exclusive with usernameExpression. Use either username or usernameExpression to
determine the user's username from the JWT token.
type: string
usernameExpression:
description: |-
usernameExpression represents an expression which will be evaluated by CEL.
The expression's result will become the user's username.
usernameExpression is similar to claimMappings.username.expression from Kubernetes AuthenticationConfiguration
as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication.
This is an advanced configuration option. During an end-user login flow, each of these CEL expressions
must evaluate to the expected type without errors, or else the user's login will fail.
Additionally, mistakes in this configuration can cause the users to have unintended usernames.
The expression must produce a non-empty string value.
If the expression uses 'claims.email', then 'claims.email_verified' must be used in
the expression or extra[*].valueExpression or claimValidationRules[*].expression.
An example claim validation rule expression that matches the validation automatically
applied when username.claim is set to 'email' is 'claims.?email_verified.orValue(true) == true'.
By explicitly comparing the value to true, we let type-checking see the result will be a boolean,
and to make sure a non-boolean email_verified claim will be caught at runtime.
CEL expressions have access to the contents of the token claims, organized into CEL variable:
- 'claims' is a map of claim names to claim values.
For example, a variable named 'sub' can be accessed as 'claims.sub'.
Nested claims can be accessed using dot notation, e.g. 'claims.foo.bar'.
Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/
Mutually exclusive with username. Use either username or usernameExpression to
determine the user's username from the JWT token.
type: string
type: object
issuer:
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 +310,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

View File

@@ -60,6 +60,78 @@ 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. +
Only string claim keys are supported. +
Mutually exclusive with expression and message. +
| *`requiredValue`* __string__ | requiredValue is the value of a required claim. +
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. +
Additionally, the key must not contain an equals sign ("="). +
| *`valueExpression`* __string__ | valueExpression is a CEL expression to extract extra attribute value. +
valueExpression must produce a string or string array value. +
"", [], and null values are treated as the extra mapping not being present. +
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 +150,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 +172,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 +182,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 +236,103 @@ for Kubernetes access.
[cols="25a,75a", options="header"]
|===
| Field | Description
| *`groups`* __string__ | Groups is the name of the claim which should be read to extract the user's +
group membership from the JWT token. When not specified, it will default to "groups". +
| *`username`* __string__ | Username is the name of the claim which should be read to extract the +
username from the JWT token. When not specified, it will default to "username". +
| *`username`* __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", +
unless usernameExpression is specified. +
Mutually exclusive with usernameExpression. Use either username or usernameExpression to +
determine the user's username from the JWT token. +
| *`usernameExpression`* __string__ | usernameExpression represents an expression which will be evaluated by CEL. +
The expression's result will become the user's username. +
usernameExpression is similar to claimMappings.username.expression from Kubernetes AuthenticationConfiguration +
as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication. +
This is an advanced configuration option. During an end-user login flow, each of these CEL expressions +
must evaluate to the expected type without errors, or else the user's login will fail. +
Additionally, mistakes in this configuration can cause the users to have unintended usernames. +
The expression must produce a non-empty string value. +
If the expression uses 'claims.email', then 'claims.email_verified' must be used in +
the expression or extra[*].valueExpression or claimValidationRules[*].expression. +
An example claim validation rule expression that matches the validation automatically +
applied when username.claim is set to 'email' is 'claims.?email_verified.orValue(true) == true'. +
By explicitly comparing the value to true, we let type-checking see the result will be a boolean, +
and to make sure a non-boolean email_verified claim will be caught at runtime. +
CEL expressions have access to the contents of the token claims, organized into CEL variable: +
- 'claims' is a map of claim names to claim values. +
For example, a variable named 'sub' can be accessed as 'claims.sub'. +
Nested claims can be accessed using dot notation, e.g. 'claims.foo.bar'. +
Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ +
Mutually exclusive with username. Use either username or usernameExpression to +
determine the user's username from the JWT token. +
| *`groups`* __string__ | groups is the name of the claim which should be read to extract the user's +
group membership from the JWT token. When not specified, it will default to "groups", +
unless groupsExpression is specified. +
Mutually exclusive with groupsExpression. Use either groups or groupsExpression to +
determine the user's group membership from the JWT token. +
| *`groupsExpression`* __string__ | groupsExpression represents an expression which will be evaluated by CEL. +
The expression's result will become the user's group memberships. +
groupsExpression is similar to claimMappings.groups.expression from Kubernetes AuthenticationConfiguration +
as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication. +
This is an advanced configuration option. During an end-user login flow, each of these CEL expressions +
must evaluate to one of the expected types without errors, or else the user's login will fail. +
Additionally, mistakes in this configuration can cause the users to have unintended group memberships. +
The expression must produce a string or string array value. +
"", [], and null values are treated as the group mapping not being present. +
CEL expressions have access to the contents of the token claims, organized into CEL variable: +
- 'claims' is a map of claim names to claim values. +
For example, a variable named 'sub' can be accessed as 'claims.sub'. +
Nested claims can be accessed using dot notation, e.g. 'claims.foo.bar'. +
Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ +
Mutually exclusive with groups. Use either groups or groupsExpression to +
determine the user's group membership from the JWT token. +
| *`extra`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-31-apis-concierge-authentication-v1alpha1-extramapping[$$ExtraMapping$$] array__ | extra is similar to claimMappings.extra from Kubernetes AuthenticationConfiguration +
as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication. +
However, note that the Pinniped Concierge issues client certificates to users for the purpose +
of authenticating, and the Kubernetes API server does not have any mechanism for transmitting +
auth extras via client certificates. When configured, these extras will appear in client +
certificates issued by the Pinniped Supervisor in the x509 Subject field as Organizational +
Units (OU). However, when this client certificate is presented to Kubernetes for authentication, +
Kubernetes will ignore these extras. This is probably only useful if you are using a custom +
authenticating proxy in front of your Kubernetes API server which can translate these OUs into +
auth extras, as described by +
https://kubernetes.io/docs/reference/access-authn-authz/authentication/#authenticating-proxy. +
This is an advanced configuration option. During an end-user login flow, each of these CEL expressions +
must evaluate to either a string or an array of strings, or else the user's login will fail. +
These keys must be a domain-prefixed path (such as "acme.io/foo") and must not contain an equals sign ("="). +
expression must produce a string or string array value. +
If the value is empty, the extra mapping will not be present. +
Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ +
hard-coded extra key/value +
- key: "acme.io/foo" +
valueExpression: "'bar'" +
This will result in an extra attribute - acme.io/foo: ["bar"] +
hard-coded key, value copying claim value +
- key: "acme.io/foo" +
valueExpression: "claims.some_claim" +
This will result in an extra attribute - acme.io/foo: [value of some_claim] +
hard-coded key, value derived from claim value +
- key: "acme.io/admin" +
valueExpression: '(has(claims.is_admin) && claims.is_admin) ? "true":""' +
This will result in: +
- if is_admin claim is present and true, extra attribute - acme.io/admin: ["true"] +
- if is_admin claim is present and false or is_admin claim is not present, no extra attribute will be added +
|===
@@ -178,6 +356,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

View File

@@ -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,255 @@ 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.
// 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.
// 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
// group membership from the JWT token. When not specified, it will default to "groups".
// username is the name of the claim which should be read to extract the
// username from the JWT token. When not specified, it will default to "username",
// unless usernameExpression is specified.
//
// Mutually exclusive with usernameExpression. Use either username or usernameExpression to
// determine the user's username from the JWT token.
// +optional
Username string `json:"username"`
// usernameExpression represents an expression which will be evaluated by CEL.
// The expression's result will become the user's username.
//
// usernameExpression is similar to claimMappings.username.expression from Kubernetes AuthenticationConfiguration
// as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication.
// This is an advanced configuration option. During an end-user login flow, each of these CEL expressions
// must evaluate to the expected type without errors, or else the user's login will fail.
// Additionally, mistakes in this configuration can cause the users to have unintended usernames.
//
// The expression must produce a non-empty string value.
// If the expression uses 'claims.email', then 'claims.email_verified' must be used in
// the expression or extra[*].valueExpression or claimValidationRules[*].expression.
// An example claim validation rule expression that matches the validation automatically
// applied when username.claim is set to 'email' is 'claims.?email_verified.orValue(true) == true'.
// By explicitly comparing the value to true, we let type-checking see the result will be a boolean,
// and to make sure a non-boolean email_verified claim will be caught at runtime.
//
// CEL expressions have access to the contents of the token claims, organized into CEL variable:
// - 'claims' is a map of claim names to claim values.
// For example, a variable named 'sub' can be accessed as 'claims.sub'.
// Nested claims can be accessed using dot notation, e.g. 'claims.foo.bar'.
//
// Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/
//
// Mutually exclusive with username. Use either username or usernameExpression to
// determine the user's username from the JWT token.
// +optional
UsernameExpression string `json:"usernameExpression,omitempty"`
// groups is the name of the claim which should be read to extract the user's
// group membership from the JWT token. When not specified, it will default to "groups",
// unless groupsExpression is specified.
//
// Mutually exclusive with groupsExpression. Use either groups or groupsExpression to
// determine the user's group membership from the JWT token.
// +optional
Groups string `json:"groups"`
// Username is the name of the claim which should be read to extract the
// username from the JWT token. When not specified, it will default to "username".
// groupsExpression represents an expression which will be evaluated by CEL.
// The expression's result will become the user's group memberships.
//
// groupsExpression is similar to claimMappings.groups.expression from Kubernetes AuthenticationConfiguration
// as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication.
// This is an advanced configuration option. During an end-user login flow, each of these CEL expressions
// must evaluate to one of the expected types without errors, or else the user's login will fail.
// Additionally, mistakes in this configuration can cause the users to have unintended group memberships.
//
// The expression must produce a string or string array value.
// "", [], and null values are treated as the group mapping not being present.
//
// CEL expressions have access to the contents of the token claims, organized into CEL variable:
// - 'claims' is a map of claim names to claim values.
// For example, a variable named 'sub' can be accessed as 'claims.sub'.
// Nested claims can be accessed using dot notation, e.g. 'claims.foo.bar'.
//
// Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/
//
// Mutually exclusive with groups. Use either groups or groupsExpression to
// determine the user's group membership from the JWT token.
// +optional
Username string `json:"username"`
GroupsExpression string `json:"groupsExpression,omitempty"`
// extra is similar to claimMappings.extra from Kubernetes AuthenticationConfiguration
// as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication.
//
// However, note that the Pinniped Concierge issues client certificates to users for the purpose
// of authenticating, and the Kubernetes API server does not have any mechanism for transmitting
// auth extras via client certificates. When configured, these extras will appear in client
// certificates issued by the Pinniped Supervisor in the x509 Subject field as Organizational
// Units (OU). However, when this client certificate is presented to Kubernetes for authentication,
// Kubernetes will ignore these extras. This is probably only useful if you are using a custom
// authenticating proxy in front of your Kubernetes API server which can translate these OUs into
// auth extras, as described by
// https://kubernetes.io/docs/reference/access-authn-authz/authentication/#authenticating-proxy.
// This is an advanced configuration option. During an end-user login flow, each of these CEL expressions
// must evaluate to either a string or an array of strings, or else the user's login will fail.
//
// These keys must be a domain-prefixed path (such as "acme.io/foo") and must not contain an equals sign ("=").
//
// expression must produce a string or string array value.
// If the value is empty, the extra mapping will not be present.
//
// Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/
//
// hard-coded extra key/value
// - key: "acme.io/foo"
// valueExpression: "'bar'"
// This will result in an extra attribute - acme.io/foo: ["bar"]
//
// hard-coded key, value copying claim value
// - key: "acme.io/foo"
// valueExpression: "claims.some_claim"
// This will result in an extra attribute - acme.io/foo: [value of some_claim]
//
// hard-coded key, value derived from claim value
// - key: "acme.io/admin"
// valueExpression: '(has(claims.is_admin) && claims.is_admin) ? "true":""'
// This will result in:
// - if is_admin claim is present and true, extra attribute - acme.io/admin: ["true"]
// - if is_admin claim is present and false or is_admin claim is not present, no extra attribute will be added
//
// +optional
Extra []ExtraMapping `json:"extra,omitempty"`
}
// 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.
// Additionally, the key must not contain an equals sign ("=").
// +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 +295,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"`

View File

@@ -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

View File

@@ -58,37 +58,219 @@ 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.
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.
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.
These keys must be a domain-prefixed path (such as "acme.io/foo") and must not contain an equals sign ("=").
expression must produce a string or string array value.
If the value is empty, the extra mapping will not be present.
Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/
hard-coded extra key/value
- key: "acme.io/foo"
valueExpression: "'bar'"
This will result in an extra attribute - acme.io/foo: ["bar"]
hard-coded key, value copying claim value
- key: "acme.io/foo"
valueExpression: "claims.some_claim"
This will result in an extra attribute - acme.io/foo: [value of some_claim]
hard-coded key, value derived from claim value
- key: "acme.io/admin"
valueExpression: '(has(claims.is_admin) && claims.is_admin) ? "true":""'
This will result in:
- if is_admin claim is present and true, extra attribute - acme.io/admin: ["true"]
- if is_admin claim is present and false or is_admin claim is not present, no extra attribute will be added
items:
description: ExtraMapping provides the configuration for a single
extra mapping.
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.
Additionally, the key must not contain an equals sign ("=").
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
group membership from the JWT token. When not specified, it will default to "groups".
groups is the name of the claim which should be read to extract the user's
group membership from the JWT token. When not specified, it will default to "groups",
unless groupsExpression is specified.
Mutually exclusive with groupsExpression. Use either groups or groupsExpression to
determine the user's group membership from the JWT token.
type: string
groupsExpression:
description: |-
groupsExpression represents an expression which will be evaluated by CEL.
The expression's result will become the user's group memberships.
groupsExpression is similar to claimMappings.groups.expression from Kubernetes AuthenticationConfiguration
as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication.
This is an advanced configuration option. During an end-user login flow, each of these CEL expressions
must evaluate to one of the expected types without errors, or else the user's login will fail.
Additionally, mistakes in this configuration can cause the users to have unintended group memberships.
The expression must produce a string or string array value.
"", [], and null values are treated as the group mapping not being present.
CEL expressions have access to the contents of the token claims, organized into CEL variable:
- 'claims' is a map of claim names to claim values.
For example, a variable named 'sub' can be accessed as 'claims.sub'.
Nested claims can be accessed using dot notation, e.g. 'claims.foo.bar'.
Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/
Mutually exclusive with groups. Use either groups or groupsExpression to
determine the user's group membership from the JWT token.
type: string
username:
description: |-
Username is the name of the claim which should be read to extract the
username from the JWT token. When not specified, it will default to "username".
username is the name of the claim which should be read to extract the
username from the JWT token. When not specified, it will default to "username",
unless usernameExpression is specified.
Mutually exclusive with usernameExpression. Use either username or usernameExpression to
determine the user's username from the JWT token.
type: string
usernameExpression:
description: |-
usernameExpression represents an expression which will be evaluated by CEL.
The expression's result will become the user's username.
usernameExpression is similar to claimMappings.username.expression from Kubernetes AuthenticationConfiguration
as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication.
This is an advanced configuration option. During an end-user login flow, each of these CEL expressions
must evaluate to the expected type without errors, or else the user's login will fail.
Additionally, mistakes in this configuration can cause the users to have unintended usernames.
The expression must produce a non-empty string value.
If the expression uses 'claims.email', then 'claims.email_verified' must be used in
the expression or extra[*].valueExpression or claimValidationRules[*].expression.
An example claim validation rule expression that matches the validation automatically
applied when username.claim is set to 'email' is 'claims.?email_verified.orValue(true) == true'.
By explicitly comparing the value to true, we let type-checking see the result will be a boolean,
and to make sure a non-boolean email_verified claim will be caught at runtime.
CEL expressions have access to the contents of the token claims, organized into CEL variable:
- 'claims' is a map of claim names to claim values.
For example, a variable named 'sub' can be accessed as 'claims.sub'.
Nested claims can be accessed using dot notation, e.g. 'claims.foo.bar'.
Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/
Mutually exclusive with username. Use either username or usernameExpression to
determine the user's username from the JWT token.
type: string
type: object
issuer:
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 +310,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

View File

@@ -60,6 +60,78 @@ 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. +
Only string claim keys are supported. +
Mutually exclusive with expression and message. +
| *`requiredValue`* __string__ | requiredValue is the value of a required claim. +
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. +
Additionally, the key must not contain an equals sign ("="). +
| *`valueExpression`* __string__ | valueExpression is a CEL expression to extract extra attribute value. +
valueExpression must produce a string or string array value. +
"", [], and null values are treated as the extra mapping not being present. +
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 +150,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 +172,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 +182,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 +236,103 @@ for Kubernetes access.
[cols="25a,75a", options="header"]
|===
| Field | Description
| *`groups`* __string__ | Groups is the name of the claim which should be read to extract the user's +
group membership from the JWT token. When not specified, it will default to "groups". +
| *`username`* __string__ | Username is the name of the claim which should be read to extract the +
username from the JWT token. When not specified, it will default to "username". +
| *`username`* __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", +
unless usernameExpression is specified. +
Mutually exclusive with usernameExpression. Use either username or usernameExpression to +
determine the user's username from the JWT token. +
| *`usernameExpression`* __string__ | usernameExpression represents an expression which will be evaluated by CEL. +
The expression's result will become the user's username. +
usernameExpression is similar to claimMappings.username.expression from Kubernetes AuthenticationConfiguration +
as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication. +
This is an advanced configuration option. During an end-user login flow, each of these CEL expressions +
must evaluate to the expected type without errors, or else the user's login will fail. +
Additionally, mistakes in this configuration can cause the users to have unintended usernames. +
The expression must produce a non-empty string value. +
If the expression uses 'claims.email', then 'claims.email_verified' must be used in +
the expression or extra[*].valueExpression or claimValidationRules[*].expression. +
An example claim validation rule expression that matches the validation automatically +
applied when username.claim is set to 'email' is 'claims.?email_verified.orValue(true) == true'. +
By explicitly comparing the value to true, we let type-checking see the result will be a boolean, +
and to make sure a non-boolean email_verified claim will be caught at runtime. +
CEL expressions have access to the contents of the token claims, organized into CEL variable: +
- 'claims' is a map of claim names to claim values. +
For example, a variable named 'sub' can be accessed as 'claims.sub'. +
Nested claims can be accessed using dot notation, e.g. 'claims.foo.bar'. +
Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ +
Mutually exclusive with username. Use either username or usernameExpression to +
determine the user's username from the JWT token. +
| *`groups`* __string__ | groups is the name of the claim which should be read to extract the user's +
group membership from the JWT token. When not specified, it will default to "groups", +
unless groupsExpression is specified. +
Mutually exclusive with groupsExpression. Use either groups or groupsExpression to +
determine the user's group membership from the JWT token. +
| *`groupsExpression`* __string__ | groupsExpression represents an expression which will be evaluated by CEL. +
The expression's result will become the user's group memberships. +
groupsExpression is similar to claimMappings.groups.expression from Kubernetes AuthenticationConfiguration +
as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication. +
This is an advanced configuration option. During an end-user login flow, each of these CEL expressions +
must evaluate to one of the expected types without errors, or else the user's login will fail. +
Additionally, mistakes in this configuration can cause the users to have unintended group memberships. +
The expression must produce a string or string array value. +
"", [], and null values are treated as the group mapping not being present. +
CEL expressions have access to the contents of the token claims, organized into CEL variable: +
- 'claims' is a map of claim names to claim values. +
For example, a variable named 'sub' can be accessed as 'claims.sub'. +
Nested claims can be accessed using dot notation, e.g. 'claims.foo.bar'. +
Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ +
Mutually exclusive with groups. Use either groups or groupsExpression to +
determine the user's group membership from the JWT token. +
| *`extra`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-32-apis-concierge-authentication-v1alpha1-extramapping[$$ExtraMapping$$] array__ | extra is similar to claimMappings.extra from Kubernetes AuthenticationConfiguration +
as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication. +
However, note that the Pinniped Concierge issues client certificates to users for the purpose +
of authenticating, and the Kubernetes API server does not have any mechanism for transmitting +
auth extras via client certificates. When configured, these extras will appear in client +
certificates issued by the Pinniped Supervisor in the x509 Subject field as Organizational +
Units (OU). However, when this client certificate is presented to Kubernetes for authentication, +
Kubernetes will ignore these extras. This is probably only useful if you are using a custom +
authenticating proxy in front of your Kubernetes API server which can translate these OUs into +
auth extras, as described by +
https://kubernetes.io/docs/reference/access-authn-authz/authentication/#authenticating-proxy. +
This is an advanced configuration option. During an end-user login flow, each of these CEL expressions +
must evaluate to either a string or an array of strings, or else the user's login will fail. +
These keys must be a domain-prefixed path (such as "acme.io/foo") and must not contain an equals sign ("="). +
expression must produce a string or string array value. +
If the value is empty, the extra mapping will not be present. +
Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ +
hard-coded extra key/value +
- key: "acme.io/foo" +
valueExpression: "'bar'" +
This will result in an extra attribute - acme.io/foo: ["bar"] +
hard-coded key, value copying claim value +
- key: "acme.io/foo" +
valueExpression: "claims.some_claim" +
This will result in an extra attribute - acme.io/foo: [value of some_claim] +
hard-coded key, value derived from claim value +
- key: "acme.io/admin" +
valueExpression: '(has(claims.is_admin) && claims.is_admin) ? "true":""' +
This will result in: +
- if is_admin claim is present and true, extra attribute - acme.io/admin: ["true"] +
- if is_admin claim is present and false or is_admin claim is not present, no extra attribute will be added +
|===
@@ -178,6 +356,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

View File

@@ -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,255 @@ 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.
// 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.
// 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
// group membership from the JWT token. When not specified, it will default to "groups".
// username is the name of the claim which should be read to extract the
// username from the JWT token. When not specified, it will default to "username",
// unless usernameExpression is specified.
//
// Mutually exclusive with usernameExpression. Use either username or usernameExpression to
// determine the user's username from the JWT token.
// +optional
Username string `json:"username"`
// usernameExpression represents an expression which will be evaluated by CEL.
// The expression's result will become the user's username.
//
// usernameExpression is similar to claimMappings.username.expression from Kubernetes AuthenticationConfiguration
// as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication.
// This is an advanced configuration option. During an end-user login flow, each of these CEL expressions
// must evaluate to the expected type without errors, or else the user's login will fail.
// Additionally, mistakes in this configuration can cause the users to have unintended usernames.
//
// The expression must produce a non-empty string value.
// If the expression uses 'claims.email', then 'claims.email_verified' must be used in
// the expression or extra[*].valueExpression or claimValidationRules[*].expression.
// An example claim validation rule expression that matches the validation automatically
// applied when username.claim is set to 'email' is 'claims.?email_verified.orValue(true) == true'.
// By explicitly comparing the value to true, we let type-checking see the result will be a boolean,
// and to make sure a non-boolean email_verified claim will be caught at runtime.
//
// CEL expressions have access to the contents of the token claims, organized into CEL variable:
// - 'claims' is a map of claim names to claim values.
// For example, a variable named 'sub' can be accessed as 'claims.sub'.
// Nested claims can be accessed using dot notation, e.g. 'claims.foo.bar'.
//
// Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/
//
// Mutually exclusive with username. Use either username or usernameExpression to
// determine the user's username from the JWT token.
// +optional
UsernameExpression string `json:"usernameExpression,omitempty"`
// groups is the name of the claim which should be read to extract the user's
// group membership from the JWT token. When not specified, it will default to "groups",
// unless groupsExpression is specified.
//
// Mutually exclusive with groupsExpression. Use either groups or groupsExpression to
// determine the user's group membership from the JWT token.
// +optional
Groups string `json:"groups"`
// Username is the name of the claim which should be read to extract the
// username from the JWT token. When not specified, it will default to "username".
// groupsExpression represents an expression which will be evaluated by CEL.
// The expression's result will become the user's group memberships.
//
// groupsExpression is similar to claimMappings.groups.expression from Kubernetes AuthenticationConfiguration
// as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication.
// This is an advanced configuration option. During an end-user login flow, each of these CEL expressions
// must evaluate to one of the expected types without errors, or else the user's login will fail.
// Additionally, mistakes in this configuration can cause the users to have unintended group memberships.
//
// The expression must produce a string or string array value.
// "", [], and null values are treated as the group mapping not being present.
//
// CEL expressions have access to the contents of the token claims, organized into CEL variable:
// - 'claims' is a map of claim names to claim values.
// For example, a variable named 'sub' can be accessed as 'claims.sub'.
// Nested claims can be accessed using dot notation, e.g. 'claims.foo.bar'.
//
// Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/
//
// Mutually exclusive with groups. Use either groups or groupsExpression to
// determine the user's group membership from the JWT token.
// +optional
Username string `json:"username"`
GroupsExpression string `json:"groupsExpression,omitempty"`
// extra is similar to claimMappings.extra from Kubernetes AuthenticationConfiguration
// as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication.
//
// However, note that the Pinniped Concierge issues client certificates to users for the purpose
// of authenticating, and the Kubernetes API server does not have any mechanism for transmitting
// auth extras via client certificates. When configured, these extras will appear in client
// certificates issued by the Pinniped Supervisor in the x509 Subject field as Organizational
// Units (OU). However, when this client certificate is presented to Kubernetes for authentication,
// Kubernetes will ignore these extras. This is probably only useful if you are using a custom
// authenticating proxy in front of your Kubernetes API server which can translate these OUs into
// auth extras, as described by
// https://kubernetes.io/docs/reference/access-authn-authz/authentication/#authenticating-proxy.
// This is an advanced configuration option. During an end-user login flow, each of these CEL expressions
// must evaluate to either a string or an array of strings, or else the user's login will fail.
//
// These keys must be a domain-prefixed path (such as "acme.io/foo") and must not contain an equals sign ("=").
//
// expression must produce a string or string array value.
// If the value is empty, the extra mapping will not be present.
//
// Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/
//
// hard-coded extra key/value
// - key: "acme.io/foo"
// valueExpression: "'bar'"
// This will result in an extra attribute - acme.io/foo: ["bar"]
//
// hard-coded key, value copying claim value
// - key: "acme.io/foo"
// valueExpression: "claims.some_claim"
// This will result in an extra attribute - acme.io/foo: [value of some_claim]
//
// hard-coded key, value derived from claim value
// - key: "acme.io/admin"
// valueExpression: '(has(claims.is_admin) && claims.is_admin) ? "true":""'
// This will result in:
// - if is_admin claim is present and true, extra attribute - acme.io/admin: ["true"]
// - if is_admin claim is present and false or is_admin claim is not present, no extra attribute will be added
//
// +optional
Extra []ExtraMapping `json:"extra,omitempty"`
}
// 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.
// Additionally, the key must not contain an equals sign ("=").
// +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 +295,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"`

View File

@@ -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

View File

@@ -58,37 +58,219 @@ 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.
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.
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.
These keys must be a domain-prefixed path (such as "acme.io/foo") and must not contain an equals sign ("=").
expression must produce a string or string array value.
If the value is empty, the extra mapping will not be present.
Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/
hard-coded extra key/value
- key: "acme.io/foo"
valueExpression: "'bar'"
This will result in an extra attribute - acme.io/foo: ["bar"]
hard-coded key, value copying claim value
- key: "acme.io/foo"
valueExpression: "claims.some_claim"
This will result in an extra attribute - acme.io/foo: [value of some_claim]
hard-coded key, value derived from claim value
- key: "acme.io/admin"
valueExpression: '(has(claims.is_admin) && claims.is_admin) ? "true":""'
This will result in:
- if is_admin claim is present and true, extra attribute - acme.io/admin: ["true"]
- if is_admin claim is present and false or is_admin claim is not present, no extra attribute will be added
items:
description: ExtraMapping provides the configuration for a single
extra mapping.
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.
Additionally, the key must not contain an equals sign ("=").
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
group membership from the JWT token. When not specified, it will default to "groups".
groups is the name of the claim which should be read to extract the user's
group membership from the JWT token. When not specified, it will default to "groups",
unless groupsExpression is specified.
Mutually exclusive with groupsExpression. Use either groups or groupsExpression to
determine the user's group membership from the JWT token.
type: string
groupsExpression:
description: |-
groupsExpression represents an expression which will be evaluated by CEL.
The expression's result will become the user's group memberships.
groupsExpression is similar to claimMappings.groups.expression from Kubernetes AuthenticationConfiguration
as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication.
This is an advanced configuration option. During an end-user login flow, each of these CEL expressions
must evaluate to one of the expected types without errors, or else the user's login will fail.
Additionally, mistakes in this configuration can cause the users to have unintended group memberships.
The expression must produce a string or string array value.
"", [], and null values are treated as the group mapping not being present.
CEL expressions have access to the contents of the token claims, organized into CEL variable:
- 'claims' is a map of claim names to claim values.
For example, a variable named 'sub' can be accessed as 'claims.sub'.
Nested claims can be accessed using dot notation, e.g. 'claims.foo.bar'.
Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/
Mutually exclusive with groups. Use either groups or groupsExpression to
determine the user's group membership from the JWT token.
type: string
username:
description: |-
Username is the name of the claim which should be read to extract the
username from the JWT token. When not specified, it will default to "username".
username is the name of the claim which should be read to extract the
username from the JWT token. When not specified, it will default to "username",
unless usernameExpression is specified.
Mutually exclusive with usernameExpression. Use either username or usernameExpression to
determine the user's username from the JWT token.
type: string
usernameExpression:
description: |-
usernameExpression represents an expression which will be evaluated by CEL.
The expression's result will become the user's username.
usernameExpression is similar to claimMappings.username.expression from Kubernetes AuthenticationConfiguration
as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication.
This is an advanced configuration option. During an end-user login flow, each of these CEL expressions
must evaluate to the expected type without errors, or else the user's login will fail.
Additionally, mistakes in this configuration can cause the users to have unintended usernames.
The expression must produce a non-empty string value.
If the expression uses 'claims.email', then 'claims.email_verified' must be used in
the expression or extra[*].valueExpression or claimValidationRules[*].expression.
An example claim validation rule expression that matches the validation automatically
applied when username.claim is set to 'email' is 'claims.?email_verified.orValue(true) == true'.
By explicitly comparing the value to true, we let type-checking see the result will be a boolean,
and to make sure a non-boolean email_verified claim will be caught at runtime.
CEL expressions have access to the contents of the token claims, organized into CEL variable:
- 'claims' is a map of claim names to claim values.
For example, a variable named 'sub' can be accessed as 'claims.sub'.
Nested claims can be accessed using dot notation, e.g. 'claims.foo.bar'.
Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/
Mutually exclusive with username. Use either username or usernameExpression to
determine the user's username from the JWT token.
type: string
type: object
issuer:
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 +310,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

View File

@@ -60,6 +60,78 @@ 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. +
Only string claim keys are supported. +
Mutually exclusive with expression and message. +
| *`requiredValue`* __string__ | requiredValue is the value of a required claim. +
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. +
Additionally, the key must not contain an equals sign ("="). +
| *`valueExpression`* __string__ | valueExpression is a CEL expression to extract extra attribute value. +
valueExpression must produce a string or string array value. +
"", [], and null values are treated as the extra mapping not being present. +
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 +150,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 +172,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 +182,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 +236,103 @@ for Kubernetes access.
[cols="25a,75a", options="header"]
|===
| Field | Description
| *`groups`* __string__ | Groups is the name of the claim which should be read to extract the user's +
group membership from the JWT token. When not specified, it will default to "groups". +
| *`username`* __string__ | Username is the name of the claim which should be read to extract the +
username from the JWT token. When not specified, it will default to "username". +
| *`username`* __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", +
unless usernameExpression is specified. +
Mutually exclusive with usernameExpression. Use either username or usernameExpression to +
determine the user's username from the JWT token. +
| *`usernameExpression`* __string__ | usernameExpression represents an expression which will be evaluated by CEL. +
The expression's result will become the user's username. +
usernameExpression is similar to claimMappings.username.expression from Kubernetes AuthenticationConfiguration +
as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication. +
This is an advanced configuration option. During an end-user login flow, each of these CEL expressions +
must evaluate to the expected type without errors, or else the user's login will fail. +
Additionally, mistakes in this configuration can cause the users to have unintended usernames. +
The expression must produce a non-empty string value. +
If the expression uses 'claims.email', then 'claims.email_verified' must be used in +
the expression or extra[*].valueExpression or claimValidationRules[*].expression. +
An example claim validation rule expression that matches the validation automatically +
applied when username.claim is set to 'email' is 'claims.?email_verified.orValue(true) == true'. +
By explicitly comparing the value to true, we let type-checking see the result will be a boolean, +
and to make sure a non-boolean email_verified claim will be caught at runtime. +
CEL expressions have access to the contents of the token claims, organized into CEL variable: +
- 'claims' is a map of claim names to claim values. +
For example, a variable named 'sub' can be accessed as 'claims.sub'. +
Nested claims can be accessed using dot notation, e.g. 'claims.foo.bar'. +
Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ +
Mutually exclusive with username. Use either username or usernameExpression to +
determine the user's username from the JWT token. +
| *`groups`* __string__ | groups is the name of the claim which should be read to extract the user's +
group membership from the JWT token. When not specified, it will default to "groups", +
unless groupsExpression is specified. +
Mutually exclusive with groupsExpression. Use either groups or groupsExpression to +
determine the user's group membership from the JWT token. +
| *`groupsExpression`* __string__ | groupsExpression represents an expression which will be evaluated by CEL. +
The expression's result will become the user's group memberships. +
groupsExpression is similar to claimMappings.groups.expression from Kubernetes AuthenticationConfiguration +
as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication. +
This is an advanced configuration option. During an end-user login flow, each of these CEL expressions +
must evaluate to one of the expected types without errors, or else the user's login will fail. +
Additionally, mistakes in this configuration can cause the users to have unintended group memberships. +
The expression must produce a string or string array value. +
"", [], and null values are treated as the group mapping not being present. +
CEL expressions have access to the contents of the token claims, organized into CEL variable: +
- 'claims' is a map of claim names to claim values. +
For example, a variable named 'sub' can be accessed as 'claims.sub'. +
Nested claims can be accessed using dot notation, e.g. 'claims.foo.bar'. +
Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ +
Mutually exclusive with groups. Use either groups or groupsExpression to +
determine the user's group membership from the JWT token. +
| *`extra`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-33-apis-concierge-authentication-v1alpha1-extramapping[$$ExtraMapping$$] array__ | extra is similar to claimMappings.extra from Kubernetes AuthenticationConfiguration +
as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication. +
However, note that the Pinniped Concierge issues client certificates to users for the purpose +
of authenticating, and the Kubernetes API server does not have any mechanism for transmitting +
auth extras via client certificates. When configured, these extras will appear in client +
certificates issued by the Pinniped Supervisor in the x509 Subject field as Organizational +
Units (OU). However, when this client certificate is presented to Kubernetes for authentication, +
Kubernetes will ignore these extras. This is probably only useful if you are using a custom +
authenticating proxy in front of your Kubernetes API server which can translate these OUs into +
auth extras, as described by +
https://kubernetes.io/docs/reference/access-authn-authz/authentication/#authenticating-proxy. +
This is an advanced configuration option. During an end-user login flow, each of these CEL expressions +
must evaluate to either a string or an array of strings, or else the user's login will fail. +
These keys must be a domain-prefixed path (such as "acme.io/foo") and must not contain an equals sign ("="). +
expression must produce a string or string array value. +
If the value is empty, the extra mapping will not be present. +
Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ +
hard-coded extra key/value +
- key: "acme.io/foo" +
valueExpression: "'bar'" +
This will result in an extra attribute - acme.io/foo: ["bar"] +
hard-coded key, value copying claim value +
- key: "acme.io/foo" +
valueExpression: "claims.some_claim" +
This will result in an extra attribute - acme.io/foo: [value of some_claim] +
hard-coded key, value derived from claim value +
- key: "acme.io/admin" +
valueExpression: '(has(claims.is_admin) && claims.is_admin) ? "true":""' +
This will result in: +
- if is_admin claim is present and true, extra attribute - acme.io/admin: ["true"] +
- if is_admin claim is present and false or is_admin claim is not present, no extra attribute will be added +
|===
@@ -178,6 +356,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

View File

@@ -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,255 @@ 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.
// 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.
// 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
// group membership from the JWT token. When not specified, it will default to "groups".
// username is the name of the claim which should be read to extract the
// username from the JWT token. When not specified, it will default to "username",
// unless usernameExpression is specified.
//
// Mutually exclusive with usernameExpression. Use either username or usernameExpression to
// determine the user's username from the JWT token.
// +optional
Username string `json:"username"`
// usernameExpression represents an expression which will be evaluated by CEL.
// The expression's result will become the user's username.
//
// usernameExpression is similar to claimMappings.username.expression from Kubernetes AuthenticationConfiguration
// as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication.
// This is an advanced configuration option. During an end-user login flow, each of these CEL expressions
// must evaluate to the expected type without errors, or else the user's login will fail.
// Additionally, mistakes in this configuration can cause the users to have unintended usernames.
//
// The expression must produce a non-empty string value.
// If the expression uses 'claims.email', then 'claims.email_verified' must be used in
// the expression or extra[*].valueExpression or claimValidationRules[*].expression.
// An example claim validation rule expression that matches the validation automatically
// applied when username.claim is set to 'email' is 'claims.?email_verified.orValue(true) == true'.
// By explicitly comparing the value to true, we let type-checking see the result will be a boolean,
// and to make sure a non-boolean email_verified claim will be caught at runtime.
//
// CEL expressions have access to the contents of the token claims, organized into CEL variable:
// - 'claims' is a map of claim names to claim values.
// For example, a variable named 'sub' can be accessed as 'claims.sub'.
// Nested claims can be accessed using dot notation, e.g. 'claims.foo.bar'.
//
// Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/
//
// Mutually exclusive with username. Use either username or usernameExpression to
// determine the user's username from the JWT token.
// +optional
UsernameExpression string `json:"usernameExpression,omitempty"`
// groups is the name of the claim which should be read to extract the user's
// group membership from the JWT token. When not specified, it will default to "groups",
// unless groupsExpression is specified.
//
// Mutually exclusive with groupsExpression. Use either groups or groupsExpression to
// determine the user's group membership from the JWT token.
// +optional
Groups string `json:"groups"`
// Username is the name of the claim which should be read to extract the
// username from the JWT token. When not specified, it will default to "username".
// groupsExpression represents an expression which will be evaluated by CEL.
// The expression's result will become the user's group memberships.
//
// groupsExpression is similar to claimMappings.groups.expression from Kubernetes AuthenticationConfiguration
// as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication.
// This is an advanced configuration option. During an end-user login flow, each of these CEL expressions
// must evaluate to one of the expected types without errors, or else the user's login will fail.
// Additionally, mistakes in this configuration can cause the users to have unintended group memberships.
//
// The expression must produce a string or string array value.
// "", [], and null values are treated as the group mapping not being present.
//
// CEL expressions have access to the contents of the token claims, organized into CEL variable:
// - 'claims' is a map of claim names to claim values.
// For example, a variable named 'sub' can be accessed as 'claims.sub'.
// Nested claims can be accessed using dot notation, e.g. 'claims.foo.bar'.
//
// Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/
//
// Mutually exclusive with groups. Use either groups or groupsExpression to
// determine the user's group membership from the JWT token.
// +optional
Username string `json:"username"`
GroupsExpression string `json:"groupsExpression,omitempty"`
// extra is similar to claimMappings.extra from Kubernetes AuthenticationConfiguration
// as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication.
//
// However, note that the Pinniped Concierge issues client certificates to users for the purpose
// of authenticating, and the Kubernetes API server does not have any mechanism for transmitting
// auth extras via client certificates. When configured, these extras will appear in client
// certificates issued by the Pinniped Supervisor in the x509 Subject field as Organizational
// Units (OU). However, when this client certificate is presented to Kubernetes for authentication,
// Kubernetes will ignore these extras. This is probably only useful if you are using a custom
// authenticating proxy in front of your Kubernetes API server which can translate these OUs into
// auth extras, as described by
// https://kubernetes.io/docs/reference/access-authn-authz/authentication/#authenticating-proxy.
// This is an advanced configuration option. During an end-user login flow, each of these CEL expressions
// must evaluate to either a string or an array of strings, or else the user's login will fail.
//
// These keys must be a domain-prefixed path (such as "acme.io/foo") and must not contain an equals sign ("=").
//
// expression must produce a string or string array value.
// If the value is empty, the extra mapping will not be present.
//
// Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/
//
// hard-coded extra key/value
// - key: "acme.io/foo"
// valueExpression: "'bar'"
// This will result in an extra attribute - acme.io/foo: ["bar"]
//
// hard-coded key, value copying claim value
// - key: "acme.io/foo"
// valueExpression: "claims.some_claim"
// This will result in an extra attribute - acme.io/foo: [value of some_claim]
//
// hard-coded key, value derived from claim value
// - key: "acme.io/admin"
// valueExpression: '(has(claims.is_admin) && claims.is_admin) ? "true":""'
// This will result in:
// - if is_admin claim is present and true, extra attribute - acme.io/admin: ["true"]
// - if is_admin claim is present and false or is_admin claim is not present, no extra attribute will be added
//
// +optional
Extra []ExtraMapping `json:"extra,omitempty"`
}
// 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.
// Additionally, the key must not contain an equals sign ("=").
// +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 +295,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"`

View File

@@ -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

View File

@@ -58,37 +58,219 @@ 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.
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.
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.
These keys must be a domain-prefixed path (such as "acme.io/foo") and must not contain an equals sign ("=").
expression must produce a string or string array value.
If the value is empty, the extra mapping will not be present.
Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/
hard-coded extra key/value
- key: "acme.io/foo"
valueExpression: "'bar'"
This will result in an extra attribute - acme.io/foo: ["bar"]
hard-coded key, value copying claim value
- key: "acme.io/foo"
valueExpression: "claims.some_claim"
This will result in an extra attribute - acme.io/foo: [value of some_claim]
hard-coded key, value derived from claim value
- key: "acme.io/admin"
valueExpression: '(has(claims.is_admin) && claims.is_admin) ? "true":""'
This will result in:
- if is_admin claim is present and true, extra attribute - acme.io/admin: ["true"]
- if is_admin claim is present and false or is_admin claim is not present, no extra attribute will be added
items:
description: ExtraMapping provides the configuration for a single
extra mapping.
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.
Additionally, the key must not contain an equals sign ("=").
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
group membership from the JWT token. When not specified, it will default to "groups".
groups is the name of the claim which should be read to extract the user's
group membership from the JWT token. When not specified, it will default to "groups",
unless groupsExpression is specified.
Mutually exclusive with groupsExpression. Use either groups or groupsExpression to
determine the user's group membership from the JWT token.
type: string
groupsExpression:
description: |-
groupsExpression represents an expression which will be evaluated by CEL.
The expression's result will become the user's group memberships.
groupsExpression is similar to claimMappings.groups.expression from Kubernetes AuthenticationConfiguration
as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication.
This is an advanced configuration option. During an end-user login flow, each of these CEL expressions
must evaluate to one of the expected types without errors, or else the user's login will fail.
Additionally, mistakes in this configuration can cause the users to have unintended group memberships.
The expression must produce a string or string array value.
"", [], and null values are treated as the group mapping not being present.
CEL expressions have access to the contents of the token claims, organized into CEL variable:
- 'claims' is a map of claim names to claim values.
For example, a variable named 'sub' can be accessed as 'claims.sub'.
Nested claims can be accessed using dot notation, e.g. 'claims.foo.bar'.
Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/
Mutually exclusive with groups. Use either groups or groupsExpression to
determine the user's group membership from the JWT token.
type: string
username:
description: |-
Username is the name of the claim which should be read to extract the
username from the JWT token. When not specified, it will default to "username".
username is the name of the claim which should be read to extract the
username from the JWT token. When not specified, it will default to "username",
unless usernameExpression is specified.
Mutually exclusive with usernameExpression. Use either username or usernameExpression to
determine the user's username from the JWT token.
type: string
usernameExpression:
description: |-
usernameExpression represents an expression which will be evaluated by CEL.
The expression's result will become the user's username.
usernameExpression is similar to claimMappings.username.expression from Kubernetes AuthenticationConfiguration
as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication.
This is an advanced configuration option. During an end-user login flow, each of these CEL expressions
must evaluate to the expected type without errors, or else the user's login will fail.
Additionally, mistakes in this configuration can cause the users to have unintended usernames.
The expression must produce a non-empty string value.
If the expression uses 'claims.email', then 'claims.email_verified' must be used in
the expression or extra[*].valueExpression or claimValidationRules[*].expression.
An example claim validation rule expression that matches the validation automatically
applied when username.claim is set to 'email' is 'claims.?email_verified.orValue(true) == true'.
By explicitly comparing the value to true, we let type-checking see the result will be a boolean,
and to make sure a non-boolean email_verified claim will be caught at runtime.
CEL expressions have access to the contents of the token claims, organized into CEL variable:
- 'claims' is a map of claim names to claim values.
For example, a variable named 'sub' can be accessed as 'claims.sub'.
Nested claims can be accessed using dot notation, e.g. 'claims.foo.bar'.
Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/
Mutually exclusive with username. Use either username or usernameExpression to
determine the user's username from the JWT token.
type: string
type: object
issuer:
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 +310,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

View File

@@ -60,6 +60,78 @@ 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. +
Only string claim keys are supported. +
Mutually exclusive with expression and message. +
| *`requiredValue`* __string__ | requiredValue is the value of a required claim. +
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. +
Additionally, the key must not contain an equals sign ("="). +
| *`valueExpression`* __string__ | valueExpression is a CEL expression to extract extra attribute value. +
valueExpression must produce a string or string array value. +
"", [], and null values are treated as the extra mapping not being present. +
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 +150,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 +172,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 +182,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 +236,103 @@ for Kubernetes access.
[cols="25a,75a", options="header"]
|===
| Field | Description
| *`groups`* __string__ | Groups is the name of the claim which should be read to extract the user's +
group membership from the JWT token. When not specified, it will default to "groups". +
| *`username`* __string__ | Username is the name of the claim which should be read to extract the +
username from the JWT token. When not specified, it will default to "username". +
| *`username`* __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", +
unless usernameExpression is specified. +
Mutually exclusive with usernameExpression. Use either username or usernameExpression to +
determine the user's username from the JWT token. +
| *`usernameExpression`* __string__ | usernameExpression represents an expression which will be evaluated by CEL. +
The expression's result will become the user's username. +
usernameExpression is similar to claimMappings.username.expression from Kubernetes AuthenticationConfiguration +
as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication. +
This is an advanced configuration option. During an end-user login flow, each of these CEL expressions +
must evaluate to the expected type without errors, or else the user's login will fail. +
Additionally, mistakes in this configuration can cause the users to have unintended usernames. +
The expression must produce a non-empty string value. +
If the expression uses 'claims.email', then 'claims.email_verified' must be used in +
the expression or extra[*].valueExpression or claimValidationRules[*].expression. +
An example claim validation rule expression that matches the validation automatically +
applied when username.claim is set to 'email' is 'claims.?email_verified.orValue(true) == true'. +
By explicitly comparing the value to true, we let type-checking see the result will be a boolean, +
and to make sure a non-boolean email_verified claim will be caught at runtime. +
CEL expressions have access to the contents of the token claims, organized into CEL variable: +
- 'claims' is a map of claim names to claim values. +
For example, a variable named 'sub' can be accessed as 'claims.sub'. +
Nested claims can be accessed using dot notation, e.g. 'claims.foo.bar'. +
Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ +
Mutually exclusive with username. Use either username or usernameExpression to +
determine the user's username from the JWT token. +
| *`groups`* __string__ | groups is the name of the claim which should be read to extract the user's +
group membership from the JWT token. When not specified, it will default to "groups", +
unless groupsExpression is specified. +
Mutually exclusive with groupsExpression. Use either groups or groupsExpression to +
determine the user's group membership from the JWT token. +
| *`groupsExpression`* __string__ | groupsExpression represents an expression which will be evaluated by CEL. +
The expression's result will become the user's group memberships. +
groupsExpression is similar to claimMappings.groups.expression from Kubernetes AuthenticationConfiguration +
as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication. +
This is an advanced configuration option. During an end-user login flow, each of these CEL expressions +
must evaluate to one of the expected types without errors, or else the user's login will fail. +
Additionally, mistakes in this configuration can cause the users to have unintended group memberships. +
The expression must produce a string or string array value. +
"", [], and null values are treated as the group mapping not being present. +
CEL expressions have access to the contents of the token claims, organized into CEL variable: +
- 'claims' is a map of claim names to claim values. +
For example, a variable named 'sub' can be accessed as 'claims.sub'. +
Nested claims can be accessed using dot notation, e.g. 'claims.foo.bar'. +
Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ +
Mutually exclusive with groups. Use either groups or groupsExpression to +
determine the user's group membership from the JWT token. +
| *`extra`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-33-apis-concierge-authentication-v1alpha1-extramapping[$$ExtraMapping$$] array__ | extra is similar to claimMappings.extra from Kubernetes AuthenticationConfiguration +
as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication. +
However, note that the Pinniped Concierge issues client certificates to users for the purpose +
of authenticating, and the Kubernetes API server does not have any mechanism for transmitting +
auth extras via client certificates. When configured, these extras will appear in client +
certificates issued by the Pinniped Supervisor in the x509 Subject field as Organizational +
Units (OU). However, when this client certificate is presented to Kubernetes for authentication, +
Kubernetes will ignore these extras. This is probably only useful if you are using a custom +
authenticating proxy in front of your Kubernetes API server which can translate these OUs into +
auth extras, as described by +
https://kubernetes.io/docs/reference/access-authn-authz/authentication/#authenticating-proxy. +
This is an advanced configuration option. During an end-user login flow, each of these CEL expressions +
must evaluate to either a string or an array of strings, or else the user's login will fail. +
These keys must be a domain-prefixed path (such as "acme.io/foo") and must not contain an equals sign ("="). +
expression must produce a string or string array value. +
If the value is empty, the extra mapping will not be present. +
Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/ +
hard-coded extra key/value +
- key: "acme.io/foo" +
valueExpression: "'bar'" +
This will result in an extra attribute - acme.io/foo: ["bar"] +
hard-coded key, value copying claim value +
- key: "acme.io/foo" +
valueExpression: "claims.some_claim" +
This will result in an extra attribute - acme.io/foo: [value of some_claim] +
hard-coded key, value derived from claim value +
- key: "acme.io/admin" +
valueExpression: '(has(claims.is_admin) && claims.is_admin) ? "true":""' +
This will result in: +
- if is_admin claim is present and true, extra attribute - acme.io/admin: ["true"] +
- if is_admin claim is present and false or is_admin claim is not present, no extra attribute will be added +
|===
@@ -178,6 +356,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

View File

@@ -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,255 @@ 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.
// 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.
// 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
// group membership from the JWT token. When not specified, it will default to "groups".
// username is the name of the claim which should be read to extract the
// username from the JWT token. When not specified, it will default to "username",
// unless usernameExpression is specified.
//
// Mutually exclusive with usernameExpression. Use either username or usernameExpression to
// determine the user's username from the JWT token.
// +optional
Username string `json:"username"`
// usernameExpression represents an expression which will be evaluated by CEL.
// The expression's result will become the user's username.
//
// usernameExpression is similar to claimMappings.username.expression from Kubernetes AuthenticationConfiguration
// as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication.
// This is an advanced configuration option. During an end-user login flow, each of these CEL expressions
// must evaluate to the expected type without errors, or else the user's login will fail.
// Additionally, mistakes in this configuration can cause the users to have unintended usernames.
//
// The expression must produce a non-empty string value.
// If the expression uses 'claims.email', then 'claims.email_verified' must be used in
// the expression or extra[*].valueExpression or claimValidationRules[*].expression.
// An example claim validation rule expression that matches the validation automatically
// applied when username.claim is set to 'email' is 'claims.?email_verified.orValue(true) == true'.
// By explicitly comparing the value to true, we let type-checking see the result will be a boolean,
// and to make sure a non-boolean email_verified claim will be caught at runtime.
//
// CEL expressions have access to the contents of the token claims, organized into CEL variable:
// - 'claims' is a map of claim names to claim values.
// For example, a variable named 'sub' can be accessed as 'claims.sub'.
// Nested claims can be accessed using dot notation, e.g. 'claims.foo.bar'.
//
// Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/
//
// Mutually exclusive with username. Use either username or usernameExpression to
// determine the user's username from the JWT token.
// +optional
UsernameExpression string `json:"usernameExpression,omitempty"`
// groups is the name of the claim which should be read to extract the user's
// group membership from the JWT token. When not specified, it will default to "groups",
// unless groupsExpression is specified.
//
// Mutually exclusive with groupsExpression. Use either groups or groupsExpression to
// determine the user's group membership from the JWT token.
// +optional
Groups string `json:"groups"`
// Username is the name of the claim which should be read to extract the
// username from the JWT token. When not specified, it will default to "username".
// groupsExpression represents an expression which will be evaluated by CEL.
// The expression's result will become the user's group memberships.
//
// groupsExpression is similar to claimMappings.groups.expression from Kubernetes AuthenticationConfiguration
// as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication.
// This is an advanced configuration option. During an end-user login flow, each of these CEL expressions
// must evaluate to one of the expected types without errors, or else the user's login will fail.
// Additionally, mistakes in this configuration can cause the users to have unintended group memberships.
//
// The expression must produce a string or string array value.
// "", [], and null values are treated as the group mapping not being present.
//
// CEL expressions have access to the contents of the token claims, organized into CEL variable:
// - 'claims' is a map of claim names to claim values.
// For example, a variable named 'sub' can be accessed as 'claims.sub'.
// Nested claims can be accessed using dot notation, e.g. 'claims.foo.bar'.
//
// Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/
//
// Mutually exclusive with groups. Use either groups or groupsExpression to
// determine the user's group membership from the JWT token.
// +optional
Username string `json:"username"`
GroupsExpression string `json:"groupsExpression,omitempty"`
// extra is similar to claimMappings.extra from Kubernetes AuthenticationConfiguration
// as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication.
//
// However, note that the Pinniped Concierge issues client certificates to users for the purpose
// of authenticating, and the Kubernetes API server does not have any mechanism for transmitting
// auth extras via client certificates. When configured, these extras will appear in client
// certificates issued by the Pinniped Supervisor in the x509 Subject field as Organizational
// Units (OU). However, when this client certificate is presented to Kubernetes for authentication,
// Kubernetes will ignore these extras. This is probably only useful if you are using a custom
// authenticating proxy in front of your Kubernetes API server which can translate these OUs into
// auth extras, as described by
// https://kubernetes.io/docs/reference/access-authn-authz/authentication/#authenticating-proxy.
// This is an advanced configuration option. During an end-user login flow, each of these CEL expressions
// must evaluate to either a string or an array of strings, or else the user's login will fail.
//
// These keys must be a domain-prefixed path (such as "acme.io/foo") and must not contain an equals sign ("=").
//
// expression must produce a string or string array value.
// If the value is empty, the extra mapping will not be present.
//
// Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/
//
// hard-coded extra key/value
// - key: "acme.io/foo"
// valueExpression: "'bar'"
// This will result in an extra attribute - acme.io/foo: ["bar"]
//
// hard-coded key, value copying claim value
// - key: "acme.io/foo"
// valueExpression: "claims.some_claim"
// This will result in an extra attribute - acme.io/foo: [value of some_claim]
//
// hard-coded key, value derived from claim value
// - key: "acme.io/admin"
// valueExpression: '(has(claims.is_admin) && claims.is_admin) ? "true":""'
// This will result in:
// - if is_admin claim is present and true, extra attribute - acme.io/admin: ["true"]
// - if is_admin claim is present and false or is_admin claim is not present, no extra attribute will be added
//
// +optional
Extra []ExtraMapping `json:"extra,omitempty"`
}
// 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.
// Additionally, the key must not contain an equals sign ("=").
// +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 +295,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"`

View File

@@ -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

View File

@@ -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

View File

@@ -5,7 +5,7 @@
set -euo pipefail
ROOT="$( cd "$( dirname "${BASH_SOURCE[0]}" )/.." && pwd )"
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "${ROOT}"
# Print the Go version.
@@ -15,6 +15,17 @@ lint_version="v$(cat hack/lib/lint-version.txt)"
# Find the toolchain version from our go.mod file. "go install" pays attention to $GOTOOLCHAIN.
GOTOOLCHAIN=$(sed -rn 's/^toolchain (go[0-9\.]+)$/\1/p' go.mod)
if [[ -z "$GOTOOLCHAIN" ]]; then
# Did not find toolchain directive. The directive is not needed in a go.mod file when it would be the same
# version as the go directive, so it will not always be there. Try using go directive instead.
GOTOOLCHAIN=$(sed -rn 's/^go ([0-9]+\.[0-9]+\.[0-9]+)$/\1/p' go.mod)
if [[ -z "$GOTOOLCHAIN" ]]; then
echo "ERROR: Could not find Go patch version from go.mod file."
exit 1
fi
GOTOOLCHAIN="go${GOTOOLCHAIN}"
fi
export GOTOOLCHAIN
echo "Installing golangci-lint@${lint_version} using toolchain ${GOTOOLCHAIN}"
@@ -22,6 +33,9 @@ echo "Installing golangci-lint@${lint_version} using toolchain ${GOTOOLCHAIN}"
# Install the same version of the linter that the pipelines will use
# so you can get the same results when running the linter locally.
go install -v "github.com/golangci/golangci-lint/v2/cmd/golangci-lint@${lint_version}"
echo "Finished installing golangci-lint@${lint_version} using toolchain ${GOTOOLCHAIN}"
golangci-lint --version
echo "Finished. You may need to run 'rehash' in your current shell before using the new version (e.g. if you are using gvm)."

View File

@@ -1,6 +1,6 @@
#!/usr/bin/env bash
# Copyright 2020-2024 the Pinniped contributors. All Rights Reserved.
# Copyright 2020-2025 the Pinniped contributors. All Rights Reserved.
# SPDX-License-Identifier: Apache-2.0
#
@@ -535,6 +535,9 @@ export PINNIPED_TEST_CLI_OIDC_CLIENT_ID=pinniped-cli
export PINNIPED_TEST_CLI_OIDC_CALLBACK_URL=http://127.0.0.1:48095/callback
export PINNIPED_TEST_CLI_OIDC_USERNAME=pinny@example.com
export PINNIPED_TEST_CLI_OIDC_PASSWORD=${dex_test_password}
export PINNIPED_TEST_CLI_OIDC_USERNAME_CLAIM=email
export PINNIPED_TEST_CLI_OIDC_GROUPS_CLAIM=groups
export PINNIPED_TEST_CLI_OIDC_EXPECTED_GROUPS= # Dex's local user store does not let us configure groups.
export PINNIPED_TEST_SUPERVISOR_UPSTREAM_OIDC_ISSUER=https://dex.tools.svc.cluster.local/dex
export PINNIPED_TEST_SUPERVISOR_UPSTREAM_OIDC_ISSUER_CA_BUNDLE="${test_ca_bundle_pem}"
export PINNIPED_TEST_SUPERVISOR_UPSTREAM_OIDC_ADDITIONAL_SCOPES="offline_access,email"

View File

@@ -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

View File

@@ -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()
}

View File

@@ -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)
}

View File

@@ -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,
)
}

View File

@@ -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
}

View File

@@ -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)

View File

@@ -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,

View File

@@ -0,0 +1,127 @@
// 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 {
return apiserver.JWTAuthenticator{
Issuer: convertIssuerType(spec),
ClaimMappings: convertClaimMappingsType(spec.Claims),
ClaimValidationRules: convertClaimValidationRulesType(spec.ClaimValidationRules),
UserValidationRules: convertUserValidationRulesType(spec.UserValidationRules),
}
}
func convertIssuerType(spec *authenticationv1alpha1.JWTAuthenticatorSpec) apiserver.Issuer {
var aud []string
if len(spec.Audience) > 0 {
aud = []string{spec.Audience}
}
return apiserver.Issuer{
URL: spec.Issuer,
Audiences: aud,
}
}
func convertClaimMappingsType(claims authenticationv1alpha1.JWTTokenClaims) apiserver.ClaimMappings {
usernameClaim := claims.Username
if usernameClaim == "" && claims.UsernameExpression == "" {
usernameClaim = defaultUsernameClaim
}
var usernamePrefix *string
if usernameClaim != "" {
// Must be set only when username claim name is set.
usernamePrefix = ptr.To("")
}
groupsClaim := claims.Groups
if groupsClaim == "" && claims.GroupsExpression == "" {
groupsClaim = defaultGroupsClaim
}
var groupsPrefix *string
if groupsClaim != "" {
// Must be set only when groups claim name is set.
groupsPrefix = ptr.To("")
}
return apiserver.ClaimMappings{
Username: apiserver.PrefixedClaimOrExpression{
Claim: usernameClaim,
Prefix: usernamePrefix,
Expression: claims.UsernameExpression,
},
Groups: apiserver.PrefixedClaimOrExpression{
Claim: groupsClaim,
Prefix: groupsPrefix,
Expression: claims.GroupsExpression,
},
Extra: convertExtraType(claims.Extra),
}
}
func convertUserValidationRulesType(rules []authenticationv1alpha1.UserValidationRule) []apiserver.UserValidationRule {
if len(rules) == 0 {
return nil
}
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
}

View File

@@ -0,0 +1,176 @@
// 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 when the usernameExpression and groupExpression are not set",
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: "does not default the username and groups claims an prefixes when the usernameExpression and groupExpression are set",
spec: &authenticationv1alpha1.JWTAuthenticatorSpec{
Issuer: "https://example.com",
Claims: authenticationv1alpha1.JWTTokenClaims{
UsernameExpression: `"foo"`,
GroupsExpression: `["foo"]`,
},
},
want: apiserver.JWTAuthenticator{
Issuer: apiserver.Issuer{
URL: "https://example.com",
},
ClaimMappings: apiserver.ClaimMappings{
Username: apiserver.PrefixedClaimOrExpression{
Claim: "",
Prefix: nil,
Expression: `"foo"`,
},
Groups: apiserver.PrefixedClaimOrExpression{
Claim: "",
Prefix: nil,
Expression: `["foo"]`,
},
},
},
},
{
name: "converts every field except for TLS",
spec: &authenticationv1alpha1.JWTAuthenticatorSpec{
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))
})
}
}

View File

@@ -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,51 @@ 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).
Child("key")
errList = append(errList, field.Invalid(fieldPath, mapping.Key,
"Pinniped does not allow extra key names to contain equals sign",
))
}
}
groupsClaim := spec.Claims.Groups
if groupsClaim == "" {
groupsClaim = defaultGroupsClaim
if errList != nil {
rewriteJWTAuthenticatorErrors(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 +730,54 @@ 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 strings created by ValidateAuthenticationConfiguration(), but we
// can rewrite them to make them more consistent with our JWTAuthenticator CRD field names where they don't agree.
func rewriteJWTAuthenticatorErrors(errList field.ErrorList) {
// ValidateAuthenticationConfiguration() will prefix all our errors with "jwt[0]." because we always pass it
// exactly one jwtAuthenticator to validate.
undesirablePrefix := fmt.Sprintf("%s.", field.NewPath("jwt").Index(0).String())
// Replace these to make the spec names consistent with how they are named in the JWTAuthenticator CRD.
replacements := []struct {
str string
repl string
}{
// replace these more specific strings first
{str: "claimMappings.username.expression", repl: "claims.usernameExpression"},
{str: "claimMappings.groups.expression", repl: "claims.groupsExpression"},
// then replace these less specific strings (substrings of the strings above) if they still exist
{str: "claimMappings.username", repl: "claims.username"},
{str: "claimMappings.groups", repl: "claims.groups"},
// and also replace this one
{str: "claimMappings.extra", repl: "claims.extra"},
}
for _, err := range errList {
err.Field = strings.TrimPrefix(err.Field, undesirablePrefix)
for _, spec := range replacements {
err.Field = strings.ReplaceAll(err.Field, spec.str, spec.repl)
err.Detail = strings.ReplaceAll(err.Detail, spec.str, spec.repl)
}
}
}
func (c *jwtCacheFillerController) updateStatus(
ctx context.Context,
original *authenticationv1alpha1.JWTAuthenticator,

View File

@@ -360,6 +360,111 @@ func TestController(t *testing.T) {
Groups: customGroupsClaim,
},
}
someJWTAuthenticatorSpecWithManyOptionalValues := &authenticationv1alpha1.JWTAuthenticatorSpec{
Issuer: goodIssuer,
Audience: goodAudience,
TLS: goodOIDCIssuerServerTLSSpec,
Claims: authenticationv1alpha1.JWTTokenClaims{
Username: "my-custom-username-claim", // note: can't specify this and usernameExpression at the same time
Groups: customGroupsClaim, // note: can't specify this and groupsExpression at the same time
Extra: []authenticationv1alpha1.ExtraMapping{
{
Key: "example.com/key-name", // must be a domain and path
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",
},
},
}
someJWTAuthenticatorSpecWithUsernameAndGroupExpressions := &authenticationv1alpha1.JWTAuthenticatorSpec{
Issuer: goodIssuer,
Audience: goodAudience,
TLS: goodOIDCIssuerServerTLSSpec,
Claims: authenticationv1alpha1.JWTTokenClaims{
UsernameExpression: "claims.otherUsernameClaim",
GroupsExpression: "has(claims.otherGroupsClaim) ? claims.otherGroupsClaim : []", // handles the case where the claim does not exist in the JWT
},
}
invalidClaimsExtraJWTAuthenticatorSpec := &authenticationv1alpha1.JWTAuthenticatorSpec{
Issuer: goodIssuer,
Audience: goodAudience,
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",
},
},
}
invalidClaimsMutualExclusiveRulesBothSetJWTAuthenticatorSpec := &authenticationv1alpha1.JWTAuthenticatorSpec{
Issuer: goodIssuer,
Audience: goodAudience,
TLS: goodOIDCIssuerServerTLSSpec,
Claims: authenticationv1alpha1.JWTTokenClaims{
Username: "user",
UsernameExpression: `"user"`,
Groups: "groups",
GroupsExpression: `["group1"]`,
},
}
invalidClaimsUsernameExpressUsesClaimsDotEmailWrongJWTAuthenticatorSpec := &authenticationv1alpha1.JWTAuthenticatorSpec{
Issuer: goodIssuer,
Audience: goodAudience,
TLS: goodOIDCIssuerServerTLSSpec,
Claims: authenticationv1alpha1.JWTTokenClaims{
UsernameExpression: "claims.email",
},
}
invalidClaimsExtraContainsEqualSignJWTAuthenticatorSpec := &authenticationv1alpha1.JWTAuthenticatorSpec{
Issuer: goodIssuer,
Audience: goodAudience,
TLS: goodOIDCIssuerServerTLSSpec,
Claims: authenticationv1alpha1.JWTTokenClaims{
Extra: []authenticationv1alpha1.ExtraMapping{
{
Key: "example.com/legal-key",
ValueExpression: `"extra-value"`,
},
{
Key: "example.com/key=contains-equals-sign", // this should cause a validation error in our own code
ValueExpression: `"extra-value"`,
},
},
},
}
otherJWTAuthenticatorSpec := &authenticationv1alpha1.JWTAuthenticatorSpec{
Issuer: someOtherIssuer,
Audience: goodAudience,
@@ -549,6 +654,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 +832,7 @@ func TestController(t *testing.T) {
wantActions func() []coretesting.Action
wantUsernameClaim string
wantGroupsClaim string
wantExtras map[string][]string
wantNamesOfJWTAuthenticatorsInCache []string
skipTestingCachedAuthenticator bool
}{
@@ -1108,6 +1224,79 @@ func TestController(t *testing.T) {
wantGroupsClaim: someJWTAuthenticatorSpecWithGroupsClaim.Claims.Groups,
wantNamesOfJWTAuthenticatorsInCache: []string{"test-name"},
},
{
name: "Sync: JWTAuthenticator with many optional values: loop will complete successfully and update status conditions",
jwtAuthenticators: []runtime.Object{
&authenticationv1alpha1.JWTAuthenticator{
ObjectMeta: metav1.ObjectMeta{
Name: "test-name",
},
Spec: *someJWTAuthenticatorSpecWithManyOptionalValues,
},
},
wantLogLines: []string{
fmt.Sprintf(`{"level":"debug","timestamp":"2099-08-08T13:57:36.123456Z","logger":"jwtcachefiller-controller","caller":"jwtcachefiller/jwtcachefiller.go:<line>$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:<line>$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: *someJWTAuthenticatorSpecWithManyOptionalValues,
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 usernameExpression and groupsExpression values: loop will complete successfully and update status conditions",
jwtAuthenticators: []runtime.Object{
&authenticationv1alpha1.JWTAuthenticator{
ObjectMeta: metav1.ObjectMeta{
Name: "test-name",
},
Spec: *someJWTAuthenticatorSpecWithUsernameAndGroupExpressions,
},
},
wantLogLines: []string{
fmt.Sprintf(`{"level":"debug","timestamp":"2099-08-08T13:57:36.123456Z","logger":"jwtcachefiller-controller","caller":"jwtcachefiller/jwtcachefiller.go:<line>$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:<line>$jwtcachefiller.(*jwtCacheFillerController).syncIndividualJWTAuthenticator","message":"added or updated jwt authenticator in cache","jwtAuthenticator":"test-name","issuer":"%s","isOverwrite":false}`, goodIssuer),
},
wantActions: func() []coretesting.Action {
updateStatusAction := coretesting.NewUpdateAction(jwtAuthenticatorsGVR, "", &authenticationv1alpha1.JWTAuthenticator{
ObjectMeta: metav1.ObjectMeta{
Name: "test-name",
},
Spec: *someJWTAuthenticatorSpecWithUsernameAndGroupExpressions,
Status: authenticationv1alpha1.JWTAuthenticatorStatus{
Conditions: allHappyConditionsSuccess(goodIssuer, frozenMetav1Now, 0),
Phase: "Ready",
},
})
updateStatusAction.Subresource = "status"
return []coretesting.Action{
coretesting.NewListAction(jwtAuthenticatorsGVR, jwtAUthenticatorGVK, "", metav1.ListOptions{}),
coretesting.NewWatchAction(jwtAuthenticatorsGVR, "", metav1.ListOptions{Watch: true}),
updateStatusAction,
}
},
wantUsernameClaim: "otherUsernameClaim",
wantGroupsClaim: "otherGroupsClaim",
wantNamesOfJWTAuthenticatorsInCache: []string{"test-name"},
},
{
name: "Sync: JWTAuthenticator with new spec.tls fields: loop will close previous instance of JWTAuthenticator and complete successfully and update status conditions",
cache: func(t *testing.T, cache *authncache.Cache, wantClose bool) {
@@ -1302,6 +1491,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:<line>$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:<line>$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:<line>$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:<line>$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 +2577,292 @@ 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:<line>$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:<line>$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:<line>$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:<line>$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: <input>:1:6: Syntax error: mismatched input 'is' expecting <EOF>\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:<line>$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:<line>$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: <input>:1:6: Syntax error: mismatched input 'is' expecting <EOF>\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: when username and usernameExpression and/or groups and groupsExpression are both set: loop will fail sync, will write failed and unknown status conditions, but will not enqueue a resync due to user config error",
jwtAuthenticators: []runtime.Object{
&authenticationv1alpha1.JWTAuthenticator{
ObjectMeta: metav1.ObjectMeta{
Name: "test-name",
},
Spec: *invalidClaimsMutualExclusiveRulesBothSetJWTAuthenticatorSpec,
},
},
wantLogLines: []string{
fmt.Sprintf(`{"level":"info","timestamp":"2099-08-08T13:57:36.123456Z","logger":"jwtcachefiller-controller","caller":"jwtcachefiller/jwtcachefiller.go:<line>$jwtcachefiller.(*jwtCacheFillerController).syncIndividualJWTAuthenticator","message":"invalid jwt authenticator","jwtAuthenticator":"test-name","issuer":"%s","removedFromCache":false}`, invalidClaimsMutualExclusiveRulesBothSetJWTAuthenticatorSpec.Issuer),
fmt.Sprintf(`{"level":"debug","timestamp":"2099-08-08T13:57:36.123456Z","logger":"jwtcachefiller-controller","caller":"jwtcachefiller/jwtcachefiller.go:<line>$jwtcachefiller.(*jwtCacheFillerController).updateStatus","message":"jwtauthenticator status successfully updated","jwtAuthenticator":"test-name","issuer":"%s","phase":"Error"}`, invalidClaimsMutualExclusiveRulesBothSetJWTAuthenticatorSpec.Issuer),
},
wantActions: func() []coretesting.Action {
updateStatusAction := coretesting.NewUpdateAction(jwtAuthenticatorsGVR, "", &authenticationv1alpha1.JWTAuthenticator{
ObjectMeta: metav1.ObjectMeta{
Name: "test-name",
},
Spec: *invalidClaimsMutualExclusiveRulesBothSetJWTAuthenticatorSpec,
Status: authenticationv1alpha1.JWTAuthenticatorStatus{
Conditions: conditionstestutil.Replace(
allHappyConditionsSuccess(goodIssuer, frozenMetav1Now, 0),
[]metav1.Condition{
sadReadyCondition(frozenMetav1Now, 0),
happyIssuerURLValid(frozenMetav1Now, 0),
happyDiscoveryURLValid(frozenMetav1Now, 0),
sadAuthenticatorValid(
`could not initialize jwt authenticator: [claims.username: Invalid value: "": claim and expression can't both be set, claims.groups: Invalid value: "": claim and expression can't both be set]`,
frozenMetav1Now,
0,
),
happyJWKSURLValid(frozenMetav1Now, 0),
happyJWKSFetch(frozenMetav1Now, 0),
},
),
Phase: "Error",
},
})
updateStatusAction.Subresource = "status"
return []coretesting.Action{
coretesting.NewListAction(jwtAuthenticatorsGVR, jwtAUthenticatorGVK, "", metav1.ListOptions{}),
coretesting.NewWatchAction(jwtAuthenticatorsGVR, "", metav1.ListOptions{Watch: true}),
updateStatusAction,
}
},
},
{
name: "newCachedJWTAuthenticator: validateAuthenticationConfiguration: when usernameExpression uses claims.email without using claims.email_verified elsewhere: loop will fail sync, will write failed and unknown status conditions, but will not enqueue a resync due to user config error",
jwtAuthenticators: []runtime.Object{
&authenticationv1alpha1.JWTAuthenticator{
ObjectMeta: metav1.ObjectMeta{
Name: "test-name",
},
Spec: *invalidClaimsUsernameExpressUsesClaimsDotEmailWrongJWTAuthenticatorSpec,
},
},
wantLogLines: []string{
fmt.Sprintf(`{"level":"info","timestamp":"2099-08-08T13:57:36.123456Z","logger":"jwtcachefiller-controller","caller":"jwtcachefiller/jwtcachefiller.go:<line>$jwtcachefiller.(*jwtCacheFillerController).syncIndividualJWTAuthenticator","message":"invalid jwt authenticator","jwtAuthenticator":"test-name","issuer":"%s","removedFromCache":false}`, invalidClaimsUsernameExpressUsesClaimsDotEmailWrongJWTAuthenticatorSpec.Issuer),
fmt.Sprintf(`{"level":"debug","timestamp":"2099-08-08T13:57:36.123456Z","logger":"jwtcachefiller-controller","caller":"jwtcachefiller/jwtcachefiller.go:<line>$jwtcachefiller.(*jwtCacheFillerController).updateStatus","message":"jwtauthenticator status successfully updated","jwtAuthenticator":"test-name","issuer":"%s","phase":"Error"}`, invalidClaimsUsernameExpressUsesClaimsDotEmailWrongJWTAuthenticatorSpec.Issuer),
},
wantActions: func() []coretesting.Action {
updateStatusAction := coretesting.NewUpdateAction(jwtAuthenticatorsGVR, "", &authenticationv1alpha1.JWTAuthenticator{
ObjectMeta: metav1.ObjectMeta{
Name: "test-name",
},
Spec: *invalidClaimsUsernameExpressUsesClaimsDotEmailWrongJWTAuthenticatorSpec,
Status: authenticationv1alpha1.JWTAuthenticatorStatus{
Conditions: conditionstestutil.Replace(
allHappyConditionsSuccess(goodIssuer, frozenMetav1Now, 0),
[]metav1.Condition{
sadReadyCondition(frozenMetav1Now, 0),
happyIssuerURLValid(frozenMetav1Now, 0),
happyDiscoveryURLValid(frozenMetav1Now, 0),
sadAuthenticatorValid(
`could not initialize jwt authenticator: claims.usernameExpression: Invalid value: "claims.email": `+
`claims.email_verified must be used in claims.usernameExpression or claims.extra[*].valueExpression or `+
`claimValidationRules[*].expression when claims.email is used in claims.usernameExpression`,
frozenMetav1Now,
0,
),
happyJWKSURLValid(frozenMetav1Now, 0),
happyJWKSFetch(frozenMetav1Now, 0),
},
),
Phase: "Error",
},
})
updateStatusAction.Subresource = "status"
return []coretesting.Action{
coretesting.NewListAction(jwtAuthenticatorsGVR, jwtAUthenticatorGVK, "", metav1.ListOptions{}),
coretesting.NewWatchAction(jwtAuthenticatorsGVR, "", metav1.ListOptions{Watch: true}),
updateStatusAction,
}
},
},
{
name: "newCachedJWTAuthenticator: when any claims.extra[].key contains an equals sign: loop will fail sync, will write failed and unknown status conditions, but will not enqueue a resync due to user config error",
jwtAuthenticators: []runtime.Object{
&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:<line>$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:<line>$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[1].key: Invalid value: "example.com/key=contains-equals-sign": 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{
@@ -2484,6 +3067,15 @@ func TestController(t *testing.T) {
cachedAuthenticator, ok := temp.(tokenAuthenticatorCloser)
require.True(t, ok)
usernameClaimIsCelExpression := false
if temp.(*cachedJWTAuthenticator).claims.UsernameExpression != "" {
usernameClaimIsCelExpression = true
}
groupsClaimIsCelExpression := false
if temp.(*cachedJWTAuthenticator).claims.GroupsExpression != "" {
groupsClaimIsCelExpression = true
}
// Schedule it to be closed at the end of the test.
t.Cleanup(cachedAuthenticator.Close)
@@ -2511,12 +3103,19 @@ func TestController(t *testing.T) {
group1,
goodUsername,
tt.wantUsernameClaim,
usernameClaimIsCelExpression,
tt.wantGroupsClaim,
groupsClaimIsCelExpression,
tt.wantExtras,
goodIssuer,
) {
t.Run(test.name, func(t *testing.T) {
t.Parallel()
if test.skip != nil {
test.skip(t) // give the test a chance to skip itself if it wants to
}
wellKnownClaims := josejwt.Claims{
Issuer: goodIssuer,
Subject: goodSubject,
@@ -2592,7 +3191,10 @@ func testTableForAuthenticateTokenTests(
group1 string,
goodUsername string,
expectedUsernameClaim string,
usernameClaimIsCelExpression bool,
expectedGroupsClaim string,
groupsClaimIsCelExpression bool,
expectedExtras map[string][]string,
issuer string,
) []struct {
name string
@@ -2602,7 +3204,22 @@ func testTableForAuthenticateTokenTests(
wantAuthenticated bool
wantErr testutil.RequireErrorStringFunc
distributedGroupsClaimURL string
skip func(t *testing.T)
} {
expectedErrForBadTokenWithGroupsAsMap := func() testutil.RequireErrorStringFunc {
if groupsClaimIsCelExpression {
return testutil.WantExactErrorString(`oidc: error evaluating group claim expression: expression must return a string or a list of strings`)
}
return testutil.WantExactErrorString(`oidc: parse groups claim "` + expectedGroupsClaim + `": json: cannot unmarshal object into Go value of type string`)
}
expectedErrForTokenDoesNotHaveUsernameClaim := func() testutil.RequireErrorStringFunc {
if usernameClaimIsCelExpression {
return testutil.WantMatchingErrorString(`oidc: error evaluating username claim expression: expression '.+' resulted in error: no such key: ` + expectedUsernameClaim)
}
return testutil.WantExactErrorString(`oidc: parse username claims "` + expectedUsernameClaim + `": claim not present`)
}
tests := []struct {
name string
jwtClaims func(wellKnownClaims *josejwt.Claims, groups *any, username *string)
@@ -2611,12 +3228,14 @@ func testTableForAuthenticateTokenTests(
wantAuthenticated bool
wantErr testutil.RequireErrorStringFunc
distributedGroupsClaimURL string
skip func(t *testing.T)
}{
{
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 +3249,8 @@ func testTableForAuthenticateTokenTests(
},
wantResponse: &authenticator.Response{
User: &user.DefaultInfo{
Name: goodUsername,
Name: goodUsername,
Extra: expectedExtras,
},
},
wantAuthenticated: true,
@@ -2644,6 +3264,7 @@ func testTableForAuthenticateTokenTests(
User: &user.DefaultInfo{
Name: goodUsername,
Groups: []string{group0, group1},
Extra: expectedExtras,
},
},
wantAuthenticated: true,
@@ -2657,16 +3278,27 @@ func testTableForAuthenticateTokenTests(
User: &user.DefaultInfo{
Name: goodUsername,
Groups: []string{"some-distributed-group-1", "some-distributed-group-2"},
Extra: expectedExtras,
},
},
wantAuthenticated: true,
skip: func(t *testing.T) {
if groupsClaimIsCelExpression {
t.Skip("skipping test because Kubernetes does not support using a CEL expression for groups mapping with distributed claims")
}
},
},
{
name: "distributed groups returns a 404",
jwtClaims: func(claims *josejwt.Claims, groups *any, username *string) {
},
distributedGroupsClaimURL: issuer + "/not_found_claim_source",
wantErr: testutil.WantMatchingErrorString(`oidc: could not expand distributed claims: while getting distributed claim "` + expectedGroupsClaim + `": error while getting distributed claim JWT: 404 Not Found`),
wantErr: testutil.WantExactErrorString(`oidc: could not expand distributed claims: while getting distributed claim "` + expectedGroupsClaim + `": error while getting distributed claim JWT: 404 Not Found`),
skip: func(t *testing.T) {
if groupsClaimIsCelExpression {
t.Skip("skipping test because Kubernetes does not support using a CEL expression for groups mapping with distributed claims")
}
},
},
{
name: "distributed groups doesn't return the right claim",
@@ -2674,6 +3306,11 @@ func testTableForAuthenticateTokenTests(
},
distributedGroupsClaimURL: issuer + "/wrong_claim_source",
wantErr: testutil.WantMatchingErrorString(`oidc: could not expand distributed claims: jwt returned by distributed claim endpoint "` + issuer + `/wrong_claim_source" did not contain claim: `),
skip: func(t *testing.T) {
if groupsClaimIsCelExpression {
t.Skip("skipping test because Kubernetes does not support using a CEL expression for groups mapping with distributed claims")
}
},
},
{
name: "good token with groups as string",
@@ -2684,6 +3321,7 @@ func testTableForAuthenticateTokenTests(
User: &user.DefaultInfo{
Name: goodUsername,
Groups: []string{group0},
Extra: expectedExtras,
},
},
wantAuthenticated: true,
@@ -2695,7 +3333,8 @@ func testTableForAuthenticateTokenTests(
},
wantResponse: &authenticator.Response{
User: &user.DefaultInfo{
Name: goodUsername,
Name: goodUsername,
Extra: expectedExtras,
},
},
wantAuthenticated: true,
@@ -2705,7 +3344,7 @@ func testTableForAuthenticateTokenTests(
jwtClaims: func(_ *josejwt.Claims, groups *any, username *string) {
*groups = map[string]string{"not an array": "or a string"}
},
wantErr: testutil.WantMatchingErrorString(`oidc: parse groups claim "` + expectedGroupsClaim + `": json: cannot unmarshal object into Go value of type string`),
wantErr: expectedErrForBadTokenWithGroupsAsMap(),
},
{
name: "bad token with wrong issuer",
@@ -2720,14 +3359,14 @@ func testTableForAuthenticateTokenTests(
jwtClaims: func(claims *josejwt.Claims, _ *any, username *string) {
claims.Audience = nil
},
wantErr: testutil.WantMatchingErrorString(`oidc: verify token: oidc: expected audience "some-audience" got \[\]`),
wantErr: testutil.WantExactErrorString(`oidc: verify token: oidc: expected audience "some-audience" got []`),
},
{
name: "bad token with wrong audience",
jwtClaims: func(claims *josejwt.Claims, _ *any, username *string) {
claims.Audience = []string{"wrong-audience"}
},
wantErr: testutil.WantMatchingErrorString(`oidc: verify token: oidc: expected audience "some-audience" got \["wrong-audience"\]`),
wantErr: testutil.WantExactErrorString(`oidc: verify token: oidc: expected audience "some-audience" got ["wrong-audience"]`),
},
{
name: "bad token with nbf in the future",
@@ -2755,7 +3394,7 @@ func testTableForAuthenticateTokenTests(
jwtClaims: func(claims *josejwt.Claims, _ *any, username *string) {
*username = ""
},
wantErr: testutil.WantMatchingErrorString(`oidc: parse username claims "` + expectedUsernameClaim + `": claim not present`),
wantErr: expectedErrForTokenDoesNotHaveUsernameClaim(),
},
{
name: "signing key is wrong",
@@ -2765,7 +3404,7 @@ func testTableForAuthenticateTokenTests(
require.NoError(t, err)
*algo = jose.ES256
},
wantErr: testutil.WantMatchingErrorString(`oidc: verify token: failed to verify signature: failed to verify id token signature`),
wantErr: testutil.WantExactErrorString(`oidc: verify token: failed to verify signature: failed to verify id token signature`),
},
{
name: "signing algo is unsupported",
@@ -2775,7 +3414,7 @@ func testTableForAuthenticateTokenTests(
require.NoError(t, err)
*algo = jose.ES384
},
wantErr: testutil.WantMatchingErrorString(`oidc: verify token: oidc: id token signed with unsupported algorithm, expected \["RS256" "ES256"\] got "ES384"`),
wantErr: testutil.WantExactErrorString(`oidc: verify token: oidc: id token signed with unsupported algorithm, expected ["RS256" "ES256"] got "ES384"`),
},
}
@@ -2828,10 +3467,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
},

View File

@@ -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()

View File

@@ -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
//

View File

@@ -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
//

View File

@@ -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
//

View File

@@ -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.

View File

@@ -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
//

View File

@@ -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
//

View File

@@ -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
//

View File

@@ -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
//

View File

@@ -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
//

View File

@@ -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
//

View File

@@ -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
//

View File

@@ -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
//

View File

@@ -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{

View File

@@ -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)
}
})
}
}

View File

@@ -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.

View File

@@ -209,6 +209,7 @@ func TestAuditLogsDuringLogin_Disruptive(t *testing.T) {
"personalInfo": map[string]any{
"username": "redacted",
"groups": []any{"redacted 2 values"},
"extras": map[string]any{"redacted": "redacted 1 keys"},
},
},
}, allConciergeTCRLogs)
@@ -342,6 +343,25 @@ func TestAuditLogsDuringLogin_Disruptive(t *testing.T) {
for _, log := range allConciergeTCRLogs {
require.NotEmpty(t, log["issuedClientCert"])
delete(log, "issuedClientCert")
// The value at the extras key "authentication.kubernetes.io/credential-id" will be a JWT ID,
// which is hard to predict, so just assert that it is there without worrying about its exact value.
require.Contains(t, log, "personalInfo")
personalInfo, ok := log["personalInfo"].(map[string]any)
require.True(t, ok)
require.NotNil(t, personalInfo["extras"])
extras, ok := personalInfo["extras"].(map[string]any)
require.True(t, ok)
require.Contains(t, extras, "authentication.kubernetes.io/credential-id")
require.Len(t, extras, 1) // should be the only key
id := extras["authentication.kubernetes.io/credential-id"]
idValues, ok := id.([]any)
require.True(t, ok)
require.Len(t, idValues, 1)
require.Regexp(t, "JTI=.+", idValues[0])
// Now that we have made assertions about all the expected extras,
// delete it so we can compare the rest using equals below.
delete(personalInfo, "extras")
}
// All values in the personalInfo map should not be redacted anymore.
@@ -357,6 +377,7 @@ func TestAuditLogsDuringLogin_Disruptive(t *testing.T) {
"personalInfo": map[string]any{
"username": expectedUsername,
"groups": expectedGroups,
// note: also has an "extras" key, which we deleted from the actual value above
},
},
}, allConciergeTCRLogs)

View File

@@ -222,7 +222,7 @@ func TestCLILoginOIDC_Browser(t *testing.T) {
cacheKey := oidcclient.SessionCacheKey{
Issuer: env.CLIUpstreamOIDC.Issuer,
ClientID: env.CLIUpstreamOIDC.ClientID,
Scopes: []string{"email", "offline_access", "openid", "profile"},
Scopes: []string{"email", "groups", "offline_access", "openid", "profile"}, // in alphabetical order
RedirectURI: strings.ReplaceAll(env.CLIUpstreamOIDC.CallbackURL, "127.0.0.1", "localhost"),
}
cached := cache.GetToken(cacheKey)
@@ -413,7 +413,7 @@ func oidcLoginCommand(ctx context.Context, t *testing.T, pinnipedExe string, ses
cmd := exec.CommandContext(ctx, pinnipedExe, "login", "oidc",
"--issuer", env.CLIUpstreamOIDC.Issuer,
"--client-id", env.CLIUpstreamOIDC.ClientID,
"--scopes", "offline_access,openid,email,profile",
"--scopes", "offline_access,openid,email,profile,groups",
"--listen-port", callbackURL.Port(),
"--session-cache", sessionCachePath,
"--credential-cache", t.TempDir()+"/credentials.yaml",

View File

@@ -1,4 +1,4 @@
// Copyright 2020-2024 the Pinniped contributors. All Rights Reserved.
// Copyright 2020-2025 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package integration
@@ -6,7 +6,9 @@ package integration
import (
"context"
"crypto/x509"
"encoding/base64"
"encoding/pem"
"fmt"
"testing"
"time"
@@ -23,12 +25,306 @@ import (
"go.pinniped.dev/test/testlib"
)
// TestCredentialRequest_Browser cannot run in parallel because runPinnipedLoginOIDC uses a fixed port
// for its localhost listener via --listen-port=env.CLIUpstreamOIDC.CallbackURL.Port() per oidcLoginCommand.
// Since ports are global to the process, tests using oidcLoginCommand must be run serially.
func TestCredentialRequest_Browser(t *testing.T) {
env := testlib.IntegrationEnv(t).WithCapability(testlib.ClusterSigningKeyIsAvailable)
ctx, cancel := context.WithTimeout(context.Background(), 6*time.Minute)
t.Cleanup(cancel)
jwtAuthenticatorTypedLocalObjectReference := func(a *authenticationv1alpha1.JWTAuthenticator) corev1.TypedLocalObjectReference {
return corev1.TypedLocalObjectReference{
APIGroup: &authenticationv1alpha1.SchemeGroupVersion.Group,
Kind: "JWTAuthenticator",
Name: a.Name,
}
}
expectedExtras := func(t *testing.T, jwt string) []string {
// Dex tokens do not include a jti claim, so check if it exists.
claims := getJWTClaims(t, jwt)
_, ok := claims["jti"]
if !ok {
return []string{}
}
// Okta tokens contain a jti, so use it to make the expected value.
jti := getJWTClaimAsString(t, jwt, "jti")
require.NotEmpty(t, jti)
return []string{
// The Kubernetes jwtAuthenticator will automatically add this extra when there is a jti claim.
fmt.Sprintf("authentication.kubernetes.io/credential-id=JTI=%s", jti),
}
}
tests := []struct {
name string
authenticator func(context.Context, *testing.T) corev1.TypedLocalObjectReference
token func(t *testing.T) (tokenToSubmit string, wantUsername string, wantGroups []string, wantExtras []string)
}{
{
name: "webhook",
authenticator: func(ctx context.Context, t *testing.T) corev1.TypedLocalObjectReference {
return testlib.CreateTestWebhookAuthenticator(ctx, t, &env.TestWebhook, authenticationv1alpha1.WebhookAuthenticatorPhaseReady)
},
token: func(t *testing.T) (string, string, []string, []string) {
return env.TestUser.Token, env.TestUser.ExpectedUsername, env.TestUser.ExpectedGroups, []string{}
},
},
{
name: "minimal jwt authenticator",
authenticator: func(ctx context.Context, t *testing.T) corev1.TypedLocalObjectReference {
authenticator := testlib.CreateTestJWTAuthenticator(ctx, t, authenticationv1alpha1.JWTAuthenticatorSpec{
Issuer: env.CLIUpstreamOIDC.Issuer,
Audience: env.CLIUpstreamOIDC.ClientID,
Claims: authenticationv1alpha1.JWTTokenClaims{
Username: env.CLIUpstreamOIDC.UsernameClaim,
Groups: env.CLIUpstreamOIDC.GroupsClaim,
},
TLS: tlsSpecForCLIUpstreamOIDC(t),
}, authenticationv1alpha1.JWTAuthenticatorPhaseReady)
return jwtAuthenticatorTypedLocalObjectReference(authenticator)
},
token: func(t *testing.T) (string, string, []string, []string) {
pinnipedExe := testlib.PinnipedCLIPath(t)
credOutput, _ := runPinnipedLoginOIDC(ctx, t, pinnipedExe)
token := credOutput.Status.Token
// Sanity check that the JWT contains the expected username claim.
username := getJWTClaimAsString(t, token, env.CLIUpstreamOIDC.UsernameClaim)
require.Equal(t, env.CLIUpstreamOIDC.Username, username)
// Sanity check that the JWT contains the expected groups claim.
// Dex doesn't return groups, so only check where we are expecting groups.
if len(env.CLIUpstreamOIDC.ExpectedGroups) > 0 {
groups := getJWTClaimAsStringSlice(t, token, env.CLIUpstreamOIDC.GroupsClaim)
t.Logf("found groups in JWT token: %#v", groups)
require.ElementsMatch(t, groups, env.CLIUpstreamOIDC.ExpectedGroups)
}
return token, env.CLIUpstreamOIDC.Username, env.CLIUpstreamOIDC.ExpectedGroups, expectedExtras(t, token)
},
},
{
name: "jwt authenticator with username and groups CEL expressions and additional extras and validation rules which allow auth",
authenticator: func(ctx context.Context, t *testing.T) corev1.TypedLocalObjectReference {
authenticator := testlib.CreateTestJWTAuthenticator(ctx, t, authenticationv1alpha1.JWTAuthenticatorSpec{
Issuer: env.CLIUpstreamOIDC.Issuer,
Audience: env.CLIUpstreamOIDC.ClientID,
Claims: authenticationv1alpha1.JWTTokenClaims{
UsernameExpression: "claims.sub",
GroupsExpression: `["group1", "group2"]`,
Extra: []authenticationv1alpha1.ExtraMapping{
{
Key: "example.com/sub",
ValueExpression: "claims.sub",
},
{
Key: "example.com/const",
ValueExpression: `"some-value"`,
},
},
},
ClaimValidationRules: []authenticationv1alpha1.ClaimValidationRule{
{
Claim: env.CLIUpstreamOIDC.UsernameClaim,
RequiredValue: env.CLIUpstreamOIDC.Username,
},
{
Expression: fmt.Sprintf("claims.%s == '%s'", env.CLIUpstreamOIDC.UsernameClaim, env.CLIUpstreamOIDC.Username),
Message: "only one specific user is allowed",
},
},
UserValidationRules: []authenticationv1alpha1.UserValidationRule{
{
Expression: "!user.username.startsWith('system:')",
Message: "username cannot used reserved system: prefix",
},
},
TLS: tlsSpecForCLIUpstreamOIDC(t),
}, authenticationv1alpha1.JWTAuthenticatorPhaseReady)
return jwtAuthenticatorTypedLocalObjectReference(authenticator)
},
token: func(t *testing.T) (string, string, []string, []string) {
pinnipedExe := testlib.PinnipedCLIPath(t)
credOutput, _ := runPinnipedLoginOIDC(ctx, t, pinnipedExe)
token := credOutput.Status.Token
subject := getJWTClaimAsString(t, token, "sub")
require.NotEmpty(t, subject)
wantExtras := expectedExtras(t, token)
wantExtras = append(wantExtras, fmt.Sprintf("example.com/sub=%s", subject))
wantExtras = append(wantExtras, "example.com/const=some-value")
return token, subject, []string{"group1", "group2"}, wantExtras
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
authenticatorRefToSubmit := test.authenticator(ctx, t)
tokenToSubmit, wantUsername, wantGroups, wantExtras := test.token(t)
var response *loginv1alpha1.TokenCredentialRequest
testlib.RequireEventually(t, func(requireEventually *require.Assertions) {
var err error
response, err = testlib.CreateTokenCredentialRequest(ctx, t,
loginv1alpha1.TokenCredentialRequestSpec{Token: tokenToSubmit, Authenticator: authenticatorRefToSubmit},
)
requireEventually.NoError(err, "the request should never fail at the HTTP level")
requireEventually.NotNil(response)
requireEventually.NotNil(response.Status.Credential, "the response should contain a credential")
requireEventually.Emptyf(response.Status.Message, "value is: %q", safeDerefStringPtr(response.Status.Message))
requireEventually.NotNil(response.Status.Credential)
requireEventually.Empty(response.Spec)
requireEventually.Empty(response.Status.Credential.Token)
requireEventually.NotEmpty(response.Status.Credential.ClientCertificateData)
requireEventually.Equal(wantUsername, getCommonName(t, response.Status.Credential.ClientCertificateData))
requireEventually.ElementsMatch(wantGroups, getOrganizations(t, response.Status.Credential.ClientCertificateData))
requireEventually.ElementsMatch(wantExtras, getOrganizationalUnits(t, response.Status.Credential.ClientCertificateData))
requireEventually.NotEmpty(response.Status.Credential.ClientKeyData)
requireEventually.NotNil(response.Status.Credential.ExpirationTimestamp)
requireEventually.InDelta(5*time.Minute, time.Until(response.Status.Credential.ExpirationTimestamp.Time), float64(time.Minute))
}, 10*time.Second, 500*time.Millisecond)
// Create a client using the certificate from the CredentialRequest.
clientWithCertFromCredentialRequest := testlib.NewClientsetWithCertAndKey(
t,
response.Status.Credential.ClientCertificateData,
response.Status.Credential.ClientKeyData,
)
t.Run(
"access as user",
testlib.AccessAsUserTest(ctx, wantUsername, clientWithCertFromCredentialRequest),
)
for _, group := range wantGroups {
t.Run(
"access as group "+group,
testlib.AccessAsGroupTest(ctx, group, clientWithCertFromCredentialRequest),
)
}
})
}
}
// This test cannot run in parallel because runPinnipedLoginOIDC uses a fixed port
// for its localhost listener via --listen-port=env.CLIUpstreamOIDC.CallbackURL.Port() per oidcLoginCommand.
// Since ports are global to the process, tests using oidcLoginCommand must be run serially.
func TestCredentialRequest_JWTAuthenticatorRulesToDisallowLogin_Browser(t *testing.T) {
env := testlib.IntegrationEnv(t).WithCapability(testlib.AnonymousAuthenticationSupported)
basicSpec := &authenticationv1alpha1.JWTAuthenticatorSpec{
Issuer: env.CLIUpstreamOIDC.Issuer,
Audience: env.CLIUpstreamOIDC.ClientID,
Claims: authenticationv1alpha1.JWTTokenClaims{
Username: env.CLIUpstreamOIDC.UsernameClaim,
Groups: env.CLIUpstreamOIDC.GroupsClaim,
},
TLS: tlsSpecForCLIUpstreamOIDC(t),
}
tests := []struct {
name string
authenticator func(context.Context, *testing.T) *authenticationv1alpha1.JWTAuthenticator
wantSuccessfulAuth bool
}{
{
// Sanity check to make sure that the basic JWTAuthenticator spec works before adding rules which should cause auth failure.
name: "JWTAuthenticator successful login",
authenticator: func(ctx context.Context, t *testing.T) *authenticationv1alpha1.JWTAuthenticator {
return testlib.CreateTestJWTAuthenticator(ctx, t, *basicSpec.DeepCopy(), authenticationv1alpha1.JWTAuthenticatorPhaseReady)
},
wantSuccessfulAuth: true,
},
{
name: "JWTAuthenticator ClaimValidationRules using CEL expression should be able to prevent login",
authenticator: func(ctx context.Context, t *testing.T) *authenticationv1alpha1.JWTAuthenticator {
spec := basicSpec.DeepCopy()
spec.ClaimValidationRules = []authenticationv1alpha1.ClaimValidationRule{
{
// This should cause the login to fail for this specific user.
Expression: fmt.Sprintf("claims.%s != '%s'", env.CLIUpstreamOIDC.UsernameClaim, env.CLIUpstreamOIDC.Username),
Message: "one specific user is disallowed",
},
}
return testlib.CreateTestJWTAuthenticator(ctx, t, *spec, authenticationv1alpha1.JWTAuthenticatorPhaseReady)
},
},
{
name: "JWTAuthenticator ClaimValidationRules using RequiredValue should be able to prevent login",
authenticator: func(ctx context.Context, t *testing.T) *authenticationv1alpha1.JWTAuthenticator {
spec := basicSpec.DeepCopy()
spec.ClaimValidationRules = []authenticationv1alpha1.ClaimValidationRule{
{
Claim: "sub",
RequiredValue: "this-will-never-be-the-sub-value",
},
}
return testlib.CreateTestJWTAuthenticator(ctx, t, *spec, authenticationv1alpha1.JWTAuthenticatorPhaseReady)
},
},
{
name: "JWTAuthenticator UserValidationRules CEL expressions should be able to prevent login",
authenticator: func(ctx context.Context, t *testing.T) *authenticationv1alpha1.JWTAuthenticator {
spec := basicSpec.DeepCopy()
spec.UserValidationRules = []authenticationv1alpha1.UserValidationRule{
{
Expression: "false",
Message: "nobody is allowed to auth",
},
}
return testlib.CreateTestJWTAuthenticator(ctx, t, *spec, authenticationv1alpha1.JWTAuthenticatorPhaseReady)
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 6*time.Minute)
t.Cleanup(cancel)
authenticator := test.authenticator(ctx, t)
pinnipedExe := testlib.PinnipedCLIPath(t)
credOutput, _ := runPinnipedLoginOIDC(ctx, t, pinnipedExe)
response, err := testlib.CreateTokenCredentialRequest(ctx, t,
loginv1alpha1.TokenCredentialRequestSpec{
Token: credOutput.Status.Token,
Authenticator: corev1.TypedLocalObjectReference{
APIGroup: &authenticationv1alpha1.SchemeGroupVersion.Group,
Kind: "JWTAuthenticator",
Name: authenticator.Name,
},
},
)
require.NoError(t, err, testlib.Sdump(err))
if test.wantSuccessfulAuth {
require.NotEmpty(t, response.Status.Credential)
require.Empty(t, response.Status.Message)
} else {
require.Nil(t, response.Status.Credential)
require.NotNil(t, response.Status.Message)
require.Equal(t, "authentication failed", *response.Status.Message)
}
})
}
}
// TCRs are non-mutating and safe to run in parallel with serial tests, see main_test.go.
func TestUnsuccessfulCredentialRequest_Parallel(t *testing.T) {
func TestCredentialRequest_ShouldFailWhenTheAuthenticatorDoesNotExist_Parallel(t *testing.T) {
env := testlib.IntegrationEnv(t).WithCapability(testlib.AnonymousAuthenticationSupported)
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
defer cancel()
t.Cleanup(cancel)
response, err := testlib.CreateTokenCredentialRequest(ctx, t,
loginv1alpha1.TokenCredentialRequestSpec{
@@ -46,110 +342,15 @@ func TestUnsuccessfulCredentialRequest_Parallel(t *testing.T) {
require.Equal(t, "authentication failed", *response.Status.Message)
}
// TestSuccessfulCredentialRequest_Browser cannot run in parallel because runPinnipedLoginOIDC uses a fixed port
// for its localhost listener via --listen-port=env.CLIUpstreamOIDC.CallbackURL.Port() per oidcLoginCommand.
// Since ports are global to the process, tests using oidcLoginCommand must be run serially.
func TestSuccessfulCredentialRequest_Browser(t *testing.T) {
env := testlib.IntegrationEnv(t).WithCapability(testlib.ClusterSigningKeyIsAvailable)
ctx, cancel := context.WithTimeout(context.Background(), 6*time.Minute)
defer cancel()
tests := []struct {
name string
authenticator func(context.Context, *testing.T) corev1.TypedLocalObjectReference
token func(t *testing.T) (token string, username string, groups []string)
}{
{
name: "webhook",
authenticator: func(ctx context.Context, t *testing.T) corev1.TypedLocalObjectReference {
return testlib.CreateTestWebhookAuthenticator(ctx, t, &testlib.IntegrationEnv(t).TestWebhook, authenticationv1alpha1.WebhookAuthenticatorPhaseReady)
},
token: func(t *testing.T) (string, string, []string) {
return testlib.IntegrationEnv(t).TestUser.Token, env.TestUser.ExpectedUsername, env.TestUser.ExpectedGroups
},
},
{
name: "jwt authenticator",
authenticator: func(ctx context.Context, t *testing.T) corev1.TypedLocalObjectReference {
authenticator := testlib.CreateTestJWTAuthenticatorForCLIUpstream(ctx, t)
return corev1.TypedLocalObjectReference{
APIGroup: &authenticationv1alpha1.SchemeGroupVersion.Group,
Kind: "JWTAuthenticator",
Name: authenticator.Name,
}
},
token: func(t *testing.T) (string, string, []string) {
pinnipedExe := testlib.PinnipedCLIPath(t)
credOutput, _ := runPinnipedLoginOIDC(ctx, t, pinnipedExe)
token := credOutput.Status.Token
// By default, the JWTAuthenticator expects the username to be in the "username" claim and the
// groups to be in the "groups" claim.
// However, we are configuring Pinniped in the `CreateTestJWTAuthenticatorForCLIUpstream` method above
// to read the username from the "sub" claim of the token instead.
username, groups := getJWTSubAndGroupsClaims(t, token)
return token, username, groups
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
authenticator := test.authenticator(ctx, t)
token, username, groups := test.token(t)
var response *loginv1alpha1.TokenCredentialRequest
testlib.RequireEventually(t, func(requireEventually *require.Assertions) {
var err error
response, err = testlib.CreateTokenCredentialRequest(ctx, t,
loginv1alpha1.TokenCredentialRequestSpec{Token: token, Authenticator: authenticator},
)
requireEventually.NoError(err, "the request should never fail at the HTTP level")
requireEventually.NotNil(response)
requireEventually.NotNil(response.Status.Credential, "the response should contain a credential")
requireEventually.Emptyf(response.Status.Message, "value is: %q", safeDerefStringPtr(response.Status.Message))
requireEventually.NotNil(response.Status.Credential)
requireEventually.Empty(response.Spec)
requireEventually.Empty(response.Status.Credential.Token)
requireEventually.NotEmpty(response.Status.Credential.ClientCertificateData)
requireEventually.Equal(username, getCommonName(t, response.Status.Credential.ClientCertificateData))
requireEventually.ElementsMatch(groups, getOrganizations(t, response.Status.Credential.ClientCertificateData))
requireEventually.NotEmpty(response.Status.Credential.ClientKeyData)
requireEventually.NotNil(response.Status.Credential.ExpirationTimestamp)
requireEventually.InDelta(5*time.Minute, time.Until(response.Status.Credential.ExpirationTimestamp.Time), float64(time.Minute))
}, 10*time.Second, 500*time.Millisecond)
// Create a client using the certificate from the CredentialRequest.
clientWithCertFromCredentialRequest := testlib.NewClientsetWithCertAndKey(
t,
response.Status.Credential.ClientCertificateData,
response.Status.Credential.ClientKeyData,
)
t.Run(
"access as user",
testlib.AccessAsUserTest(ctx, username, clientWithCertFromCredentialRequest),
)
for _, group := range groups {
t.Run(
"access as group "+group,
testlib.AccessAsGroupTest(ctx, group, clientWithCertFromCredentialRequest),
)
}
})
}
}
// TCRs are non-mutating and safe to run in parallel with serial tests, see main_test.go.
func TestFailedCredentialRequestWhenTheRequestIsValidButTheTokenDoesNotAuthenticateTheUser_Parallel(t *testing.T) {
_ = testlib.IntegrationEnv(t).WithCapability(testlib.ClusterSigningKeyIsAvailable)
func TestCredentialRequest_ShouldFailWhenTheRequestIsValidButTheTokenDoesNotAuthenticateTheUser_Parallel(t *testing.T) {
env := testlib.IntegrationEnv(t).WithCapability(testlib.AnonymousAuthenticationSupported)
// Create a testWebhook so we have a legitimate authenticator to pass to the
// TokenCredentialRequest API.
// Create a testWebhook so we have a legitimate authenticator to pass to the TokenCredentialRequest API.
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
defer cancel()
testWebhook := testlib.CreateTestWebhookAuthenticator(ctx, t, &testlib.IntegrationEnv(t).TestWebhook, authenticationv1alpha1.WebhookAuthenticatorPhaseReady)
t.Cleanup(cancel)
testWebhook := testlib.CreateTestWebhookAuthenticator(ctx, t, &env.TestWebhook, authenticationv1alpha1.WebhookAuthenticatorPhaseReady)
response, err := testlib.CreateTokenCredentialRequest(context.Background(), t,
loginv1alpha1.TokenCredentialRequestSpec{Token: "not a good token", Authenticator: testWebhook},
@@ -164,13 +365,13 @@ func TestFailedCredentialRequestWhenTheRequestIsValidButTheTokenDoesNotAuthentic
// TCRs are non-mutating and safe to run in parallel with serial tests, see main_test.go.
func TestCredentialRequest_ShouldFailWhenRequestDoesNotIncludeToken_Parallel(t *testing.T) {
_ = testlib.IntegrationEnv(t).WithCapability(testlib.ClusterSigningKeyIsAvailable)
env := testlib.IntegrationEnv(t).WithCapability(testlib.AnonymousAuthenticationSupported)
// Create a testWebhook so we have a legitimate authenticator to pass to the
// TokenCredentialRequest API.
// Create a testWebhook so we have a legitimate authenticator to pass to the TokenCredentialRequest API.
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
defer cancel()
testWebhook := testlib.CreateTestWebhookAuthenticator(ctx, t, &testlib.IntegrationEnv(t).TestWebhook, authenticationv1alpha1.WebhookAuthenticatorPhaseReady)
t.Cleanup(cancel)
testWebhook := testlib.CreateTestWebhookAuthenticator(ctx, t, &env.TestWebhook, authenticationv1alpha1.WebhookAuthenticatorPhaseReady)
response, err := testlib.CreateTokenCredentialRequest(context.Background(), t,
loginv1alpha1.TokenCredentialRequestSpec{Token: "", Authenticator: testWebhook},
@@ -210,6 +411,16 @@ func getOrganizations(t *testing.T, certPEM string) []string {
return cert.Subject.Organization
}
func getOrganizationalUnits(t *testing.T, certPEM string) []string {
t.Helper()
pemBlock, _ := pem.Decode([]byte(certPEM))
cert, err := x509.ParseCertificate(pemBlock.Bytes)
require.NoError(t, err)
return cert.Subject.OrganizationalUnit
}
func safeDerefStringPtr(s *string) string {
if s == nil {
return "<nil>"
@@ -217,18 +428,51 @@ func safeDerefStringPtr(s *string) string {
return *s
}
func getJWTSubAndGroupsClaims(t *testing.T, jwtToken string) (string, []string) {
func getJWTClaimAsString(t *testing.T, jwtToken string, claimName string) string {
t.Helper()
claims := getJWTClaims(t, jwtToken)
require.Contains(t, claims, claimName)
val := claims[claimName]
strVal, ok := val.(string)
require.Truef(t, ok, "expected value of claim %q to be a string, but it was: %#v", claimName, claims[claimName])
return strVal
}
func getJWTClaimAsStringSlice(t *testing.T, jwtToken string, claimName string) []string {
t.Helper()
claims := getJWTClaims(t, jwtToken)
require.Contains(t, claims, claimName)
val := claims[claimName]
anySliceVal, ok := val.([]any)
require.Truef(t, ok, "expected value of claim %q to be a []any, but it was: %#v", claimName, claims[claimName])
strSliceVal := make([]string, len(anySliceVal))
for i := range anySliceVal {
strSliceVal[i], ok = anySliceVal[i].(string)
require.Truef(t, ok, "expected every value of array at claim %q to be a string, but one element was: %#v", claimName, anySliceVal[i])
}
return strSliceVal
}
func getJWTClaims(t *testing.T, jwtToken string) map[string]any {
t.Helper()
token, err := josejwt.ParseSigned(jwtToken, []jose.SignatureAlgorithm{jose.ES256, jose.RS256})
require.NoError(t, err)
var claims struct {
Sub string `json:"sub"`
Groups []string `json:"groups"`
}
claims := map[string]any{}
err = token.UnsafeClaimsWithoutVerification(&claims)
require.NoError(t, err)
return claims.Sub, claims.Groups
return claims
}
func tlsSpecForCLIUpstreamOIDC(t *testing.T) *authenticationv1alpha1.TLSSpec {
env := testlib.IntegrationEnv(t)
// If the test upstream does not have a CA bundle specified, then don't configure it.
if env.CLIUpstreamOIDC.CABundle != "" {
return &authenticationv1alpha1.TLSSpec{
CertificateAuthorityData: base64.StdEncoding.EncodeToString([]byte(env.CLIUpstreamOIDC.CABundle)),
}
}
return nil
}

View File

@@ -1,4 +1,4 @@
// Copyright 2024 the Pinniped contributors. All Rights Reserved.
// Copyright 2024-2025 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package integration
@@ -322,6 +322,308 @@ func TestConciergeJWTAuthenticatorStatus_Parallel(t *testing.T) {
},
),
},
{
name: "claims cannot use both username and usernameExpression",
spec: authenticationv1alpha1.JWTAuthenticatorSpec{
Issuer: env.SupervisorUpstreamOIDC.Issuer,
Audience: "some-fake-audience",
TLS: &authenticationv1alpha1.TLSSpec{
CertificateAuthorityData: base64.StdEncoding.EncodeToString([]byte(env.SupervisorUpstreamOIDC.CABundle)),
},
Claims: authenticationv1alpha1.JWTTokenClaims{
Username: "foo",
UsernameExpression: "bar",
},
},
wantPhase: authenticationv1alpha1.JWTAuthenticatorPhaseError,
wantConditions: replaceSomeConditions(t,
allSuccessfulJWTAuthenticatorConditions(len(env.SupervisorUpstreamOIDC.CABundle) != 0),
[]metav1.Condition{
{
Type: "Ready",
Status: "False",
Reason: "NotReady",
Message: "the JWTAuthenticator is not ready: see other conditions for details",
}, {
Type: "AuthenticatorValid",
Status: "False",
Reason: "InvalidAuthenticator",
Message: `could not initialize jwt authenticator: claims.username: Invalid value: "": claim and expression can't both be set`,
},
},
),
},
{
name: "claims cannot use both groups and groupsExpression",
spec: authenticationv1alpha1.JWTAuthenticatorSpec{
Issuer: env.SupervisorUpstreamOIDC.Issuer,
Audience: "some-fake-audience",
TLS: &authenticationv1alpha1.TLSSpec{
CertificateAuthorityData: base64.StdEncoding.EncodeToString([]byte(env.SupervisorUpstreamOIDC.CABundle)),
},
Claims: authenticationv1alpha1.JWTTokenClaims{
Groups: "foo",
GroupsExpression: "bar",
},
},
wantPhase: authenticationv1alpha1.JWTAuthenticatorPhaseError,
wantConditions: replaceSomeConditions(t,
allSuccessfulJWTAuthenticatorConditions(len(env.SupervisorUpstreamOIDC.CABundle) != 0),
[]metav1.Condition{
{
Type: "Ready",
Status: "False",
Reason: "NotReady",
Message: "the JWTAuthenticator is not ready: see other conditions for details",
}, {
Type: "AuthenticatorValid",
Status: "False",
Reason: "InvalidAuthenticator",
Message: `could not initialize jwt authenticator: claims.groups: Invalid value: "": claim and expression can't both be set`,
},
},
),
},
{
name: "username claim expression cannot use clams.email unless it also uses claims.email_verified elsewhere",
spec: authenticationv1alpha1.JWTAuthenticatorSpec{
Issuer: env.SupervisorUpstreamOIDC.Issuer,
Audience: "some-fake-audience",
TLS: &authenticationv1alpha1.TLSSpec{
CertificateAuthorityData: base64.StdEncoding.EncodeToString([]byte(env.SupervisorUpstreamOIDC.CABundle)),
},
Claims: authenticationv1alpha1.JWTTokenClaims{
UsernameExpression: "claims.email",
},
},
wantPhase: authenticationv1alpha1.JWTAuthenticatorPhaseError,
wantConditions: replaceSomeConditions(t,
allSuccessfulJWTAuthenticatorConditions(len(env.SupervisorUpstreamOIDC.CABundle) != 0),
[]metav1.Condition{
{
Type: "Ready",
Status: "False",
Reason: "NotReady",
Message: "the JWTAuthenticator is not ready: see other conditions for details",
}, {
Type: "AuthenticatorValid",
Status: "False",
Reason: "InvalidAuthenticator",
Message: `could not initialize jwt authenticator: claims.usernameExpression: Invalid value: "claims.email": ` +
`claims.email_verified must be used in claims.usernameExpression or claims.extra[*].valueExpression or ` +
`claimValidationRules[*].expression when claims.email is used in claims.usernameExpression`,
},
},
),
},
{
name: "username claim expression cannot use invalid CEL expression",
spec: authenticationv1alpha1.JWTAuthenticatorSpec{
Issuer: env.SupervisorUpstreamOIDC.Issuer,
Audience: "some-fake-audience",
TLS: &authenticationv1alpha1.TLSSpec{
CertificateAuthorityData: base64.StdEncoding.EncodeToString([]byte(env.SupervisorUpstreamOIDC.CABundle)),
},
Claims: authenticationv1alpha1.JWTTokenClaims{
UsernameExpression: "this is not a valid CEL expression",
},
},
wantPhase: authenticationv1alpha1.JWTAuthenticatorPhaseError,
wantConditions: replaceSomeConditions(t,
allSuccessfulJWTAuthenticatorConditions(len(env.SupervisorUpstreamOIDC.CABundle) != 0),
[]metav1.Condition{
{
Type: "Ready",
Status: "False",
Reason: "NotReady",
Message: "the JWTAuthenticator is not ready: see other conditions for details",
}, {
Type: "AuthenticatorValid",
Status: "False",
Reason: "InvalidAuthenticator",
Message: "could not initialize jwt authenticator: claims.usernameExpression: Invalid value: " +
"\"this is not a valid CEL expression\": compilation failed: ERROR: <input>:1:6: Syntax error: mismatched input 'is' expecting <EOF>\n" +
" | this is not a valid CEL expression\n" +
" | .....^",
},
},
),
},
{
name: "groups claim expression cannot use invalid CEL expression",
spec: authenticationv1alpha1.JWTAuthenticatorSpec{
Issuer: env.SupervisorUpstreamOIDC.Issuer,
Audience: "some-fake-audience",
TLS: &authenticationv1alpha1.TLSSpec{
CertificateAuthorityData: base64.StdEncoding.EncodeToString([]byte(env.SupervisorUpstreamOIDC.CABundle)),
},
Claims: authenticationv1alpha1.JWTTokenClaims{
GroupsExpression: "this is not a valid CEL expression",
},
},
wantPhase: authenticationv1alpha1.JWTAuthenticatorPhaseError,
wantConditions: replaceSomeConditions(t,
allSuccessfulJWTAuthenticatorConditions(len(env.SupervisorUpstreamOIDC.CABundle) != 0),
[]metav1.Condition{
{
Type: "Ready",
Status: "False",
Reason: "NotReady",
Message: "the JWTAuthenticator is not ready: see other conditions for details",
}, {
Type: "AuthenticatorValid",
Status: "False",
Reason: "InvalidAuthenticator",
Message: "could not initialize jwt authenticator: claims.groupsExpression: Invalid value: " +
"\"this is not a valid CEL expression\": compilation failed: ERROR: <input>:1:6: Syntax error: mismatched input 'is' expecting <EOF>\n" +
" | this is not a valid CEL expression\n" +
" | .....^",
},
},
),
},
{
name: "extra keys cannot have equal sign and must be domain-prefixed path",
spec: authenticationv1alpha1.JWTAuthenticatorSpec{
Issuer: env.SupervisorUpstreamOIDC.Issuer,
Audience: "some-fake-audience",
TLS: &authenticationv1alpha1.TLSSpec{
CertificateAuthorityData: base64.StdEncoding.EncodeToString([]byte(env.SupervisorUpstreamOIDC.CABundle)),
},
Claims: authenticationv1alpha1.JWTTokenClaims{
Extra: []authenticationv1alpha1.ExtraMapping{
{
Key: "a=b",
ValueExpression: `"value"`,
},
},
},
},
wantPhase: authenticationv1alpha1.JWTAuthenticatorPhaseError,
wantConditions: replaceSomeConditions(t,
allSuccessfulJWTAuthenticatorConditions(len(env.SupervisorUpstreamOIDC.CABundle) != 0),
[]metav1.Condition{
{
Type: "Ready",
Status: "False",
Reason: "NotReady",
Message: "the JWTAuthenticator is not ready: see other conditions for details",
}, {
Type: "AuthenticatorValid",
Status: "False",
Reason: "InvalidAuthenticator",
Message: `could not initialize jwt authenticator: [` +
`claims.extra[0].key: Invalid value: "a=b": must be a domain-prefixed path (such as "acme.io/foo"), ` +
`claims.extra[0].key: Invalid value: "a=b": Pinniped does not allow extra key names to contain equals sign]`,
},
},
),
},
{
name: "claimValidationRules claim and requiredValue are mutually exclusive with expression and message",
spec: authenticationv1alpha1.JWTAuthenticatorSpec{
Issuer: env.SupervisorUpstreamOIDC.Issuer,
Audience: "some-fake-audience",
TLS: &authenticationv1alpha1.TLSSpec{
CertificateAuthorityData: base64.StdEncoding.EncodeToString([]byte(env.SupervisorUpstreamOIDC.CABundle)),
},
ClaimValidationRules: []authenticationv1alpha1.ClaimValidationRule{
{
Claim: "foo",
RequiredValue: "bar",
Expression: "baz",
Message: "bat",
},
},
},
wantPhase: authenticationv1alpha1.JWTAuthenticatorPhaseError,
wantConditions: replaceSomeConditions(t,
allSuccessfulJWTAuthenticatorConditions(len(env.SupervisorUpstreamOIDC.CABundle) != 0),
[]metav1.Condition{
{
Type: "Ready",
Status: "False",
Reason: "NotReady",
Message: "the JWTAuthenticator is not ready: see other conditions for details",
}, {
Type: "AuthenticatorValid",
Status: "False",
Reason: "InvalidAuthenticator",
Message: `could not initialize jwt authenticator: claimValidationRules[0]: Invalid value: "foo": claim and expression can't both be set`,
},
},
),
},
{
name: "claimValidationRules cannot use invalid CEL expressions",
spec: authenticationv1alpha1.JWTAuthenticatorSpec{
Issuer: env.SupervisorUpstreamOIDC.Issuer,
Audience: "some-fake-audience",
TLS: &authenticationv1alpha1.TLSSpec{
CertificateAuthorityData: base64.StdEncoding.EncodeToString([]byte(env.SupervisorUpstreamOIDC.CABundle)),
},
ClaimValidationRules: []authenticationv1alpha1.ClaimValidationRule{
{
Expression: "this is not a valid CEL expression",
},
},
},
wantPhase: authenticationv1alpha1.JWTAuthenticatorPhaseError,
wantConditions: replaceSomeConditions(t,
allSuccessfulJWTAuthenticatorConditions(len(env.SupervisorUpstreamOIDC.CABundle) != 0),
[]metav1.Condition{
{
Type: "Ready",
Status: "False",
Reason: "NotReady",
Message: "the JWTAuthenticator is not ready: see other conditions for details",
}, {
Type: "AuthenticatorValid",
Status: "False",
Reason: "InvalidAuthenticator",
Message: "could not initialize jwt authenticator: claimValidationRules[0].expression: Invalid value: " +
"\"this is not a valid CEL expression\": compilation failed: ERROR: <input>:1:6: Syntax error: mismatched input 'is' expecting <EOF>\n" +
" | this is not a valid CEL expression\n" +
" | .....^",
},
},
),
},
{
name: "userValidationRules must use valid CEL expressions",
spec: authenticationv1alpha1.JWTAuthenticatorSpec{
Issuer: env.SupervisorUpstreamOIDC.Issuer,
Audience: "some-fake-audience",
TLS: &authenticationv1alpha1.TLSSpec{
CertificateAuthorityData: base64.StdEncoding.EncodeToString([]byte(env.SupervisorUpstreamOIDC.CABundle)),
},
UserValidationRules: []authenticationv1alpha1.UserValidationRule{
{
Expression: "this is not a valid CEL expression",
},
},
},
wantPhase: authenticationv1alpha1.JWTAuthenticatorPhaseError,
wantConditions: replaceSomeConditions(t,
allSuccessfulJWTAuthenticatorConditions(len(env.SupervisorUpstreamOIDC.CABundle) != 0),
[]metav1.Condition{
{
Type: "Ready",
Status: "False",
Reason: "NotReady",
Message: "the JWTAuthenticator is not ready: see other conditions for details",
}, {
Type: "AuthenticatorValid",
Status: "False",
Reason: "InvalidAuthenticator",
Message: "could not initialize jwt authenticator: userValidationRules[0].expression: Invalid value: " +
"\"this is not a valid CEL expression\": compilation failed: ERROR: <input>:1:6: Syntax error: mismatched input 'is' expecting <EOF>\n" +
" | this is not a valid CEL expression\n" +
" | .....^",
},
},
),
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
@@ -479,40 +781,42 @@ func allSuccessfulJWTAuthenticatorConditions(caBundleExists bool) []metav1.Condi
if caBundleExists {
tlsConfigValidMsg = "spec.tls is valid: using configured CA bundle"
}
return []metav1.Condition{{
Type: "AuthenticatorValid",
Status: "True",
Reason: "Success",
Message: "authenticator initialized",
}, {
Type: "DiscoveryURLValid",
Status: "True",
Reason: "Success",
Message: "discovery performed successfully",
}, {
Type: "IssuerURLValid",
Status: "True",
Reason: "Success",
Message: "issuer is a valid URL",
}, {
Type: "JWKSFetchValid",
Status: "True",
Reason: "Success",
Message: "successfully fetched jwks",
}, {
Type: "JWKSURLValid",
Status: "True",
Reason: "Success",
Message: "jwks_uri is a valid URL",
}, {
Type: "Ready",
Status: "True",
Reason: "Success",
Message: "the JWTAuthenticator is ready",
}, {
Type: "TLSConfigurationValid",
Status: "True",
Reason: "Success",
Message: tlsConfigValidMsg,
}}
return []metav1.Condition{
{
Type: "AuthenticatorValid",
Status: "True",
Reason: "Success",
Message: "authenticator initialized",
}, {
Type: "DiscoveryURLValid",
Status: "True",
Reason: "Success",
Message: "discovery performed successfully",
}, {
Type: "IssuerURLValid",
Status: "True",
Reason: "Success",
Message: "issuer is a valid URL",
}, {
Type: "JWKSFetchValid",
Status: "True",
Reason: "Success",
Message: "successfully fetched jwks",
}, {
Type: "JWKSURLValid",
Status: "True",
Reason: "Success",
Message: "jwks_uri is a valid URL",
}, {
Type: "Ready",
Status: "True",
Reason: "Success",
Message: "the JWTAuthenticator is ready",
}, {
Type: "TLSConfigurationValid",
Status: "True",
Reason: "Success",
Message: tlsConfigValidMsg,
},
}
}

View File

@@ -451,8 +451,9 @@ func TestGetAPIResourceList(t *testing.T) { //nolint:gocyclo // each t.Run is pr
}
}
// manually update this value whenever you add additional fields to an API resource and then run the generator
totalExpectedAPIFields := 310
// Manually update this value whenever you add additional fields to an API resource and then run the generator.
// This is to ensure that this test checked every field in our whole API surface area.
totalExpectedAPIFields := 323
// Because we are parsing text from `kubectl explain` and because the format of that text can change
// over time, make a rudimentary assertion that this test exercised the whole tree of all fields of all

View File

@@ -555,6 +555,7 @@ func TestSupervisorFederationDomainStatus_Disruptive(t *testing.T) {
}
}
//nolint:gocyclo // we have a lot of "if" conditions here, but it's okay
func TestSupervisorFederationDomainCRDValidations_Parallel(t *testing.T) {
env := testlib.IntegrationEnv(t)
fdClient := testlib.NewSupervisorClientset(t).ConfigV1alpha1().FederationDomains(env.SupervisorNamespace)
@@ -566,7 +567,10 @@ func TestSupervisorFederationDomainCRDValidations_Parallel(t *testing.T) {
// Certain non-CEL validation failures will prevent CEL validations from running,
// and the Kubernetes API server will return this error message for those cases.
const couldNotRunCELValidationsErrMessage = `<nil>: Invalid value: "null": some validation rules were not checked because the object was invalid; correct the existing errors to complete validation`
const couldNotRunCELValidationsErrMessage = "some validation rules were not checked because the object was invalid; correct the existing errors to complete validation"
const oldCouldNotRunCELValidationsErrMessage = `<nil>: Invalid value: "null": ` + couldNotRunCELValidationsErrMessage
// Starting in beta version of Kube 1.34, they removed the quotes around the null in this message.
const newCouldNotRunCELValidationsErrMessage = `<nil>: Invalid value: null: ` + couldNotRunCELValidationsErrMessage
tests := []struct {
name string
@@ -576,11 +580,14 @@ func TestSupervisorFederationDomainCRDValidations_Parallel(t *testing.T) {
// optionally override wantErr for one or more specific versions of Kube, due to changing validation error text
wantKube23OrOlderErrs []string
wantKube24Through31InclusiveErrs []string
wantKube32OrNewerErrs []string
wantKube32Through33InclusiveErrs []string
wantKube34OrNewerErrs []string
// These errors are appended to any other wanted errors when k8sAPIServerSupportsCEL is true
wantCELErrorsForKube25Through28Inclusive []string
wantCELErrorsForKube29Through33Inclusive []string
wantCELErrorsForKube29OrNewer []string
wantCELErrorsForKube34OrNewer []string
}{
{
name: "issuer cannot be empty",
@@ -644,6 +651,7 @@ func TestSupervisorFederationDomainCRDValidations_Parallel(t *testing.T) {
// For some unknown reason, Kubernetes versions 1.23 and older return errors *with indices* for this test case only.
wantKube23OrOlderErrs: []string{`spec.identityProviders[0].transforms.constants[1]: Duplicate value: map[string]interface {}{"name":"notUnique"}`},
wantErrs: []string{`spec.identityProviders[0].transforms.constants[1]: Duplicate value: map[string]interface {}{"name":"notUnique"}`},
wantKube34OrNewerErrs: []string{`spec.identityProviders[0].transforms.constants[1]: Duplicate value: {"name":"notUnique"}`},
},
{
name: "IDP transform constant names cannot be empty",
@@ -685,9 +693,10 @@ func TestSupervisorFederationDomainCRDValidations_Parallel(t *testing.T) {
},
wantKube23OrOlderErrs: []string{`spec.identityProviders.transforms.constants.name: Invalid value: "12345678901234567890123456789012345678901234567890123456789012345": spec.identityProviders.transforms.constants.name in body should be at most 64 chars long`},
wantKube24Through31InclusiveErrs: []string{`spec.identityProviders[0].transforms.constants[0].name: Too long: may not be longer than 64`},
wantKube32OrNewerErrs: []string{`spec.identityProviders[0].transforms.constants[0].name: Too long: may not be more than 64 bytes`},
wantCELErrorsForKube25Through28Inclusive: []string{couldNotRunCELValidationsErrMessage},
wantCELErrorsForKube29OrNewer: []string{couldNotRunCELValidationsErrMessage},
wantErrs: []string{`spec.identityProviders[0].transforms.constants[0].name: Too long: may not be more than 64 bytes`},
wantCELErrorsForKube25Through28Inclusive: []string{oldCouldNotRunCELValidationsErrMessage},
wantCELErrorsForKube29Through33Inclusive: []string{oldCouldNotRunCELValidationsErrMessage},
wantCELErrorsForKube34OrNewer: []string{newCouldNotRunCELValidationsErrMessage},
},
{
name: "IDP transform constant names must be a legal CEL variable name",
@@ -737,8 +746,9 @@ func TestSupervisorFederationDomainCRDValidations_Parallel(t *testing.T) {
},
},
},
wantErrs: []string{`spec.identityProviders[0].transforms.constants[0].type: Unsupported value: "this is invalid": supported values: "string", "stringList"`},
wantCELErrorsForKube29OrNewer: []string{couldNotRunCELValidationsErrMessage}, // this should not be checked on kind 1.25, 1.26, 1.27, 1.28
wantErrs: []string{`spec.identityProviders[0].transforms.constants[0].type: Unsupported value: "this is invalid": supported values: "string", "stringList"`},
wantCELErrorsForKube29Through33Inclusive: []string{oldCouldNotRunCELValidationsErrMessage},
wantCELErrorsForKube34OrNewer: []string{newCouldNotRunCELValidationsErrMessage},
},
{
name: "IDP transform expression types must be one of the allowed enum strings",
@@ -761,8 +771,9 @@ func TestSupervisorFederationDomainCRDValidations_Parallel(t *testing.T) {
},
},
},
wantErrs: []string{`spec.identityProviders[0].transforms.expressions[0].type: Unsupported value: "this is invalid": supported values: "policy/v1", "username/v1", "groups/v1"`},
wantCELErrorsForKube29OrNewer: []string{couldNotRunCELValidationsErrMessage}, // this should not be checked on kind 1.25, 1.26, 1.27, 1.28
wantErrs: []string{`spec.identityProviders[0].transforms.expressions[0].type: Unsupported value: "this is invalid": supported values: "policy/v1", "username/v1", "groups/v1"`},
wantCELErrorsForKube29Through33Inclusive: []string{oldCouldNotRunCELValidationsErrMessage},
wantCELErrorsForKube34OrNewer: []string{newCouldNotRunCELValidationsErrMessage},
},
{
name: "IDP transform expressions cannot be empty",
@@ -872,7 +883,7 @@ func TestSupervisorFederationDomainCRDValidations_Parallel(t *testing.T) {
}
})
if len(tt.wantErrs) > 0 && len(tt.wantKube23OrOlderErrs) > 0 && len(tt.wantKube24Through31InclusiveErrs) > 0 && len(tt.wantKube32OrNewerErrs) > 0 {
if len(tt.wantErrs) > 0 && len(tt.wantKube23OrOlderErrs) > 0 && len(tt.wantKube24Through31InclusiveErrs) > 0 && len(tt.wantKube32Through33InclusiveErrs) > 0 && len(tt.wantKube34OrNewerErrs) > 0 {
require.Fail(t, "test setup problem: wanted every possible kind of error, which would cause tt.wantErr to be unused")
}
@@ -900,16 +911,27 @@ func TestSupervisorFederationDomainCRDValidations_Parallel(t *testing.T) {
// Also allow overriding with an exact expected error for these Kube versions.
wantErr = tt.wantKube24Through31InclusiveErrs
}
if minor >= 32 && len(tt.wantKube32OrNewerErrs) > 0 {
if minor >= 32 && minor <= 33 && len(tt.wantKube32Through33InclusiveErrs) > 0 {
// Also allow overriding with an exact expected error for these Kube versions.
wantErr = tt.wantKube32OrNewerErrs
wantErr = tt.wantKube32Through33InclusiveErrs
}
if minor >= 34 && len(tt.wantKube34OrNewerErrs) > 0 {
// Also allow overriding with an exact expected error for these Kube versions.
wantErr = tt.wantKube34OrNewerErrs
}
if minor >= 25 && minor <= 28 && len(tt.wantCELErrorsForKube25Through28Inclusive) > 0 {
wantErr = append(wantErr, tt.wantCELErrorsForKube25Through28Inclusive...)
} else if minor >= 29 && len(tt.wantCELErrorsForKube29OrNewer) > 0 {
}
if minor >= 29 && minor <= 33 && len(tt.wantCELErrorsForKube29Through33Inclusive) > 0 {
wantErr = append(wantErr, tt.wantCELErrorsForKube29Through33Inclusive...)
}
if minor >= 29 && len(tt.wantCELErrorsForKube29OrNewer) > 0 {
wantErr = append(wantErr, tt.wantCELErrorsForKube29OrNewer...)
}
if minor >= 34 && len(tt.wantCELErrorsForKube34OrNewer) > 0 {
wantErr = append(wantErr, tt.wantCELErrorsForKube34OrNewer...)
}
// Did not want any error.
if len(wantErr) == 0 {

View File

@@ -1,4 +1,4 @@
// Copyright 2020-2024 the Pinniped contributors. All Rights Reserved.
// Copyright 2020-2025 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package testlib
@@ -6,7 +6,6 @@ package testlib
import (
"context"
"crypto/rand"
"encoding/base64"
"encoding/hex"
"fmt"
"io"
@@ -253,32 +252,6 @@ func WaitForWebhookAuthenticatorStatusConditions(ctx context.Context, t *testing
}, 60*time.Second, 1*time.Second, "wanted WebhookAuthenticator conditions")
}
// CreateTestJWTAuthenticatorForCLIUpstream creates and returns a test JWTAuthenticator which will be automatically
// deleted at the end of the current test's lifetime.
//
// CreateTestJWTAuthenticatorForCLIUpstream gets the OIDC issuer info from IntegrationEnv().CLIUpstreamOIDC.
func CreateTestJWTAuthenticatorForCLIUpstream(ctx context.Context, t *testing.T) *authenticationv1alpha1.JWTAuthenticator {
t.Helper()
testEnv := IntegrationEnv(t)
spec := authenticationv1alpha1.JWTAuthenticatorSpec{
Issuer: testEnv.CLIUpstreamOIDC.Issuer,
Audience: testEnv.CLIUpstreamOIDC.ClientID,
// The default UsernameClaim is "username" but the upstreams that we use for
// integration tests won't necessarily have that claim, so use "sub" here.
Claims: authenticationv1alpha1.JWTTokenClaims{Username: "sub"},
}
// If the test upstream does not have a CA bundle specified, then don't configure one in the
// JWTAuthenticator. Leaving TLSSpec set to nil will result in OIDC discovery using the OS's root
// CA store.
if testEnv.CLIUpstreamOIDC.CABundle != "" {
spec.TLS = &authenticationv1alpha1.TLSSpec{
CertificateAuthorityData: base64.StdEncoding.EncodeToString([]byte(testEnv.CLIUpstreamOIDC.CABundle)),
}
}
authenticator := CreateTestJWTAuthenticator(ctx, t, spec, authenticationv1alpha1.JWTAuthenticatorPhaseReady)
return authenticator
}
// CreateTestJWTAuthenticator creates and returns a test JWTAuthenticator which will be automatically deleted
// at the end of the current test's lifetime.
func CreateTestJWTAuthenticator(

View File

@@ -304,12 +304,15 @@ func loadEnvVars(t *testing.T, result *TestEnv) {
result.ShellContainerImage = needEnv(t, "PINNIPED_TEST_SHELL_CONTAINER_IMAGE")
result.CLIUpstreamOIDC = TestOIDCUpstream{
Issuer: needEnv(t, "PINNIPED_TEST_CLI_OIDC_ISSUER"),
CABundle: base64Decoded(t, os.Getenv("PINNIPED_TEST_CLI_OIDC_ISSUER_CA_BUNDLE")),
ClientID: needEnv(t, "PINNIPED_TEST_CLI_OIDC_CLIENT_ID"),
CallbackURL: needEnv(t, "PINNIPED_TEST_CLI_OIDC_CALLBACK_URL"),
Username: needEnv(t, "PINNIPED_TEST_CLI_OIDC_USERNAME"),
Password: needEnv(t, "PINNIPED_TEST_CLI_OIDC_PASSWORD"),
Issuer: needEnv(t, "PINNIPED_TEST_CLI_OIDC_ISSUER"),
CABundle: base64Decoded(t, os.Getenv("PINNIPED_TEST_CLI_OIDC_ISSUER_CA_BUNDLE")),
UsernameClaim: os.Getenv("PINNIPED_TEST_CLI_OIDC_USERNAME_CLAIM"),
GroupsClaim: os.Getenv("PINNIPED_TEST_CLI_OIDC_GROUPS_CLAIM"),
ClientID: needEnv(t, "PINNIPED_TEST_CLI_OIDC_CLIENT_ID"),
CallbackURL: needEnv(t, "PINNIPED_TEST_CLI_OIDC_CALLBACK_URL"),
Username: needEnv(t, "PINNIPED_TEST_CLI_OIDC_USERNAME"),
Password: needEnv(t, "PINNIPED_TEST_CLI_OIDC_PASSWORD"),
ExpectedGroups: filterEmpty(strings.Split(strings.ReplaceAll(os.Getenv("PINNIPED_TEST_CLI_OIDC_EXPECTED_GROUPS"), " ", ""), ",")),
}
result.SupervisorUpstreamOIDC = TestOIDCUpstream{