Skip to content

Commit

Permalink
Add APIs for multiple IDP and id transformations to FederationDomain CRD
Browse files Browse the repository at this point in the history
  • Loading branch information
cfryanr committed Feb 13, 2023
1 parent 60d12d8 commit a9d1475
Show file tree
Hide file tree
Showing 44 changed files with 7,611 additions and 12 deletions.
177 changes: 176 additions & 1 deletion apis/supervisor/config/v1alpha1/types_federationdomain.go.tmpl
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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.
Expand Down
Loading

0 comments on commit a9d1475

Please sign in to comment.