From a9d14750da6a9a316842129119b4a55da3da3a04 Mon Sep 17 00:00:00 2001 From: Ryan Richard Date: Mon, 13 Feb 2023 14:57:28 -0800 Subject: [PATCH] Add APIs for multiple IDP and id transformations to FederationDomain CRD --- .../v1alpha1/types_federationdomain.go.tmpl | 177 ++++++++++++- ...rvisor.pinniped.dev_federationdomains.yaml | 249 ++++++++++++++++++ generated/1.17/README.adoc | 122 +++++++++ .../config/v1alpha1/types_federationdomain.go | 177 ++++++++++++- .../config/v1alpha1/zz_generated.deepcopy.go | 140 ++++++++++ ...rvisor.pinniped.dev_federationdomains.yaml | 249 ++++++++++++++++++ generated/1.18/README.adoc | 122 +++++++++ .../config/v1alpha1/types_federationdomain.go | 177 ++++++++++++- .../config/v1alpha1/zz_generated.deepcopy.go | 140 ++++++++++ ...rvisor.pinniped.dev_federationdomains.yaml | 249 ++++++++++++++++++ generated/1.19/README.adoc | 122 +++++++++ .../config/v1alpha1/types_federationdomain.go | 177 ++++++++++++- .../config/v1alpha1/zz_generated.deepcopy.go | 140 ++++++++++ ...rvisor.pinniped.dev_federationdomains.yaml | 249 ++++++++++++++++++ generated/1.20/README.adoc | 122 +++++++++ .../config/v1alpha1/types_federationdomain.go | 177 ++++++++++++- .../config/v1alpha1/zz_generated.deepcopy.go | 140 ++++++++++ ...rvisor.pinniped.dev_federationdomains.yaml | 249 ++++++++++++++++++ generated/1.21/README.adoc | 122 +++++++++ .../config/v1alpha1/types_federationdomain.go | 177 ++++++++++++- .../config/v1alpha1/zz_generated.deepcopy.go | 140 ++++++++++ ...rvisor.pinniped.dev_federationdomains.yaml | 249 ++++++++++++++++++ generated/1.22/README.adoc | 122 +++++++++ .../config/v1alpha1/types_federationdomain.go | 177 ++++++++++++- .../config/v1alpha1/zz_generated.deepcopy.go | 140 ++++++++++ ...rvisor.pinniped.dev_federationdomains.yaml | 249 ++++++++++++++++++ generated/1.23/README.adoc | 122 +++++++++ .../config/v1alpha1/types_federationdomain.go | 177 ++++++++++++- .../config/v1alpha1/zz_generated.deepcopy.go | 140 ++++++++++ ...rvisor.pinniped.dev_federationdomains.yaml | 249 ++++++++++++++++++ generated/1.24/README.adoc | 122 +++++++++ .../config/v1alpha1/types_federationdomain.go | 177 ++++++++++++- .../config/v1alpha1/zz_generated.deepcopy.go | 140 ++++++++++ ...rvisor.pinniped.dev_federationdomains.yaml | 249 ++++++++++++++++++ generated/1.25/README.adoc | 122 +++++++++ .../config/v1alpha1/types_federationdomain.go | 177 ++++++++++++- .../config/v1alpha1/zz_generated.deepcopy.go | 140 ++++++++++ ...rvisor.pinniped.dev_federationdomains.yaml | 249 ++++++++++++++++++ generated/1.26/README.adoc | 122 +++++++++ .../config/v1alpha1/types_federationdomain.go | 177 ++++++++++++- .../config/v1alpha1/zz_generated.deepcopy.go | 140 ++++++++++ ...rvisor.pinniped.dev_federationdomains.yaml | 249 ++++++++++++++++++ .../config/v1alpha1/types_federationdomain.go | 177 ++++++++++++- .../config/v1alpha1/zz_generated.deepcopy.go | 140 ++++++++++ 44 files changed, 7611 insertions(+), 12 deletions(-) diff --git a/apis/supervisor/config/v1alpha1/types_federationdomain.go.tmpl b/apis/supervisor/config/v1alpha1/types_federationdomain.go.tmpl index 27de4401c0..7f1956845e 100644 --- a/apis/supervisor/config/v1alpha1/types_federationdomain.go.tmpl +++ b/apis/supervisor/config/v1alpha1/types_federationdomain.go.tmpl @@ -1,4 +1,4 @@ -// Copyright 2020-2022 the Pinniped contributors. All Rights Reserved. +// Copyright 2020-2023 the Pinniped contributors. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 package v1alpha1 @@ -42,6 +42,155 @@ type FederationDomainTLSSpec struct { SecretName string `json:"secretName,omitempty"` } +// FederationDomainTransformsConstant defines a constant variable and its value which will be made available to +// the transform expressions. This is a union type, and Type is the discriminator field. +type FederationDomainTransformsConstant struct { + // Name determines the name of the constant. It must be a valid identifier name. + // +kubebuilder:validation:Pattern=`^[a-zA-Z][_a-zA-Z0-9]*$` + // +kubebuilder:validation:MinLength=1 + // +kubebuilder:validation:MaxLength=64 + Name string `json:"name"` + + // Type determines the type of the constant, and indicates which other field should be non-empty. + // +kubebuilder:validation:Enum=string;stringList + Type string `json:"type"` + + // StringValue should hold the value when Type is "string", and is otherwise ignored. + // +optional + StringValue string `json:"stringValue,omitempty"` + + // StringListValue should hold the value when Type is "stringList", and is otherwise ignored. + // +optional + StringListValue []string `json:"stringListValue,omitempty"` +} + +// FederationDomainTransformsExpression defines a transform expression. +type FederationDomainTransformsExpression struct { + // Type determines the type of the expression. It must be one of the supported types. + // +kubebuilder:validation:Enum=policy/v1;username/v1;groups/v1 + Type string `json:"type"` + + // Expression is a CEL expression that will be evaluated based on the Type during an authentication. + // +kubebuilder:validation:MinLength=1 + Expression string `json:"expression"` + + // Message is only used when Type is policy/v1. It defines an error message to be used when the policy rejects + // an authentication attempt. When empty, a default message will be used. + // +optional + Message string `json:"message,omitempty"` +} + +// FederationDomainTransformsExample defines a transform example. +type FederationDomainTransformsExample struct { + // Username is the input username. + // +kubebuilder:validation:MinLength=1 + Username string `json:"username"` + + // Groups is the input list of group names. + // +optional + Groups []string `json:"groups,omitempty"` + + // Expects is the expected output of the entire sequence of transforms when they are run against the + // input Username and Groups. + Expects FederationDomainTransformsExampleExpects `json:"expects"` +} + +// FederationDomainTransformsExampleExpects defines the expected result for a transforms example. +type FederationDomainTransformsExampleExpects struct { + // Username is the expected username. + // +optional + Username string `json:"username,omitempty"` + + // Groups is the expected list of group names. + // +optional + Groups []string `json:"groups,omitempty"` + + // Rejected should be set to true when the expected result of the transforms is that a policy rejected the + // authentication attempt. + // +optional + Rejected bool `json:"rejected,omitempty"` + + // Message is the expected error message of the transforms. When Rejected is true, then Message is the expected + // message for the policy which rejected the authentication attempt. When Rejected is true and Message is blank, + // then Message will be treated as the default error message for authentication attempts which are rejected by a + // policy. When Rejected is false, then Message is the expected error message for some other non-policy + // transformation error, such as a runtime error. When Rejected is false, there is no default expected Message. + // +optional + Message string `json:"message,omitempty"` +} + +// FederationDomainTransforms defines identity transformations for an identity provider's usage on a FederationDomain. +type FederationDomainTransforms struct { + // Constants defines constant variables and their values which will be made available to the transform expressions. + // +patchMergeKey=name + // +patchStrategy=merge + // +listType=map + // +listMapKey=name + // +optional + Constants []FederationDomainTransformsConstant `json:"constants,omitempty"` + + // Expressions are an optional list of transforms and policies to be executed in the order given during every + // authentication attempt, including during every session refresh. + // Each is a CEL expression. It may use the basic CEL language as defined in + // https://github.com/google/cel-spec/blob/master/doc/langdef.md plus the CEL string extensions defined in + // https://github.com/google/cel-go/tree/master/ext#strings. + // + // The username and groups extracted from the identity provider, and the constants defined in this CR, are + // available as variables in all expressions. The username is provided via a variable called `username` and + // the list of group names is provided via a variable called `groups` (which may be an empty list). + // Each user-provided constants is provided via a variable named `strConst.varName` for string constants + // and `strListConst.varName` for string list constants. + // + // The only allowed types for expressions are currently policy/v1, username/v1, and groups/v1. + // Each policy/v1 must return a boolean, and when it returns false, no more expressions from the list are evaluated + // and the authentication attempt is rejected. + // Transformations of type policy/v1 do not return usernames or group names, and therefore cannot change the + // username or group names. + // Each username/v1 transform must return the new username (a string), which can be the same as the old username. + // Transformations of type username/v1 do not return group names, and therefore cannot change the group names. + // Each groups/v1 transform must return the new groups list (list of strings), which can be the same as the old + // groups list. + // Transformations of type groups/v1 do not return usernames, and therefore cannot change the usernames. + // After each expression, the new (potentially changed) username or groups get passed to the following expression. + // + // Any compilation or static type-checking failure of any expression will cause an error status on the FederationDomain. + // During an authentication attempt, any unexpected runtime evaluation errors (e.g. division by zero) cause the + // authentication attempt to fail. When all expressions evaluate successfully, then the (potentially changed) username + // and group names have been decided for that authentication attempt. + // + // +optional + Expressions []FederationDomainTransformsExpression `json:"expressions,omitempty"` + + // Examples can optionally be used to ensure that the sequence of transformation expressions are working as + // expected. Examples define sample input identities which are then run through the expression list, and the + // results are compared to the expected results. If any example in this list fails, then this + // FederationDomain will not be available for use, and the error(s) will be added to its status. + // This can be used to help guard against programming mistakes in the expressions, and also + // act as living documentation for other administrators to better understand the expressions. + // +optional + Examples []FederationDomainTransformsExample `json:"examples,omitempty"` +} + +// FederationDomainIdentityProvider describes how an identity provider is made available in this FederationDomain. +type FederationDomainIdentityProvider struct { + // DisplayName is the name of this identity provider as it will appear to clients. This name ends up in the + // kubeconfig of end users, so changing the name of an identity provider that is in use by end users will be a + // disruptive change for those users. + // +kubebuilder:validation:MinLength=1 + DisplayName string `json:"displayName"` + + // ObjectRef is a reference to a Pinniped identity provider resource. A valid reference is required. + // If the reference cannot be resolved then the identity provider will not be made available. + // Must refer to a resource of one of the Pinniped identity provider types, e.g. OIDCIdentityProvider, + // LDAPIdentityProvider, ActiveDirectoryIdentityProvider. + ObjectRef corev1.TypedLocalObjectReference `json:"objectRef"` + + // Transforms is an optional way to specify transformations to be applied during user authentication and + // session refresh. + // +optional + Transforms FederationDomainTransforms `json:"transforms,omitempty"` +} + // FederationDomainSpec is a struct that describes an OIDC Provider. type FederationDomainSpec struct { // Issuer is the OIDC Provider's issuer, per the OIDC Discovery Metadata document, as well as the @@ -58,6 +207,32 @@ type FederationDomainSpec struct { // TLS configures how this FederationDomain is served over Transport Layer Security (TLS). // +optional TLS *FederationDomainTLSSpec `json:"tls,omitempty"` + + // IdentityProviders is the list of identity providers available for use by this FederationDomain. + // + // An identity provider (e.g. OIDCIdentityProvider or LDAPIdentityProvider) describes how to connect to a server, + // how to talk in a specific protocol for authentication, and how to use the schema of that server/protocol to + // extract a normalized user identity. Normalized user identities include a username and a list of group names. + // In contrast, IdentityProviders describes how to use that normalized identity in a group of + // Kubernetes clusters. It can perform arbitrary transformations on that normalized identity. For example, a + // transformation can add a prefix to all usernames to help avoid accidental conflicts when multiple identity + // providers have different users with the same username (e.g. "idp1:ryan" versus "idp2:ryan"). + // IdentityProviders can also implement arbitrary authentication rejection policies. + // Even though a user was able to authenticate with the identity provider, a policy can disallow the authentication + // to the Kubernetes clusters that belong to this FederationDomain. For example, a policy could disallow the + // authentication unless the user belongs to a specific group in the identity provider. + // + // For backwards compatibility with versions of Pinniped which predate support for multiple identity providers, + // an empty IdentityProviders list will cause the FederationDomain to use all available identity providers which + // exist in the same namespace, but also to reject all authentication requests when there is more than one identity + // provider currently defined. In this backwards compatibility mode, the name of the identity provider resource + // (e.g. the Name of an OIDCIdentityProvider resource) will be used as the name of the identity provider in this + // FederationDomain. This mode is provided to make upgrading from older versions easier. However, instead of + // relying on this backwards compatibility mode, please consider this mode to be deprecated and please instead + // explicitly list the identity provider using this IdentityProviders field. + // + // +optional + IdentityProviders []FederationDomainIdentityProvider `json:"identityProviders,omitempty"` } // FederationDomainSecrets holds information about this OIDC Provider's secrets. diff --git a/deploy/supervisor/config.supervisor.pinniped.dev_federationdomains.yaml b/deploy/supervisor/config.supervisor.pinniped.dev_federationdomains.yaml index 71f7370d11..5e839fccd0 100644 --- a/deploy/supervisor/config.supervisor.pinniped.dev_federationdomains.yaml +++ b/deploy/supervisor/config.supervisor.pinniped.dev_federationdomains.yaml @@ -47,6 +47,255 @@ spec: spec: description: Spec of the OIDC provider. properties: + identityProviders: + description: "IdentityProviders is the list of identity providers + available for use by this FederationDomain. \n An identity provider + (e.g. OIDCIdentityProvider or LDAPIdentityProvider) describes how + to connect to a server, how to talk in a specific protocol for authentication, + and how to use the schema of that server/protocol to extract a normalized + user identity. Normalized user identities include a username and + a list of group names. In contrast, IdentityProviders describes + how to use that normalized identity in a group of Kubernetes clusters. + It can perform arbitrary transformations on that normalized identity. + For example, a transformation can add a prefix to all usernames + to help avoid accidental conflicts when multiple identity providers + have different users with the same username (e.g. \"idp1:ryan\" + versus \"idp2:ryan\"). IdentityProviders can also implement arbitrary + authentication rejection policies. Even though a user was able to + authenticate with the identity provider, a policy can disallow the + authentication to the Kubernetes clusters that belong to this FederationDomain. + For example, a policy could disallow the authentication unless the + user belongs to a specific group in the identity provider. \n For + backwards compatibility with versions of Pinniped which predate + support for multiple identity providers, an empty IdentityProviders + list will cause the FederationDomain to use all available identity + providers which exist in the same namespace, but also to reject + all authentication requests when there is more than one identity + provider currently defined. In this backwards compatibility mode, + the name of the identity provider resource (e.g. the Name of an + OIDCIdentityProvider resource) will be used as the name of the identity + provider in this FederationDomain. This mode is provided to make + upgrading from older versions easier. However, instead of relying + on this backwards compatibility mode, please consider this mode + to be deprecated and please instead explicitly list the identity + provider using this IdentityProviders field." + items: + description: FederationDomainIdentityProvider describes how an identity + provider is made available in this FederationDomain. + properties: + displayName: + description: DisplayName is the name of this identity provider + as it will appear to clients. This name ends up in the kubeconfig + of end users, so changing the name of an identity provider + that is in use by end users will be a disruptive change for + those users. + minLength: 1 + type: string + objectRef: + description: ObjectRef is a reference to a Pinniped identity + provider resource. A valid reference is required. If the reference + cannot be resolved then the identity provider will not be + made available. Must refer to a resource of one of the Pinniped + identity provider types, e.g. OIDCIdentityProvider, LDAPIdentityProvider, + ActiveDirectoryIdentityProvider. + properties: + apiGroup: + description: APIGroup is the group for the resource being + referenced. If APIGroup is not specified, the specified + Kind must be in the core API group. For any other third-party + types, APIGroup is required. + type: string + kind: + description: Kind is the type of resource being referenced + type: string + name: + description: Name is the name of resource being referenced + type: string + required: + - kind + - name + type: object + transforms: + description: Transforms is an optional way to specify transformations + to be applied during user authentication and session refresh. + properties: + constants: + description: Constants defines constant variables and their + values which will be made available to the transform expressions. + items: + description: FederationDomainTransformsConstant defines + a constant variable and its value which will be made + available to the transform expressions. This is a union + type, and Type is the discriminator field. + properties: + name: + description: Name determines the name of the constant. + It must be a valid identifier name. + maxLength: 64 + minLength: 1 + pattern: ^[a-zA-Z][_a-zA-Z0-9]*$ + type: string + stringListValue: + description: StringListValue should hold the value + when Type is "stringList", and is otherwise ignored. + items: + type: string + type: array + stringValue: + description: StringValue should hold the value when + Type is "string", and is otherwise ignored. + type: string + type: + description: Type determines the type of the constant, + and indicates which other field should be non-empty. + enum: + - string + - stringList + type: string + required: + - name + - type + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + examples: + description: Examples can optionally be used to ensure that + the sequence of transformation expressions are working + as expected. Examples define sample input identities which + are then run through the expression list, and the results + are compared to the expected results. If any example in + this list fails, then this FederationDomain will not be + available for use, and the error(s) will be added to its + status. This can be used to help guard against programming + mistakes in the expressions, and also act as living documentation + for other administrators to better understand the expressions. + items: + description: FederationDomainTransformsExample defines + a transform example. + properties: + expects: + description: Expects is the expected output of the + entire sequence of transforms when they are run + against the input Username and Groups. + properties: + groups: + description: Groups is the expected list of group + names. + items: + type: string + type: array + message: + description: Message is the expected error message + of the transforms. When Rejected is true, then + Message is the expected message for the policy + which rejected the authentication attempt. When + Rejected is true and Message is blank, then + Message will be treated as the default error + message for authentication attempts which are + rejected by a policy. When Rejected is false, + then Message is the expected error message for + some other non-policy transformation error, + such as a runtime error. When Rejected is false, + there is no default expected Message. + type: string + rejected: + description: Rejected should be set to true when + the expected result of the transforms is that + a policy rejected the authentication attempt. + type: boolean + username: + description: Username is the expected username. + type: string + type: object + groups: + description: Groups is the input list of group names. + items: + type: string + type: array + username: + description: Username is the input username. + minLength: 1 + type: string + required: + - expects + - username + type: object + type: array + expressions: + description: "Expressions are an optional list of transforms + and policies to be executed in the order given during + every authentication attempt, including during every session + refresh. Each is a CEL expression. It may use the basic + CEL language as defined in https://github.com/google/cel-spec/blob/master/doc/langdef.md + plus the CEL string extensions defined in https://github.com/google/cel-go/tree/master/ext#strings. + \n The username and groups extracted from the identity + provider, and the constants defined in this CR, are available + as variables in all expressions. The username is provided + via a variable called `username` and the list of group + names is provided via a variable called `groups` (which + may be an empty list). Each user-provided constants is + provided via a variable named `strConst.varName` for string + constants and `strListConst.varName` for string list constants. + \n The only allowed types for expressions are currently + policy/v1, username/v1, and groups/v1. Each policy/v1 + must return a boolean, and when it returns false, no more + expressions from the list are evaluated and the authentication + attempt is rejected. Transformations of type policy/v1 + do not return usernames or group names, and therefore + cannot change the username or group names. Each username/v1 + transform must return the new username (a string), which + can be the same as the old username. Transformations of + type username/v1 do not return group names, and therefore + cannot change the group names. Each groups/v1 transform + must return the new groups list (list of strings), which + can be the same as the old groups list. Transformations + of type groups/v1 do not return usernames, and therefore + cannot change the usernames. After each expression, the + new (potentially changed) username or groups get passed + to the following expression. \n Any compilation or static + type-checking failure of any expression will cause an + error status on the FederationDomain. During an authentication + attempt, any unexpected runtime evaluation errors (e.g. + division by zero) cause the authentication attempt to + fail. When all expressions evaluate successfully, then + the (potentially changed) username and group names have + been decided for that authentication attempt." + items: + description: FederationDomainTransformsExpression defines + a transform expression. + properties: + expression: + description: Expression is a CEL expression that will + be evaluated based on the Type during an authentication. + minLength: 1 + type: string + message: + description: Message is only used when Type is policy/v1. + It defines an error message to be used when the + policy rejects an authentication attempt. When empty, + a default message will be used. + type: string + type: + description: Type determines the type of the expression. + It must be one of the supported types. + enum: + - policy/v1 + - username/v1 + - groups/v1 + type: string + required: + - expression + - type + type: object + type: array + type: object + required: + - displayName + - objectRef + type: object + type: array issuer: description: "Issuer is the OIDC Provider's issuer, per the OIDC Discovery Metadata document, as well as the identifier that it will use for diff --git a/generated/1.17/README.adoc b/generated/1.17/README.adoc index a7f8a6228f..65d95ba877 100644 --- a/generated/1.17/README.adoc +++ b/generated/1.17/README.adoc @@ -639,6 +639,25 @@ FederationDomain describes the configuration of an OIDC provider. |=== +[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-config-v1alpha1-federationdomainidentityprovider"] +==== FederationDomainIdentityProvider + +FederationDomainIdentityProvider describes how an identity provider is made available in this FederationDomain. + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-config-v1alpha1-federationdomainspec[$$FederationDomainSpec$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description +| *`displayName`* __string__ | DisplayName is the name of this identity provider as it will appear to clients. This name ends up in the kubeconfig of end users, so changing the name of an identity provider that is in use by end users will be a disruptive change for those users. +| *`objectRef`* __link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.17/#typedlocalobjectreference-v1-core[$$TypedLocalObjectReference$$]__ | ObjectRef is a reference to a Pinniped identity provider resource. A valid reference is required. If the reference cannot be resolved then the identity provider will not be made available. Must refer to a resource of one of the Pinniped identity provider types, e.g. OIDCIdentityProvider, LDAPIdentityProvider, ActiveDirectoryIdentityProvider. +| *`transforms`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-config-v1alpha1-federationdomaintransforms[$$FederationDomainTransforms$$]__ | Transforms is an optional way to specify transformations to be applied during user authentication and session refresh. +|=== + + [id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-config-v1alpha1-federationdomainsecrets"] @@ -677,6 +696,9 @@ FederationDomainSpec is a struct that describes an OIDC Provider. | *`issuer`* __string__ | Issuer is the OIDC Provider's issuer, per the OIDC Discovery Metadata document, as well as the identifier that it will use for the iss claim in issued JWTs. This field will also be used as the base URL for any endpoints used by the OIDC Provider (e.g., if your issuer is https://example.com/foo, then your authorization endpoint will look like https://example.com/foo/some/path/to/auth/endpoint). See https://openid.net/specs/openid-connect-discovery-1_0.html#rfc.section.3 for more information. | *`tls`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-config-v1alpha1-federationdomaintlsspec[$$FederationDomainTLSSpec$$]__ | TLS configures how this FederationDomain is served over Transport Layer Security (TLS). +| *`identityProviders`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-config-v1alpha1-federationdomainidentityprovider[$$FederationDomainIdentityProvider$$] array__ | IdentityProviders is the list of identity providers available for use by this FederationDomain. + An identity provider (e.g. OIDCIdentityProvider or LDAPIdentityProvider) describes how to connect to a server, how to talk in a specific protocol for authentication, and how to use the schema of that server/protocol to extract a normalized user identity. Normalized user identities include a username and a list of group names. In contrast, IdentityProviders describes how to use that normalized identity in a group of Kubernetes clusters. It can perform arbitrary transformations on that normalized identity. For example, a transformation can add a prefix to all usernames to help avoid accidental conflicts when multiple identity providers have different users with the same username (e.g. "idp1:ryan" versus "idp2:ryan"). IdentityProviders can also implement arbitrary authentication rejection policies. Even though a user was able to authenticate with the identity provider, a policy can disallow the authentication to the Kubernetes clusters that belong to this FederationDomain. For example, a policy could disallow the authentication unless the user belongs to a specific group in the identity provider. + For backwards compatibility with versions of Pinniped which predate support for multiple identity providers, an empty IdentityProviders list will cause the FederationDomain to use all available identity providers which exist in the same namespace, but also to reject all authentication requests when there is more than one identity provider currently defined. In this backwards compatibility mode, the name of the identity provider resource (e.g. the Name of an OIDCIdentityProvider resource) will be used as the name of the identity provider in this FederationDomain. This mode is provided to make upgrading from older versions easier. However, instead of relying on this backwards compatibility mode, please consider this mode to be deprecated and please instead explicitly list the identity provider using this IdentityProviders field. |=== @@ -721,6 +743,106 @@ FederationDomainTLSSpec is a struct that describes the TLS configuration for an |=== +[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-config-v1alpha1-federationdomaintransforms"] +==== FederationDomainTransforms + +FederationDomainTransforms defines identity transformations for an identity provider's usage on a FederationDomain. + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-config-v1alpha1-federationdomainidentityprovider[$$FederationDomainIdentityProvider$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description +| *`constants`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-config-v1alpha1-federationdomaintransformsconstant[$$FederationDomainTransformsConstant$$] array__ | Constants defines constant variables and their values which will be made available to the transform expressions. +| *`expressions`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-config-v1alpha1-federationdomaintransformsexpression[$$FederationDomainTransformsExpression$$] array__ | Expressions are an optional list of transforms and policies to be executed in the order given during every authentication attempt, including during every session refresh. Each is a CEL expression. It may use the basic CEL language as defined in https://github.com/google/cel-spec/blob/master/doc/langdef.md plus the CEL string extensions defined in https://github.com/google/cel-go/tree/master/ext#strings. + The username and groups extracted from the identity provider, and the constants defined in this CR, are available as variables in all expressions. The username is provided via a variable called `username` and the list of group names is provided via a variable called `groups` (which may be an empty list). Each user-provided constants is provided via a variable named `strConst.varName` for string constants and `strListConst.varName` for string list constants. + The only allowed types for expressions are currently policy/v1, username/v1, and groups/v1. Each policy/v1 must return a boolean, and when it returns false, no more expressions from the list are evaluated and the authentication attempt is rejected. Transformations of type policy/v1 do not return usernames or group names, and therefore cannot change the username or group names. Each username/v1 transform must return the new username (a string), which can be the same as the old username. Transformations of type username/v1 do not return group names, and therefore cannot change the group names. Each groups/v1 transform must return the new groups list (list of strings), which can be the same as the old groups list. Transformations of type groups/v1 do not return usernames, and therefore cannot change the usernames. After each expression, the new (potentially changed) username or groups get passed to the following expression. + Any compilation or static type-checking failure of any expression will cause an error status on the FederationDomain. During an authentication attempt, any unexpected runtime evaluation errors (e.g. division by zero) cause the authentication attempt to fail. When all expressions evaluate successfully, then the (potentially changed) username and group names have been decided for that authentication attempt. +| *`examples`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-config-v1alpha1-federationdomaintransformsexample[$$FederationDomainTransformsExample$$] array__ | Examples can optionally be used to ensure that the sequence of transformation expressions are working as expected. Examples define sample input identities which are then run through the expression list, and the results are compared to the expected results. If any example in this list fails, then this FederationDomain will not be available for use, and the error(s) will be added to its status. This can be used to help guard against programming mistakes in the expressions, and also act as living documentation for other administrators to better understand the expressions. +|=== + + +[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-config-v1alpha1-federationdomaintransformsconstant"] +==== FederationDomainTransformsConstant + +FederationDomainTransformsConstant defines a constant variable and its value which will be made available to the transform expressions. This is a union type, and Type is the discriminator field. + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-config-v1alpha1-federationdomaintransforms[$$FederationDomainTransforms$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description +| *`name`* __string__ | Name determines the name of the constant. It must be a valid identifier name. +| *`type`* __string__ | Type determines the type of the constant, and indicates which other field should be non-empty. +| *`stringValue`* __string__ | StringValue should hold the value when Type is "string", and is otherwise ignored. +| *`stringListValue`* __string array__ | StringListValue should hold the value when Type is "stringList", and is otherwise ignored. +|=== + + +[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-config-v1alpha1-federationdomaintransformsexample"] +==== FederationDomainTransformsExample + +FederationDomainTransformsExample defines a transform example. + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-config-v1alpha1-federationdomaintransforms[$$FederationDomainTransforms$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description +| *`username`* __string__ | Username is the input username. +| *`groups`* __string array__ | Groups is the input list of group names. +| *`expects`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-config-v1alpha1-federationdomaintransformsexampleexpects[$$FederationDomainTransformsExampleExpects$$]__ | Expects is the expected output of the entire sequence of transforms when they are run against the input Username and Groups. +|=== + + +[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-config-v1alpha1-federationdomaintransformsexampleexpects"] +==== FederationDomainTransformsExampleExpects + +FederationDomainTransformsExampleExpects defines the expected result for a transforms example. + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-config-v1alpha1-federationdomaintransformsexample[$$FederationDomainTransformsExample$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description +| *`username`* __string__ | Username is the expected username. +| *`groups`* __string array__ | Groups is the expected list of group names. +| *`rejected`* __boolean__ | Rejected should be set to true when the expected result of the transforms is that a policy rejected the authentication attempt. +| *`message`* __string__ | Message is the expected error message of the transforms. When Rejected is true, then Message is the expected message for the policy which rejected the authentication attempt. When Rejected is true and Message is blank, then Message will be treated as the default error message for authentication attempts which are rejected by a policy. When Rejected is false, then Message is the expected error message for some other non-policy transformation error, such as a runtime error. When Rejected is false, there is no default expected Message. +|=== + + +[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-config-v1alpha1-federationdomaintransformsexpression"] +==== FederationDomainTransformsExpression + +FederationDomainTransformsExpression defines a transform expression. + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-config-v1alpha1-federationdomaintransforms[$$FederationDomainTransforms$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description +| *`type`* __string__ | Type determines the type of the expression. It must be one of the supported types. +| *`expression`* __string__ | Expression is a CEL expression that will be evaluated based on the Type during an authentication. +| *`message`* __string__ | Message is only used when Type is policy/v1. It defines an error message to be used when the policy rejects an authentication attempt. When empty, a default message will be used. +|=== + + [id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-config-v1alpha1-oidcclient"] ==== OIDCClient diff --git a/generated/1.17/apis/supervisor/config/v1alpha1/types_federationdomain.go b/generated/1.17/apis/supervisor/config/v1alpha1/types_federationdomain.go index 27de4401c0..7f1956845e 100644 --- a/generated/1.17/apis/supervisor/config/v1alpha1/types_federationdomain.go +++ b/generated/1.17/apis/supervisor/config/v1alpha1/types_federationdomain.go @@ -1,4 +1,4 @@ -// Copyright 2020-2022 the Pinniped contributors. All Rights Reserved. +// Copyright 2020-2023 the Pinniped contributors. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 package v1alpha1 @@ -42,6 +42,155 @@ type FederationDomainTLSSpec struct { SecretName string `json:"secretName,omitempty"` } +// FederationDomainTransformsConstant defines a constant variable and its value which will be made available to +// the transform expressions. This is a union type, and Type is the discriminator field. +type FederationDomainTransformsConstant struct { + // Name determines the name of the constant. It must be a valid identifier name. + // +kubebuilder:validation:Pattern=`^[a-zA-Z][_a-zA-Z0-9]*$` + // +kubebuilder:validation:MinLength=1 + // +kubebuilder:validation:MaxLength=64 + Name string `json:"name"` + + // Type determines the type of the constant, and indicates which other field should be non-empty. + // +kubebuilder:validation:Enum=string;stringList + Type string `json:"type"` + + // StringValue should hold the value when Type is "string", and is otherwise ignored. + // +optional + StringValue string `json:"stringValue,omitempty"` + + // StringListValue should hold the value when Type is "stringList", and is otherwise ignored. + // +optional + StringListValue []string `json:"stringListValue,omitempty"` +} + +// FederationDomainTransformsExpression defines a transform expression. +type FederationDomainTransformsExpression struct { + // Type determines the type of the expression. It must be one of the supported types. + // +kubebuilder:validation:Enum=policy/v1;username/v1;groups/v1 + Type string `json:"type"` + + // Expression is a CEL expression that will be evaluated based on the Type during an authentication. + // +kubebuilder:validation:MinLength=1 + Expression string `json:"expression"` + + // Message is only used when Type is policy/v1. It defines an error message to be used when the policy rejects + // an authentication attempt. When empty, a default message will be used. + // +optional + Message string `json:"message,omitempty"` +} + +// FederationDomainTransformsExample defines a transform example. +type FederationDomainTransformsExample struct { + // Username is the input username. + // +kubebuilder:validation:MinLength=1 + Username string `json:"username"` + + // Groups is the input list of group names. + // +optional + Groups []string `json:"groups,omitempty"` + + // Expects is the expected output of the entire sequence of transforms when they are run against the + // input Username and Groups. + Expects FederationDomainTransformsExampleExpects `json:"expects"` +} + +// FederationDomainTransformsExampleExpects defines the expected result for a transforms example. +type FederationDomainTransformsExampleExpects struct { + // Username is the expected username. + // +optional + Username string `json:"username,omitempty"` + + // Groups is the expected list of group names. + // +optional + Groups []string `json:"groups,omitempty"` + + // Rejected should be set to true when the expected result of the transforms is that a policy rejected the + // authentication attempt. + // +optional + Rejected bool `json:"rejected,omitempty"` + + // Message is the expected error message of the transforms. When Rejected is true, then Message is the expected + // message for the policy which rejected the authentication attempt. When Rejected is true and Message is blank, + // then Message will be treated as the default error message for authentication attempts which are rejected by a + // policy. When Rejected is false, then Message is the expected error message for some other non-policy + // transformation error, such as a runtime error. When Rejected is false, there is no default expected Message. + // +optional + Message string `json:"message,omitempty"` +} + +// FederationDomainTransforms defines identity transformations for an identity provider's usage on a FederationDomain. +type FederationDomainTransforms struct { + // Constants defines constant variables and their values which will be made available to the transform expressions. + // +patchMergeKey=name + // +patchStrategy=merge + // +listType=map + // +listMapKey=name + // +optional + Constants []FederationDomainTransformsConstant `json:"constants,omitempty"` + + // Expressions are an optional list of transforms and policies to be executed in the order given during every + // authentication attempt, including during every session refresh. + // Each is a CEL expression. It may use the basic CEL language as defined in + // https://github.com/google/cel-spec/blob/master/doc/langdef.md plus the CEL string extensions defined in + // https://github.com/google/cel-go/tree/master/ext#strings. + // + // The username and groups extracted from the identity provider, and the constants defined in this CR, are + // available as variables in all expressions. The username is provided via a variable called `username` and + // the list of group names is provided via a variable called `groups` (which may be an empty list). + // Each user-provided constants is provided via a variable named `strConst.varName` for string constants + // and `strListConst.varName` for string list constants. + // + // The only allowed types for expressions are currently policy/v1, username/v1, and groups/v1. + // Each policy/v1 must return a boolean, and when it returns false, no more expressions from the list are evaluated + // and the authentication attempt is rejected. + // Transformations of type policy/v1 do not return usernames or group names, and therefore cannot change the + // username or group names. + // Each username/v1 transform must return the new username (a string), which can be the same as the old username. + // Transformations of type username/v1 do not return group names, and therefore cannot change the group names. + // Each groups/v1 transform must return the new groups list (list of strings), which can be the same as the old + // groups list. + // Transformations of type groups/v1 do not return usernames, and therefore cannot change the usernames. + // After each expression, the new (potentially changed) username or groups get passed to the following expression. + // + // Any compilation or static type-checking failure of any expression will cause an error status on the FederationDomain. + // During an authentication attempt, any unexpected runtime evaluation errors (e.g. division by zero) cause the + // authentication attempt to fail. When all expressions evaluate successfully, then the (potentially changed) username + // and group names have been decided for that authentication attempt. + // + // +optional + Expressions []FederationDomainTransformsExpression `json:"expressions,omitempty"` + + // Examples can optionally be used to ensure that the sequence of transformation expressions are working as + // expected. Examples define sample input identities which are then run through the expression list, and the + // results are compared to the expected results. If any example in this list fails, then this + // FederationDomain will not be available for use, and the error(s) will be added to its status. + // This can be used to help guard against programming mistakes in the expressions, and also + // act as living documentation for other administrators to better understand the expressions. + // +optional + Examples []FederationDomainTransformsExample `json:"examples,omitempty"` +} + +// FederationDomainIdentityProvider describes how an identity provider is made available in this FederationDomain. +type FederationDomainIdentityProvider struct { + // DisplayName is the name of this identity provider as it will appear to clients. This name ends up in the + // kubeconfig of end users, so changing the name of an identity provider that is in use by end users will be a + // disruptive change for those users. + // +kubebuilder:validation:MinLength=1 + DisplayName string `json:"displayName"` + + // ObjectRef is a reference to a Pinniped identity provider resource. A valid reference is required. + // If the reference cannot be resolved then the identity provider will not be made available. + // Must refer to a resource of one of the Pinniped identity provider types, e.g. OIDCIdentityProvider, + // LDAPIdentityProvider, ActiveDirectoryIdentityProvider. + ObjectRef corev1.TypedLocalObjectReference `json:"objectRef"` + + // Transforms is an optional way to specify transformations to be applied during user authentication and + // session refresh. + // +optional + Transforms FederationDomainTransforms `json:"transforms,omitempty"` +} + // FederationDomainSpec is a struct that describes an OIDC Provider. type FederationDomainSpec struct { // Issuer is the OIDC Provider's issuer, per the OIDC Discovery Metadata document, as well as the @@ -58,6 +207,32 @@ type FederationDomainSpec struct { // TLS configures how this FederationDomain is served over Transport Layer Security (TLS). // +optional TLS *FederationDomainTLSSpec `json:"tls,omitempty"` + + // IdentityProviders is the list of identity providers available for use by this FederationDomain. + // + // An identity provider (e.g. OIDCIdentityProvider or LDAPIdentityProvider) describes how to connect to a server, + // how to talk in a specific protocol for authentication, and how to use the schema of that server/protocol to + // extract a normalized user identity. Normalized user identities include a username and a list of group names. + // In contrast, IdentityProviders describes how to use that normalized identity in a group of + // Kubernetes clusters. It can perform arbitrary transformations on that normalized identity. For example, a + // transformation can add a prefix to all usernames to help avoid accidental conflicts when multiple identity + // providers have different users with the same username (e.g. "idp1:ryan" versus "idp2:ryan"). + // IdentityProviders can also implement arbitrary authentication rejection policies. + // Even though a user was able to authenticate with the identity provider, a policy can disallow the authentication + // to the Kubernetes clusters that belong to this FederationDomain. For example, a policy could disallow the + // authentication unless the user belongs to a specific group in the identity provider. + // + // For backwards compatibility with versions of Pinniped which predate support for multiple identity providers, + // an empty IdentityProviders list will cause the FederationDomain to use all available identity providers which + // exist in the same namespace, but also to reject all authentication requests when there is more than one identity + // provider currently defined. In this backwards compatibility mode, the name of the identity provider resource + // (e.g. the Name of an OIDCIdentityProvider resource) will be used as the name of the identity provider in this + // FederationDomain. This mode is provided to make upgrading from older versions easier. However, instead of + // relying on this backwards compatibility mode, please consider this mode to be deprecated and please instead + // explicitly list the identity provider using this IdentityProviders field. + // + // +optional + IdentityProviders []FederationDomainIdentityProvider `json:"identityProviders,omitempty"` } // FederationDomainSecrets holds information about this OIDC Provider's secrets. diff --git a/generated/1.17/apis/supervisor/config/v1alpha1/zz_generated.deepcopy.go b/generated/1.17/apis/supervisor/config/v1alpha1/zz_generated.deepcopy.go index 682013782a..5e48e41556 100644 --- a/generated/1.17/apis/supervisor/config/v1alpha1/zz_generated.deepcopy.go +++ b/generated/1.17/apis/supervisor/config/v1alpha1/zz_generated.deepcopy.go @@ -57,6 +57,24 @@ func (in *FederationDomain) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FederationDomainIdentityProvider) DeepCopyInto(out *FederationDomainIdentityProvider) { + *out = *in + in.ObjectRef.DeepCopyInto(&out.ObjectRef) + in.Transforms.DeepCopyInto(&out.Transforms) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FederationDomainIdentityProvider. +func (in *FederationDomainIdentityProvider) DeepCopy() *FederationDomainIdentityProvider { + if in == nil { + return nil + } + out := new(FederationDomainIdentityProvider) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *FederationDomainList) DeepCopyInto(out *FederationDomainList) { *out = *in @@ -118,6 +136,13 @@ func (in *FederationDomainSpec) DeepCopyInto(out *FederationDomainSpec) { *out = new(FederationDomainTLSSpec) **out = **in } + if in.IdentityProviders != nil { + in, out := &in.IdentityProviders, &out.IdentityProviders + *out = make([]FederationDomainIdentityProvider, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } return } @@ -168,6 +193,121 @@ func (in *FederationDomainTLSSpec) DeepCopy() *FederationDomainTLSSpec { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FederationDomainTransforms) DeepCopyInto(out *FederationDomainTransforms) { + *out = *in + if in.Constants != nil { + in, out := &in.Constants, &out.Constants + *out = make([]FederationDomainTransformsConstant, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.Expressions != nil { + in, out := &in.Expressions, &out.Expressions + *out = make([]FederationDomainTransformsExpression, len(*in)) + copy(*out, *in) + } + if in.Examples != nil { + in, out := &in.Examples, &out.Examples + *out = make([]FederationDomainTransformsExample, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FederationDomainTransforms. +func (in *FederationDomainTransforms) DeepCopy() *FederationDomainTransforms { + if in == nil { + return nil + } + out := new(FederationDomainTransforms) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FederationDomainTransformsConstant) DeepCopyInto(out *FederationDomainTransformsConstant) { + *out = *in + if in.StringListValue != nil { + in, out := &in.StringListValue, &out.StringListValue + *out = make([]string, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FederationDomainTransformsConstant. +func (in *FederationDomainTransformsConstant) DeepCopy() *FederationDomainTransformsConstant { + if in == nil { + return nil + } + out := new(FederationDomainTransformsConstant) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FederationDomainTransformsExample) DeepCopyInto(out *FederationDomainTransformsExample) { + *out = *in + if in.Groups != nil { + in, out := &in.Groups, &out.Groups + *out = make([]string, len(*in)) + copy(*out, *in) + } + in.Expects.DeepCopyInto(&out.Expects) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FederationDomainTransformsExample. +func (in *FederationDomainTransformsExample) DeepCopy() *FederationDomainTransformsExample { + if in == nil { + return nil + } + out := new(FederationDomainTransformsExample) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FederationDomainTransformsExampleExpects) DeepCopyInto(out *FederationDomainTransformsExampleExpects) { + *out = *in + if in.Groups != nil { + in, out := &in.Groups, &out.Groups + *out = make([]string, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FederationDomainTransformsExampleExpects. +func (in *FederationDomainTransformsExampleExpects) DeepCopy() *FederationDomainTransformsExampleExpects { + if in == nil { + return nil + } + out := new(FederationDomainTransformsExampleExpects) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FederationDomainTransformsExpression) DeepCopyInto(out *FederationDomainTransformsExpression) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FederationDomainTransformsExpression. +func (in *FederationDomainTransformsExpression) DeepCopy() *FederationDomainTransformsExpression { + if in == nil { + return nil + } + out := new(FederationDomainTransformsExpression) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *OIDCClient) DeepCopyInto(out *OIDCClient) { *out = *in diff --git a/generated/1.17/crds/config.supervisor.pinniped.dev_federationdomains.yaml b/generated/1.17/crds/config.supervisor.pinniped.dev_federationdomains.yaml index 71f7370d11..5e839fccd0 100644 --- a/generated/1.17/crds/config.supervisor.pinniped.dev_federationdomains.yaml +++ b/generated/1.17/crds/config.supervisor.pinniped.dev_federationdomains.yaml @@ -47,6 +47,255 @@ spec: spec: description: Spec of the OIDC provider. properties: + identityProviders: + description: "IdentityProviders is the list of identity providers + available for use by this FederationDomain. \n An identity provider + (e.g. OIDCIdentityProvider or LDAPIdentityProvider) describes how + to connect to a server, how to talk in a specific protocol for authentication, + and how to use the schema of that server/protocol to extract a normalized + user identity. Normalized user identities include a username and + a list of group names. In contrast, IdentityProviders describes + how to use that normalized identity in a group of Kubernetes clusters. + It can perform arbitrary transformations on that normalized identity. + For example, a transformation can add a prefix to all usernames + to help avoid accidental conflicts when multiple identity providers + have different users with the same username (e.g. \"idp1:ryan\" + versus \"idp2:ryan\"). IdentityProviders can also implement arbitrary + authentication rejection policies. Even though a user was able to + authenticate with the identity provider, a policy can disallow the + authentication to the Kubernetes clusters that belong to this FederationDomain. + For example, a policy could disallow the authentication unless the + user belongs to a specific group in the identity provider. \n For + backwards compatibility with versions of Pinniped which predate + support for multiple identity providers, an empty IdentityProviders + list will cause the FederationDomain to use all available identity + providers which exist in the same namespace, but also to reject + all authentication requests when there is more than one identity + provider currently defined. In this backwards compatibility mode, + the name of the identity provider resource (e.g. the Name of an + OIDCIdentityProvider resource) will be used as the name of the identity + provider in this FederationDomain. This mode is provided to make + upgrading from older versions easier. However, instead of relying + on this backwards compatibility mode, please consider this mode + to be deprecated and please instead explicitly list the identity + provider using this IdentityProviders field." + items: + description: FederationDomainIdentityProvider describes how an identity + provider is made available in this FederationDomain. + properties: + displayName: + description: DisplayName is the name of this identity provider + as it will appear to clients. This name ends up in the kubeconfig + of end users, so changing the name of an identity provider + that is in use by end users will be a disruptive change for + those users. + minLength: 1 + type: string + objectRef: + description: ObjectRef is a reference to a Pinniped identity + provider resource. A valid reference is required. If the reference + cannot be resolved then the identity provider will not be + made available. Must refer to a resource of one of the Pinniped + identity provider types, e.g. OIDCIdentityProvider, LDAPIdentityProvider, + ActiveDirectoryIdentityProvider. + properties: + apiGroup: + description: APIGroup is the group for the resource being + referenced. If APIGroup is not specified, the specified + Kind must be in the core API group. For any other third-party + types, APIGroup is required. + type: string + kind: + description: Kind is the type of resource being referenced + type: string + name: + description: Name is the name of resource being referenced + type: string + required: + - kind + - name + type: object + transforms: + description: Transforms is an optional way to specify transformations + to be applied during user authentication and session refresh. + properties: + constants: + description: Constants defines constant variables and their + values which will be made available to the transform expressions. + items: + description: FederationDomainTransformsConstant defines + a constant variable and its value which will be made + available to the transform expressions. This is a union + type, and Type is the discriminator field. + properties: + name: + description: Name determines the name of the constant. + It must be a valid identifier name. + maxLength: 64 + minLength: 1 + pattern: ^[a-zA-Z][_a-zA-Z0-9]*$ + type: string + stringListValue: + description: StringListValue should hold the value + when Type is "stringList", and is otherwise ignored. + items: + type: string + type: array + stringValue: + description: StringValue should hold the value when + Type is "string", and is otherwise ignored. + type: string + type: + description: Type determines the type of the constant, + and indicates which other field should be non-empty. + enum: + - string + - stringList + type: string + required: + - name + - type + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + examples: + description: Examples can optionally be used to ensure that + the sequence of transformation expressions are working + as expected. Examples define sample input identities which + are then run through the expression list, and the results + are compared to the expected results. If any example in + this list fails, then this FederationDomain will not be + available for use, and the error(s) will be added to its + status. This can be used to help guard against programming + mistakes in the expressions, and also act as living documentation + for other administrators to better understand the expressions. + items: + description: FederationDomainTransformsExample defines + a transform example. + properties: + expects: + description: Expects is the expected output of the + entire sequence of transforms when they are run + against the input Username and Groups. + properties: + groups: + description: Groups is the expected list of group + names. + items: + type: string + type: array + message: + description: Message is the expected error message + of the transforms. When Rejected is true, then + Message is the expected message for the policy + which rejected the authentication attempt. When + Rejected is true and Message is blank, then + Message will be treated as the default error + message for authentication attempts which are + rejected by a policy. When Rejected is false, + then Message is the expected error message for + some other non-policy transformation error, + such as a runtime error. When Rejected is false, + there is no default expected Message. + type: string + rejected: + description: Rejected should be set to true when + the expected result of the transforms is that + a policy rejected the authentication attempt. + type: boolean + username: + description: Username is the expected username. + type: string + type: object + groups: + description: Groups is the input list of group names. + items: + type: string + type: array + username: + description: Username is the input username. + minLength: 1 + type: string + required: + - expects + - username + type: object + type: array + expressions: + description: "Expressions are an optional list of transforms + and policies to be executed in the order given during + every authentication attempt, including during every session + refresh. Each is a CEL expression. It may use the basic + CEL language as defined in https://github.com/google/cel-spec/blob/master/doc/langdef.md + plus the CEL string extensions defined in https://github.com/google/cel-go/tree/master/ext#strings. + \n The username and groups extracted from the identity + provider, and the constants defined in this CR, are available + as variables in all expressions. The username is provided + via a variable called `username` and the list of group + names is provided via a variable called `groups` (which + may be an empty list). Each user-provided constants is + provided via a variable named `strConst.varName` for string + constants and `strListConst.varName` for string list constants. + \n The only allowed types for expressions are currently + policy/v1, username/v1, and groups/v1. Each policy/v1 + must return a boolean, and when it returns false, no more + expressions from the list are evaluated and the authentication + attempt is rejected. Transformations of type policy/v1 + do not return usernames or group names, and therefore + cannot change the username or group names. Each username/v1 + transform must return the new username (a string), which + can be the same as the old username. Transformations of + type username/v1 do not return group names, and therefore + cannot change the group names. Each groups/v1 transform + must return the new groups list (list of strings), which + can be the same as the old groups list. Transformations + of type groups/v1 do not return usernames, and therefore + cannot change the usernames. After each expression, the + new (potentially changed) username or groups get passed + to the following expression. \n Any compilation or static + type-checking failure of any expression will cause an + error status on the FederationDomain. During an authentication + attempt, any unexpected runtime evaluation errors (e.g. + division by zero) cause the authentication attempt to + fail. When all expressions evaluate successfully, then + the (potentially changed) username and group names have + been decided for that authentication attempt." + items: + description: FederationDomainTransformsExpression defines + a transform expression. + properties: + expression: + description: Expression is a CEL expression that will + be evaluated based on the Type during an authentication. + minLength: 1 + type: string + message: + description: Message is only used when Type is policy/v1. + It defines an error message to be used when the + policy rejects an authentication attempt. When empty, + a default message will be used. + type: string + type: + description: Type determines the type of the expression. + It must be one of the supported types. + enum: + - policy/v1 + - username/v1 + - groups/v1 + type: string + required: + - expression + - type + type: object + type: array + type: object + required: + - displayName + - objectRef + type: object + type: array issuer: description: "Issuer is the OIDC Provider's issuer, per the OIDC Discovery Metadata document, as well as the identifier that it will use for diff --git a/generated/1.18/README.adoc b/generated/1.18/README.adoc index 992d27c13d..54d5858216 100644 --- a/generated/1.18/README.adoc +++ b/generated/1.18/README.adoc @@ -639,6 +639,25 @@ FederationDomain describes the configuration of an OIDC provider. |=== +[id="{anchor_prefix}-go-pinniped-dev-generated-1-18-apis-supervisor-config-v1alpha1-federationdomainidentityprovider"] +==== FederationDomainIdentityProvider + +FederationDomainIdentityProvider describes how an identity provider is made available in this FederationDomain. + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-18-apis-supervisor-config-v1alpha1-federationdomainspec[$$FederationDomainSpec$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description +| *`displayName`* __string__ | DisplayName is the name of this identity provider as it will appear to clients. This name ends up in the kubeconfig of end users, so changing the name of an identity provider that is in use by end users will be a disruptive change for those users. +| *`objectRef`* __link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.18/#typedlocalobjectreference-v1-core[$$TypedLocalObjectReference$$]__ | ObjectRef is a reference to a Pinniped identity provider resource. A valid reference is required. If the reference cannot be resolved then the identity provider will not be made available. Must refer to a resource of one of the Pinniped identity provider types, e.g. OIDCIdentityProvider, LDAPIdentityProvider, ActiveDirectoryIdentityProvider. +| *`transforms`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-18-apis-supervisor-config-v1alpha1-federationdomaintransforms[$$FederationDomainTransforms$$]__ | Transforms is an optional way to specify transformations to be applied during user authentication and session refresh. +|=== + + [id="{anchor_prefix}-go-pinniped-dev-generated-1-18-apis-supervisor-config-v1alpha1-federationdomainsecrets"] @@ -677,6 +696,9 @@ FederationDomainSpec is a struct that describes an OIDC Provider. | *`issuer`* __string__ | Issuer is the OIDC Provider's issuer, per the OIDC Discovery Metadata document, as well as the identifier that it will use for the iss claim in issued JWTs. This field will also be used as the base URL for any endpoints used by the OIDC Provider (e.g., if your issuer is https://example.com/foo, then your authorization endpoint will look like https://example.com/foo/some/path/to/auth/endpoint). See https://openid.net/specs/openid-connect-discovery-1_0.html#rfc.section.3 for more information. | *`tls`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-18-apis-supervisor-config-v1alpha1-federationdomaintlsspec[$$FederationDomainTLSSpec$$]__ | TLS configures how this FederationDomain is served over Transport Layer Security (TLS). +| *`identityProviders`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-18-apis-supervisor-config-v1alpha1-federationdomainidentityprovider[$$FederationDomainIdentityProvider$$] array__ | IdentityProviders is the list of identity providers available for use by this FederationDomain. + An identity provider (e.g. OIDCIdentityProvider or LDAPIdentityProvider) describes how to connect to a server, how to talk in a specific protocol for authentication, and how to use the schema of that server/protocol to extract a normalized user identity. Normalized user identities include a username and a list of group names. In contrast, IdentityProviders describes how to use that normalized identity in a group of Kubernetes clusters. It can perform arbitrary transformations on that normalized identity. For example, a transformation can add a prefix to all usernames to help avoid accidental conflicts when multiple identity providers have different users with the same username (e.g. "idp1:ryan" versus "idp2:ryan"). IdentityProviders can also implement arbitrary authentication rejection policies. Even though a user was able to authenticate with the identity provider, a policy can disallow the authentication to the Kubernetes clusters that belong to this FederationDomain. For example, a policy could disallow the authentication unless the user belongs to a specific group in the identity provider. + For backwards compatibility with versions of Pinniped which predate support for multiple identity providers, an empty IdentityProviders list will cause the FederationDomain to use all available identity providers which exist in the same namespace, but also to reject all authentication requests when there is more than one identity provider currently defined. In this backwards compatibility mode, the name of the identity provider resource (e.g. the Name of an OIDCIdentityProvider resource) will be used as the name of the identity provider in this FederationDomain. This mode is provided to make upgrading from older versions easier. However, instead of relying on this backwards compatibility mode, please consider this mode to be deprecated and please instead explicitly list the identity provider using this IdentityProviders field. |=== @@ -721,6 +743,106 @@ FederationDomainTLSSpec is a struct that describes the TLS configuration for an |=== +[id="{anchor_prefix}-go-pinniped-dev-generated-1-18-apis-supervisor-config-v1alpha1-federationdomaintransforms"] +==== FederationDomainTransforms + +FederationDomainTransforms defines identity transformations for an identity provider's usage on a FederationDomain. + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-18-apis-supervisor-config-v1alpha1-federationdomainidentityprovider[$$FederationDomainIdentityProvider$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description +| *`constants`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-18-apis-supervisor-config-v1alpha1-federationdomaintransformsconstant[$$FederationDomainTransformsConstant$$] array__ | Constants defines constant variables and their values which will be made available to the transform expressions. +| *`expressions`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-18-apis-supervisor-config-v1alpha1-federationdomaintransformsexpression[$$FederationDomainTransformsExpression$$] array__ | Expressions are an optional list of transforms and policies to be executed in the order given during every authentication attempt, including during every session refresh. Each is a CEL expression. It may use the basic CEL language as defined in https://github.com/google/cel-spec/blob/master/doc/langdef.md plus the CEL string extensions defined in https://github.com/google/cel-go/tree/master/ext#strings. + The username and groups extracted from the identity provider, and the constants defined in this CR, are available as variables in all expressions. The username is provided via a variable called `username` and the list of group names is provided via a variable called `groups` (which may be an empty list). Each user-provided constants is provided via a variable named `strConst.varName` for string constants and `strListConst.varName` for string list constants. + The only allowed types for expressions are currently policy/v1, username/v1, and groups/v1. Each policy/v1 must return a boolean, and when it returns false, no more expressions from the list are evaluated and the authentication attempt is rejected. Transformations of type policy/v1 do not return usernames or group names, and therefore cannot change the username or group names. Each username/v1 transform must return the new username (a string), which can be the same as the old username. Transformations of type username/v1 do not return group names, and therefore cannot change the group names. Each groups/v1 transform must return the new groups list (list of strings), which can be the same as the old groups list. Transformations of type groups/v1 do not return usernames, and therefore cannot change the usernames. After each expression, the new (potentially changed) username or groups get passed to the following expression. + Any compilation or static type-checking failure of any expression will cause an error status on the FederationDomain. During an authentication attempt, any unexpected runtime evaluation errors (e.g. division by zero) cause the authentication attempt to fail. When all expressions evaluate successfully, then the (potentially changed) username and group names have been decided for that authentication attempt. +| *`examples`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-18-apis-supervisor-config-v1alpha1-federationdomaintransformsexample[$$FederationDomainTransformsExample$$] array__ | Examples can optionally be used to ensure that the sequence of transformation expressions are working as expected. Examples define sample input identities which are then run through the expression list, and the results are compared to the expected results. If any example in this list fails, then this FederationDomain will not be available for use, and the error(s) will be added to its status. This can be used to help guard against programming mistakes in the expressions, and also act as living documentation for other administrators to better understand the expressions. +|=== + + +[id="{anchor_prefix}-go-pinniped-dev-generated-1-18-apis-supervisor-config-v1alpha1-federationdomaintransformsconstant"] +==== FederationDomainTransformsConstant + +FederationDomainTransformsConstant defines a constant variable and its value which will be made available to the transform expressions. This is a union type, and Type is the discriminator field. + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-18-apis-supervisor-config-v1alpha1-federationdomaintransforms[$$FederationDomainTransforms$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description +| *`name`* __string__ | Name determines the name of the constant. It must be a valid identifier name. +| *`type`* __string__ | Type determines the type of the constant, and indicates which other field should be non-empty. +| *`stringValue`* __string__ | StringValue should hold the value when Type is "string", and is otherwise ignored. +| *`stringListValue`* __string array__ | StringListValue should hold the value when Type is "stringList", and is otherwise ignored. +|=== + + +[id="{anchor_prefix}-go-pinniped-dev-generated-1-18-apis-supervisor-config-v1alpha1-federationdomaintransformsexample"] +==== FederationDomainTransformsExample + +FederationDomainTransformsExample defines a transform example. + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-18-apis-supervisor-config-v1alpha1-federationdomaintransforms[$$FederationDomainTransforms$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description +| *`username`* __string__ | Username is the input username. +| *`groups`* __string array__ | Groups is the input list of group names. +| *`expects`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-18-apis-supervisor-config-v1alpha1-federationdomaintransformsexampleexpects[$$FederationDomainTransformsExampleExpects$$]__ | Expects is the expected output of the entire sequence of transforms when they are run against the input Username and Groups. +|=== + + +[id="{anchor_prefix}-go-pinniped-dev-generated-1-18-apis-supervisor-config-v1alpha1-federationdomaintransformsexampleexpects"] +==== FederationDomainTransformsExampleExpects + +FederationDomainTransformsExampleExpects defines the expected result for a transforms example. + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-18-apis-supervisor-config-v1alpha1-federationdomaintransformsexample[$$FederationDomainTransformsExample$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description +| *`username`* __string__ | Username is the expected username. +| *`groups`* __string array__ | Groups is the expected list of group names. +| *`rejected`* __boolean__ | Rejected should be set to true when the expected result of the transforms is that a policy rejected the authentication attempt. +| *`message`* __string__ | Message is the expected error message of the transforms. When Rejected is true, then Message is the expected message for the policy which rejected the authentication attempt. When Rejected is true and Message is blank, then Message will be treated as the default error message for authentication attempts which are rejected by a policy. When Rejected is false, then Message is the expected error message for some other non-policy transformation error, such as a runtime error. When Rejected is false, there is no default expected Message. +|=== + + +[id="{anchor_prefix}-go-pinniped-dev-generated-1-18-apis-supervisor-config-v1alpha1-federationdomaintransformsexpression"] +==== FederationDomainTransformsExpression + +FederationDomainTransformsExpression defines a transform expression. + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-18-apis-supervisor-config-v1alpha1-federationdomaintransforms[$$FederationDomainTransforms$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description +| *`type`* __string__ | Type determines the type of the expression. It must be one of the supported types. +| *`expression`* __string__ | Expression is a CEL expression that will be evaluated based on the Type during an authentication. +| *`message`* __string__ | Message is only used when Type is policy/v1. It defines an error message to be used when the policy rejects an authentication attempt. When empty, a default message will be used. +|=== + + [id="{anchor_prefix}-go-pinniped-dev-generated-1-18-apis-supervisor-config-v1alpha1-oidcclient"] ==== OIDCClient diff --git a/generated/1.18/apis/supervisor/config/v1alpha1/types_federationdomain.go b/generated/1.18/apis/supervisor/config/v1alpha1/types_federationdomain.go index 27de4401c0..7f1956845e 100644 --- a/generated/1.18/apis/supervisor/config/v1alpha1/types_federationdomain.go +++ b/generated/1.18/apis/supervisor/config/v1alpha1/types_federationdomain.go @@ -1,4 +1,4 @@ -// Copyright 2020-2022 the Pinniped contributors. All Rights Reserved. +// Copyright 2020-2023 the Pinniped contributors. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 package v1alpha1 @@ -42,6 +42,155 @@ type FederationDomainTLSSpec struct { SecretName string `json:"secretName,omitempty"` } +// FederationDomainTransformsConstant defines a constant variable and its value which will be made available to +// the transform expressions. This is a union type, and Type is the discriminator field. +type FederationDomainTransformsConstant struct { + // Name determines the name of the constant. It must be a valid identifier name. + // +kubebuilder:validation:Pattern=`^[a-zA-Z][_a-zA-Z0-9]*$` + // +kubebuilder:validation:MinLength=1 + // +kubebuilder:validation:MaxLength=64 + Name string `json:"name"` + + // Type determines the type of the constant, and indicates which other field should be non-empty. + // +kubebuilder:validation:Enum=string;stringList + Type string `json:"type"` + + // StringValue should hold the value when Type is "string", and is otherwise ignored. + // +optional + StringValue string `json:"stringValue,omitempty"` + + // StringListValue should hold the value when Type is "stringList", and is otherwise ignored. + // +optional + StringListValue []string `json:"stringListValue,omitempty"` +} + +// FederationDomainTransformsExpression defines a transform expression. +type FederationDomainTransformsExpression struct { + // Type determines the type of the expression. It must be one of the supported types. + // +kubebuilder:validation:Enum=policy/v1;username/v1;groups/v1 + Type string `json:"type"` + + // Expression is a CEL expression that will be evaluated based on the Type during an authentication. + // +kubebuilder:validation:MinLength=1 + Expression string `json:"expression"` + + // Message is only used when Type is policy/v1. It defines an error message to be used when the policy rejects + // an authentication attempt. When empty, a default message will be used. + // +optional + Message string `json:"message,omitempty"` +} + +// FederationDomainTransformsExample defines a transform example. +type FederationDomainTransformsExample struct { + // Username is the input username. + // +kubebuilder:validation:MinLength=1 + Username string `json:"username"` + + // Groups is the input list of group names. + // +optional + Groups []string `json:"groups,omitempty"` + + // Expects is the expected output of the entire sequence of transforms when they are run against the + // input Username and Groups. + Expects FederationDomainTransformsExampleExpects `json:"expects"` +} + +// FederationDomainTransformsExampleExpects defines the expected result for a transforms example. +type FederationDomainTransformsExampleExpects struct { + // Username is the expected username. + // +optional + Username string `json:"username,omitempty"` + + // Groups is the expected list of group names. + // +optional + Groups []string `json:"groups,omitempty"` + + // Rejected should be set to true when the expected result of the transforms is that a policy rejected the + // authentication attempt. + // +optional + Rejected bool `json:"rejected,omitempty"` + + // Message is the expected error message of the transforms. When Rejected is true, then Message is the expected + // message for the policy which rejected the authentication attempt. When Rejected is true and Message is blank, + // then Message will be treated as the default error message for authentication attempts which are rejected by a + // policy. When Rejected is false, then Message is the expected error message for some other non-policy + // transformation error, such as a runtime error. When Rejected is false, there is no default expected Message. + // +optional + Message string `json:"message,omitempty"` +} + +// FederationDomainTransforms defines identity transformations for an identity provider's usage on a FederationDomain. +type FederationDomainTransforms struct { + // Constants defines constant variables and their values which will be made available to the transform expressions. + // +patchMergeKey=name + // +patchStrategy=merge + // +listType=map + // +listMapKey=name + // +optional + Constants []FederationDomainTransformsConstant `json:"constants,omitempty"` + + // Expressions are an optional list of transforms and policies to be executed in the order given during every + // authentication attempt, including during every session refresh. + // Each is a CEL expression. It may use the basic CEL language as defined in + // https://github.com/google/cel-spec/blob/master/doc/langdef.md plus the CEL string extensions defined in + // https://github.com/google/cel-go/tree/master/ext#strings. + // + // The username and groups extracted from the identity provider, and the constants defined in this CR, are + // available as variables in all expressions. The username is provided via a variable called `username` and + // the list of group names is provided via a variable called `groups` (which may be an empty list). + // Each user-provided constants is provided via a variable named `strConst.varName` for string constants + // and `strListConst.varName` for string list constants. + // + // The only allowed types for expressions are currently policy/v1, username/v1, and groups/v1. + // Each policy/v1 must return a boolean, and when it returns false, no more expressions from the list are evaluated + // and the authentication attempt is rejected. + // Transformations of type policy/v1 do not return usernames or group names, and therefore cannot change the + // username or group names. + // Each username/v1 transform must return the new username (a string), which can be the same as the old username. + // Transformations of type username/v1 do not return group names, and therefore cannot change the group names. + // Each groups/v1 transform must return the new groups list (list of strings), which can be the same as the old + // groups list. + // Transformations of type groups/v1 do not return usernames, and therefore cannot change the usernames. + // After each expression, the new (potentially changed) username or groups get passed to the following expression. + // + // Any compilation or static type-checking failure of any expression will cause an error status on the FederationDomain. + // During an authentication attempt, any unexpected runtime evaluation errors (e.g. division by zero) cause the + // authentication attempt to fail. When all expressions evaluate successfully, then the (potentially changed) username + // and group names have been decided for that authentication attempt. + // + // +optional + Expressions []FederationDomainTransformsExpression `json:"expressions,omitempty"` + + // Examples can optionally be used to ensure that the sequence of transformation expressions are working as + // expected. Examples define sample input identities which are then run through the expression list, and the + // results are compared to the expected results. If any example in this list fails, then this + // FederationDomain will not be available for use, and the error(s) will be added to its status. + // This can be used to help guard against programming mistakes in the expressions, and also + // act as living documentation for other administrators to better understand the expressions. + // +optional + Examples []FederationDomainTransformsExample `json:"examples,omitempty"` +} + +// FederationDomainIdentityProvider describes how an identity provider is made available in this FederationDomain. +type FederationDomainIdentityProvider struct { + // DisplayName is the name of this identity provider as it will appear to clients. This name ends up in the + // kubeconfig of end users, so changing the name of an identity provider that is in use by end users will be a + // disruptive change for those users. + // +kubebuilder:validation:MinLength=1 + DisplayName string `json:"displayName"` + + // ObjectRef is a reference to a Pinniped identity provider resource. A valid reference is required. + // If the reference cannot be resolved then the identity provider will not be made available. + // Must refer to a resource of one of the Pinniped identity provider types, e.g. OIDCIdentityProvider, + // LDAPIdentityProvider, ActiveDirectoryIdentityProvider. + ObjectRef corev1.TypedLocalObjectReference `json:"objectRef"` + + // Transforms is an optional way to specify transformations to be applied during user authentication and + // session refresh. + // +optional + Transforms FederationDomainTransforms `json:"transforms,omitempty"` +} + // FederationDomainSpec is a struct that describes an OIDC Provider. type FederationDomainSpec struct { // Issuer is the OIDC Provider's issuer, per the OIDC Discovery Metadata document, as well as the @@ -58,6 +207,32 @@ type FederationDomainSpec struct { // TLS configures how this FederationDomain is served over Transport Layer Security (TLS). // +optional TLS *FederationDomainTLSSpec `json:"tls,omitempty"` + + // IdentityProviders is the list of identity providers available for use by this FederationDomain. + // + // An identity provider (e.g. OIDCIdentityProvider or LDAPIdentityProvider) describes how to connect to a server, + // how to talk in a specific protocol for authentication, and how to use the schema of that server/protocol to + // extract a normalized user identity. Normalized user identities include a username and a list of group names. + // In contrast, IdentityProviders describes how to use that normalized identity in a group of + // Kubernetes clusters. It can perform arbitrary transformations on that normalized identity. For example, a + // transformation can add a prefix to all usernames to help avoid accidental conflicts when multiple identity + // providers have different users with the same username (e.g. "idp1:ryan" versus "idp2:ryan"). + // IdentityProviders can also implement arbitrary authentication rejection policies. + // Even though a user was able to authenticate with the identity provider, a policy can disallow the authentication + // to the Kubernetes clusters that belong to this FederationDomain. For example, a policy could disallow the + // authentication unless the user belongs to a specific group in the identity provider. + // + // For backwards compatibility with versions of Pinniped which predate support for multiple identity providers, + // an empty IdentityProviders list will cause the FederationDomain to use all available identity providers which + // exist in the same namespace, but also to reject all authentication requests when there is more than one identity + // provider currently defined. In this backwards compatibility mode, the name of the identity provider resource + // (e.g. the Name of an OIDCIdentityProvider resource) will be used as the name of the identity provider in this + // FederationDomain. This mode is provided to make upgrading from older versions easier. However, instead of + // relying on this backwards compatibility mode, please consider this mode to be deprecated and please instead + // explicitly list the identity provider using this IdentityProviders field. + // + // +optional + IdentityProviders []FederationDomainIdentityProvider `json:"identityProviders,omitempty"` } // FederationDomainSecrets holds information about this OIDC Provider's secrets. diff --git a/generated/1.18/apis/supervisor/config/v1alpha1/zz_generated.deepcopy.go b/generated/1.18/apis/supervisor/config/v1alpha1/zz_generated.deepcopy.go index 682013782a..5e48e41556 100644 --- a/generated/1.18/apis/supervisor/config/v1alpha1/zz_generated.deepcopy.go +++ b/generated/1.18/apis/supervisor/config/v1alpha1/zz_generated.deepcopy.go @@ -57,6 +57,24 @@ func (in *FederationDomain) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FederationDomainIdentityProvider) DeepCopyInto(out *FederationDomainIdentityProvider) { + *out = *in + in.ObjectRef.DeepCopyInto(&out.ObjectRef) + in.Transforms.DeepCopyInto(&out.Transforms) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FederationDomainIdentityProvider. +func (in *FederationDomainIdentityProvider) DeepCopy() *FederationDomainIdentityProvider { + if in == nil { + return nil + } + out := new(FederationDomainIdentityProvider) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *FederationDomainList) DeepCopyInto(out *FederationDomainList) { *out = *in @@ -118,6 +136,13 @@ func (in *FederationDomainSpec) DeepCopyInto(out *FederationDomainSpec) { *out = new(FederationDomainTLSSpec) **out = **in } + if in.IdentityProviders != nil { + in, out := &in.IdentityProviders, &out.IdentityProviders + *out = make([]FederationDomainIdentityProvider, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } return } @@ -168,6 +193,121 @@ func (in *FederationDomainTLSSpec) DeepCopy() *FederationDomainTLSSpec { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FederationDomainTransforms) DeepCopyInto(out *FederationDomainTransforms) { + *out = *in + if in.Constants != nil { + in, out := &in.Constants, &out.Constants + *out = make([]FederationDomainTransformsConstant, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.Expressions != nil { + in, out := &in.Expressions, &out.Expressions + *out = make([]FederationDomainTransformsExpression, len(*in)) + copy(*out, *in) + } + if in.Examples != nil { + in, out := &in.Examples, &out.Examples + *out = make([]FederationDomainTransformsExample, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FederationDomainTransforms. +func (in *FederationDomainTransforms) DeepCopy() *FederationDomainTransforms { + if in == nil { + return nil + } + out := new(FederationDomainTransforms) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FederationDomainTransformsConstant) DeepCopyInto(out *FederationDomainTransformsConstant) { + *out = *in + if in.StringListValue != nil { + in, out := &in.StringListValue, &out.StringListValue + *out = make([]string, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FederationDomainTransformsConstant. +func (in *FederationDomainTransformsConstant) DeepCopy() *FederationDomainTransformsConstant { + if in == nil { + return nil + } + out := new(FederationDomainTransformsConstant) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FederationDomainTransformsExample) DeepCopyInto(out *FederationDomainTransformsExample) { + *out = *in + if in.Groups != nil { + in, out := &in.Groups, &out.Groups + *out = make([]string, len(*in)) + copy(*out, *in) + } + in.Expects.DeepCopyInto(&out.Expects) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FederationDomainTransformsExample. +func (in *FederationDomainTransformsExample) DeepCopy() *FederationDomainTransformsExample { + if in == nil { + return nil + } + out := new(FederationDomainTransformsExample) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FederationDomainTransformsExampleExpects) DeepCopyInto(out *FederationDomainTransformsExampleExpects) { + *out = *in + if in.Groups != nil { + in, out := &in.Groups, &out.Groups + *out = make([]string, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FederationDomainTransformsExampleExpects. +func (in *FederationDomainTransformsExampleExpects) DeepCopy() *FederationDomainTransformsExampleExpects { + if in == nil { + return nil + } + out := new(FederationDomainTransformsExampleExpects) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FederationDomainTransformsExpression) DeepCopyInto(out *FederationDomainTransformsExpression) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FederationDomainTransformsExpression. +func (in *FederationDomainTransformsExpression) DeepCopy() *FederationDomainTransformsExpression { + if in == nil { + return nil + } + out := new(FederationDomainTransformsExpression) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *OIDCClient) DeepCopyInto(out *OIDCClient) { *out = *in diff --git a/generated/1.18/crds/config.supervisor.pinniped.dev_federationdomains.yaml b/generated/1.18/crds/config.supervisor.pinniped.dev_federationdomains.yaml index 71f7370d11..5e839fccd0 100644 --- a/generated/1.18/crds/config.supervisor.pinniped.dev_federationdomains.yaml +++ b/generated/1.18/crds/config.supervisor.pinniped.dev_federationdomains.yaml @@ -47,6 +47,255 @@ spec: spec: description: Spec of the OIDC provider. properties: + identityProviders: + description: "IdentityProviders is the list of identity providers + available for use by this FederationDomain. \n An identity provider + (e.g. OIDCIdentityProvider or LDAPIdentityProvider) describes how + to connect to a server, how to talk in a specific protocol for authentication, + and how to use the schema of that server/protocol to extract a normalized + user identity. Normalized user identities include a username and + a list of group names. In contrast, IdentityProviders describes + how to use that normalized identity in a group of Kubernetes clusters. + It can perform arbitrary transformations on that normalized identity. + For example, a transformation can add a prefix to all usernames + to help avoid accidental conflicts when multiple identity providers + have different users with the same username (e.g. \"idp1:ryan\" + versus \"idp2:ryan\"). IdentityProviders can also implement arbitrary + authentication rejection policies. Even though a user was able to + authenticate with the identity provider, a policy can disallow the + authentication to the Kubernetes clusters that belong to this FederationDomain. + For example, a policy could disallow the authentication unless the + user belongs to a specific group in the identity provider. \n For + backwards compatibility with versions of Pinniped which predate + support for multiple identity providers, an empty IdentityProviders + list will cause the FederationDomain to use all available identity + providers which exist in the same namespace, but also to reject + all authentication requests when there is more than one identity + provider currently defined. In this backwards compatibility mode, + the name of the identity provider resource (e.g. the Name of an + OIDCIdentityProvider resource) will be used as the name of the identity + provider in this FederationDomain. This mode is provided to make + upgrading from older versions easier. However, instead of relying + on this backwards compatibility mode, please consider this mode + to be deprecated and please instead explicitly list the identity + provider using this IdentityProviders field." + items: + description: FederationDomainIdentityProvider describes how an identity + provider is made available in this FederationDomain. + properties: + displayName: + description: DisplayName is the name of this identity provider + as it will appear to clients. This name ends up in the kubeconfig + of end users, so changing the name of an identity provider + that is in use by end users will be a disruptive change for + those users. + minLength: 1 + type: string + objectRef: + description: ObjectRef is a reference to a Pinniped identity + provider resource. A valid reference is required. If the reference + cannot be resolved then the identity provider will not be + made available. Must refer to a resource of one of the Pinniped + identity provider types, e.g. OIDCIdentityProvider, LDAPIdentityProvider, + ActiveDirectoryIdentityProvider. + properties: + apiGroup: + description: APIGroup is the group for the resource being + referenced. If APIGroup is not specified, the specified + Kind must be in the core API group. For any other third-party + types, APIGroup is required. + type: string + kind: + description: Kind is the type of resource being referenced + type: string + name: + description: Name is the name of resource being referenced + type: string + required: + - kind + - name + type: object + transforms: + description: Transforms is an optional way to specify transformations + to be applied during user authentication and session refresh. + properties: + constants: + description: Constants defines constant variables and their + values which will be made available to the transform expressions. + items: + description: FederationDomainTransformsConstant defines + a constant variable and its value which will be made + available to the transform expressions. This is a union + type, and Type is the discriminator field. + properties: + name: + description: Name determines the name of the constant. + It must be a valid identifier name. + maxLength: 64 + minLength: 1 + pattern: ^[a-zA-Z][_a-zA-Z0-9]*$ + type: string + stringListValue: + description: StringListValue should hold the value + when Type is "stringList", and is otherwise ignored. + items: + type: string + type: array + stringValue: + description: StringValue should hold the value when + Type is "string", and is otherwise ignored. + type: string + type: + description: Type determines the type of the constant, + and indicates which other field should be non-empty. + enum: + - string + - stringList + type: string + required: + - name + - type + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + examples: + description: Examples can optionally be used to ensure that + the sequence of transformation expressions are working + as expected. Examples define sample input identities which + are then run through the expression list, and the results + are compared to the expected results. If any example in + this list fails, then this FederationDomain will not be + available for use, and the error(s) will be added to its + status. This can be used to help guard against programming + mistakes in the expressions, and also act as living documentation + for other administrators to better understand the expressions. + items: + description: FederationDomainTransformsExample defines + a transform example. + properties: + expects: + description: Expects is the expected output of the + entire sequence of transforms when they are run + against the input Username and Groups. + properties: + groups: + description: Groups is the expected list of group + names. + items: + type: string + type: array + message: + description: Message is the expected error message + of the transforms. When Rejected is true, then + Message is the expected message for the policy + which rejected the authentication attempt. When + Rejected is true and Message is blank, then + Message will be treated as the default error + message for authentication attempts which are + rejected by a policy. When Rejected is false, + then Message is the expected error message for + some other non-policy transformation error, + such as a runtime error. When Rejected is false, + there is no default expected Message. + type: string + rejected: + description: Rejected should be set to true when + the expected result of the transforms is that + a policy rejected the authentication attempt. + type: boolean + username: + description: Username is the expected username. + type: string + type: object + groups: + description: Groups is the input list of group names. + items: + type: string + type: array + username: + description: Username is the input username. + minLength: 1 + type: string + required: + - expects + - username + type: object + type: array + expressions: + description: "Expressions are an optional list of transforms + and policies to be executed in the order given during + every authentication attempt, including during every session + refresh. Each is a CEL expression. It may use the basic + CEL language as defined in https://github.com/google/cel-spec/blob/master/doc/langdef.md + plus the CEL string extensions defined in https://github.com/google/cel-go/tree/master/ext#strings. + \n The username and groups extracted from the identity + provider, and the constants defined in this CR, are available + as variables in all expressions. The username is provided + via a variable called `username` and the list of group + names is provided via a variable called `groups` (which + may be an empty list). Each user-provided constants is + provided via a variable named `strConst.varName` for string + constants and `strListConst.varName` for string list constants. + \n The only allowed types for expressions are currently + policy/v1, username/v1, and groups/v1. Each policy/v1 + must return a boolean, and when it returns false, no more + expressions from the list are evaluated and the authentication + attempt is rejected. Transformations of type policy/v1 + do not return usernames or group names, and therefore + cannot change the username or group names. Each username/v1 + transform must return the new username (a string), which + can be the same as the old username. Transformations of + type username/v1 do not return group names, and therefore + cannot change the group names. Each groups/v1 transform + must return the new groups list (list of strings), which + can be the same as the old groups list. Transformations + of type groups/v1 do not return usernames, and therefore + cannot change the usernames. After each expression, the + new (potentially changed) username or groups get passed + to the following expression. \n Any compilation or static + type-checking failure of any expression will cause an + error status on the FederationDomain. During an authentication + attempt, any unexpected runtime evaluation errors (e.g. + division by zero) cause the authentication attempt to + fail. When all expressions evaluate successfully, then + the (potentially changed) username and group names have + been decided for that authentication attempt." + items: + description: FederationDomainTransformsExpression defines + a transform expression. + properties: + expression: + description: Expression is a CEL expression that will + be evaluated based on the Type during an authentication. + minLength: 1 + type: string + message: + description: Message is only used when Type is policy/v1. + It defines an error message to be used when the + policy rejects an authentication attempt. When empty, + a default message will be used. + type: string + type: + description: Type determines the type of the expression. + It must be one of the supported types. + enum: + - policy/v1 + - username/v1 + - groups/v1 + type: string + required: + - expression + - type + type: object + type: array + type: object + required: + - displayName + - objectRef + type: object + type: array issuer: description: "Issuer is the OIDC Provider's issuer, per the OIDC Discovery Metadata document, as well as the identifier that it will use for diff --git a/generated/1.19/README.adoc b/generated/1.19/README.adoc index c15fa11d01..6183632f92 100644 --- a/generated/1.19/README.adoc +++ b/generated/1.19/README.adoc @@ -639,6 +639,25 @@ FederationDomain describes the configuration of an OIDC provider. |=== +[id="{anchor_prefix}-go-pinniped-dev-generated-1-19-apis-supervisor-config-v1alpha1-federationdomainidentityprovider"] +==== FederationDomainIdentityProvider + +FederationDomainIdentityProvider describes how an identity provider is made available in this FederationDomain. + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-19-apis-supervisor-config-v1alpha1-federationdomainspec[$$FederationDomainSpec$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description +| *`displayName`* __string__ | DisplayName is the name of this identity provider as it will appear to clients. This name ends up in the kubeconfig of end users, so changing the name of an identity provider that is in use by end users will be a disruptive change for those users. +| *`objectRef`* __link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.19/#typedlocalobjectreference-v1-core[$$TypedLocalObjectReference$$]__ | ObjectRef is a reference to a Pinniped identity provider resource. A valid reference is required. If the reference cannot be resolved then the identity provider will not be made available. Must refer to a resource of one of the Pinniped identity provider types, e.g. OIDCIdentityProvider, LDAPIdentityProvider, ActiveDirectoryIdentityProvider. +| *`transforms`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-19-apis-supervisor-config-v1alpha1-federationdomaintransforms[$$FederationDomainTransforms$$]__ | Transforms is an optional way to specify transformations to be applied during user authentication and session refresh. +|=== + + [id="{anchor_prefix}-go-pinniped-dev-generated-1-19-apis-supervisor-config-v1alpha1-federationdomainsecrets"] @@ -677,6 +696,9 @@ FederationDomainSpec is a struct that describes an OIDC Provider. | *`issuer`* __string__ | Issuer is the OIDC Provider's issuer, per the OIDC Discovery Metadata document, as well as the identifier that it will use for the iss claim in issued JWTs. This field will also be used as the base URL for any endpoints used by the OIDC Provider (e.g., if your issuer is https://example.com/foo, then your authorization endpoint will look like https://example.com/foo/some/path/to/auth/endpoint). See https://openid.net/specs/openid-connect-discovery-1_0.html#rfc.section.3 for more information. | *`tls`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-19-apis-supervisor-config-v1alpha1-federationdomaintlsspec[$$FederationDomainTLSSpec$$]__ | TLS configures how this FederationDomain is served over Transport Layer Security (TLS). +| *`identityProviders`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-19-apis-supervisor-config-v1alpha1-federationdomainidentityprovider[$$FederationDomainIdentityProvider$$] array__ | IdentityProviders is the list of identity providers available for use by this FederationDomain. + An identity provider (e.g. OIDCIdentityProvider or LDAPIdentityProvider) describes how to connect to a server, how to talk in a specific protocol for authentication, and how to use the schema of that server/protocol to extract a normalized user identity. Normalized user identities include a username and a list of group names. In contrast, IdentityProviders describes how to use that normalized identity in a group of Kubernetes clusters. It can perform arbitrary transformations on that normalized identity. For example, a transformation can add a prefix to all usernames to help avoid accidental conflicts when multiple identity providers have different users with the same username (e.g. "idp1:ryan" versus "idp2:ryan"). IdentityProviders can also implement arbitrary authentication rejection policies. Even though a user was able to authenticate with the identity provider, a policy can disallow the authentication to the Kubernetes clusters that belong to this FederationDomain. For example, a policy could disallow the authentication unless the user belongs to a specific group in the identity provider. + For backwards compatibility with versions of Pinniped which predate support for multiple identity providers, an empty IdentityProviders list will cause the FederationDomain to use all available identity providers which exist in the same namespace, but also to reject all authentication requests when there is more than one identity provider currently defined. In this backwards compatibility mode, the name of the identity provider resource (e.g. the Name of an OIDCIdentityProvider resource) will be used as the name of the identity provider in this FederationDomain. This mode is provided to make upgrading from older versions easier. However, instead of relying on this backwards compatibility mode, please consider this mode to be deprecated and please instead explicitly list the identity provider using this IdentityProviders field. |=== @@ -721,6 +743,106 @@ FederationDomainTLSSpec is a struct that describes the TLS configuration for an |=== +[id="{anchor_prefix}-go-pinniped-dev-generated-1-19-apis-supervisor-config-v1alpha1-federationdomaintransforms"] +==== FederationDomainTransforms + +FederationDomainTransforms defines identity transformations for an identity provider's usage on a FederationDomain. + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-19-apis-supervisor-config-v1alpha1-federationdomainidentityprovider[$$FederationDomainIdentityProvider$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description +| *`constants`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-19-apis-supervisor-config-v1alpha1-federationdomaintransformsconstant[$$FederationDomainTransformsConstant$$] array__ | Constants defines constant variables and their values which will be made available to the transform expressions. +| *`expressions`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-19-apis-supervisor-config-v1alpha1-federationdomaintransformsexpression[$$FederationDomainTransformsExpression$$] array__ | Expressions are an optional list of transforms and policies to be executed in the order given during every authentication attempt, including during every session refresh. Each is a CEL expression. It may use the basic CEL language as defined in https://github.com/google/cel-spec/blob/master/doc/langdef.md plus the CEL string extensions defined in https://github.com/google/cel-go/tree/master/ext#strings. + The username and groups extracted from the identity provider, and the constants defined in this CR, are available as variables in all expressions. The username is provided via a variable called `username` and the list of group names is provided via a variable called `groups` (which may be an empty list). Each user-provided constants is provided via a variable named `strConst.varName` for string constants and `strListConst.varName` for string list constants. + The only allowed types for expressions are currently policy/v1, username/v1, and groups/v1. Each policy/v1 must return a boolean, and when it returns false, no more expressions from the list are evaluated and the authentication attempt is rejected. Transformations of type policy/v1 do not return usernames or group names, and therefore cannot change the username or group names. Each username/v1 transform must return the new username (a string), which can be the same as the old username. Transformations of type username/v1 do not return group names, and therefore cannot change the group names. Each groups/v1 transform must return the new groups list (list of strings), which can be the same as the old groups list. Transformations of type groups/v1 do not return usernames, and therefore cannot change the usernames. After each expression, the new (potentially changed) username or groups get passed to the following expression. + Any compilation or static type-checking failure of any expression will cause an error status on the FederationDomain. During an authentication attempt, any unexpected runtime evaluation errors (e.g. division by zero) cause the authentication attempt to fail. When all expressions evaluate successfully, then the (potentially changed) username and group names have been decided for that authentication attempt. +| *`examples`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-19-apis-supervisor-config-v1alpha1-federationdomaintransformsexample[$$FederationDomainTransformsExample$$] array__ | Examples can optionally be used to ensure that the sequence of transformation expressions are working as expected. Examples define sample input identities which are then run through the expression list, and the results are compared to the expected results. If any example in this list fails, then this FederationDomain will not be available for use, and the error(s) will be added to its status. This can be used to help guard against programming mistakes in the expressions, and also act as living documentation for other administrators to better understand the expressions. +|=== + + +[id="{anchor_prefix}-go-pinniped-dev-generated-1-19-apis-supervisor-config-v1alpha1-federationdomaintransformsconstant"] +==== FederationDomainTransformsConstant + +FederationDomainTransformsConstant defines a constant variable and its value which will be made available to the transform expressions. This is a union type, and Type is the discriminator field. + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-19-apis-supervisor-config-v1alpha1-federationdomaintransforms[$$FederationDomainTransforms$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description +| *`name`* __string__ | Name determines the name of the constant. It must be a valid identifier name. +| *`type`* __string__ | Type determines the type of the constant, and indicates which other field should be non-empty. +| *`stringValue`* __string__ | StringValue should hold the value when Type is "string", and is otherwise ignored. +| *`stringListValue`* __string array__ | StringListValue should hold the value when Type is "stringList", and is otherwise ignored. +|=== + + +[id="{anchor_prefix}-go-pinniped-dev-generated-1-19-apis-supervisor-config-v1alpha1-federationdomaintransformsexample"] +==== FederationDomainTransformsExample + +FederationDomainTransformsExample defines a transform example. + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-19-apis-supervisor-config-v1alpha1-federationdomaintransforms[$$FederationDomainTransforms$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description +| *`username`* __string__ | Username is the input username. +| *`groups`* __string array__ | Groups is the input list of group names. +| *`expects`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-19-apis-supervisor-config-v1alpha1-federationdomaintransformsexampleexpects[$$FederationDomainTransformsExampleExpects$$]__ | Expects is the expected output of the entire sequence of transforms when they are run against the input Username and Groups. +|=== + + +[id="{anchor_prefix}-go-pinniped-dev-generated-1-19-apis-supervisor-config-v1alpha1-federationdomaintransformsexampleexpects"] +==== FederationDomainTransformsExampleExpects + +FederationDomainTransformsExampleExpects defines the expected result for a transforms example. + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-19-apis-supervisor-config-v1alpha1-federationdomaintransformsexample[$$FederationDomainTransformsExample$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description +| *`username`* __string__ | Username is the expected username. +| *`groups`* __string array__ | Groups is the expected list of group names. +| *`rejected`* __boolean__ | Rejected should be set to true when the expected result of the transforms is that a policy rejected the authentication attempt. +| *`message`* __string__ | Message is the expected error message of the transforms. When Rejected is true, then Message is the expected message for the policy which rejected the authentication attempt. When Rejected is true and Message is blank, then Message will be treated as the default error message for authentication attempts which are rejected by a policy. When Rejected is false, then Message is the expected error message for some other non-policy transformation error, such as a runtime error. When Rejected is false, there is no default expected Message. +|=== + + +[id="{anchor_prefix}-go-pinniped-dev-generated-1-19-apis-supervisor-config-v1alpha1-federationdomaintransformsexpression"] +==== FederationDomainTransformsExpression + +FederationDomainTransformsExpression defines a transform expression. + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-19-apis-supervisor-config-v1alpha1-federationdomaintransforms[$$FederationDomainTransforms$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description +| *`type`* __string__ | Type determines the type of the expression. It must be one of the supported types. +| *`expression`* __string__ | Expression is a CEL expression that will be evaluated based on the Type during an authentication. +| *`message`* __string__ | Message is only used when Type is policy/v1. It defines an error message to be used when the policy rejects an authentication attempt. When empty, a default message will be used. +|=== + + [id="{anchor_prefix}-go-pinniped-dev-generated-1-19-apis-supervisor-config-v1alpha1-oidcclient"] ==== OIDCClient diff --git a/generated/1.19/apis/supervisor/config/v1alpha1/types_federationdomain.go b/generated/1.19/apis/supervisor/config/v1alpha1/types_federationdomain.go index 27de4401c0..7f1956845e 100644 --- a/generated/1.19/apis/supervisor/config/v1alpha1/types_federationdomain.go +++ b/generated/1.19/apis/supervisor/config/v1alpha1/types_federationdomain.go @@ -1,4 +1,4 @@ -// Copyright 2020-2022 the Pinniped contributors. All Rights Reserved. +// Copyright 2020-2023 the Pinniped contributors. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 package v1alpha1 @@ -42,6 +42,155 @@ type FederationDomainTLSSpec struct { SecretName string `json:"secretName,omitempty"` } +// FederationDomainTransformsConstant defines a constant variable and its value which will be made available to +// the transform expressions. This is a union type, and Type is the discriminator field. +type FederationDomainTransformsConstant struct { + // Name determines the name of the constant. It must be a valid identifier name. + // +kubebuilder:validation:Pattern=`^[a-zA-Z][_a-zA-Z0-9]*$` + // +kubebuilder:validation:MinLength=1 + // +kubebuilder:validation:MaxLength=64 + Name string `json:"name"` + + // Type determines the type of the constant, and indicates which other field should be non-empty. + // +kubebuilder:validation:Enum=string;stringList + Type string `json:"type"` + + // StringValue should hold the value when Type is "string", and is otherwise ignored. + // +optional + StringValue string `json:"stringValue,omitempty"` + + // StringListValue should hold the value when Type is "stringList", and is otherwise ignored. + // +optional + StringListValue []string `json:"stringListValue,omitempty"` +} + +// FederationDomainTransformsExpression defines a transform expression. +type FederationDomainTransformsExpression struct { + // Type determines the type of the expression. It must be one of the supported types. + // +kubebuilder:validation:Enum=policy/v1;username/v1;groups/v1 + Type string `json:"type"` + + // Expression is a CEL expression that will be evaluated based on the Type during an authentication. + // +kubebuilder:validation:MinLength=1 + Expression string `json:"expression"` + + // Message is only used when Type is policy/v1. It defines an error message to be used when the policy rejects + // an authentication attempt. When empty, a default message will be used. + // +optional + Message string `json:"message,omitempty"` +} + +// FederationDomainTransformsExample defines a transform example. +type FederationDomainTransformsExample struct { + // Username is the input username. + // +kubebuilder:validation:MinLength=1 + Username string `json:"username"` + + // Groups is the input list of group names. + // +optional + Groups []string `json:"groups,omitempty"` + + // Expects is the expected output of the entire sequence of transforms when they are run against the + // input Username and Groups. + Expects FederationDomainTransformsExampleExpects `json:"expects"` +} + +// FederationDomainTransformsExampleExpects defines the expected result for a transforms example. +type FederationDomainTransformsExampleExpects struct { + // Username is the expected username. + // +optional + Username string `json:"username,omitempty"` + + // Groups is the expected list of group names. + // +optional + Groups []string `json:"groups,omitempty"` + + // Rejected should be set to true when the expected result of the transforms is that a policy rejected the + // authentication attempt. + // +optional + Rejected bool `json:"rejected,omitempty"` + + // Message is the expected error message of the transforms. When Rejected is true, then Message is the expected + // message for the policy which rejected the authentication attempt. When Rejected is true and Message is blank, + // then Message will be treated as the default error message for authentication attempts which are rejected by a + // policy. When Rejected is false, then Message is the expected error message for some other non-policy + // transformation error, such as a runtime error. When Rejected is false, there is no default expected Message. + // +optional + Message string `json:"message,omitempty"` +} + +// FederationDomainTransforms defines identity transformations for an identity provider's usage on a FederationDomain. +type FederationDomainTransforms struct { + // Constants defines constant variables and their values which will be made available to the transform expressions. + // +patchMergeKey=name + // +patchStrategy=merge + // +listType=map + // +listMapKey=name + // +optional + Constants []FederationDomainTransformsConstant `json:"constants,omitempty"` + + // Expressions are an optional list of transforms and policies to be executed in the order given during every + // authentication attempt, including during every session refresh. + // Each is a CEL expression. It may use the basic CEL language as defined in + // https://github.com/google/cel-spec/blob/master/doc/langdef.md plus the CEL string extensions defined in + // https://github.com/google/cel-go/tree/master/ext#strings. + // + // The username and groups extracted from the identity provider, and the constants defined in this CR, are + // available as variables in all expressions. The username is provided via a variable called `username` and + // the list of group names is provided via a variable called `groups` (which may be an empty list). + // Each user-provided constants is provided via a variable named `strConst.varName` for string constants + // and `strListConst.varName` for string list constants. + // + // The only allowed types for expressions are currently policy/v1, username/v1, and groups/v1. + // Each policy/v1 must return a boolean, and when it returns false, no more expressions from the list are evaluated + // and the authentication attempt is rejected. + // Transformations of type policy/v1 do not return usernames or group names, and therefore cannot change the + // username or group names. + // Each username/v1 transform must return the new username (a string), which can be the same as the old username. + // Transformations of type username/v1 do not return group names, and therefore cannot change the group names. + // Each groups/v1 transform must return the new groups list (list of strings), which can be the same as the old + // groups list. + // Transformations of type groups/v1 do not return usernames, and therefore cannot change the usernames. + // After each expression, the new (potentially changed) username or groups get passed to the following expression. + // + // Any compilation or static type-checking failure of any expression will cause an error status on the FederationDomain. + // During an authentication attempt, any unexpected runtime evaluation errors (e.g. division by zero) cause the + // authentication attempt to fail. When all expressions evaluate successfully, then the (potentially changed) username + // and group names have been decided for that authentication attempt. + // + // +optional + Expressions []FederationDomainTransformsExpression `json:"expressions,omitempty"` + + // Examples can optionally be used to ensure that the sequence of transformation expressions are working as + // expected. Examples define sample input identities which are then run through the expression list, and the + // results are compared to the expected results. If any example in this list fails, then this + // FederationDomain will not be available for use, and the error(s) will be added to its status. + // This can be used to help guard against programming mistakes in the expressions, and also + // act as living documentation for other administrators to better understand the expressions. + // +optional + Examples []FederationDomainTransformsExample `json:"examples,omitempty"` +} + +// FederationDomainIdentityProvider describes how an identity provider is made available in this FederationDomain. +type FederationDomainIdentityProvider struct { + // DisplayName is the name of this identity provider as it will appear to clients. This name ends up in the + // kubeconfig of end users, so changing the name of an identity provider that is in use by end users will be a + // disruptive change for those users. + // +kubebuilder:validation:MinLength=1 + DisplayName string `json:"displayName"` + + // ObjectRef is a reference to a Pinniped identity provider resource. A valid reference is required. + // If the reference cannot be resolved then the identity provider will not be made available. + // Must refer to a resource of one of the Pinniped identity provider types, e.g. OIDCIdentityProvider, + // LDAPIdentityProvider, ActiveDirectoryIdentityProvider. + ObjectRef corev1.TypedLocalObjectReference `json:"objectRef"` + + // Transforms is an optional way to specify transformations to be applied during user authentication and + // session refresh. + // +optional + Transforms FederationDomainTransforms `json:"transforms,omitempty"` +} + // FederationDomainSpec is a struct that describes an OIDC Provider. type FederationDomainSpec struct { // Issuer is the OIDC Provider's issuer, per the OIDC Discovery Metadata document, as well as the @@ -58,6 +207,32 @@ type FederationDomainSpec struct { // TLS configures how this FederationDomain is served over Transport Layer Security (TLS). // +optional TLS *FederationDomainTLSSpec `json:"tls,omitempty"` + + // IdentityProviders is the list of identity providers available for use by this FederationDomain. + // + // An identity provider (e.g. OIDCIdentityProvider or LDAPIdentityProvider) describes how to connect to a server, + // how to talk in a specific protocol for authentication, and how to use the schema of that server/protocol to + // extract a normalized user identity. Normalized user identities include a username and a list of group names. + // In contrast, IdentityProviders describes how to use that normalized identity in a group of + // Kubernetes clusters. It can perform arbitrary transformations on that normalized identity. For example, a + // transformation can add a prefix to all usernames to help avoid accidental conflicts when multiple identity + // providers have different users with the same username (e.g. "idp1:ryan" versus "idp2:ryan"). + // IdentityProviders can also implement arbitrary authentication rejection policies. + // Even though a user was able to authenticate with the identity provider, a policy can disallow the authentication + // to the Kubernetes clusters that belong to this FederationDomain. For example, a policy could disallow the + // authentication unless the user belongs to a specific group in the identity provider. + // + // For backwards compatibility with versions of Pinniped which predate support for multiple identity providers, + // an empty IdentityProviders list will cause the FederationDomain to use all available identity providers which + // exist in the same namespace, but also to reject all authentication requests when there is more than one identity + // provider currently defined. In this backwards compatibility mode, the name of the identity provider resource + // (e.g. the Name of an OIDCIdentityProvider resource) will be used as the name of the identity provider in this + // FederationDomain. This mode is provided to make upgrading from older versions easier. However, instead of + // relying on this backwards compatibility mode, please consider this mode to be deprecated and please instead + // explicitly list the identity provider using this IdentityProviders field. + // + // +optional + IdentityProviders []FederationDomainIdentityProvider `json:"identityProviders,omitempty"` } // FederationDomainSecrets holds information about this OIDC Provider's secrets. diff --git a/generated/1.19/apis/supervisor/config/v1alpha1/zz_generated.deepcopy.go b/generated/1.19/apis/supervisor/config/v1alpha1/zz_generated.deepcopy.go index 682013782a..5e48e41556 100644 --- a/generated/1.19/apis/supervisor/config/v1alpha1/zz_generated.deepcopy.go +++ b/generated/1.19/apis/supervisor/config/v1alpha1/zz_generated.deepcopy.go @@ -57,6 +57,24 @@ func (in *FederationDomain) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FederationDomainIdentityProvider) DeepCopyInto(out *FederationDomainIdentityProvider) { + *out = *in + in.ObjectRef.DeepCopyInto(&out.ObjectRef) + in.Transforms.DeepCopyInto(&out.Transforms) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FederationDomainIdentityProvider. +func (in *FederationDomainIdentityProvider) DeepCopy() *FederationDomainIdentityProvider { + if in == nil { + return nil + } + out := new(FederationDomainIdentityProvider) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *FederationDomainList) DeepCopyInto(out *FederationDomainList) { *out = *in @@ -118,6 +136,13 @@ func (in *FederationDomainSpec) DeepCopyInto(out *FederationDomainSpec) { *out = new(FederationDomainTLSSpec) **out = **in } + if in.IdentityProviders != nil { + in, out := &in.IdentityProviders, &out.IdentityProviders + *out = make([]FederationDomainIdentityProvider, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } return } @@ -168,6 +193,121 @@ func (in *FederationDomainTLSSpec) DeepCopy() *FederationDomainTLSSpec { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FederationDomainTransforms) DeepCopyInto(out *FederationDomainTransforms) { + *out = *in + if in.Constants != nil { + in, out := &in.Constants, &out.Constants + *out = make([]FederationDomainTransformsConstant, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.Expressions != nil { + in, out := &in.Expressions, &out.Expressions + *out = make([]FederationDomainTransformsExpression, len(*in)) + copy(*out, *in) + } + if in.Examples != nil { + in, out := &in.Examples, &out.Examples + *out = make([]FederationDomainTransformsExample, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FederationDomainTransforms. +func (in *FederationDomainTransforms) DeepCopy() *FederationDomainTransforms { + if in == nil { + return nil + } + out := new(FederationDomainTransforms) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FederationDomainTransformsConstant) DeepCopyInto(out *FederationDomainTransformsConstant) { + *out = *in + if in.StringListValue != nil { + in, out := &in.StringListValue, &out.StringListValue + *out = make([]string, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FederationDomainTransformsConstant. +func (in *FederationDomainTransformsConstant) DeepCopy() *FederationDomainTransformsConstant { + if in == nil { + return nil + } + out := new(FederationDomainTransformsConstant) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FederationDomainTransformsExample) DeepCopyInto(out *FederationDomainTransformsExample) { + *out = *in + if in.Groups != nil { + in, out := &in.Groups, &out.Groups + *out = make([]string, len(*in)) + copy(*out, *in) + } + in.Expects.DeepCopyInto(&out.Expects) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FederationDomainTransformsExample. +func (in *FederationDomainTransformsExample) DeepCopy() *FederationDomainTransformsExample { + if in == nil { + return nil + } + out := new(FederationDomainTransformsExample) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FederationDomainTransformsExampleExpects) DeepCopyInto(out *FederationDomainTransformsExampleExpects) { + *out = *in + if in.Groups != nil { + in, out := &in.Groups, &out.Groups + *out = make([]string, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FederationDomainTransformsExampleExpects. +func (in *FederationDomainTransformsExampleExpects) DeepCopy() *FederationDomainTransformsExampleExpects { + if in == nil { + return nil + } + out := new(FederationDomainTransformsExampleExpects) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FederationDomainTransformsExpression) DeepCopyInto(out *FederationDomainTransformsExpression) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FederationDomainTransformsExpression. +func (in *FederationDomainTransformsExpression) DeepCopy() *FederationDomainTransformsExpression { + if in == nil { + return nil + } + out := new(FederationDomainTransformsExpression) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *OIDCClient) DeepCopyInto(out *OIDCClient) { *out = *in diff --git a/generated/1.19/crds/config.supervisor.pinniped.dev_federationdomains.yaml b/generated/1.19/crds/config.supervisor.pinniped.dev_federationdomains.yaml index 71f7370d11..5e839fccd0 100644 --- a/generated/1.19/crds/config.supervisor.pinniped.dev_federationdomains.yaml +++ b/generated/1.19/crds/config.supervisor.pinniped.dev_federationdomains.yaml @@ -47,6 +47,255 @@ spec: spec: description: Spec of the OIDC provider. properties: + identityProviders: + description: "IdentityProviders is the list of identity providers + available for use by this FederationDomain. \n An identity provider + (e.g. OIDCIdentityProvider or LDAPIdentityProvider) describes how + to connect to a server, how to talk in a specific protocol for authentication, + and how to use the schema of that server/protocol to extract a normalized + user identity. Normalized user identities include a username and + a list of group names. In contrast, IdentityProviders describes + how to use that normalized identity in a group of Kubernetes clusters. + It can perform arbitrary transformations on that normalized identity. + For example, a transformation can add a prefix to all usernames + to help avoid accidental conflicts when multiple identity providers + have different users with the same username (e.g. \"idp1:ryan\" + versus \"idp2:ryan\"). IdentityProviders can also implement arbitrary + authentication rejection policies. Even though a user was able to + authenticate with the identity provider, a policy can disallow the + authentication to the Kubernetes clusters that belong to this FederationDomain. + For example, a policy could disallow the authentication unless the + user belongs to a specific group in the identity provider. \n For + backwards compatibility with versions of Pinniped which predate + support for multiple identity providers, an empty IdentityProviders + list will cause the FederationDomain to use all available identity + providers which exist in the same namespace, but also to reject + all authentication requests when there is more than one identity + provider currently defined. In this backwards compatibility mode, + the name of the identity provider resource (e.g. the Name of an + OIDCIdentityProvider resource) will be used as the name of the identity + provider in this FederationDomain. This mode is provided to make + upgrading from older versions easier. However, instead of relying + on this backwards compatibility mode, please consider this mode + to be deprecated and please instead explicitly list the identity + provider using this IdentityProviders field." + items: + description: FederationDomainIdentityProvider describes how an identity + provider is made available in this FederationDomain. + properties: + displayName: + description: DisplayName is the name of this identity provider + as it will appear to clients. This name ends up in the kubeconfig + of end users, so changing the name of an identity provider + that is in use by end users will be a disruptive change for + those users. + minLength: 1 + type: string + objectRef: + description: ObjectRef is a reference to a Pinniped identity + provider resource. A valid reference is required. If the reference + cannot be resolved then the identity provider will not be + made available. Must refer to a resource of one of the Pinniped + identity provider types, e.g. OIDCIdentityProvider, LDAPIdentityProvider, + ActiveDirectoryIdentityProvider. + properties: + apiGroup: + description: APIGroup is the group for the resource being + referenced. If APIGroup is not specified, the specified + Kind must be in the core API group. For any other third-party + types, APIGroup is required. + type: string + kind: + description: Kind is the type of resource being referenced + type: string + name: + description: Name is the name of resource being referenced + type: string + required: + - kind + - name + type: object + transforms: + description: Transforms is an optional way to specify transformations + to be applied during user authentication and session refresh. + properties: + constants: + description: Constants defines constant variables and their + values which will be made available to the transform expressions. + items: + description: FederationDomainTransformsConstant defines + a constant variable and its value which will be made + available to the transform expressions. This is a union + type, and Type is the discriminator field. + properties: + name: + description: Name determines the name of the constant. + It must be a valid identifier name. + maxLength: 64 + minLength: 1 + pattern: ^[a-zA-Z][_a-zA-Z0-9]*$ + type: string + stringListValue: + description: StringListValue should hold the value + when Type is "stringList", and is otherwise ignored. + items: + type: string + type: array + stringValue: + description: StringValue should hold the value when + Type is "string", and is otherwise ignored. + type: string + type: + description: Type determines the type of the constant, + and indicates which other field should be non-empty. + enum: + - string + - stringList + type: string + required: + - name + - type + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + examples: + description: Examples can optionally be used to ensure that + the sequence of transformation expressions are working + as expected. Examples define sample input identities which + are then run through the expression list, and the results + are compared to the expected results. If any example in + this list fails, then this FederationDomain will not be + available for use, and the error(s) will be added to its + status. This can be used to help guard against programming + mistakes in the expressions, and also act as living documentation + for other administrators to better understand the expressions. + items: + description: FederationDomainTransformsExample defines + a transform example. + properties: + expects: + description: Expects is the expected output of the + entire sequence of transforms when they are run + against the input Username and Groups. + properties: + groups: + description: Groups is the expected list of group + names. + items: + type: string + type: array + message: + description: Message is the expected error message + of the transforms. When Rejected is true, then + Message is the expected message for the policy + which rejected the authentication attempt. When + Rejected is true and Message is blank, then + Message will be treated as the default error + message for authentication attempts which are + rejected by a policy. When Rejected is false, + then Message is the expected error message for + some other non-policy transformation error, + such as a runtime error. When Rejected is false, + there is no default expected Message. + type: string + rejected: + description: Rejected should be set to true when + the expected result of the transforms is that + a policy rejected the authentication attempt. + type: boolean + username: + description: Username is the expected username. + type: string + type: object + groups: + description: Groups is the input list of group names. + items: + type: string + type: array + username: + description: Username is the input username. + minLength: 1 + type: string + required: + - expects + - username + type: object + type: array + expressions: + description: "Expressions are an optional list of transforms + and policies to be executed in the order given during + every authentication attempt, including during every session + refresh. Each is a CEL expression. It may use the basic + CEL language as defined in https://github.com/google/cel-spec/blob/master/doc/langdef.md + plus the CEL string extensions defined in https://github.com/google/cel-go/tree/master/ext#strings. + \n The username and groups extracted from the identity + provider, and the constants defined in this CR, are available + as variables in all expressions. The username is provided + via a variable called `username` and the list of group + names is provided via a variable called `groups` (which + may be an empty list). Each user-provided constants is + provided via a variable named `strConst.varName` for string + constants and `strListConst.varName` for string list constants. + \n The only allowed types for expressions are currently + policy/v1, username/v1, and groups/v1. Each policy/v1 + must return a boolean, and when it returns false, no more + expressions from the list are evaluated and the authentication + attempt is rejected. Transformations of type policy/v1 + do not return usernames or group names, and therefore + cannot change the username or group names. Each username/v1 + transform must return the new username (a string), which + can be the same as the old username. Transformations of + type username/v1 do not return group names, and therefore + cannot change the group names. Each groups/v1 transform + must return the new groups list (list of strings), which + can be the same as the old groups list. Transformations + of type groups/v1 do not return usernames, and therefore + cannot change the usernames. After each expression, the + new (potentially changed) username or groups get passed + to the following expression. \n Any compilation or static + type-checking failure of any expression will cause an + error status on the FederationDomain. During an authentication + attempt, any unexpected runtime evaluation errors (e.g. + division by zero) cause the authentication attempt to + fail. When all expressions evaluate successfully, then + the (potentially changed) username and group names have + been decided for that authentication attempt." + items: + description: FederationDomainTransformsExpression defines + a transform expression. + properties: + expression: + description: Expression is a CEL expression that will + be evaluated based on the Type during an authentication. + minLength: 1 + type: string + message: + description: Message is only used when Type is policy/v1. + It defines an error message to be used when the + policy rejects an authentication attempt. When empty, + a default message will be used. + type: string + type: + description: Type determines the type of the expression. + It must be one of the supported types. + enum: + - policy/v1 + - username/v1 + - groups/v1 + type: string + required: + - expression + - type + type: object + type: array + type: object + required: + - displayName + - objectRef + type: object + type: array issuer: description: "Issuer is the OIDC Provider's issuer, per the OIDC Discovery Metadata document, as well as the identifier that it will use for diff --git a/generated/1.20/README.adoc b/generated/1.20/README.adoc index f027b91cc8..823d6003df 100644 --- a/generated/1.20/README.adoc +++ b/generated/1.20/README.adoc @@ -639,6 +639,25 @@ FederationDomain describes the configuration of an OIDC provider. |=== +[id="{anchor_prefix}-go-pinniped-dev-generated-1-20-apis-supervisor-config-v1alpha1-federationdomainidentityprovider"] +==== FederationDomainIdentityProvider + +FederationDomainIdentityProvider describes how an identity provider is made available in this FederationDomain. + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-20-apis-supervisor-config-v1alpha1-federationdomainspec[$$FederationDomainSpec$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description +| *`displayName`* __string__ | DisplayName is the name of this identity provider as it will appear to clients. This name ends up in the kubeconfig of end users, so changing the name of an identity provider that is in use by end users will be a disruptive change for those users. +| *`objectRef`* __link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.2/#typedlocalobjectreference-v1-core[$$TypedLocalObjectReference$$]__ | ObjectRef is a reference to a Pinniped identity provider resource. A valid reference is required. If the reference cannot be resolved then the identity provider will not be made available. Must refer to a resource of one of the Pinniped identity provider types, e.g. OIDCIdentityProvider, LDAPIdentityProvider, ActiveDirectoryIdentityProvider. +| *`transforms`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-20-apis-supervisor-config-v1alpha1-federationdomaintransforms[$$FederationDomainTransforms$$]__ | Transforms is an optional way to specify transformations to be applied during user authentication and session refresh. +|=== + + [id="{anchor_prefix}-go-pinniped-dev-generated-1-20-apis-supervisor-config-v1alpha1-federationdomainsecrets"] @@ -677,6 +696,9 @@ FederationDomainSpec is a struct that describes an OIDC Provider. | *`issuer`* __string__ | Issuer is the OIDC Provider's issuer, per the OIDC Discovery Metadata document, as well as the identifier that it will use for the iss claim in issued JWTs. This field will also be used as the base URL for any endpoints used by the OIDC Provider (e.g., if your issuer is https://example.com/foo, then your authorization endpoint will look like https://example.com/foo/some/path/to/auth/endpoint). See https://openid.net/specs/openid-connect-discovery-1_0.html#rfc.section.3 for more information. | *`tls`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-20-apis-supervisor-config-v1alpha1-federationdomaintlsspec[$$FederationDomainTLSSpec$$]__ | TLS configures how this FederationDomain is served over Transport Layer Security (TLS). +| *`identityProviders`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-20-apis-supervisor-config-v1alpha1-federationdomainidentityprovider[$$FederationDomainIdentityProvider$$] array__ | IdentityProviders is the list of identity providers available for use by this FederationDomain. + An identity provider (e.g. OIDCIdentityProvider or LDAPIdentityProvider) describes how to connect to a server, how to talk in a specific protocol for authentication, and how to use the schema of that server/protocol to extract a normalized user identity. Normalized user identities include a username and a list of group names. In contrast, IdentityProviders describes how to use that normalized identity in a group of Kubernetes clusters. It can perform arbitrary transformations on that normalized identity. For example, a transformation can add a prefix to all usernames to help avoid accidental conflicts when multiple identity providers have different users with the same username (e.g. "idp1:ryan" versus "idp2:ryan"). IdentityProviders can also implement arbitrary authentication rejection policies. Even though a user was able to authenticate with the identity provider, a policy can disallow the authentication to the Kubernetes clusters that belong to this FederationDomain. For example, a policy could disallow the authentication unless the user belongs to a specific group in the identity provider. + For backwards compatibility with versions of Pinniped which predate support for multiple identity providers, an empty IdentityProviders list will cause the FederationDomain to use all available identity providers which exist in the same namespace, but also to reject all authentication requests when there is more than one identity provider currently defined. In this backwards compatibility mode, the name of the identity provider resource (e.g. the Name of an OIDCIdentityProvider resource) will be used as the name of the identity provider in this FederationDomain. This mode is provided to make upgrading from older versions easier. However, instead of relying on this backwards compatibility mode, please consider this mode to be deprecated and please instead explicitly list the identity provider using this IdentityProviders field. |=== @@ -721,6 +743,106 @@ FederationDomainTLSSpec is a struct that describes the TLS configuration for an |=== +[id="{anchor_prefix}-go-pinniped-dev-generated-1-20-apis-supervisor-config-v1alpha1-federationdomaintransforms"] +==== FederationDomainTransforms + +FederationDomainTransforms defines identity transformations for an identity provider's usage on a FederationDomain. + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-20-apis-supervisor-config-v1alpha1-federationdomainidentityprovider[$$FederationDomainIdentityProvider$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description +| *`constants`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-20-apis-supervisor-config-v1alpha1-federationdomaintransformsconstant[$$FederationDomainTransformsConstant$$] array__ | Constants defines constant variables and their values which will be made available to the transform expressions. +| *`expressions`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-20-apis-supervisor-config-v1alpha1-federationdomaintransformsexpression[$$FederationDomainTransformsExpression$$] array__ | Expressions are an optional list of transforms and policies to be executed in the order given during every authentication attempt, including during every session refresh. Each is a CEL expression. It may use the basic CEL language as defined in https://github.com/google/cel-spec/blob/master/doc/langdef.md plus the CEL string extensions defined in https://github.com/google/cel-go/tree/master/ext#strings. + The username and groups extracted from the identity provider, and the constants defined in this CR, are available as variables in all expressions. The username is provided via a variable called `username` and the list of group names is provided via a variable called `groups` (which may be an empty list). Each user-provided constants is provided via a variable named `strConst.varName` for string constants and `strListConst.varName` for string list constants. + The only allowed types for expressions are currently policy/v1, username/v1, and groups/v1. Each policy/v1 must return a boolean, and when it returns false, no more expressions from the list are evaluated and the authentication attempt is rejected. Transformations of type policy/v1 do not return usernames or group names, and therefore cannot change the username or group names. Each username/v1 transform must return the new username (a string), which can be the same as the old username. Transformations of type username/v1 do not return group names, and therefore cannot change the group names. Each groups/v1 transform must return the new groups list (list of strings), which can be the same as the old groups list. Transformations of type groups/v1 do not return usernames, and therefore cannot change the usernames. After each expression, the new (potentially changed) username or groups get passed to the following expression. + Any compilation or static type-checking failure of any expression will cause an error status on the FederationDomain. During an authentication attempt, any unexpected runtime evaluation errors (e.g. division by zero) cause the authentication attempt to fail. When all expressions evaluate successfully, then the (potentially changed) username and group names have been decided for that authentication attempt. +| *`examples`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-20-apis-supervisor-config-v1alpha1-federationdomaintransformsexample[$$FederationDomainTransformsExample$$] array__ | Examples can optionally be used to ensure that the sequence of transformation expressions are working as expected. Examples define sample input identities which are then run through the expression list, and the results are compared to the expected results. If any example in this list fails, then this FederationDomain will not be available for use, and the error(s) will be added to its status. This can be used to help guard against programming mistakes in the expressions, and also act as living documentation for other administrators to better understand the expressions. +|=== + + +[id="{anchor_prefix}-go-pinniped-dev-generated-1-20-apis-supervisor-config-v1alpha1-federationdomaintransformsconstant"] +==== FederationDomainTransformsConstant + +FederationDomainTransformsConstant defines a constant variable and its value which will be made available to the transform expressions. This is a union type, and Type is the discriminator field. + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-20-apis-supervisor-config-v1alpha1-federationdomaintransforms[$$FederationDomainTransforms$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description +| *`name`* __string__ | Name determines the name of the constant. It must be a valid identifier name. +| *`type`* __string__ | Type determines the type of the constant, and indicates which other field should be non-empty. +| *`stringValue`* __string__ | StringValue should hold the value when Type is "string", and is otherwise ignored. +| *`stringListValue`* __string array__ | StringListValue should hold the value when Type is "stringList", and is otherwise ignored. +|=== + + +[id="{anchor_prefix}-go-pinniped-dev-generated-1-20-apis-supervisor-config-v1alpha1-federationdomaintransformsexample"] +==== FederationDomainTransformsExample + +FederationDomainTransformsExample defines a transform example. + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-20-apis-supervisor-config-v1alpha1-federationdomaintransforms[$$FederationDomainTransforms$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description +| *`username`* __string__ | Username is the input username. +| *`groups`* __string array__ | Groups is the input list of group names. +| *`expects`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-20-apis-supervisor-config-v1alpha1-federationdomaintransformsexampleexpects[$$FederationDomainTransformsExampleExpects$$]__ | Expects is the expected output of the entire sequence of transforms when they are run against the input Username and Groups. +|=== + + +[id="{anchor_prefix}-go-pinniped-dev-generated-1-20-apis-supervisor-config-v1alpha1-federationdomaintransformsexampleexpects"] +==== FederationDomainTransformsExampleExpects + +FederationDomainTransformsExampleExpects defines the expected result for a transforms example. + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-20-apis-supervisor-config-v1alpha1-federationdomaintransformsexample[$$FederationDomainTransformsExample$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description +| *`username`* __string__ | Username is the expected username. +| *`groups`* __string array__ | Groups is the expected list of group names. +| *`rejected`* __boolean__ | Rejected should be set to true when the expected result of the transforms is that a policy rejected the authentication attempt. +| *`message`* __string__ | Message is the expected error message of the transforms. When Rejected is true, then Message is the expected message for the policy which rejected the authentication attempt. When Rejected is true and Message is blank, then Message will be treated as the default error message for authentication attempts which are rejected by a policy. When Rejected is false, then Message is the expected error message for some other non-policy transformation error, such as a runtime error. When Rejected is false, there is no default expected Message. +|=== + + +[id="{anchor_prefix}-go-pinniped-dev-generated-1-20-apis-supervisor-config-v1alpha1-federationdomaintransformsexpression"] +==== FederationDomainTransformsExpression + +FederationDomainTransformsExpression defines a transform expression. + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-20-apis-supervisor-config-v1alpha1-federationdomaintransforms[$$FederationDomainTransforms$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description +| *`type`* __string__ | Type determines the type of the expression. It must be one of the supported types. +| *`expression`* __string__ | Expression is a CEL expression that will be evaluated based on the Type during an authentication. +| *`message`* __string__ | Message is only used when Type is policy/v1. It defines an error message to be used when the policy rejects an authentication attempt. When empty, a default message will be used. +|=== + + [id="{anchor_prefix}-go-pinniped-dev-generated-1-20-apis-supervisor-config-v1alpha1-oidcclient"] ==== OIDCClient diff --git a/generated/1.20/apis/supervisor/config/v1alpha1/types_federationdomain.go b/generated/1.20/apis/supervisor/config/v1alpha1/types_federationdomain.go index 27de4401c0..7f1956845e 100644 --- a/generated/1.20/apis/supervisor/config/v1alpha1/types_federationdomain.go +++ b/generated/1.20/apis/supervisor/config/v1alpha1/types_federationdomain.go @@ -1,4 +1,4 @@ -// Copyright 2020-2022 the Pinniped contributors. All Rights Reserved. +// Copyright 2020-2023 the Pinniped contributors. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 package v1alpha1 @@ -42,6 +42,155 @@ type FederationDomainTLSSpec struct { SecretName string `json:"secretName,omitempty"` } +// FederationDomainTransformsConstant defines a constant variable and its value which will be made available to +// the transform expressions. This is a union type, and Type is the discriminator field. +type FederationDomainTransformsConstant struct { + // Name determines the name of the constant. It must be a valid identifier name. + // +kubebuilder:validation:Pattern=`^[a-zA-Z][_a-zA-Z0-9]*$` + // +kubebuilder:validation:MinLength=1 + // +kubebuilder:validation:MaxLength=64 + Name string `json:"name"` + + // Type determines the type of the constant, and indicates which other field should be non-empty. + // +kubebuilder:validation:Enum=string;stringList + Type string `json:"type"` + + // StringValue should hold the value when Type is "string", and is otherwise ignored. + // +optional + StringValue string `json:"stringValue,omitempty"` + + // StringListValue should hold the value when Type is "stringList", and is otherwise ignored. + // +optional + StringListValue []string `json:"stringListValue,omitempty"` +} + +// FederationDomainTransformsExpression defines a transform expression. +type FederationDomainTransformsExpression struct { + // Type determines the type of the expression. It must be one of the supported types. + // +kubebuilder:validation:Enum=policy/v1;username/v1;groups/v1 + Type string `json:"type"` + + // Expression is a CEL expression that will be evaluated based on the Type during an authentication. + // +kubebuilder:validation:MinLength=1 + Expression string `json:"expression"` + + // Message is only used when Type is policy/v1. It defines an error message to be used when the policy rejects + // an authentication attempt. When empty, a default message will be used. + // +optional + Message string `json:"message,omitempty"` +} + +// FederationDomainTransformsExample defines a transform example. +type FederationDomainTransformsExample struct { + // Username is the input username. + // +kubebuilder:validation:MinLength=1 + Username string `json:"username"` + + // Groups is the input list of group names. + // +optional + Groups []string `json:"groups,omitempty"` + + // Expects is the expected output of the entire sequence of transforms when they are run against the + // input Username and Groups. + Expects FederationDomainTransformsExampleExpects `json:"expects"` +} + +// FederationDomainTransformsExampleExpects defines the expected result for a transforms example. +type FederationDomainTransformsExampleExpects struct { + // Username is the expected username. + // +optional + Username string `json:"username,omitempty"` + + // Groups is the expected list of group names. + // +optional + Groups []string `json:"groups,omitempty"` + + // Rejected should be set to true when the expected result of the transforms is that a policy rejected the + // authentication attempt. + // +optional + Rejected bool `json:"rejected,omitempty"` + + // Message is the expected error message of the transforms. When Rejected is true, then Message is the expected + // message for the policy which rejected the authentication attempt. When Rejected is true and Message is blank, + // then Message will be treated as the default error message for authentication attempts which are rejected by a + // policy. When Rejected is false, then Message is the expected error message for some other non-policy + // transformation error, such as a runtime error. When Rejected is false, there is no default expected Message. + // +optional + Message string `json:"message,omitempty"` +} + +// FederationDomainTransforms defines identity transformations for an identity provider's usage on a FederationDomain. +type FederationDomainTransforms struct { + // Constants defines constant variables and their values which will be made available to the transform expressions. + // +patchMergeKey=name + // +patchStrategy=merge + // +listType=map + // +listMapKey=name + // +optional + Constants []FederationDomainTransformsConstant `json:"constants,omitempty"` + + // Expressions are an optional list of transforms and policies to be executed in the order given during every + // authentication attempt, including during every session refresh. + // Each is a CEL expression. It may use the basic CEL language as defined in + // https://github.com/google/cel-spec/blob/master/doc/langdef.md plus the CEL string extensions defined in + // https://github.com/google/cel-go/tree/master/ext#strings. + // + // The username and groups extracted from the identity provider, and the constants defined in this CR, are + // available as variables in all expressions. The username is provided via a variable called `username` and + // the list of group names is provided via a variable called `groups` (which may be an empty list). + // Each user-provided constants is provided via a variable named `strConst.varName` for string constants + // and `strListConst.varName` for string list constants. + // + // The only allowed types for expressions are currently policy/v1, username/v1, and groups/v1. + // Each policy/v1 must return a boolean, and when it returns false, no more expressions from the list are evaluated + // and the authentication attempt is rejected. + // Transformations of type policy/v1 do not return usernames or group names, and therefore cannot change the + // username or group names. + // Each username/v1 transform must return the new username (a string), which can be the same as the old username. + // Transformations of type username/v1 do not return group names, and therefore cannot change the group names. + // Each groups/v1 transform must return the new groups list (list of strings), which can be the same as the old + // groups list. + // Transformations of type groups/v1 do not return usernames, and therefore cannot change the usernames. + // After each expression, the new (potentially changed) username or groups get passed to the following expression. + // + // Any compilation or static type-checking failure of any expression will cause an error status on the FederationDomain. + // During an authentication attempt, any unexpected runtime evaluation errors (e.g. division by zero) cause the + // authentication attempt to fail. When all expressions evaluate successfully, then the (potentially changed) username + // and group names have been decided for that authentication attempt. + // + // +optional + Expressions []FederationDomainTransformsExpression `json:"expressions,omitempty"` + + // Examples can optionally be used to ensure that the sequence of transformation expressions are working as + // expected. Examples define sample input identities which are then run through the expression list, and the + // results are compared to the expected results. If any example in this list fails, then this + // FederationDomain will not be available for use, and the error(s) will be added to its status. + // This can be used to help guard against programming mistakes in the expressions, and also + // act as living documentation for other administrators to better understand the expressions. + // +optional + Examples []FederationDomainTransformsExample `json:"examples,omitempty"` +} + +// FederationDomainIdentityProvider describes how an identity provider is made available in this FederationDomain. +type FederationDomainIdentityProvider struct { + // DisplayName is the name of this identity provider as it will appear to clients. This name ends up in the + // kubeconfig of end users, so changing the name of an identity provider that is in use by end users will be a + // disruptive change for those users. + // +kubebuilder:validation:MinLength=1 + DisplayName string `json:"displayName"` + + // ObjectRef is a reference to a Pinniped identity provider resource. A valid reference is required. + // If the reference cannot be resolved then the identity provider will not be made available. + // Must refer to a resource of one of the Pinniped identity provider types, e.g. OIDCIdentityProvider, + // LDAPIdentityProvider, ActiveDirectoryIdentityProvider. + ObjectRef corev1.TypedLocalObjectReference `json:"objectRef"` + + // Transforms is an optional way to specify transformations to be applied during user authentication and + // session refresh. + // +optional + Transforms FederationDomainTransforms `json:"transforms,omitempty"` +} + // FederationDomainSpec is a struct that describes an OIDC Provider. type FederationDomainSpec struct { // Issuer is the OIDC Provider's issuer, per the OIDC Discovery Metadata document, as well as the @@ -58,6 +207,32 @@ type FederationDomainSpec struct { // TLS configures how this FederationDomain is served over Transport Layer Security (TLS). // +optional TLS *FederationDomainTLSSpec `json:"tls,omitempty"` + + // IdentityProviders is the list of identity providers available for use by this FederationDomain. + // + // An identity provider (e.g. OIDCIdentityProvider or LDAPIdentityProvider) describes how to connect to a server, + // how to talk in a specific protocol for authentication, and how to use the schema of that server/protocol to + // extract a normalized user identity. Normalized user identities include a username and a list of group names. + // In contrast, IdentityProviders describes how to use that normalized identity in a group of + // Kubernetes clusters. It can perform arbitrary transformations on that normalized identity. For example, a + // transformation can add a prefix to all usernames to help avoid accidental conflicts when multiple identity + // providers have different users with the same username (e.g. "idp1:ryan" versus "idp2:ryan"). + // IdentityProviders can also implement arbitrary authentication rejection policies. + // Even though a user was able to authenticate with the identity provider, a policy can disallow the authentication + // to the Kubernetes clusters that belong to this FederationDomain. For example, a policy could disallow the + // authentication unless the user belongs to a specific group in the identity provider. + // + // For backwards compatibility with versions of Pinniped which predate support for multiple identity providers, + // an empty IdentityProviders list will cause the FederationDomain to use all available identity providers which + // exist in the same namespace, but also to reject all authentication requests when there is more than one identity + // provider currently defined. In this backwards compatibility mode, the name of the identity provider resource + // (e.g. the Name of an OIDCIdentityProvider resource) will be used as the name of the identity provider in this + // FederationDomain. This mode is provided to make upgrading from older versions easier. However, instead of + // relying on this backwards compatibility mode, please consider this mode to be deprecated and please instead + // explicitly list the identity provider using this IdentityProviders field. + // + // +optional + IdentityProviders []FederationDomainIdentityProvider `json:"identityProviders,omitempty"` } // FederationDomainSecrets holds information about this OIDC Provider's secrets. diff --git a/generated/1.20/apis/supervisor/config/v1alpha1/zz_generated.deepcopy.go b/generated/1.20/apis/supervisor/config/v1alpha1/zz_generated.deepcopy.go index 682013782a..5e48e41556 100644 --- a/generated/1.20/apis/supervisor/config/v1alpha1/zz_generated.deepcopy.go +++ b/generated/1.20/apis/supervisor/config/v1alpha1/zz_generated.deepcopy.go @@ -57,6 +57,24 @@ func (in *FederationDomain) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FederationDomainIdentityProvider) DeepCopyInto(out *FederationDomainIdentityProvider) { + *out = *in + in.ObjectRef.DeepCopyInto(&out.ObjectRef) + in.Transforms.DeepCopyInto(&out.Transforms) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FederationDomainIdentityProvider. +func (in *FederationDomainIdentityProvider) DeepCopy() *FederationDomainIdentityProvider { + if in == nil { + return nil + } + out := new(FederationDomainIdentityProvider) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *FederationDomainList) DeepCopyInto(out *FederationDomainList) { *out = *in @@ -118,6 +136,13 @@ func (in *FederationDomainSpec) DeepCopyInto(out *FederationDomainSpec) { *out = new(FederationDomainTLSSpec) **out = **in } + if in.IdentityProviders != nil { + in, out := &in.IdentityProviders, &out.IdentityProviders + *out = make([]FederationDomainIdentityProvider, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } return } @@ -168,6 +193,121 @@ func (in *FederationDomainTLSSpec) DeepCopy() *FederationDomainTLSSpec { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FederationDomainTransforms) DeepCopyInto(out *FederationDomainTransforms) { + *out = *in + if in.Constants != nil { + in, out := &in.Constants, &out.Constants + *out = make([]FederationDomainTransformsConstant, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.Expressions != nil { + in, out := &in.Expressions, &out.Expressions + *out = make([]FederationDomainTransformsExpression, len(*in)) + copy(*out, *in) + } + if in.Examples != nil { + in, out := &in.Examples, &out.Examples + *out = make([]FederationDomainTransformsExample, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FederationDomainTransforms. +func (in *FederationDomainTransforms) DeepCopy() *FederationDomainTransforms { + if in == nil { + return nil + } + out := new(FederationDomainTransforms) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FederationDomainTransformsConstant) DeepCopyInto(out *FederationDomainTransformsConstant) { + *out = *in + if in.StringListValue != nil { + in, out := &in.StringListValue, &out.StringListValue + *out = make([]string, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FederationDomainTransformsConstant. +func (in *FederationDomainTransformsConstant) DeepCopy() *FederationDomainTransformsConstant { + if in == nil { + return nil + } + out := new(FederationDomainTransformsConstant) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FederationDomainTransformsExample) DeepCopyInto(out *FederationDomainTransformsExample) { + *out = *in + if in.Groups != nil { + in, out := &in.Groups, &out.Groups + *out = make([]string, len(*in)) + copy(*out, *in) + } + in.Expects.DeepCopyInto(&out.Expects) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FederationDomainTransformsExample. +func (in *FederationDomainTransformsExample) DeepCopy() *FederationDomainTransformsExample { + if in == nil { + return nil + } + out := new(FederationDomainTransformsExample) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FederationDomainTransformsExampleExpects) DeepCopyInto(out *FederationDomainTransformsExampleExpects) { + *out = *in + if in.Groups != nil { + in, out := &in.Groups, &out.Groups + *out = make([]string, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FederationDomainTransformsExampleExpects. +func (in *FederationDomainTransformsExampleExpects) DeepCopy() *FederationDomainTransformsExampleExpects { + if in == nil { + return nil + } + out := new(FederationDomainTransformsExampleExpects) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FederationDomainTransformsExpression) DeepCopyInto(out *FederationDomainTransformsExpression) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FederationDomainTransformsExpression. +func (in *FederationDomainTransformsExpression) DeepCopy() *FederationDomainTransformsExpression { + if in == nil { + return nil + } + out := new(FederationDomainTransformsExpression) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *OIDCClient) DeepCopyInto(out *OIDCClient) { *out = *in diff --git a/generated/1.20/crds/config.supervisor.pinniped.dev_federationdomains.yaml b/generated/1.20/crds/config.supervisor.pinniped.dev_federationdomains.yaml index 71f7370d11..5e839fccd0 100644 --- a/generated/1.20/crds/config.supervisor.pinniped.dev_federationdomains.yaml +++ b/generated/1.20/crds/config.supervisor.pinniped.dev_federationdomains.yaml @@ -47,6 +47,255 @@ spec: spec: description: Spec of the OIDC provider. properties: + identityProviders: + description: "IdentityProviders is the list of identity providers + available for use by this FederationDomain. \n An identity provider + (e.g. OIDCIdentityProvider or LDAPIdentityProvider) describes how + to connect to a server, how to talk in a specific protocol for authentication, + and how to use the schema of that server/protocol to extract a normalized + user identity. Normalized user identities include a username and + a list of group names. In contrast, IdentityProviders describes + how to use that normalized identity in a group of Kubernetes clusters. + It can perform arbitrary transformations on that normalized identity. + For example, a transformation can add a prefix to all usernames + to help avoid accidental conflicts when multiple identity providers + have different users with the same username (e.g. \"idp1:ryan\" + versus \"idp2:ryan\"). IdentityProviders can also implement arbitrary + authentication rejection policies. Even though a user was able to + authenticate with the identity provider, a policy can disallow the + authentication to the Kubernetes clusters that belong to this FederationDomain. + For example, a policy could disallow the authentication unless the + user belongs to a specific group in the identity provider. \n For + backwards compatibility with versions of Pinniped which predate + support for multiple identity providers, an empty IdentityProviders + list will cause the FederationDomain to use all available identity + providers which exist in the same namespace, but also to reject + all authentication requests when there is more than one identity + provider currently defined. In this backwards compatibility mode, + the name of the identity provider resource (e.g. the Name of an + OIDCIdentityProvider resource) will be used as the name of the identity + provider in this FederationDomain. This mode is provided to make + upgrading from older versions easier. However, instead of relying + on this backwards compatibility mode, please consider this mode + to be deprecated and please instead explicitly list the identity + provider using this IdentityProviders field." + items: + description: FederationDomainIdentityProvider describes how an identity + provider is made available in this FederationDomain. + properties: + displayName: + description: DisplayName is the name of this identity provider + as it will appear to clients. This name ends up in the kubeconfig + of end users, so changing the name of an identity provider + that is in use by end users will be a disruptive change for + those users. + minLength: 1 + type: string + objectRef: + description: ObjectRef is a reference to a Pinniped identity + provider resource. A valid reference is required. If the reference + cannot be resolved then the identity provider will not be + made available. Must refer to a resource of one of the Pinniped + identity provider types, e.g. OIDCIdentityProvider, LDAPIdentityProvider, + ActiveDirectoryIdentityProvider. + properties: + apiGroup: + description: APIGroup is the group for the resource being + referenced. If APIGroup is not specified, the specified + Kind must be in the core API group. For any other third-party + types, APIGroup is required. + type: string + kind: + description: Kind is the type of resource being referenced + type: string + name: + description: Name is the name of resource being referenced + type: string + required: + - kind + - name + type: object + transforms: + description: Transforms is an optional way to specify transformations + to be applied during user authentication and session refresh. + properties: + constants: + description: Constants defines constant variables and their + values which will be made available to the transform expressions. + items: + description: FederationDomainTransformsConstant defines + a constant variable and its value which will be made + available to the transform expressions. This is a union + type, and Type is the discriminator field. + properties: + name: + description: Name determines the name of the constant. + It must be a valid identifier name. + maxLength: 64 + minLength: 1 + pattern: ^[a-zA-Z][_a-zA-Z0-9]*$ + type: string + stringListValue: + description: StringListValue should hold the value + when Type is "stringList", and is otherwise ignored. + items: + type: string + type: array + stringValue: + description: StringValue should hold the value when + Type is "string", and is otherwise ignored. + type: string + type: + description: Type determines the type of the constant, + and indicates which other field should be non-empty. + enum: + - string + - stringList + type: string + required: + - name + - type + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + examples: + description: Examples can optionally be used to ensure that + the sequence of transformation expressions are working + as expected. Examples define sample input identities which + are then run through the expression list, and the results + are compared to the expected results. If any example in + this list fails, then this FederationDomain will not be + available for use, and the error(s) will be added to its + status. This can be used to help guard against programming + mistakes in the expressions, and also act as living documentation + for other administrators to better understand the expressions. + items: + description: FederationDomainTransformsExample defines + a transform example. + properties: + expects: + description: Expects is the expected output of the + entire sequence of transforms when they are run + against the input Username and Groups. + properties: + groups: + description: Groups is the expected list of group + names. + items: + type: string + type: array + message: + description: Message is the expected error message + of the transforms. When Rejected is true, then + Message is the expected message for the policy + which rejected the authentication attempt. When + Rejected is true and Message is blank, then + Message will be treated as the default error + message for authentication attempts which are + rejected by a policy. When Rejected is false, + then Message is the expected error message for + some other non-policy transformation error, + such as a runtime error. When Rejected is false, + there is no default expected Message. + type: string + rejected: + description: Rejected should be set to true when + the expected result of the transforms is that + a policy rejected the authentication attempt. + type: boolean + username: + description: Username is the expected username. + type: string + type: object + groups: + description: Groups is the input list of group names. + items: + type: string + type: array + username: + description: Username is the input username. + minLength: 1 + type: string + required: + - expects + - username + type: object + type: array + expressions: + description: "Expressions are an optional list of transforms + and policies to be executed in the order given during + every authentication attempt, including during every session + refresh. Each is a CEL expression. It may use the basic + CEL language as defined in https://github.com/google/cel-spec/blob/master/doc/langdef.md + plus the CEL string extensions defined in https://github.com/google/cel-go/tree/master/ext#strings. + \n The username and groups extracted from the identity + provider, and the constants defined in this CR, are available + as variables in all expressions. The username is provided + via a variable called `username` and the list of group + names is provided via a variable called `groups` (which + may be an empty list). Each user-provided constants is + provided via a variable named `strConst.varName` for string + constants and `strListConst.varName` for string list constants. + \n The only allowed types for expressions are currently + policy/v1, username/v1, and groups/v1. Each policy/v1 + must return a boolean, and when it returns false, no more + expressions from the list are evaluated and the authentication + attempt is rejected. Transformations of type policy/v1 + do not return usernames or group names, and therefore + cannot change the username or group names. Each username/v1 + transform must return the new username (a string), which + can be the same as the old username. Transformations of + type username/v1 do not return group names, and therefore + cannot change the group names. Each groups/v1 transform + must return the new groups list (list of strings), which + can be the same as the old groups list. Transformations + of type groups/v1 do not return usernames, and therefore + cannot change the usernames. After each expression, the + new (potentially changed) username or groups get passed + to the following expression. \n Any compilation or static + type-checking failure of any expression will cause an + error status on the FederationDomain. During an authentication + attempt, any unexpected runtime evaluation errors (e.g. + division by zero) cause the authentication attempt to + fail. When all expressions evaluate successfully, then + the (potentially changed) username and group names have + been decided for that authentication attempt." + items: + description: FederationDomainTransformsExpression defines + a transform expression. + properties: + expression: + description: Expression is a CEL expression that will + be evaluated based on the Type during an authentication. + minLength: 1 + type: string + message: + description: Message is only used when Type is policy/v1. + It defines an error message to be used when the + policy rejects an authentication attempt. When empty, + a default message will be used. + type: string + type: + description: Type determines the type of the expression. + It must be one of the supported types. + enum: + - policy/v1 + - username/v1 + - groups/v1 + type: string + required: + - expression + - type + type: object + type: array + type: object + required: + - displayName + - objectRef + type: object + type: array issuer: description: "Issuer is the OIDC Provider's issuer, per the OIDC Discovery Metadata document, as well as the identifier that it will use for diff --git a/generated/1.21/README.adoc b/generated/1.21/README.adoc index af34613b4d..dc6a505e80 100644 --- a/generated/1.21/README.adoc +++ b/generated/1.21/README.adoc @@ -639,6 +639,25 @@ FederationDomain describes the configuration of an OIDC provider. |=== +[id="{anchor_prefix}-go-pinniped-dev-generated-1-21-apis-supervisor-config-v1alpha1-federationdomainidentityprovider"] +==== FederationDomainIdentityProvider + +FederationDomainIdentityProvider describes how an identity provider is made available in this FederationDomain. + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-21-apis-supervisor-config-v1alpha1-federationdomainspec[$$FederationDomainSpec$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description +| *`displayName`* __string__ | DisplayName is the name of this identity provider as it will appear to clients. This name ends up in the kubeconfig of end users, so changing the name of an identity provider that is in use by end users will be a disruptive change for those users. +| *`objectRef`* __link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.21/#typedlocalobjectreference-v1-core[$$TypedLocalObjectReference$$]__ | ObjectRef is a reference to a Pinniped identity provider resource. A valid reference is required. If the reference cannot be resolved then the identity provider will not be made available. Must refer to a resource of one of the Pinniped identity provider types, e.g. OIDCIdentityProvider, LDAPIdentityProvider, ActiveDirectoryIdentityProvider. +| *`transforms`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-21-apis-supervisor-config-v1alpha1-federationdomaintransforms[$$FederationDomainTransforms$$]__ | Transforms is an optional way to specify transformations to be applied during user authentication and session refresh. +|=== + + [id="{anchor_prefix}-go-pinniped-dev-generated-1-21-apis-supervisor-config-v1alpha1-federationdomainsecrets"] @@ -677,6 +696,9 @@ FederationDomainSpec is a struct that describes an OIDC Provider. | *`issuer`* __string__ | Issuer is the OIDC Provider's issuer, per the OIDC Discovery Metadata document, as well as the identifier that it will use for the iss claim in issued JWTs. This field will also be used as the base URL for any endpoints used by the OIDC Provider (e.g., if your issuer is https://example.com/foo, then your authorization endpoint will look like https://example.com/foo/some/path/to/auth/endpoint). See https://openid.net/specs/openid-connect-discovery-1_0.html#rfc.section.3 for more information. | *`tls`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-21-apis-supervisor-config-v1alpha1-federationdomaintlsspec[$$FederationDomainTLSSpec$$]__ | TLS configures how this FederationDomain is served over Transport Layer Security (TLS). +| *`identityProviders`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-21-apis-supervisor-config-v1alpha1-federationdomainidentityprovider[$$FederationDomainIdentityProvider$$] array__ | IdentityProviders is the list of identity providers available for use by this FederationDomain. + An identity provider (e.g. OIDCIdentityProvider or LDAPIdentityProvider) describes how to connect to a server, how to talk in a specific protocol for authentication, and how to use the schema of that server/protocol to extract a normalized user identity. Normalized user identities include a username and a list of group names. In contrast, IdentityProviders describes how to use that normalized identity in a group of Kubernetes clusters. It can perform arbitrary transformations on that normalized identity. For example, a transformation can add a prefix to all usernames to help avoid accidental conflicts when multiple identity providers have different users with the same username (e.g. "idp1:ryan" versus "idp2:ryan"). IdentityProviders can also implement arbitrary authentication rejection policies. Even though a user was able to authenticate with the identity provider, a policy can disallow the authentication to the Kubernetes clusters that belong to this FederationDomain. For example, a policy could disallow the authentication unless the user belongs to a specific group in the identity provider. + For backwards compatibility with versions of Pinniped which predate support for multiple identity providers, an empty IdentityProviders list will cause the FederationDomain to use all available identity providers which exist in the same namespace, but also to reject all authentication requests when there is more than one identity provider currently defined. In this backwards compatibility mode, the name of the identity provider resource (e.g. the Name of an OIDCIdentityProvider resource) will be used as the name of the identity provider in this FederationDomain. This mode is provided to make upgrading from older versions easier. However, instead of relying on this backwards compatibility mode, please consider this mode to be deprecated and please instead explicitly list the identity provider using this IdentityProviders field. |=== @@ -721,6 +743,106 @@ FederationDomainTLSSpec is a struct that describes the TLS configuration for an |=== +[id="{anchor_prefix}-go-pinniped-dev-generated-1-21-apis-supervisor-config-v1alpha1-federationdomaintransforms"] +==== FederationDomainTransforms + +FederationDomainTransforms defines identity transformations for an identity provider's usage on a FederationDomain. + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-21-apis-supervisor-config-v1alpha1-federationdomainidentityprovider[$$FederationDomainIdentityProvider$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description +| *`constants`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-21-apis-supervisor-config-v1alpha1-federationdomaintransformsconstant[$$FederationDomainTransformsConstant$$] array__ | Constants defines constant variables and their values which will be made available to the transform expressions. +| *`expressions`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-21-apis-supervisor-config-v1alpha1-federationdomaintransformsexpression[$$FederationDomainTransformsExpression$$] array__ | Expressions are an optional list of transforms and policies to be executed in the order given during every authentication attempt, including during every session refresh. Each is a CEL expression. It may use the basic CEL language as defined in https://github.com/google/cel-spec/blob/master/doc/langdef.md plus the CEL string extensions defined in https://github.com/google/cel-go/tree/master/ext#strings. + The username and groups extracted from the identity provider, and the constants defined in this CR, are available as variables in all expressions. The username is provided via a variable called `username` and the list of group names is provided via a variable called `groups` (which may be an empty list). Each user-provided constants is provided via a variable named `strConst.varName` for string constants and `strListConst.varName` for string list constants. + The only allowed types for expressions are currently policy/v1, username/v1, and groups/v1. Each policy/v1 must return a boolean, and when it returns false, no more expressions from the list are evaluated and the authentication attempt is rejected. Transformations of type policy/v1 do not return usernames or group names, and therefore cannot change the username or group names. Each username/v1 transform must return the new username (a string), which can be the same as the old username. Transformations of type username/v1 do not return group names, and therefore cannot change the group names. Each groups/v1 transform must return the new groups list (list of strings), which can be the same as the old groups list. Transformations of type groups/v1 do not return usernames, and therefore cannot change the usernames. After each expression, the new (potentially changed) username or groups get passed to the following expression. + Any compilation or static type-checking failure of any expression will cause an error status on the FederationDomain. During an authentication attempt, any unexpected runtime evaluation errors (e.g. division by zero) cause the authentication attempt to fail. When all expressions evaluate successfully, then the (potentially changed) username and group names have been decided for that authentication attempt. +| *`examples`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-21-apis-supervisor-config-v1alpha1-federationdomaintransformsexample[$$FederationDomainTransformsExample$$] array__ | Examples can optionally be used to ensure that the sequence of transformation expressions are working as expected. Examples define sample input identities which are then run through the expression list, and the results are compared to the expected results. If any example in this list fails, then this FederationDomain will not be available for use, and the error(s) will be added to its status. This can be used to help guard against programming mistakes in the expressions, and also act as living documentation for other administrators to better understand the expressions. +|=== + + +[id="{anchor_prefix}-go-pinniped-dev-generated-1-21-apis-supervisor-config-v1alpha1-federationdomaintransformsconstant"] +==== FederationDomainTransformsConstant + +FederationDomainTransformsConstant defines a constant variable and its value which will be made available to the transform expressions. This is a union type, and Type is the discriminator field. + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-21-apis-supervisor-config-v1alpha1-federationdomaintransforms[$$FederationDomainTransforms$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description +| *`name`* __string__ | Name determines the name of the constant. It must be a valid identifier name. +| *`type`* __string__ | Type determines the type of the constant, and indicates which other field should be non-empty. +| *`stringValue`* __string__ | StringValue should hold the value when Type is "string", and is otherwise ignored. +| *`stringListValue`* __string array__ | StringListValue should hold the value when Type is "stringList", and is otherwise ignored. +|=== + + +[id="{anchor_prefix}-go-pinniped-dev-generated-1-21-apis-supervisor-config-v1alpha1-federationdomaintransformsexample"] +==== FederationDomainTransformsExample + +FederationDomainTransformsExample defines a transform example. + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-21-apis-supervisor-config-v1alpha1-federationdomaintransforms[$$FederationDomainTransforms$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description +| *`username`* __string__ | Username is the input username. +| *`groups`* __string array__ | Groups is the input list of group names. +| *`expects`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-21-apis-supervisor-config-v1alpha1-federationdomaintransformsexampleexpects[$$FederationDomainTransformsExampleExpects$$]__ | Expects is the expected output of the entire sequence of transforms when they are run against the input Username and Groups. +|=== + + +[id="{anchor_prefix}-go-pinniped-dev-generated-1-21-apis-supervisor-config-v1alpha1-federationdomaintransformsexampleexpects"] +==== FederationDomainTransformsExampleExpects + +FederationDomainTransformsExampleExpects defines the expected result for a transforms example. + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-21-apis-supervisor-config-v1alpha1-federationdomaintransformsexample[$$FederationDomainTransformsExample$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description +| *`username`* __string__ | Username is the expected username. +| *`groups`* __string array__ | Groups is the expected list of group names. +| *`rejected`* __boolean__ | Rejected should be set to true when the expected result of the transforms is that a policy rejected the authentication attempt. +| *`message`* __string__ | Message is the expected error message of the transforms. When Rejected is true, then Message is the expected message for the policy which rejected the authentication attempt. When Rejected is true and Message is blank, then Message will be treated as the default error message for authentication attempts which are rejected by a policy. When Rejected is false, then Message is the expected error message for some other non-policy transformation error, such as a runtime error. When Rejected is false, there is no default expected Message. +|=== + + +[id="{anchor_prefix}-go-pinniped-dev-generated-1-21-apis-supervisor-config-v1alpha1-federationdomaintransformsexpression"] +==== FederationDomainTransformsExpression + +FederationDomainTransformsExpression defines a transform expression. + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-21-apis-supervisor-config-v1alpha1-federationdomaintransforms[$$FederationDomainTransforms$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description +| *`type`* __string__ | Type determines the type of the expression. It must be one of the supported types. +| *`expression`* __string__ | Expression is a CEL expression that will be evaluated based on the Type during an authentication. +| *`message`* __string__ | Message is only used when Type is policy/v1. It defines an error message to be used when the policy rejects an authentication attempt. When empty, a default message will be used. +|=== + + [id="{anchor_prefix}-go-pinniped-dev-generated-1-21-apis-supervisor-config-v1alpha1-oidcclient"] ==== OIDCClient diff --git a/generated/1.21/apis/supervisor/config/v1alpha1/types_federationdomain.go b/generated/1.21/apis/supervisor/config/v1alpha1/types_federationdomain.go index 27de4401c0..7f1956845e 100644 --- a/generated/1.21/apis/supervisor/config/v1alpha1/types_federationdomain.go +++ b/generated/1.21/apis/supervisor/config/v1alpha1/types_federationdomain.go @@ -1,4 +1,4 @@ -// Copyright 2020-2022 the Pinniped contributors. All Rights Reserved. +// Copyright 2020-2023 the Pinniped contributors. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 package v1alpha1 @@ -42,6 +42,155 @@ type FederationDomainTLSSpec struct { SecretName string `json:"secretName,omitempty"` } +// FederationDomainTransformsConstant defines a constant variable and its value which will be made available to +// the transform expressions. This is a union type, and Type is the discriminator field. +type FederationDomainTransformsConstant struct { + // Name determines the name of the constant. It must be a valid identifier name. + // +kubebuilder:validation:Pattern=`^[a-zA-Z][_a-zA-Z0-9]*$` + // +kubebuilder:validation:MinLength=1 + // +kubebuilder:validation:MaxLength=64 + Name string `json:"name"` + + // Type determines the type of the constant, and indicates which other field should be non-empty. + // +kubebuilder:validation:Enum=string;stringList + Type string `json:"type"` + + // StringValue should hold the value when Type is "string", and is otherwise ignored. + // +optional + StringValue string `json:"stringValue,omitempty"` + + // StringListValue should hold the value when Type is "stringList", and is otherwise ignored. + // +optional + StringListValue []string `json:"stringListValue,omitempty"` +} + +// FederationDomainTransformsExpression defines a transform expression. +type FederationDomainTransformsExpression struct { + // Type determines the type of the expression. It must be one of the supported types. + // +kubebuilder:validation:Enum=policy/v1;username/v1;groups/v1 + Type string `json:"type"` + + // Expression is a CEL expression that will be evaluated based on the Type during an authentication. + // +kubebuilder:validation:MinLength=1 + Expression string `json:"expression"` + + // Message is only used when Type is policy/v1. It defines an error message to be used when the policy rejects + // an authentication attempt. When empty, a default message will be used. + // +optional + Message string `json:"message,omitempty"` +} + +// FederationDomainTransformsExample defines a transform example. +type FederationDomainTransformsExample struct { + // Username is the input username. + // +kubebuilder:validation:MinLength=1 + Username string `json:"username"` + + // Groups is the input list of group names. + // +optional + Groups []string `json:"groups,omitempty"` + + // Expects is the expected output of the entire sequence of transforms when they are run against the + // input Username and Groups. + Expects FederationDomainTransformsExampleExpects `json:"expects"` +} + +// FederationDomainTransformsExampleExpects defines the expected result for a transforms example. +type FederationDomainTransformsExampleExpects struct { + // Username is the expected username. + // +optional + Username string `json:"username,omitempty"` + + // Groups is the expected list of group names. + // +optional + Groups []string `json:"groups,omitempty"` + + // Rejected should be set to true when the expected result of the transforms is that a policy rejected the + // authentication attempt. + // +optional + Rejected bool `json:"rejected,omitempty"` + + // Message is the expected error message of the transforms. When Rejected is true, then Message is the expected + // message for the policy which rejected the authentication attempt. When Rejected is true and Message is blank, + // then Message will be treated as the default error message for authentication attempts which are rejected by a + // policy. When Rejected is false, then Message is the expected error message for some other non-policy + // transformation error, such as a runtime error. When Rejected is false, there is no default expected Message. + // +optional + Message string `json:"message,omitempty"` +} + +// FederationDomainTransforms defines identity transformations for an identity provider's usage on a FederationDomain. +type FederationDomainTransforms struct { + // Constants defines constant variables and their values which will be made available to the transform expressions. + // +patchMergeKey=name + // +patchStrategy=merge + // +listType=map + // +listMapKey=name + // +optional + Constants []FederationDomainTransformsConstant `json:"constants,omitempty"` + + // Expressions are an optional list of transforms and policies to be executed in the order given during every + // authentication attempt, including during every session refresh. + // Each is a CEL expression. It may use the basic CEL language as defined in + // https://github.com/google/cel-spec/blob/master/doc/langdef.md plus the CEL string extensions defined in + // https://github.com/google/cel-go/tree/master/ext#strings. + // + // The username and groups extracted from the identity provider, and the constants defined in this CR, are + // available as variables in all expressions. The username is provided via a variable called `username` and + // the list of group names is provided via a variable called `groups` (which may be an empty list). + // Each user-provided constants is provided via a variable named `strConst.varName` for string constants + // and `strListConst.varName` for string list constants. + // + // The only allowed types for expressions are currently policy/v1, username/v1, and groups/v1. + // Each policy/v1 must return a boolean, and when it returns false, no more expressions from the list are evaluated + // and the authentication attempt is rejected. + // Transformations of type policy/v1 do not return usernames or group names, and therefore cannot change the + // username or group names. + // Each username/v1 transform must return the new username (a string), which can be the same as the old username. + // Transformations of type username/v1 do not return group names, and therefore cannot change the group names. + // Each groups/v1 transform must return the new groups list (list of strings), which can be the same as the old + // groups list. + // Transformations of type groups/v1 do not return usernames, and therefore cannot change the usernames. + // After each expression, the new (potentially changed) username or groups get passed to the following expression. + // + // Any compilation or static type-checking failure of any expression will cause an error status on the FederationDomain. + // During an authentication attempt, any unexpected runtime evaluation errors (e.g. division by zero) cause the + // authentication attempt to fail. When all expressions evaluate successfully, then the (potentially changed) username + // and group names have been decided for that authentication attempt. + // + // +optional + Expressions []FederationDomainTransformsExpression `json:"expressions,omitempty"` + + // Examples can optionally be used to ensure that the sequence of transformation expressions are working as + // expected. Examples define sample input identities which are then run through the expression list, and the + // results are compared to the expected results. If any example in this list fails, then this + // FederationDomain will not be available for use, and the error(s) will be added to its status. + // This can be used to help guard against programming mistakes in the expressions, and also + // act as living documentation for other administrators to better understand the expressions. + // +optional + Examples []FederationDomainTransformsExample `json:"examples,omitempty"` +} + +// FederationDomainIdentityProvider describes how an identity provider is made available in this FederationDomain. +type FederationDomainIdentityProvider struct { + // DisplayName is the name of this identity provider as it will appear to clients. This name ends up in the + // kubeconfig of end users, so changing the name of an identity provider that is in use by end users will be a + // disruptive change for those users. + // +kubebuilder:validation:MinLength=1 + DisplayName string `json:"displayName"` + + // ObjectRef is a reference to a Pinniped identity provider resource. A valid reference is required. + // If the reference cannot be resolved then the identity provider will not be made available. + // Must refer to a resource of one of the Pinniped identity provider types, e.g. OIDCIdentityProvider, + // LDAPIdentityProvider, ActiveDirectoryIdentityProvider. + ObjectRef corev1.TypedLocalObjectReference `json:"objectRef"` + + // Transforms is an optional way to specify transformations to be applied during user authentication and + // session refresh. + // +optional + Transforms FederationDomainTransforms `json:"transforms,omitempty"` +} + // FederationDomainSpec is a struct that describes an OIDC Provider. type FederationDomainSpec struct { // Issuer is the OIDC Provider's issuer, per the OIDC Discovery Metadata document, as well as the @@ -58,6 +207,32 @@ type FederationDomainSpec struct { // TLS configures how this FederationDomain is served over Transport Layer Security (TLS). // +optional TLS *FederationDomainTLSSpec `json:"tls,omitempty"` + + // IdentityProviders is the list of identity providers available for use by this FederationDomain. + // + // An identity provider (e.g. OIDCIdentityProvider or LDAPIdentityProvider) describes how to connect to a server, + // how to talk in a specific protocol for authentication, and how to use the schema of that server/protocol to + // extract a normalized user identity. Normalized user identities include a username and a list of group names. + // In contrast, IdentityProviders describes how to use that normalized identity in a group of + // Kubernetes clusters. It can perform arbitrary transformations on that normalized identity. For example, a + // transformation can add a prefix to all usernames to help avoid accidental conflicts when multiple identity + // providers have different users with the same username (e.g. "idp1:ryan" versus "idp2:ryan"). + // IdentityProviders can also implement arbitrary authentication rejection policies. + // Even though a user was able to authenticate with the identity provider, a policy can disallow the authentication + // to the Kubernetes clusters that belong to this FederationDomain. For example, a policy could disallow the + // authentication unless the user belongs to a specific group in the identity provider. + // + // For backwards compatibility with versions of Pinniped which predate support for multiple identity providers, + // an empty IdentityProviders list will cause the FederationDomain to use all available identity providers which + // exist in the same namespace, but also to reject all authentication requests when there is more than one identity + // provider currently defined. In this backwards compatibility mode, the name of the identity provider resource + // (e.g. the Name of an OIDCIdentityProvider resource) will be used as the name of the identity provider in this + // FederationDomain. This mode is provided to make upgrading from older versions easier. However, instead of + // relying on this backwards compatibility mode, please consider this mode to be deprecated and please instead + // explicitly list the identity provider using this IdentityProviders field. + // + // +optional + IdentityProviders []FederationDomainIdentityProvider `json:"identityProviders,omitempty"` } // FederationDomainSecrets holds information about this OIDC Provider's secrets. diff --git a/generated/1.21/apis/supervisor/config/v1alpha1/zz_generated.deepcopy.go b/generated/1.21/apis/supervisor/config/v1alpha1/zz_generated.deepcopy.go index 682013782a..5e48e41556 100644 --- a/generated/1.21/apis/supervisor/config/v1alpha1/zz_generated.deepcopy.go +++ b/generated/1.21/apis/supervisor/config/v1alpha1/zz_generated.deepcopy.go @@ -57,6 +57,24 @@ func (in *FederationDomain) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FederationDomainIdentityProvider) DeepCopyInto(out *FederationDomainIdentityProvider) { + *out = *in + in.ObjectRef.DeepCopyInto(&out.ObjectRef) + in.Transforms.DeepCopyInto(&out.Transforms) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FederationDomainIdentityProvider. +func (in *FederationDomainIdentityProvider) DeepCopy() *FederationDomainIdentityProvider { + if in == nil { + return nil + } + out := new(FederationDomainIdentityProvider) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *FederationDomainList) DeepCopyInto(out *FederationDomainList) { *out = *in @@ -118,6 +136,13 @@ func (in *FederationDomainSpec) DeepCopyInto(out *FederationDomainSpec) { *out = new(FederationDomainTLSSpec) **out = **in } + if in.IdentityProviders != nil { + in, out := &in.IdentityProviders, &out.IdentityProviders + *out = make([]FederationDomainIdentityProvider, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } return } @@ -168,6 +193,121 @@ func (in *FederationDomainTLSSpec) DeepCopy() *FederationDomainTLSSpec { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FederationDomainTransforms) DeepCopyInto(out *FederationDomainTransforms) { + *out = *in + if in.Constants != nil { + in, out := &in.Constants, &out.Constants + *out = make([]FederationDomainTransformsConstant, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.Expressions != nil { + in, out := &in.Expressions, &out.Expressions + *out = make([]FederationDomainTransformsExpression, len(*in)) + copy(*out, *in) + } + if in.Examples != nil { + in, out := &in.Examples, &out.Examples + *out = make([]FederationDomainTransformsExample, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FederationDomainTransforms. +func (in *FederationDomainTransforms) DeepCopy() *FederationDomainTransforms { + if in == nil { + return nil + } + out := new(FederationDomainTransforms) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FederationDomainTransformsConstant) DeepCopyInto(out *FederationDomainTransformsConstant) { + *out = *in + if in.StringListValue != nil { + in, out := &in.StringListValue, &out.StringListValue + *out = make([]string, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FederationDomainTransformsConstant. +func (in *FederationDomainTransformsConstant) DeepCopy() *FederationDomainTransformsConstant { + if in == nil { + return nil + } + out := new(FederationDomainTransformsConstant) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FederationDomainTransformsExample) DeepCopyInto(out *FederationDomainTransformsExample) { + *out = *in + if in.Groups != nil { + in, out := &in.Groups, &out.Groups + *out = make([]string, len(*in)) + copy(*out, *in) + } + in.Expects.DeepCopyInto(&out.Expects) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FederationDomainTransformsExample. +func (in *FederationDomainTransformsExample) DeepCopy() *FederationDomainTransformsExample { + if in == nil { + return nil + } + out := new(FederationDomainTransformsExample) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FederationDomainTransformsExampleExpects) DeepCopyInto(out *FederationDomainTransformsExampleExpects) { + *out = *in + if in.Groups != nil { + in, out := &in.Groups, &out.Groups + *out = make([]string, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FederationDomainTransformsExampleExpects. +func (in *FederationDomainTransformsExampleExpects) DeepCopy() *FederationDomainTransformsExampleExpects { + if in == nil { + return nil + } + out := new(FederationDomainTransformsExampleExpects) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FederationDomainTransformsExpression) DeepCopyInto(out *FederationDomainTransformsExpression) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FederationDomainTransformsExpression. +func (in *FederationDomainTransformsExpression) DeepCopy() *FederationDomainTransformsExpression { + if in == nil { + return nil + } + out := new(FederationDomainTransformsExpression) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *OIDCClient) DeepCopyInto(out *OIDCClient) { *out = *in diff --git a/generated/1.21/crds/config.supervisor.pinniped.dev_federationdomains.yaml b/generated/1.21/crds/config.supervisor.pinniped.dev_federationdomains.yaml index 71f7370d11..5e839fccd0 100644 --- a/generated/1.21/crds/config.supervisor.pinniped.dev_federationdomains.yaml +++ b/generated/1.21/crds/config.supervisor.pinniped.dev_federationdomains.yaml @@ -47,6 +47,255 @@ spec: spec: description: Spec of the OIDC provider. properties: + identityProviders: + description: "IdentityProviders is the list of identity providers + available for use by this FederationDomain. \n An identity provider + (e.g. OIDCIdentityProvider or LDAPIdentityProvider) describes how + to connect to a server, how to talk in a specific protocol for authentication, + and how to use the schema of that server/protocol to extract a normalized + user identity. Normalized user identities include a username and + a list of group names. In contrast, IdentityProviders describes + how to use that normalized identity in a group of Kubernetes clusters. + It can perform arbitrary transformations on that normalized identity. + For example, a transformation can add a prefix to all usernames + to help avoid accidental conflicts when multiple identity providers + have different users with the same username (e.g. \"idp1:ryan\" + versus \"idp2:ryan\"). IdentityProviders can also implement arbitrary + authentication rejection policies. Even though a user was able to + authenticate with the identity provider, a policy can disallow the + authentication to the Kubernetes clusters that belong to this FederationDomain. + For example, a policy could disallow the authentication unless the + user belongs to a specific group in the identity provider. \n For + backwards compatibility with versions of Pinniped which predate + support for multiple identity providers, an empty IdentityProviders + list will cause the FederationDomain to use all available identity + providers which exist in the same namespace, but also to reject + all authentication requests when there is more than one identity + provider currently defined. In this backwards compatibility mode, + the name of the identity provider resource (e.g. the Name of an + OIDCIdentityProvider resource) will be used as the name of the identity + provider in this FederationDomain. This mode is provided to make + upgrading from older versions easier. However, instead of relying + on this backwards compatibility mode, please consider this mode + to be deprecated and please instead explicitly list the identity + provider using this IdentityProviders field." + items: + description: FederationDomainIdentityProvider describes how an identity + provider is made available in this FederationDomain. + properties: + displayName: + description: DisplayName is the name of this identity provider + as it will appear to clients. This name ends up in the kubeconfig + of end users, so changing the name of an identity provider + that is in use by end users will be a disruptive change for + those users. + minLength: 1 + type: string + objectRef: + description: ObjectRef is a reference to a Pinniped identity + provider resource. A valid reference is required. If the reference + cannot be resolved then the identity provider will not be + made available. Must refer to a resource of one of the Pinniped + identity provider types, e.g. OIDCIdentityProvider, LDAPIdentityProvider, + ActiveDirectoryIdentityProvider. + properties: + apiGroup: + description: APIGroup is the group for the resource being + referenced. If APIGroup is not specified, the specified + Kind must be in the core API group. For any other third-party + types, APIGroup is required. + type: string + kind: + description: Kind is the type of resource being referenced + type: string + name: + description: Name is the name of resource being referenced + type: string + required: + - kind + - name + type: object + transforms: + description: Transforms is an optional way to specify transformations + to be applied during user authentication and session refresh. + properties: + constants: + description: Constants defines constant variables and their + values which will be made available to the transform expressions. + items: + description: FederationDomainTransformsConstant defines + a constant variable and its value which will be made + available to the transform expressions. This is a union + type, and Type is the discriminator field. + properties: + name: + description: Name determines the name of the constant. + It must be a valid identifier name. + maxLength: 64 + minLength: 1 + pattern: ^[a-zA-Z][_a-zA-Z0-9]*$ + type: string + stringListValue: + description: StringListValue should hold the value + when Type is "stringList", and is otherwise ignored. + items: + type: string + type: array + stringValue: + description: StringValue should hold the value when + Type is "string", and is otherwise ignored. + type: string + type: + description: Type determines the type of the constant, + and indicates which other field should be non-empty. + enum: + - string + - stringList + type: string + required: + - name + - type + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + examples: + description: Examples can optionally be used to ensure that + the sequence of transformation expressions are working + as expected. Examples define sample input identities which + are then run through the expression list, and the results + are compared to the expected results. If any example in + this list fails, then this FederationDomain will not be + available for use, and the error(s) will be added to its + status. This can be used to help guard against programming + mistakes in the expressions, and also act as living documentation + for other administrators to better understand the expressions. + items: + description: FederationDomainTransformsExample defines + a transform example. + properties: + expects: + description: Expects is the expected output of the + entire sequence of transforms when they are run + against the input Username and Groups. + properties: + groups: + description: Groups is the expected list of group + names. + items: + type: string + type: array + message: + description: Message is the expected error message + of the transforms. When Rejected is true, then + Message is the expected message for the policy + which rejected the authentication attempt. When + Rejected is true and Message is blank, then + Message will be treated as the default error + message for authentication attempts which are + rejected by a policy. When Rejected is false, + then Message is the expected error message for + some other non-policy transformation error, + such as a runtime error. When Rejected is false, + there is no default expected Message. + type: string + rejected: + description: Rejected should be set to true when + the expected result of the transforms is that + a policy rejected the authentication attempt. + type: boolean + username: + description: Username is the expected username. + type: string + type: object + groups: + description: Groups is the input list of group names. + items: + type: string + type: array + username: + description: Username is the input username. + minLength: 1 + type: string + required: + - expects + - username + type: object + type: array + expressions: + description: "Expressions are an optional list of transforms + and policies to be executed in the order given during + every authentication attempt, including during every session + refresh. Each is a CEL expression. It may use the basic + CEL language as defined in https://github.com/google/cel-spec/blob/master/doc/langdef.md + plus the CEL string extensions defined in https://github.com/google/cel-go/tree/master/ext#strings. + \n The username and groups extracted from the identity + provider, and the constants defined in this CR, are available + as variables in all expressions. The username is provided + via a variable called `username` and the list of group + names is provided via a variable called `groups` (which + may be an empty list). Each user-provided constants is + provided via a variable named `strConst.varName` for string + constants and `strListConst.varName` for string list constants. + \n The only allowed types for expressions are currently + policy/v1, username/v1, and groups/v1. Each policy/v1 + must return a boolean, and when it returns false, no more + expressions from the list are evaluated and the authentication + attempt is rejected. Transformations of type policy/v1 + do not return usernames or group names, and therefore + cannot change the username or group names. Each username/v1 + transform must return the new username (a string), which + can be the same as the old username. Transformations of + type username/v1 do not return group names, and therefore + cannot change the group names. Each groups/v1 transform + must return the new groups list (list of strings), which + can be the same as the old groups list. Transformations + of type groups/v1 do not return usernames, and therefore + cannot change the usernames. After each expression, the + new (potentially changed) username or groups get passed + to the following expression. \n Any compilation or static + type-checking failure of any expression will cause an + error status on the FederationDomain. During an authentication + attempt, any unexpected runtime evaluation errors (e.g. + division by zero) cause the authentication attempt to + fail. When all expressions evaluate successfully, then + the (potentially changed) username and group names have + been decided for that authentication attempt." + items: + description: FederationDomainTransformsExpression defines + a transform expression. + properties: + expression: + description: Expression is a CEL expression that will + be evaluated based on the Type during an authentication. + minLength: 1 + type: string + message: + description: Message is only used when Type is policy/v1. + It defines an error message to be used when the + policy rejects an authentication attempt. When empty, + a default message will be used. + type: string + type: + description: Type determines the type of the expression. + It must be one of the supported types. + enum: + - policy/v1 + - username/v1 + - groups/v1 + type: string + required: + - expression + - type + type: object + type: array + type: object + required: + - displayName + - objectRef + type: object + type: array issuer: description: "Issuer is the OIDC Provider's issuer, per the OIDC Discovery Metadata document, as well as the identifier that it will use for diff --git a/generated/1.22/README.adoc b/generated/1.22/README.adoc index 3967d637d5..9d10139b25 100644 --- a/generated/1.22/README.adoc +++ b/generated/1.22/README.adoc @@ -639,6 +639,25 @@ FederationDomain describes the configuration of an OIDC provider. |=== +[id="{anchor_prefix}-go-pinniped-dev-generated-1-22-apis-supervisor-config-v1alpha1-federationdomainidentityprovider"] +==== FederationDomainIdentityProvider + +FederationDomainIdentityProvider describes how an identity provider is made available in this FederationDomain. + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-22-apis-supervisor-config-v1alpha1-federationdomainspec[$$FederationDomainSpec$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description +| *`displayName`* __string__ | DisplayName is the name of this identity provider as it will appear to clients. This name ends up in the kubeconfig of end users, so changing the name of an identity provider that is in use by end users will be a disruptive change for those users. +| *`objectRef`* __link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.22/#typedlocalobjectreference-v1-core[$$TypedLocalObjectReference$$]__ | ObjectRef is a reference to a Pinniped identity provider resource. A valid reference is required. If the reference cannot be resolved then the identity provider will not be made available. Must refer to a resource of one of the Pinniped identity provider types, e.g. OIDCIdentityProvider, LDAPIdentityProvider, ActiveDirectoryIdentityProvider. +| *`transforms`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-22-apis-supervisor-config-v1alpha1-federationdomaintransforms[$$FederationDomainTransforms$$]__ | Transforms is an optional way to specify transformations to be applied during user authentication and session refresh. +|=== + + [id="{anchor_prefix}-go-pinniped-dev-generated-1-22-apis-supervisor-config-v1alpha1-federationdomainsecrets"] @@ -677,6 +696,9 @@ FederationDomainSpec is a struct that describes an OIDC Provider. | *`issuer`* __string__ | Issuer is the OIDC Provider's issuer, per the OIDC Discovery Metadata document, as well as the identifier that it will use for the iss claim in issued JWTs. This field will also be used as the base URL for any endpoints used by the OIDC Provider (e.g., if your issuer is https://example.com/foo, then your authorization endpoint will look like https://example.com/foo/some/path/to/auth/endpoint). See https://openid.net/specs/openid-connect-discovery-1_0.html#rfc.section.3 for more information. | *`tls`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-22-apis-supervisor-config-v1alpha1-federationdomaintlsspec[$$FederationDomainTLSSpec$$]__ | TLS configures how this FederationDomain is served over Transport Layer Security (TLS). +| *`identityProviders`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-22-apis-supervisor-config-v1alpha1-federationdomainidentityprovider[$$FederationDomainIdentityProvider$$] array__ | IdentityProviders is the list of identity providers available for use by this FederationDomain. + An identity provider (e.g. OIDCIdentityProvider or LDAPIdentityProvider) describes how to connect to a server, how to talk in a specific protocol for authentication, and how to use the schema of that server/protocol to extract a normalized user identity. Normalized user identities include a username and a list of group names. In contrast, IdentityProviders describes how to use that normalized identity in a group of Kubernetes clusters. It can perform arbitrary transformations on that normalized identity. For example, a transformation can add a prefix to all usernames to help avoid accidental conflicts when multiple identity providers have different users with the same username (e.g. "idp1:ryan" versus "idp2:ryan"). IdentityProviders can also implement arbitrary authentication rejection policies. Even though a user was able to authenticate with the identity provider, a policy can disallow the authentication to the Kubernetes clusters that belong to this FederationDomain. For example, a policy could disallow the authentication unless the user belongs to a specific group in the identity provider. + For backwards compatibility with versions of Pinniped which predate support for multiple identity providers, an empty IdentityProviders list will cause the FederationDomain to use all available identity providers which exist in the same namespace, but also to reject all authentication requests when there is more than one identity provider currently defined. In this backwards compatibility mode, the name of the identity provider resource (e.g. the Name of an OIDCIdentityProvider resource) will be used as the name of the identity provider in this FederationDomain. This mode is provided to make upgrading from older versions easier. However, instead of relying on this backwards compatibility mode, please consider this mode to be deprecated and please instead explicitly list the identity provider using this IdentityProviders field. |=== @@ -721,6 +743,106 @@ FederationDomainTLSSpec is a struct that describes the TLS configuration for an |=== +[id="{anchor_prefix}-go-pinniped-dev-generated-1-22-apis-supervisor-config-v1alpha1-federationdomaintransforms"] +==== FederationDomainTransforms + +FederationDomainTransforms defines identity transformations for an identity provider's usage on a FederationDomain. + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-22-apis-supervisor-config-v1alpha1-federationdomainidentityprovider[$$FederationDomainIdentityProvider$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description +| *`constants`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-22-apis-supervisor-config-v1alpha1-federationdomaintransformsconstant[$$FederationDomainTransformsConstant$$] array__ | Constants defines constant variables and their values which will be made available to the transform expressions. +| *`expressions`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-22-apis-supervisor-config-v1alpha1-federationdomaintransformsexpression[$$FederationDomainTransformsExpression$$] array__ | Expressions are an optional list of transforms and policies to be executed in the order given during every authentication attempt, including during every session refresh. Each is a CEL expression. It may use the basic CEL language as defined in https://github.com/google/cel-spec/blob/master/doc/langdef.md plus the CEL string extensions defined in https://github.com/google/cel-go/tree/master/ext#strings. + The username and groups extracted from the identity provider, and the constants defined in this CR, are available as variables in all expressions. The username is provided via a variable called `username` and the list of group names is provided via a variable called `groups` (which may be an empty list). Each user-provided constants is provided via a variable named `strConst.varName` for string constants and `strListConst.varName` for string list constants. + The only allowed types for expressions are currently policy/v1, username/v1, and groups/v1. Each policy/v1 must return a boolean, and when it returns false, no more expressions from the list are evaluated and the authentication attempt is rejected. Transformations of type policy/v1 do not return usernames or group names, and therefore cannot change the username or group names. Each username/v1 transform must return the new username (a string), which can be the same as the old username. Transformations of type username/v1 do not return group names, and therefore cannot change the group names. Each groups/v1 transform must return the new groups list (list of strings), which can be the same as the old groups list. Transformations of type groups/v1 do not return usernames, and therefore cannot change the usernames. After each expression, the new (potentially changed) username or groups get passed to the following expression. + Any compilation or static type-checking failure of any expression will cause an error status on the FederationDomain. During an authentication attempt, any unexpected runtime evaluation errors (e.g. division by zero) cause the authentication attempt to fail. When all expressions evaluate successfully, then the (potentially changed) username and group names have been decided for that authentication attempt. +| *`examples`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-22-apis-supervisor-config-v1alpha1-federationdomaintransformsexample[$$FederationDomainTransformsExample$$] array__ | Examples can optionally be used to ensure that the sequence of transformation expressions are working as expected. Examples define sample input identities which are then run through the expression list, and the results are compared to the expected results. If any example in this list fails, then this FederationDomain will not be available for use, and the error(s) will be added to its status. This can be used to help guard against programming mistakes in the expressions, and also act as living documentation for other administrators to better understand the expressions. +|=== + + +[id="{anchor_prefix}-go-pinniped-dev-generated-1-22-apis-supervisor-config-v1alpha1-federationdomaintransformsconstant"] +==== FederationDomainTransformsConstant + +FederationDomainTransformsConstant defines a constant variable and its value which will be made available to the transform expressions. This is a union type, and Type is the discriminator field. + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-22-apis-supervisor-config-v1alpha1-federationdomaintransforms[$$FederationDomainTransforms$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description +| *`name`* __string__ | Name determines the name of the constant. It must be a valid identifier name. +| *`type`* __string__ | Type determines the type of the constant, and indicates which other field should be non-empty. +| *`stringValue`* __string__ | StringValue should hold the value when Type is "string", and is otherwise ignored. +| *`stringListValue`* __string array__ | StringListValue should hold the value when Type is "stringList", and is otherwise ignored. +|=== + + +[id="{anchor_prefix}-go-pinniped-dev-generated-1-22-apis-supervisor-config-v1alpha1-federationdomaintransformsexample"] +==== FederationDomainTransformsExample + +FederationDomainTransformsExample defines a transform example. + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-22-apis-supervisor-config-v1alpha1-federationdomaintransforms[$$FederationDomainTransforms$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description +| *`username`* __string__ | Username is the input username. +| *`groups`* __string array__ | Groups is the input list of group names. +| *`expects`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-22-apis-supervisor-config-v1alpha1-federationdomaintransformsexampleexpects[$$FederationDomainTransformsExampleExpects$$]__ | Expects is the expected output of the entire sequence of transforms when they are run against the input Username and Groups. +|=== + + +[id="{anchor_prefix}-go-pinniped-dev-generated-1-22-apis-supervisor-config-v1alpha1-federationdomaintransformsexampleexpects"] +==== FederationDomainTransformsExampleExpects + +FederationDomainTransformsExampleExpects defines the expected result for a transforms example. + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-22-apis-supervisor-config-v1alpha1-federationdomaintransformsexample[$$FederationDomainTransformsExample$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description +| *`username`* __string__ | Username is the expected username. +| *`groups`* __string array__ | Groups is the expected list of group names. +| *`rejected`* __boolean__ | Rejected should be set to true when the expected result of the transforms is that a policy rejected the authentication attempt. +| *`message`* __string__ | Message is the expected error message of the transforms. When Rejected is true, then Message is the expected message for the policy which rejected the authentication attempt. When Rejected is true and Message is blank, then Message will be treated as the default error message for authentication attempts which are rejected by a policy. When Rejected is false, then Message is the expected error message for some other non-policy transformation error, such as a runtime error. When Rejected is false, there is no default expected Message. +|=== + + +[id="{anchor_prefix}-go-pinniped-dev-generated-1-22-apis-supervisor-config-v1alpha1-federationdomaintransformsexpression"] +==== FederationDomainTransformsExpression + +FederationDomainTransformsExpression defines a transform expression. + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-22-apis-supervisor-config-v1alpha1-federationdomaintransforms[$$FederationDomainTransforms$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description +| *`type`* __string__ | Type determines the type of the expression. It must be one of the supported types. +| *`expression`* __string__ | Expression is a CEL expression that will be evaluated based on the Type during an authentication. +| *`message`* __string__ | Message is only used when Type is policy/v1. It defines an error message to be used when the policy rejects an authentication attempt. When empty, a default message will be used. +|=== + + [id="{anchor_prefix}-go-pinniped-dev-generated-1-22-apis-supervisor-config-v1alpha1-oidcclient"] ==== OIDCClient diff --git a/generated/1.22/apis/supervisor/config/v1alpha1/types_federationdomain.go b/generated/1.22/apis/supervisor/config/v1alpha1/types_federationdomain.go index 27de4401c0..7f1956845e 100644 --- a/generated/1.22/apis/supervisor/config/v1alpha1/types_federationdomain.go +++ b/generated/1.22/apis/supervisor/config/v1alpha1/types_federationdomain.go @@ -1,4 +1,4 @@ -// Copyright 2020-2022 the Pinniped contributors. All Rights Reserved. +// Copyright 2020-2023 the Pinniped contributors. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 package v1alpha1 @@ -42,6 +42,155 @@ type FederationDomainTLSSpec struct { SecretName string `json:"secretName,omitempty"` } +// FederationDomainTransformsConstant defines a constant variable and its value which will be made available to +// the transform expressions. This is a union type, and Type is the discriminator field. +type FederationDomainTransformsConstant struct { + // Name determines the name of the constant. It must be a valid identifier name. + // +kubebuilder:validation:Pattern=`^[a-zA-Z][_a-zA-Z0-9]*$` + // +kubebuilder:validation:MinLength=1 + // +kubebuilder:validation:MaxLength=64 + Name string `json:"name"` + + // Type determines the type of the constant, and indicates which other field should be non-empty. + // +kubebuilder:validation:Enum=string;stringList + Type string `json:"type"` + + // StringValue should hold the value when Type is "string", and is otherwise ignored. + // +optional + StringValue string `json:"stringValue,omitempty"` + + // StringListValue should hold the value when Type is "stringList", and is otherwise ignored. + // +optional + StringListValue []string `json:"stringListValue,omitempty"` +} + +// FederationDomainTransformsExpression defines a transform expression. +type FederationDomainTransformsExpression struct { + // Type determines the type of the expression. It must be one of the supported types. + // +kubebuilder:validation:Enum=policy/v1;username/v1;groups/v1 + Type string `json:"type"` + + // Expression is a CEL expression that will be evaluated based on the Type during an authentication. + // +kubebuilder:validation:MinLength=1 + Expression string `json:"expression"` + + // Message is only used when Type is policy/v1. It defines an error message to be used when the policy rejects + // an authentication attempt. When empty, a default message will be used. + // +optional + Message string `json:"message,omitempty"` +} + +// FederationDomainTransformsExample defines a transform example. +type FederationDomainTransformsExample struct { + // Username is the input username. + // +kubebuilder:validation:MinLength=1 + Username string `json:"username"` + + // Groups is the input list of group names. + // +optional + Groups []string `json:"groups,omitempty"` + + // Expects is the expected output of the entire sequence of transforms when they are run against the + // input Username and Groups. + Expects FederationDomainTransformsExampleExpects `json:"expects"` +} + +// FederationDomainTransformsExampleExpects defines the expected result for a transforms example. +type FederationDomainTransformsExampleExpects struct { + // Username is the expected username. + // +optional + Username string `json:"username,omitempty"` + + // Groups is the expected list of group names. + // +optional + Groups []string `json:"groups,omitempty"` + + // Rejected should be set to true when the expected result of the transforms is that a policy rejected the + // authentication attempt. + // +optional + Rejected bool `json:"rejected,omitempty"` + + // Message is the expected error message of the transforms. When Rejected is true, then Message is the expected + // message for the policy which rejected the authentication attempt. When Rejected is true and Message is blank, + // then Message will be treated as the default error message for authentication attempts which are rejected by a + // policy. When Rejected is false, then Message is the expected error message for some other non-policy + // transformation error, such as a runtime error. When Rejected is false, there is no default expected Message. + // +optional + Message string `json:"message,omitempty"` +} + +// FederationDomainTransforms defines identity transformations for an identity provider's usage on a FederationDomain. +type FederationDomainTransforms struct { + // Constants defines constant variables and their values which will be made available to the transform expressions. + // +patchMergeKey=name + // +patchStrategy=merge + // +listType=map + // +listMapKey=name + // +optional + Constants []FederationDomainTransformsConstant `json:"constants,omitempty"` + + // Expressions are an optional list of transforms and policies to be executed in the order given during every + // authentication attempt, including during every session refresh. + // Each is a CEL expression. It may use the basic CEL language as defined in + // https://github.com/google/cel-spec/blob/master/doc/langdef.md plus the CEL string extensions defined in + // https://github.com/google/cel-go/tree/master/ext#strings. + // + // The username and groups extracted from the identity provider, and the constants defined in this CR, are + // available as variables in all expressions. The username is provided via a variable called `username` and + // the list of group names is provided via a variable called `groups` (which may be an empty list). + // Each user-provided constants is provided via a variable named `strConst.varName` for string constants + // and `strListConst.varName` for string list constants. + // + // The only allowed types for expressions are currently policy/v1, username/v1, and groups/v1. + // Each policy/v1 must return a boolean, and when it returns false, no more expressions from the list are evaluated + // and the authentication attempt is rejected. + // Transformations of type policy/v1 do not return usernames or group names, and therefore cannot change the + // username or group names. + // Each username/v1 transform must return the new username (a string), which can be the same as the old username. + // Transformations of type username/v1 do not return group names, and therefore cannot change the group names. + // Each groups/v1 transform must return the new groups list (list of strings), which can be the same as the old + // groups list. + // Transformations of type groups/v1 do not return usernames, and therefore cannot change the usernames. + // After each expression, the new (potentially changed) username or groups get passed to the following expression. + // + // Any compilation or static type-checking failure of any expression will cause an error status on the FederationDomain. + // During an authentication attempt, any unexpected runtime evaluation errors (e.g. division by zero) cause the + // authentication attempt to fail. When all expressions evaluate successfully, then the (potentially changed) username + // and group names have been decided for that authentication attempt. + // + // +optional + Expressions []FederationDomainTransformsExpression `json:"expressions,omitempty"` + + // Examples can optionally be used to ensure that the sequence of transformation expressions are working as + // expected. Examples define sample input identities which are then run through the expression list, and the + // results are compared to the expected results. If any example in this list fails, then this + // FederationDomain will not be available for use, and the error(s) will be added to its status. + // This can be used to help guard against programming mistakes in the expressions, and also + // act as living documentation for other administrators to better understand the expressions. + // +optional + Examples []FederationDomainTransformsExample `json:"examples,omitempty"` +} + +// FederationDomainIdentityProvider describes how an identity provider is made available in this FederationDomain. +type FederationDomainIdentityProvider struct { + // DisplayName is the name of this identity provider as it will appear to clients. This name ends up in the + // kubeconfig of end users, so changing the name of an identity provider that is in use by end users will be a + // disruptive change for those users. + // +kubebuilder:validation:MinLength=1 + DisplayName string `json:"displayName"` + + // ObjectRef is a reference to a Pinniped identity provider resource. A valid reference is required. + // If the reference cannot be resolved then the identity provider will not be made available. + // Must refer to a resource of one of the Pinniped identity provider types, e.g. OIDCIdentityProvider, + // LDAPIdentityProvider, ActiveDirectoryIdentityProvider. + ObjectRef corev1.TypedLocalObjectReference `json:"objectRef"` + + // Transforms is an optional way to specify transformations to be applied during user authentication and + // session refresh. + // +optional + Transforms FederationDomainTransforms `json:"transforms,omitempty"` +} + // FederationDomainSpec is a struct that describes an OIDC Provider. type FederationDomainSpec struct { // Issuer is the OIDC Provider's issuer, per the OIDC Discovery Metadata document, as well as the @@ -58,6 +207,32 @@ type FederationDomainSpec struct { // TLS configures how this FederationDomain is served over Transport Layer Security (TLS). // +optional TLS *FederationDomainTLSSpec `json:"tls,omitempty"` + + // IdentityProviders is the list of identity providers available for use by this FederationDomain. + // + // An identity provider (e.g. OIDCIdentityProvider or LDAPIdentityProvider) describes how to connect to a server, + // how to talk in a specific protocol for authentication, and how to use the schema of that server/protocol to + // extract a normalized user identity. Normalized user identities include a username and a list of group names. + // In contrast, IdentityProviders describes how to use that normalized identity in a group of + // Kubernetes clusters. It can perform arbitrary transformations on that normalized identity. For example, a + // transformation can add a prefix to all usernames to help avoid accidental conflicts when multiple identity + // providers have different users with the same username (e.g. "idp1:ryan" versus "idp2:ryan"). + // IdentityProviders can also implement arbitrary authentication rejection policies. + // Even though a user was able to authenticate with the identity provider, a policy can disallow the authentication + // to the Kubernetes clusters that belong to this FederationDomain. For example, a policy could disallow the + // authentication unless the user belongs to a specific group in the identity provider. + // + // For backwards compatibility with versions of Pinniped which predate support for multiple identity providers, + // an empty IdentityProviders list will cause the FederationDomain to use all available identity providers which + // exist in the same namespace, but also to reject all authentication requests when there is more than one identity + // provider currently defined. In this backwards compatibility mode, the name of the identity provider resource + // (e.g. the Name of an OIDCIdentityProvider resource) will be used as the name of the identity provider in this + // FederationDomain. This mode is provided to make upgrading from older versions easier. However, instead of + // relying on this backwards compatibility mode, please consider this mode to be deprecated and please instead + // explicitly list the identity provider using this IdentityProviders field. + // + // +optional + IdentityProviders []FederationDomainIdentityProvider `json:"identityProviders,omitempty"` } // FederationDomainSecrets holds information about this OIDC Provider's secrets. diff --git a/generated/1.22/apis/supervisor/config/v1alpha1/zz_generated.deepcopy.go b/generated/1.22/apis/supervisor/config/v1alpha1/zz_generated.deepcopy.go index 682013782a..5e48e41556 100644 --- a/generated/1.22/apis/supervisor/config/v1alpha1/zz_generated.deepcopy.go +++ b/generated/1.22/apis/supervisor/config/v1alpha1/zz_generated.deepcopy.go @@ -57,6 +57,24 @@ func (in *FederationDomain) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FederationDomainIdentityProvider) DeepCopyInto(out *FederationDomainIdentityProvider) { + *out = *in + in.ObjectRef.DeepCopyInto(&out.ObjectRef) + in.Transforms.DeepCopyInto(&out.Transforms) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FederationDomainIdentityProvider. +func (in *FederationDomainIdentityProvider) DeepCopy() *FederationDomainIdentityProvider { + if in == nil { + return nil + } + out := new(FederationDomainIdentityProvider) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *FederationDomainList) DeepCopyInto(out *FederationDomainList) { *out = *in @@ -118,6 +136,13 @@ func (in *FederationDomainSpec) DeepCopyInto(out *FederationDomainSpec) { *out = new(FederationDomainTLSSpec) **out = **in } + if in.IdentityProviders != nil { + in, out := &in.IdentityProviders, &out.IdentityProviders + *out = make([]FederationDomainIdentityProvider, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } return } @@ -168,6 +193,121 @@ func (in *FederationDomainTLSSpec) DeepCopy() *FederationDomainTLSSpec { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FederationDomainTransforms) DeepCopyInto(out *FederationDomainTransforms) { + *out = *in + if in.Constants != nil { + in, out := &in.Constants, &out.Constants + *out = make([]FederationDomainTransformsConstant, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.Expressions != nil { + in, out := &in.Expressions, &out.Expressions + *out = make([]FederationDomainTransformsExpression, len(*in)) + copy(*out, *in) + } + if in.Examples != nil { + in, out := &in.Examples, &out.Examples + *out = make([]FederationDomainTransformsExample, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FederationDomainTransforms. +func (in *FederationDomainTransforms) DeepCopy() *FederationDomainTransforms { + if in == nil { + return nil + } + out := new(FederationDomainTransforms) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FederationDomainTransformsConstant) DeepCopyInto(out *FederationDomainTransformsConstant) { + *out = *in + if in.StringListValue != nil { + in, out := &in.StringListValue, &out.StringListValue + *out = make([]string, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FederationDomainTransformsConstant. +func (in *FederationDomainTransformsConstant) DeepCopy() *FederationDomainTransformsConstant { + if in == nil { + return nil + } + out := new(FederationDomainTransformsConstant) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FederationDomainTransformsExample) DeepCopyInto(out *FederationDomainTransformsExample) { + *out = *in + if in.Groups != nil { + in, out := &in.Groups, &out.Groups + *out = make([]string, len(*in)) + copy(*out, *in) + } + in.Expects.DeepCopyInto(&out.Expects) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FederationDomainTransformsExample. +func (in *FederationDomainTransformsExample) DeepCopy() *FederationDomainTransformsExample { + if in == nil { + return nil + } + out := new(FederationDomainTransformsExample) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FederationDomainTransformsExampleExpects) DeepCopyInto(out *FederationDomainTransformsExampleExpects) { + *out = *in + if in.Groups != nil { + in, out := &in.Groups, &out.Groups + *out = make([]string, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FederationDomainTransformsExampleExpects. +func (in *FederationDomainTransformsExampleExpects) DeepCopy() *FederationDomainTransformsExampleExpects { + if in == nil { + return nil + } + out := new(FederationDomainTransformsExampleExpects) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FederationDomainTransformsExpression) DeepCopyInto(out *FederationDomainTransformsExpression) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FederationDomainTransformsExpression. +func (in *FederationDomainTransformsExpression) DeepCopy() *FederationDomainTransformsExpression { + if in == nil { + return nil + } + out := new(FederationDomainTransformsExpression) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *OIDCClient) DeepCopyInto(out *OIDCClient) { *out = *in diff --git a/generated/1.22/crds/config.supervisor.pinniped.dev_federationdomains.yaml b/generated/1.22/crds/config.supervisor.pinniped.dev_federationdomains.yaml index 71f7370d11..5e839fccd0 100644 --- a/generated/1.22/crds/config.supervisor.pinniped.dev_federationdomains.yaml +++ b/generated/1.22/crds/config.supervisor.pinniped.dev_federationdomains.yaml @@ -47,6 +47,255 @@ spec: spec: description: Spec of the OIDC provider. properties: + identityProviders: + description: "IdentityProviders is the list of identity providers + available for use by this FederationDomain. \n An identity provider + (e.g. OIDCIdentityProvider or LDAPIdentityProvider) describes how + to connect to a server, how to talk in a specific protocol for authentication, + and how to use the schema of that server/protocol to extract a normalized + user identity. Normalized user identities include a username and + a list of group names. In contrast, IdentityProviders describes + how to use that normalized identity in a group of Kubernetes clusters. + It can perform arbitrary transformations on that normalized identity. + For example, a transformation can add a prefix to all usernames + to help avoid accidental conflicts when multiple identity providers + have different users with the same username (e.g. \"idp1:ryan\" + versus \"idp2:ryan\"). IdentityProviders can also implement arbitrary + authentication rejection policies. Even though a user was able to + authenticate with the identity provider, a policy can disallow the + authentication to the Kubernetes clusters that belong to this FederationDomain. + For example, a policy could disallow the authentication unless the + user belongs to a specific group in the identity provider. \n For + backwards compatibility with versions of Pinniped which predate + support for multiple identity providers, an empty IdentityProviders + list will cause the FederationDomain to use all available identity + providers which exist in the same namespace, but also to reject + all authentication requests when there is more than one identity + provider currently defined. In this backwards compatibility mode, + the name of the identity provider resource (e.g. the Name of an + OIDCIdentityProvider resource) will be used as the name of the identity + provider in this FederationDomain. This mode is provided to make + upgrading from older versions easier. However, instead of relying + on this backwards compatibility mode, please consider this mode + to be deprecated and please instead explicitly list the identity + provider using this IdentityProviders field." + items: + description: FederationDomainIdentityProvider describes how an identity + provider is made available in this FederationDomain. + properties: + displayName: + description: DisplayName is the name of this identity provider + as it will appear to clients. This name ends up in the kubeconfig + of end users, so changing the name of an identity provider + that is in use by end users will be a disruptive change for + those users. + minLength: 1 + type: string + objectRef: + description: ObjectRef is a reference to a Pinniped identity + provider resource. A valid reference is required. If the reference + cannot be resolved then the identity provider will not be + made available. Must refer to a resource of one of the Pinniped + identity provider types, e.g. OIDCIdentityProvider, LDAPIdentityProvider, + ActiveDirectoryIdentityProvider. + properties: + apiGroup: + description: APIGroup is the group for the resource being + referenced. If APIGroup is not specified, the specified + Kind must be in the core API group. For any other third-party + types, APIGroup is required. + type: string + kind: + description: Kind is the type of resource being referenced + type: string + name: + description: Name is the name of resource being referenced + type: string + required: + - kind + - name + type: object + transforms: + description: Transforms is an optional way to specify transformations + to be applied during user authentication and session refresh. + properties: + constants: + description: Constants defines constant variables and their + values which will be made available to the transform expressions. + items: + description: FederationDomainTransformsConstant defines + a constant variable and its value which will be made + available to the transform expressions. This is a union + type, and Type is the discriminator field. + properties: + name: + description: Name determines the name of the constant. + It must be a valid identifier name. + maxLength: 64 + minLength: 1 + pattern: ^[a-zA-Z][_a-zA-Z0-9]*$ + type: string + stringListValue: + description: StringListValue should hold the value + when Type is "stringList", and is otherwise ignored. + items: + type: string + type: array + stringValue: + description: StringValue should hold the value when + Type is "string", and is otherwise ignored. + type: string + type: + description: Type determines the type of the constant, + and indicates which other field should be non-empty. + enum: + - string + - stringList + type: string + required: + - name + - type + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + examples: + description: Examples can optionally be used to ensure that + the sequence of transformation expressions are working + as expected. Examples define sample input identities which + are then run through the expression list, and the results + are compared to the expected results. If any example in + this list fails, then this FederationDomain will not be + available for use, and the error(s) will be added to its + status. This can be used to help guard against programming + mistakes in the expressions, and also act as living documentation + for other administrators to better understand the expressions. + items: + description: FederationDomainTransformsExample defines + a transform example. + properties: + expects: + description: Expects is the expected output of the + entire sequence of transforms when they are run + against the input Username and Groups. + properties: + groups: + description: Groups is the expected list of group + names. + items: + type: string + type: array + message: + description: Message is the expected error message + of the transforms. When Rejected is true, then + Message is the expected message for the policy + which rejected the authentication attempt. When + Rejected is true and Message is blank, then + Message will be treated as the default error + message for authentication attempts which are + rejected by a policy. When Rejected is false, + then Message is the expected error message for + some other non-policy transformation error, + such as a runtime error. When Rejected is false, + there is no default expected Message. + type: string + rejected: + description: Rejected should be set to true when + the expected result of the transforms is that + a policy rejected the authentication attempt. + type: boolean + username: + description: Username is the expected username. + type: string + type: object + groups: + description: Groups is the input list of group names. + items: + type: string + type: array + username: + description: Username is the input username. + minLength: 1 + type: string + required: + - expects + - username + type: object + type: array + expressions: + description: "Expressions are an optional list of transforms + and policies to be executed in the order given during + every authentication attempt, including during every session + refresh. Each is a CEL expression. It may use the basic + CEL language as defined in https://github.com/google/cel-spec/blob/master/doc/langdef.md + plus the CEL string extensions defined in https://github.com/google/cel-go/tree/master/ext#strings. + \n The username and groups extracted from the identity + provider, and the constants defined in this CR, are available + as variables in all expressions. The username is provided + via a variable called `username` and the list of group + names is provided via a variable called `groups` (which + may be an empty list). Each user-provided constants is + provided via a variable named `strConst.varName` for string + constants and `strListConst.varName` for string list constants. + \n The only allowed types for expressions are currently + policy/v1, username/v1, and groups/v1. Each policy/v1 + must return a boolean, and when it returns false, no more + expressions from the list are evaluated and the authentication + attempt is rejected. Transformations of type policy/v1 + do not return usernames or group names, and therefore + cannot change the username or group names. Each username/v1 + transform must return the new username (a string), which + can be the same as the old username. Transformations of + type username/v1 do not return group names, and therefore + cannot change the group names. Each groups/v1 transform + must return the new groups list (list of strings), which + can be the same as the old groups list. Transformations + of type groups/v1 do not return usernames, and therefore + cannot change the usernames. After each expression, the + new (potentially changed) username or groups get passed + to the following expression. \n Any compilation or static + type-checking failure of any expression will cause an + error status on the FederationDomain. During an authentication + attempt, any unexpected runtime evaluation errors (e.g. + division by zero) cause the authentication attempt to + fail. When all expressions evaluate successfully, then + the (potentially changed) username and group names have + been decided for that authentication attempt." + items: + description: FederationDomainTransformsExpression defines + a transform expression. + properties: + expression: + description: Expression is a CEL expression that will + be evaluated based on the Type during an authentication. + minLength: 1 + type: string + message: + description: Message is only used when Type is policy/v1. + It defines an error message to be used when the + policy rejects an authentication attempt. When empty, + a default message will be used. + type: string + type: + description: Type determines the type of the expression. + It must be one of the supported types. + enum: + - policy/v1 + - username/v1 + - groups/v1 + type: string + required: + - expression + - type + type: object + type: array + type: object + required: + - displayName + - objectRef + type: object + type: array issuer: description: "Issuer is the OIDC Provider's issuer, per the OIDC Discovery Metadata document, as well as the identifier that it will use for diff --git a/generated/1.23/README.adoc b/generated/1.23/README.adoc index 2e47487a5e..07c59de044 100644 --- a/generated/1.23/README.adoc +++ b/generated/1.23/README.adoc @@ -639,6 +639,25 @@ FederationDomain describes the configuration of an OIDC provider. |=== +[id="{anchor_prefix}-go-pinniped-dev-generated-1-23-apis-supervisor-config-v1alpha1-federationdomainidentityprovider"] +==== FederationDomainIdentityProvider + +FederationDomainIdentityProvider describes how an identity provider is made available in this FederationDomain. + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-23-apis-supervisor-config-v1alpha1-federationdomainspec[$$FederationDomainSpec$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description +| *`displayName`* __string__ | DisplayName is the name of this identity provider as it will appear to clients. This name ends up in the kubeconfig of end users, so changing the name of an identity provider that is in use by end users will be a disruptive change for those users. +| *`objectRef`* __link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.23/#typedlocalobjectreference-v1-core[$$TypedLocalObjectReference$$]__ | ObjectRef is a reference to a Pinniped identity provider resource. A valid reference is required. If the reference cannot be resolved then the identity provider will not be made available. Must refer to a resource of one of the Pinniped identity provider types, e.g. OIDCIdentityProvider, LDAPIdentityProvider, ActiveDirectoryIdentityProvider. +| *`transforms`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-23-apis-supervisor-config-v1alpha1-federationdomaintransforms[$$FederationDomainTransforms$$]__ | Transforms is an optional way to specify transformations to be applied during user authentication and session refresh. +|=== + + [id="{anchor_prefix}-go-pinniped-dev-generated-1-23-apis-supervisor-config-v1alpha1-federationdomainsecrets"] @@ -677,6 +696,9 @@ FederationDomainSpec is a struct that describes an OIDC Provider. | *`issuer`* __string__ | Issuer is the OIDC Provider's issuer, per the OIDC Discovery Metadata document, as well as the identifier that it will use for the iss claim in issued JWTs. This field will also be used as the base URL for any endpoints used by the OIDC Provider (e.g., if your issuer is https://example.com/foo, then your authorization endpoint will look like https://example.com/foo/some/path/to/auth/endpoint). See https://openid.net/specs/openid-connect-discovery-1_0.html#rfc.section.3 for more information. | *`tls`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-23-apis-supervisor-config-v1alpha1-federationdomaintlsspec[$$FederationDomainTLSSpec$$]__ | TLS configures how this FederationDomain is served over Transport Layer Security (TLS). +| *`identityProviders`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-23-apis-supervisor-config-v1alpha1-federationdomainidentityprovider[$$FederationDomainIdentityProvider$$] array__ | IdentityProviders is the list of identity providers available for use by this FederationDomain. + An identity provider (e.g. OIDCIdentityProvider or LDAPIdentityProvider) describes how to connect to a server, how to talk in a specific protocol for authentication, and how to use the schema of that server/protocol to extract a normalized user identity. Normalized user identities include a username and a list of group names. In contrast, IdentityProviders describes how to use that normalized identity in a group of Kubernetes clusters. It can perform arbitrary transformations on that normalized identity. For example, a transformation can add a prefix to all usernames to help avoid accidental conflicts when multiple identity providers have different users with the same username (e.g. "idp1:ryan" versus "idp2:ryan"). IdentityProviders can also implement arbitrary authentication rejection policies. Even though a user was able to authenticate with the identity provider, a policy can disallow the authentication to the Kubernetes clusters that belong to this FederationDomain. For example, a policy could disallow the authentication unless the user belongs to a specific group in the identity provider. + For backwards compatibility with versions of Pinniped which predate support for multiple identity providers, an empty IdentityProviders list will cause the FederationDomain to use all available identity providers which exist in the same namespace, but also to reject all authentication requests when there is more than one identity provider currently defined. In this backwards compatibility mode, the name of the identity provider resource (e.g. the Name of an OIDCIdentityProvider resource) will be used as the name of the identity provider in this FederationDomain. This mode is provided to make upgrading from older versions easier. However, instead of relying on this backwards compatibility mode, please consider this mode to be deprecated and please instead explicitly list the identity provider using this IdentityProviders field. |=== @@ -721,6 +743,106 @@ FederationDomainTLSSpec is a struct that describes the TLS configuration for an |=== +[id="{anchor_prefix}-go-pinniped-dev-generated-1-23-apis-supervisor-config-v1alpha1-federationdomaintransforms"] +==== FederationDomainTransforms + +FederationDomainTransforms defines identity transformations for an identity provider's usage on a FederationDomain. + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-23-apis-supervisor-config-v1alpha1-federationdomainidentityprovider[$$FederationDomainIdentityProvider$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description +| *`constants`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-23-apis-supervisor-config-v1alpha1-federationdomaintransformsconstant[$$FederationDomainTransformsConstant$$] array__ | Constants defines constant variables and their values which will be made available to the transform expressions. +| *`expressions`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-23-apis-supervisor-config-v1alpha1-federationdomaintransformsexpression[$$FederationDomainTransformsExpression$$] array__ | Expressions are an optional list of transforms and policies to be executed in the order given during every authentication attempt, including during every session refresh. Each is a CEL expression. It may use the basic CEL language as defined in https://github.com/google/cel-spec/blob/master/doc/langdef.md plus the CEL string extensions defined in https://github.com/google/cel-go/tree/master/ext#strings. + The username and groups extracted from the identity provider, and the constants defined in this CR, are available as variables in all expressions. The username is provided via a variable called `username` and the list of group names is provided via a variable called `groups` (which may be an empty list). Each user-provided constants is provided via a variable named `strConst.varName` for string constants and `strListConst.varName` for string list constants. + The only allowed types for expressions are currently policy/v1, username/v1, and groups/v1. Each policy/v1 must return a boolean, and when it returns false, no more expressions from the list are evaluated and the authentication attempt is rejected. Transformations of type policy/v1 do not return usernames or group names, and therefore cannot change the username or group names. Each username/v1 transform must return the new username (a string), which can be the same as the old username. Transformations of type username/v1 do not return group names, and therefore cannot change the group names. Each groups/v1 transform must return the new groups list (list of strings), which can be the same as the old groups list. Transformations of type groups/v1 do not return usernames, and therefore cannot change the usernames. After each expression, the new (potentially changed) username or groups get passed to the following expression. + Any compilation or static type-checking failure of any expression will cause an error status on the FederationDomain. During an authentication attempt, any unexpected runtime evaluation errors (e.g. division by zero) cause the authentication attempt to fail. When all expressions evaluate successfully, then the (potentially changed) username and group names have been decided for that authentication attempt. +| *`examples`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-23-apis-supervisor-config-v1alpha1-federationdomaintransformsexample[$$FederationDomainTransformsExample$$] array__ | Examples can optionally be used to ensure that the sequence of transformation expressions are working as expected. Examples define sample input identities which are then run through the expression list, and the results are compared to the expected results. If any example in this list fails, then this FederationDomain will not be available for use, and the error(s) will be added to its status. This can be used to help guard against programming mistakes in the expressions, and also act as living documentation for other administrators to better understand the expressions. +|=== + + +[id="{anchor_prefix}-go-pinniped-dev-generated-1-23-apis-supervisor-config-v1alpha1-federationdomaintransformsconstant"] +==== FederationDomainTransformsConstant + +FederationDomainTransformsConstant defines a constant variable and its value which will be made available to the transform expressions. This is a union type, and Type is the discriminator field. + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-23-apis-supervisor-config-v1alpha1-federationdomaintransforms[$$FederationDomainTransforms$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description +| *`name`* __string__ | Name determines the name of the constant. It must be a valid identifier name. +| *`type`* __string__ | Type determines the type of the constant, and indicates which other field should be non-empty. +| *`stringValue`* __string__ | StringValue should hold the value when Type is "string", and is otherwise ignored. +| *`stringListValue`* __string array__ | StringListValue should hold the value when Type is "stringList", and is otherwise ignored. +|=== + + +[id="{anchor_prefix}-go-pinniped-dev-generated-1-23-apis-supervisor-config-v1alpha1-federationdomaintransformsexample"] +==== FederationDomainTransformsExample + +FederationDomainTransformsExample defines a transform example. + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-23-apis-supervisor-config-v1alpha1-federationdomaintransforms[$$FederationDomainTransforms$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description +| *`username`* __string__ | Username is the input username. +| *`groups`* __string array__ | Groups is the input list of group names. +| *`expects`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-23-apis-supervisor-config-v1alpha1-federationdomaintransformsexampleexpects[$$FederationDomainTransformsExampleExpects$$]__ | Expects is the expected output of the entire sequence of transforms when they are run against the input Username and Groups. +|=== + + +[id="{anchor_prefix}-go-pinniped-dev-generated-1-23-apis-supervisor-config-v1alpha1-federationdomaintransformsexampleexpects"] +==== FederationDomainTransformsExampleExpects + +FederationDomainTransformsExampleExpects defines the expected result for a transforms example. + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-23-apis-supervisor-config-v1alpha1-federationdomaintransformsexample[$$FederationDomainTransformsExample$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description +| *`username`* __string__ | Username is the expected username. +| *`groups`* __string array__ | Groups is the expected list of group names. +| *`rejected`* __boolean__ | Rejected should be set to true when the expected result of the transforms is that a policy rejected the authentication attempt. +| *`message`* __string__ | Message is the expected error message of the transforms. When Rejected is true, then Message is the expected message for the policy which rejected the authentication attempt. When Rejected is true and Message is blank, then Message will be treated as the default error message for authentication attempts which are rejected by a policy. When Rejected is false, then Message is the expected error message for some other non-policy transformation error, such as a runtime error. When Rejected is false, there is no default expected Message. +|=== + + +[id="{anchor_prefix}-go-pinniped-dev-generated-1-23-apis-supervisor-config-v1alpha1-federationdomaintransformsexpression"] +==== FederationDomainTransformsExpression + +FederationDomainTransformsExpression defines a transform expression. + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-23-apis-supervisor-config-v1alpha1-federationdomaintransforms[$$FederationDomainTransforms$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description +| *`type`* __string__ | Type determines the type of the expression. It must be one of the supported types. +| *`expression`* __string__ | Expression is a CEL expression that will be evaluated based on the Type during an authentication. +| *`message`* __string__ | Message is only used when Type is policy/v1. It defines an error message to be used when the policy rejects an authentication attempt. When empty, a default message will be used. +|=== + + [id="{anchor_prefix}-go-pinniped-dev-generated-1-23-apis-supervisor-config-v1alpha1-oidcclient"] ==== OIDCClient diff --git a/generated/1.23/apis/supervisor/config/v1alpha1/types_federationdomain.go b/generated/1.23/apis/supervisor/config/v1alpha1/types_federationdomain.go index 27de4401c0..7f1956845e 100644 --- a/generated/1.23/apis/supervisor/config/v1alpha1/types_federationdomain.go +++ b/generated/1.23/apis/supervisor/config/v1alpha1/types_federationdomain.go @@ -1,4 +1,4 @@ -// Copyright 2020-2022 the Pinniped contributors. All Rights Reserved. +// Copyright 2020-2023 the Pinniped contributors. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 package v1alpha1 @@ -42,6 +42,155 @@ type FederationDomainTLSSpec struct { SecretName string `json:"secretName,omitempty"` } +// FederationDomainTransformsConstant defines a constant variable and its value which will be made available to +// the transform expressions. This is a union type, and Type is the discriminator field. +type FederationDomainTransformsConstant struct { + // Name determines the name of the constant. It must be a valid identifier name. + // +kubebuilder:validation:Pattern=`^[a-zA-Z][_a-zA-Z0-9]*$` + // +kubebuilder:validation:MinLength=1 + // +kubebuilder:validation:MaxLength=64 + Name string `json:"name"` + + // Type determines the type of the constant, and indicates which other field should be non-empty. + // +kubebuilder:validation:Enum=string;stringList + Type string `json:"type"` + + // StringValue should hold the value when Type is "string", and is otherwise ignored. + // +optional + StringValue string `json:"stringValue,omitempty"` + + // StringListValue should hold the value when Type is "stringList", and is otherwise ignored. + // +optional + StringListValue []string `json:"stringListValue,omitempty"` +} + +// FederationDomainTransformsExpression defines a transform expression. +type FederationDomainTransformsExpression struct { + // Type determines the type of the expression. It must be one of the supported types. + // +kubebuilder:validation:Enum=policy/v1;username/v1;groups/v1 + Type string `json:"type"` + + // Expression is a CEL expression that will be evaluated based on the Type during an authentication. + // +kubebuilder:validation:MinLength=1 + Expression string `json:"expression"` + + // Message is only used when Type is policy/v1. It defines an error message to be used when the policy rejects + // an authentication attempt. When empty, a default message will be used. + // +optional + Message string `json:"message,omitempty"` +} + +// FederationDomainTransformsExample defines a transform example. +type FederationDomainTransformsExample struct { + // Username is the input username. + // +kubebuilder:validation:MinLength=1 + Username string `json:"username"` + + // Groups is the input list of group names. + // +optional + Groups []string `json:"groups,omitempty"` + + // Expects is the expected output of the entire sequence of transforms when they are run against the + // input Username and Groups. + Expects FederationDomainTransformsExampleExpects `json:"expects"` +} + +// FederationDomainTransformsExampleExpects defines the expected result for a transforms example. +type FederationDomainTransformsExampleExpects struct { + // Username is the expected username. + // +optional + Username string `json:"username,omitempty"` + + // Groups is the expected list of group names. + // +optional + Groups []string `json:"groups,omitempty"` + + // Rejected should be set to true when the expected result of the transforms is that a policy rejected the + // authentication attempt. + // +optional + Rejected bool `json:"rejected,omitempty"` + + // Message is the expected error message of the transforms. When Rejected is true, then Message is the expected + // message for the policy which rejected the authentication attempt. When Rejected is true and Message is blank, + // then Message will be treated as the default error message for authentication attempts which are rejected by a + // policy. When Rejected is false, then Message is the expected error message for some other non-policy + // transformation error, such as a runtime error. When Rejected is false, there is no default expected Message. + // +optional + Message string `json:"message,omitempty"` +} + +// FederationDomainTransforms defines identity transformations for an identity provider's usage on a FederationDomain. +type FederationDomainTransforms struct { + // Constants defines constant variables and their values which will be made available to the transform expressions. + // +patchMergeKey=name + // +patchStrategy=merge + // +listType=map + // +listMapKey=name + // +optional + Constants []FederationDomainTransformsConstant `json:"constants,omitempty"` + + // Expressions are an optional list of transforms and policies to be executed in the order given during every + // authentication attempt, including during every session refresh. + // Each is a CEL expression. It may use the basic CEL language as defined in + // https://github.com/google/cel-spec/blob/master/doc/langdef.md plus the CEL string extensions defined in + // https://github.com/google/cel-go/tree/master/ext#strings. + // + // The username and groups extracted from the identity provider, and the constants defined in this CR, are + // available as variables in all expressions. The username is provided via a variable called `username` and + // the list of group names is provided via a variable called `groups` (which may be an empty list). + // Each user-provided constants is provided via a variable named `strConst.varName` for string constants + // and `strListConst.varName` for string list constants. + // + // The only allowed types for expressions are currently policy/v1, username/v1, and groups/v1. + // Each policy/v1 must return a boolean, and when it returns false, no more expressions from the list are evaluated + // and the authentication attempt is rejected. + // Transformations of type policy/v1 do not return usernames or group names, and therefore cannot change the + // username or group names. + // Each username/v1 transform must return the new username (a string), which can be the same as the old username. + // Transformations of type username/v1 do not return group names, and therefore cannot change the group names. + // Each groups/v1 transform must return the new groups list (list of strings), which can be the same as the old + // groups list. + // Transformations of type groups/v1 do not return usernames, and therefore cannot change the usernames. + // After each expression, the new (potentially changed) username or groups get passed to the following expression. + // + // Any compilation or static type-checking failure of any expression will cause an error status on the FederationDomain. + // During an authentication attempt, any unexpected runtime evaluation errors (e.g. division by zero) cause the + // authentication attempt to fail. When all expressions evaluate successfully, then the (potentially changed) username + // and group names have been decided for that authentication attempt. + // + // +optional + Expressions []FederationDomainTransformsExpression `json:"expressions,omitempty"` + + // Examples can optionally be used to ensure that the sequence of transformation expressions are working as + // expected. Examples define sample input identities which are then run through the expression list, and the + // results are compared to the expected results. If any example in this list fails, then this + // FederationDomain will not be available for use, and the error(s) will be added to its status. + // This can be used to help guard against programming mistakes in the expressions, and also + // act as living documentation for other administrators to better understand the expressions. + // +optional + Examples []FederationDomainTransformsExample `json:"examples,omitempty"` +} + +// FederationDomainIdentityProvider describes how an identity provider is made available in this FederationDomain. +type FederationDomainIdentityProvider struct { + // DisplayName is the name of this identity provider as it will appear to clients. This name ends up in the + // kubeconfig of end users, so changing the name of an identity provider that is in use by end users will be a + // disruptive change for those users. + // +kubebuilder:validation:MinLength=1 + DisplayName string `json:"displayName"` + + // ObjectRef is a reference to a Pinniped identity provider resource. A valid reference is required. + // If the reference cannot be resolved then the identity provider will not be made available. + // Must refer to a resource of one of the Pinniped identity provider types, e.g. OIDCIdentityProvider, + // LDAPIdentityProvider, ActiveDirectoryIdentityProvider. + ObjectRef corev1.TypedLocalObjectReference `json:"objectRef"` + + // Transforms is an optional way to specify transformations to be applied during user authentication and + // session refresh. + // +optional + Transforms FederationDomainTransforms `json:"transforms,omitempty"` +} + // FederationDomainSpec is a struct that describes an OIDC Provider. type FederationDomainSpec struct { // Issuer is the OIDC Provider's issuer, per the OIDC Discovery Metadata document, as well as the @@ -58,6 +207,32 @@ type FederationDomainSpec struct { // TLS configures how this FederationDomain is served over Transport Layer Security (TLS). // +optional TLS *FederationDomainTLSSpec `json:"tls,omitempty"` + + // IdentityProviders is the list of identity providers available for use by this FederationDomain. + // + // An identity provider (e.g. OIDCIdentityProvider or LDAPIdentityProvider) describes how to connect to a server, + // how to talk in a specific protocol for authentication, and how to use the schema of that server/protocol to + // extract a normalized user identity. Normalized user identities include a username and a list of group names. + // In contrast, IdentityProviders describes how to use that normalized identity in a group of + // Kubernetes clusters. It can perform arbitrary transformations on that normalized identity. For example, a + // transformation can add a prefix to all usernames to help avoid accidental conflicts when multiple identity + // providers have different users with the same username (e.g. "idp1:ryan" versus "idp2:ryan"). + // IdentityProviders can also implement arbitrary authentication rejection policies. + // Even though a user was able to authenticate with the identity provider, a policy can disallow the authentication + // to the Kubernetes clusters that belong to this FederationDomain. For example, a policy could disallow the + // authentication unless the user belongs to a specific group in the identity provider. + // + // For backwards compatibility with versions of Pinniped which predate support for multiple identity providers, + // an empty IdentityProviders list will cause the FederationDomain to use all available identity providers which + // exist in the same namespace, but also to reject all authentication requests when there is more than one identity + // provider currently defined. In this backwards compatibility mode, the name of the identity provider resource + // (e.g. the Name of an OIDCIdentityProvider resource) will be used as the name of the identity provider in this + // FederationDomain. This mode is provided to make upgrading from older versions easier. However, instead of + // relying on this backwards compatibility mode, please consider this mode to be deprecated and please instead + // explicitly list the identity provider using this IdentityProviders field. + // + // +optional + IdentityProviders []FederationDomainIdentityProvider `json:"identityProviders,omitempty"` } // FederationDomainSecrets holds information about this OIDC Provider's secrets. diff --git a/generated/1.23/apis/supervisor/config/v1alpha1/zz_generated.deepcopy.go b/generated/1.23/apis/supervisor/config/v1alpha1/zz_generated.deepcopy.go index 682013782a..5e48e41556 100644 --- a/generated/1.23/apis/supervisor/config/v1alpha1/zz_generated.deepcopy.go +++ b/generated/1.23/apis/supervisor/config/v1alpha1/zz_generated.deepcopy.go @@ -57,6 +57,24 @@ func (in *FederationDomain) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FederationDomainIdentityProvider) DeepCopyInto(out *FederationDomainIdentityProvider) { + *out = *in + in.ObjectRef.DeepCopyInto(&out.ObjectRef) + in.Transforms.DeepCopyInto(&out.Transforms) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FederationDomainIdentityProvider. +func (in *FederationDomainIdentityProvider) DeepCopy() *FederationDomainIdentityProvider { + if in == nil { + return nil + } + out := new(FederationDomainIdentityProvider) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *FederationDomainList) DeepCopyInto(out *FederationDomainList) { *out = *in @@ -118,6 +136,13 @@ func (in *FederationDomainSpec) DeepCopyInto(out *FederationDomainSpec) { *out = new(FederationDomainTLSSpec) **out = **in } + if in.IdentityProviders != nil { + in, out := &in.IdentityProviders, &out.IdentityProviders + *out = make([]FederationDomainIdentityProvider, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } return } @@ -168,6 +193,121 @@ func (in *FederationDomainTLSSpec) DeepCopy() *FederationDomainTLSSpec { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FederationDomainTransforms) DeepCopyInto(out *FederationDomainTransforms) { + *out = *in + if in.Constants != nil { + in, out := &in.Constants, &out.Constants + *out = make([]FederationDomainTransformsConstant, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.Expressions != nil { + in, out := &in.Expressions, &out.Expressions + *out = make([]FederationDomainTransformsExpression, len(*in)) + copy(*out, *in) + } + if in.Examples != nil { + in, out := &in.Examples, &out.Examples + *out = make([]FederationDomainTransformsExample, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FederationDomainTransforms. +func (in *FederationDomainTransforms) DeepCopy() *FederationDomainTransforms { + if in == nil { + return nil + } + out := new(FederationDomainTransforms) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FederationDomainTransformsConstant) DeepCopyInto(out *FederationDomainTransformsConstant) { + *out = *in + if in.StringListValue != nil { + in, out := &in.StringListValue, &out.StringListValue + *out = make([]string, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FederationDomainTransformsConstant. +func (in *FederationDomainTransformsConstant) DeepCopy() *FederationDomainTransformsConstant { + if in == nil { + return nil + } + out := new(FederationDomainTransformsConstant) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FederationDomainTransformsExample) DeepCopyInto(out *FederationDomainTransformsExample) { + *out = *in + if in.Groups != nil { + in, out := &in.Groups, &out.Groups + *out = make([]string, len(*in)) + copy(*out, *in) + } + in.Expects.DeepCopyInto(&out.Expects) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FederationDomainTransformsExample. +func (in *FederationDomainTransformsExample) DeepCopy() *FederationDomainTransformsExample { + if in == nil { + return nil + } + out := new(FederationDomainTransformsExample) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FederationDomainTransformsExampleExpects) DeepCopyInto(out *FederationDomainTransformsExampleExpects) { + *out = *in + if in.Groups != nil { + in, out := &in.Groups, &out.Groups + *out = make([]string, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FederationDomainTransformsExampleExpects. +func (in *FederationDomainTransformsExampleExpects) DeepCopy() *FederationDomainTransformsExampleExpects { + if in == nil { + return nil + } + out := new(FederationDomainTransformsExampleExpects) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FederationDomainTransformsExpression) DeepCopyInto(out *FederationDomainTransformsExpression) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FederationDomainTransformsExpression. +func (in *FederationDomainTransformsExpression) DeepCopy() *FederationDomainTransformsExpression { + if in == nil { + return nil + } + out := new(FederationDomainTransformsExpression) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *OIDCClient) DeepCopyInto(out *OIDCClient) { *out = *in diff --git a/generated/1.23/crds/config.supervisor.pinniped.dev_federationdomains.yaml b/generated/1.23/crds/config.supervisor.pinniped.dev_federationdomains.yaml index 71f7370d11..5e839fccd0 100644 --- a/generated/1.23/crds/config.supervisor.pinniped.dev_federationdomains.yaml +++ b/generated/1.23/crds/config.supervisor.pinniped.dev_federationdomains.yaml @@ -47,6 +47,255 @@ spec: spec: description: Spec of the OIDC provider. properties: + identityProviders: + description: "IdentityProviders is the list of identity providers + available for use by this FederationDomain. \n An identity provider + (e.g. OIDCIdentityProvider or LDAPIdentityProvider) describes how + to connect to a server, how to talk in a specific protocol for authentication, + and how to use the schema of that server/protocol to extract a normalized + user identity. Normalized user identities include a username and + a list of group names. In contrast, IdentityProviders describes + how to use that normalized identity in a group of Kubernetes clusters. + It can perform arbitrary transformations on that normalized identity. + For example, a transformation can add a prefix to all usernames + to help avoid accidental conflicts when multiple identity providers + have different users with the same username (e.g. \"idp1:ryan\" + versus \"idp2:ryan\"). IdentityProviders can also implement arbitrary + authentication rejection policies. Even though a user was able to + authenticate with the identity provider, a policy can disallow the + authentication to the Kubernetes clusters that belong to this FederationDomain. + For example, a policy could disallow the authentication unless the + user belongs to a specific group in the identity provider. \n For + backwards compatibility with versions of Pinniped which predate + support for multiple identity providers, an empty IdentityProviders + list will cause the FederationDomain to use all available identity + providers which exist in the same namespace, but also to reject + all authentication requests when there is more than one identity + provider currently defined. In this backwards compatibility mode, + the name of the identity provider resource (e.g. the Name of an + OIDCIdentityProvider resource) will be used as the name of the identity + provider in this FederationDomain. This mode is provided to make + upgrading from older versions easier. However, instead of relying + on this backwards compatibility mode, please consider this mode + to be deprecated and please instead explicitly list the identity + provider using this IdentityProviders field." + items: + description: FederationDomainIdentityProvider describes how an identity + provider is made available in this FederationDomain. + properties: + displayName: + description: DisplayName is the name of this identity provider + as it will appear to clients. This name ends up in the kubeconfig + of end users, so changing the name of an identity provider + that is in use by end users will be a disruptive change for + those users. + minLength: 1 + type: string + objectRef: + description: ObjectRef is a reference to a Pinniped identity + provider resource. A valid reference is required. If the reference + cannot be resolved then the identity provider will not be + made available. Must refer to a resource of one of the Pinniped + identity provider types, e.g. OIDCIdentityProvider, LDAPIdentityProvider, + ActiveDirectoryIdentityProvider. + properties: + apiGroup: + description: APIGroup is the group for the resource being + referenced. If APIGroup is not specified, the specified + Kind must be in the core API group. For any other third-party + types, APIGroup is required. + type: string + kind: + description: Kind is the type of resource being referenced + type: string + name: + description: Name is the name of resource being referenced + type: string + required: + - kind + - name + type: object + transforms: + description: Transforms is an optional way to specify transformations + to be applied during user authentication and session refresh. + properties: + constants: + description: Constants defines constant variables and their + values which will be made available to the transform expressions. + items: + description: FederationDomainTransformsConstant defines + a constant variable and its value which will be made + available to the transform expressions. This is a union + type, and Type is the discriminator field. + properties: + name: + description: Name determines the name of the constant. + It must be a valid identifier name. + maxLength: 64 + minLength: 1 + pattern: ^[a-zA-Z][_a-zA-Z0-9]*$ + type: string + stringListValue: + description: StringListValue should hold the value + when Type is "stringList", and is otherwise ignored. + items: + type: string + type: array + stringValue: + description: StringValue should hold the value when + Type is "string", and is otherwise ignored. + type: string + type: + description: Type determines the type of the constant, + and indicates which other field should be non-empty. + enum: + - string + - stringList + type: string + required: + - name + - type + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + examples: + description: Examples can optionally be used to ensure that + the sequence of transformation expressions are working + as expected. Examples define sample input identities which + are then run through the expression list, and the results + are compared to the expected results. If any example in + this list fails, then this FederationDomain will not be + available for use, and the error(s) will be added to its + status. This can be used to help guard against programming + mistakes in the expressions, and also act as living documentation + for other administrators to better understand the expressions. + items: + description: FederationDomainTransformsExample defines + a transform example. + properties: + expects: + description: Expects is the expected output of the + entire sequence of transforms when they are run + against the input Username and Groups. + properties: + groups: + description: Groups is the expected list of group + names. + items: + type: string + type: array + message: + description: Message is the expected error message + of the transforms. When Rejected is true, then + Message is the expected message for the policy + which rejected the authentication attempt. When + Rejected is true and Message is blank, then + Message will be treated as the default error + message for authentication attempts which are + rejected by a policy. When Rejected is false, + then Message is the expected error message for + some other non-policy transformation error, + such as a runtime error. When Rejected is false, + there is no default expected Message. + type: string + rejected: + description: Rejected should be set to true when + the expected result of the transforms is that + a policy rejected the authentication attempt. + type: boolean + username: + description: Username is the expected username. + type: string + type: object + groups: + description: Groups is the input list of group names. + items: + type: string + type: array + username: + description: Username is the input username. + minLength: 1 + type: string + required: + - expects + - username + type: object + type: array + expressions: + description: "Expressions are an optional list of transforms + and policies to be executed in the order given during + every authentication attempt, including during every session + refresh. Each is a CEL expression. It may use the basic + CEL language as defined in https://github.com/google/cel-spec/blob/master/doc/langdef.md + plus the CEL string extensions defined in https://github.com/google/cel-go/tree/master/ext#strings. + \n The username and groups extracted from the identity + provider, and the constants defined in this CR, are available + as variables in all expressions. The username is provided + via a variable called `username` and the list of group + names is provided via a variable called `groups` (which + may be an empty list). Each user-provided constants is + provided via a variable named `strConst.varName` for string + constants and `strListConst.varName` for string list constants. + \n The only allowed types for expressions are currently + policy/v1, username/v1, and groups/v1. Each policy/v1 + must return a boolean, and when it returns false, no more + expressions from the list are evaluated and the authentication + attempt is rejected. Transformations of type policy/v1 + do not return usernames or group names, and therefore + cannot change the username or group names. Each username/v1 + transform must return the new username (a string), which + can be the same as the old username. Transformations of + type username/v1 do not return group names, and therefore + cannot change the group names. Each groups/v1 transform + must return the new groups list (list of strings), which + can be the same as the old groups list. Transformations + of type groups/v1 do not return usernames, and therefore + cannot change the usernames. After each expression, the + new (potentially changed) username or groups get passed + to the following expression. \n Any compilation or static + type-checking failure of any expression will cause an + error status on the FederationDomain. During an authentication + attempt, any unexpected runtime evaluation errors (e.g. + division by zero) cause the authentication attempt to + fail. When all expressions evaluate successfully, then + the (potentially changed) username and group names have + been decided for that authentication attempt." + items: + description: FederationDomainTransformsExpression defines + a transform expression. + properties: + expression: + description: Expression is a CEL expression that will + be evaluated based on the Type during an authentication. + minLength: 1 + type: string + message: + description: Message is only used when Type is policy/v1. + It defines an error message to be used when the + policy rejects an authentication attempt. When empty, + a default message will be used. + type: string + type: + description: Type determines the type of the expression. + It must be one of the supported types. + enum: + - policy/v1 + - username/v1 + - groups/v1 + type: string + required: + - expression + - type + type: object + type: array + type: object + required: + - displayName + - objectRef + type: object + type: array issuer: description: "Issuer is the OIDC Provider's issuer, per the OIDC Discovery Metadata document, as well as the identifier that it will use for diff --git a/generated/1.24/README.adoc b/generated/1.24/README.adoc index d69b3c1d4d..3e6398253a 100644 --- a/generated/1.24/README.adoc +++ b/generated/1.24/README.adoc @@ -639,6 +639,25 @@ FederationDomain describes the configuration of an OIDC provider. |=== +[id="{anchor_prefix}-go-pinniped-dev-generated-1-24-apis-supervisor-config-v1alpha1-federationdomainidentityprovider"] +==== FederationDomainIdentityProvider + +FederationDomainIdentityProvider describes how an identity provider is made available in this FederationDomain. + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-24-apis-supervisor-config-v1alpha1-federationdomainspec[$$FederationDomainSpec$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description +| *`displayName`* __string__ | DisplayName is the name of this identity provider as it will appear to clients. This name ends up in the kubeconfig of end users, so changing the name of an identity provider that is in use by end users will be a disruptive change for those users. +| *`objectRef`* __link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.24/#typedlocalobjectreference-v1-core[$$TypedLocalObjectReference$$]__ | ObjectRef is a reference to a Pinniped identity provider resource. A valid reference is required. If the reference cannot be resolved then the identity provider will not be made available. Must refer to a resource of one of the Pinniped identity provider types, e.g. OIDCIdentityProvider, LDAPIdentityProvider, ActiveDirectoryIdentityProvider. +| *`transforms`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-24-apis-supervisor-config-v1alpha1-federationdomaintransforms[$$FederationDomainTransforms$$]__ | Transforms is an optional way to specify transformations to be applied during user authentication and session refresh. +|=== + + [id="{anchor_prefix}-go-pinniped-dev-generated-1-24-apis-supervisor-config-v1alpha1-federationdomainsecrets"] @@ -677,6 +696,9 @@ FederationDomainSpec is a struct that describes an OIDC Provider. | *`issuer`* __string__ | Issuer is the OIDC Provider's issuer, per the OIDC Discovery Metadata document, as well as the identifier that it will use for the iss claim in issued JWTs. This field will also be used as the base URL for any endpoints used by the OIDC Provider (e.g., if your issuer is https://example.com/foo, then your authorization endpoint will look like https://example.com/foo/some/path/to/auth/endpoint). See https://openid.net/specs/openid-connect-discovery-1_0.html#rfc.section.3 for more information. | *`tls`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-24-apis-supervisor-config-v1alpha1-federationdomaintlsspec[$$FederationDomainTLSSpec$$]__ | TLS configures how this FederationDomain is served over Transport Layer Security (TLS). +| *`identityProviders`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-24-apis-supervisor-config-v1alpha1-federationdomainidentityprovider[$$FederationDomainIdentityProvider$$] array__ | IdentityProviders is the list of identity providers available for use by this FederationDomain. + An identity provider (e.g. OIDCIdentityProvider or LDAPIdentityProvider) describes how to connect to a server, how to talk in a specific protocol for authentication, and how to use the schema of that server/protocol to extract a normalized user identity. Normalized user identities include a username and a list of group names. In contrast, IdentityProviders describes how to use that normalized identity in a group of Kubernetes clusters. It can perform arbitrary transformations on that normalized identity. For example, a transformation can add a prefix to all usernames to help avoid accidental conflicts when multiple identity providers have different users with the same username (e.g. "idp1:ryan" versus "idp2:ryan"). IdentityProviders can also implement arbitrary authentication rejection policies. Even though a user was able to authenticate with the identity provider, a policy can disallow the authentication to the Kubernetes clusters that belong to this FederationDomain. For example, a policy could disallow the authentication unless the user belongs to a specific group in the identity provider. + For backwards compatibility with versions of Pinniped which predate support for multiple identity providers, an empty IdentityProviders list will cause the FederationDomain to use all available identity providers which exist in the same namespace, but also to reject all authentication requests when there is more than one identity provider currently defined. In this backwards compatibility mode, the name of the identity provider resource (e.g. the Name of an OIDCIdentityProvider resource) will be used as the name of the identity provider in this FederationDomain. This mode is provided to make upgrading from older versions easier. However, instead of relying on this backwards compatibility mode, please consider this mode to be deprecated and please instead explicitly list the identity provider using this IdentityProviders field. |=== @@ -721,6 +743,106 @@ FederationDomainTLSSpec is a struct that describes the TLS configuration for an |=== +[id="{anchor_prefix}-go-pinniped-dev-generated-1-24-apis-supervisor-config-v1alpha1-federationdomaintransforms"] +==== FederationDomainTransforms + +FederationDomainTransforms defines identity transformations for an identity provider's usage on a FederationDomain. + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-24-apis-supervisor-config-v1alpha1-federationdomainidentityprovider[$$FederationDomainIdentityProvider$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description +| *`constants`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-24-apis-supervisor-config-v1alpha1-federationdomaintransformsconstant[$$FederationDomainTransformsConstant$$] array__ | Constants defines constant variables and their values which will be made available to the transform expressions. +| *`expressions`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-24-apis-supervisor-config-v1alpha1-federationdomaintransformsexpression[$$FederationDomainTransformsExpression$$] array__ | Expressions are an optional list of transforms and policies to be executed in the order given during every authentication attempt, including during every session refresh. Each is a CEL expression. It may use the basic CEL language as defined in https://github.com/google/cel-spec/blob/master/doc/langdef.md plus the CEL string extensions defined in https://github.com/google/cel-go/tree/master/ext#strings. + The username and groups extracted from the identity provider, and the constants defined in this CR, are available as variables in all expressions. The username is provided via a variable called `username` and the list of group names is provided via a variable called `groups` (which may be an empty list). Each user-provided constants is provided via a variable named `strConst.varName` for string constants and `strListConst.varName` for string list constants. + The only allowed types for expressions are currently policy/v1, username/v1, and groups/v1. Each policy/v1 must return a boolean, and when it returns false, no more expressions from the list are evaluated and the authentication attempt is rejected. Transformations of type policy/v1 do not return usernames or group names, and therefore cannot change the username or group names. Each username/v1 transform must return the new username (a string), which can be the same as the old username. Transformations of type username/v1 do not return group names, and therefore cannot change the group names. Each groups/v1 transform must return the new groups list (list of strings), which can be the same as the old groups list. Transformations of type groups/v1 do not return usernames, and therefore cannot change the usernames. After each expression, the new (potentially changed) username or groups get passed to the following expression. + Any compilation or static type-checking failure of any expression will cause an error status on the FederationDomain. During an authentication attempt, any unexpected runtime evaluation errors (e.g. division by zero) cause the authentication attempt to fail. When all expressions evaluate successfully, then the (potentially changed) username and group names have been decided for that authentication attempt. +| *`examples`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-24-apis-supervisor-config-v1alpha1-federationdomaintransformsexample[$$FederationDomainTransformsExample$$] array__ | Examples can optionally be used to ensure that the sequence of transformation expressions are working as expected. Examples define sample input identities which are then run through the expression list, and the results are compared to the expected results. If any example in this list fails, then this FederationDomain will not be available for use, and the error(s) will be added to its status. This can be used to help guard against programming mistakes in the expressions, and also act as living documentation for other administrators to better understand the expressions. +|=== + + +[id="{anchor_prefix}-go-pinniped-dev-generated-1-24-apis-supervisor-config-v1alpha1-federationdomaintransformsconstant"] +==== FederationDomainTransformsConstant + +FederationDomainTransformsConstant defines a constant variable and its value which will be made available to the transform expressions. This is a union type, and Type is the discriminator field. + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-24-apis-supervisor-config-v1alpha1-federationdomaintransforms[$$FederationDomainTransforms$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description +| *`name`* __string__ | Name determines the name of the constant. It must be a valid identifier name. +| *`type`* __string__ | Type determines the type of the constant, and indicates which other field should be non-empty. +| *`stringValue`* __string__ | StringValue should hold the value when Type is "string", and is otherwise ignored. +| *`stringListValue`* __string array__ | StringListValue should hold the value when Type is "stringList", and is otherwise ignored. +|=== + + +[id="{anchor_prefix}-go-pinniped-dev-generated-1-24-apis-supervisor-config-v1alpha1-federationdomaintransformsexample"] +==== FederationDomainTransformsExample + +FederationDomainTransformsExample defines a transform example. + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-24-apis-supervisor-config-v1alpha1-federationdomaintransforms[$$FederationDomainTransforms$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description +| *`username`* __string__ | Username is the input username. +| *`groups`* __string array__ | Groups is the input list of group names. +| *`expects`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-24-apis-supervisor-config-v1alpha1-federationdomaintransformsexampleexpects[$$FederationDomainTransformsExampleExpects$$]__ | Expects is the expected output of the entire sequence of transforms when they are run against the input Username and Groups. +|=== + + +[id="{anchor_prefix}-go-pinniped-dev-generated-1-24-apis-supervisor-config-v1alpha1-federationdomaintransformsexampleexpects"] +==== FederationDomainTransformsExampleExpects + +FederationDomainTransformsExampleExpects defines the expected result for a transforms example. + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-24-apis-supervisor-config-v1alpha1-federationdomaintransformsexample[$$FederationDomainTransformsExample$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description +| *`username`* __string__ | Username is the expected username. +| *`groups`* __string array__ | Groups is the expected list of group names. +| *`rejected`* __boolean__ | Rejected should be set to true when the expected result of the transforms is that a policy rejected the authentication attempt. +| *`message`* __string__ | Message is the expected error message of the transforms. When Rejected is true, then Message is the expected message for the policy which rejected the authentication attempt. When Rejected is true and Message is blank, then Message will be treated as the default error message for authentication attempts which are rejected by a policy. When Rejected is false, then Message is the expected error message for some other non-policy transformation error, such as a runtime error. When Rejected is false, there is no default expected Message. +|=== + + +[id="{anchor_prefix}-go-pinniped-dev-generated-1-24-apis-supervisor-config-v1alpha1-federationdomaintransformsexpression"] +==== FederationDomainTransformsExpression + +FederationDomainTransformsExpression defines a transform expression. + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-24-apis-supervisor-config-v1alpha1-federationdomaintransforms[$$FederationDomainTransforms$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description +| *`type`* __string__ | Type determines the type of the expression. It must be one of the supported types. +| *`expression`* __string__ | Expression is a CEL expression that will be evaluated based on the Type during an authentication. +| *`message`* __string__ | Message is only used when Type is policy/v1. It defines an error message to be used when the policy rejects an authentication attempt. When empty, a default message will be used. +|=== + + [id="{anchor_prefix}-go-pinniped-dev-generated-1-24-apis-supervisor-config-v1alpha1-oidcclient"] ==== OIDCClient diff --git a/generated/1.24/apis/supervisor/config/v1alpha1/types_federationdomain.go b/generated/1.24/apis/supervisor/config/v1alpha1/types_federationdomain.go index 27de4401c0..7f1956845e 100644 --- a/generated/1.24/apis/supervisor/config/v1alpha1/types_federationdomain.go +++ b/generated/1.24/apis/supervisor/config/v1alpha1/types_federationdomain.go @@ -1,4 +1,4 @@ -// Copyright 2020-2022 the Pinniped contributors. All Rights Reserved. +// Copyright 2020-2023 the Pinniped contributors. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 package v1alpha1 @@ -42,6 +42,155 @@ type FederationDomainTLSSpec struct { SecretName string `json:"secretName,omitempty"` } +// FederationDomainTransformsConstant defines a constant variable and its value which will be made available to +// the transform expressions. This is a union type, and Type is the discriminator field. +type FederationDomainTransformsConstant struct { + // Name determines the name of the constant. It must be a valid identifier name. + // +kubebuilder:validation:Pattern=`^[a-zA-Z][_a-zA-Z0-9]*$` + // +kubebuilder:validation:MinLength=1 + // +kubebuilder:validation:MaxLength=64 + Name string `json:"name"` + + // Type determines the type of the constant, and indicates which other field should be non-empty. + // +kubebuilder:validation:Enum=string;stringList + Type string `json:"type"` + + // StringValue should hold the value when Type is "string", and is otherwise ignored. + // +optional + StringValue string `json:"stringValue,omitempty"` + + // StringListValue should hold the value when Type is "stringList", and is otherwise ignored. + // +optional + StringListValue []string `json:"stringListValue,omitempty"` +} + +// FederationDomainTransformsExpression defines a transform expression. +type FederationDomainTransformsExpression struct { + // Type determines the type of the expression. It must be one of the supported types. + // +kubebuilder:validation:Enum=policy/v1;username/v1;groups/v1 + Type string `json:"type"` + + // Expression is a CEL expression that will be evaluated based on the Type during an authentication. + // +kubebuilder:validation:MinLength=1 + Expression string `json:"expression"` + + // Message is only used when Type is policy/v1. It defines an error message to be used when the policy rejects + // an authentication attempt. When empty, a default message will be used. + // +optional + Message string `json:"message,omitempty"` +} + +// FederationDomainTransformsExample defines a transform example. +type FederationDomainTransformsExample struct { + // Username is the input username. + // +kubebuilder:validation:MinLength=1 + Username string `json:"username"` + + // Groups is the input list of group names. + // +optional + Groups []string `json:"groups,omitempty"` + + // Expects is the expected output of the entire sequence of transforms when they are run against the + // input Username and Groups. + Expects FederationDomainTransformsExampleExpects `json:"expects"` +} + +// FederationDomainTransformsExampleExpects defines the expected result for a transforms example. +type FederationDomainTransformsExampleExpects struct { + // Username is the expected username. + // +optional + Username string `json:"username,omitempty"` + + // Groups is the expected list of group names. + // +optional + Groups []string `json:"groups,omitempty"` + + // Rejected should be set to true when the expected result of the transforms is that a policy rejected the + // authentication attempt. + // +optional + Rejected bool `json:"rejected,omitempty"` + + // Message is the expected error message of the transforms. When Rejected is true, then Message is the expected + // message for the policy which rejected the authentication attempt. When Rejected is true and Message is blank, + // then Message will be treated as the default error message for authentication attempts which are rejected by a + // policy. When Rejected is false, then Message is the expected error message for some other non-policy + // transformation error, such as a runtime error. When Rejected is false, there is no default expected Message. + // +optional + Message string `json:"message,omitempty"` +} + +// FederationDomainTransforms defines identity transformations for an identity provider's usage on a FederationDomain. +type FederationDomainTransforms struct { + // Constants defines constant variables and their values which will be made available to the transform expressions. + // +patchMergeKey=name + // +patchStrategy=merge + // +listType=map + // +listMapKey=name + // +optional + Constants []FederationDomainTransformsConstant `json:"constants,omitempty"` + + // Expressions are an optional list of transforms and policies to be executed in the order given during every + // authentication attempt, including during every session refresh. + // Each is a CEL expression. It may use the basic CEL language as defined in + // https://github.com/google/cel-spec/blob/master/doc/langdef.md plus the CEL string extensions defined in + // https://github.com/google/cel-go/tree/master/ext#strings. + // + // The username and groups extracted from the identity provider, and the constants defined in this CR, are + // available as variables in all expressions. The username is provided via a variable called `username` and + // the list of group names is provided via a variable called `groups` (which may be an empty list). + // Each user-provided constants is provided via a variable named `strConst.varName` for string constants + // and `strListConst.varName` for string list constants. + // + // The only allowed types for expressions are currently policy/v1, username/v1, and groups/v1. + // Each policy/v1 must return a boolean, and when it returns false, no more expressions from the list are evaluated + // and the authentication attempt is rejected. + // Transformations of type policy/v1 do not return usernames or group names, and therefore cannot change the + // username or group names. + // Each username/v1 transform must return the new username (a string), which can be the same as the old username. + // Transformations of type username/v1 do not return group names, and therefore cannot change the group names. + // Each groups/v1 transform must return the new groups list (list of strings), which can be the same as the old + // groups list. + // Transformations of type groups/v1 do not return usernames, and therefore cannot change the usernames. + // After each expression, the new (potentially changed) username or groups get passed to the following expression. + // + // Any compilation or static type-checking failure of any expression will cause an error status on the FederationDomain. + // During an authentication attempt, any unexpected runtime evaluation errors (e.g. division by zero) cause the + // authentication attempt to fail. When all expressions evaluate successfully, then the (potentially changed) username + // and group names have been decided for that authentication attempt. + // + // +optional + Expressions []FederationDomainTransformsExpression `json:"expressions,omitempty"` + + // Examples can optionally be used to ensure that the sequence of transformation expressions are working as + // expected. Examples define sample input identities which are then run through the expression list, and the + // results are compared to the expected results. If any example in this list fails, then this + // FederationDomain will not be available for use, and the error(s) will be added to its status. + // This can be used to help guard against programming mistakes in the expressions, and also + // act as living documentation for other administrators to better understand the expressions. + // +optional + Examples []FederationDomainTransformsExample `json:"examples,omitempty"` +} + +// FederationDomainIdentityProvider describes how an identity provider is made available in this FederationDomain. +type FederationDomainIdentityProvider struct { + // DisplayName is the name of this identity provider as it will appear to clients. This name ends up in the + // kubeconfig of end users, so changing the name of an identity provider that is in use by end users will be a + // disruptive change for those users. + // +kubebuilder:validation:MinLength=1 + DisplayName string `json:"displayName"` + + // ObjectRef is a reference to a Pinniped identity provider resource. A valid reference is required. + // If the reference cannot be resolved then the identity provider will not be made available. + // Must refer to a resource of one of the Pinniped identity provider types, e.g. OIDCIdentityProvider, + // LDAPIdentityProvider, ActiveDirectoryIdentityProvider. + ObjectRef corev1.TypedLocalObjectReference `json:"objectRef"` + + // Transforms is an optional way to specify transformations to be applied during user authentication and + // session refresh. + // +optional + Transforms FederationDomainTransforms `json:"transforms,omitempty"` +} + // FederationDomainSpec is a struct that describes an OIDC Provider. type FederationDomainSpec struct { // Issuer is the OIDC Provider's issuer, per the OIDC Discovery Metadata document, as well as the @@ -58,6 +207,32 @@ type FederationDomainSpec struct { // TLS configures how this FederationDomain is served over Transport Layer Security (TLS). // +optional TLS *FederationDomainTLSSpec `json:"tls,omitempty"` + + // IdentityProviders is the list of identity providers available for use by this FederationDomain. + // + // An identity provider (e.g. OIDCIdentityProvider or LDAPIdentityProvider) describes how to connect to a server, + // how to talk in a specific protocol for authentication, and how to use the schema of that server/protocol to + // extract a normalized user identity. Normalized user identities include a username and a list of group names. + // In contrast, IdentityProviders describes how to use that normalized identity in a group of + // Kubernetes clusters. It can perform arbitrary transformations on that normalized identity. For example, a + // transformation can add a prefix to all usernames to help avoid accidental conflicts when multiple identity + // providers have different users with the same username (e.g. "idp1:ryan" versus "idp2:ryan"). + // IdentityProviders can also implement arbitrary authentication rejection policies. + // Even though a user was able to authenticate with the identity provider, a policy can disallow the authentication + // to the Kubernetes clusters that belong to this FederationDomain. For example, a policy could disallow the + // authentication unless the user belongs to a specific group in the identity provider. + // + // For backwards compatibility with versions of Pinniped which predate support for multiple identity providers, + // an empty IdentityProviders list will cause the FederationDomain to use all available identity providers which + // exist in the same namespace, but also to reject all authentication requests when there is more than one identity + // provider currently defined. In this backwards compatibility mode, the name of the identity provider resource + // (e.g. the Name of an OIDCIdentityProvider resource) will be used as the name of the identity provider in this + // FederationDomain. This mode is provided to make upgrading from older versions easier. However, instead of + // relying on this backwards compatibility mode, please consider this mode to be deprecated and please instead + // explicitly list the identity provider using this IdentityProviders field. + // + // +optional + IdentityProviders []FederationDomainIdentityProvider `json:"identityProviders,omitempty"` } // FederationDomainSecrets holds information about this OIDC Provider's secrets. diff --git a/generated/1.24/apis/supervisor/config/v1alpha1/zz_generated.deepcopy.go b/generated/1.24/apis/supervisor/config/v1alpha1/zz_generated.deepcopy.go index 682013782a..5e48e41556 100644 --- a/generated/1.24/apis/supervisor/config/v1alpha1/zz_generated.deepcopy.go +++ b/generated/1.24/apis/supervisor/config/v1alpha1/zz_generated.deepcopy.go @@ -57,6 +57,24 @@ func (in *FederationDomain) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FederationDomainIdentityProvider) DeepCopyInto(out *FederationDomainIdentityProvider) { + *out = *in + in.ObjectRef.DeepCopyInto(&out.ObjectRef) + in.Transforms.DeepCopyInto(&out.Transforms) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FederationDomainIdentityProvider. +func (in *FederationDomainIdentityProvider) DeepCopy() *FederationDomainIdentityProvider { + if in == nil { + return nil + } + out := new(FederationDomainIdentityProvider) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *FederationDomainList) DeepCopyInto(out *FederationDomainList) { *out = *in @@ -118,6 +136,13 @@ func (in *FederationDomainSpec) DeepCopyInto(out *FederationDomainSpec) { *out = new(FederationDomainTLSSpec) **out = **in } + if in.IdentityProviders != nil { + in, out := &in.IdentityProviders, &out.IdentityProviders + *out = make([]FederationDomainIdentityProvider, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } return } @@ -168,6 +193,121 @@ func (in *FederationDomainTLSSpec) DeepCopy() *FederationDomainTLSSpec { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FederationDomainTransforms) DeepCopyInto(out *FederationDomainTransforms) { + *out = *in + if in.Constants != nil { + in, out := &in.Constants, &out.Constants + *out = make([]FederationDomainTransformsConstant, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.Expressions != nil { + in, out := &in.Expressions, &out.Expressions + *out = make([]FederationDomainTransformsExpression, len(*in)) + copy(*out, *in) + } + if in.Examples != nil { + in, out := &in.Examples, &out.Examples + *out = make([]FederationDomainTransformsExample, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FederationDomainTransforms. +func (in *FederationDomainTransforms) DeepCopy() *FederationDomainTransforms { + if in == nil { + return nil + } + out := new(FederationDomainTransforms) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FederationDomainTransformsConstant) DeepCopyInto(out *FederationDomainTransformsConstant) { + *out = *in + if in.StringListValue != nil { + in, out := &in.StringListValue, &out.StringListValue + *out = make([]string, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FederationDomainTransformsConstant. +func (in *FederationDomainTransformsConstant) DeepCopy() *FederationDomainTransformsConstant { + if in == nil { + return nil + } + out := new(FederationDomainTransformsConstant) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FederationDomainTransformsExample) DeepCopyInto(out *FederationDomainTransformsExample) { + *out = *in + if in.Groups != nil { + in, out := &in.Groups, &out.Groups + *out = make([]string, len(*in)) + copy(*out, *in) + } + in.Expects.DeepCopyInto(&out.Expects) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FederationDomainTransformsExample. +func (in *FederationDomainTransformsExample) DeepCopy() *FederationDomainTransformsExample { + if in == nil { + return nil + } + out := new(FederationDomainTransformsExample) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FederationDomainTransformsExampleExpects) DeepCopyInto(out *FederationDomainTransformsExampleExpects) { + *out = *in + if in.Groups != nil { + in, out := &in.Groups, &out.Groups + *out = make([]string, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FederationDomainTransformsExampleExpects. +func (in *FederationDomainTransformsExampleExpects) DeepCopy() *FederationDomainTransformsExampleExpects { + if in == nil { + return nil + } + out := new(FederationDomainTransformsExampleExpects) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FederationDomainTransformsExpression) DeepCopyInto(out *FederationDomainTransformsExpression) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FederationDomainTransformsExpression. +func (in *FederationDomainTransformsExpression) DeepCopy() *FederationDomainTransformsExpression { + if in == nil { + return nil + } + out := new(FederationDomainTransformsExpression) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *OIDCClient) DeepCopyInto(out *OIDCClient) { *out = *in diff --git a/generated/1.24/crds/config.supervisor.pinniped.dev_federationdomains.yaml b/generated/1.24/crds/config.supervisor.pinniped.dev_federationdomains.yaml index 71f7370d11..5e839fccd0 100644 --- a/generated/1.24/crds/config.supervisor.pinniped.dev_federationdomains.yaml +++ b/generated/1.24/crds/config.supervisor.pinniped.dev_federationdomains.yaml @@ -47,6 +47,255 @@ spec: spec: description: Spec of the OIDC provider. properties: + identityProviders: + description: "IdentityProviders is the list of identity providers + available for use by this FederationDomain. \n An identity provider + (e.g. OIDCIdentityProvider or LDAPIdentityProvider) describes how + to connect to a server, how to talk in a specific protocol for authentication, + and how to use the schema of that server/protocol to extract a normalized + user identity. Normalized user identities include a username and + a list of group names. In contrast, IdentityProviders describes + how to use that normalized identity in a group of Kubernetes clusters. + It can perform arbitrary transformations on that normalized identity. + For example, a transformation can add a prefix to all usernames + to help avoid accidental conflicts when multiple identity providers + have different users with the same username (e.g. \"idp1:ryan\" + versus \"idp2:ryan\"). IdentityProviders can also implement arbitrary + authentication rejection policies. Even though a user was able to + authenticate with the identity provider, a policy can disallow the + authentication to the Kubernetes clusters that belong to this FederationDomain. + For example, a policy could disallow the authentication unless the + user belongs to a specific group in the identity provider. \n For + backwards compatibility with versions of Pinniped which predate + support for multiple identity providers, an empty IdentityProviders + list will cause the FederationDomain to use all available identity + providers which exist in the same namespace, but also to reject + all authentication requests when there is more than one identity + provider currently defined. In this backwards compatibility mode, + the name of the identity provider resource (e.g. the Name of an + OIDCIdentityProvider resource) will be used as the name of the identity + provider in this FederationDomain. This mode is provided to make + upgrading from older versions easier. However, instead of relying + on this backwards compatibility mode, please consider this mode + to be deprecated and please instead explicitly list the identity + provider using this IdentityProviders field." + items: + description: FederationDomainIdentityProvider describes how an identity + provider is made available in this FederationDomain. + properties: + displayName: + description: DisplayName is the name of this identity provider + as it will appear to clients. This name ends up in the kubeconfig + of end users, so changing the name of an identity provider + that is in use by end users will be a disruptive change for + those users. + minLength: 1 + type: string + objectRef: + description: ObjectRef is a reference to a Pinniped identity + provider resource. A valid reference is required. If the reference + cannot be resolved then the identity provider will not be + made available. Must refer to a resource of one of the Pinniped + identity provider types, e.g. OIDCIdentityProvider, LDAPIdentityProvider, + ActiveDirectoryIdentityProvider. + properties: + apiGroup: + description: APIGroup is the group for the resource being + referenced. If APIGroup is not specified, the specified + Kind must be in the core API group. For any other third-party + types, APIGroup is required. + type: string + kind: + description: Kind is the type of resource being referenced + type: string + name: + description: Name is the name of resource being referenced + type: string + required: + - kind + - name + type: object + transforms: + description: Transforms is an optional way to specify transformations + to be applied during user authentication and session refresh. + properties: + constants: + description: Constants defines constant variables and their + values which will be made available to the transform expressions. + items: + description: FederationDomainTransformsConstant defines + a constant variable and its value which will be made + available to the transform expressions. This is a union + type, and Type is the discriminator field. + properties: + name: + description: Name determines the name of the constant. + It must be a valid identifier name. + maxLength: 64 + minLength: 1 + pattern: ^[a-zA-Z][_a-zA-Z0-9]*$ + type: string + stringListValue: + description: StringListValue should hold the value + when Type is "stringList", and is otherwise ignored. + items: + type: string + type: array + stringValue: + description: StringValue should hold the value when + Type is "string", and is otherwise ignored. + type: string + type: + description: Type determines the type of the constant, + and indicates which other field should be non-empty. + enum: + - string + - stringList + type: string + required: + - name + - type + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + examples: + description: Examples can optionally be used to ensure that + the sequence of transformation expressions are working + as expected. Examples define sample input identities which + are then run through the expression list, and the results + are compared to the expected results. If any example in + this list fails, then this FederationDomain will not be + available for use, and the error(s) will be added to its + status. This can be used to help guard against programming + mistakes in the expressions, and also act as living documentation + for other administrators to better understand the expressions. + items: + description: FederationDomainTransformsExample defines + a transform example. + properties: + expects: + description: Expects is the expected output of the + entire sequence of transforms when they are run + against the input Username and Groups. + properties: + groups: + description: Groups is the expected list of group + names. + items: + type: string + type: array + message: + description: Message is the expected error message + of the transforms. When Rejected is true, then + Message is the expected message for the policy + which rejected the authentication attempt. When + Rejected is true and Message is blank, then + Message will be treated as the default error + message for authentication attempts which are + rejected by a policy. When Rejected is false, + then Message is the expected error message for + some other non-policy transformation error, + such as a runtime error. When Rejected is false, + there is no default expected Message. + type: string + rejected: + description: Rejected should be set to true when + the expected result of the transforms is that + a policy rejected the authentication attempt. + type: boolean + username: + description: Username is the expected username. + type: string + type: object + groups: + description: Groups is the input list of group names. + items: + type: string + type: array + username: + description: Username is the input username. + minLength: 1 + type: string + required: + - expects + - username + type: object + type: array + expressions: + description: "Expressions are an optional list of transforms + and policies to be executed in the order given during + every authentication attempt, including during every session + refresh. Each is a CEL expression. It may use the basic + CEL language as defined in https://github.com/google/cel-spec/blob/master/doc/langdef.md + plus the CEL string extensions defined in https://github.com/google/cel-go/tree/master/ext#strings. + \n The username and groups extracted from the identity + provider, and the constants defined in this CR, are available + as variables in all expressions. The username is provided + via a variable called `username` and the list of group + names is provided via a variable called `groups` (which + may be an empty list). Each user-provided constants is + provided via a variable named `strConst.varName` for string + constants and `strListConst.varName` for string list constants. + \n The only allowed types for expressions are currently + policy/v1, username/v1, and groups/v1. Each policy/v1 + must return a boolean, and when it returns false, no more + expressions from the list are evaluated and the authentication + attempt is rejected. Transformations of type policy/v1 + do not return usernames or group names, and therefore + cannot change the username or group names. Each username/v1 + transform must return the new username (a string), which + can be the same as the old username. Transformations of + type username/v1 do not return group names, and therefore + cannot change the group names. Each groups/v1 transform + must return the new groups list (list of strings), which + can be the same as the old groups list. Transformations + of type groups/v1 do not return usernames, and therefore + cannot change the usernames. After each expression, the + new (potentially changed) username or groups get passed + to the following expression. \n Any compilation or static + type-checking failure of any expression will cause an + error status on the FederationDomain. During an authentication + attempt, any unexpected runtime evaluation errors (e.g. + division by zero) cause the authentication attempt to + fail. When all expressions evaluate successfully, then + the (potentially changed) username and group names have + been decided for that authentication attempt." + items: + description: FederationDomainTransformsExpression defines + a transform expression. + properties: + expression: + description: Expression is a CEL expression that will + be evaluated based on the Type during an authentication. + minLength: 1 + type: string + message: + description: Message is only used when Type is policy/v1. + It defines an error message to be used when the + policy rejects an authentication attempt. When empty, + a default message will be used. + type: string + type: + description: Type determines the type of the expression. + It must be one of the supported types. + enum: + - policy/v1 + - username/v1 + - groups/v1 + type: string + required: + - expression + - type + type: object + type: array + type: object + required: + - displayName + - objectRef + type: object + type: array issuer: description: "Issuer is the OIDC Provider's issuer, per the OIDC Discovery Metadata document, as well as the identifier that it will use for diff --git a/generated/1.25/README.adoc b/generated/1.25/README.adoc index 204f1ea0f5..640b0608b8 100644 --- a/generated/1.25/README.adoc +++ b/generated/1.25/README.adoc @@ -637,6 +637,25 @@ FederationDomain describes the configuration of an OIDC provider. |=== +[id="{anchor_prefix}-go-pinniped-dev-generated-1-25-apis-supervisor-config-v1alpha1-federationdomainidentityprovider"] +==== FederationDomainIdentityProvider + +FederationDomainIdentityProvider describes how an identity provider is made available in this FederationDomain. + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-25-apis-supervisor-config-v1alpha1-federationdomainspec[$$FederationDomainSpec$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description +| *`displayName`* __string__ | DisplayName is the name of this identity provider as it will appear to clients. This name ends up in the kubeconfig of end users, so changing the name of an identity provider that is in use by end users will be a disruptive change for those users. +| *`objectRef`* __link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.25/#typedlocalobjectreference-v1-core[$$TypedLocalObjectReference$$]__ | ObjectRef is a reference to a Pinniped identity provider resource. A valid reference is required. If the reference cannot be resolved then the identity provider will not be made available. Must refer to a resource of one of the Pinniped identity provider types, e.g. OIDCIdentityProvider, LDAPIdentityProvider, ActiveDirectoryIdentityProvider. +| *`transforms`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-25-apis-supervisor-config-v1alpha1-federationdomaintransforms[$$FederationDomainTransforms$$]__ | Transforms is an optional way to specify transformations to be applied during user authentication and session refresh. +|=== + + [id="{anchor_prefix}-go-pinniped-dev-generated-1-25-apis-supervisor-config-v1alpha1-federationdomainsecrets"] @@ -675,6 +694,9 @@ FederationDomainSpec is a struct that describes an OIDC Provider. | *`issuer`* __string__ | Issuer is the OIDC Provider's issuer, per the OIDC Discovery Metadata document, as well as the identifier that it will use for the iss claim in issued JWTs. This field will also be used as the base URL for any endpoints used by the OIDC Provider (e.g., if your issuer is https://example.com/foo, then your authorization endpoint will look like https://example.com/foo/some/path/to/auth/endpoint). See https://openid.net/specs/openid-connect-discovery-1_0.html#rfc.section.3 for more information. | *`tls`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-25-apis-supervisor-config-v1alpha1-federationdomaintlsspec[$$FederationDomainTLSSpec$$]__ | TLS configures how this FederationDomain is served over Transport Layer Security (TLS). +| *`identityProviders`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-25-apis-supervisor-config-v1alpha1-federationdomainidentityprovider[$$FederationDomainIdentityProvider$$] array__ | IdentityProviders is the list of identity providers available for use by this FederationDomain. + An identity provider (e.g. OIDCIdentityProvider or LDAPIdentityProvider) describes how to connect to a server, how to talk in a specific protocol for authentication, and how to use the schema of that server/protocol to extract a normalized user identity. Normalized user identities include a username and a list of group names. In contrast, IdentityProviders describes how to use that normalized identity in a group of Kubernetes clusters. It can perform arbitrary transformations on that normalized identity. For example, a transformation can add a prefix to all usernames to help avoid accidental conflicts when multiple identity providers have different users with the same username (e.g. "idp1:ryan" versus "idp2:ryan"). IdentityProviders can also implement arbitrary authentication rejection policies. Even though a user was able to authenticate with the identity provider, a policy can disallow the authentication to the Kubernetes clusters that belong to this FederationDomain. For example, a policy could disallow the authentication unless the user belongs to a specific group in the identity provider. + For backwards compatibility with versions of Pinniped which predate support for multiple identity providers, an empty IdentityProviders list will cause the FederationDomain to use all available identity providers which exist in the same namespace, but also to reject all authentication requests when there is more than one identity provider currently defined. In this backwards compatibility mode, the name of the identity provider resource (e.g. the Name of an OIDCIdentityProvider resource) will be used as the name of the identity provider in this FederationDomain. This mode is provided to make upgrading from older versions easier. However, instead of relying on this backwards compatibility mode, please consider this mode to be deprecated and please instead explicitly list the identity provider using this IdentityProviders field. |=== @@ -719,6 +741,106 @@ FederationDomainTLSSpec is a struct that describes the TLS configuration for an |=== +[id="{anchor_prefix}-go-pinniped-dev-generated-1-25-apis-supervisor-config-v1alpha1-federationdomaintransforms"] +==== FederationDomainTransforms + +FederationDomainTransforms defines identity transformations for an identity provider's usage on a FederationDomain. + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-25-apis-supervisor-config-v1alpha1-federationdomainidentityprovider[$$FederationDomainIdentityProvider$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description +| *`constants`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-25-apis-supervisor-config-v1alpha1-federationdomaintransformsconstant[$$FederationDomainTransformsConstant$$] array__ | Constants defines constant variables and their values which will be made available to the transform expressions. +| *`expressions`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-25-apis-supervisor-config-v1alpha1-federationdomaintransformsexpression[$$FederationDomainTransformsExpression$$] array__ | Expressions are an optional list of transforms and policies to be executed in the order given during every authentication attempt, including during every session refresh. Each is a CEL expression. It may use the basic CEL language as defined in https://github.com/google/cel-spec/blob/master/doc/langdef.md plus the CEL string extensions defined in https://github.com/google/cel-go/tree/master/ext#strings. + The username and groups extracted from the identity provider, and the constants defined in this CR, are available as variables in all expressions. The username is provided via a variable called `username` and the list of group names is provided via a variable called `groups` (which may be an empty list). Each user-provided constants is provided via a variable named `strConst.varName` for string constants and `strListConst.varName` for string list constants. + The only allowed types for expressions are currently policy/v1, username/v1, and groups/v1. Each policy/v1 must return a boolean, and when it returns false, no more expressions from the list are evaluated and the authentication attempt is rejected. Transformations of type policy/v1 do not return usernames or group names, and therefore cannot change the username or group names. Each username/v1 transform must return the new username (a string), which can be the same as the old username. Transformations of type username/v1 do not return group names, and therefore cannot change the group names. Each groups/v1 transform must return the new groups list (list of strings), which can be the same as the old groups list. Transformations of type groups/v1 do not return usernames, and therefore cannot change the usernames. After each expression, the new (potentially changed) username or groups get passed to the following expression. + Any compilation or static type-checking failure of any expression will cause an error status on the FederationDomain. During an authentication attempt, any unexpected runtime evaluation errors (e.g. division by zero) cause the authentication attempt to fail. When all expressions evaluate successfully, then the (potentially changed) username and group names have been decided for that authentication attempt. +| *`examples`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-25-apis-supervisor-config-v1alpha1-federationdomaintransformsexample[$$FederationDomainTransformsExample$$] array__ | Examples can optionally be used to ensure that the sequence of transformation expressions are working as expected. Examples define sample input identities which are then run through the expression list, and the results are compared to the expected results. If any example in this list fails, then this FederationDomain will not be available for use, and the error(s) will be added to its status. This can be used to help guard against programming mistakes in the expressions, and also act as living documentation for other administrators to better understand the expressions. +|=== + + +[id="{anchor_prefix}-go-pinniped-dev-generated-1-25-apis-supervisor-config-v1alpha1-federationdomaintransformsconstant"] +==== FederationDomainTransformsConstant + +FederationDomainTransformsConstant defines a constant variable and its value which will be made available to the transform expressions. This is a union type, and Type is the discriminator field. + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-25-apis-supervisor-config-v1alpha1-federationdomaintransforms[$$FederationDomainTransforms$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description +| *`name`* __string__ | Name determines the name of the constant. It must be a valid identifier name. +| *`type`* __string__ | Type determines the type of the constant, and indicates which other field should be non-empty. +| *`stringValue`* __string__ | StringValue should hold the value when Type is "string", and is otherwise ignored. +| *`stringListValue`* __string array__ | StringListValue should hold the value when Type is "stringList", and is otherwise ignored. +|=== + + +[id="{anchor_prefix}-go-pinniped-dev-generated-1-25-apis-supervisor-config-v1alpha1-federationdomaintransformsexample"] +==== FederationDomainTransformsExample + +FederationDomainTransformsExample defines a transform example. + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-25-apis-supervisor-config-v1alpha1-federationdomaintransforms[$$FederationDomainTransforms$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description +| *`username`* __string__ | Username is the input username. +| *`groups`* __string array__ | Groups is the input list of group names. +| *`expects`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-25-apis-supervisor-config-v1alpha1-federationdomaintransformsexampleexpects[$$FederationDomainTransformsExampleExpects$$]__ | Expects is the expected output of the entire sequence of transforms when they are run against the input Username and Groups. +|=== + + +[id="{anchor_prefix}-go-pinniped-dev-generated-1-25-apis-supervisor-config-v1alpha1-federationdomaintransformsexampleexpects"] +==== FederationDomainTransformsExampleExpects + +FederationDomainTransformsExampleExpects defines the expected result for a transforms example. + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-25-apis-supervisor-config-v1alpha1-federationdomaintransformsexample[$$FederationDomainTransformsExample$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description +| *`username`* __string__ | Username is the expected username. +| *`groups`* __string array__ | Groups is the expected list of group names. +| *`rejected`* __boolean__ | Rejected should be set to true when the expected result of the transforms is that a policy rejected the authentication attempt. +| *`message`* __string__ | Message is the expected error message of the transforms. When Rejected is true, then Message is the expected message for the policy which rejected the authentication attempt. When Rejected is true and Message is blank, then Message will be treated as the default error message for authentication attempts which are rejected by a policy. When Rejected is false, then Message is the expected error message for some other non-policy transformation error, such as a runtime error. When Rejected is false, there is no default expected Message. +|=== + + +[id="{anchor_prefix}-go-pinniped-dev-generated-1-25-apis-supervisor-config-v1alpha1-federationdomaintransformsexpression"] +==== FederationDomainTransformsExpression + +FederationDomainTransformsExpression defines a transform expression. + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-25-apis-supervisor-config-v1alpha1-federationdomaintransforms[$$FederationDomainTransforms$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description +| *`type`* __string__ | Type determines the type of the expression. It must be one of the supported types. +| *`expression`* __string__ | Expression is a CEL expression that will be evaluated based on the Type during an authentication. +| *`message`* __string__ | Message is only used when Type is policy/v1. It defines an error message to be used when the policy rejects an authentication attempt. When empty, a default message will be used. +|=== + + [id="{anchor_prefix}-go-pinniped-dev-generated-1-25-apis-supervisor-config-v1alpha1-oidcclient"] ==== OIDCClient diff --git a/generated/1.25/apis/supervisor/config/v1alpha1/types_federationdomain.go b/generated/1.25/apis/supervisor/config/v1alpha1/types_federationdomain.go index 27de4401c0..7f1956845e 100644 --- a/generated/1.25/apis/supervisor/config/v1alpha1/types_federationdomain.go +++ b/generated/1.25/apis/supervisor/config/v1alpha1/types_federationdomain.go @@ -1,4 +1,4 @@ -// Copyright 2020-2022 the Pinniped contributors. All Rights Reserved. +// Copyright 2020-2023 the Pinniped contributors. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 package v1alpha1 @@ -42,6 +42,155 @@ type FederationDomainTLSSpec struct { SecretName string `json:"secretName,omitempty"` } +// FederationDomainTransformsConstant defines a constant variable and its value which will be made available to +// the transform expressions. This is a union type, and Type is the discriminator field. +type FederationDomainTransformsConstant struct { + // Name determines the name of the constant. It must be a valid identifier name. + // +kubebuilder:validation:Pattern=`^[a-zA-Z][_a-zA-Z0-9]*$` + // +kubebuilder:validation:MinLength=1 + // +kubebuilder:validation:MaxLength=64 + Name string `json:"name"` + + // Type determines the type of the constant, and indicates which other field should be non-empty. + // +kubebuilder:validation:Enum=string;stringList + Type string `json:"type"` + + // StringValue should hold the value when Type is "string", and is otherwise ignored. + // +optional + StringValue string `json:"stringValue,omitempty"` + + // StringListValue should hold the value when Type is "stringList", and is otherwise ignored. + // +optional + StringListValue []string `json:"stringListValue,omitempty"` +} + +// FederationDomainTransformsExpression defines a transform expression. +type FederationDomainTransformsExpression struct { + // Type determines the type of the expression. It must be one of the supported types. + // +kubebuilder:validation:Enum=policy/v1;username/v1;groups/v1 + Type string `json:"type"` + + // Expression is a CEL expression that will be evaluated based on the Type during an authentication. + // +kubebuilder:validation:MinLength=1 + Expression string `json:"expression"` + + // Message is only used when Type is policy/v1. It defines an error message to be used when the policy rejects + // an authentication attempt. When empty, a default message will be used. + // +optional + Message string `json:"message,omitempty"` +} + +// FederationDomainTransformsExample defines a transform example. +type FederationDomainTransformsExample struct { + // Username is the input username. + // +kubebuilder:validation:MinLength=1 + Username string `json:"username"` + + // Groups is the input list of group names. + // +optional + Groups []string `json:"groups,omitempty"` + + // Expects is the expected output of the entire sequence of transforms when they are run against the + // input Username and Groups. + Expects FederationDomainTransformsExampleExpects `json:"expects"` +} + +// FederationDomainTransformsExampleExpects defines the expected result for a transforms example. +type FederationDomainTransformsExampleExpects struct { + // Username is the expected username. + // +optional + Username string `json:"username,omitempty"` + + // Groups is the expected list of group names. + // +optional + Groups []string `json:"groups,omitempty"` + + // Rejected should be set to true when the expected result of the transforms is that a policy rejected the + // authentication attempt. + // +optional + Rejected bool `json:"rejected,omitempty"` + + // Message is the expected error message of the transforms. When Rejected is true, then Message is the expected + // message for the policy which rejected the authentication attempt. When Rejected is true and Message is blank, + // then Message will be treated as the default error message for authentication attempts which are rejected by a + // policy. When Rejected is false, then Message is the expected error message for some other non-policy + // transformation error, such as a runtime error. When Rejected is false, there is no default expected Message. + // +optional + Message string `json:"message,omitempty"` +} + +// FederationDomainTransforms defines identity transformations for an identity provider's usage on a FederationDomain. +type FederationDomainTransforms struct { + // Constants defines constant variables and their values which will be made available to the transform expressions. + // +patchMergeKey=name + // +patchStrategy=merge + // +listType=map + // +listMapKey=name + // +optional + Constants []FederationDomainTransformsConstant `json:"constants,omitempty"` + + // Expressions are an optional list of transforms and policies to be executed in the order given during every + // authentication attempt, including during every session refresh. + // Each is a CEL expression. It may use the basic CEL language as defined in + // https://github.com/google/cel-spec/blob/master/doc/langdef.md plus the CEL string extensions defined in + // https://github.com/google/cel-go/tree/master/ext#strings. + // + // The username and groups extracted from the identity provider, and the constants defined in this CR, are + // available as variables in all expressions. The username is provided via a variable called `username` and + // the list of group names is provided via a variable called `groups` (which may be an empty list). + // Each user-provided constants is provided via a variable named `strConst.varName` for string constants + // and `strListConst.varName` for string list constants. + // + // The only allowed types for expressions are currently policy/v1, username/v1, and groups/v1. + // Each policy/v1 must return a boolean, and when it returns false, no more expressions from the list are evaluated + // and the authentication attempt is rejected. + // Transformations of type policy/v1 do not return usernames or group names, and therefore cannot change the + // username or group names. + // Each username/v1 transform must return the new username (a string), which can be the same as the old username. + // Transformations of type username/v1 do not return group names, and therefore cannot change the group names. + // Each groups/v1 transform must return the new groups list (list of strings), which can be the same as the old + // groups list. + // Transformations of type groups/v1 do not return usernames, and therefore cannot change the usernames. + // After each expression, the new (potentially changed) username or groups get passed to the following expression. + // + // Any compilation or static type-checking failure of any expression will cause an error status on the FederationDomain. + // During an authentication attempt, any unexpected runtime evaluation errors (e.g. division by zero) cause the + // authentication attempt to fail. When all expressions evaluate successfully, then the (potentially changed) username + // and group names have been decided for that authentication attempt. + // + // +optional + Expressions []FederationDomainTransformsExpression `json:"expressions,omitempty"` + + // Examples can optionally be used to ensure that the sequence of transformation expressions are working as + // expected. Examples define sample input identities which are then run through the expression list, and the + // results are compared to the expected results. If any example in this list fails, then this + // FederationDomain will not be available for use, and the error(s) will be added to its status. + // This can be used to help guard against programming mistakes in the expressions, and also + // act as living documentation for other administrators to better understand the expressions. + // +optional + Examples []FederationDomainTransformsExample `json:"examples,omitempty"` +} + +// FederationDomainIdentityProvider describes how an identity provider is made available in this FederationDomain. +type FederationDomainIdentityProvider struct { + // DisplayName is the name of this identity provider as it will appear to clients. This name ends up in the + // kubeconfig of end users, so changing the name of an identity provider that is in use by end users will be a + // disruptive change for those users. + // +kubebuilder:validation:MinLength=1 + DisplayName string `json:"displayName"` + + // ObjectRef is a reference to a Pinniped identity provider resource. A valid reference is required. + // If the reference cannot be resolved then the identity provider will not be made available. + // Must refer to a resource of one of the Pinniped identity provider types, e.g. OIDCIdentityProvider, + // LDAPIdentityProvider, ActiveDirectoryIdentityProvider. + ObjectRef corev1.TypedLocalObjectReference `json:"objectRef"` + + // Transforms is an optional way to specify transformations to be applied during user authentication and + // session refresh. + // +optional + Transforms FederationDomainTransforms `json:"transforms,omitempty"` +} + // FederationDomainSpec is a struct that describes an OIDC Provider. type FederationDomainSpec struct { // Issuer is the OIDC Provider's issuer, per the OIDC Discovery Metadata document, as well as the @@ -58,6 +207,32 @@ type FederationDomainSpec struct { // TLS configures how this FederationDomain is served over Transport Layer Security (TLS). // +optional TLS *FederationDomainTLSSpec `json:"tls,omitempty"` + + // IdentityProviders is the list of identity providers available for use by this FederationDomain. + // + // An identity provider (e.g. OIDCIdentityProvider or LDAPIdentityProvider) describes how to connect to a server, + // how to talk in a specific protocol for authentication, and how to use the schema of that server/protocol to + // extract a normalized user identity. Normalized user identities include a username and a list of group names. + // In contrast, IdentityProviders describes how to use that normalized identity in a group of + // Kubernetes clusters. It can perform arbitrary transformations on that normalized identity. For example, a + // transformation can add a prefix to all usernames to help avoid accidental conflicts when multiple identity + // providers have different users with the same username (e.g. "idp1:ryan" versus "idp2:ryan"). + // IdentityProviders can also implement arbitrary authentication rejection policies. + // Even though a user was able to authenticate with the identity provider, a policy can disallow the authentication + // to the Kubernetes clusters that belong to this FederationDomain. For example, a policy could disallow the + // authentication unless the user belongs to a specific group in the identity provider. + // + // For backwards compatibility with versions of Pinniped which predate support for multiple identity providers, + // an empty IdentityProviders list will cause the FederationDomain to use all available identity providers which + // exist in the same namespace, but also to reject all authentication requests when there is more than one identity + // provider currently defined. In this backwards compatibility mode, the name of the identity provider resource + // (e.g. the Name of an OIDCIdentityProvider resource) will be used as the name of the identity provider in this + // FederationDomain. This mode is provided to make upgrading from older versions easier. However, instead of + // relying on this backwards compatibility mode, please consider this mode to be deprecated and please instead + // explicitly list the identity provider using this IdentityProviders field. + // + // +optional + IdentityProviders []FederationDomainIdentityProvider `json:"identityProviders,omitempty"` } // FederationDomainSecrets holds information about this OIDC Provider's secrets. diff --git a/generated/1.25/apis/supervisor/config/v1alpha1/zz_generated.deepcopy.go b/generated/1.25/apis/supervisor/config/v1alpha1/zz_generated.deepcopy.go index 682013782a..5e48e41556 100644 --- a/generated/1.25/apis/supervisor/config/v1alpha1/zz_generated.deepcopy.go +++ b/generated/1.25/apis/supervisor/config/v1alpha1/zz_generated.deepcopy.go @@ -57,6 +57,24 @@ func (in *FederationDomain) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FederationDomainIdentityProvider) DeepCopyInto(out *FederationDomainIdentityProvider) { + *out = *in + in.ObjectRef.DeepCopyInto(&out.ObjectRef) + in.Transforms.DeepCopyInto(&out.Transforms) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FederationDomainIdentityProvider. +func (in *FederationDomainIdentityProvider) DeepCopy() *FederationDomainIdentityProvider { + if in == nil { + return nil + } + out := new(FederationDomainIdentityProvider) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *FederationDomainList) DeepCopyInto(out *FederationDomainList) { *out = *in @@ -118,6 +136,13 @@ func (in *FederationDomainSpec) DeepCopyInto(out *FederationDomainSpec) { *out = new(FederationDomainTLSSpec) **out = **in } + if in.IdentityProviders != nil { + in, out := &in.IdentityProviders, &out.IdentityProviders + *out = make([]FederationDomainIdentityProvider, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } return } @@ -168,6 +193,121 @@ func (in *FederationDomainTLSSpec) DeepCopy() *FederationDomainTLSSpec { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FederationDomainTransforms) DeepCopyInto(out *FederationDomainTransforms) { + *out = *in + if in.Constants != nil { + in, out := &in.Constants, &out.Constants + *out = make([]FederationDomainTransformsConstant, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.Expressions != nil { + in, out := &in.Expressions, &out.Expressions + *out = make([]FederationDomainTransformsExpression, len(*in)) + copy(*out, *in) + } + if in.Examples != nil { + in, out := &in.Examples, &out.Examples + *out = make([]FederationDomainTransformsExample, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FederationDomainTransforms. +func (in *FederationDomainTransforms) DeepCopy() *FederationDomainTransforms { + if in == nil { + return nil + } + out := new(FederationDomainTransforms) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FederationDomainTransformsConstant) DeepCopyInto(out *FederationDomainTransformsConstant) { + *out = *in + if in.StringListValue != nil { + in, out := &in.StringListValue, &out.StringListValue + *out = make([]string, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FederationDomainTransformsConstant. +func (in *FederationDomainTransformsConstant) DeepCopy() *FederationDomainTransformsConstant { + if in == nil { + return nil + } + out := new(FederationDomainTransformsConstant) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FederationDomainTransformsExample) DeepCopyInto(out *FederationDomainTransformsExample) { + *out = *in + if in.Groups != nil { + in, out := &in.Groups, &out.Groups + *out = make([]string, len(*in)) + copy(*out, *in) + } + in.Expects.DeepCopyInto(&out.Expects) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FederationDomainTransformsExample. +func (in *FederationDomainTransformsExample) DeepCopy() *FederationDomainTransformsExample { + if in == nil { + return nil + } + out := new(FederationDomainTransformsExample) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FederationDomainTransformsExampleExpects) DeepCopyInto(out *FederationDomainTransformsExampleExpects) { + *out = *in + if in.Groups != nil { + in, out := &in.Groups, &out.Groups + *out = make([]string, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FederationDomainTransformsExampleExpects. +func (in *FederationDomainTransformsExampleExpects) DeepCopy() *FederationDomainTransformsExampleExpects { + if in == nil { + return nil + } + out := new(FederationDomainTransformsExampleExpects) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FederationDomainTransformsExpression) DeepCopyInto(out *FederationDomainTransformsExpression) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FederationDomainTransformsExpression. +func (in *FederationDomainTransformsExpression) DeepCopy() *FederationDomainTransformsExpression { + if in == nil { + return nil + } + out := new(FederationDomainTransformsExpression) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *OIDCClient) DeepCopyInto(out *OIDCClient) { *out = *in diff --git a/generated/1.25/crds/config.supervisor.pinniped.dev_federationdomains.yaml b/generated/1.25/crds/config.supervisor.pinniped.dev_federationdomains.yaml index 71f7370d11..5e839fccd0 100644 --- a/generated/1.25/crds/config.supervisor.pinniped.dev_federationdomains.yaml +++ b/generated/1.25/crds/config.supervisor.pinniped.dev_federationdomains.yaml @@ -47,6 +47,255 @@ spec: spec: description: Spec of the OIDC provider. properties: + identityProviders: + description: "IdentityProviders is the list of identity providers + available for use by this FederationDomain. \n An identity provider + (e.g. OIDCIdentityProvider or LDAPIdentityProvider) describes how + to connect to a server, how to talk in a specific protocol for authentication, + and how to use the schema of that server/protocol to extract a normalized + user identity. Normalized user identities include a username and + a list of group names. In contrast, IdentityProviders describes + how to use that normalized identity in a group of Kubernetes clusters. + It can perform arbitrary transformations on that normalized identity. + For example, a transformation can add a prefix to all usernames + to help avoid accidental conflicts when multiple identity providers + have different users with the same username (e.g. \"idp1:ryan\" + versus \"idp2:ryan\"). IdentityProviders can also implement arbitrary + authentication rejection policies. Even though a user was able to + authenticate with the identity provider, a policy can disallow the + authentication to the Kubernetes clusters that belong to this FederationDomain. + For example, a policy could disallow the authentication unless the + user belongs to a specific group in the identity provider. \n For + backwards compatibility with versions of Pinniped which predate + support for multiple identity providers, an empty IdentityProviders + list will cause the FederationDomain to use all available identity + providers which exist in the same namespace, but also to reject + all authentication requests when there is more than one identity + provider currently defined. In this backwards compatibility mode, + the name of the identity provider resource (e.g. the Name of an + OIDCIdentityProvider resource) will be used as the name of the identity + provider in this FederationDomain. This mode is provided to make + upgrading from older versions easier. However, instead of relying + on this backwards compatibility mode, please consider this mode + to be deprecated and please instead explicitly list the identity + provider using this IdentityProviders field." + items: + description: FederationDomainIdentityProvider describes how an identity + provider is made available in this FederationDomain. + properties: + displayName: + description: DisplayName is the name of this identity provider + as it will appear to clients. This name ends up in the kubeconfig + of end users, so changing the name of an identity provider + that is in use by end users will be a disruptive change for + those users. + minLength: 1 + type: string + objectRef: + description: ObjectRef is a reference to a Pinniped identity + provider resource. A valid reference is required. If the reference + cannot be resolved then the identity provider will not be + made available. Must refer to a resource of one of the Pinniped + identity provider types, e.g. OIDCIdentityProvider, LDAPIdentityProvider, + ActiveDirectoryIdentityProvider. + properties: + apiGroup: + description: APIGroup is the group for the resource being + referenced. If APIGroup is not specified, the specified + Kind must be in the core API group. For any other third-party + types, APIGroup is required. + type: string + kind: + description: Kind is the type of resource being referenced + type: string + name: + description: Name is the name of resource being referenced + type: string + required: + - kind + - name + type: object + transforms: + description: Transforms is an optional way to specify transformations + to be applied during user authentication and session refresh. + properties: + constants: + description: Constants defines constant variables and their + values which will be made available to the transform expressions. + items: + description: FederationDomainTransformsConstant defines + a constant variable and its value which will be made + available to the transform expressions. This is a union + type, and Type is the discriminator field. + properties: + name: + description: Name determines the name of the constant. + It must be a valid identifier name. + maxLength: 64 + minLength: 1 + pattern: ^[a-zA-Z][_a-zA-Z0-9]*$ + type: string + stringListValue: + description: StringListValue should hold the value + when Type is "stringList", and is otherwise ignored. + items: + type: string + type: array + stringValue: + description: StringValue should hold the value when + Type is "string", and is otherwise ignored. + type: string + type: + description: Type determines the type of the constant, + and indicates which other field should be non-empty. + enum: + - string + - stringList + type: string + required: + - name + - type + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + examples: + description: Examples can optionally be used to ensure that + the sequence of transformation expressions are working + as expected. Examples define sample input identities which + are then run through the expression list, and the results + are compared to the expected results. If any example in + this list fails, then this FederationDomain will not be + available for use, and the error(s) will be added to its + status. This can be used to help guard against programming + mistakes in the expressions, and also act as living documentation + for other administrators to better understand the expressions. + items: + description: FederationDomainTransformsExample defines + a transform example. + properties: + expects: + description: Expects is the expected output of the + entire sequence of transforms when they are run + against the input Username and Groups. + properties: + groups: + description: Groups is the expected list of group + names. + items: + type: string + type: array + message: + description: Message is the expected error message + of the transforms. When Rejected is true, then + Message is the expected message for the policy + which rejected the authentication attempt. When + Rejected is true and Message is blank, then + Message will be treated as the default error + message for authentication attempts which are + rejected by a policy. When Rejected is false, + then Message is the expected error message for + some other non-policy transformation error, + such as a runtime error. When Rejected is false, + there is no default expected Message. + type: string + rejected: + description: Rejected should be set to true when + the expected result of the transforms is that + a policy rejected the authentication attempt. + type: boolean + username: + description: Username is the expected username. + type: string + type: object + groups: + description: Groups is the input list of group names. + items: + type: string + type: array + username: + description: Username is the input username. + minLength: 1 + type: string + required: + - expects + - username + type: object + type: array + expressions: + description: "Expressions are an optional list of transforms + and policies to be executed in the order given during + every authentication attempt, including during every session + refresh. Each is a CEL expression. It may use the basic + CEL language as defined in https://github.com/google/cel-spec/blob/master/doc/langdef.md + plus the CEL string extensions defined in https://github.com/google/cel-go/tree/master/ext#strings. + \n The username and groups extracted from the identity + provider, and the constants defined in this CR, are available + as variables in all expressions. The username is provided + via a variable called `username` and the list of group + names is provided via a variable called `groups` (which + may be an empty list). Each user-provided constants is + provided via a variable named `strConst.varName` for string + constants and `strListConst.varName` for string list constants. + \n The only allowed types for expressions are currently + policy/v1, username/v1, and groups/v1. Each policy/v1 + must return a boolean, and when it returns false, no more + expressions from the list are evaluated and the authentication + attempt is rejected. Transformations of type policy/v1 + do not return usernames or group names, and therefore + cannot change the username or group names. Each username/v1 + transform must return the new username (a string), which + can be the same as the old username. Transformations of + type username/v1 do not return group names, and therefore + cannot change the group names. Each groups/v1 transform + must return the new groups list (list of strings), which + can be the same as the old groups list. Transformations + of type groups/v1 do not return usernames, and therefore + cannot change the usernames. After each expression, the + new (potentially changed) username or groups get passed + to the following expression. \n Any compilation or static + type-checking failure of any expression will cause an + error status on the FederationDomain. During an authentication + attempt, any unexpected runtime evaluation errors (e.g. + division by zero) cause the authentication attempt to + fail. When all expressions evaluate successfully, then + the (potentially changed) username and group names have + been decided for that authentication attempt." + items: + description: FederationDomainTransformsExpression defines + a transform expression. + properties: + expression: + description: Expression is a CEL expression that will + be evaluated based on the Type during an authentication. + minLength: 1 + type: string + message: + description: Message is only used when Type is policy/v1. + It defines an error message to be used when the + policy rejects an authentication attempt. When empty, + a default message will be used. + type: string + type: + description: Type determines the type of the expression. + It must be one of the supported types. + enum: + - policy/v1 + - username/v1 + - groups/v1 + type: string + required: + - expression + - type + type: object + type: array + type: object + required: + - displayName + - objectRef + type: object + type: array issuer: description: "Issuer is the OIDC Provider's issuer, per the OIDC Discovery Metadata document, as well as the identifier that it will use for diff --git a/generated/1.26/README.adoc b/generated/1.26/README.adoc index 88527a218c..89e56d905d 100644 --- a/generated/1.26/README.adoc +++ b/generated/1.26/README.adoc @@ -637,6 +637,25 @@ FederationDomain describes the configuration of an OIDC provider. |=== +[id="{anchor_prefix}-go-pinniped-dev-generated-1-26-apis-supervisor-config-v1alpha1-federationdomainidentityprovider"] +==== FederationDomainIdentityProvider + +FederationDomainIdentityProvider describes how an identity provider is made available in this FederationDomain. + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-26-apis-supervisor-config-v1alpha1-federationdomainspec[$$FederationDomainSpec$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description +| *`displayName`* __string__ | DisplayName is the name of this identity provider as it will appear to clients. This name ends up in the kubeconfig of end users, so changing the name of an identity provider that is in use by end users will be a disruptive change for those users. +| *`objectRef`* __link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.26/#typedlocalobjectreference-v1-core[$$TypedLocalObjectReference$$]__ | ObjectRef is a reference to a Pinniped identity provider resource. A valid reference is required. If the reference cannot be resolved then the identity provider will not be made available. Must refer to a resource of one of the Pinniped identity provider types, e.g. OIDCIdentityProvider, LDAPIdentityProvider, ActiveDirectoryIdentityProvider. +| *`transforms`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-26-apis-supervisor-config-v1alpha1-federationdomaintransforms[$$FederationDomainTransforms$$]__ | Transforms is an optional way to specify transformations to be applied during user authentication and session refresh. +|=== + + [id="{anchor_prefix}-go-pinniped-dev-generated-1-26-apis-supervisor-config-v1alpha1-federationdomainsecrets"] @@ -675,6 +694,9 @@ FederationDomainSpec is a struct that describes an OIDC Provider. | *`issuer`* __string__ | Issuer is the OIDC Provider's issuer, per the OIDC Discovery Metadata document, as well as the identifier that it will use for the iss claim in issued JWTs. This field will also be used as the base URL for any endpoints used by the OIDC Provider (e.g., if your issuer is https://example.com/foo, then your authorization endpoint will look like https://example.com/foo/some/path/to/auth/endpoint). See https://openid.net/specs/openid-connect-discovery-1_0.html#rfc.section.3 for more information. | *`tls`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-26-apis-supervisor-config-v1alpha1-federationdomaintlsspec[$$FederationDomainTLSSpec$$]__ | TLS configures how this FederationDomain is served over Transport Layer Security (TLS). +| *`identityProviders`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-26-apis-supervisor-config-v1alpha1-federationdomainidentityprovider[$$FederationDomainIdentityProvider$$] array__ | IdentityProviders is the list of identity providers available for use by this FederationDomain. + An identity provider (e.g. OIDCIdentityProvider or LDAPIdentityProvider) describes how to connect to a server, how to talk in a specific protocol for authentication, and how to use the schema of that server/protocol to extract a normalized user identity. Normalized user identities include a username and a list of group names. In contrast, IdentityProviders describes how to use that normalized identity in a group of Kubernetes clusters. It can perform arbitrary transformations on that normalized identity. For example, a transformation can add a prefix to all usernames to help avoid accidental conflicts when multiple identity providers have different users with the same username (e.g. "idp1:ryan" versus "idp2:ryan"). IdentityProviders can also implement arbitrary authentication rejection policies. Even though a user was able to authenticate with the identity provider, a policy can disallow the authentication to the Kubernetes clusters that belong to this FederationDomain. For example, a policy could disallow the authentication unless the user belongs to a specific group in the identity provider. + For backwards compatibility with versions of Pinniped which predate support for multiple identity providers, an empty IdentityProviders list will cause the FederationDomain to use all available identity providers which exist in the same namespace, but also to reject all authentication requests when there is more than one identity provider currently defined. In this backwards compatibility mode, the name of the identity provider resource (e.g. the Name of an OIDCIdentityProvider resource) will be used as the name of the identity provider in this FederationDomain. This mode is provided to make upgrading from older versions easier. However, instead of relying on this backwards compatibility mode, please consider this mode to be deprecated and please instead explicitly list the identity provider using this IdentityProviders field. |=== @@ -719,6 +741,106 @@ FederationDomainTLSSpec is a struct that describes the TLS configuration for an |=== +[id="{anchor_prefix}-go-pinniped-dev-generated-1-26-apis-supervisor-config-v1alpha1-federationdomaintransforms"] +==== FederationDomainTransforms + +FederationDomainTransforms defines identity transformations for an identity provider's usage on a FederationDomain. + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-26-apis-supervisor-config-v1alpha1-federationdomainidentityprovider[$$FederationDomainIdentityProvider$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description +| *`constants`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-26-apis-supervisor-config-v1alpha1-federationdomaintransformsconstant[$$FederationDomainTransformsConstant$$] array__ | Constants defines constant variables and their values which will be made available to the transform expressions. +| *`expressions`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-26-apis-supervisor-config-v1alpha1-federationdomaintransformsexpression[$$FederationDomainTransformsExpression$$] array__ | Expressions are an optional list of transforms and policies to be executed in the order given during every authentication attempt, including during every session refresh. Each is a CEL expression. It may use the basic CEL language as defined in https://github.com/google/cel-spec/blob/master/doc/langdef.md plus the CEL string extensions defined in https://github.com/google/cel-go/tree/master/ext#strings. + The username and groups extracted from the identity provider, and the constants defined in this CR, are available as variables in all expressions. The username is provided via a variable called `username` and the list of group names is provided via a variable called `groups` (which may be an empty list). Each user-provided constants is provided via a variable named `strConst.varName` for string constants and `strListConst.varName` for string list constants. + The only allowed types for expressions are currently policy/v1, username/v1, and groups/v1. Each policy/v1 must return a boolean, and when it returns false, no more expressions from the list are evaluated and the authentication attempt is rejected. Transformations of type policy/v1 do not return usernames or group names, and therefore cannot change the username or group names. Each username/v1 transform must return the new username (a string), which can be the same as the old username. Transformations of type username/v1 do not return group names, and therefore cannot change the group names. Each groups/v1 transform must return the new groups list (list of strings), which can be the same as the old groups list. Transformations of type groups/v1 do not return usernames, and therefore cannot change the usernames. After each expression, the new (potentially changed) username or groups get passed to the following expression. + Any compilation or static type-checking failure of any expression will cause an error status on the FederationDomain. During an authentication attempt, any unexpected runtime evaluation errors (e.g. division by zero) cause the authentication attempt to fail. When all expressions evaluate successfully, then the (potentially changed) username and group names have been decided for that authentication attempt. +| *`examples`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-26-apis-supervisor-config-v1alpha1-federationdomaintransformsexample[$$FederationDomainTransformsExample$$] array__ | Examples can optionally be used to ensure that the sequence of transformation expressions are working as expected. Examples define sample input identities which are then run through the expression list, and the results are compared to the expected results. If any example in this list fails, then this FederationDomain will not be available for use, and the error(s) will be added to its status. This can be used to help guard against programming mistakes in the expressions, and also act as living documentation for other administrators to better understand the expressions. +|=== + + +[id="{anchor_prefix}-go-pinniped-dev-generated-1-26-apis-supervisor-config-v1alpha1-federationdomaintransformsconstant"] +==== FederationDomainTransformsConstant + +FederationDomainTransformsConstant defines a constant variable and its value which will be made available to the transform expressions. This is a union type, and Type is the discriminator field. + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-26-apis-supervisor-config-v1alpha1-federationdomaintransforms[$$FederationDomainTransforms$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description +| *`name`* __string__ | Name determines the name of the constant. It must be a valid identifier name. +| *`type`* __string__ | Type determines the type of the constant, and indicates which other field should be non-empty. +| *`stringValue`* __string__ | StringValue should hold the value when Type is "string", and is otherwise ignored. +| *`stringListValue`* __string array__ | StringListValue should hold the value when Type is "stringList", and is otherwise ignored. +|=== + + +[id="{anchor_prefix}-go-pinniped-dev-generated-1-26-apis-supervisor-config-v1alpha1-federationdomaintransformsexample"] +==== FederationDomainTransformsExample + +FederationDomainTransformsExample defines a transform example. + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-26-apis-supervisor-config-v1alpha1-federationdomaintransforms[$$FederationDomainTransforms$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description +| *`username`* __string__ | Username is the input username. +| *`groups`* __string array__ | Groups is the input list of group names. +| *`expects`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-26-apis-supervisor-config-v1alpha1-federationdomaintransformsexampleexpects[$$FederationDomainTransformsExampleExpects$$]__ | Expects is the expected output of the entire sequence of transforms when they are run against the input Username and Groups. +|=== + + +[id="{anchor_prefix}-go-pinniped-dev-generated-1-26-apis-supervisor-config-v1alpha1-federationdomaintransformsexampleexpects"] +==== FederationDomainTransformsExampleExpects + +FederationDomainTransformsExampleExpects defines the expected result for a transforms example. + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-26-apis-supervisor-config-v1alpha1-federationdomaintransformsexample[$$FederationDomainTransformsExample$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description +| *`username`* __string__ | Username is the expected username. +| *`groups`* __string array__ | Groups is the expected list of group names. +| *`rejected`* __boolean__ | Rejected should be set to true when the expected result of the transforms is that a policy rejected the authentication attempt. +| *`message`* __string__ | Message is the expected error message of the transforms. When Rejected is true, then Message is the expected message for the policy which rejected the authentication attempt. When Rejected is true and Message is blank, then Message will be treated as the default error message for authentication attempts which are rejected by a policy. When Rejected is false, then Message is the expected error message for some other non-policy transformation error, such as a runtime error. When Rejected is false, there is no default expected Message. +|=== + + +[id="{anchor_prefix}-go-pinniped-dev-generated-1-26-apis-supervisor-config-v1alpha1-federationdomaintransformsexpression"] +==== FederationDomainTransformsExpression + +FederationDomainTransformsExpression defines a transform expression. + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-26-apis-supervisor-config-v1alpha1-federationdomaintransforms[$$FederationDomainTransforms$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description +| *`type`* __string__ | Type determines the type of the expression. It must be one of the supported types. +| *`expression`* __string__ | Expression is a CEL expression that will be evaluated based on the Type during an authentication. +| *`message`* __string__ | Message is only used when Type is policy/v1. It defines an error message to be used when the policy rejects an authentication attempt. When empty, a default message will be used. +|=== + + [id="{anchor_prefix}-go-pinniped-dev-generated-1-26-apis-supervisor-config-v1alpha1-oidcclient"] ==== OIDCClient diff --git a/generated/1.26/apis/supervisor/config/v1alpha1/types_federationdomain.go b/generated/1.26/apis/supervisor/config/v1alpha1/types_federationdomain.go index 27de4401c0..7f1956845e 100644 --- a/generated/1.26/apis/supervisor/config/v1alpha1/types_federationdomain.go +++ b/generated/1.26/apis/supervisor/config/v1alpha1/types_federationdomain.go @@ -1,4 +1,4 @@ -// Copyright 2020-2022 the Pinniped contributors. All Rights Reserved. +// Copyright 2020-2023 the Pinniped contributors. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 package v1alpha1 @@ -42,6 +42,155 @@ type FederationDomainTLSSpec struct { SecretName string `json:"secretName,omitempty"` } +// FederationDomainTransformsConstant defines a constant variable and its value which will be made available to +// the transform expressions. This is a union type, and Type is the discriminator field. +type FederationDomainTransformsConstant struct { + // Name determines the name of the constant. It must be a valid identifier name. + // +kubebuilder:validation:Pattern=`^[a-zA-Z][_a-zA-Z0-9]*$` + // +kubebuilder:validation:MinLength=1 + // +kubebuilder:validation:MaxLength=64 + Name string `json:"name"` + + // Type determines the type of the constant, and indicates which other field should be non-empty. + // +kubebuilder:validation:Enum=string;stringList + Type string `json:"type"` + + // StringValue should hold the value when Type is "string", and is otherwise ignored. + // +optional + StringValue string `json:"stringValue,omitempty"` + + // StringListValue should hold the value when Type is "stringList", and is otherwise ignored. + // +optional + StringListValue []string `json:"stringListValue,omitempty"` +} + +// FederationDomainTransformsExpression defines a transform expression. +type FederationDomainTransformsExpression struct { + // Type determines the type of the expression. It must be one of the supported types. + // +kubebuilder:validation:Enum=policy/v1;username/v1;groups/v1 + Type string `json:"type"` + + // Expression is a CEL expression that will be evaluated based on the Type during an authentication. + // +kubebuilder:validation:MinLength=1 + Expression string `json:"expression"` + + // Message is only used when Type is policy/v1. It defines an error message to be used when the policy rejects + // an authentication attempt. When empty, a default message will be used. + // +optional + Message string `json:"message,omitempty"` +} + +// FederationDomainTransformsExample defines a transform example. +type FederationDomainTransformsExample struct { + // Username is the input username. + // +kubebuilder:validation:MinLength=1 + Username string `json:"username"` + + // Groups is the input list of group names. + // +optional + Groups []string `json:"groups,omitempty"` + + // Expects is the expected output of the entire sequence of transforms when they are run against the + // input Username and Groups. + Expects FederationDomainTransformsExampleExpects `json:"expects"` +} + +// FederationDomainTransformsExampleExpects defines the expected result for a transforms example. +type FederationDomainTransformsExampleExpects struct { + // Username is the expected username. + // +optional + Username string `json:"username,omitempty"` + + // Groups is the expected list of group names. + // +optional + Groups []string `json:"groups,omitempty"` + + // Rejected should be set to true when the expected result of the transforms is that a policy rejected the + // authentication attempt. + // +optional + Rejected bool `json:"rejected,omitempty"` + + // Message is the expected error message of the transforms. When Rejected is true, then Message is the expected + // message for the policy which rejected the authentication attempt. When Rejected is true and Message is blank, + // then Message will be treated as the default error message for authentication attempts which are rejected by a + // policy. When Rejected is false, then Message is the expected error message for some other non-policy + // transformation error, such as a runtime error. When Rejected is false, there is no default expected Message. + // +optional + Message string `json:"message,omitempty"` +} + +// FederationDomainTransforms defines identity transformations for an identity provider's usage on a FederationDomain. +type FederationDomainTransforms struct { + // Constants defines constant variables and their values which will be made available to the transform expressions. + // +patchMergeKey=name + // +patchStrategy=merge + // +listType=map + // +listMapKey=name + // +optional + Constants []FederationDomainTransformsConstant `json:"constants,omitempty"` + + // Expressions are an optional list of transforms and policies to be executed in the order given during every + // authentication attempt, including during every session refresh. + // Each is a CEL expression. It may use the basic CEL language as defined in + // https://github.com/google/cel-spec/blob/master/doc/langdef.md plus the CEL string extensions defined in + // https://github.com/google/cel-go/tree/master/ext#strings. + // + // The username and groups extracted from the identity provider, and the constants defined in this CR, are + // available as variables in all expressions. The username is provided via a variable called `username` and + // the list of group names is provided via a variable called `groups` (which may be an empty list). + // Each user-provided constants is provided via a variable named `strConst.varName` for string constants + // and `strListConst.varName` for string list constants. + // + // The only allowed types for expressions are currently policy/v1, username/v1, and groups/v1. + // Each policy/v1 must return a boolean, and when it returns false, no more expressions from the list are evaluated + // and the authentication attempt is rejected. + // Transformations of type policy/v1 do not return usernames or group names, and therefore cannot change the + // username or group names. + // Each username/v1 transform must return the new username (a string), which can be the same as the old username. + // Transformations of type username/v1 do not return group names, and therefore cannot change the group names. + // Each groups/v1 transform must return the new groups list (list of strings), which can be the same as the old + // groups list. + // Transformations of type groups/v1 do not return usernames, and therefore cannot change the usernames. + // After each expression, the new (potentially changed) username or groups get passed to the following expression. + // + // Any compilation or static type-checking failure of any expression will cause an error status on the FederationDomain. + // During an authentication attempt, any unexpected runtime evaluation errors (e.g. division by zero) cause the + // authentication attempt to fail. When all expressions evaluate successfully, then the (potentially changed) username + // and group names have been decided for that authentication attempt. + // + // +optional + Expressions []FederationDomainTransformsExpression `json:"expressions,omitempty"` + + // Examples can optionally be used to ensure that the sequence of transformation expressions are working as + // expected. Examples define sample input identities which are then run through the expression list, and the + // results are compared to the expected results. If any example in this list fails, then this + // FederationDomain will not be available for use, and the error(s) will be added to its status. + // This can be used to help guard against programming mistakes in the expressions, and also + // act as living documentation for other administrators to better understand the expressions. + // +optional + Examples []FederationDomainTransformsExample `json:"examples,omitempty"` +} + +// FederationDomainIdentityProvider describes how an identity provider is made available in this FederationDomain. +type FederationDomainIdentityProvider struct { + // DisplayName is the name of this identity provider as it will appear to clients. This name ends up in the + // kubeconfig of end users, so changing the name of an identity provider that is in use by end users will be a + // disruptive change for those users. + // +kubebuilder:validation:MinLength=1 + DisplayName string `json:"displayName"` + + // ObjectRef is a reference to a Pinniped identity provider resource. A valid reference is required. + // If the reference cannot be resolved then the identity provider will not be made available. + // Must refer to a resource of one of the Pinniped identity provider types, e.g. OIDCIdentityProvider, + // LDAPIdentityProvider, ActiveDirectoryIdentityProvider. + ObjectRef corev1.TypedLocalObjectReference `json:"objectRef"` + + // Transforms is an optional way to specify transformations to be applied during user authentication and + // session refresh. + // +optional + Transforms FederationDomainTransforms `json:"transforms,omitempty"` +} + // FederationDomainSpec is a struct that describes an OIDC Provider. type FederationDomainSpec struct { // Issuer is the OIDC Provider's issuer, per the OIDC Discovery Metadata document, as well as the @@ -58,6 +207,32 @@ type FederationDomainSpec struct { // TLS configures how this FederationDomain is served over Transport Layer Security (TLS). // +optional TLS *FederationDomainTLSSpec `json:"tls,omitempty"` + + // IdentityProviders is the list of identity providers available for use by this FederationDomain. + // + // An identity provider (e.g. OIDCIdentityProvider or LDAPIdentityProvider) describes how to connect to a server, + // how to talk in a specific protocol for authentication, and how to use the schema of that server/protocol to + // extract a normalized user identity. Normalized user identities include a username and a list of group names. + // In contrast, IdentityProviders describes how to use that normalized identity in a group of + // Kubernetes clusters. It can perform arbitrary transformations on that normalized identity. For example, a + // transformation can add a prefix to all usernames to help avoid accidental conflicts when multiple identity + // providers have different users with the same username (e.g. "idp1:ryan" versus "idp2:ryan"). + // IdentityProviders can also implement arbitrary authentication rejection policies. + // Even though a user was able to authenticate with the identity provider, a policy can disallow the authentication + // to the Kubernetes clusters that belong to this FederationDomain. For example, a policy could disallow the + // authentication unless the user belongs to a specific group in the identity provider. + // + // For backwards compatibility with versions of Pinniped which predate support for multiple identity providers, + // an empty IdentityProviders list will cause the FederationDomain to use all available identity providers which + // exist in the same namespace, but also to reject all authentication requests when there is more than one identity + // provider currently defined. In this backwards compatibility mode, the name of the identity provider resource + // (e.g. the Name of an OIDCIdentityProvider resource) will be used as the name of the identity provider in this + // FederationDomain. This mode is provided to make upgrading from older versions easier. However, instead of + // relying on this backwards compatibility mode, please consider this mode to be deprecated and please instead + // explicitly list the identity provider using this IdentityProviders field. + // + // +optional + IdentityProviders []FederationDomainIdentityProvider `json:"identityProviders,omitempty"` } // FederationDomainSecrets holds information about this OIDC Provider's secrets. diff --git a/generated/1.26/apis/supervisor/config/v1alpha1/zz_generated.deepcopy.go b/generated/1.26/apis/supervisor/config/v1alpha1/zz_generated.deepcopy.go index 682013782a..5e48e41556 100644 --- a/generated/1.26/apis/supervisor/config/v1alpha1/zz_generated.deepcopy.go +++ b/generated/1.26/apis/supervisor/config/v1alpha1/zz_generated.deepcopy.go @@ -57,6 +57,24 @@ func (in *FederationDomain) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FederationDomainIdentityProvider) DeepCopyInto(out *FederationDomainIdentityProvider) { + *out = *in + in.ObjectRef.DeepCopyInto(&out.ObjectRef) + in.Transforms.DeepCopyInto(&out.Transforms) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FederationDomainIdentityProvider. +func (in *FederationDomainIdentityProvider) DeepCopy() *FederationDomainIdentityProvider { + if in == nil { + return nil + } + out := new(FederationDomainIdentityProvider) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *FederationDomainList) DeepCopyInto(out *FederationDomainList) { *out = *in @@ -118,6 +136,13 @@ func (in *FederationDomainSpec) DeepCopyInto(out *FederationDomainSpec) { *out = new(FederationDomainTLSSpec) **out = **in } + if in.IdentityProviders != nil { + in, out := &in.IdentityProviders, &out.IdentityProviders + *out = make([]FederationDomainIdentityProvider, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } return } @@ -168,6 +193,121 @@ func (in *FederationDomainTLSSpec) DeepCopy() *FederationDomainTLSSpec { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FederationDomainTransforms) DeepCopyInto(out *FederationDomainTransforms) { + *out = *in + if in.Constants != nil { + in, out := &in.Constants, &out.Constants + *out = make([]FederationDomainTransformsConstant, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.Expressions != nil { + in, out := &in.Expressions, &out.Expressions + *out = make([]FederationDomainTransformsExpression, len(*in)) + copy(*out, *in) + } + if in.Examples != nil { + in, out := &in.Examples, &out.Examples + *out = make([]FederationDomainTransformsExample, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FederationDomainTransforms. +func (in *FederationDomainTransforms) DeepCopy() *FederationDomainTransforms { + if in == nil { + return nil + } + out := new(FederationDomainTransforms) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FederationDomainTransformsConstant) DeepCopyInto(out *FederationDomainTransformsConstant) { + *out = *in + if in.StringListValue != nil { + in, out := &in.StringListValue, &out.StringListValue + *out = make([]string, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FederationDomainTransformsConstant. +func (in *FederationDomainTransformsConstant) DeepCopy() *FederationDomainTransformsConstant { + if in == nil { + return nil + } + out := new(FederationDomainTransformsConstant) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FederationDomainTransformsExample) DeepCopyInto(out *FederationDomainTransformsExample) { + *out = *in + if in.Groups != nil { + in, out := &in.Groups, &out.Groups + *out = make([]string, len(*in)) + copy(*out, *in) + } + in.Expects.DeepCopyInto(&out.Expects) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FederationDomainTransformsExample. +func (in *FederationDomainTransformsExample) DeepCopy() *FederationDomainTransformsExample { + if in == nil { + return nil + } + out := new(FederationDomainTransformsExample) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FederationDomainTransformsExampleExpects) DeepCopyInto(out *FederationDomainTransformsExampleExpects) { + *out = *in + if in.Groups != nil { + in, out := &in.Groups, &out.Groups + *out = make([]string, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FederationDomainTransformsExampleExpects. +func (in *FederationDomainTransformsExampleExpects) DeepCopy() *FederationDomainTransformsExampleExpects { + if in == nil { + return nil + } + out := new(FederationDomainTransformsExampleExpects) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FederationDomainTransformsExpression) DeepCopyInto(out *FederationDomainTransformsExpression) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FederationDomainTransformsExpression. +func (in *FederationDomainTransformsExpression) DeepCopy() *FederationDomainTransformsExpression { + if in == nil { + return nil + } + out := new(FederationDomainTransformsExpression) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *OIDCClient) DeepCopyInto(out *OIDCClient) { *out = *in diff --git a/generated/1.26/crds/config.supervisor.pinniped.dev_federationdomains.yaml b/generated/1.26/crds/config.supervisor.pinniped.dev_federationdomains.yaml index 71f7370d11..5e839fccd0 100644 --- a/generated/1.26/crds/config.supervisor.pinniped.dev_federationdomains.yaml +++ b/generated/1.26/crds/config.supervisor.pinniped.dev_federationdomains.yaml @@ -47,6 +47,255 @@ spec: spec: description: Spec of the OIDC provider. properties: + identityProviders: + description: "IdentityProviders is the list of identity providers + available for use by this FederationDomain. \n An identity provider + (e.g. OIDCIdentityProvider or LDAPIdentityProvider) describes how + to connect to a server, how to talk in a specific protocol for authentication, + and how to use the schema of that server/protocol to extract a normalized + user identity. Normalized user identities include a username and + a list of group names. In contrast, IdentityProviders describes + how to use that normalized identity in a group of Kubernetes clusters. + It can perform arbitrary transformations on that normalized identity. + For example, a transformation can add a prefix to all usernames + to help avoid accidental conflicts when multiple identity providers + have different users with the same username (e.g. \"idp1:ryan\" + versus \"idp2:ryan\"). IdentityProviders can also implement arbitrary + authentication rejection policies. Even though a user was able to + authenticate with the identity provider, a policy can disallow the + authentication to the Kubernetes clusters that belong to this FederationDomain. + For example, a policy could disallow the authentication unless the + user belongs to a specific group in the identity provider. \n For + backwards compatibility with versions of Pinniped which predate + support for multiple identity providers, an empty IdentityProviders + list will cause the FederationDomain to use all available identity + providers which exist in the same namespace, but also to reject + all authentication requests when there is more than one identity + provider currently defined. In this backwards compatibility mode, + the name of the identity provider resource (e.g. the Name of an + OIDCIdentityProvider resource) will be used as the name of the identity + provider in this FederationDomain. This mode is provided to make + upgrading from older versions easier. However, instead of relying + on this backwards compatibility mode, please consider this mode + to be deprecated and please instead explicitly list the identity + provider using this IdentityProviders field." + items: + description: FederationDomainIdentityProvider describes how an identity + provider is made available in this FederationDomain. + properties: + displayName: + description: DisplayName is the name of this identity provider + as it will appear to clients. This name ends up in the kubeconfig + of end users, so changing the name of an identity provider + that is in use by end users will be a disruptive change for + those users. + minLength: 1 + type: string + objectRef: + description: ObjectRef is a reference to a Pinniped identity + provider resource. A valid reference is required. If the reference + cannot be resolved then the identity provider will not be + made available. Must refer to a resource of one of the Pinniped + identity provider types, e.g. OIDCIdentityProvider, LDAPIdentityProvider, + ActiveDirectoryIdentityProvider. + properties: + apiGroup: + description: APIGroup is the group for the resource being + referenced. If APIGroup is not specified, the specified + Kind must be in the core API group. For any other third-party + types, APIGroup is required. + type: string + kind: + description: Kind is the type of resource being referenced + type: string + name: + description: Name is the name of resource being referenced + type: string + required: + - kind + - name + type: object + transforms: + description: Transforms is an optional way to specify transformations + to be applied during user authentication and session refresh. + properties: + constants: + description: Constants defines constant variables and their + values which will be made available to the transform expressions. + items: + description: FederationDomainTransformsConstant defines + a constant variable and its value which will be made + available to the transform expressions. This is a union + type, and Type is the discriminator field. + properties: + name: + description: Name determines the name of the constant. + It must be a valid identifier name. + maxLength: 64 + minLength: 1 + pattern: ^[a-zA-Z][_a-zA-Z0-9]*$ + type: string + stringListValue: + description: StringListValue should hold the value + when Type is "stringList", and is otherwise ignored. + items: + type: string + type: array + stringValue: + description: StringValue should hold the value when + Type is "string", and is otherwise ignored. + type: string + type: + description: Type determines the type of the constant, + and indicates which other field should be non-empty. + enum: + - string + - stringList + type: string + required: + - name + - type + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + examples: + description: Examples can optionally be used to ensure that + the sequence of transformation expressions are working + as expected. Examples define sample input identities which + are then run through the expression list, and the results + are compared to the expected results. If any example in + this list fails, then this FederationDomain will not be + available for use, and the error(s) will be added to its + status. This can be used to help guard against programming + mistakes in the expressions, and also act as living documentation + for other administrators to better understand the expressions. + items: + description: FederationDomainTransformsExample defines + a transform example. + properties: + expects: + description: Expects is the expected output of the + entire sequence of transforms when they are run + against the input Username and Groups. + properties: + groups: + description: Groups is the expected list of group + names. + items: + type: string + type: array + message: + description: Message is the expected error message + of the transforms. When Rejected is true, then + Message is the expected message for the policy + which rejected the authentication attempt. When + Rejected is true and Message is blank, then + Message will be treated as the default error + message for authentication attempts which are + rejected by a policy. When Rejected is false, + then Message is the expected error message for + some other non-policy transformation error, + such as a runtime error. When Rejected is false, + there is no default expected Message. + type: string + rejected: + description: Rejected should be set to true when + the expected result of the transforms is that + a policy rejected the authentication attempt. + type: boolean + username: + description: Username is the expected username. + type: string + type: object + groups: + description: Groups is the input list of group names. + items: + type: string + type: array + username: + description: Username is the input username. + minLength: 1 + type: string + required: + - expects + - username + type: object + type: array + expressions: + description: "Expressions are an optional list of transforms + and policies to be executed in the order given during + every authentication attempt, including during every session + refresh. Each is a CEL expression. It may use the basic + CEL language as defined in https://github.com/google/cel-spec/blob/master/doc/langdef.md + plus the CEL string extensions defined in https://github.com/google/cel-go/tree/master/ext#strings. + \n The username and groups extracted from the identity + provider, and the constants defined in this CR, are available + as variables in all expressions. The username is provided + via a variable called `username` and the list of group + names is provided via a variable called `groups` (which + may be an empty list). Each user-provided constants is + provided via a variable named `strConst.varName` for string + constants and `strListConst.varName` for string list constants. + \n The only allowed types for expressions are currently + policy/v1, username/v1, and groups/v1. Each policy/v1 + must return a boolean, and when it returns false, no more + expressions from the list are evaluated and the authentication + attempt is rejected. Transformations of type policy/v1 + do not return usernames or group names, and therefore + cannot change the username or group names. Each username/v1 + transform must return the new username (a string), which + can be the same as the old username. Transformations of + type username/v1 do not return group names, and therefore + cannot change the group names. Each groups/v1 transform + must return the new groups list (list of strings), which + can be the same as the old groups list. Transformations + of type groups/v1 do not return usernames, and therefore + cannot change the usernames. After each expression, the + new (potentially changed) username or groups get passed + to the following expression. \n Any compilation or static + type-checking failure of any expression will cause an + error status on the FederationDomain. During an authentication + attempt, any unexpected runtime evaluation errors (e.g. + division by zero) cause the authentication attempt to + fail. When all expressions evaluate successfully, then + the (potentially changed) username and group names have + been decided for that authentication attempt." + items: + description: FederationDomainTransformsExpression defines + a transform expression. + properties: + expression: + description: Expression is a CEL expression that will + be evaluated based on the Type during an authentication. + minLength: 1 + type: string + message: + description: Message is only used when Type is policy/v1. + It defines an error message to be used when the + policy rejects an authentication attempt. When empty, + a default message will be used. + type: string + type: + description: Type determines the type of the expression. + It must be one of the supported types. + enum: + - policy/v1 + - username/v1 + - groups/v1 + type: string + required: + - expression + - type + type: object + type: array + type: object + required: + - displayName + - objectRef + type: object + type: array issuer: description: "Issuer is the OIDC Provider's issuer, per the OIDC Discovery Metadata document, as well as the identifier that it will use for diff --git a/generated/latest/apis/supervisor/config/v1alpha1/types_federationdomain.go b/generated/latest/apis/supervisor/config/v1alpha1/types_federationdomain.go index 27de4401c0..7f1956845e 100644 --- a/generated/latest/apis/supervisor/config/v1alpha1/types_federationdomain.go +++ b/generated/latest/apis/supervisor/config/v1alpha1/types_federationdomain.go @@ -1,4 +1,4 @@ -// Copyright 2020-2022 the Pinniped contributors. All Rights Reserved. +// Copyright 2020-2023 the Pinniped contributors. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 package v1alpha1 @@ -42,6 +42,155 @@ type FederationDomainTLSSpec struct { SecretName string `json:"secretName,omitempty"` } +// FederationDomainTransformsConstant defines a constant variable and its value which will be made available to +// the transform expressions. This is a union type, and Type is the discriminator field. +type FederationDomainTransformsConstant struct { + // Name determines the name of the constant. It must be a valid identifier name. + // +kubebuilder:validation:Pattern=`^[a-zA-Z][_a-zA-Z0-9]*$` + // +kubebuilder:validation:MinLength=1 + // +kubebuilder:validation:MaxLength=64 + Name string `json:"name"` + + // Type determines the type of the constant, and indicates which other field should be non-empty. + // +kubebuilder:validation:Enum=string;stringList + Type string `json:"type"` + + // StringValue should hold the value when Type is "string", and is otherwise ignored. + // +optional + StringValue string `json:"stringValue,omitempty"` + + // StringListValue should hold the value when Type is "stringList", and is otherwise ignored. + // +optional + StringListValue []string `json:"stringListValue,omitempty"` +} + +// FederationDomainTransformsExpression defines a transform expression. +type FederationDomainTransformsExpression struct { + // Type determines the type of the expression. It must be one of the supported types. + // +kubebuilder:validation:Enum=policy/v1;username/v1;groups/v1 + Type string `json:"type"` + + // Expression is a CEL expression that will be evaluated based on the Type during an authentication. + // +kubebuilder:validation:MinLength=1 + Expression string `json:"expression"` + + // Message is only used when Type is policy/v1. It defines an error message to be used when the policy rejects + // an authentication attempt. When empty, a default message will be used. + // +optional + Message string `json:"message,omitempty"` +} + +// FederationDomainTransformsExample defines a transform example. +type FederationDomainTransformsExample struct { + // Username is the input username. + // +kubebuilder:validation:MinLength=1 + Username string `json:"username"` + + // Groups is the input list of group names. + // +optional + Groups []string `json:"groups,omitempty"` + + // Expects is the expected output of the entire sequence of transforms when they are run against the + // input Username and Groups. + Expects FederationDomainTransformsExampleExpects `json:"expects"` +} + +// FederationDomainTransformsExampleExpects defines the expected result for a transforms example. +type FederationDomainTransformsExampleExpects struct { + // Username is the expected username. + // +optional + Username string `json:"username,omitempty"` + + // Groups is the expected list of group names. + // +optional + Groups []string `json:"groups,omitempty"` + + // Rejected should be set to true when the expected result of the transforms is that a policy rejected the + // authentication attempt. + // +optional + Rejected bool `json:"rejected,omitempty"` + + // Message is the expected error message of the transforms. When Rejected is true, then Message is the expected + // message for the policy which rejected the authentication attempt. When Rejected is true and Message is blank, + // then Message will be treated as the default error message for authentication attempts which are rejected by a + // policy. When Rejected is false, then Message is the expected error message for some other non-policy + // transformation error, such as a runtime error. When Rejected is false, there is no default expected Message. + // +optional + Message string `json:"message,omitempty"` +} + +// FederationDomainTransforms defines identity transformations for an identity provider's usage on a FederationDomain. +type FederationDomainTransforms struct { + // Constants defines constant variables and their values which will be made available to the transform expressions. + // +patchMergeKey=name + // +patchStrategy=merge + // +listType=map + // +listMapKey=name + // +optional + Constants []FederationDomainTransformsConstant `json:"constants,omitempty"` + + // Expressions are an optional list of transforms and policies to be executed in the order given during every + // authentication attempt, including during every session refresh. + // Each is a CEL expression. It may use the basic CEL language as defined in + // https://github.com/google/cel-spec/blob/master/doc/langdef.md plus the CEL string extensions defined in + // https://github.com/google/cel-go/tree/master/ext#strings. + // + // The username and groups extracted from the identity provider, and the constants defined in this CR, are + // available as variables in all expressions. The username is provided via a variable called `username` and + // the list of group names is provided via a variable called `groups` (which may be an empty list). + // Each user-provided constants is provided via a variable named `strConst.varName` for string constants + // and `strListConst.varName` for string list constants. + // + // The only allowed types for expressions are currently policy/v1, username/v1, and groups/v1. + // Each policy/v1 must return a boolean, and when it returns false, no more expressions from the list are evaluated + // and the authentication attempt is rejected. + // Transformations of type policy/v1 do not return usernames or group names, and therefore cannot change the + // username or group names. + // Each username/v1 transform must return the new username (a string), which can be the same as the old username. + // Transformations of type username/v1 do not return group names, and therefore cannot change the group names. + // Each groups/v1 transform must return the new groups list (list of strings), which can be the same as the old + // groups list. + // Transformations of type groups/v1 do not return usernames, and therefore cannot change the usernames. + // After each expression, the new (potentially changed) username or groups get passed to the following expression. + // + // Any compilation or static type-checking failure of any expression will cause an error status on the FederationDomain. + // During an authentication attempt, any unexpected runtime evaluation errors (e.g. division by zero) cause the + // authentication attempt to fail. When all expressions evaluate successfully, then the (potentially changed) username + // and group names have been decided for that authentication attempt. + // + // +optional + Expressions []FederationDomainTransformsExpression `json:"expressions,omitempty"` + + // Examples can optionally be used to ensure that the sequence of transformation expressions are working as + // expected. Examples define sample input identities which are then run through the expression list, and the + // results are compared to the expected results. If any example in this list fails, then this + // FederationDomain will not be available for use, and the error(s) will be added to its status. + // This can be used to help guard against programming mistakes in the expressions, and also + // act as living documentation for other administrators to better understand the expressions. + // +optional + Examples []FederationDomainTransformsExample `json:"examples,omitempty"` +} + +// FederationDomainIdentityProvider describes how an identity provider is made available in this FederationDomain. +type FederationDomainIdentityProvider struct { + // DisplayName is the name of this identity provider as it will appear to clients. This name ends up in the + // kubeconfig of end users, so changing the name of an identity provider that is in use by end users will be a + // disruptive change for those users. + // +kubebuilder:validation:MinLength=1 + DisplayName string `json:"displayName"` + + // ObjectRef is a reference to a Pinniped identity provider resource. A valid reference is required. + // If the reference cannot be resolved then the identity provider will not be made available. + // Must refer to a resource of one of the Pinniped identity provider types, e.g. OIDCIdentityProvider, + // LDAPIdentityProvider, ActiveDirectoryIdentityProvider. + ObjectRef corev1.TypedLocalObjectReference `json:"objectRef"` + + // Transforms is an optional way to specify transformations to be applied during user authentication and + // session refresh. + // +optional + Transforms FederationDomainTransforms `json:"transforms,omitempty"` +} + // FederationDomainSpec is a struct that describes an OIDC Provider. type FederationDomainSpec struct { // Issuer is the OIDC Provider's issuer, per the OIDC Discovery Metadata document, as well as the @@ -58,6 +207,32 @@ type FederationDomainSpec struct { // TLS configures how this FederationDomain is served over Transport Layer Security (TLS). // +optional TLS *FederationDomainTLSSpec `json:"tls,omitempty"` + + // IdentityProviders is the list of identity providers available for use by this FederationDomain. + // + // An identity provider (e.g. OIDCIdentityProvider or LDAPIdentityProvider) describes how to connect to a server, + // how to talk in a specific protocol for authentication, and how to use the schema of that server/protocol to + // extract a normalized user identity. Normalized user identities include a username and a list of group names. + // In contrast, IdentityProviders describes how to use that normalized identity in a group of + // Kubernetes clusters. It can perform arbitrary transformations on that normalized identity. For example, a + // transformation can add a prefix to all usernames to help avoid accidental conflicts when multiple identity + // providers have different users with the same username (e.g. "idp1:ryan" versus "idp2:ryan"). + // IdentityProviders can also implement arbitrary authentication rejection policies. + // Even though a user was able to authenticate with the identity provider, a policy can disallow the authentication + // to the Kubernetes clusters that belong to this FederationDomain. For example, a policy could disallow the + // authentication unless the user belongs to a specific group in the identity provider. + // + // For backwards compatibility with versions of Pinniped which predate support for multiple identity providers, + // an empty IdentityProviders list will cause the FederationDomain to use all available identity providers which + // exist in the same namespace, but also to reject all authentication requests when there is more than one identity + // provider currently defined. In this backwards compatibility mode, the name of the identity provider resource + // (e.g. the Name of an OIDCIdentityProvider resource) will be used as the name of the identity provider in this + // FederationDomain. This mode is provided to make upgrading from older versions easier. However, instead of + // relying on this backwards compatibility mode, please consider this mode to be deprecated and please instead + // explicitly list the identity provider using this IdentityProviders field. + // + // +optional + IdentityProviders []FederationDomainIdentityProvider `json:"identityProviders,omitempty"` } // FederationDomainSecrets holds information about this OIDC Provider's secrets. diff --git a/generated/latest/apis/supervisor/config/v1alpha1/zz_generated.deepcopy.go b/generated/latest/apis/supervisor/config/v1alpha1/zz_generated.deepcopy.go index 682013782a..5e48e41556 100644 --- a/generated/latest/apis/supervisor/config/v1alpha1/zz_generated.deepcopy.go +++ b/generated/latest/apis/supervisor/config/v1alpha1/zz_generated.deepcopy.go @@ -57,6 +57,24 @@ func (in *FederationDomain) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FederationDomainIdentityProvider) DeepCopyInto(out *FederationDomainIdentityProvider) { + *out = *in + in.ObjectRef.DeepCopyInto(&out.ObjectRef) + in.Transforms.DeepCopyInto(&out.Transforms) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FederationDomainIdentityProvider. +func (in *FederationDomainIdentityProvider) DeepCopy() *FederationDomainIdentityProvider { + if in == nil { + return nil + } + out := new(FederationDomainIdentityProvider) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *FederationDomainList) DeepCopyInto(out *FederationDomainList) { *out = *in @@ -118,6 +136,13 @@ func (in *FederationDomainSpec) DeepCopyInto(out *FederationDomainSpec) { *out = new(FederationDomainTLSSpec) **out = **in } + if in.IdentityProviders != nil { + in, out := &in.IdentityProviders, &out.IdentityProviders + *out = make([]FederationDomainIdentityProvider, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } return } @@ -168,6 +193,121 @@ func (in *FederationDomainTLSSpec) DeepCopy() *FederationDomainTLSSpec { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FederationDomainTransforms) DeepCopyInto(out *FederationDomainTransforms) { + *out = *in + if in.Constants != nil { + in, out := &in.Constants, &out.Constants + *out = make([]FederationDomainTransformsConstant, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.Expressions != nil { + in, out := &in.Expressions, &out.Expressions + *out = make([]FederationDomainTransformsExpression, len(*in)) + copy(*out, *in) + } + if in.Examples != nil { + in, out := &in.Examples, &out.Examples + *out = make([]FederationDomainTransformsExample, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FederationDomainTransforms. +func (in *FederationDomainTransforms) DeepCopy() *FederationDomainTransforms { + if in == nil { + return nil + } + out := new(FederationDomainTransforms) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FederationDomainTransformsConstant) DeepCopyInto(out *FederationDomainTransformsConstant) { + *out = *in + if in.StringListValue != nil { + in, out := &in.StringListValue, &out.StringListValue + *out = make([]string, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FederationDomainTransformsConstant. +func (in *FederationDomainTransformsConstant) DeepCopy() *FederationDomainTransformsConstant { + if in == nil { + return nil + } + out := new(FederationDomainTransformsConstant) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FederationDomainTransformsExample) DeepCopyInto(out *FederationDomainTransformsExample) { + *out = *in + if in.Groups != nil { + in, out := &in.Groups, &out.Groups + *out = make([]string, len(*in)) + copy(*out, *in) + } + in.Expects.DeepCopyInto(&out.Expects) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FederationDomainTransformsExample. +func (in *FederationDomainTransformsExample) DeepCopy() *FederationDomainTransformsExample { + if in == nil { + return nil + } + out := new(FederationDomainTransformsExample) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FederationDomainTransformsExampleExpects) DeepCopyInto(out *FederationDomainTransformsExampleExpects) { + *out = *in + if in.Groups != nil { + in, out := &in.Groups, &out.Groups + *out = make([]string, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FederationDomainTransformsExampleExpects. +func (in *FederationDomainTransformsExampleExpects) DeepCopy() *FederationDomainTransformsExampleExpects { + if in == nil { + return nil + } + out := new(FederationDomainTransformsExampleExpects) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FederationDomainTransformsExpression) DeepCopyInto(out *FederationDomainTransformsExpression) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FederationDomainTransformsExpression. +func (in *FederationDomainTransformsExpression) DeepCopy() *FederationDomainTransformsExpression { + if in == nil { + return nil + } + out := new(FederationDomainTransformsExpression) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *OIDCClient) DeepCopyInto(out *OIDCClient) { *out = *in