Merge pull request #555 from vmware-tanzu/initial_ldap

Initial `LDAPIdentityProvider` support for the Supervisor and CLI
This commit is contained in:
Ryan Richard 2021-05-17 10:40:50 -07:00 committed by GitHub
commit 14b8fcc472
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
142 changed files with 15168 additions and 2199 deletions

View File

@ -32,6 +32,8 @@ func addKnownTypes(scheme *runtime.Scheme) error {
scheme.AddKnownTypes(SchemeGroupVersion,
&OIDCIdentityProvider{},
&OIDCIdentityProviderList{},
&LDAPIdentityProvider{},
&LDAPIdentityProviderList{},
)
metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
return nil

View File

@ -0,0 +1,132 @@
// Copyright 2021 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package v1alpha1
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
type LDAPIdentityProviderPhase string
const (
// LDAPPhasePending is the default phase for newly-created LDAPIdentityProvider resources.
LDAPPhasePending LDAPIdentityProviderPhase = "Pending"
// LDAPPhaseReady is the phase for an LDAPIdentityProvider resource in a healthy state.
LDAPPhaseReady LDAPIdentityProviderPhase = "Ready"
// LDAPPhaseError is the phase for an LDAPIdentityProvider in an unhealthy state.
LDAPPhaseError LDAPIdentityProviderPhase = "Error"
)
// Status of an LDAP identity provider.
type LDAPIdentityProviderStatus struct {
// Phase summarizes the overall status of the LDAPIdentityProvider.
// +kubebuilder:default=Pending
// +kubebuilder:validation:Enum=Pending;Ready;Error
Phase LDAPIdentityProviderPhase `json:"phase,omitempty"`
// Represents the observations of an identity provider's current state.
// +patchMergeKey=type
// +patchStrategy=merge
// +listType=map
// +listMapKey=type
Conditions []Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"`
}
type LDAPIdentityProviderBind struct {
// SecretName contains the name of a namespace-local Secret object that provides the username and
// password for an LDAP bind user. This account will be used to perform LDAP searches. The Secret should be
// of type "kubernetes.io/basic-auth" which includes "username" and "password" keys. The username value
// should be the full DN of your bind account, e.g. "cn=bind-account,ou=users,dc=example,dc=com".
// The password must be non-empty.
// +kubebuilder:validation:MinLength=1
SecretName string `json:"secretName"`
}
type LDAPIdentityProviderUserSearchAttributes struct {
// Username specifies the name of attribute in the LDAP entry which whose value shall become the username
// of the user after a successful authentication. This would typically be the same attribute name used in
// the user search filter, although it can be different. E.g. "mail" or "uid" or "userPrincipalName".
// The value of this field is case-sensitive and must match the case of the attribute name returned by the LDAP
// server in the user's entry. Distinguished names can be used by specifying lower-case "dn". When this field
// is set to "dn" then the LDAPIdentityProviderUserSearch's Filter field cannot be blank, since the default
// value of "dn={}" would not work.
// +kubebuilder:validation:MinLength=1
Username string `json:"username,omitempty"`
// UID specifies the name of the attribute in the LDAP entry which whose value shall be used to uniquely
// identify the user within this LDAP provider after a successful authentication. E.g. "uidNumber" or "objectGUID".
// The value of this field is case-sensitive and must match the case of the attribute name returned by the LDAP
// server in the user's entry. Distinguished names can be used by specifying lower-case "dn".
// +kubebuilder:validation:MinLength=1
UID string `json:"uid,omitempty"`
}
type LDAPIdentityProviderUserSearch struct {
// Base is the DN that should be used as the search base when searching for users. E.g. "ou=users,dc=example,dc=com".
// +kubebuilder:validation:MinLength=1
Base string `json:"base,omitempty"`
// Filter is the LDAP search filter which should be applied when searching for users. The pattern "{}" must occur
// in the filter and will be dynamically replaced by the username for which the search is being run. E.g. "mail={}"
// or "&(objectClass=person)(uid={})". For more information about LDAP filters, see https://ldap.com/ldap-filters.
// Note that the dn (distinguished name) is not an attribute of an entry, so "dn={}" cannot be used.
// Optional. When not specified, the default will act as if the Filter were specified as the value from
// Attributes.Username appended by "={}". When the Attributes.Username is set to "dn" then the Filter must be
// explicitly specified, since the default value of "dn={}" would not work.
// +optional
Filter string `json:"filter,omitempty"`
// Attributes specifies how the user's information should be read from the LDAP entry which was found as
// the result of the user search.
// +optional
Attributes LDAPIdentityProviderUserSearchAttributes `json:"attributes,omitempty"`
}
// Spec for configuring an LDAP identity provider.
type LDAPIdentityProviderSpec struct {
// Host is the hostname of this LDAP identity provider, i.e., where to connect. For example: ldap.example.com:636.
// +kubebuilder:validation:MinLength=1
Host string `json:"host"`
// TLS contains the connection settings for how to establish the connection to the Host.
TLS *TLSSpec `json:"tls,omitempty"`
// Bind contains the configuration for how to provide access credentials during an initial bind to the LDAP server
// to be allowed to perform searches and binds to validate a user's credentials during a user's authentication attempt.
Bind LDAPIdentityProviderBind `json:"bind,omitempty"`
// UserSearch contains the configuration for searching for a user by name in the LDAP provider.
UserSearch LDAPIdentityProviderUserSearch `json:"userSearch,omitempty"`
}
// LDAPIdentityProvider describes the configuration of an upstream Lightweight Directory Access
// Protocol (LDAP) identity provider.
// +genclient
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// +kubebuilder:resource:categories=pinniped;pinniped-idp;pinniped-idps
// +kubebuilder:printcolumn:name="Host",type=string,JSONPath=`.spec.host`
// +kubebuilder:printcolumn:name="Status",type=string,JSONPath=`.status.phase`
// +kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp`
// +kubebuilder:subresource:status
type LDAPIdentityProvider struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
// Spec for configuring the identity provider.
Spec LDAPIdentityProviderSpec `json:"spec"`
// Status of the identity provider.
Status LDAPIdentityProviderStatus `json:"status,omitempty"`
}
// List of LDAPIdentityProvider objects.
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
type LDAPIdentityProviderList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []LDAPIdentityProvider `json:"items"`
}

View File

@ -32,7 +32,8 @@ import (
"go.pinniped.dev/internal/config/supervisor"
"go.pinniped.dev/internal/controller/supervisorconfig"
"go.pinniped.dev/internal/controller/supervisorconfig/generator"
"go.pinniped.dev/internal/controller/supervisorconfig/upstreamwatcher"
"go.pinniped.dev/internal/controller/supervisorconfig/ldapupstreamwatcher"
"go.pinniped.dev/internal/controller/supervisorconfig/oidcupstreamwatcher"
"go.pinniped.dev/internal/controller/supervisorstorage"
"go.pinniped.dev/internal/controllerlib"
"go.pinniped.dev/internal/deploymentref"
@ -233,7 +234,7 @@ func startControllers(
singletonWorker,
).
WithController(
upstreamwatcher.New(
oidcupstreamwatcher.New(
dynamicUpstreamIDPProvider,
pinnipedClient,
pinnipedInformers.IDP().V1alpha1().OIDCIdentityProviders(),
@ -241,6 +242,15 @@ func startControllers(
klogr.New(),
controllerlib.WithInformer,
),
singletonWorker).
WithController(
ldapupstreamwatcher.New(
dynamicUpstreamIDPProvider,
pinnipedClient,
pinnipedInformers.IDP().V1alpha1().LDAPIdentityProviders(),
secretInformer,
controllerlib.WithInformer,
),
singletonWorker)
kubeInformers.Start(ctx.Done())

View File

@ -8,8 +8,10 @@ import (
"crypto/tls"
"crypto/x509"
"encoding/base64"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"log"
"net/http"
"os"
@ -26,6 +28,7 @@ import (
_ "k8s.io/client-go/plugin/pkg/client/auth" // Adds handlers for various dynamic auth plugins in client-go
"k8s.io/client-go/tools/clientcmd"
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
"k8s.io/client-go/transport"
conciergev1alpha1 "go.pinniped.dev/generated/latest/apis/concierge/authentication/v1alpha1"
configv1alpha1 "go.pinniped.dev/generated/latest/apis/concierge/config/v1alpha1"
@ -62,6 +65,8 @@ type getKubeconfigOIDCParams struct {
debugSessionCache bool
caBundle caBundleFlag
requestAudience string
upstreamIDPName string
upstreamIDPType string
}
type getKubeconfigConciergeParams struct {
@ -91,6 +96,23 @@ type getKubeconfigParams struct {
credentialCachePathSet bool
}
type supervisorOIDCDiscoveryResponseWithV1Alpha1 struct {
SupervisorDiscovery SupervisorDiscoveryResponseV1Alpha1 `json:"discovery.supervisor.pinniped.dev/v1alpha1"`
}
type SupervisorDiscoveryResponseV1Alpha1 struct {
PinnipedIDPsEndpoint string `json:"pinniped_identity_providers_endpoint"`
}
type supervisorIDPsDiscoveryResponseV1Alpha1 struct {
PinnipedIDPs []pinnipedIDPResponse `json:"pinniped_identity_providers"`
}
type pinnipedIDPResponse struct {
Name string `json:"name"`
Type string `json:"type"`
}
func kubeconfigCommand(deps kubeconfigDeps) *cobra.Command {
var (
cmd = &cobra.Command{
@ -128,6 +150,8 @@ func kubeconfigCommand(deps kubeconfigDeps) *cobra.Command {
f.Var(&flags.oidc.caBundle, "oidc-ca-bundle", "Path to TLS certificate authority bundle (PEM format, optional, can be repeated)")
f.BoolVar(&flags.oidc.debugSessionCache, "oidc-debug-session-cache", false, "Print debug logs related to the OpenID Connect session cache")
f.StringVar(&flags.oidc.requestAudience, "oidc-request-audience", "", "Request a token with an alternate audience using RFC8693 token exchange")
f.StringVar(&flags.oidc.upstreamIDPName, "upstream-identity-provider-name", "", "The name of the upstream identity provider used during login with a Supervisor")
f.StringVar(&flags.oidc.upstreamIDPType, "upstream-identity-provider-type", "", "The type of the upstream identity provider used during login with a Supervisor (e.g. 'oidc', 'ldap')")
f.StringVar(&flags.kubeconfigPath, "kubeconfig", os.Getenv("KUBECONFIG"), "Path to kubeconfig file")
f.StringVar(&flags.kubeconfigContextOverride, "kubeconfig-context", "", "Kubeconfig context name (default: current active context)")
f.BoolVar(&flags.skipValidate, "skip-validation", false, "Skip final validation of the kubeconfig (default: false)")
@ -165,19 +189,6 @@ func runGetKubeconfig(ctx context.Context, out io.Writer, deps kubeconfigDeps, f
return fmt.Errorf("invalid API group suffix: %w", err)
}
execConfig := clientcmdapi.ExecConfig{
APIVersion: clientauthenticationv1beta1.SchemeGroupVersion.String(),
Args: []string{},
Env: []clientcmdapi.ExecEnvVar{},
}
var err error
execConfig.Command, err = deps.getPathToSelf()
if err != nil {
return fmt.Errorf("could not determine the Pinniped executable path: %w", err)
}
execConfig.ProvideClusterInfo = true
clientConfig := newClientConfig(flags.kubeconfigPath, flags.kubeconfigContextOverride)
currentKubeConfig, err := clientConfig.RawConfig()
if err != nil {
@ -221,6 +232,47 @@ func runGetKubeconfig(ctx context.Context, out io.Writer, deps kubeconfigDeps, f
if err := discoverAuthenticatorParams(authenticator, &flags, deps.log); err != nil {
return err
}
// Point kubectl at the concierge endpoint.
cluster.Server = flags.concierge.endpoint
cluster.CertificateAuthorityData = flags.concierge.caBundle
}
// If there is an issuer, and if both upstream flags are not already set, then try to discover Supervisor upstream IDP.
if len(flags.oidc.issuer) > 0 && (flags.oidc.upstreamIDPType == "" || flags.oidc.upstreamIDPName == "") {
if err := discoverSupervisorUpstreamIDP(ctx, &flags); err != nil {
return err
}
}
execConfig, err := newExecConfig(deps, flags)
if err != nil {
return err
}
kubeconfig := newExecKubeconfig(cluster, execConfig, newKubeconfigNames)
if err := validateKubeconfig(ctx, flags, kubeconfig, deps.log); err != nil {
return err
}
return writeConfigAsYAML(out, kubeconfig)
}
func newExecConfig(deps kubeconfigDeps, flags getKubeconfigParams) (*clientcmdapi.ExecConfig, error) {
execConfig := &clientcmdapi.ExecConfig{
APIVersion: clientauthenticationv1beta1.SchemeGroupVersion.String(),
Args: []string{},
Env: []clientcmdapi.ExecEnvVar{},
ProvideClusterInfo: true,
}
var err error
execConfig.Command, err = deps.getPathToSelf()
if err != nil {
return nil, fmt.Errorf("could not determine the Pinniped executable path: %w", err)
}
if !flags.concierge.disabled {
// Append the flags to configure the Concierge credential exchange at runtime.
execConfig.Args = append(execConfig.Args,
"--enable-concierge",
@ -230,10 +282,6 @@ func runGetKubeconfig(ctx context.Context, out io.Writer, deps kubeconfigDeps, f
"--concierge-endpoint="+flags.concierge.endpoint,
"--concierge-ca-bundle-data="+base64.StdEncoding.EncodeToString(flags.concierge.caBundle),
)
// Point kubectl at the concierge endpoint.
cluster.Server = flags.concierge.endpoint
cluster.CertificateAuthorityData = flags.concierge.caBundle
}
// If --credential-cache is set, pass it through.
@ -244,7 +292,7 @@ func runGetKubeconfig(ctx context.Context, out io.Writer, deps kubeconfigDeps, f
// If one of the --static-* flags was passed, output a config that runs `pinniped login static`.
if flags.staticToken != "" || flags.staticTokenEnvName != "" {
if flags.staticToken != "" && flags.staticTokenEnvName != "" {
return fmt.Errorf("only one of --static-token and --static-token-env can be specified")
return nil, fmt.Errorf("only one of --static-token and --static-token-env can be specified")
}
execConfig.Args = append([]string{"login", "static"}, execConfig.Args...)
if flags.staticToken != "" {
@ -253,18 +301,13 @@ func runGetKubeconfig(ctx context.Context, out io.Writer, deps kubeconfigDeps, f
if flags.staticTokenEnvName != "" {
execConfig.Args = append(execConfig.Args, "--token-env="+flags.staticTokenEnvName)
}
kubeconfig := newExecKubeconfig(cluster, &execConfig, newKubeconfigNames)
if err := validateKubeconfig(ctx, flags, kubeconfig, deps.log); err != nil {
return err
}
return writeConfigAsYAML(out, kubeconfig)
return execConfig, nil
}
// Otherwise continue to parse the OIDC-related flags and output a config that runs `pinniped login oidc`.
execConfig.Args = append([]string{"login", "oidc"}, execConfig.Args...)
if flags.oidc.issuer == "" {
return fmt.Errorf("could not autodiscover --oidc-issuer and none was provided")
return nil, fmt.Errorf("could not autodiscover --oidc-issuer and none was provided")
}
execConfig.Args = append(execConfig.Args,
"--issuer="+flags.oidc.issuer,
@ -289,11 +332,14 @@ func runGetKubeconfig(ctx context.Context, out io.Writer, deps kubeconfigDeps, f
if flags.oidc.requestAudience != "" {
execConfig.Args = append(execConfig.Args, "--request-audience="+flags.oidc.requestAudience)
}
kubeconfig := newExecKubeconfig(cluster, &execConfig, newKubeconfigNames)
if err := validateKubeconfig(ctx, flags, kubeconfig, deps.log); err != nil {
return err
if flags.oidc.upstreamIDPName != "" {
execConfig.Args = append(execConfig.Args, "--upstream-identity-provider-name="+flags.oidc.upstreamIDPName)
}
return writeConfigAsYAML(out, kubeconfig)
if flags.oidc.upstreamIDPType != "" {
execConfig.Args = append(execConfig.Args, "--upstream-identity-provider-type="+flags.oidc.upstreamIDPType)
}
return execConfig, nil
}
type kubeconfigNames struct{ ContextName, UserName, ClusterName string }
@ -688,3 +734,151 @@ func hasPendingStrategy(credentialIssuer *configv1alpha1.CredentialIssuer) bool
}
return false
}
func discoverSupervisorUpstreamIDP(ctx context.Context, flags *getKubeconfigParams) error {
httpClient, err := newDiscoveryHTTPClient(flags.oidc.caBundle)
if err != nil {
return err
}
pinnipedIDPsEndpoint, err := discoverIDPsDiscoveryEndpointURL(ctx, flags.oidc.issuer, httpClient)
if err != nil {
return err
}
if pinnipedIDPsEndpoint == "" {
// The issuer is not advertising itself as a Pinniped Supervisor which supports upstream IDP discovery.
return nil
}
upstreamIDPs, err := discoverAllAvailableSupervisorUpstreamIDPs(ctx, pinnipedIDPsEndpoint, httpClient)
if err != nil {
return err
}
if len(upstreamIDPs) == 1 {
flags.oidc.upstreamIDPName = upstreamIDPs[0].Name
flags.oidc.upstreamIDPType = upstreamIDPs[0].Type
} else if len(upstreamIDPs) > 1 {
idpName, idpType, err := selectUpstreamIDP(upstreamIDPs, flags.oidc.upstreamIDPName, flags.oidc.upstreamIDPType)
if err != nil {
return err
}
flags.oidc.upstreamIDPName = idpName
flags.oidc.upstreamIDPType = idpType
}
return nil
}
func newDiscoveryHTTPClient(caBundleFlag caBundleFlag) (*http.Client, error) {
t := &http.Transport{
TLSClientConfig: &tls.Config{MinVersion: tls.VersionTLS12},
Proxy: http.ProxyFromEnvironment,
}
httpClient := &http.Client{Transport: t}
if caBundleFlag != nil {
rootCAs := x509.NewCertPool()
ok := rootCAs.AppendCertsFromPEM(caBundleFlag)
if !ok {
return nil, fmt.Errorf("unable to fetch OIDC discovery data from issuer: could not parse CA bundle")
}
t.TLSClientConfig.RootCAs = rootCAs
}
httpClient.Transport = transport.DebugWrappers(httpClient.Transport)
return httpClient, nil
}
func discoverIDPsDiscoveryEndpointURL(ctx context.Context, issuer string, httpClient *http.Client) (string, error) {
discoveredProvider, err := oidc.NewProvider(oidc.ClientContext(ctx, httpClient), issuer)
if err != nil {
return "", fmt.Errorf("while fetching OIDC discovery data from issuer: %w", err)
}
var body supervisorOIDCDiscoveryResponseWithV1Alpha1
err = discoveredProvider.Claims(&body)
if err != nil {
return "", fmt.Errorf("while fetching OIDC discovery data from issuer: %w", err)
}
return body.SupervisorDiscovery.PinnipedIDPsEndpoint, nil
}
func discoverAllAvailableSupervisorUpstreamIDPs(ctx context.Context, pinnipedIDPsEndpoint string, httpClient *http.Client) ([]pinnipedIDPResponse, error) {
request, err := http.NewRequestWithContext(ctx, http.MethodGet, pinnipedIDPsEndpoint, nil)
if err != nil {
return nil, fmt.Errorf("while forming request to IDP discovery URL: %w", err)
}
response, err := httpClient.Do(request)
if err != nil {
return nil, fmt.Errorf("unable to fetch IDP discovery data from issuer: %w", err)
}
defer func() {
_ = response.Body.Close()
}()
if response.StatusCode != http.StatusOK {
return nil, fmt.Errorf("unable to fetch IDP discovery data from issuer: unexpected http response status: %s", response.Status)
}
rawBody, err := ioutil.ReadAll(response.Body)
if err != nil {
return nil, fmt.Errorf("unable to fetch IDP discovery data from issuer: could not read response body: %w", err)
}
var body supervisorIDPsDiscoveryResponseV1Alpha1
err = json.Unmarshal(rawBody, &body)
if err != nil {
return nil, fmt.Errorf("unable to fetch IDP discovery data from issuer: could not parse response JSON: %w", err)
}
return body.PinnipedIDPs, nil
}
func selectUpstreamIDP(pinnipedIDPs []pinnipedIDPResponse, idpName, idpType string) (string, string, error) {
pinnipedIDPsString, _ := json.Marshal(pinnipedIDPs)
switch {
case idpType != "":
discoveredName := ""
for _, idp := range pinnipedIDPs {
if idp.Type == idpType {
if discoveredName != "" {
return "", "", fmt.Errorf(
"multiple Supervisor upstream identity providers of type \"%s\" were found,"+
" so the --upstream-identity-provider-name flag must be specified. "+
"Found these upstreams: %s",
idpType, pinnipedIDPsString)
}
discoveredName = idp.Name
}
}
if discoveredName == "" {
return "", "", fmt.Errorf(
"no Supervisor upstream identity providers of type \"%s\" were found."+
" Found these upstreams: %s", idpType, pinnipedIDPsString)
}
return discoveredName, idpType, nil
case idpName != "":
discoveredType := ""
for _, idp := range pinnipedIDPs {
if idp.Name == idpName {
if discoveredType != "" {
return "", "", fmt.Errorf(
"multiple Supervisor upstream identity providers with name \"%s\" were found,"+
" so the --upstream-identity-provider-type flag must be specified. Found these upstreams: %s",
idpName, pinnipedIDPsString)
}
discoveredType = idp.Type
}
}
if discoveredType == "" {
return "", "", fmt.Errorf(
"no Supervisor upstream identity providers with name \"%s\" were found."+
" Found these upstreams: %s", idpName, pinnipedIDPsString)
}
return idpName, discoveredType, nil
default:
return "", "", fmt.Errorf(
"multiple Supervisor upstream identity providers were found,"+
" so the --upstream-identity-provider-name/--upstream-identity-provider-type flags must be specified."+
" Found these upstreams: %s",
pinnipedIDPsString)
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,4 @@
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
// Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package cmd

View File

@ -54,23 +54,25 @@ func oidcLoginCommandRealDeps() oidcLoginCommandDeps {
}
type oidcLoginFlags struct {
issuer string
clientID string
listenPort uint16
scopes []string
skipBrowser bool
sessionCachePath string
caBundlePaths []string
caBundleData []string
debugSessionCache bool
requestAudience string
conciergeEnabled bool
conciergeAuthenticatorType string
conciergeAuthenticatorName string
conciergeEndpoint string
conciergeCABundle string
conciergeAPIGroupSuffix string
credentialCachePath string
issuer string
clientID string
listenPort uint16
scopes []string
skipBrowser bool
sessionCachePath string
caBundlePaths []string
caBundleData []string
debugSessionCache bool
requestAudience string
conciergeEnabled bool
conciergeAuthenticatorType string
conciergeAuthenticatorName string
conciergeEndpoint string
conciergeCABundle string
conciergeAPIGroupSuffix string
credentialCachePath string
upstreamIdentityProviderName string
upstreamIdentityProviderType string
}
func oidcLoginCommand(deps oidcLoginCommandDeps) *cobra.Command {
@ -102,6 +104,8 @@ func oidcLoginCommand(deps oidcLoginCommandDeps) *cobra.Command {
cmd.Flags().StringVar(&flags.conciergeCABundle, "concierge-ca-bundle-data", "", "CA bundle to use when connecting to the Concierge")
cmd.Flags().StringVar(&flags.conciergeAPIGroupSuffix, "concierge-api-group-suffix", groupsuffix.PinnipedDefaultSuffix, "Concierge API group suffix")
cmd.Flags().StringVar(&flags.credentialCachePath, "credential-cache", filepath.Join(mustGetConfigDir(), "credentials.yaml"), "Path to cluster-specific credentials cache (\"\" disables the cache)")
cmd.Flags().StringVar(&flags.upstreamIdentityProviderName, "upstream-identity-provider-name", "", "The name of the upstream identity provider used during login with a Supervisor")
cmd.Flags().StringVar(&flags.upstreamIdentityProviderType, "upstream-identity-provider-type", "oidc", "The type of the upstream identity provider used during login with a Supervisor (e.g. 'oidc', 'ldap')")
mustMarkHidden(cmd, "debug-session-cache")
mustMarkRequired(cmd, "issuer")
@ -113,7 +117,7 @@ func oidcLoginCommand(deps oidcLoginCommandDeps) *cobra.Command {
return cmd
}
func runOIDCLogin(cmd *cobra.Command, deps oidcLoginCommandDeps, flags oidcLoginFlags) error {
func runOIDCLogin(cmd *cobra.Command, deps oidcLoginCommandDeps, flags oidcLoginFlags) error { //nolint:funlen
pLogger, err := SetLogLevel(deps.lookupEnv)
if err != nil {
plog.WarningErr("Received error while setting log level", err)
@ -147,6 +151,23 @@ func runOIDCLogin(cmd *cobra.Command, deps oidcLoginCommandDeps, flags oidcLogin
opts = append(opts, oidcclient.WithRequestAudience(flags.requestAudience))
}
if flags.upstreamIdentityProviderName != "" {
opts = append(opts, oidcclient.WithUpstreamIdentityProvider(
flags.upstreamIdentityProviderName, flags.upstreamIdentityProviderType))
}
switch flags.upstreamIdentityProviderType {
case "oidc":
// this is the default, so don't need to do anything
case "ldap":
opts = append(opts, oidcclient.WithCLISendingCredentials())
default:
// Surprisingly cobra does not support this kind of flag validation. See https://github.com/spf13/pflag/issues/236
return fmt.Errorf(
"--upstream-identity-provider-type value not recognized: %s (supported values: oidc, ldap)",
flags.upstreamIdentityProviderType)
}
var concierge *conciergeclient.Client
if flags.conciergeEnabled {
var err error

View File

@ -60,23 +60,25 @@ func TestLoginOIDCCommand(t *testing.T) {
oidc --issuer ISSUER [flags]
Flags:
--ca-bundle strings Path to TLS certificate authority bundle (PEM format, optional, can be repeated)
--ca-bundle-data strings Base64 encoded TLS certificate authority bundle (base64 encoded PEM format, optional, can be repeated)
--client-id string OpenID Connect client ID (default "pinniped-cli")
--concierge-api-group-suffix string Concierge API group suffix (default "pinniped.dev")
--concierge-authenticator-name string Concierge authenticator name
--concierge-authenticator-type string Concierge authenticator type (e.g., 'webhook', 'jwt')
--concierge-ca-bundle-data string CA bundle to use when connecting to the Concierge
--concierge-endpoint string API base for the Concierge endpoint
--credential-cache string Path to cluster-specific credentials cache ("" disables the cache) (default "` + cfgDir + `/credentials.yaml")
--enable-concierge Use the Concierge to login
-h, --help help for oidc
--issuer string OpenID Connect issuer URL
--listen-port uint16 TCP port for localhost listener (authorization code flow only)
--request-audience string Request a token with an alternate audience using RFC8693 token exchange
--scopes strings OIDC scopes to request during login (default [offline_access,openid,pinniped:request-audience])
--session-cache string Path to session cache file (default "` + cfgDir + `/sessions.yaml")
--skip-browser Skip opening the browser (just print the URL)
--ca-bundle strings Path to TLS certificate authority bundle (PEM format, optional, can be repeated)
--ca-bundle-data strings Base64 encoded TLS certificate authority bundle (base64 encoded PEM format, optional, can be repeated)
--client-id string OpenID Connect client ID (default "pinniped-cli")
--concierge-api-group-suffix string Concierge API group suffix (default "pinniped.dev")
--concierge-authenticator-name string Concierge authenticator name
--concierge-authenticator-type string Concierge authenticator type (e.g., 'webhook', 'jwt')
--concierge-ca-bundle-data string CA bundle to use when connecting to the Concierge
--concierge-endpoint string API base for the Concierge endpoint
--credential-cache string Path to cluster-specific credentials cache ("" disables the cache) (default "` + cfgDir + `/credentials.yaml")
--enable-concierge Use the Concierge to login
-h, --help help for oidc
--issuer string OpenID Connect issuer URL
--listen-port uint16 TCP port for localhost listener (authorization code flow only)
--request-audience string Request a token with an alternate audience using RFC8693 token exchange
--scopes strings OIDC scopes to request during login (default [offline_access,openid,pinniped:request-audience])
--session-cache string Path to session cache file (default "` + cfgDir + `/sessions.yaml")
--skip-browser Skip opening the browser (just print the URL)
--upstream-identity-provider-name string The name of the upstream identity provider used during login with a Supervisor
--upstream-identity-provider-type string The type of the upstream identity provider used during login with a Supervisor (e.g. 'oidc', 'ldap') (default "oidc")
`),
},
{
@ -138,11 +140,45 @@ func TestLoginOIDCCommand(t *testing.T) {
Error: invalid Concierge parameters: invalid API group suffix: a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character (e.g. 'example.com', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*')
`),
},
{
name: "invalid upstream type",
args: []string{
"--issuer", "test-issuer",
"--upstream-identity-provider-type", "invalid",
},
wantError: true,
wantStderr: here.Doc(`
Error: --upstream-identity-provider-type value not recognized: invalid (supported values: oidc, ldap)
`),
},
{
name: "oidc upstream type is allowed",
args: []string{
"--issuer", "test-issuer",
"--client-id", "test-client-id",
"--upstream-identity-provider-type", "oidc",
"--credential-cache", "", // must specify --credential-cache or else the cache file on disk causes test pollution
},
wantOptionsCount: 4,
wantStdout: `{"kind":"ExecCredential","apiVersion":"client.authentication.k8s.io/v1beta1","spec":{},"status":{"expirationTimestamp":"3020-10-12T13:14:15Z","token":"test-id-token"}}` + "\n",
},
{
name: "ldap upstream type is allowed",
args: []string{
"--issuer", "test-issuer",
"--client-id", "test-client-id",
"--upstream-identity-provider-type", "ldap",
"--credential-cache", "", // must specify --credential-cache or else the cache file on disk causes test pollution
},
wantOptionsCount: 5,
wantStdout: `{"kind":"ExecCredential","apiVersion":"client.authentication.k8s.io/v1beta1","spec":{},"status":{"expirationTimestamp":"3020-10-12T13:14:15Z","token":"test-id-token"}}` + "\n",
},
{
name: "login error",
args: []string{
"--client-id", "test-client-id",
"--issuer", "test-issuer",
"--credential-cache", "", // must specify --credential-cache or else the cache file on disk causes test pollution
},
loginErr: fmt.Errorf("some login error"),
wantOptionsCount: 4,
@ -160,6 +196,7 @@ func TestLoginOIDCCommand(t *testing.T) {
"--concierge-authenticator-type", "jwt",
"--concierge-authenticator-name", "test-authenticator",
"--concierge-endpoint", "https://127.0.0.1:1234/",
"--credential-cache", "", // must specify --credential-cache or else the cache file on disk causes test pollution
},
conciergeErr: fmt.Errorf("some concierge error"),
wantOptionsCount: 4,
@ -173,6 +210,7 @@ func TestLoginOIDCCommand(t *testing.T) {
args: []string{
"--client-id", "test-client-id",
"--issuer", "test-issuer",
"--credential-cache", "", // must specify --credential-cache or else the cache file on disk causes test pollution
},
env: map[string]string{"PINNIPED_DEBUG": "true"},
wantOptionsCount: 4,
@ -180,7 +218,6 @@ func TestLoginOIDCCommand(t *testing.T) {
wantLogs: []string{
"\"level\"=0 \"msg\"=\"Pinniped login: Performing OIDC login\" \"client id\"=\"test-client-id\" \"issuer\"=\"test-issuer\"",
"\"level\"=0 \"msg\"=\"Pinniped login: No concierge configured, skipping token credential exchange\"",
"\"level\"=0 \"msg\"=\"Pinniped login: caching cluster credential for future use.\"",
},
},
{
@ -200,10 +237,12 @@ func TestLoginOIDCCommand(t *testing.T) {
"--concierge-endpoint", "https://127.0.0.1:1234/",
"--concierge-ca-bundle-data", base64.StdEncoding.EncodeToString(testCA.Bundle()),
"--concierge-api-group-suffix", "some.suffix.com",
"--credential-cache", testutil.TempDir(t) + "/credentials.yaml",
"--credential-cache", testutil.TempDir(t) + "/credentials.yaml", // must specify --credential-cache or else the cache file on disk causes test pollution
"--upstream-identity-provider-name", "some-upstream-name",
"--upstream-identity-provider-type", "ldap",
},
env: map[string]string{"PINNIPED_DEBUG": "true"},
wantOptionsCount: 8,
wantOptionsCount: 10,
wantStdout: `{"kind":"ExecCredential","apiVersion":"client.authentication.k8s.io/v1beta1","spec":{},"status":{"token":"exchanged-token"}}` + "\n",
wantLogs: []string{
"\"level\"=0 \"msg\"=\"Pinniped login: Performing OIDC login\" \"client id\"=\"test-client-id\" \"issuer\"=\"test-issuer\"",

View File

@ -0,0 +1,234 @@
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.4.0
creationTimestamp: null
name: ldapidentityproviders.idp.supervisor.pinniped.dev
spec:
group: idp.supervisor.pinniped.dev
names:
categories:
- pinniped
- pinniped-idp
- pinniped-idps
kind: LDAPIdentityProvider
listKind: LDAPIdentityProviderList
plural: ldapidentityproviders
singular: ldapidentityprovider
scope: Namespaced
versions:
- additionalPrinterColumns:
- jsonPath: .spec.host
name: Host
type: string
- jsonPath: .status.phase
name: Status
type: string
- jsonPath: .metadata.creationTimestamp
name: Age
type: date
name: v1alpha1
schema:
openAPIV3Schema:
description: LDAPIdentityProvider describes the configuration of an upstream
Lightweight Directory Access Protocol (LDAP) identity provider.
properties:
apiVersion:
description: 'APIVersion defines the versioned schema of this representation
of an object. Servers should convert recognized schemas to the latest
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
type: string
kind:
description: 'Kind is a string value representing the REST resource this
object represents. Servers may infer this from the endpoint the client
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
type: string
metadata:
type: object
spec:
description: Spec for configuring the identity provider.
properties:
bind:
description: Bind contains the configuration for how to provide access
credentials during an initial bind to the LDAP server to be allowed
to perform searches and binds to validate a user's credentials during
a user's authentication attempt.
properties:
secretName:
description: SecretName contains the name of a namespace-local
Secret object that provides the username and password for an
LDAP bind user. This account will be used to perform LDAP searches.
The Secret should be of type "kubernetes.io/basic-auth" which
includes "username" and "password" keys. The username value
should be the full DN of your bind account, e.g. "cn=bind-account,ou=users,dc=example,dc=com".
The password must be non-empty.
minLength: 1
type: string
required:
- secretName
type: object
host:
description: 'Host is the hostname of this LDAP identity provider,
i.e., where to connect. For example: ldap.example.com:636.'
minLength: 1
type: string
tls:
description: TLS contains the connection settings for how to establish
the connection to the Host.
properties:
certificateAuthorityData:
description: X.509 Certificate Authority (base64-encoded PEM bundle).
If omitted, a default set of system roots will be trusted.
type: string
type: object
userSearch:
description: UserSearch contains the configuration for searching for
a user by name in the LDAP provider.
properties:
attributes:
description: Attributes specifies how the user's information should
be read from the LDAP entry which was found as the result of
the user search.
properties:
uid:
description: UID specifies the name of the attribute in the
LDAP entry which whose value shall be used to uniquely identify
the user within this LDAP provider after a successful authentication.
E.g. "uidNumber" or "objectGUID". The value of this field
is case-sensitive and must match the case of the attribute
name returned by the LDAP server in the user's entry. Distinguished
names can be used by specifying lower-case "dn".
minLength: 1
type: string
username:
description: Username specifies the name of attribute in the
LDAP entry which whose value shall become the username of
the user after a successful authentication. This would typically
be the same attribute name used in the user search filter,
although it can be different. E.g. "mail" or "uid" or "userPrincipalName".
The value of this field is case-sensitive and must match
the case of the attribute name returned by the LDAP server
in the user's entry. Distinguished names can be used by
specifying lower-case "dn". When this field is set to "dn"
then the LDAPIdentityProviderUserSearch's Filter field cannot
be blank, since the default value of "dn={}" would not work.
minLength: 1
type: string
type: object
base:
description: Base is the DN that should be used as the search
base when searching for users. E.g. "ou=users,dc=example,dc=com".
minLength: 1
type: string
filter:
description: Filter is the LDAP search filter which should be
applied when searching for users. The pattern "{}" must occur
in the filter and will be dynamically replaced by the username
for which the search is being run. E.g. "mail={}" or "&(objectClass=person)(uid={})".
For more information about LDAP filters, see https://ldap.com/ldap-filters.
Note that the dn (distinguished name) is not an attribute of
an entry, so "dn={}" cannot be used. Optional. When not specified,
the default will act as if the Filter were specified as the
value from Attributes.Username appended by "={}". When the Attributes.Username
is set to "dn" then the Filter must be explicitly specified,
since the default value of "dn={}" would not work.
type: string
type: object
required:
- host
type: object
status:
description: Status of the identity provider.
properties:
conditions:
description: Represents the observations of an identity provider's
current state.
items:
description: Condition status of a resource (mirrored from the metav1.Condition
type added in Kubernetes 1.19). In a future API version we can
switch to using the upstream type. See https://github.com/kubernetes/apimachinery/blob/v0.19.0/pkg/apis/meta/v1/types.go#L1353-L1413.
properties:
lastTransitionTime:
description: lastTransitionTime is the last time the condition
transitioned from one status to another. This should be when
the underlying condition changed. If that is not known, then
using the time when the API field changed is acceptable.
format: date-time
type: string
message:
description: message is a human readable message indicating
details about the transition. This may be an empty string.
maxLength: 32768
type: string
observedGeneration:
description: observedGeneration represents the .metadata.generation
that the condition was set based upon. For instance, if .metadata.generation
is currently 12, but the .status.conditions[x].observedGeneration
is 9, the condition is out of date with respect to the current
state of the instance.
format: int64
minimum: 0
type: integer
reason:
description: reason contains a programmatic identifier indicating
the reason for the condition's last transition. Producers
of specific condition types may define expected values and
meanings for this field, and whether the values are considered
a guaranteed API. The value should be a CamelCase string.
This field may not be empty.
maxLength: 1024
minLength: 1
pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$
type: string
status:
description: status of the condition, one of True, False, Unknown.
enum:
- "True"
- "False"
- Unknown
type: string
type:
description: type of condition in CamelCase or in foo.example.com/CamelCase.
--- Many .condition.type values are consistent across resources
like Available, but because arbitrary conditions can be useful
(see .node.status.conditions), the ability to deconflict is
important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt)
maxLength: 316
pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
type: string
required:
- lastTransitionTime
- message
- reason
- status
- type
type: object
type: array
x-kubernetes-list-map-keys:
- type
x-kubernetes-list-type: map
phase:
default: Pending
description: Phase summarizes the overall status of the LDAPIdentityProvider.
enum:
- Pending
- Ready
- Error
type: string
type: object
required:
- spec
type: object
served: true
storage: true
subresources:
status: {}
status:
acceptedNames:
kind: ""
plural: ""
conditions: []
storedVersions: []

View File

@ -32,6 +32,14 @@ rules:
- #@ pinnipedDevAPIGroupWithPrefix("idp.supervisor")
resources: [oidcidentityproviders/status]
verbs: [get, patch, update]
- apiGroups:
- #@ pinnipedDevAPIGroupWithPrefix("idp.supervisor")
resources: [ldapidentityproviders]
verbs: [get, list, watch]
- apiGroups:
- #@ pinnipedDevAPIGroupWithPrefix("idp.supervisor")
resources: [ldapidentityproviders/status]
verbs: [get, patch, update]
#! We want to be able to read pods/replicasets/deployment so we can learn who our deployment is to set
#! as an owner reference.
- apiGroups: [""]

View File

@ -22,3 +22,12 @@ metadata:
name: #@ pinnipedDevAPIGroupWithPrefix("oidcidentityproviders.idp.supervisor")
spec:
group: #@ pinnipedDevAPIGroupWithPrefix("idp.supervisor")
#@overlay/match by=overlay.subset({"kind": "CustomResourceDefinition", "metadata":{"name":"ldapidentityproviders.idp.supervisor.pinniped.dev"}}), expects=1
---
metadata:
#@overlay/match missing_ok=True
labels: #@ labels()
name: #@ pinnipedDevAPIGroupWithPrefix("ldapidentityproviders.idp.supervisor")
spec:
group: #@ pinnipedDevAPIGroupWithPrefix("idp.supervisor")

View File

@ -669,10 +669,11 @@ Package v1alpha1 is the v1alpha1 version of the Pinniped supervisor identity pro
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-condition"]
==== Condition
Condition status of a resource (mirrored from the metav1.Condition type added in Kubernetes 1.19). In a future API version we can switch to using the upstream type. See https://github.com/kubernetes/apimachinery/blob/v0.19.0/pkg/apis/meta/v1/types.go#L1353-L1413.
.Appears In:
****
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-ldapidentityproviderstatus[$$LDAPIdentityProviderStatus$$]
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-oidcidentityproviderstatus[$$OIDCIdentityProviderStatus$$]
****
@ -688,6 +689,132 @@ Condition status of a resource (mirrored from the metav1.Condition type added in
|===
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-conditionstatus"]
==== ConditionStatus (string)
.Appears In:
****
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-condition[$$Condition$$]
****
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-ldapidentityprovider"]
==== LDAPIdentityProvider
LDAPIdentityProvider describes the configuration of an upstream Lightweight Directory Access Protocol (LDAP) identity provider.
.Appears In:
****
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-ldapidentityproviderlist[$$LDAPIdentityProviderList$$]
****
[cols="25a,75a", options="header"]
|===
| Field | Description
| *`metadata`* __link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.17/#objectmeta-v1-meta[$$ObjectMeta$$]__ | Refer to Kubernetes API documentation for fields of `metadata`.
| *`spec`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-ldapidentityproviderspec[$$LDAPIdentityProviderSpec$$]__ | Spec for configuring the identity provider.
| *`status`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-ldapidentityproviderstatus[$$LDAPIdentityProviderStatus$$]__ | Status of the identity provider.
|===
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-ldapidentityproviderbind"]
==== LDAPIdentityProviderBind
.Appears In:
****
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-ldapidentityproviderspec[$$LDAPIdentityProviderSpec$$]
****
[cols="25a,75a", options="header"]
|===
| Field | Description
| *`secretName`* __string__ | SecretName contains the name of a namespace-local Secret object that provides the username and password for an LDAP bind user. This account will be used to perform LDAP searches. The Secret should be of type "kubernetes.io/basic-auth" which includes "username" and "password" keys. The username value should be the full DN of your bind account, e.g. "cn=bind-account,ou=users,dc=example,dc=com". The password must be non-empty.
|===
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-ldapidentityproviderspec"]
==== LDAPIdentityProviderSpec
Spec for configuring an LDAP identity provider.
.Appears In:
****
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-ldapidentityprovider[$$LDAPIdentityProvider$$]
****
[cols="25a,75a", options="header"]
|===
| Field | Description
| *`host`* __string__ | Host is the hostname of this LDAP identity provider, i.e., where to connect. For example: ldap.example.com:636.
| *`tls`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-tlsspec[$$TLSSpec$$]__ | TLS contains the connection settings for how to establish the connection to the Host.
| *`bind`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-ldapidentityproviderbind[$$LDAPIdentityProviderBind$$]__ | Bind contains the configuration for how to provide access credentials during an initial bind to the LDAP server to be allowed to perform searches and binds to validate a user's credentials during a user's authentication attempt.
| *`userSearch`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-ldapidentityproviderusersearch[$$LDAPIdentityProviderUserSearch$$]__ | UserSearch contains the configuration for searching for a user by name in the LDAP provider.
|===
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-ldapidentityproviderstatus"]
==== LDAPIdentityProviderStatus
Status of an LDAP identity provider.
.Appears In:
****
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-ldapidentityprovider[$$LDAPIdentityProvider$$]
****
[cols="25a,75a", options="header"]
|===
| Field | Description
| *`phase`* __LDAPIdentityProviderPhase__ | Phase summarizes the overall status of the LDAPIdentityProvider.
| *`conditions`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-condition[$$Condition$$] array__ | Represents the observations of an identity provider's current state.
|===
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-ldapidentityproviderusersearch"]
==== LDAPIdentityProviderUserSearch
.Appears In:
****
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-ldapidentityproviderspec[$$LDAPIdentityProviderSpec$$]
****
[cols="25a,75a", options="header"]
|===
| Field | Description
| *`base`* __string__ | Base is the DN that should be used as the search base when searching for users. E.g. "ou=users,dc=example,dc=com".
| *`filter`* __string__ | Filter is the LDAP search filter which should be applied when searching for users. The pattern "{}" must occur in the filter and will be dynamically replaced by the username for which the search is being run. E.g. "mail={}" or "&(objectClass=person)(uid={})". For more information about LDAP filters, see https://ldap.com/ldap-filters. Note that the dn (distinguished name) is not an attribute of an entry, so "dn={}" cannot be used. Optional. When not specified, the default will act as if the Filter were specified as the value from Attributes.Username appended by "={}". When the Attributes.Username is set to "dn" then the Filter must be explicitly specified, since the default value of "dn={}" would not work.
| *`attributes`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-ldapidentityproviderusersearchattributes[$$LDAPIdentityProviderUserSearchAttributes$$]__ | Attributes specifies how the user's information should be read from the LDAP entry which was found as the result of the user search.
|===
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-ldapidentityproviderusersearchattributes"]
==== LDAPIdentityProviderUserSearchAttributes
.Appears In:
****
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-ldapidentityproviderusersearch[$$LDAPIdentityProviderUserSearch$$]
****
[cols="25a,75a", options="header"]
|===
| Field | Description
| *`username`* __string__ | Username specifies the name of attribute in the LDAP entry which whose value shall become the username of the user after a successful authentication. This would typically be the same attribute name used in the user search filter, although it can be different. E.g. "mail" or "uid" or "userPrincipalName". The value of this field is case-sensitive and must match the case of the attribute name returned by the LDAP server in the user's entry. Distinguished names can be used by specifying lower-case "dn". When this field is set to "dn" then the LDAPIdentityProviderUserSearch's Filter field cannot be blank, since the default value of "dn={}" would not work.
| *`uid`* __string__ | UID specifies the name of the attribute in the LDAP entry which whose value shall be used to uniquely identify the user within this LDAP provider after a successful authentication. E.g. "uidNumber" or "objectGUID". The value of this field is case-sensitive and must match the case of the attribute name returned by the LDAP server in the user's entry. Distinguished names can be used by specifying lower-case "dn".
|===
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-oidcauthorizationconfig"]
==== OIDCAuthorizationConfig
@ -797,7 +924,7 @@ Status of an OIDC identity provider.
|===
| Field | Description
| *`phase`* __OIDCIdentityProviderPhase__ | Phase summarizes the overall status of the OIDCIdentityProvider.
| *`conditions`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-condition[$$Condition$$]__ | Represents the observations of an identity provider's current state.
| *`conditions`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-condition[$$Condition$$] array__ | Represents the observations of an identity provider's current state.
|===
@ -808,6 +935,7 @@ Status of an OIDC identity provider.
.Appears In:
****
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-ldapidentityproviderspec[$$LDAPIdentityProviderSpec$$]
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-oidcidentityproviderspec[$$OIDCIdentityProviderSpec$$]
****

View File

@ -32,6 +32,8 @@ func addKnownTypes(scheme *runtime.Scheme) error {
scheme.AddKnownTypes(SchemeGroupVersion,
&OIDCIdentityProvider{},
&OIDCIdentityProviderList{},
&LDAPIdentityProvider{},
&LDAPIdentityProviderList{},
)
metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
return nil

View File

@ -0,0 +1,132 @@
// Copyright 2021 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package v1alpha1
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
type LDAPIdentityProviderPhase string
const (
// LDAPPhasePending is the default phase for newly-created LDAPIdentityProvider resources.
LDAPPhasePending LDAPIdentityProviderPhase = "Pending"
// LDAPPhaseReady is the phase for an LDAPIdentityProvider resource in a healthy state.
LDAPPhaseReady LDAPIdentityProviderPhase = "Ready"
// LDAPPhaseError is the phase for an LDAPIdentityProvider in an unhealthy state.
LDAPPhaseError LDAPIdentityProviderPhase = "Error"
)
// Status of an LDAP identity provider.
type LDAPIdentityProviderStatus struct {
// Phase summarizes the overall status of the LDAPIdentityProvider.
// +kubebuilder:default=Pending
// +kubebuilder:validation:Enum=Pending;Ready;Error
Phase LDAPIdentityProviderPhase `json:"phase,omitempty"`
// Represents the observations of an identity provider's current state.
// +patchMergeKey=type
// +patchStrategy=merge
// +listType=map
// +listMapKey=type
Conditions []Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"`
}
type LDAPIdentityProviderBind struct {
// SecretName contains the name of a namespace-local Secret object that provides the username and
// password for an LDAP bind user. This account will be used to perform LDAP searches. The Secret should be
// of type "kubernetes.io/basic-auth" which includes "username" and "password" keys. The username value
// should be the full DN of your bind account, e.g. "cn=bind-account,ou=users,dc=example,dc=com".
// The password must be non-empty.
// +kubebuilder:validation:MinLength=1
SecretName string `json:"secretName"`
}
type LDAPIdentityProviderUserSearchAttributes struct {
// Username specifies the name of attribute in the LDAP entry which whose value shall become the username
// of the user after a successful authentication. This would typically be the same attribute name used in
// the user search filter, although it can be different. E.g. "mail" or "uid" or "userPrincipalName".
// The value of this field is case-sensitive and must match the case of the attribute name returned by the LDAP
// server in the user's entry. Distinguished names can be used by specifying lower-case "dn". When this field
// is set to "dn" then the LDAPIdentityProviderUserSearch's Filter field cannot be blank, since the default
// value of "dn={}" would not work.
// +kubebuilder:validation:MinLength=1
Username string `json:"username,omitempty"`
// UID specifies the name of the attribute in the LDAP entry which whose value shall be used to uniquely
// identify the user within this LDAP provider after a successful authentication. E.g. "uidNumber" or "objectGUID".
// The value of this field is case-sensitive and must match the case of the attribute name returned by the LDAP
// server in the user's entry. Distinguished names can be used by specifying lower-case "dn".
// +kubebuilder:validation:MinLength=1
UID string `json:"uid,omitempty"`
}
type LDAPIdentityProviderUserSearch struct {
// Base is the DN that should be used as the search base when searching for users. E.g. "ou=users,dc=example,dc=com".
// +kubebuilder:validation:MinLength=1
Base string `json:"base,omitempty"`
// Filter is the LDAP search filter which should be applied when searching for users. The pattern "{}" must occur
// in the filter and will be dynamically replaced by the username for which the search is being run. E.g. "mail={}"
// or "&(objectClass=person)(uid={})". For more information about LDAP filters, see https://ldap.com/ldap-filters.
// Note that the dn (distinguished name) is not an attribute of an entry, so "dn={}" cannot be used.
// Optional. When not specified, the default will act as if the Filter were specified as the value from
// Attributes.Username appended by "={}". When the Attributes.Username is set to "dn" then the Filter must be
// explicitly specified, since the default value of "dn={}" would not work.
// +optional
Filter string `json:"filter,omitempty"`
// Attributes specifies how the user's information should be read from the LDAP entry which was found as
// the result of the user search.
// +optional
Attributes LDAPIdentityProviderUserSearchAttributes `json:"attributes,omitempty"`
}
// Spec for configuring an LDAP identity provider.
type LDAPIdentityProviderSpec struct {
// Host is the hostname of this LDAP identity provider, i.e., where to connect. For example: ldap.example.com:636.
// +kubebuilder:validation:MinLength=1
Host string `json:"host"`
// TLS contains the connection settings for how to establish the connection to the Host.
TLS *TLSSpec `json:"tls,omitempty"`
// Bind contains the configuration for how to provide access credentials during an initial bind to the LDAP server
// to be allowed to perform searches and binds to validate a user's credentials during a user's authentication attempt.
Bind LDAPIdentityProviderBind `json:"bind,omitempty"`
// UserSearch contains the configuration for searching for a user by name in the LDAP provider.
UserSearch LDAPIdentityProviderUserSearch `json:"userSearch,omitempty"`
}
// LDAPIdentityProvider describes the configuration of an upstream Lightweight Directory Access
// Protocol (LDAP) identity provider.
// +genclient
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// +kubebuilder:resource:categories=pinniped;pinniped-idp;pinniped-idps
// +kubebuilder:printcolumn:name="Host",type=string,JSONPath=`.spec.host`
// +kubebuilder:printcolumn:name="Status",type=string,JSONPath=`.status.phase`
// +kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp`
// +kubebuilder:subresource:status
type LDAPIdentityProvider struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
// Spec for configuring the identity provider.
Spec LDAPIdentityProviderSpec `json:"spec"`
// Status of the identity provider.
Status LDAPIdentityProviderStatus `json:"status,omitempty"`
}
// List of LDAPIdentityProvider objects.
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
type LDAPIdentityProviderList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []LDAPIdentityProvider `json:"items"`
}

View File

@ -28,6 +28,162 @@ func (in *Condition) DeepCopy() *Condition {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *LDAPIdentityProvider) DeepCopyInto(out *LDAPIdentityProvider) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
in.Spec.DeepCopyInto(&out.Spec)
in.Status.DeepCopyInto(&out.Status)
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LDAPIdentityProvider.
func (in *LDAPIdentityProvider) DeepCopy() *LDAPIdentityProvider {
if in == nil {
return nil
}
out := new(LDAPIdentityProvider)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *LDAPIdentityProvider) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *LDAPIdentityProviderBind) DeepCopyInto(out *LDAPIdentityProviderBind) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LDAPIdentityProviderBind.
func (in *LDAPIdentityProviderBind) DeepCopy() *LDAPIdentityProviderBind {
if in == nil {
return nil
}
out := new(LDAPIdentityProviderBind)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *LDAPIdentityProviderList) DeepCopyInto(out *LDAPIdentityProviderList) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ListMeta.DeepCopyInto(&out.ListMeta)
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]LDAPIdentityProvider, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LDAPIdentityProviderList.
func (in *LDAPIdentityProviderList) DeepCopy() *LDAPIdentityProviderList {
if in == nil {
return nil
}
out := new(LDAPIdentityProviderList)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *LDAPIdentityProviderList) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *LDAPIdentityProviderSpec) DeepCopyInto(out *LDAPIdentityProviderSpec) {
*out = *in
if in.TLS != nil {
in, out := &in.TLS, &out.TLS
*out = new(TLSSpec)
**out = **in
}
out.Bind = in.Bind
out.UserSearch = in.UserSearch
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LDAPIdentityProviderSpec.
func (in *LDAPIdentityProviderSpec) DeepCopy() *LDAPIdentityProviderSpec {
if in == nil {
return nil
}
out := new(LDAPIdentityProviderSpec)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *LDAPIdentityProviderStatus) DeepCopyInto(out *LDAPIdentityProviderStatus) {
*out = *in
if in.Conditions != nil {
in, out := &in.Conditions, &out.Conditions
*out = make([]Condition, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LDAPIdentityProviderStatus.
func (in *LDAPIdentityProviderStatus) DeepCopy() *LDAPIdentityProviderStatus {
if in == nil {
return nil
}
out := new(LDAPIdentityProviderStatus)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *LDAPIdentityProviderUserSearch) DeepCopyInto(out *LDAPIdentityProviderUserSearch) {
*out = *in
out.Attributes = in.Attributes
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LDAPIdentityProviderUserSearch.
func (in *LDAPIdentityProviderUserSearch) DeepCopy() *LDAPIdentityProviderUserSearch {
if in == nil {
return nil
}
out := new(LDAPIdentityProviderUserSearch)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *LDAPIdentityProviderUserSearchAttributes) DeepCopyInto(out *LDAPIdentityProviderUserSearchAttributes) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LDAPIdentityProviderUserSearchAttributes.
func (in *LDAPIdentityProviderUserSearchAttributes) DeepCopy() *LDAPIdentityProviderUserSearchAttributes {
if in == nil {
return nil
}
out := new(LDAPIdentityProviderUserSearchAttributes)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *OIDCAuthorizationConfig) DeepCopyInto(out *OIDCAuthorizationConfig) {
*out = *in

View File

@ -15,6 +15,10 @@ type FakeIDPV1alpha1 struct {
*testing.Fake
}
func (c *FakeIDPV1alpha1) LDAPIdentityProviders(namespace string) v1alpha1.LDAPIdentityProviderInterface {
return &FakeLDAPIdentityProviders{c, namespace}
}
func (c *FakeIDPV1alpha1) OIDCIdentityProviders(namespace string) v1alpha1.OIDCIdentityProviderInterface {
return &FakeOIDCIdentityProviders{c, namespace}
}

View File

@ -0,0 +1,127 @@
// Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
// Code generated by client-gen. DO NOT EDIT.
package fake
import (
v1alpha1 "go.pinniped.dev/generated/1.17/apis/supervisor/idp/v1alpha1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
labels "k8s.io/apimachinery/pkg/labels"
schema "k8s.io/apimachinery/pkg/runtime/schema"
types "k8s.io/apimachinery/pkg/types"
watch "k8s.io/apimachinery/pkg/watch"
testing "k8s.io/client-go/testing"
)
// FakeLDAPIdentityProviders implements LDAPIdentityProviderInterface
type FakeLDAPIdentityProviders struct {
Fake *FakeIDPV1alpha1
ns string
}
var ldapidentityprovidersResource = schema.GroupVersionResource{Group: "idp.supervisor.pinniped.dev", Version: "v1alpha1", Resource: "ldapidentityproviders"}
var ldapidentityprovidersKind = schema.GroupVersionKind{Group: "idp.supervisor.pinniped.dev", Version: "v1alpha1", Kind: "LDAPIdentityProvider"}
// Get takes name of the lDAPIdentityProvider, and returns the corresponding lDAPIdentityProvider object, and an error if there is any.
func (c *FakeLDAPIdentityProviders) Get(name string, options v1.GetOptions) (result *v1alpha1.LDAPIdentityProvider, err error) {
obj, err := c.Fake.
Invokes(testing.NewGetAction(ldapidentityprovidersResource, c.ns, name), &v1alpha1.LDAPIdentityProvider{})
if obj == nil {
return nil, err
}
return obj.(*v1alpha1.LDAPIdentityProvider), err
}
// List takes label and field selectors, and returns the list of LDAPIdentityProviders that match those selectors.
func (c *FakeLDAPIdentityProviders) List(opts v1.ListOptions) (result *v1alpha1.LDAPIdentityProviderList, err error) {
obj, err := c.Fake.
Invokes(testing.NewListAction(ldapidentityprovidersResource, ldapidentityprovidersKind, c.ns, opts), &v1alpha1.LDAPIdentityProviderList{})
if obj == nil {
return nil, err
}
label, _, _ := testing.ExtractFromListOptions(opts)
if label == nil {
label = labels.Everything()
}
list := &v1alpha1.LDAPIdentityProviderList{ListMeta: obj.(*v1alpha1.LDAPIdentityProviderList).ListMeta}
for _, item := range obj.(*v1alpha1.LDAPIdentityProviderList).Items {
if label.Matches(labels.Set(item.Labels)) {
list.Items = append(list.Items, item)
}
}
return list, err
}
// Watch returns a watch.Interface that watches the requested lDAPIdentityProviders.
func (c *FakeLDAPIdentityProviders) Watch(opts v1.ListOptions) (watch.Interface, error) {
return c.Fake.
InvokesWatch(testing.NewWatchAction(ldapidentityprovidersResource, c.ns, opts))
}
// Create takes the representation of a lDAPIdentityProvider and creates it. Returns the server's representation of the lDAPIdentityProvider, and an error, if there is any.
func (c *FakeLDAPIdentityProviders) Create(lDAPIdentityProvider *v1alpha1.LDAPIdentityProvider) (result *v1alpha1.LDAPIdentityProvider, err error) {
obj, err := c.Fake.
Invokes(testing.NewCreateAction(ldapidentityprovidersResource, c.ns, lDAPIdentityProvider), &v1alpha1.LDAPIdentityProvider{})
if obj == nil {
return nil, err
}
return obj.(*v1alpha1.LDAPIdentityProvider), err
}
// Update takes the representation of a lDAPIdentityProvider and updates it. Returns the server's representation of the lDAPIdentityProvider, and an error, if there is any.
func (c *FakeLDAPIdentityProviders) Update(lDAPIdentityProvider *v1alpha1.LDAPIdentityProvider) (result *v1alpha1.LDAPIdentityProvider, err error) {
obj, err := c.Fake.
Invokes(testing.NewUpdateAction(ldapidentityprovidersResource, c.ns, lDAPIdentityProvider), &v1alpha1.LDAPIdentityProvider{})
if obj == nil {
return nil, err
}
return obj.(*v1alpha1.LDAPIdentityProvider), err
}
// UpdateStatus was generated because the type contains a Status member.
// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus().
func (c *FakeLDAPIdentityProviders) UpdateStatus(lDAPIdentityProvider *v1alpha1.LDAPIdentityProvider) (*v1alpha1.LDAPIdentityProvider, error) {
obj, err := c.Fake.
Invokes(testing.NewUpdateSubresourceAction(ldapidentityprovidersResource, "status", c.ns, lDAPIdentityProvider), &v1alpha1.LDAPIdentityProvider{})
if obj == nil {
return nil, err
}
return obj.(*v1alpha1.LDAPIdentityProvider), err
}
// Delete takes name of the lDAPIdentityProvider and deletes it. Returns an error if one occurs.
func (c *FakeLDAPIdentityProviders) Delete(name string, options *v1.DeleteOptions) error {
_, err := c.Fake.
Invokes(testing.NewDeleteAction(ldapidentityprovidersResource, c.ns, name), &v1alpha1.LDAPIdentityProvider{})
return err
}
// DeleteCollection deletes a collection of objects.
func (c *FakeLDAPIdentityProviders) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error {
action := testing.NewDeleteCollectionAction(ldapidentityprovidersResource, c.ns, listOptions)
_, err := c.Fake.Invokes(action, &v1alpha1.LDAPIdentityProviderList{})
return err
}
// Patch applies the patch and returns the patched lDAPIdentityProvider.
func (c *FakeLDAPIdentityProviders) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1alpha1.LDAPIdentityProvider, err error) {
obj, err := c.Fake.
Invokes(testing.NewPatchSubresourceAction(ldapidentityprovidersResource, c.ns, name, pt, data, subresources...), &v1alpha1.LDAPIdentityProvider{})
if obj == nil {
return nil, err
}
return obj.(*v1alpha1.LDAPIdentityProvider), err
}

View File

@ -5,4 +5,6 @@
package v1alpha1
type LDAPIdentityProviderExpansion interface{}
type OIDCIdentityProviderExpansion interface{}

View File

@ -13,6 +13,7 @@ import (
type IDPV1alpha1Interface interface {
RESTClient() rest.Interface
LDAPIdentityProvidersGetter
OIDCIdentityProvidersGetter
}
@ -21,6 +22,10 @@ type IDPV1alpha1Client struct {
restClient rest.Interface
}
func (c *IDPV1alpha1Client) LDAPIdentityProviders(namespace string) LDAPIdentityProviderInterface {
return newLDAPIdentityProviders(c, namespace)
}
func (c *IDPV1alpha1Client) OIDCIdentityProviders(namespace string) OIDCIdentityProviderInterface {
return newOIDCIdentityProviders(c, namespace)
}

View File

@ -0,0 +1,178 @@
// Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
// Code generated by client-gen. DO NOT EDIT.
package v1alpha1
import (
"time"
v1alpha1 "go.pinniped.dev/generated/1.17/apis/supervisor/idp/v1alpha1"
scheme "go.pinniped.dev/generated/1.17/client/supervisor/clientset/versioned/scheme"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
types "k8s.io/apimachinery/pkg/types"
watch "k8s.io/apimachinery/pkg/watch"
rest "k8s.io/client-go/rest"
)
// LDAPIdentityProvidersGetter has a method to return a LDAPIdentityProviderInterface.
// A group's client should implement this interface.
type LDAPIdentityProvidersGetter interface {
LDAPIdentityProviders(namespace string) LDAPIdentityProviderInterface
}
// LDAPIdentityProviderInterface has methods to work with LDAPIdentityProvider resources.
type LDAPIdentityProviderInterface interface {
Create(*v1alpha1.LDAPIdentityProvider) (*v1alpha1.LDAPIdentityProvider, error)
Update(*v1alpha1.LDAPIdentityProvider) (*v1alpha1.LDAPIdentityProvider, error)
UpdateStatus(*v1alpha1.LDAPIdentityProvider) (*v1alpha1.LDAPIdentityProvider, error)
Delete(name string, options *v1.DeleteOptions) error
DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error
Get(name string, options v1.GetOptions) (*v1alpha1.LDAPIdentityProvider, error)
List(opts v1.ListOptions) (*v1alpha1.LDAPIdentityProviderList, error)
Watch(opts v1.ListOptions) (watch.Interface, error)
Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1alpha1.LDAPIdentityProvider, err error)
LDAPIdentityProviderExpansion
}
// lDAPIdentityProviders implements LDAPIdentityProviderInterface
type lDAPIdentityProviders struct {
client rest.Interface
ns string
}
// newLDAPIdentityProviders returns a LDAPIdentityProviders
func newLDAPIdentityProviders(c *IDPV1alpha1Client, namespace string) *lDAPIdentityProviders {
return &lDAPIdentityProviders{
client: c.RESTClient(),
ns: namespace,
}
}
// Get takes name of the lDAPIdentityProvider, and returns the corresponding lDAPIdentityProvider object, and an error if there is any.
func (c *lDAPIdentityProviders) Get(name string, options v1.GetOptions) (result *v1alpha1.LDAPIdentityProvider, err error) {
result = &v1alpha1.LDAPIdentityProvider{}
err = c.client.Get().
Namespace(c.ns).
Resource("ldapidentityproviders").
Name(name).
VersionedParams(&options, scheme.ParameterCodec).
Do().
Into(result)
return
}
// List takes label and field selectors, and returns the list of LDAPIdentityProviders that match those selectors.
func (c *lDAPIdentityProviders) List(opts v1.ListOptions) (result *v1alpha1.LDAPIdentityProviderList, err error) {
var timeout time.Duration
if opts.TimeoutSeconds != nil {
timeout = time.Duration(*opts.TimeoutSeconds) * time.Second
}
result = &v1alpha1.LDAPIdentityProviderList{}
err = c.client.Get().
Namespace(c.ns).
Resource("ldapidentityproviders").
VersionedParams(&opts, scheme.ParameterCodec).
Timeout(timeout).
Do().
Into(result)
return
}
// Watch returns a watch.Interface that watches the requested lDAPIdentityProviders.
func (c *lDAPIdentityProviders) Watch(opts v1.ListOptions) (watch.Interface, error) {
var timeout time.Duration
if opts.TimeoutSeconds != nil {
timeout = time.Duration(*opts.TimeoutSeconds) * time.Second
}
opts.Watch = true
return c.client.Get().
Namespace(c.ns).
Resource("ldapidentityproviders").
VersionedParams(&opts, scheme.ParameterCodec).
Timeout(timeout).
Watch()
}
// Create takes the representation of a lDAPIdentityProvider and creates it. Returns the server's representation of the lDAPIdentityProvider, and an error, if there is any.
func (c *lDAPIdentityProviders) Create(lDAPIdentityProvider *v1alpha1.LDAPIdentityProvider) (result *v1alpha1.LDAPIdentityProvider, err error) {
result = &v1alpha1.LDAPIdentityProvider{}
err = c.client.Post().
Namespace(c.ns).
Resource("ldapidentityproviders").
Body(lDAPIdentityProvider).
Do().
Into(result)
return
}
// Update takes the representation of a lDAPIdentityProvider and updates it. Returns the server's representation of the lDAPIdentityProvider, and an error, if there is any.
func (c *lDAPIdentityProviders) Update(lDAPIdentityProvider *v1alpha1.LDAPIdentityProvider) (result *v1alpha1.LDAPIdentityProvider, err error) {
result = &v1alpha1.LDAPIdentityProvider{}
err = c.client.Put().
Namespace(c.ns).
Resource("ldapidentityproviders").
Name(lDAPIdentityProvider.Name).
Body(lDAPIdentityProvider).
Do().
Into(result)
return
}
// UpdateStatus was generated because the type contains a Status member.
// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus().
func (c *lDAPIdentityProviders) UpdateStatus(lDAPIdentityProvider *v1alpha1.LDAPIdentityProvider) (result *v1alpha1.LDAPIdentityProvider, err error) {
result = &v1alpha1.LDAPIdentityProvider{}
err = c.client.Put().
Namespace(c.ns).
Resource("ldapidentityproviders").
Name(lDAPIdentityProvider.Name).
SubResource("status").
Body(lDAPIdentityProvider).
Do().
Into(result)
return
}
// Delete takes name of the lDAPIdentityProvider and deletes it. Returns an error if one occurs.
func (c *lDAPIdentityProviders) Delete(name string, options *v1.DeleteOptions) error {
return c.client.Delete().
Namespace(c.ns).
Resource("ldapidentityproviders").
Name(name).
Body(options).
Do().
Error()
}
// DeleteCollection deletes a collection of objects.
func (c *lDAPIdentityProviders) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error {
var timeout time.Duration
if listOptions.TimeoutSeconds != nil {
timeout = time.Duration(*listOptions.TimeoutSeconds) * time.Second
}
return c.client.Delete().
Namespace(c.ns).
Resource("ldapidentityproviders").
VersionedParams(&listOptions, scheme.ParameterCodec).
Timeout(timeout).
Body(options).
Do().
Error()
}
// Patch applies the patch and returns the patched lDAPIdentityProvider.
func (c *lDAPIdentityProviders) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1alpha1.LDAPIdentityProvider, err error) {
result = &v1alpha1.LDAPIdentityProvider{}
err = c.client.Patch(pt).
Namespace(c.ns).
Resource("ldapidentityproviders").
SubResource(subresources...).
Name(name).
Body(data).
Do().
Into(result)
return
}

View File

@ -45,6 +45,8 @@ func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource
return &genericInformer{resource: resource.GroupResource(), informer: f.Config().V1alpha1().FederationDomains().Informer()}, nil
// Group=idp.supervisor.pinniped.dev, Version=v1alpha1
case idpv1alpha1.SchemeGroupVersion.WithResource("ldapidentityproviders"):
return &genericInformer{resource: resource.GroupResource(), informer: f.IDP().V1alpha1().LDAPIdentityProviders().Informer()}, nil
case idpv1alpha1.SchemeGroupVersion.WithResource("oidcidentityproviders"):
return &genericInformer{resource: resource.GroupResource(), informer: f.IDP().V1alpha1().OIDCIdentityProviders().Informer()}, nil

View File

@ -11,6 +11,8 @@ import (
// Interface provides access to all the informers in this group version.
type Interface interface {
// LDAPIdentityProviders returns a LDAPIdentityProviderInformer.
LDAPIdentityProviders() LDAPIdentityProviderInformer
// OIDCIdentityProviders returns a OIDCIdentityProviderInformer.
OIDCIdentityProviders() OIDCIdentityProviderInformer
}
@ -26,6 +28,11 @@ func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakList
return &version{factory: f, namespace: namespace, tweakListOptions: tweakListOptions}
}
// LDAPIdentityProviders returns a LDAPIdentityProviderInformer.
func (v *version) LDAPIdentityProviders() LDAPIdentityProviderInformer {
return &lDAPIdentityProviderInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions}
}
// OIDCIdentityProviders returns a OIDCIdentityProviderInformer.
func (v *version) OIDCIdentityProviders() OIDCIdentityProviderInformer {
return &oIDCIdentityProviderInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions}

View File

@ -0,0 +1,76 @@
// Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
// Code generated by informer-gen. DO NOT EDIT.
package v1alpha1
import (
time "time"
idpv1alpha1 "go.pinniped.dev/generated/1.17/apis/supervisor/idp/v1alpha1"
versioned "go.pinniped.dev/generated/1.17/client/supervisor/clientset/versioned"
internalinterfaces "go.pinniped.dev/generated/1.17/client/supervisor/informers/externalversions/internalinterfaces"
v1alpha1 "go.pinniped.dev/generated/1.17/client/supervisor/listers/idp/v1alpha1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
runtime "k8s.io/apimachinery/pkg/runtime"
watch "k8s.io/apimachinery/pkg/watch"
cache "k8s.io/client-go/tools/cache"
)
// LDAPIdentityProviderInformer provides access to a shared informer and lister for
// LDAPIdentityProviders.
type LDAPIdentityProviderInformer interface {
Informer() cache.SharedIndexInformer
Lister() v1alpha1.LDAPIdentityProviderLister
}
type lDAPIdentityProviderInformer struct {
factory internalinterfaces.SharedInformerFactory
tweakListOptions internalinterfaces.TweakListOptionsFunc
namespace string
}
// NewLDAPIdentityProviderInformer constructs a new informer for LDAPIdentityProvider type.
// Always prefer using an informer factory to get a shared informer instead of getting an independent
// one. This reduces memory footprint and number of connections to the server.
func NewLDAPIdentityProviderInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer {
return NewFilteredLDAPIdentityProviderInformer(client, namespace, resyncPeriod, indexers, nil)
}
// NewFilteredLDAPIdentityProviderInformer constructs a new informer for LDAPIdentityProvider type.
// Always prefer using an informer factory to get a shared informer instead of getting an independent
// one. This reduces memory footprint and number of connections to the server.
func NewFilteredLDAPIdentityProviderInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer {
return cache.NewSharedIndexInformer(
&cache.ListWatch{
ListFunc: func(options v1.ListOptions) (runtime.Object, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.IDPV1alpha1().LDAPIdentityProviders(namespace).List(options)
},
WatchFunc: func(options v1.ListOptions) (watch.Interface, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.IDPV1alpha1().LDAPIdentityProviders(namespace).Watch(options)
},
},
&idpv1alpha1.LDAPIdentityProvider{},
resyncPeriod,
indexers,
)
}
func (f *lDAPIdentityProviderInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer {
return NewFilteredLDAPIdentityProviderInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions)
}
func (f *lDAPIdentityProviderInformer) Informer() cache.SharedIndexInformer {
return f.factory.InformerFor(&idpv1alpha1.LDAPIdentityProvider{}, f.defaultInformer)
}
func (f *lDAPIdentityProviderInformer) Lister() v1alpha1.LDAPIdentityProviderLister {
return v1alpha1.NewLDAPIdentityProviderLister(f.Informer().GetIndexer())
}

View File

@ -5,6 +5,14 @@
package v1alpha1
// LDAPIdentityProviderListerExpansion allows custom methods to be added to
// LDAPIdentityProviderLister.
type LDAPIdentityProviderListerExpansion interface{}
// LDAPIdentityProviderNamespaceListerExpansion allows custom methods to be added to
// LDAPIdentityProviderNamespaceLister.
type LDAPIdentityProviderNamespaceListerExpansion interface{}
// OIDCIdentityProviderListerExpansion allows custom methods to be added to
// OIDCIdentityProviderLister.
type OIDCIdentityProviderListerExpansion interface{}

View File

@ -0,0 +1,81 @@
// Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
// Code generated by lister-gen. DO NOT EDIT.
package v1alpha1
import (
v1alpha1 "go.pinniped.dev/generated/1.17/apis/supervisor/idp/v1alpha1"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/client-go/tools/cache"
)
// LDAPIdentityProviderLister helps list LDAPIdentityProviders.
type LDAPIdentityProviderLister interface {
// List lists all LDAPIdentityProviders in the indexer.
List(selector labels.Selector) (ret []*v1alpha1.LDAPIdentityProvider, err error)
// LDAPIdentityProviders returns an object that can list and get LDAPIdentityProviders.
LDAPIdentityProviders(namespace string) LDAPIdentityProviderNamespaceLister
LDAPIdentityProviderListerExpansion
}
// lDAPIdentityProviderLister implements the LDAPIdentityProviderLister interface.
type lDAPIdentityProviderLister struct {
indexer cache.Indexer
}
// NewLDAPIdentityProviderLister returns a new LDAPIdentityProviderLister.
func NewLDAPIdentityProviderLister(indexer cache.Indexer) LDAPIdentityProviderLister {
return &lDAPIdentityProviderLister{indexer: indexer}
}
// List lists all LDAPIdentityProviders in the indexer.
func (s *lDAPIdentityProviderLister) List(selector labels.Selector) (ret []*v1alpha1.LDAPIdentityProvider, err error) {
err = cache.ListAll(s.indexer, selector, func(m interface{}) {
ret = append(ret, m.(*v1alpha1.LDAPIdentityProvider))
})
return ret, err
}
// LDAPIdentityProviders returns an object that can list and get LDAPIdentityProviders.
func (s *lDAPIdentityProviderLister) LDAPIdentityProviders(namespace string) LDAPIdentityProviderNamespaceLister {
return lDAPIdentityProviderNamespaceLister{indexer: s.indexer, namespace: namespace}
}
// LDAPIdentityProviderNamespaceLister helps list and get LDAPIdentityProviders.
type LDAPIdentityProviderNamespaceLister interface {
// List lists all LDAPIdentityProviders in the indexer for a given namespace.
List(selector labels.Selector) (ret []*v1alpha1.LDAPIdentityProvider, err error)
// Get retrieves the LDAPIdentityProvider from the indexer for a given namespace and name.
Get(name string) (*v1alpha1.LDAPIdentityProvider, error)
LDAPIdentityProviderNamespaceListerExpansion
}
// lDAPIdentityProviderNamespaceLister implements the LDAPIdentityProviderNamespaceLister
// interface.
type lDAPIdentityProviderNamespaceLister struct {
indexer cache.Indexer
namespace string
}
// List lists all LDAPIdentityProviders in the indexer for a given namespace.
func (s lDAPIdentityProviderNamespaceLister) List(selector labels.Selector) (ret []*v1alpha1.LDAPIdentityProvider, err error) {
err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) {
ret = append(ret, m.(*v1alpha1.LDAPIdentityProvider))
})
return ret, err
}
// Get retrieves the LDAPIdentityProvider from the indexer for a given namespace and name.
func (s lDAPIdentityProviderNamespaceLister) Get(name string) (*v1alpha1.LDAPIdentityProvider, error) {
obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name)
if err != nil {
return nil, err
}
if !exists {
return nil, errors.NewNotFound(v1alpha1.Resource("ldapidentityprovider"), name)
}
return obj.(*v1alpha1.LDAPIdentityProvider), nil
}

View File

@ -0,0 +1,234 @@
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.4.0
creationTimestamp: null
name: ldapidentityproviders.idp.supervisor.pinniped.dev
spec:
group: idp.supervisor.pinniped.dev
names:
categories:
- pinniped
- pinniped-idp
- pinniped-idps
kind: LDAPIdentityProvider
listKind: LDAPIdentityProviderList
plural: ldapidentityproviders
singular: ldapidentityprovider
scope: Namespaced
versions:
- additionalPrinterColumns:
- jsonPath: .spec.host
name: Host
type: string
- jsonPath: .status.phase
name: Status
type: string
- jsonPath: .metadata.creationTimestamp
name: Age
type: date
name: v1alpha1
schema:
openAPIV3Schema:
description: LDAPIdentityProvider describes the configuration of an upstream
Lightweight Directory Access Protocol (LDAP) identity provider.
properties:
apiVersion:
description: 'APIVersion defines the versioned schema of this representation
of an object. Servers should convert recognized schemas to the latest
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
type: string
kind:
description: 'Kind is a string value representing the REST resource this
object represents. Servers may infer this from the endpoint the client
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
type: string
metadata:
type: object
spec:
description: Spec for configuring the identity provider.
properties:
bind:
description: Bind contains the configuration for how to provide access
credentials during an initial bind to the LDAP server to be allowed
to perform searches and binds to validate a user's credentials during
a user's authentication attempt.
properties:
secretName:
description: SecretName contains the name of a namespace-local
Secret object that provides the username and password for an
LDAP bind user. This account will be used to perform LDAP searches.
The Secret should be of type "kubernetes.io/basic-auth" which
includes "username" and "password" keys. The username value
should be the full DN of your bind account, e.g. "cn=bind-account,ou=users,dc=example,dc=com".
The password must be non-empty.
minLength: 1
type: string
required:
- secretName
type: object
host:
description: 'Host is the hostname of this LDAP identity provider,
i.e., where to connect. For example: ldap.example.com:636.'
minLength: 1
type: string
tls:
description: TLS contains the connection settings for how to establish
the connection to the Host.
properties:
certificateAuthorityData:
description: X.509 Certificate Authority (base64-encoded PEM bundle).
If omitted, a default set of system roots will be trusted.
type: string
type: object
userSearch:
description: UserSearch contains the configuration for searching for
a user by name in the LDAP provider.
properties:
attributes:
description: Attributes specifies how the user's information should
be read from the LDAP entry which was found as the result of
the user search.
properties:
uid:
description: UID specifies the name of the attribute in the
LDAP entry which whose value shall be used to uniquely identify
the user within this LDAP provider after a successful authentication.
E.g. "uidNumber" or "objectGUID". The value of this field
is case-sensitive and must match the case of the attribute
name returned by the LDAP server in the user's entry. Distinguished
names can be used by specifying lower-case "dn".
minLength: 1
type: string
username:
description: Username specifies the name of attribute in the
LDAP entry which whose value shall become the username of
the user after a successful authentication. This would typically
be the same attribute name used in the user search filter,
although it can be different. E.g. "mail" or "uid" or "userPrincipalName".
The value of this field is case-sensitive and must match
the case of the attribute name returned by the LDAP server
in the user's entry. Distinguished names can be used by
specifying lower-case "dn". When this field is set to "dn"
then the LDAPIdentityProviderUserSearch's Filter field cannot
be blank, since the default value of "dn={}" would not work.
minLength: 1
type: string
type: object
base:
description: Base is the DN that should be used as the search
base when searching for users. E.g. "ou=users,dc=example,dc=com".
minLength: 1
type: string
filter:
description: Filter is the LDAP search filter which should be
applied when searching for users. The pattern "{}" must occur
in the filter and will be dynamically replaced by the username
for which the search is being run. E.g. "mail={}" or "&(objectClass=person)(uid={})".
For more information about LDAP filters, see https://ldap.com/ldap-filters.
Note that the dn (distinguished name) is not an attribute of
an entry, so "dn={}" cannot be used. Optional. When not specified,
the default will act as if the Filter were specified as the
value from Attributes.Username appended by "={}". When the Attributes.Username
is set to "dn" then the Filter must be explicitly specified,
since the default value of "dn={}" would not work.
type: string
type: object
required:
- host
type: object
status:
description: Status of the identity provider.
properties:
conditions:
description: Represents the observations of an identity provider's
current state.
items:
description: Condition status of a resource (mirrored from the metav1.Condition
type added in Kubernetes 1.19). In a future API version we can
switch to using the upstream type. See https://github.com/kubernetes/apimachinery/blob/v0.19.0/pkg/apis/meta/v1/types.go#L1353-L1413.
properties:
lastTransitionTime:
description: lastTransitionTime is the last time the condition
transitioned from one status to another. This should be when
the underlying condition changed. If that is not known, then
using the time when the API field changed is acceptable.
format: date-time
type: string
message:
description: message is a human readable message indicating
details about the transition. This may be an empty string.
maxLength: 32768
type: string
observedGeneration:
description: observedGeneration represents the .metadata.generation
that the condition was set based upon. For instance, if .metadata.generation
is currently 12, but the .status.conditions[x].observedGeneration
is 9, the condition is out of date with respect to the current
state of the instance.
format: int64
minimum: 0
type: integer
reason:
description: reason contains a programmatic identifier indicating
the reason for the condition's last transition. Producers
of specific condition types may define expected values and
meanings for this field, and whether the values are considered
a guaranteed API. The value should be a CamelCase string.
This field may not be empty.
maxLength: 1024
minLength: 1
pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$
type: string
status:
description: status of the condition, one of True, False, Unknown.
enum:
- "True"
- "False"
- Unknown
type: string
type:
description: type of condition in CamelCase or in foo.example.com/CamelCase.
--- Many .condition.type values are consistent across resources
like Available, but because arbitrary conditions can be useful
(see .node.status.conditions), the ability to deconflict is
important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt)
maxLength: 316
pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
type: string
required:
- lastTransitionTime
- message
- reason
- status
- type
type: object
type: array
x-kubernetes-list-map-keys:
- type
x-kubernetes-list-type: map
phase:
default: Pending
description: Phase summarizes the overall status of the LDAPIdentityProvider.
enum:
- Pending
- Ready
- Error
type: string
type: object
required:
- spec
type: object
served: true
storage: true
subresources:
status: {}
status:
acceptedNames:
kind: ""
plural: ""
conditions: []
storedVersions: []

View File

@ -669,10 +669,11 @@ Package v1alpha1 is the v1alpha1 version of the Pinniped supervisor identity pro
[id="{anchor_prefix}-go-pinniped-dev-generated-1-18-apis-supervisor-idp-v1alpha1-condition"]
==== Condition
Condition status of a resource (mirrored from the metav1.Condition type added in Kubernetes 1.19). In a future API version we can switch to using the upstream type. See https://github.com/kubernetes/apimachinery/blob/v0.19.0/pkg/apis/meta/v1/types.go#L1353-L1413.
.Appears In:
****
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-18-apis-supervisor-idp-v1alpha1-ldapidentityproviderstatus[$$LDAPIdentityProviderStatus$$]
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-18-apis-supervisor-idp-v1alpha1-oidcidentityproviderstatus[$$OIDCIdentityProviderStatus$$]
****
@ -688,6 +689,132 @@ Condition status of a resource (mirrored from the metav1.Condition type added in
|===
[id="{anchor_prefix}-go-pinniped-dev-generated-1-18-apis-supervisor-idp-v1alpha1-conditionstatus"]
==== ConditionStatus (string)
.Appears In:
****
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-18-apis-supervisor-idp-v1alpha1-condition[$$Condition$$]
****
[id="{anchor_prefix}-go-pinniped-dev-generated-1-18-apis-supervisor-idp-v1alpha1-ldapidentityprovider"]
==== LDAPIdentityProvider
LDAPIdentityProvider describes the configuration of an upstream Lightweight Directory Access Protocol (LDAP) identity provider.
.Appears In:
****
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-18-apis-supervisor-idp-v1alpha1-ldapidentityproviderlist[$$LDAPIdentityProviderList$$]
****
[cols="25a,75a", options="header"]
|===
| Field | Description
| *`metadata`* __link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.18/#objectmeta-v1-meta[$$ObjectMeta$$]__ | Refer to Kubernetes API documentation for fields of `metadata`.
| *`spec`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-18-apis-supervisor-idp-v1alpha1-ldapidentityproviderspec[$$LDAPIdentityProviderSpec$$]__ | Spec for configuring the identity provider.
| *`status`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-18-apis-supervisor-idp-v1alpha1-ldapidentityproviderstatus[$$LDAPIdentityProviderStatus$$]__ | Status of the identity provider.
|===
[id="{anchor_prefix}-go-pinniped-dev-generated-1-18-apis-supervisor-idp-v1alpha1-ldapidentityproviderbind"]
==== LDAPIdentityProviderBind
.Appears In:
****
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-18-apis-supervisor-idp-v1alpha1-ldapidentityproviderspec[$$LDAPIdentityProviderSpec$$]
****
[cols="25a,75a", options="header"]
|===
| Field | Description
| *`secretName`* __string__ | SecretName contains the name of a namespace-local Secret object that provides the username and password for an LDAP bind user. This account will be used to perform LDAP searches. The Secret should be of type "kubernetes.io/basic-auth" which includes "username" and "password" keys. The username value should be the full DN of your bind account, e.g. "cn=bind-account,ou=users,dc=example,dc=com". The password must be non-empty.
|===
[id="{anchor_prefix}-go-pinniped-dev-generated-1-18-apis-supervisor-idp-v1alpha1-ldapidentityproviderspec"]
==== LDAPIdentityProviderSpec
Spec for configuring an LDAP identity provider.
.Appears In:
****
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-18-apis-supervisor-idp-v1alpha1-ldapidentityprovider[$$LDAPIdentityProvider$$]
****
[cols="25a,75a", options="header"]
|===
| Field | Description
| *`host`* __string__ | Host is the hostname of this LDAP identity provider, i.e., where to connect. For example: ldap.example.com:636.
| *`tls`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-18-apis-supervisor-idp-v1alpha1-tlsspec[$$TLSSpec$$]__ | TLS contains the connection settings for how to establish the connection to the Host.
| *`bind`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-18-apis-supervisor-idp-v1alpha1-ldapidentityproviderbind[$$LDAPIdentityProviderBind$$]__ | Bind contains the configuration for how to provide access credentials during an initial bind to the LDAP server to be allowed to perform searches and binds to validate a user's credentials during a user's authentication attempt.
| *`userSearch`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-18-apis-supervisor-idp-v1alpha1-ldapidentityproviderusersearch[$$LDAPIdentityProviderUserSearch$$]__ | UserSearch contains the configuration for searching for a user by name in the LDAP provider.
|===
[id="{anchor_prefix}-go-pinniped-dev-generated-1-18-apis-supervisor-idp-v1alpha1-ldapidentityproviderstatus"]
==== LDAPIdentityProviderStatus
Status of an LDAP identity provider.
.Appears In:
****
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-18-apis-supervisor-idp-v1alpha1-ldapidentityprovider[$$LDAPIdentityProvider$$]
****
[cols="25a,75a", options="header"]
|===
| Field | Description
| *`phase`* __LDAPIdentityProviderPhase__ | Phase summarizes the overall status of the LDAPIdentityProvider.
| *`conditions`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-18-apis-supervisor-idp-v1alpha1-condition[$$Condition$$] array__ | Represents the observations of an identity provider's current state.
|===
[id="{anchor_prefix}-go-pinniped-dev-generated-1-18-apis-supervisor-idp-v1alpha1-ldapidentityproviderusersearch"]
==== LDAPIdentityProviderUserSearch
.Appears In:
****
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-18-apis-supervisor-idp-v1alpha1-ldapidentityproviderspec[$$LDAPIdentityProviderSpec$$]
****
[cols="25a,75a", options="header"]
|===
| Field | Description
| *`base`* __string__ | Base is the DN that should be used as the search base when searching for users. E.g. "ou=users,dc=example,dc=com".
| *`filter`* __string__ | Filter is the LDAP search filter which should be applied when searching for users. The pattern "{}" must occur in the filter and will be dynamically replaced by the username for which the search is being run. E.g. "mail={}" or "&(objectClass=person)(uid={})". For more information about LDAP filters, see https://ldap.com/ldap-filters. Note that the dn (distinguished name) is not an attribute of an entry, so "dn={}" cannot be used. Optional. When not specified, the default will act as if the Filter were specified as the value from Attributes.Username appended by "={}". When the Attributes.Username is set to "dn" then the Filter must be explicitly specified, since the default value of "dn={}" would not work.
| *`attributes`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-18-apis-supervisor-idp-v1alpha1-ldapidentityproviderusersearchattributes[$$LDAPIdentityProviderUserSearchAttributes$$]__ | Attributes specifies how the user's information should be read from the LDAP entry which was found as the result of the user search.
|===
[id="{anchor_prefix}-go-pinniped-dev-generated-1-18-apis-supervisor-idp-v1alpha1-ldapidentityproviderusersearchattributes"]
==== LDAPIdentityProviderUserSearchAttributes
.Appears In:
****
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-18-apis-supervisor-idp-v1alpha1-ldapidentityproviderusersearch[$$LDAPIdentityProviderUserSearch$$]
****
[cols="25a,75a", options="header"]
|===
| Field | Description
| *`username`* __string__ | Username specifies the name of attribute in the LDAP entry which whose value shall become the username of the user after a successful authentication. This would typically be the same attribute name used in the user search filter, although it can be different. E.g. "mail" or "uid" or "userPrincipalName". The value of this field is case-sensitive and must match the case of the attribute name returned by the LDAP server in the user's entry. Distinguished names can be used by specifying lower-case "dn". When this field is set to "dn" then the LDAPIdentityProviderUserSearch's Filter field cannot be blank, since the default value of "dn={}" would not work.
| *`uid`* __string__ | UID specifies the name of the attribute in the LDAP entry which whose value shall be used to uniquely identify the user within this LDAP provider after a successful authentication. E.g. "uidNumber" or "objectGUID". The value of this field is case-sensitive and must match the case of the attribute name returned by the LDAP server in the user's entry. Distinguished names can be used by specifying lower-case "dn".
|===
[id="{anchor_prefix}-go-pinniped-dev-generated-1-18-apis-supervisor-idp-v1alpha1-oidcauthorizationconfig"]
==== OIDCAuthorizationConfig
@ -797,7 +924,7 @@ Status of an OIDC identity provider.
|===
| Field | Description
| *`phase`* __OIDCIdentityProviderPhase__ | Phase summarizes the overall status of the OIDCIdentityProvider.
| *`conditions`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-18-apis-supervisor-idp-v1alpha1-condition[$$Condition$$]__ | Represents the observations of an identity provider's current state.
| *`conditions`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-18-apis-supervisor-idp-v1alpha1-condition[$$Condition$$] array__ | Represents the observations of an identity provider's current state.
|===
@ -808,6 +935,7 @@ Status of an OIDC identity provider.
.Appears In:
****
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-18-apis-supervisor-idp-v1alpha1-ldapidentityproviderspec[$$LDAPIdentityProviderSpec$$]
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-18-apis-supervisor-idp-v1alpha1-oidcidentityproviderspec[$$OIDCIdentityProviderSpec$$]
****

View File

@ -32,6 +32,8 @@ func addKnownTypes(scheme *runtime.Scheme) error {
scheme.AddKnownTypes(SchemeGroupVersion,
&OIDCIdentityProvider{},
&OIDCIdentityProviderList{},
&LDAPIdentityProvider{},
&LDAPIdentityProviderList{},
)
metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
return nil

View File

@ -0,0 +1,132 @@
// Copyright 2021 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package v1alpha1
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
type LDAPIdentityProviderPhase string
const (
// LDAPPhasePending is the default phase for newly-created LDAPIdentityProvider resources.
LDAPPhasePending LDAPIdentityProviderPhase = "Pending"
// LDAPPhaseReady is the phase for an LDAPIdentityProvider resource in a healthy state.
LDAPPhaseReady LDAPIdentityProviderPhase = "Ready"
// LDAPPhaseError is the phase for an LDAPIdentityProvider in an unhealthy state.
LDAPPhaseError LDAPIdentityProviderPhase = "Error"
)
// Status of an LDAP identity provider.
type LDAPIdentityProviderStatus struct {
// Phase summarizes the overall status of the LDAPIdentityProvider.
// +kubebuilder:default=Pending
// +kubebuilder:validation:Enum=Pending;Ready;Error
Phase LDAPIdentityProviderPhase `json:"phase,omitempty"`
// Represents the observations of an identity provider's current state.
// +patchMergeKey=type
// +patchStrategy=merge
// +listType=map
// +listMapKey=type
Conditions []Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"`
}
type LDAPIdentityProviderBind struct {
// SecretName contains the name of a namespace-local Secret object that provides the username and
// password for an LDAP bind user. This account will be used to perform LDAP searches. The Secret should be
// of type "kubernetes.io/basic-auth" which includes "username" and "password" keys. The username value
// should be the full DN of your bind account, e.g. "cn=bind-account,ou=users,dc=example,dc=com".
// The password must be non-empty.
// +kubebuilder:validation:MinLength=1
SecretName string `json:"secretName"`
}
type LDAPIdentityProviderUserSearchAttributes struct {
// Username specifies the name of attribute in the LDAP entry which whose value shall become the username
// of the user after a successful authentication. This would typically be the same attribute name used in
// the user search filter, although it can be different. E.g. "mail" or "uid" or "userPrincipalName".
// The value of this field is case-sensitive and must match the case of the attribute name returned by the LDAP
// server in the user's entry. Distinguished names can be used by specifying lower-case "dn". When this field
// is set to "dn" then the LDAPIdentityProviderUserSearch's Filter field cannot be blank, since the default
// value of "dn={}" would not work.
// +kubebuilder:validation:MinLength=1
Username string `json:"username,omitempty"`
// UID specifies the name of the attribute in the LDAP entry which whose value shall be used to uniquely
// identify the user within this LDAP provider after a successful authentication. E.g. "uidNumber" or "objectGUID".
// The value of this field is case-sensitive and must match the case of the attribute name returned by the LDAP
// server in the user's entry. Distinguished names can be used by specifying lower-case "dn".
// +kubebuilder:validation:MinLength=1
UID string `json:"uid,omitempty"`
}
type LDAPIdentityProviderUserSearch struct {
// Base is the DN that should be used as the search base when searching for users. E.g. "ou=users,dc=example,dc=com".
// +kubebuilder:validation:MinLength=1
Base string `json:"base,omitempty"`
// Filter is the LDAP search filter which should be applied when searching for users. The pattern "{}" must occur
// in the filter and will be dynamically replaced by the username for which the search is being run. E.g. "mail={}"
// or "&(objectClass=person)(uid={})". For more information about LDAP filters, see https://ldap.com/ldap-filters.
// Note that the dn (distinguished name) is not an attribute of an entry, so "dn={}" cannot be used.
// Optional. When not specified, the default will act as if the Filter were specified as the value from
// Attributes.Username appended by "={}". When the Attributes.Username is set to "dn" then the Filter must be
// explicitly specified, since the default value of "dn={}" would not work.
// +optional
Filter string `json:"filter,omitempty"`
// Attributes specifies how the user's information should be read from the LDAP entry which was found as
// the result of the user search.
// +optional
Attributes LDAPIdentityProviderUserSearchAttributes `json:"attributes,omitempty"`
}
// Spec for configuring an LDAP identity provider.
type LDAPIdentityProviderSpec struct {
// Host is the hostname of this LDAP identity provider, i.e., where to connect. For example: ldap.example.com:636.
// +kubebuilder:validation:MinLength=1
Host string `json:"host"`
// TLS contains the connection settings for how to establish the connection to the Host.
TLS *TLSSpec `json:"tls,omitempty"`
// Bind contains the configuration for how to provide access credentials during an initial bind to the LDAP server
// to be allowed to perform searches and binds to validate a user's credentials during a user's authentication attempt.
Bind LDAPIdentityProviderBind `json:"bind,omitempty"`
// UserSearch contains the configuration for searching for a user by name in the LDAP provider.
UserSearch LDAPIdentityProviderUserSearch `json:"userSearch,omitempty"`
}
// LDAPIdentityProvider describes the configuration of an upstream Lightweight Directory Access
// Protocol (LDAP) identity provider.
// +genclient
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// +kubebuilder:resource:categories=pinniped;pinniped-idp;pinniped-idps
// +kubebuilder:printcolumn:name="Host",type=string,JSONPath=`.spec.host`
// +kubebuilder:printcolumn:name="Status",type=string,JSONPath=`.status.phase`
// +kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp`
// +kubebuilder:subresource:status
type LDAPIdentityProvider struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
// Spec for configuring the identity provider.
Spec LDAPIdentityProviderSpec `json:"spec"`
// Status of the identity provider.
Status LDAPIdentityProviderStatus `json:"status,omitempty"`
}
// List of LDAPIdentityProvider objects.
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
type LDAPIdentityProviderList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []LDAPIdentityProvider `json:"items"`
}

View File

@ -28,6 +28,162 @@ func (in *Condition) DeepCopy() *Condition {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *LDAPIdentityProvider) DeepCopyInto(out *LDAPIdentityProvider) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
in.Spec.DeepCopyInto(&out.Spec)
in.Status.DeepCopyInto(&out.Status)
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LDAPIdentityProvider.
func (in *LDAPIdentityProvider) DeepCopy() *LDAPIdentityProvider {
if in == nil {
return nil
}
out := new(LDAPIdentityProvider)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *LDAPIdentityProvider) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *LDAPIdentityProviderBind) DeepCopyInto(out *LDAPIdentityProviderBind) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LDAPIdentityProviderBind.
func (in *LDAPIdentityProviderBind) DeepCopy() *LDAPIdentityProviderBind {
if in == nil {
return nil
}
out := new(LDAPIdentityProviderBind)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *LDAPIdentityProviderList) DeepCopyInto(out *LDAPIdentityProviderList) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ListMeta.DeepCopyInto(&out.ListMeta)
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]LDAPIdentityProvider, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LDAPIdentityProviderList.
func (in *LDAPIdentityProviderList) DeepCopy() *LDAPIdentityProviderList {
if in == nil {
return nil
}
out := new(LDAPIdentityProviderList)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *LDAPIdentityProviderList) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *LDAPIdentityProviderSpec) DeepCopyInto(out *LDAPIdentityProviderSpec) {
*out = *in
if in.TLS != nil {
in, out := &in.TLS, &out.TLS
*out = new(TLSSpec)
**out = **in
}
out.Bind = in.Bind
out.UserSearch = in.UserSearch
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LDAPIdentityProviderSpec.
func (in *LDAPIdentityProviderSpec) DeepCopy() *LDAPIdentityProviderSpec {
if in == nil {
return nil
}
out := new(LDAPIdentityProviderSpec)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *LDAPIdentityProviderStatus) DeepCopyInto(out *LDAPIdentityProviderStatus) {
*out = *in
if in.Conditions != nil {
in, out := &in.Conditions, &out.Conditions
*out = make([]Condition, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LDAPIdentityProviderStatus.
func (in *LDAPIdentityProviderStatus) DeepCopy() *LDAPIdentityProviderStatus {
if in == nil {
return nil
}
out := new(LDAPIdentityProviderStatus)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *LDAPIdentityProviderUserSearch) DeepCopyInto(out *LDAPIdentityProviderUserSearch) {
*out = *in
out.Attributes = in.Attributes
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LDAPIdentityProviderUserSearch.
func (in *LDAPIdentityProviderUserSearch) DeepCopy() *LDAPIdentityProviderUserSearch {
if in == nil {
return nil
}
out := new(LDAPIdentityProviderUserSearch)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *LDAPIdentityProviderUserSearchAttributes) DeepCopyInto(out *LDAPIdentityProviderUserSearchAttributes) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LDAPIdentityProviderUserSearchAttributes.
func (in *LDAPIdentityProviderUserSearchAttributes) DeepCopy() *LDAPIdentityProviderUserSearchAttributes {
if in == nil {
return nil
}
out := new(LDAPIdentityProviderUserSearchAttributes)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *OIDCAuthorizationConfig) DeepCopyInto(out *OIDCAuthorizationConfig) {
*out = *in

View File

@ -15,6 +15,10 @@ type FakeIDPV1alpha1 struct {
*testing.Fake
}
func (c *FakeIDPV1alpha1) LDAPIdentityProviders(namespace string) v1alpha1.LDAPIdentityProviderInterface {
return &FakeLDAPIdentityProviders{c, namespace}
}
func (c *FakeIDPV1alpha1) OIDCIdentityProviders(namespace string) v1alpha1.OIDCIdentityProviderInterface {
return &FakeOIDCIdentityProviders{c, namespace}
}

View File

@ -0,0 +1,129 @@
// Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
// Code generated by client-gen. DO NOT EDIT.
package fake
import (
"context"
v1alpha1 "go.pinniped.dev/generated/1.18/apis/supervisor/idp/v1alpha1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
labels "k8s.io/apimachinery/pkg/labels"
schema "k8s.io/apimachinery/pkg/runtime/schema"
types "k8s.io/apimachinery/pkg/types"
watch "k8s.io/apimachinery/pkg/watch"
testing "k8s.io/client-go/testing"
)
// FakeLDAPIdentityProviders implements LDAPIdentityProviderInterface
type FakeLDAPIdentityProviders struct {
Fake *FakeIDPV1alpha1
ns string
}
var ldapidentityprovidersResource = schema.GroupVersionResource{Group: "idp.supervisor.pinniped.dev", Version: "v1alpha1", Resource: "ldapidentityproviders"}
var ldapidentityprovidersKind = schema.GroupVersionKind{Group: "idp.supervisor.pinniped.dev", Version: "v1alpha1", Kind: "LDAPIdentityProvider"}
// Get takes name of the lDAPIdentityProvider, and returns the corresponding lDAPIdentityProvider object, and an error if there is any.
func (c *FakeLDAPIdentityProviders) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.LDAPIdentityProvider, err error) {
obj, err := c.Fake.
Invokes(testing.NewGetAction(ldapidentityprovidersResource, c.ns, name), &v1alpha1.LDAPIdentityProvider{})
if obj == nil {
return nil, err
}
return obj.(*v1alpha1.LDAPIdentityProvider), err
}
// List takes label and field selectors, and returns the list of LDAPIdentityProviders that match those selectors.
func (c *FakeLDAPIdentityProviders) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.LDAPIdentityProviderList, err error) {
obj, err := c.Fake.
Invokes(testing.NewListAction(ldapidentityprovidersResource, ldapidentityprovidersKind, c.ns, opts), &v1alpha1.LDAPIdentityProviderList{})
if obj == nil {
return nil, err
}
label, _, _ := testing.ExtractFromListOptions(opts)
if label == nil {
label = labels.Everything()
}
list := &v1alpha1.LDAPIdentityProviderList{ListMeta: obj.(*v1alpha1.LDAPIdentityProviderList).ListMeta}
for _, item := range obj.(*v1alpha1.LDAPIdentityProviderList).Items {
if label.Matches(labels.Set(item.Labels)) {
list.Items = append(list.Items, item)
}
}
return list, err
}
// Watch returns a watch.Interface that watches the requested lDAPIdentityProviders.
func (c *FakeLDAPIdentityProviders) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) {
return c.Fake.
InvokesWatch(testing.NewWatchAction(ldapidentityprovidersResource, c.ns, opts))
}
// Create takes the representation of a lDAPIdentityProvider and creates it. Returns the server's representation of the lDAPIdentityProvider, and an error, if there is any.
func (c *FakeLDAPIdentityProviders) Create(ctx context.Context, lDAPIdentityProvider *v1alpha1.LDAPIdentityProvider, opts v1.CreateOptions) (result *v1alpha1.LDAPIdentityProvider, err error) {
obj, err := c.Fake.
Invokes(testing.NewCreateAction(ldapidentityprovidersResource, c.ns, lDAPIdentityProvider), &v1alpha1.LDAPIdentityProvider{})
if obj == nil {
return nil, err
}
return obj.(*v1alpha1.LDAPIdentityProvider), err
}
// Update takes the representation of a lDAPIdentityProvider and updates it. Returns the server's representation of the lDAPIdentityProvider, and an error, if there is any.
func (c *FakeLDAPIdentityProviders) Update(ctx context.Context, lDAPIdentityProvider *v1alpha1.LDAPIdentityProvider, opts v1.UpdateOptions) (result *v1alpha1.LDAPIdentityProvider, err error) {
obj, err := c.Fake.
Invokes(testing.NewUpdateAction(ldapidentityprovidersResource, c.ns, lDAPIdentityProvider), &v1alpha1.LDAPIdentityProvider{})
if obj == nil {
return nil, err
}
return obj.(*v1alpha1.LDAPIdentityProvider), err
}
// UpdateStatus was generated because the type contains a Status member.
// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus().
func (c *FakeLDAPIdentityProviders) UpdateStatus(ctx context.Context, lDAPIdentityProvider *v1alpha1.LDAPIdentityProvider, opts v1.UpdateOptions) (*v1alpha1.LDAPIdentityProvider, error) {
obj, err := c.Fake.
Invokes(testing.NewUpdateSubresourceAction(ldapidentityprovidersResource, "status", c.ns, lDAPIdentityProvider), &v1alpha1.LDAPIdentityProvider{})
if obj == nil {
return nil, err
}
return obj.(*v1alpha1.LDAPIdentityProvider), err
}
// Delete takes name of the lDAPIdentityProvider and deletes it. Returns an error if one occurs.
func (c *FakeLDAPIdentityProviders) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error {
_, err := c.Fake.
Invokes(testing.NewDeleteAction(ldapidentityprovidersResource, c.ns, name), &v1alpha1.LDAPIdentityProvider{})
return err
}
// DeleteCollection deletes a collection of objects.
func (c *FakeLDAPIdentityProviders) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error {
action := testing.NewDeleteCollectionAction(ldapidentityprovidersResource, c.ns, listOpts)
_, err := c.Fake.Invokes(action, &v1alpha1.LDAPIdentityProviderList{})
return err
}
// Patch applies the patch and returns the patched lDAPIdentityProvider.
func (c *FakeLDAPIdentityProviders) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.LDAPIdentityProvider, err error) {
obj, err := c.Fake.
Invokes(testing.NewPatchSubresourceAction(ldapidentityprovidersResource, c.ns, name, pt, data, subresources...), &v1alpha1.LDAPIdentityProvider{})
if obj == nil {
return nil, err
}
return obj.(*v1alpha1.LDAPIdentityProvider), err
}

View File

@ -5,4 +5,6 @@
package v1alpha1
type LDAPIdentityProviderExpansion interface{}
type OIDCIdentityProviderExpansion interface{}

View File

@ -13,6 +13,7 @@ import (
type IDPV1alpha1Interface interface {
RESTClient() rest.Interface
LDAPIdentityProvidersGetter
OIDCIdentityProvidersGetter
}
@ -21,6 +22,10 @@ type IDPV1alpha1Client struct {
restClient rest.Interface
}
func (c *IDPV1alpha1Client) LDAPIdentityProviders(namespace string) LDAPIdentityProviderInterface {
return newLDAPIdentityProviders(c, namespace)
}
func (c *IDPV1alpha1Client) OIDCIdentityProviders(namespace string) OIDCIdentityProviderInterface {
return newOIDCIdentityProviders(c, namespace)
}

View File

@ -0,0 +1,182 @@
// Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
// Code generated by client-gen. DO NOT EDIT.
package v1alpha1
import (
"context"
"time"
v1alpha1 "go.pinniped.dev/generated/1.18/apis/supervisor/idp/v1alpha1"
scheme "go.pinniped.dev/generated/1.18/client/supervisor/clientset/versioned/scheme"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
types "k8s.io/apimachinery/pkg/types"
watch "k8s.io/apimachinery/pkg/watch"
rest "k8s.io/client-go/rest"
)
// LDAPIdentityProvidersGetter has a method to return a LDAPIdentityProviderInterface.
// A group's client should implement this interface.
type LDAPIdentityProvidersGetter interface {
LDAPIdentityProviders(namespace string) LDAPIdentityProviderInterface
}
// LDAPIdentityProviderInterface has methods to work with LDAPIdentityProvider resources.
type LDAPIdentityProviderInterface interface {
Create(ctx context.Context, lDAPIdentityProvider *v1alpha1.LDAPIdentityProvider, opts v1.CreateOptions) (*v1alpha1.LDAPIdentityProvider, error)
Update(ctx context.Context, lDAPIdentityProvider *v1alpha1.LDAPIdentityProvider, opts v1.UpdateOptions) (*v1alpha1.LDAPIdentityProvider, error)
UpdateStatus(ctx context.Context, lDAPIdentityProvider *v1alpha1.LDAPIdentityProvider, opts v1.UpdateOptions) (*v1alpha1.LDAPIdentityProvider, error)
Delete(ctx context.Context, name string, opts v1.DeleteOptions) error
DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error
Get(ctx context.Context, name string, opts v1.GetOptions) (*v1alpha1.LDAPIdentityProvider, error)
List(ctx context.Context, opts v1.ListOptions) (*v1alpha1.LDAPIdentityProviderList, error)
Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error)
Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.LDAPIdentityProvider, err error)
LDAPIdentityProviderExpansion
}
// lDAPIdentityProviders implements LDAPIdentityProviderInterface
type lDAPIdentityProviders struct {
client rest.Interface
ns string
}
// newLDAPIdentityProviders returns a LDAPIdentityProviders
func newLDAPIdentityProviders(c *IDPV1alpha1Client, namespace string) *lDAPIdentityProviders {
return &lDAPIdentityProviders{
client: c.RESTClient(),
ns: namespace,
}
}
// Get takes name of the lDAPIdentityProvider, and returns the corresponding lDAPIdentityProvider object, and an error if there is any.
func (c *lDAPIdentityProviders) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.LDAPIdentityProvider, err error) {
result = &v1alpha1.LDAPIdentityProvider{}
err = c.client.Get().
Namespace(c.ns).
Resource("ldapidentityproviders").
Name(name).
VersionedParams(&options, scheme.ParameterCodec).
Do(ctx).
Into(result)
return
}
// List takes label and field selectors, and returns the list of LDAPIdentityProviders that match those selectors.
func (c *lDAPIdentityProviders) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.LDAPIdentityProviderList, err error) {
var timeout time.Duration
if opts.TimeoutSeconds != nil {
timeout = time.Duration(*opts.TimeoutSeconds) * time.Second
}
result = &v1alpha1.LDAPIdentityProviderList{}
err = c.client.Get().
Namespace(c.ns).
Resource("ldapidentityproviders").
VersionedParams(&opts, scheme.ParameterCodec).
Timeout(timeout).
Do(ctx).
Into(result)
return
}
// Watch returns a watch.Interface that watches the requested lDAPIdentityProviders.
func (c *lDAPIdentityProviders) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) {
var timeout time.Duration
if opts.TimeoutSeconds != nil {
timeout = time.Duration(*opts.TimeoutSeconds) * time.Second
}
opts.Watch = true
return c.client.Get().
Namespace(c.ns).
Resource("ldapidentityproviders").
VersionedParams(&opts, scheme.ParameterCodec).
Timeout(timeout).
Watch(ctx)
}
// Create takes the representation of a lDAPIdentityProvider and creates it. Returns the server's representation of the lDAPIdentityProvider, and an error, if there is any.
func (c *lDAPIdentityProviders) Create(ctx context.Context, lDAPIdentityProvider *v1alpha1.LDAPIdentityProvider, opts v1.CreateOptions) (result *v1alpha1.LDAPIdentityProvider, err error) {
result = &v1alpha1.LDAPIdentityProvider{}
err = c.client.Post().
Namespace(c.ns).
Resource("ldapidentityproviders").
VersionedParams(&opts, scheme.ParameterCodec).
Body(lDAPIdentityProvider).
Do(ctx).
Into(result)
return
}
// Update takes the representation of a lDAPIdentityProvider and updates it. Returns the server's representation of the lDAPIdentityProvider, and an error, if there is any.
func (c *lDAPIdentityProviders) Update(ctx context.Context, lDAPIdentityProvider *v1alpha1.LDAPIdentityProvider, opts v1.UpdateOptions) (result *v1alpha1.LDAPIdentityProvider, err error) {
result = &v1alpha1.LDAPIdentityProvider{}
err = c.client.Put().
Namespace(c.ns).
Resource("ldapidentityproviders").
Name(lDAPIdentityProvider.Name).
VersionedParams(&opts, scheme.ParameterCodec).
Body(lDAPIdentityProvider).
Do(ctx).
Into(result)
return
}
// UpdateStatus was generated because the type contains a Status member.
// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus().
func (c *lDAPIdentityProviders) UpdateStatus(ctx context.Context, lDAPIdentityProvider *v1alpha1.LDAPIdentityProvider, opts v1.UpdateOptions) (result *v1alpha1.LDAPIdentityProvider, err error) {
result = &v1alpha1.LDAPIdentityProvider{}
err = c.client.Put().
Namespace(c.ns).
Resource("ldapidentityproviders").
Name(lDAPIdentityProvider.Name).
SubResource("status").
VersionedParams(&opts, scheme.ParameterCodec).
Body(lDAPIdentityProvider).
Do(ctx).
Into(result)
return
}
// Delete takes name of the lDAPIdentityProvider and deletes it. Returns an error if one occurs.
func (c *lDAPIdentityProviders) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error {
return c.client.Delete().
Namespace(c.ns).
Resource("ldapidentityproviders").
Name(name).
Body(&opts).
Do(ctx).
Error()
}
// DeleteCollection deletes a collection of objects.
func (c *lDAPIdentityProviders) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error {
var timeout time.Duration
if listOpts.TimeoutSeconds != nil {
timeout = time.Duration(*listOpts.TimeoutSeconds) * time.Second
}
return c.client.Delete().
Namespace(c.ns).
Resource("ldapidentityproviders").
VersionedParams(&listOpts, scheme.ParameterCodec).
Timeout(timeout).
Body(&opts).
Do(ctx).
Error()
}
// Patch applies the patch and returns the patched lDAPIdentityProvider.
func (c *lDAPIdentityProviders) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.LDAPIdentityProvider, err error) {
result = &v1alpha1.LDAPIdentityProvider{}
err = c.client.Patch(pt).
Namespace(c.ns).
Resource("ldapidentityproviders").
Name(name).
SubResource(subresources...).
VersionedParams(&opts, scheme.ParameterCodec).
Body(data).
Do(ctx).
Into(result)
return
}

View File

@ -45,6 +45,8 @@ func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource
return &genericInformer{resource: resource.GroupResource(), informer: f.Config().V1alpha1().FederationDomains().Informer()}, nil
// Group=idp.supervisor.pinniped.dev, Version=v1alpha1
case idpv1alpha1.SchemeGroupVersion.WithResource("ldapidentityproviders"):
return &genericInformer{resource: resource.GroupResource(), informer: f.IDP().V1alpha1().LDAPIdentityProviders().Informer()}, nil
case idpv1alpha1.SchemeGroupVersion.WithResource("oidcidentityproviders"):
return &genericInformer{resource: resource.GroupResource(), informer: f.IDP().V1alpha1().OIDCIdentityProviders().Informer()}, nil

View File

@ -11,6 +11,8 @@ import (
// Interface provides access to all the informers in this group version.
type Interface interface {
// LDAPIdentityProviders returns a LDAPIdentityProviderInformer.
LDAPIdentityProviders() LDAPIdentityProviderInformer
// OIDCIdentityProviders returns a OIDCIdentityProviderInformer.
OIDCIdentityProviders() OIDCIdentityProviderInformer
}
@ -26,6 +28,11 @@ func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakList
return &version{factory: f, namespace: namespace, tweakListOptions: tweakListOptions}
}
// LDAPIdentityProviders returns a LDAPIdentityProviderInformer.
func (v *version) LDAPIdentityProviders() LDAPIdentityProviderInformer {
return &lDAPIdentityProviderInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions}
}
// OIDCIdentityProviders returns a OIDCIdentityProviderInformer.
func (v *version) OIDCIdentityProviders() OIDCIdentityProviderInformer {
return &oIDCIdentityProviderInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions}

View File

@ -0,0 +1,77 @@
// Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
// Code generated by informer-gen. DO NOT EDIT.
package v1alpha1
import (
"context"
time "time"
idpv1alpha1 "go.pinniped.dev/generated/1.18/apis/supervisor/idp/v1alpha1"
versioned "go.pinniped.dev/generated/1.18/client/supervisor/clientset/versioned"
internalinterfaces "go.pinniped.dev/generated/1.18/client/supervisor/informers/externalversions/internalinterfaces"
v1alpha1 "go.pinniped.dev/generated/1.18/client/supervisor/listers/idp/v1alpha1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
runtime "k8s.io/apimachinery/pkg/runtime"
watch "k8s.io/apimachinery/pkg/watch"
cache "k8s.io/client-go/tools/cache"
)
// LDAPIdentityProviderInformer provides access to a shared informer and lister for
// LDAPIdentityProviders.
type LDAPIdentityProviderInformer interface {
Informer() cache.SharedIndexInformer
Lister() v1alpha1.LDAPIdentityProviderLister
}
type lDAPIdentityProviderInformer struct {
factory internalinterfaces.SharedInformerFactory
tweakListOptions internalinterfaces.TweakListOptionsFunc
namespace string
}
// NewLDAPIdentityProviderInformer constructs a new informer for LDAPIdentityProvider type.
// Always prefer using an informer factory to get a shared informer instead of getting an independent
// one. This reduces memory footprint and number of connections to the server.
func NewLDAPIdentityProviderInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer {
return NewFilteredLDAPIdentityProviderInformer(client, namespace, resyncPeriod, indexers, nil)
}
// NewFilteredLDAPIdentityProviderInformer constructs a new informer for LDAPIdentityProvider type.
// Always prefer using an informer factory to get a shared informer instead of getting an independent
// one. This reduces memory footprint and number of connections to the server.
func NewFilteredLDAPIdentityProviderInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer {
return cache.NewSharedIndexInformer(
&cache.ListWatch{
ListFunc: func(options v1.ListOptions) (runtime.Object, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.IDPV1alpha1().LDAPIdentityProviders(namespace).List(context.TODO(), options)
},
WatchFunc: func(options v1.ListOptions) (watch.Interface, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.IDPV1alpha1().LDAPIdentityProviders(namespace).Watch(context.TODO(), options)
},
},
&idpv1alpha1.LDAPIdentityProvider{},
resyncPeriod,
indexers,
)
}
func (f *lDAPIdentityProviderInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer {
return NewFilteredLDAPIdentityProviderInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions)
}
func (f *lDAPIdentityProviderInformer) Informer() cache.SharedIndexInformer {
return f.factory.InformerFor(&idpv1alpha1.LDAPIdentityProvider{}, f.defaultInformer)
}
func (f *lDAPIdentityProviderInformer) Lister() v1alpha1.LDAPIdentityProviderLister {
return v1alpha1.NewLDAPIdentityProviderLister(f.Informer().GetIndexer())
}

View File

@ -5,6 +5,14 @@
package v1alpha1
// LDAPIdentityProviderListerExpansion allows custom methods to be added to
// LDAPIdentityProviderLister.
type LDAPIdentityProviderListerExpansion interface{}
// LDAPIdentityProviderNamespaceListerExpansion allows custom methods to be added to
// LDAPIdentityProviderNamespaceLister.
type LDAPIdentityProviderNamespaceListerExpansion interface{}
// OIDCIdentityProviderListerExpansion allows custom methods to be added to
// OIDCIdentityProviderLister.
type OIDCIdentityProviderListerExpansion interface{}

View File

@ -0,0 +1,81 @@
// Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
// Code generated by lister-gen. DO NOT EDIT.
package v1alpha1
import (
v1alpha1 "go.pinniped.dev/generated/1.18/apis/supervisor/idp/v1alpha1"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/client-go/tools/cache"
)
// LDAPIdentityProviderLister helps list LDAPIdentityProviders.
type LDAPIdentityProviderLister interface {
// List lists all LDAPIdentityProviders in the indexer.
List(selector labels.Selector) (ret []*v1alpha1.LDAPIdentityProvider, err error)
// LDAPIdentityProviders returns an object that can list and get LDAPIdentityProviders.
LDAPIdentityProviders(namespace string) LDAPIdentityProviderNamespaceLister
LDAPIdentityProviderListerExpansion
}
// lDAPIdentityProviderLister implements the LDAPIdentityProviderLister interface.
type lDAPIdentityProviderLister struct {
indexer cache.Indexer
}
// NewLDAPIdentityProviderLister returns a new LDAPIdentityProviderLister.
func NewLDAPIdentityProviderLister(indexer cache.Indexer) LDAPIdentityProviderLister {
return &lDAPIdentityProviderLister{indexer: indexer}
}
// List lists all LDAPIdentityProviders in the indexer.
func (s *lDAPIdentityProviderLister) List(selector labels.Selector) (ret []*v1alpha1.LDAPIdentityProvider, err error) {
err = cache.ListAll(s.indexer, selector, func(m interface{}) {
ret = append(ret, m.(*v1alpha1.LDAPIdentityProvider))
})
return ret, err
}
// LDAPIdentityProviders returns an object that can list and get LDAPIdentityProviders.
func (s *lDAPIdentityProviderLister) LDAPIdentityProviders(namespace string) LDAPIdentityProviderNamespaceLister {
return lDAPIdentityProviderNamespaceLister{indexer: s.indexer, namespace: namespace}
}
// LDAPIdentityProviderNamespaceLister helps list and get LDAPIdentityProviders.
type LDAPIdentityProviderNamespaceLister interface {
// List lists all LDAPIdentityProviders in the indexer for a given namespace.
List(selector labels.Selector) (ret []*v1alpha1.LDAPIdentityProvider, err error)
// Get retrieves the LDAPIdentityProvider from the indexer for a given namespace and name.
Get(name string) (*v1alpha1.LDAPIdentityProvider, error)
LDAPIdentityProviderNamespaceListerExpansion
}
// lDAPIdentityProviderNamespaceLister implements the LDAPIdentityProviderNamespaceLister
// interface.
type lDAPIdentityProviderNamespaceLister struct {
indexer cache.Indexer
namespace string
}
// List lists all LDAPIdentityProviders in the indexer for a given namespace.
func (s lDAPIdentityProviderNamespaceLister) List(selector labels.Selector) (ret []*v1alpha1.LDAPIdentityProvider, err error) {
err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) {
ret = append(ret, m.(*v1alpha1.LDAPIdentityProvider))
})
return ret, err
}
// Get retrieves the LDAPIdentityProvider from the indexer for a given namespace and name.
func (s lDAPIdentityProviderNamespaceLister) Get(name string) (*v1alpha1.LDAPIdentityProvider, error) {
obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name)
if err != nil {
return nil, err
}
if !exists {
return nil, errors.NewNotFound(v1alpha1.Resource("ldapidentityprovider"), name)
}
return obj.(*v1alpha1.LDAPIdentityProvider), nil
}

View File

@ -0,0 +1,234 @@
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.4.0
creationTimestamp: null
name: ldapidentityproviders.idp.supervisor.pinniped.dev
spec:
group: idp.supervisor.pinniped.dev
names:
categories:
- pinniped
- pinniped-idp
- pinniped-idps
kind: LDAPIdentityProvider
listKind: LDAPIdentityProviderList
plural: ldapidentityproviders
singular: ldapidentityprovider
scope: Namespaced
versions:
- additionalPrinterColumns:
- jsonPath: .spec.host
name: Host
type: string
- jsonPath: .status.phase
name: Status
type: string
- jsonPath: .metadata.creationTimestamp
name: Age
type: date
name: v1alpha1
schema:
openAPIV3Schema:
description: LDAPIdentityProvider describes the configuration of an upstream
Lightweight Directory Access Protocol (LDAP) identity provider.
properties:
apiVersion:
description: 'APIVersion defines the versioned schema of this representation
of an object. Servers should convert recognized schemas to the latest
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
type: string
kind:
description: 'Kind is a string value representing the REST resource this
object represents. Servers may infer this from the endpoint the client
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
type: string
metadata:
type: object
spec:
description: Spec for configuring the identity provider.
properties:
bind:
description: Bind contains the configuration for how to provide access
credentials during an initial bind to the LDAP server to be allowed
to perform searches and binds to validate a user's credentials during
a user's authentication attempt.
properties:
secretName:
description: SecretName contains the name of a namespace-local
Secret object that provides the username and password for an
LDAP bind user. This account will be used to perform LDAP searches.
The Secret should be of type "kubernetes.io/basic-auth" which
includes "username" and "password" keys. The username value
should be the full DN of your bind account, e.g. "cn=bind-account,ou=users,dc=example,dc=com".
The password must be non-empty.
minLength: 1
type: string
required:
- secretName
type: object
host:
description: 'Host is the hostname of this LDAP identity provider,
i.e., where to connect. For example: ldap.example.com:636.'
minLength: 1
type: string
tls:
description: TLS contains the connection settings for how to establish
the connection to the Host.
properties:
certificateAuthorityData:
description: X.509 Certificate Authority (base64-encoded PEM bundle).
If omitted, a default set of system roots will be trusted.
type: string
type: object
userSearch:
description: UserSearch contains the configuration for searching for
a user by name in the LDAP provider.
properties:
attributes:
description: Attributes specifies how the user's information should
be read from the LDAP entry which was found as the result of
the user search.
properties:
uid:
description: UID specifies the name of the attribute in the
LDAP entry which whose value shall be used to uniquely identify
the user within this LDAP provider after a successful authentication.
E.g. "uidNumber" or "objectGUID". The value of this field
is case-sensitive and must match the case of the attribute
name returned by the LDAP server in the user's entry. Distinguished
names can be used by specifying lower-case "dn".
minLength: 1
type: string
username:
description: Username specifies the name of attribute in the
LDAP entry which whose value shall become the username of
the user after a successful authentication. This would typically
be the same attribute name used in the user search filter,
although it can be different. E.g. "mail" or "uid" or "userPrincipalName".
The value of this field is case-sensitive and must match
the case of the attribute name returned by the LDAP server
in the user's entry. Distinguished names can be used by
specifying lower-case "dn". When this field is set to "dn"
then the LDAPIdentityProviderUserSearch's Filter field cannot
be blank, since the default value of "dn={}" would not work.
minLength: 1
type: string
type: object
base:
description: Base is the DN that should be used as the search
base when searching for users. E.g. "ou=users,dc=example,dc=com".
minLength: 1
type: string
filter:
description: Filter is the LDAP search filter which should be
applied when searching for users. The pattern "{}" must occur
in the filter and will be dynamically replaced by the username
for which the search is being run. E.g. "mail={}" or "&(objectClass=person)(uid={})".
For more information about LDAP filters, see https://ldap.com/ldap-filters.
Note that the dn (distinguished name) is not an attribute of
an entry, so "dn={}" cannot be used. Optional. When not specified,
the default will act as if the Filter were specified as the
value from Attributes.Username appended by "={}". When the Attributes.Username
is set to "dn" then the Filter must be explicitly specified,
since the default value of "dn={}" would not work.
type: string
type: object
required:
- host
type: object
status:
description: Status of the identity provider.
properties:
conditions:
description: Represents the observations of an identity provider's
current state.
items:
description: Condition status of a resource (mirrored from the metav1.Condition
type added in Kubernetes 1.19). In a future API version we can
switch to using the upstream type. See https://github.com/kubernetes/apimachinery/blob/v0.19.0/pkg/apis/meta/v1/types.go#L1353-L1413.
properties:
lastTransitionTime:
description: lastTransitionTime is the last time the condition
transitioned from one status to another. This should be when
the underlying condition changed. If that is not known, then
using the time when the API field changed is acceptable.
format: date-time
type: string
message:
description: message is a human readable message indicating
details about the transition. This may be an empty string.
maxLength: 32768
type: string
observedGeneration:
description: observedGeneration represents the .metadata.generation
that the condition was set based upon. For instance, if .metadata.generation
is currently 12, but the .status.conditions[x].observedGeneration
is 9, the condition is out of date with respect to the current
state of the instance.
format: int64
minimum: 0
type: integer
reason:
description: reason contains a programmatic identifier indicating
the reason for the condition's last transition. Producers
of specific condition types may define expected values and
meanings for this field, and whether the values are considered
a guaranteed API. The value should be a CamelCase string.
This field may not be empty.
maxLength: 1024
minLength: 1
pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$
type: string
status:
description: status of the condition, one of True, False, Unknown.
enum:
- "True"
- "False"
- Unknown
type: string
type:
description: type of condition in CamelCase or in foo.example.com/CamelCase.
--- Many .condition.type values are consistent across resources
like Available, but because arbitrary conditions can be useful
(see .node.status.conditions), the ability to deconflict is
important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt)
maxLength: 316
pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
type: string
required:
- lastTransitionTime
- message
- reason
- status
- type
type: object
type: array
x-kubernetes-list-map-keys:
- type
x-kubernetes-list-type: map
phase:
default: Pending
description: Phase summarizes the overall status of the LDAPIdentityProvider.
enum:
- Pending
- Ready
- Error
type: string
type: object
required:
- spec
type: object
served: true
storage: true
subresources:
status: {}
status:
acceptedNames:
kind: ""
plural: ""
conditions: []
storedVersions: []

View File

@ -669,10 +669,11 @@ Package v1alpha1 is the v1alpha1 version of the Pinniped supervisor identity pro
[id="{anchor_prefix}-go-pinniped-dev-generated-1-19-apis-supervisor-idp-v1alpha1-condition"]
==== Condition
Condition status of a resource (mirrored from the metav1.Condition type added in Kubernetes 1.19). In a future API version we can switch to using the upstream type. See https://github.com/kubernetes/apimachinery/blob/v0.19.0/pkg/apis/meta/v1/types.go#L1353-L1413.
.Appears In:
****
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-19-apis-supervisor-idp-v1alpha1-ldapidentityproviderstatus[$$LDAPIdentityProviderStatus$$]
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-19-apis-supervisor-idp-v1alpha1-oidcidentityproviderstatus[$$OIDCIdentityProviderStatus$$]
****
@ -688,6 +689,132 @@ Condition status of a resource (mirrored from the metav1.Condition type added in
|===
[id="{anchor_prefix}-go-pinniped-dev-generated-1-19-apis-supervisor-idp-v1alpha1-conditionstatus"]
==== ConditionStatus (string)
.Appears In:
****
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-19-apis-supervisor-idp-v1alpha1-condition[$$Condition$$]
****
[id="{anchor_prefix}-go-pinniped-dev-generated-1-19-apis-supervisor-idp-v1alpha1-ldapidentityprovider"]
==== LDAPIdentityProvider
LDAPIdentityProvider describes the configuration of an upstream Lightweight Directory Access Protocol (LDAP) identity provider.
.Appears In:
****
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-19-apis-supervisor-idp-v1alpha1-ldapidentityproviderlist[$$LDAPIdentityProviderList$$]
****
[cols="25a,75a", options="header"]
|===
| Field | Description
| *`metadata`* __link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.19/#objectmeta-v1-meta[$$ObjectMeta$$]__ | Refer to Kubernetes API documentation for fields of `metadata`.
| *`spec`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-19-apis-supervisor-idp-v1alpha1-ldapidentityproviderspec[$$LDAPIdentityProviderSpec$$]__ | Spec for configuring the identity provider.
| *`status`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-19-apis-supervisor-idp-v1alpha1-ldapidentityproviderstatus[$$LDAPIdentityProviderStatus$$]__ | Status of the identity provider.
|===
[id="{anchor_prefix}-go-pinniped-dev-generated-1-19-apis-supervisor-idp-v1alpha1-ldapidentityproviderbind"]
==== LDAPIdentityProviderBind
.Appears In:
****
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-19-apis-supervisor-idp-v1alpha1-ldapidentityproviderspec[$$LDAPIdentityProviderSpec$$]
****
[cols="25a,75a", options="header"]
|===
| Field | Description
| *`secretName`* __string__ | SecretName contains the name of a namespace-local Secret object that provides the username and password for an LDAP bind user. This account will be used to perform LDAP searches. The Secret should be of type "kubernetes.io/basic-auth" which includes "username" and "password" keys. The username value should be the full DN of your bind account, e.g. "cn=bind-account,ou=users,dc=example,dc=com". The password must be non-empty.
|===
[id="{anchor_prefix}-go-pinniped-dev-generated-1-19-apis-supervisor-idp-v1alpha1-ldapidentityproviderspec"]
==== LDAPIdentityProviderSpec
Spec for configuring an LDAP identity provider.
.Appears In:
****
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-19-apis-supervisor-idp-v1alpha1-ldapidentityprovider[$$LDAPIdentityProvider$$]
****
[cols="25a,75a", options="header"]
|===
| Field | Description
| *`host`* __string__ | Host is the hostname of this LDAP identity provider, i.e., where to connect. For example: ldap.example.com:636.
| *`tls`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-19-apis-supervisor-idp-v1alpha1-tlsspec[$$TLSSpec$$]__ | TLS contains the connection settings for how to establish the connection to the Host.
| *`bind`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-19-apis-supervisor-idp-v1alpha1-ldapidentityproviderbind[$$LDAPIdentityProviderBind$$]__ | Bind contains the configuration for how to provide access credentials during an initial bind to the LDAP server to be allowed to perform searches and binds to validate a user's credentials during a user's authentication attempt.
| *`userSearch`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-19-apis-supervisor-idp-v1alpha1-ldapidentityproviderusersearch[$$LDAPIdentityProviderUserSearch$$]__ | UserSearch contains the configuration for searching for a user by name in the LDAP provider.
|===
[id="{anchor_prefix}-go-pinniped-dev-generated-1-19-apis-supervisor-idp-v1alpha1-ldapidentityproviderstatus"]
==== LDAPIdentityProviderStatus
Status of an LDAP identity provider.
.Appears In:
****
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-19-apis-supervisor-idp-v1alpha1-ldapidentityprovider[$$LDAPIdentityProvider$$]
****
[cols="25a,75a", options="header"]
|===
| Field | Description
| *`phase`* __LDAPIdentityProviderPhase__ | Phase summarizes the overall status of the LDAPIdentityProvider.
| *`conditions`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-19-apis-supervisor-idp-v1alpha1-condition[$$Condition$$] array__ | Represents the observations of an identity provider's current state.
|===
[id="{anchor_prefix}-go-pinniped-dev-generated-1-19-apis-supervisor-idp-v1alpha1-ldapidentityproviderusersearch"]
==== LDAPIdentityProviderUserSearch
.Appears In:
****
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-19-apis-supervisor-idp-v1alpha1-ldapidentityproviderspec[$$LDAPIdentityProviderSpec$$]
****
[cols="25a,75a", options="header"]
|===
| Field | Description
| *`base`* __string__ | Base is the DN that should be used as the search base when searching for users. E.g. "ou=users,dc=example,dc=com".
| *`filter`* __string__ | Filter is the LDAP search filter which should be applied when searching for users. The pattern "{}" must occur in the filter and will be dynamically replaced by the username for which the search is being run. E.g. "mail={}" or "&(objectClass=person)(uid={})". For more information about LDAP filters, see https://ldap.com/ldap-filters. Note that the dn (distinguished name) is not an attribute of an entry, so "dn={}" cannot be used. Optional. When not specified, the default will act as if the Filter were specified as the value from Attributes.Username appended by "={}". When the Attributes.Username is set to "dn" then the Filter must be explicitly specified, since the default value of "dn={}" would not work.
| *`attributes`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-19-apis-supervisor-idp-v1alpha1-ldapidentityproviderusersearchattributes[$$LDAPIdentityProviderUserSearchAttributes$$]__ | Attributes specifies how the user's information should be read from the LDAP entry which was found as the result of the user search.
|===
[id="{anchor_prefix}-go-pinniped-dev-generated-1-19-apis-supervisor-idp-v1alpha1-ldapidentityproviderusersearchattributes"]
==== LDAPIdentityProviderUserSearchAttributes
.Appears In:
****
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-19-apis-supervisor-idp-v1alpha1-ldapidentityproviderusersearch[$$LDAPIdentityProviderUserSearch$$]
****
[cols="25a,75a", options="header"]
|===
| Field | Description
| *`username`* __string__ | Username specifies the name of attribute in the LDAP entry which whose value shall become the username of the user after a successful authentication. This would typically be the same attribute name used in the user search filter, although it can be different. E.g. "mail" or "uid" or "userPrincipalName". The value of this field is case-sensitive and must match the case of the attribute name returned by the LDAP server in the user's entry. Distinguished names can be used by specifying lower-case "dn". When this field is set to "dn" then the LDAPIdentityProviderUserSearch's Filter field cannot be blank, since the default value of "dn={}" would not work.
| *`uid`* __string__ | UID specifies the name of the attribute in the LDAP entry which whose value shall be used to uniquely identify the user within this LDAP provider after a successful authentication. E.g. "uidNumber" or "objectGUID". The value of this field is case-sensitive and must match the case of the attribute name returned by the LDAP server in the user's entry. Distinguished names can be used by specifying lower-case "dn".
|===
[id="{anchor_prefix}-go-pinniped-dev-generated-1-19-apis-supervisor-idp-v1alpha1-oidcauthorizationconfig"]
==== OIDCAuthorizationConfig
@ -797,7 +924,7 @@ Status of an OIDC identity provider.
|===
| Field | Description
| *`phase`* __OIDCIdentityProviderPhase__ | Phase summarizes the overall status of the OIDCIdentityProvider.
| *`conditions`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-19-apis-supervisor-idp-v1alpha1-condition[$$Condition$$]__ | Represents the observations of an identity provider's current state.
| *`conditions`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-19-apis-supervisor-idp-v1alpha1-condition[$$Condition$$] array__ | Represents the observations of an identity provider's current state.
|===
@ -808,6 +935,7 @@ Status of an OIDC identity provider.
.Appears In:
****
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-19-apis-supervisor-idp-v1alpha1-ldapidentityproviderspec[$$LDAPIdentityProviderSpec$$]
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-19-apis-supervisor-idp-v1alpha1-oidcidentityproviderspec[$$OIDCIdentityProviderSpec$$]
****

View File

@ -32,6 +32,8 @@ func addKnownTypes(scheme *runtime.Scheme) error {
scheme.AddKnownTypes(SchemeGroupVersion,
&OIDCIdentityProvider{},
&OIDCIdentityProviderList{},
&LDAPIdentityProvider{},
&LDAPIdentityProviderList{},
)
metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
return nil

View File

@ -0,0 +1,132 @@
// Copyright 2021 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package v1alpha1
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
type LDAPIdentityProviderPhase string
const (
// LDAPPhasePending is the default phase for newly-created LDAPIdentityProvider resources.
LDAPPhasePending LDAPIdentityProviderPhase = "Pending"
// LDAPPhaseReady is the phase for an LDAPIdentityProvider resource in a healthy state.
LDAPPhaseReady LDAPIdentityProviderPhase = "Ready"
// LDAPPhaseError is the phase for an LDAPIdentityProvider in an unhealthy state.
LDAPPhaseError LDAPIdentityProviderPhase = "Error"
)
// Status of an LDAP identity provider.
type LDAPIdentityProviderStatus struct {
// Phase summarizes the overall status of the LDAPIdentityProvider.
// +kubebuilder:default=Pending
// +kubebuilder:validation:Enum=Pending;Ready;Error
Phase LDAPIdentityProviderPhase `json:"phase,omitempty"`
// Represents the observations of an identity provider's current state.
// +patchMergeKey=type
// +patchStrategy=merge
// +listType=map
// +listMapKey=type
Conditions []Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"`
}
type LDAPIdentityProviderBind struct {
// SecretName contains the name of a namespace-local Secret object that provides the username and
// password for an LDAP bind user. This account will be used to perform LDAP searches. The Secret should be
// of type "kubernetes.io/basic-auth" which includes "username" and "password" keys. The username value
// should be the full DN of your bind account, e.g. "cn=bind-account,ou=users,dc=example,dc=com".
// The password must be non-empty.
// +kubebuilder:validation:MinLength=1
SecretName string `json:"secretName"`
}
type LDAPIdentityProviderUserSearchAttributes struct {
// Username specifies the name of attribute in the LDAP entry which whose value shall become the username
// of the user after a successful authentication. This would typically be the same attribute name used in
// the user search filter, although it can be different. E.g. "mail" or "uid" or "userPrincipalName".
// The value of this field is case-sensitive and must match the case of the attribute name returned by the LDAP
// server in the user's entry. Distinguished names can be used by specifying lower-case "dn". When this field
// is set to "dn" then the LDAPIdentityProviderUserSearch's Filter field cannot be blank, since the default
// value of "dn={}" would not work.
// +kubebuilder:validation:MinLength=1
Username string `json:"username,omitempty"`
// UID specifies the name of the attribute in the LDAP entry which whose value shall be used to uniquely
// identify the user within this LDAP provider after a successful authentication. E.g. "uidNumber" or "objectGUID".
// The value of this field is case-sensitive and must match the case of the attribute name returned by the LDAP
// server in the user's entry. Distinguished names can be used by specifying lower-case "dn".
// +kubebuilder:validation:MinLength=1
UID string `json:"uid,omitempty"`
}
type LDAPIdentityProviderUserSearch struct {
// Base is the DN that should be used as the search base when searching for users. E.g. "ou=users,dc=example,dc=com".
// +kubebuilder:validation:MinLength=1
Base string `json:"base,omitempty"`
// Filter is the LDAP search filter which should be applied when searching for users. The pattern "{}" must occur
// in the filter and will be dynamically replaced by the username for which the search is being run. E.g. "mail={}"
// or "&(objectClass=person)(uid={})". For more information about LDAP filters, see https://ldap.com/ldap-filters.
// Note that the dn (distinguished name) is not an attribute of an entry, so "dn={}" cannot be used.
// Optional. When not specified, the default will act as if the Filter were specified as the value from
// Attributes.Username appended by "={}". When the Attributes.Username is set to "dn" then the Filter must be
// explicitly specified, since the default value of "dn={}" would not work.
// +optional
Filter string `json:"filter,omitempty"`
// Attributes specifies how the user's information should be read from the LDAP entry which was found as
// the result of the user search.
// +optional
Attributes LDAPIdentityProviderUserSearchAttributes `json:"attributes,omitempty"`
}
// Spec for configuring an LDAP identity provider.
type LDAPIdentityProviderSpec struct {
// Host is the hostname of this LDAP identity provider, i.e., where to connect. For example: ldap.example.com:636.
// +kubebuilder:validation:MinLength=1
Host string `json:"host"`
// TLS contains the connection settings for how to establish the connection to the Host.
TLS *TLSSpec `json:"tls,omitempty"`
// Bind contains the configuration for how to provide access credentials during an initial bind to the LDAP server
// to be allowed to perform searches and binds to validate a user's credentials during a user's authentication attempt.
Bind LDAPIdentityProviderBind `json:"bind,omitempty"`
// UserSearch contains the configuration for searching for a user by name in the LDAP provider.
UserSearch LDAPIdentityProviderUserSearch `json:"userSearch,omitempty"`
}
// LDAPIdentityProvider describes the configuration of an upstream Lightweight Directory Access
// Protocol (LDAP) identity provider.
// +genclient
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// +kubebuilder:resource:categories=pinniped;pinniped-idp;pinniped-idps
// +kubebuilder:printcolumn:name="Host",type=string,JSONPath=`.spec.host`
// +kubebuilder:printcolumn:name="Status",type=string,JSONPath=`.status.phase`
// +kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp`
// +kubebuilder:subresource:status
type LDAPIdentityProvider struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
// Spec for configuring the identity provider.
Spec LDAPIdentityProviderSpec `json:"spec"`
// Status of the identity provider.
Status LDAPIdentityProviderStatus `json:"status,omitempty"`
}
// List of LDAPIdentityProvider objects.
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
type LDAPIdentityProviderList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []LDAPIdentityProvider `json:"items"`
}

View File

@ -28,6 +28,162 @@ func (in *Condition) DeepCopy() *Condition {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *LDAPIdentityProvider) DeepCopyInto(out *LDAPIdentityProvider) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
in.Spec.DeepCopyInto(&out.Spec)
in.Status.DeepCopyInto(&out.Status)
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LDAPIdentityProvider.
func (in *LDAPIdentityProvider) DeepCopy() *LDAPIdentityProvider {
if in == nil {
return nil
}
out := new(LDAPIdentityProvider)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *LDAPIdentityProvider) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *LDAPIdentityProviderBind) DeepCopyInto(out *LDAPIdentityProviderBind) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LDAPIdentityProviderBind.
func (in *LDAPIdentityProviderBind) DeepCopy() *LDAPIdentityProviderBind {
if in == nil {
return nil
}
out := new(LDAPIdentityProviderBind)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *LDAPIdentityProviderList) DeepCopyInto(out *LDAPIdentityProviderList) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ListMeta.DeepCopyInto(&out.ListMeta)
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]LDAPIdentityProvider, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LDAPIdentityProviderList.
func (in *LDAPIdentityProviderList) DeepCopy() *LDAPIdentityProviderList {
if in == nil {
return nil
}
out := new(LDAPIdentityProviderList)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *LDAPIdentityProviderList) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *LDAPIdentityProviderSpec) DeepCopyInto(out *LDAPIdentityProviderSpec) {
*out = *in
if in.TLS != nil {
in, out := &in.TLS, &out.TLS
*out = new(TLSSpec)
**out = **in
}
out.Bind = in.Bind
out.UserSearch = in.UserSearch
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LDAPIdentityProviderSpec.
func (in *LDAPIdentityProviderSpec) DeepCopy() *LDAPIdentityProviderSpec {
if in == nil {
return nil
}
out := new(LDAPIdentityProviderSpec)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *LDAPIdentityProviderStatus) DeepCopyInto(out *LDAPIdentityProviderStatus) {
*out = *in
if in.Conditions != nil {
in, out := &in.Conditions, &out.Conditions
*out = make([]Condition, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LDAPIdentityProviderStatus.
func (in *LDAPIdentityProviderStatus) DeepCopy() *LDAPIdentityProviderStatus {
if in == nil {
return nil
}
out := new(LDAPIdentityProviderStatus)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *LDAPIdentityProviderUserSearch) DeepCopyInto(out *LDAPIdentityProviderUserSearch) {
*out = *in
out.Attributes = in.Attributes
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LDAPIdentityProviderUserSearch.
func (in *LDAPIdentityProviderUserSearch) DeepCopy() *LDAPIdentityProviderUserSearch {
if in == nil {
return nil
}
out := new(LDAPIdentityProviderUserSearch)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *LDAPIdentityProviderUserSearchAttributes) DeepCopyInto(out *LDAPIdentityProviderUserSearchAttributes) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LDAPIdentityProviderUserSearchAttributes.
func (in *LDAPIdentityProviderUserSearchAttributes) DeepCopy() *LDAPIdentityProviderUserSearchAttributes {
if in == nil {
return nil
}
out := new(LDAPIdentityProviderUserSearchAttributes)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *OIDCAuthorizationConfig) DeepCopyInto(out *OIDCAuthorizationConfig) {
*out = *in

View File

@ -15,6 +15,10 @@ type FakeIDPV1alpha1 struct {
*testing.Fake
}
func (c *FakeIDPV1alpha1) LDAPIdentityProviders(namespace string) v1alpha1.LDAPIdentityProviderInterface {
return &FakeLDAPIdentityProviders{c, namespace}
}
func (c *FakeIDPV1alpha1) OIDCIdentityProviders(namespace string) v1alpha1.OIDCIdentityProviderInterface {
return &FakeOIDCIdentityProviders{c, namespace}
}

View File

@ -0,0 +1,129 @@
// Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
// Code generated by client-gen. DO NOT EDIT.
package fake
import (
"context"
v1alpha1 "go.pinniped.dev/generated/1.19/apis/supervisor/idp/v1alpha1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
labels "k8s.io/apimachinery/pkg/labels"
schema "k8s.io/apimachinery/pkg/runtime/schema"
types "k8s.io/apimachinery/pkg/types"
watch "k8s.io/apimachinery/pkg/watch"
testing "k8s.io/client-go/testing"
)
// FakeLDAPIdentityProviders implements LDAPIdentityProviderInterface
type FakeLDAPIdentityProviders struct {
Fake *FakeIDPV1alpha1
ns string
}
var ldapidentityprovidersResource = schema.GroupVersionResource{Group: "idp.supervisor.pinniped.dev", Version: "v1alpha1", Resource: "ldapidentityproviders"}
var ldapidentityprovidersKind = schema.GroupVersionKind{Group: "idp.supervisor.pinniped.dev", Version: "v1alpha1", Kind: "LDAPIdentityProvider"}
// Get takes name of the lDAPIdentityProvider, and returns the corresponding lDAPIdentityProvider object, and an error if there is any.
func (c *FakeLDAPIdentityProviders) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.LDAPIdentityProvider, err error) {
obj, err := c.Fake.
Invokes(testing.NewGetAction(ldapidentityprovidersResource, c.ns, name), &v1alpha1.LDAPIdentityProvider{})
if obj == nil {
return nil, err
}
return obj.(*v1alpha1.LDAPIdentityProvider), err
}
// List takes label and field selectors, and returns the list of LDAPIdentityProviders that match those selectors.
func (c *FakeLDAPIdentityProviders) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.LDAPIdentityProviderList, err error) {
obj, err := c.Fake.
Invokes(testing.NewListAction(ldapidentityprovidersResource, ldapidentityprovidersKind, c.ns, opts), &v1alpha1.LDAPIdentityProviderList{})
if obj == nil {
return nil, err
}
label, _, _ := testing.ExtractFromListOptions(opts)
if label == nil {
label = labels.Everything()
}
list := &v1alpha1.LDAPIdentityProviderList{ListMeta: obj.(*v1alpha1.LDAPIdentityProviderList).ListMeta}
for _, item := range obj.(*v1alpha1.LDAPIdentityProviderList).Items {
if label.Matches(labels.Set(item.Labels)) {
list.Items = append(list.Items, item)
}
}
return list, err
}
// Watch returns a watch.Interface that watches the requested lDAPIdentityProviders.
func (c *FakeLDAPIdentityProviders) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) {
return c.Fake.
InvokesWatch(testing.NewWatchAction(ldapidentityprovidersResource, c.ns, opts))
}
// Create takes the representation of a lDAPIdentityProvider and creates it. Returns the server's representation of the lDAPIdentityProvider, and an error, if there is any.
func (c *FakeLDAPIdentityProviders) Create(ctx context.Context, lDAPIdentityProvider *v1alpha1.LDAPIdentityProvider, opts v1.CreateOptions) (result *v1alpha1.LDAPIdentityProvider, err error) {
obj, err := c.Fake.
Invokes(testing.NewCreateAction(ldapidentityprovidersResource, c.ns, lDAPIdentityProvider), &v1alpha1.LDAPIdentityProvider{})
if obj == nil {
return nil, err
}
return obj.(*v1alpha1.LDAPIdentityProvider), err
}
// Update takes the representation of a lDAPIdentityProvider and updates it. Returns the server's representation of the lDAPIdentityProvider, and an error, if there is any.
func (c *FakeLDAPIdentityProviders) Update(ctx context.Context, lDAPIdentityProvider *v1alpha1.LDAPIdentityProvider, opts v1.UpdateOptions) (result *v1alpha1.LDAPIdentityProvider, err error) {
obj, err := c.Fake.
Invokes(testing.NewUpdateAction(ldapidentityprovidersResource, c.ns, lDAPIdentityProvider), &v1alpha1.LDAPIdentityProvider{})
if obj == nil {
return nil, err
}
return obj.(*v1alpha1.LDAPIdentityProvider), err
}
// UpdateStatus was generated because the type contains a Status member.
// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus().
func (c *FakeLDAPIdentityProviders) UpdateStatus(ctx context.Context, lDAPIdentityProvider *v1alpha1.LDAPIdentityProvider, opts v1.UpdateOptions) (*v1alpha1.LDAPIdentityProvider, error) {
obj, err := c.Fake.
Invokes(testing.NewUpdateSubresourceAction(ldapidentityprovidersResource, "status", c.ns, lDAPIdentityProvider), &v1alpha1.LDAPIdentityProvider{})
if obj == nil {
return nil, err
}
return obj.(*v1alpha1.LDAPIdentityProvider), err
}
// Delete takes name of the lDAPIdentityProvider and deletes it. Returns an error if one occurs.
func (c *FakeLDAPIdentityProviders) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error {
_, err := c.Fake.
Invokes(testing.NewDeleteAction(ldapidentityprovidersResource, c.ns, name), &v1alpha1.LDAPIdentityProvider{})
return err
}
// DeleteCollection deletes a collection of objects.
func (c *FakeLDAPIdentityProviders) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error {
action := testing.NewDeleteCollectionAction(ldapidentityprovidersResource, c.ns, listOpts)
_, err := c.Fake.Invokes(action, &v1alpha1.LDAPIdentityProviderList{})
return err
}
// Patch applies the patch and returns the patched lDAPIdentityProvider.
func (c *FakeLDAPIdentityProviders) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.LDAPIdentityProvider, err error) {
obj, err := c.Fake.
Invokes(testing.NewPatchSubresourceAction(ldapidentityprovidersResource, c.ns, name, pt, data, subresources...), &v1alpha1.LDAPIdentityProvider{})
if obj == nil {
return nil, err
}
return obj.(*v1alpha1.LDAPIdentityProvider), err
}

View File

@ -5,4 +5,6 @@
package v1alpha1
type LDAPIdentityProviderExpansion interface{}
type OIDCIdentityProviderExpansion interface{}

View File

@ -13,6 +13,7 @@ import (
type IDPV1alpha1Interface interface {
RESTClient() rest.Interface
LDAPIdentityProvidersGetter
OIDCIdentityProvidersGetter
}
@ -21,6 +22,10 @@ type IDPV1alpha1Client struct {
restClient rest.Interface
}
func (c *IDPV1alpha1Client) LDAPIdentityProviders(namespace string) LDAPIdentityProviderInterface {
return newLDAPIdentityProviders(c, namespace)
}
func (c *IDPV1alpha1Client) OIDCIdentityProviders(namespace string) OIDCIdentityProviderInterface {
return newOIDCIdentityProviders(c, namespace)
}

View File

@ -0,0 +1,182 @@
// Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
// Code generated by client-gen. DO NOT EDIT.
package v1alpha1
import (
"context"
"time"
v1alpha1 "go.pinniped.dev/generated/1.19/apis/supervisor/idp/v1alpha1"
scheme "go.pinniped.dev/generated/1.19/client/supervisor/clientset/versioned/scheme"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
types "k8s.io/apimachinery/pkg/types"
watch "k8s.io/apimachinery/pkg/watch"
rest "k8s.io/client-go/rest"
)
// LDAPIdentityProvidersGetter has a method to return a LDAPIdentityProviderInterface.
// A group's client should implement this interface.
type LDAPIdentityProvidersGetter interface {
LDAPIdentityProviders(namespace string) LDAPIdentityProviderInterface
}
// LDAPIdentityProviderInterface has methods to work with LDAPIdentityProvider resources.
type LDAPIdentityProviderInterface interface {
Create(ctx context.Context, lDAPIdentityProvider *v1alpha1.LDAPIdentityProvider, opts v1.CreateOptions) (*v1alpha1.LDAPIdentityProvider, error)
Update(ctx context.Context, lDAPIdentityProvider *v1alpha1.LDAPIdentityProvider, opts v1.UpdateOptions) (*v1alpha1.LDAPIdentityProvider, error)
UpdateStatus(ctx context.Context, lDAPIdentityProvider *v1alpha1.LDAPIdentityProvider, opts v1.UpdateOptions) (*v1alpha1.LDAPIdentityProvider, error)
Delete(ctx context.Context, name string, opts v1.DeleteOptions) error
DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error
Get(ctx context.Context, name string, opts v1.GetOptions) (*v1alpha1.LDAPIdentityProvider, error)
List(ctx context.Context, opts v1.ListOptions) (*v1alpha1.LDAPIdentityProviderList, error)
Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error)
Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.LDAPIdentityProvider, err error)
LDAPIdentityProviderExpansion
}
// lDAPIdentityProviders implements LDAPIdentityProviderInterface
type lDAPIdentityProviders struct {
client rest.Interface
ns string
}
// newLDAPIdentityProviders returns a LDAPIdentityProviders
func newLDAPIdentityProviders(c *IDPV1alpha1Client, namespace string) *lDAPIdentityProviders {
return &lDAPIdentityProviders{
client: c.RESTClient(),
ns: namespace,
}
}
// Get takes name of the lDAPIdentityProvider, and returns the corresponding lDAPIdentityProvider object, and an error if there is any.
func (c *lDAPIdentityProviders) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.LDAPIdentityProvider, err error) {
result = &v1alpha1.LDAPIdentityProvider{}
err = c.client.Get().
Namespace(c.ns).
Resource("ldapidentityproviders").
Name(name).
VersionedParams(&options, scheme.ParameterCodec).
Do(ctx).
Into(result)
return
}
// List takes label and field selectors, and returns the list of LDAPIdentityProviders that match those selectors.
func (c *lDAPIdentityProviders) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.LDAPIdentityProviderList, err error) {
var timeout time.Duration
if opts.TimeoutSeconds != nil {
timeout = time.Duration(*opts.TimeoutSeconds) * time.Second
}
result = &v1alpha1.LDAPIdentityProviderList{}
err = c.client.Get().
Namespace(c.ns).
Resource("ldapidentityproviders").
VersionedParams(&opts, scheme.ParameterCodec).
Timeout(timeout).
Do(ctx).
Into(result)
return
}
// Watch returns a watch.Interface that watches the requested lDAPIdentityProviders.
func (c *lDAPIdentityProviders) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) {
var timeout time.Duration
if opts.TimeoutSeconds != nil {
timeout = time.Duration(*opts.TimeoutSeconds) * time.Second
}
opts.Watch = true
return c.client.Get().
Namespace(c.ns).
Resource("ldapidentityproviders").
VersionedParams(&opts, scheme.ParameterCodec).
Timeout(timeout).
Watch(ctx)
}
// Create takes the representation of a lDAPIdentityProvider and creates it. Returns the server's representation of the lDAPIdentityProvider, and an error, if there is any.
func (c *lDAPIdentityProviders) Create(ctx context.Context, lDAPIdentityProvider *v1alpha1.LDAPIdentityProvider, opts v1.CreateOptions) (result *v1alpha1.LDAPIdentityProvider, err error) {
result = &v1alpha1.LDAPIdentityProvider{}
err = c.client.Post().
Namespace(c.ns).
Resource("ldapidentityproviders").
VersionedParams(&opts, scheme.ParameterCodec).
Body(lDAPIdentityProvider).
Do(ctx).
Into(result)
return
}
// Update takes the representation of a lDAPIdentityProvider and updates it. Returns the server's representation of the lDAPIdentityProvider, and an error, if there is any.
func (c *lDAPIdentityProviders) Update(ctx context.Context, lDAPIdentityProvider *v1alpha1.LDAPIdentityProvider, opts v1.UpdateOptions) (result *v1alpha1.LDAPIdentityProvider, err error) {
result = &v1alpha1.LDAPIdentityProvider{}
err = c.client.Put().
Namespace(c.ns).
Resource("ldapidentityproviders").
Name(lDAPIdentityProvider.Name).
VersionedParams(&opts, scheme.ParameterCodec).
Body(lDAPIdentityProvider).
Do(ctx).
Into(result)
return
}
// UpdateStatus was generated because the type contains a Status member.
// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus().
func (c *lDAPIdentityProviders) UpdateStatus(ctx context.Context, lDAPIdentityProvider *v1alpha1.LDAPIdentityProvider, opts v1.UpdateOptions) (result *v1alpha1.LDAPIdentityProvider, err error) {
result = &v1alpha1.LDAPIdentityProvider{}
err = c.client.Put().
Namespace(c.ns).
Resource("ldapidentityproviders").
Name(lDAPIdentityProvider.Name).
SubResource("status").
VersionedParams(&opts, scheme.ParameterCodec).
Body(lDAPIdentityProvider).
Do(ctx).
Into(result)
return
}
// Delete takes name of the lDAPIdentityProvider and deletes it. Returns an error if one occurs.
func (c *lDAPIdentityProviders) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error {
return c.client.Delete().
Namespace(c.ns).
Resource("ldapidentityproviders").
Name(name).
Body(&opts).
Do(ctx).
Error()
}
// DeleteCollection deletes a collection of objects.
func (c *lDAPIdentityProviders) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error {
var timeout time.Duration
if listOpts.TimeoutSeconds != nil {
timeout = time.Duration(*listOpts.TimeoutSeconds) * time.Second
}
return c.client.Delete().
Namespace(c.ns).
Resource("ldapidentityproviders").
VersionedParams(&listOpts, scheme.ParameterCodec).
Timeout(timeout).
Body(&opts).
Do(ctx).
Error()
}
// Patch applies the patch and returns the patched lDAPIdentityProvider.
func (c *lDAPIdentityProviders) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.LDAPIdentityProvider, err error) {
result = &v1alpha1.LDAPIdentityProvider{}
err = c.client.Patch(pt).
Namespace(c.ns).
Resource("ldapidentityproviders").
Name(name).
SubResource(subresources...).
VersionedParams(&opts, scheme.ParameterCodec).
Body(data).
Do(ctx).
Into(result)
return
}

View File

@ -45,6 +45,8 @@ func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource
return &genericInformer{resource: resource.GroupResource(), informer: f.Config().V1alpha1().FederationDomains().Informer()}, nil
// Group=idp.supervisor.pinniped.dev, Version=v1alpha1
case idpv1alpha1.SchemeGroupVersion.WithResource("ldapidentityproviders"):
return &genericInformer{resource: resource.GroupResource(), informer: f.IDP().V1alpha1().LDAPIdentityProviders().Informer()}, nil
case idpv1alpha1.SchemeGroupVersion.WithResource("oidcidentityproviders"):
return &genericInformer{resource: resource.GroupResource(), informer: f.IDP().V1alpha1().OIDCIdentityProviders().Informer()}, nil

View File

@ -11,6 +11,8 @@ import (
// Interface provides access to all the informers in this group version.
type Interface interface {
// LDAPIdentityProviders returns a LDAPIdentityProviderInformer.
LDAPIdentityProviders() LDAPIdentityProviderInformer
// OIDCIdentityProviders returns a OIDCIdentityProviderInformer.
OIDCIdentityProviders() OIDCIdentityProviderInformer
}
@ -26,6 +28,11 @@ func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakList
return &version{factory: f, namespace: namespace, tweakListOptions: tweakListOptions}
}
// LDAPIdentityProviders returns a LDAPIdentityProviderInformer.
func (v *version) LDAPIdentityProviders() LDAPIdentityProviderInformer {
return &lDAPIdentityProviderInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions}
}
// OIDCIdentityProviders returns a OIDCIdentityProviderInformer.
func (v *version) OIDCIdentityProviders() OIDCIdentityProviderInformer {
return &oIDCIdentityProviderInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions}

View File

@ -0,0 +1,77 @@
// Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
// Code generated by informer-gen. DO NOT EDIT.
package v1alpha1
import (
"context"
time "time"
idpv1alpha1 "go.pinniped.dev/generated/1.19/apis/supervisor/idp/v1alpha1"
versioned "go.pinniped.dev/generated/1.19/client/supervisor/clientset/versioned"
internalinterfaces "go.pinniped.dev/generated/1.19/client/supervisor/informers/externalversions/internalinterfaces"
v1alpha1 "go.pinniped.dev/generated/1.19/client/supervisor/listers/idp/v1alpha1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
runtime "k8s.io/apimachinery/pkg/runtime"
watch "k8s.io/apimachinery/pkg/watch"
cache "k8s.io/client-go/tools/cache"
)
// LDAPIdentityProviderInformer provides access to a shared informer and lister for
// LDAPIdentityProviders.
type LDAPIdentityProviderInformer interface {
Informer() cache.SharedIndexInformer
Lister() v1alpha1.LDAPIdentityProviderLister
}
type lDAPIdentityProviderInformer struct {
factory internalinterfaces.SharedInformerFactory
tweakListOptions internalinterfaces.TweakListOptionsFunc
namespace string
}
// NewLDAPIdentityProviderInformer constructs a new informer for LDAPIdentityProvider type.
// Always prefer using an informer factory to get a shared informer instead of getting an independent
// one. This reduces memory footprint and number of connections to the server.
func NewLDAPIdentityProviderInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer {
return NewFilteredLDAPIdentityProviderInformer(client, namespace, resyncPeriod, indexers, nil)
}
// NewFilteredLDAPIdentityProviderInformer constructs a new informer for LDAPIdentityProvider type.
// Always prefer using an informer factory to get a shared informer instead of getting an independent
// one. This reduces memory footprint and number of connections to the server.
func NewFilteredLDAPIdentityProviderInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer {
return cache.NewSharedIndexInformer(
&cache.ListWatch{
ListFunc: func(options v1.ListOptions) (runtime.Object, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.IDPV1alpha1().LDAPIdentityProviders(namespace).List(context.TODO(), options)
},
WatchFunc: func(options v1.ListOptions) (watch.Interface, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.IDPV1alpha1().LDAPIdentityProviders(namespace).Watch(context.TODO(), options)
},
},
&idpv1alpha1.LDAPIdentityProvider{},
resyncPeriod,
indexers,
)
}
func (f *lDAPIdentityProviderInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer {
return NewFilteredLDAPIdentityProviderInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions)
}
func (f *lDAPIdentityProviderInformer) Informer() cache.SharedIndexInformer {
return f.factory.InformerFor(&idpv1alpha1.LDAPIdentityProvider{}, f.defaultInformer)
}
func (f *lDAPIdentityProviderInformer) Lister() v1alpha1.LDAPIdentityProviderLister {
return v1alpha1.NewLDAPIdentityProviderLister(f.Informer().GetIndexer())
}

View File

@ -5,6 +5,14 @@
package v1alpha1
// LDAPIdentityProviderListerExpansion allows custom methods to be added to
// LDAPIdentityProviderLister.
type LDAPIdentityProviderListerExpansion interface{}
// LDAPIdentityProviderNamespaceListerExpansion allows custom methods to be added to
// LDAPIdentityProviderNamespaceLister.
type LDAPIdentityProviderNamespaceListerExpansion interface{}
// OIDCIdentityProviderListerExpansion allows custom methods to be added to
// OIDCIdentityProviderLister.
type OIDCIdentityProviderListerExpansion interface{}

View File

@ -0,0 +1,86 @@
// Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
// Code generated by lister-gen. DO NOT EDIT.
package v1alpha1
import (
v1alpha1 "go.pinniped.dev/generated/1.19/apis/supervisor/idp/v1alpha1"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/client-go/tools/cache"
)
// LDAPIdentityProviderLister helps list LDAPIdentityProviders.
// All objects returned here must be treated as read-only.
type LDAPIdentityProviderLister interface {
// List lists all LDAPIdentityProviders in the indexer.
// Objects returned here must be treated as read-only.
List(selector labels.Selector) (ret []*v1alpha1.LDAPIdentityProvider, err error)
// LDAPIdentityProviders returns an object that can list and get LDAPIdentityProviders.
LDAPIdentityProviders(namespace string) LDAPIdentityProviderNamespaceLister
LDAPIdentityProviderListerExpansion
}
// lDAPIdentityProviderLister implements the LDAPIdentityProviderLister interface.
type lDAPIdentityProviderLister struct {
indexer cache.Indexer
}
// NewLDAPIdentityProviderLister returns a new LDAPIdentityProviderLister.
func NewLDAPIdentityProviderLister(indexer cache.Indexer) LDAPIdentityProviderLister {
return &lDAPIdentityProviderLister{indexer: indexer}
}
// List lists all LDAPIdentityProviders in the indexer.
func (s *lDAPIdentityProviderLister) List(selector labels.Selector) (ret []*v1alpha1.LDAPIdentityProvider, err error) {
err = cache.ListAll(s.indexer, selector, func(m interface{}) {
ret = append(ret, m.(*v1alpha1.LDAPIdentityProvider))
})
return ret, err
}
// LDAPIdentityProviders returns an object that can list and get LDAPIdentityProviders.
func (s *lDAPIdentityProviderLister) LDAPIdentityProviders(namespace string) LDAPIdentityProviderNamespaceLister {
return lDAPIdentityProviderNamespaceLister{indexer: s.indexer, namespace: namespace}
}
// LDAPIdentityProviderNamespaceLister helps list and get LDAPIdentityProviders.
// All objects returned here must be treated as read-only.
type LDAPIdentityProviderNamespaceLister interface {
// List lists all LDAPIdentityProviders in the indexer for a given namespace.
// Objects returned here must be treated as read-only.
List(selector labels.Selector) (ret []*v1alpha1.LDAPIdentityProvider, err error)
// Get retrieves the LDAPIdentityProvider from the indexer for a given namespace and name.
// Objects returned here must be treated as read-only.
Get(name string) (*v1alpha1.LDAPIdentityProvider, error)
LDAPIdentityProviderNamespaceListerExpansion
}
// lDAPIdentityProviderNamespaceLister implements the LDAPIdentityProviderNamespaceLister
// interface.
type lDAPIdentityProviderNamespaceLister struct {
indexer cache.Indexer
namespace string
}
// List lists all LDAPIdentityProviders in the indexer for a given namespace.
func (s lDAPIdentityProviderNamespaceLister) List(selector labels.Selector) (ret []*v1alpha1.LDAPIdentityProvider, err error) {
err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) {
ret = append(ret, m.(*v1alpha1.LDAPIdentityProvider))
})
return ret, err
}
// Get retrieves the LDAPIdentityProvider from the indexer for a given namespace and name.
func (s lDAPIdentityProviderNamespaceLister) Get(name string) (*v1alpha1.LDAPIdentityProvider, error) {
obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name)
if err != nil {
return nil, err
}
if !exists {
return nil, errors.NewNotFound(v1alpha1.Resource("ldapidentityprovider"), name)
}
return obj.(*v1alpha1.LDAPIdentityProvider), nil
}

View File

@ -0,0 +1,234 @@
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.4.0
creationTimestamp: null
name: ldapidentityproviders.idp.supervisor.pinniped.dev
spec:
group: idp.supervisor.pinniped.dev
names:
categories:
- pinniped
- pinniped-idp
- pinniped-idps
kind: LDAPIdentityProvider
listKind: LDAPIdentityProviderList
plural: ldapidentityproviders
singular: ldapidentityprovider
scope: Namespaced
versions:
- additionalPrinterColumns:
- jsonPath: .spec.host
name: Host
type: string
- jsonPath: .status.phase
name: Status
type: string
- jsonPath: .metadata.creationTimestamp
name: Age
type: date
name: v1alpha1
schema:
openAPIV3Schema:
description: LDAPIdentityProvider describes the configuration of an upstream
Lightweight Directory Access Protocol (LDAP) identity provider.
properties:
apiVersion:
description: 'APIVersion defines the versioned schema of this representation
of an object. Servers should convert recognized schemas to the latest
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
type: string
kind:
description: 'Kind is a string value representing the REST resource this
object represents. Servers may infer this from the endpoint the client
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
type: string
metadata:
type: object
spec:
description: Spec for configuring the identity provider.
properties:
bind:
description: Bind contains the configuration for how to provide access
credentials during an initial bind to the LDAP server to be allowed
to perform searches and binds to validate a user's credentials during
a user's authentication attempt.
properties:
secretName:
description: SecretName contains the name of a namespace-local
Secret object that provides the username and password for an
LDAP bind user. This account will be used to perform LDAP searches.
The Secret should be of type "kubernetes.io/basic-auth" which
includes "username" and "password" keys. The username value
should be the full DN of your bind account, e.g. "cn=bind-account,ou=users,dc=example,dc=com".
The password must be non-empty.
minLength: 1
type: string
required:
- secretName
type: object
host:
description: 'Host is the hostname of this LDAP identity provider,
i.e., where to connect. For example: ldap.example.com:636.'
minLength: 1
type: string
tls:
description: TLS contains the connection settings for how to establish
the connection to the Host.
properties:
certificateAuthorityData:
description: X.509 Certificate Authority (base64-encoded PEM bundle).
If omitted, a default set of system roots will be trusted.
type: string
type: object
userSearch:
description: UserSearch contains the configuration for searching for
a user by name in the LDAP provider.
properties:
attributes:
description: Attributes specifies how the user's information should
be read from the LDAP entry which was found as the result of
the user search.
properties:
uid:
description: UID specifies the name of the attribute in the
LDAP entry which whose value shall be used to uniquely identify
the user within this LDAP provider after a successful authentication.
E.g. "uidNumber" or "objectGUID". The value of this field
is case-sensitive and must match the case of the attribute
name returned by the LDAP server in the user's entry. Distinguished
names can be used by specifying lower-case "dn".
minLength: 1
type: string
username:
description: Username specifies the name of attribute in the
LDAP entry which whose value shall become the username of
the user after a successful authentication. This would typically
be the same attribute name used in the user search filter,
although it can be different. E.g. "mail" or "uid" or "userPrincipalName".
The value of this field is case-sensitive and must match
the case of the attribute name returned by the LDAP server
in the user's entry. Distinguished names can be used by
specifying lower-case "dn". When this field is set to "dn"
then the LDAPIdentityProviderUserSearch's Filter field cannot
be blank, since the default value of "dn={}" would not work.
minLength: 1
type: string
type: object
base:
description: Base is the DN that should be used as the search
base when searching for users. E.g. "ou=users,dc=example,dc=com".
minLength: 1
type: string
filter:
description: Filter is the LDAP search filter which should be
applied when searching for users. The pattern "{}" must occur
in the filter and will be dynamically replaced by the username
for which the search is being run. E.g. "mail={}" or "&(objectClass=person)(uid={})".
For more information about LDAP filters, see https://ldap.com/ldap-filters.
Note that the dn (distinguished name) is not an attribute of
an entry, so "dn={}" cannot be used. Optional. When not specified,
the default will act as if the Filter were specified as the
value from Attributes.Username appended by "={}". When the Attributes.Username
is set to "dn" then the Filter must be explicitly specified,
since the default value of "dn={}" would not work.
type: string
type: object
required:
- host
type: object
status:
description: Status of the identity provider.
properties:
conditions:
description: Represents the observations of an identity provider's
current state.
items:
description: Condition status of a resource (mirrored from the metav1.Condition
type added in Kubernetes 1.19). In a future API version we can
switch to using the upstream type. See https://github.com/kubernetes/apimachinery/blob/v0.19.0/pkg/apis/meta/v1/types.go#L1353-L1413.
properties:
lastTransitionTime:
description: lastTransitionTime is the last time the condition
transitioned from one status to another. This should be when
the underlying condition changed. If that is not known, then
using the time when the API field changed is acceptable.
format: date-time
type: string
message:
description: message is a human readable message indicating
details about the transition. This may be an empty string.
maxLength: 32768
type: string
observedGeneration:
description: observedGeneration represents the .metadata.generation
that the condition was set based upon. For instance, if .metadata.generation
is currently 12, but the .status.conditions[x].observedGeneration
is 9, the condition is out of date with respect to the current
state of the instance.
format: int64
minimum: 0
type: integer
reason:
description: reason contains a programmatic identifier indicating
the reason for the condition's last transition. Producers
of specific condition types may define expected values and
meanings for this field, and whether the values are considered
a guaranteed API. The value should be a CamelCase string.
This field may not be empty.
maxLength: 1024
minLength: 1
pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$
type: string
status:
description: status of the condition, one of True, False, Unknown.
enum:
- "True"
- "False"
- Unknown
type: string
type:
description: type of condition in CamelCase or in foo.example.com/CamelCase.
--- Many .condition.type values are consistent across resources
like Available, but because arbitrary conditions can be useful
(see .node.status.conditions), the ability to deconflict is
important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt)
maxLength: 316
pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
type: string
required:
- lastTransitionTime
- message
- reason
- status
- type
type: object
type: array
x-kubernetes-list-map-keys:
- type
x-kubernetes-list-type: map
phase:
default: Pending
description: Phase summarizes the overall status of the LDAPIdentityProvider.
enum:
- Pending
- Ready
- Error
type: string
type: object
required:
- spec
type: object
served: true
storage: true
subresources:
status: {}
status:
acceptedNames:
kind: ""
plural: ""
conditions: []
storedVersions: []

View File

@ -669,10 +669,11 @@ Package v1alpha1 is the v1alpha1 version of the Pinniped supervisor identity pro
[id="{anchor_prefix}-go-pinniped-dev-generated-1-20-apis-supervisor-idp-v1alpha1-condition"]
==== Condition
Condition status of a resource (mirrored from the metav1.Condition type added in Kubernetes 1.19). In a future API version we can switch to using the upstream type. See https://github.com/kubernetes/apimachinery/blob/v0.19.0/pkg/apis/meta/v1/types.go#L1353-L1413.
.Appears In:
****
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-20-apis-supervisor-idp-v1alpha1-ldapidentityproviderstatus[$$LDAPIdentityProviderStatus$$]
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-20-apis-supervisor-idp-v1alpha1-oidcidentityproviderstatus[$$OIDCIdentityProviderStatus$$]
****
@ -688,6 +689,132 @@ Condition status of a resource (mirrored from the metav1.Condition type added in
|===
[id="{anchor_prefix}-go-pinniped-dev-generated-1-20-apis-supervisor-idp-v1alpha1-conditionstatus"]
==== ConditionStatus (string)
.Appears In:
****
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-20-apis-supervisor-idp-v1alpha1-condition[$$Condition$$]
****
[id="{anchor_prefix}-go-pinniped-dev-generated-1-20-apis-supervisor-idp-v1alpha1-ldapidentityprovider"]
==== LDAPIdentityProvider
LDAPIdentityProvider describes the configuration of an upstream Lightweight Directory Access Protocol (LDAP) identity provider.
.Appears In:
****
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-20-apis-supervisor-idp-v1alpha1-ldapidentityproviderlist[$$LDAPIdentityProviderList$$]
****
[cols="25a,75a", options="header"]
|===
| Field | Description
| *`metadata`* __link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.2/#objectmeta-v1-meta[$$ObjectMeta$$]__ | Refer to Kubernetes API documentation for fields of `metadata`.
| *`spec`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-20-apis-supervisor-idp-v1alpha1-ldapidentityproviderspec[$$LDAPIdentityProviderSpec$$]__ | Spec for configuring the identity provider.
| *`status`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-20-apis-supervisor-idp-v1alpha1-ldapidentityproviderstatus[$$LDAPIdentityProviderStatus$$]__ | Status of the identity provider.
|===
[id="{anchor_prefix}-go-pinniped-dev-generated-1-20-apis-supervisor-idp-v1alpha1-ldapidentityproviderbind"]
==== LDAPIdentityProviderBind
.Appears In:
****
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-20-apis-supervisor-idp-v1alpha1-ldapidentityproviderspec[$$LDAPIdentityProviderSpec$$]
****
[cols="25a,75a", options="header"]
|===
| Field | Description
| *`secretName`* __string__ | SecretName contains the name of a namespace-local Secret object that provides the username and password for an LDAP bind user. This account will be used to perform LDAP searches. The Secret should be of type "kubernetes.io/basic-auth" which includes "username" and "password" keys. The username value should be the full DN of your bind account, e.g. "cn=bind-account,ou=users,dc=example,dc=com". The password must be non-empty.
|===
[id="{anchor_prefix}-go-pinniped-dev-generated-1-20-apis-supervisor-idp-v1alpha1-ldapidentityproviderspec"]
==== LDAPIdentityProviderSpec
Spec for configuring an LDAP identity provider.
.Appears In:
****
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-20-apis-supervisor-idp-v1alpha1-ldapidentityprovider[$$LDAPIdentityProvider$$]
****
[cols="25a,75a", options="header"]
|===
| Field | Description
| *`host`* __string__ | Host is the hostname of this LDAP identity provider, i.e., where to connect. For example: ldap.example.com:636.
| *`tls`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-20-apis-supervisor-idp-v1alpha1-tlsspec[$$TLSSpec$$]__ | TLS contains the connection settings for how to establish the connection to the Host.
| *`bind`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-20-apis-supervisor-idp-v1alpha1-ldapidentityproviderbind[$$LDAPIdentityProviderBind$$]__ | Bind contains the configuration for how to provide access credentials during an initial bind to the LDAP server to be allowed to perform searches and binds to validate a user's credentials during a user's authentication attempt.
| *`userSearch`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-20-apis-supervisor-idp-v1alpha1-ldapidentityproviderusersearch[$$LDAPIdentityProviderUserSearch$$]__ | UserSearch contains the configuration for searching for a user by name in the LDAP provider.
|===
[id="{anchor_prefix}-go-pinniped-dev-generated-1-20-apis-supervisor-idp-v1alpha1-ldapidentityproviderstatus"]
==== LDAPIdentityProviderStatus
Status of an LDAP identity provider.
.Appears In:
****
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-20-apis-supervisor-idp-v1alpha1-ldapidentityprovider[$$LDAPIdentityProvider$$]
****
[cols="25a,75a", options="header"]
|===
| Field | Description
| *`phase`* __LDAPIdentityProviderPhase__ | Phase summarizes the overall status of the LDAPIdentityProvider.
| *`conditions`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-20-apis-supervisor-idp-v1alpha1-condition[$$Condition$$] array__ | Represents the observations of an identity provider's current state.
|===
[id="{anchor_prefix}-go-pinniped-dev-generated-1-20-apis-supervisor-idp-v1alpha1-ldapidentityproviderusersearch"]
==== LDAPIdentityProviderUserSearch
.Appears In:
****
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-20-apis-supervisor-idp-v1alpha1-ldapidentityproviderspec[$$LDAPIdentityProviderSpec$$]
****
[cols="25a,75a", options="header"]
|===
| Field | Description
| *`base`* __string__ | Base is the DN that should be used as the search base when searching for users. E.g. "ou=users,dc=example,dc=com".
| *`filter`* __string__ | Filter is the LDAP search filter which should be applied when searching for users. The pattern "{}" must occur in the filter and will be dynamically replaced by the username for which the search is being run. E.g. "mail={}" or "&(objectClass=person)(uid={})". For more information about LDAP filters, see https://ldap.com/ldap-filters. Note that the dn (distinguished name) is not an attribute of an entry, so "dn={}" cannot be used. Optional. When not specified, the default will act as if the Filter were specified as the value from Attributes.Username appended by "={}". When the Attributes.Username is set to "dn" then the Filter must be explicitly specified, since the default value of "dn={}" would not work.
| *`attributes`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-20-apis-supervisor-idp-v1alpha1-ldapidentityproviderusersearchattributes[$$LDAPIdentityProviderUserSearchAttributes$$]__ | Attributes specifies how the user's information should be read from the LDAP entry which was found as the result of the user search.
|===
[id="{anchor_prefix}-go-pinniped-dev-generated-1-20-apis-supervisor-idp-v1alpha1-ldapidentityproviderusersearchattributes"]
==== LDAPIdentityProviderUserSearchAttributes
.Appears In:
****
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-20-apis-supervisor-idp-v1alpha1-ldapidentityproviderusersearch[$$LDAPIdentityProviderUserSearch$$]
****
[cols="25a,75a", options="header"]
|===
| Field | Description
| *`username`* __string__ | Username specifies the name of attribute in the LDAP entry which whose value shall become the username of the user after a successful authentication. This would typically be the same attribute name used in the user search filter, although it can be different. E.g. "mail" or "uid" or "userPrincipalName". The value of this field is case-sensitive and must match the case of the attribute name returned by the LDAP server in the user's entry. Distinguished names can be used by specifying lower-case "dn". When this field is set to "dn" then the LDAPIdentityProviderUserSearch's Filter field cannot be blank, since the default value of "dn={}" would not work.
| *`uid`* __string__ | UID specifies the name of the attribute in the LDAP entry which whose value shall be used to uniquely identify the user within this LDAP provider after a successful authentication. E.g. "uidNumber" or "objectGUID". The value of this field is case-sensitive and must match the case of the attribute name returned by the LDAP server in the user's entry. Distinguished names can be used by specifying lower-case "dn".
|===
[id="{anchor_prefix}-go-pinniped-dev-generated-1-20-apis-supervisor-idp-v1alpha1-oidcauthorizationconfig"]
==== OIDCAuthorizationConfig
@ -797,7 +924,7 @@ Status of an OIDC identity provider.
|===
| Field | Description
| *`phase`* __OIDCIdentityProviderPhase__ | Phase summarizes the overall status of the OIDCIdentityProvider.
| *`conditions`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-20-apis-supervisor-idp-v1alpha1-condition[$$Condition$$]__ | Represents the observations of an identity provider's current state.
| *`conditions`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-20-apis-supervisor-idp-v1alpha1-condition[$$Condition$$] array__ | Represents the observations of an identity provider's current state.
|===
@ -808,6 +935,7 @@ Status of an OIDC identity provider.
.Appears In:
****
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-20-apis-supervisor-idp-v1alpha1-ldapidentityproviderspec[$$LDAPIdentityProviderSpec$$]
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-20-apis-supervisor-idp-v1alpha1-oidcidentityproviderspec[$$OIDCIdentityProviderSpec$$]
****

View File

@ -32,6 +32,8 @@ func addKnownTypes(scheme *runtime.Scheme) error {
scheme.AddKnownTypes(SchemeGroupVersion,
&OIDCIdentityProvider{},
&OIDCIdentityProviderList{},
&LDAPIdentityProvider{},
&LDAPIdentityProviderList{},
)
metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
return nil

View File

@ -0,0 +1,132 @@
// Copyright 2021 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package v1alpha1
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
type LDAPIdentityProviderPhase string
const (
// LDAPPhasePending is the default phase for newly-created LDAPIdentityProvider resources.
LDAPPhasePending LDAPIdentityProviderPhase = "Pending"
// LDAPPhaseReady is the phase for an LDAPIdentityProvider resource in a healthy state.
LDAPPhaseReady LDAPIdentityProviderPhase = "Ready"
// LDAPPhaseError is the phase for an LDAPIdentityProvider in an unhealthy state.
LDAPPhaseError LDAPIdentityProviderPhase = "Error"
)
// Status of an LDAP identity provider.
type LDAPIdentityProviderStatus struct {
// Phase summarizes the overall status of the LDAPIdentityProvider.
// +kubebuilder:default=Pending
// +kubebuilder:validation:Enum=Pending;Ready;Error
Phase LDAPIdentityProviderPhase `json:"phase,omitempty"`
// Represents the observations of an identity provider's current state.
// +patchMergeKey=type
// +patchStrategy=merge
// +listType=map
// +listMapKey=type
Conditions []Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"`
}
type LDAPIdentityProviderBind struct {
// SecretName contains the name of a namespace-local Secret object that provides the username and
// password for an LDAP bind user. This account will be used to perform LDAP searches. The Secret should be
// of type "kubernetes.io/basic-auth" which includes "username" and "password" keys. The username value
// should be the full DN of your bind account, e.g. "cn=bind-account,ou=users,dc=example,dc=com".
// The password must be non-empty.
// +kubebuilder:validation:MinLength=1
SecretName string `json:"secretName"`
}
type LDAPIdentityProviderUserSearchAttributes struct {
// Username specifies the name of attribute in the LDAP entry which whose value shall become the username
// of the user after a successful authentication. This would typically be the same attribute name used in
// the user search filter, although it can be different. E.g. "mail" or "uid" or "userPrincipalName".
// The value of this field is case-sensitive and must match the case of the attribute name returned by the LDAP
// server in the user's entry. Distinguished names can be used by specifying lower-case "dn". When this field
// is set to "dn" then the LDAPIdentityProviderUserSearch's Filter field cannot be blank, since the default
// value of "dn={}" would not work.
// +kubebuilder:validation:MinLength=1
Username string `json:"username,omitempty"`
// UID specifies the name of the attribute in the LDAP entry which whose value shall be used to uniquely
// identify the user within this LDAP provider after a successful authentication. E.g. "uidNumber" or "objectGUID".
// The value of this field is case-sensitive and must match the case of the attribute name returned by the LDAP
// server in the user's entry. Distinguished names can be used by specifying lower-case "dn".
// +kubebuilder:validation:MinLength=1
UID string `json:"uid,omitempty"`
}
type LDAPIdentityProviderUserSearch struct {
// Base is the DN that should be used as the search base when searching for users. E.g. "ou=users,dc=example,dc=com".
// +kubebuilder:validation:MinLength=1
Base string `json:"base,omitempty"`
// Filter is the LDAP search filter which should be applied when searching for users. The pattern "{}" must occur
// in the filter and will be dynamically replaced by the username for which the search is being run. E.g. "mail={}"
// or "&(objectClass=person)(uid={})". For more information about LDAP filters, see https://ldap.com/ldap-filters.
// Note that the dn (distinguished name) is not an attribute of an entry, so "dn={}" cannot be used.
// Optional. When not specified, the default will act as if the Filter were specified as the value from
// Attributes.Username appended by "={}". When the Attributes.Username is set to "dn" then the Filter must be
// explicitly specified, since the default value of "dn={}" would not work.
// +optional
Filter string `json:"filter,omitempty"`
// Attributes specifies how the user's information should be read from the LDAP entry which was found as
// the result of the user search.
// +optional
Attributes LDAPIdentityProviderUserSearchAttributes `json:"attributes,omitempty"`
}
// Spec for configuring an LDAP identity provider.
type LDAPIdentityProviderSpec struct {
// Host is the hostname of this LDAP identity provider, i.e., where to connect. For example: ldap.example.com:636.
// +kubebuilder:validation:MinLength=1
Host string `json:"host"`
// TLS contains the connection settings for how to establish the connection to the Host.
TLS *TLSSpec `json:"tls,omitempty"`
// Bind contains the configuration for how to provide access credentials during an initial bind to the LDAP server
// to be allowed to perform searches and binds to validate a user's credentials during a user's authentication attempt.
Bind LDAPIdentityProviderBind `json:"bind,omitempty"`
// UserSearch contains the configuration for searching for a user by name in the LDAP provider.
UserSearch LDAPIdentityProviderUserSearch `json:"userSearch,omitempty"`
}
// LDAPIdentityProvider describes the configuration of an upstream Lightweight Directory Access
// Protocol (LDAP) identity provider.
// +genclient
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// +kubebuilder:resource:categories=pinniped;pinniped-idp;pinniped-idps
// +kubebuilder:printcolumn:name="Host",type=string,JSONPath=`.spec.host`
// +kubebuilder:printcolumn:name="Status",type=string,JSONPath=`.status.phase`
// +kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp`
// +kubebuilder:subresource:status
type LDAPIdentityProvider struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
// Spec for configuring the identity provider.
Spec LDAPIdentityProviderSpec `json:"spec"`
// Status of the identity provider.
Status LDAPIdentityProviderStatus `json:"status,omitempty"`
}
// List of LDAPIdentityProvider objects.
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
type LDAPIdentityProviderList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []LDAPIdentityProvider `json:"items"`
}

View File

@ -28,6 +28,162 @@ func (in *Condition) DeepCopy() *Condition {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *LDAPIdentityProvider) DeepCopyInto(out *LDAPIdentityProvider) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
in.Spec.DeepCopyInto(&out.Spec)
in.Status.DeepCopyInto(&out.Status)
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LDAPIdentityProvider.
func (in *LDAPIdentityProvider) DeepCopy() *LDAPIdentityProvider {
if in == nil {
return nil
}
out := new(LDAPIdentityProvider)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *LDAPIdentityProvider) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *LDAPIdentityProviderBind) DeepCopyInto(out *LDAPIdentityProviderBind) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LDAPIdentityProviderBind.
func (in *LDAPIdentityProviderBind) DeepCopy() *LDAPIdentityProviderBind {
if in == nil {
return nil
}
out := new(LDAPIdentityProviderBind)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *LDAPIdentityProviderList) DeepCopyInto(out *LDAPIdentityProviderList) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ListMeta.DeepCopyInto(&out.ListMeta)
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]LDAPIdentityProvider, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LDAPIdentityProviderList.
func (in *LDAPIdentityProviderList) DeepCopy() *LDAPIdentityProviderList {
if in == nil {
return nil
}
out := new(LDAPIdentityProviderList)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *LDAPIdentityProviderList) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *LDAPIdentityProviderSpec) DeepCopyInto(out *LDAPIdentityProviderSpec) {
*out = *in
if in.TLS != nil {
in, out := &in.TLS, &out.TLS
*out = new(TLSSpec)
**out = **in
}
out.Bind = in.Bind
out.UserSearch = in.UserSearch
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LDAPIdentityProviderSpec.
func (in *LDAPIdentityProviderSpec) DeepCopy() *LDAPIdentityProviderSpec {
if in == nil {
return nil
}
out := new(LDAPIdentityProviderSpec)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *LDAPIdentityProviderStatus) DeepCopyInto(out *LDAPIdentityProviderStatus) {
*out = *in
if in.Conditions != nil {
in, out := &in.Conditions, &out.Conditions
*out = make([]Condition, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LDAPIdentityProviderStatus.
func (in *LDAPIdentityProviderStatus) DeepCopy() *LDAPIdentityProviderStatus {
if in == nil {
return nil
}
out := new(LDAPIdentityProviderStatus)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *LDAPIdentityProviderUserSearch) DeepCopyInto(out *LDAPIdentityProviderUserSearch) {
*out = *in
out.Attributes = in.Attributes
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LDAPIdentityProviderUserSearch.
func (in *LDAPIdentityProviderUserSearch) DeepCopy() *LDAPIdentityProviderUserSearch {
if in == nil {
return nil
}
out := new(LDAPIdentityProviderUserSearch)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *LDAPIdentityProviderUserSearchAttributes) DeepCopyInto(out *LDAPIdentityProviderUserSearchAttributes) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LDAPIdentityProviderUserSearchAttributes.
func (in *LDAPIdentityProviderUserSearchAttributes) DeepCopy() *LDAPIdentityProviderUserSearchAttributes {
if in == nil {
return nil
}
out := new(LDAPIdentityProviderUserSearchAttributes)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *OIDCAuthorizationConfig) DeepCopyInto(out *OIDCAuthorizationConfig) {
*out = *in

View File

@ -15,6 +15,10 @@ type FakeIDPV1alpha1 struct {
*testing.Fake
}
func (c *FakeIDPV1alpha1) LDAPIdentityProviders(namespace string) v1alpha1.LDAPIdentityProviderInterface {
return &FakeLDAPIdentityProviders{c, namespace}
}
func (c *FakeIDPV1alpha1) OIDCIdentityProviders(namespace string) v1alpha1.OIDCIdentityProviderInterface {
return &FakeOIDCIdentityProviders{c, namespace}
}

View File

@ -0,0 +1,129 @@
// Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
// Code generated by client-gen. DO NOT EDIT.
package fake
import (
"context"
v1alpha1 "go.pinniped.dev/generated/1.20/apis/supervisor/idp/v1alpha1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
labels "k8s.io/apimachinery/pkg/labels"
schema "k8s.io/apimachinery/pkg/runtime/schema"
types "k8s.io/apimachinery/pkg/types"
watch "k8s.io/apimachinery/pkg/watch"
testing "k8s.io/client-go/testing"
)
// FakeLDAPIdentityProviders implements LDAPIdentityProviderInterface
type FakeLDAPIdentityProviders struct {
Fake *FakeIDPV1alpha1
ns string
}
var ldapidentityprovidersResource = schema.GroupVersionResource{Group: "idp.supervisor.pinniped.dev", Version: "v1alpha1", Resource: "ldapidentityproviders"}
var ldapidentityprovidersKind = schema.GroupVersionKind{Group: "idp.supervisor.pinniped.dev", Version: "v1alpha1", Kind: "LDAPIdentityProvider"}
// Get takes name of the lDAPIdentityProvider, and returns the corresponding lDAPIdentityProvider object, and an error if there is any.
func (c *FakeLDAPIdentityProviders) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.LDAPIdentityProvider, err error) {
obj, err := c.Fake.
Invokes(testing.NewGetAction(ldapidentityprovidersResource, c.ns, name), &v1alpha1.LDAPIdentityProvider{})
if obj == nil {
return nil, err
}
return obj.(*v1alpha1.LDAPIdentityProvider), err
}
// List takes label and field selectors, and returns the list of LDAPIdentityProviders that match those selectors.
func (c *FakeLDAPIdentityProviders) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.LDAPIdentityProviderList, err error) {
obj, err := c.Fake.
Invokes(testing.NewListAction(ldapidentityprovidersResource, ldapidentityprovidersKind, c.ns, opts), &v1alpha1.LDAPIdentityProviderList{})
if obj == nil {
return nil, err
}
label, _, _ := testing.ExtractFromListOptions(opts)
if label == nil {
label = labels.Everything()
}
list := &v1alpha1.LDAPIdentityProviderList{ListMeta: obj.(*v1alpha1.LDAPIdentityProviderList).ListMeta}
for _, item := range obj.(*v1alpha1.LDAPIdentityProviderList).Items {
if label.Matches(labels.Set(item.Labels)) {
list.Items = append(list.Items, item)
}
}
return list, err
}
// Watch returns a watch.Interface that watches the requested lDAPIdentityProviders.
func (c *FakeLDAPIdentityProviders) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) {
return c.Fake.
InvokesWatch(testing.NewWatchAction(ldapidentityprovidersResource, c.ns, opts))
}
// Create takes the representation of a lDAPIdentityProvider and creates it. Returns the server's representation of the lDAPIdentityProvider, and an error, if there is any.
func (c *FakeLDAPIdentityProviders) Create(ctx context.Context, lDAPIdentityProvider *v1alpha1.LDAPIdentityProvider, opts v1.CreateOptions) (result *v1alpha1.LDAPIdentityProvider, err error) {
obj, err := c.Fake.
Invokes(testing.NewCreateAction(ldapidentityprovidersResource, c.ns, lDAPIdentityProvider), &v1alpha1.LDAPIdentityProvider{})
if obj == nil {
return nil, err
}
return obj.(*v1alpha1.LDAPIdentityProvider), err
}
// Update takes the representation of a lDAPIdentityProvider and updates it. Returns the server's representation of the lDAPIdentityProvider, and an error, if there is any.
func (c *FakeLDAPIdentityProviders) Update(ctx context.Context, lDAPIdentityProvider *v1alpha1.LDAPIdentityProvider, opts v1.UpdateOptions) (result *v1alpha1.LDAPIdentityProvider, err error) {
obj, err := c.Fake.
Invokes(testing.NewUpdateAction(ldapidentityprovidersResource, c.ns, lDAPIdentityProvider), &v1alpha1.LDAPIdentityProvider{})
if obj == nil {
return nil, err
}
return obj.(*v1alpha1.LDAPIdentityProvider), err
}
// UpdateStatus was generated because the type contains a Status member.
// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus().
func (c *FakeLDAPIdentityProviders) UpdateStatus(ctx context.Context, lDAPIdentityProvider *v1alpha1.LDAPIdentityProvider, opts v1.UpdateOptions) (*v1alpha1.LDAPIdentityProvider, error) {
obj, err := c.Fake.
Invokes(testing.NewUpdateSubresourceAction(ldapidentityprovidersResource, "status", c.ns, lDAPIdentityProvider), &v1alpha1.LDAPIdentityProvider{})
if obj == nil {
return nil, err
}
return obj.(*v1alpha1.LDAPIdentityProvider), err
}
// Delete takes name of the lDAPIdentityProvider and deletes it. Returns an error if one occurs.
func (c *FakeLDAPIdentityProviders) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error {
_, err := c.Fake.
Invokes(testing.NewDeleteAction(ldapidentityprovidersResource, c.ns, name), &v1alpha1.LDAPIdentityProvider{})
return err
}
// DeleteCollection deletes a collection of objects.
func (c *FakeLDAPIdentityProviders) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error {
action := testing.NewDeleteCollectionAction(ldapidentityprovidersResource, c.ns, listOpts)
_, err := c.Fake.Invokes(action, &v1alpha1.LDAPIdentityProviderList{})
return err
}
// Patch applies the patch and returns the patched lDAPIdentityProvider.
func (c *FakeLDAPIdentityProviders) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.LDAPIdentityProvider, err error) {
obj, err := c.Fake.
Invokes(testing.NewPatchSubresourceAction(ldapidentityprovidersResource, c.ns, name, pt, data, subresources...), &v1alpha1.LDAPIdentityProvider{})
if obj == nil {
return nil, err
}
return obj.(*v1alpha1.LDAPIdentityProvider), err
}

View File

@ -5,4 +5,6 @@
package v1alpha1
type LDAPIdentityProviderExpansion interface{}
type OIDCIdentityProviderExpansion interface{}

View File

@ -13,6 +13,7 @@ import (
type IDPV1alpha1Interface interface {
RESTClient() rest.Interface
LDAPIdentityProvidersGetter
OIDCIdentityProvidersGetter
}
@ -21,6 +22,10 @@ type IDPV1alpha1Client struct {
restClient rest.Interface
}
func (c *IDPV1alpha1Client) LDAPIdentityProviders(namespace string) LDAPIdentityProviderInterface {
return newLDAPIdentityProviders(c, namespace)
}
func (c *IDPV1alpha1Client) OIDCIdentityProviders(namespace string) OIDCIdentityProviderInterface {
return newOIDCIdentityProviders(c, namespace)
}

View File

@ -0,0 +1,182 @@
// Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
// Code generated by client-gen. DO NOT EDIT.
package v1alpha1
import (
"context"
"time"
v1alpha1 "go.pinniped.dev/generated/1.20/apis/supervisor/idp/v1alpha1"
scheme "go.pinniped.dev/generated/1.20/client/supervisor/clientset/versioned/scheme"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
types "k8s.io/apimachinery/pkg/types"
watch "k8s.io/apimachinery/pkg/watch"
rest "k8s.io/client-go/rest"
)
// LDAPIdentityProvidersGetter has a method to return a LDAPIdentityProviderInterface.
// A group's client should implement this interface.
type LDAPIdentityProvidersGetter interface {
LDAPIdentityProviders(namespace string) LDAPIdentityProviderInterface
}
// LDAPIdentityProviderInterface has methods to work with LDAPIdentityProvider resources.
type LDAPIdentityProviderInterface interface {
Create(ctx context.Context, lDAPIdentityProvider *v1alpha1.LDAPIdentityProvider, opts v1.CreateOptions) (*v1alpha1.LDAPIdentityProvider, error)
Update(ctx context.Context, lDAPIdentityProvider *v1alpha1.LDAPIdentityProvider, opts v1.UpdateOptions) (*v1alpha1.LDAPIdentityProvider, error)
UpdateStatus(ctx context.Context, lDAPIdentityProvider *v1alpha1.LDAPIdentityProvider, opts v1.UpdateOptions) (*v1alpha1.LDAPIdentityProvider, error)
Delete(ctx context.Context, name string, opts v1.DeleteOptions) error
DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error
Get(ctx context.Context, name string, opts v1.GetOptions) (*v1alpha1.LDAPIdentityProvider, error)
List(ctx context.Context, opts v1.ListOptions) (*v1alpha1.LDAPIdentityProviderList, error)
Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error)
Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.LDAPIdentityProvider, err error)
LDAPIdentityProviderExpansion
}
// lDAPIdentityProviders implements LDAPIdentityProviderInterface
type lDAPIdentityProviders struct {
client rest.Interface
ns string
}
// newLDAPIdentityProviders returns a LDAPIdentityProviders
func newLDAPIdentityProviders(c *IDPV1alpha1Client, namespace string) *lDAPIdentityProviders {
return &lDAPIdentityProviders{
client: c.RESTClient(),
ns: namespace,
}
}
// Get takes name of the lDAPIdentityProvider, and returns the corresponding lDAPIdentityProvider object, and an error if there is any.
func (c *lDAPIdentityProviders) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.LDAPIdentityProvider, err error) {
result = &v1alpha1.LDAPIdentityProvider{}
err = c.client.Get().
Namespace(c.ns).
Resource("ldapidentityproviders").
Name(name).
VersionedParams(&options, scheme.ParameterCodec).
Do(ctx).
Into(result)
return
}
// List takes label and field selectors, and returns the list of LDAPIdentityProviders that match those selectors.
func (c *lDAPIdentityProviders) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.LDAPIdentityProviderList, err error) {
var timeout time.Duration
if opts.TimeoutSeconds != nil {
timeout = time.Duration(*opts.TimeoutSeconds) * time.Second
}
result = &v1alpha1.LDAPIdentityProviderList{}
err = c.client.Get().
Namespace(c.ns).
Resource("ldapidentityproviders").
VersionedParams(&opts, scheme.ParameterCodec).
Timeout(timeout).
Do(ctx).
Into(result)
return
}
// Watch returns a watch.Interface that watches the requested lDAPIdentityProviders.
func (c *lDAPIdentityProviders) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) {
var timeout time.Duration
if opts.TimeoutSeconds != nil {
timeout = time.Duration(*opts.TimeoutSeconds) * time.Second
}
opts.Watch = true
return c.client.Get().
Namespace(c.ns).
Resource("ldapidentityproviders").
VersionedParams(&opts, scheme.ParameterCodec).
Timeout(timeout).
Watch(ctx)
}
// Create takes the representation of a lDAPIdentityProvider and creates it. Returns the server's representation of the lDAPIdentityProvider, and an error, if there is any.
func (c *lDAPIdentityProviders) Create(ctx context.Context, lDAPIdentityProvider *v1alpha1.LDAPIdentityProvider, opts v1.CreateOptions) (result *v1alpha1.LDAPIdentityProvider, err error) {
result = &v1alpha1.LDAPIdentityProvider{}
err = c.client.Post().
Namespace(c.ns).
Resource("ldapidentityproviders").
VersionedParams(&opts, scheme.ParameterCodec).
Body(lDAPIdentityProvider).
Do(ctx).
Into(result)
return
}
// Update takes the representation of a lDAPIdentityProvider and updates it. Returns the server's representation of the lDAPIdentityProvider, and an error, if there is any.
func (c *lDAPIdentityProviders) Update(ctx context.Context, lDAPIdentityProvider *v1alpha1.LDAPIdentityProvider, opts v1.UpdateOptions) (result *v1alpha1.LDAPIdentityProvider, err error) {
result = &v1alpha1.LDAPIdentityProvider{}
err = c.client.Put().
Namespace(c.ns).
Resource("ldapidentityproviders").
Name(lDAPIdentityProvider.Name).
VersionedParams(&opts, scheme.ParameterCodec).
Body(lDAPIdentityProvider).
Do(ctx).
Into(result)
return
}
// UpdateStatus was generated because the type contains a Status member.
// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus().
func (c *lDAPIdentityProviders) UpdateStatus(ctx context.Context, lDAPIdentityProvider *v1alpha1.LDAPIdentityProvider, opts v1.UpdateOptions) (result *v1alpha1.LDAPIdentityProvider, err error) {
result = &v1alpha1.LDAPIdentityProvider{}
err = c.client.Put().
Namespace(c.ns).
Resource("ldapidentityproviders").
Name(lDAPIdentityProvider.Name).
SubResource("status").
VersionedParams(&opts, scheme.ParameterCodec).
Body(lDAPIdentityProvider).
Do(ctx).
Into(result)
return
}
// Delete takes name of the lDAPIdentityProvider and deletes it. Returns an error if one occurs.
func (c *lDAPIdentityProviders) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error {
return c.client.Delete().
Namespace(c.ns).
Resource("ldapidentityproviders").
Name(name).
Body(&opts).
Do(ctx).
Error()
}
// DeleteCollection deletes a collection of objects.
func (c *lDAPIdentityProviders) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error {
var timeout time.Duration
if listOpts.TimeoutSeconds != nil {
timeout = time.Duration(*listOpts.TimeoutSeconds) * time.Second
}
return c.client.Delete().
Namespace(c.ns).
Resource("ldapidentityproviders").
VersionedParams(&listOpts, scheme.ParameterCodec).
Timeout(timeout).
Body(&opts).
Do(ctx).
Error()
}
// Patch applies the patch and returns the patched lDAPIdentityProvider.
func (c *lDAPIdentityProviders) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.LDAPIdentityProvider, err error) {
result = &v1alpha1.LDAPIdentityProvider{}
err = c.client.Patch(pt).
Namespace(c.ns).
Resource("ldapidentityproviders").
Name(name).
SubResource(subresources...).
VersionedParams(&opts, scheme.ParameterCodec).
Body(data).
Do(ctx).
Into(result)
return
}

View File

@ -45,6 +45,8 @@ func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource
return &genericInformer{resource: resource.GroupResource(), informer: f.Config().V1alpha1().FederationDomains().Informer()}, nil
// Group=idp.supervisor.pinniped.dev, Version=v1alpha1
case idpv1alpha1.SchemeGroupVersion.WithResource("ldapidentityproviders"):
return &genericInformer{resource: resource.GroupResource(), informer: f.IDP().V1alpha1().LDAPIdentityProviders().Informer()}, nil
case idpv1alpha1.SchemeGroupVersion.WithResource("oidcidentityproviders"):
return &genericInformer{resource: resource.GroupResource(), informer: f.IDP().V1alpha1().OIDCIdentityProviders().Informer()}, nil

View File

@ -11,6 +11,8 @@ import (
// Interface provides access to all the informers in this group version.
type Interface interface {
// LDAPIdentityProviders returns a LDAPIdentityProviderInformer.
LDAPIdentityProviders() LDAPIdentityProviderInformer
// OIDCIdentityProviders returns a OIDCIdentityProviderInformer.
OIDCIdentityProviders() OIDCIdentityProviderInformer
}
@ -26,6 +28,11 @@ func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakList
return &version{factory: f, namespace: namespace, tweakListOptions: tweakListOptions}
}
// LDAPIdentityProviders returns a LDAPIdentityProviderInformer.
func (v *version) LDAPIdentityProviders() LDAPIdentityProviderInformer {
return &lDAPIdentityProviderInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions}
}
// OIDCIdentityProviders returns a OIDCIdentityProviderInformer.
func (v *version) OIDCIdentityProviders() OIDCIdentityProviderInformer {
return &oIDCIdentityProviderInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions}

View File

@ -0,0 +1,77 @@
// Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
// Code generated by informer-gen. DO NOT EDIT.
package v1alpha1
import (
"context"
time "time"
idpv1alpha1 "go.pinniped.dev/generated/1.20/apis/supervisor/idp/v1alpha1"
versioned "go.pinniped.dev/generated/1.20/client/supervisor/clientset/versioned"
internalinterfaces "go.pinniped.dev/generated/1.20/client/supervisor/informers/externalversions/internalinterfaces"
v1alpha1 "go.pinniped.dev/generated/1.20/client/supervisor/listers/idp/v1alpha1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
runtime "k8s.io/apimachinery/pkg/runtime"
watch "k8s.io/apimachinery/pkg/watch"
cache "k8s.io/client-go/tools/cache"
)
// LDAPIdentityProviderInformer provides access to a shared informer and lister for
// LDAPIdentityProviders.
type LDAPIdentityProviderInformer interface {
Informer() cache.SharedIndexInformer
Lister() v1alpha1.LDAPIdentityProviderLister
}
type lDAPIdentityProviderInformer struct {
factory internalinterfaces.SharedInformerFactory
tweakListOptions internalinterfaces.TweakListOptionsFunc
namespace string
}
// NewLDAPIdentityProviderInformer constructs a new informer for LDAPIdentityProvider type.
// Always prefer using an informer factory to get a shared informer instead of getting an independent
// one. This reduces memory footprint and number of connections to the server.
func NewLDAPIdentityProviderInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer {
return NewFilteredLDAPIdentityProviderInformer(client, namespace, resyncPeriod, indexers, nil)
}
// NewFilteredLDAPIdentityProviderInformer constructs a new informer for LDAPIdentityProvider type.
// Always prefer using an informer factory to get a shared informer instead of getting an independent
// one. This reduces memory footprint and number of connections to the server.
func NewFilteredLDAPIdentityProviderInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer {
return cache.NewSharedIndexInformer(
&cache.ListWatch{
ListFunc: func(options v1.ListOptions) (runtime.Object, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.IDPV1alpha1().LDAPIdentityProviders(namespace).List(context.TODO(), options)
},
WatchFunc: func(options v1.ListOptions) (watch.Interface, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.IDPV1alpha1().LDAPIdentityProviders(namespace).Watch(context.TODO(), options)
},
},
&idpv1alpha1.LDAPIdentityProvider{},
resyncPeriod,
indexers,
)
}
func (f *lDAPIdentityProviderInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer {
return NewFilteredLDAPIdentityProviderInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions)
}
func (f *lDAPIdentityProviderInformer) Informer() cache.SharedIndexInformer {
return f.factory.InformerFor(&idpv1alpha1.LDAPIdentityProvider{}, f.defaultInformer)
}
func (f *lDAPIdentityProviderInformer) Lister() v1alpha1.LDAPIdentityProviderLister {
return v1alpha1.NewLDAPIdentityProviderLister(f.Informer().GetIndexer())
}

View File

@ -5,6 +5,14 @@
package v1alpha1
// LDAPIdentityProviderListerExpansion allows custom methods to be added to
// LDAPIdentityProviderLister.
type LDAPIdentityProviderListerExpansion interface{}
// LDAPIdentityProviderNamespaceListerExpansion allows custom methods to be added to
// LDAPIdentityProviderNamespaceLister.
type LDAPIdentityProviderNamespaceListerExpansion interface{}
// OIDCIdentityProviderListerExpansion allows custom methods to be added to
// OIDCIdentityProviderLister.
type OIDCIdentityProviderListerExpansion interface{}

View File

@ -0,0 +1,86 @@
// Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
// Code generated by lister-gen. DO NOT EDIT.
package v1alpha1
import (
v1alpha1 "go.pinniped.dev/generated/1.20/apis/supervisor/idp/v1alpha1"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/client-go/tools/cache"
)
// LDAPIdentityProviderLister helps list LDAPIdentityProviders.
// All objects returned here must be treated as read-only.
type LDAPIdentityProviderLister interface {
// List lists all LDAPIdentityProviders in the indexer.
// Objects returned here must be treated as read-only.
List(selector labels.Selector) (ret []*v1alpha1.LDAPIdentityProvider, err error)
// LDAPIdentityProviders returns an object that can list and get LDAPIdentityProviders.
LDAPIdentityProviders(namespace string) LDAPIdentityProviderNamespaceLister
LDAPIdentityProviderListerExpansion
}
// lDAPIdentityProviderLister implements the LDAPIdentityProviderLister interface.
type lDAPIdentityProviderLister struct {
indexer cache.Indexer
}
// NewLDAPIdentityProviderLister returns a new LDAPIdentityProviderLister.
func NewLDAPIdentityProviderLister(indexer cache.Indexer) LDAPIdentityProviderLister {
return &lDAPIdentityProviderLister{indexer: indexer}
}
// List lists all LDAPIdentityProviders in the indexer.
func (s *lDAPIdentityProviderLister) List(selector labels.Selector) (ret []*v1alpha1.LDAPIdentityProvider, err error) {
err = cache.ListAll(s.indexer, selector, func(m interface{}) {
ret = append(ret, m.(*v1alpha1.LDAPIdentityProvider))
})
return ret, err
}
// LDAPIdentityProviders returns an object that can list and get LDAPIdentityProviders.
func (s *lDAPIdentityProviderLister) LDAPIdentityProviders(namespace string) LDAPIdentityProviderNamespaceLister {
return lDAPIdentityProviderNamespaceLister{indexer: s.indexer, namespace: namespace}
}
// LDAPIdentityProviderNamespaceLister helps list and get LDAPIdentityProviders.
// All objects returned here must be treated as read-only.
type LDAPIdentityProviderNamespaceLister interface {
// List lists all LDAPIdentityProviders in the indexer for a given namespace.
// Objects returned here must be treated as read-only.
List(selector labels.Selector) (ret []*v1alpha1.LDAPIdentityProvider, err error)
// Get retrieves the LDAPIdentityProvider from the indexer for a given namespace and name.
// Objects returned here must be treated as read-only.
Get(name string) (*v1alpha1.LDAPIdentityProvider, error)
LDAPIdentityProviderNamespaceListerExpansion
}
// lDAPIdentityProviderNamespaceLister implements the LDAPIdentityProviderNamespaceLister
// interface.
type lDAPIdentityProviderNamespaceLister struct {
indexer cache.Indexer
namespace string
}
// List lists all LDAPIdentityProviders in the indexer for a given namespace.
func (s lDAPIdentityProviderNamespaceLister) List(selector labels.Selector) (ret []*v1alpha1.LDAPIdentityProvider, err error) {
err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) {
ret = append(ret, m.(*v1alpha1.LDAPIdentityProvider))
})
return ret, err
}
// Get retrieves the LDAPIdentityProvider from the indexer for a given namespace and name.
func (s lDAPIdentityProviderNamespaceLister) Get(name string) (*v1alpha1.LDAPIdentityProvider, error) {
obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name)
if err != nil {
return nil, err
}
if !exists {
return nil, errors.NewNotFound(v1alpha1.Resource("ldapidentityprovider"), name)
}
return obj.(*v1alpha1.LDAPIdentityProvider), nil
}

View File

@ -0,0 +1,234 @@
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.4.0
creationTimestamp: null
name: ldapidentityproviders.idp.supervisor.pinniped.dev
spec:
group: idp.supervisor.pinniped.dev
names:
categories:
- pinniped
- pinniped-idp
- pinniped-idps
kind: LDAPIdentityProvider
listKind: LDAPIdentityProviderList
plural: ldapidentityproviders
singular: ldapidentityprovider
scope: Namespaced
versions:
- additionalPrinterColumns:
- jsonPath: .spec.host
name: Host
type: string
- jsonPath: .status.phase
name: Status
type: string
- jsonPath: .metadata.creationTimestamp
name: Age
type: date
name: v1alpha1
schema:
openAPIV3Schema:
description: LDAPIdentityProvider describes the configuration of an upstream
Lightweight Directory Access Protocol (LDAP) identity provider.
properties:
apiVersion:
description: 'APIVersion defines the versioned schema of this representation
of an object. Servers should convert recognized schemas to the latest
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
type: string
kind:
description: 'Kind is a string value representing the REST resource this
object represents. Servers may infer this from the endpoint the client
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
type: string
metadata:
type: object
spec:
description: Spec for configuring the identity provider.
properties:
bind:
description: Bind contains the configuration for how to provide access
credentials during an initial bind to the LDAP server to be allowed
to perform searches and binds to validate a user's credentials during
a user's authentication attempt.
properties:
secretName:
description: SecretName contains the name of a namespace-local
Secret object that provides the username and password for an
LDAP bind user. This account will be used to perform LDAP searches.
The Secret should be of type "kubernetes.io/basic-auth" which
includes "username" and "password" keys. The username value
should be the full DN of your bind account, e.g. "cn=bind-account,ou=users,dc=example,dc=com".
The password must be non-empty.
minLength: 1
type: string
required:
- secretName
type: object
host:
description: 'Host is the hostname of this LDAP identity provider,
i.e., where to connect. For example: ldap.example.com:636.'
minLength: 1
type: string
tls:
description: TLS contains the connection settings for how to establish
the connection to the Host.
properties:
certificateAuthorityData:
description: X.509 Certificate Authority (base64-encoded PEM bundle).
If omitted, a default set of system roots will be trusted.
type: string
type: object
userSearch:
description: UserSearch contains the configuration for searching for
a user by name in the LDAP provider.
properties:
attributes:
description: Attributes specifies how the user's information should
be read from the LDAP entry which was found as the result of
the user search.
properties:
uid:
description: UID specifies the name of the attribute in the
LDAP entry which whose value shall be used to uniquely identify
the user within this LDAP provider after a successful authentication.
E.g. "uidNumber" or "objectGUID". The value of this field
is case-sensitive and must match the case of the attribute
name returned by the LDAP server in the user's entry. Distinguished
names can be used by specifying lower-case "dn".
minLength: 1
type: string
username:
description: Username specifies the name of attribute in the
LDAP entry which whose value shall become the username of
the user after a successful authentication. This would typically
be the same attribute name used in the user search filter,
although it can be different. E.g. "mail" or "uid" or "userPrincipalName".
The value of this field is case-sensitive and must match
the case of the attribute name returned by the LDAP server
in the user's entry. Distinguished names can be used by
specifying lower-case "dn". When this field is set to "dn"
then the LDAPIdentityProviderUserSearch's Filter field cannot
be blank, since the default value of "dn={}" would not work.
minLength: 1
type: string
type: object
base:
description: Base is the DN that should be used as the search
base when searching for users. E.g. "ou=users,dc=example,dc=com".
minLength: 1
type: string
filter:
description: Filter is the LDAP search filter which should be
applied when searching for users. The pattern "{}" must occur
in the filter and will be dynamically replaced by the username
for which the search is being run. E.g. "mail={}" or "&(objectClass=person)(uid={})".
For more information about LDAP filters, see https://ldap.com/ldap-filters.
Note that the dn (distinguished name) is not an attribute of
an entry, so "dn={}" cannot be used. Optional. When not specified,
the default will act as if the Filter were specified as the
value from Attributes.Username appended by "={}". When the Attributes.Username
is set to "dn" then the Filter must be explicitly specified,
since the default value of "dn={}" would not work.
type: string
type: object
required:
- host
type: object
status:
description: Status of the identity provider.
properties:
conditions:
description: Represents the observations of an identity provider's
current state.
items:
description: Condition status of a resource (mirrored from the metav1.Condition
type added in Kubernetes 1.19). In a future API version we can
switch to using the upstream type. See https://github.com/kubernetes/apimachinery/blob/v0.19.0/pkg/apis/meta/v1/types.go#L1353-L1413.
properties:
lastTransitionTime:
description: lastTransitionTime is the last time the condition
transitioned from one status to another. This should be when
the underlying condition changed. If that is not known, then
using the time when the API field changed is acceptable.
format: date-time
type: string
message:
description: message is a human readable message indicating
details about the transition. This may be an empty string.
maxLength: 32768
type: string
observedGeneration:
description: observedGeneration represents the .metadata.generation
that the condition was set based upon. For instance, if .metadata.generation
is currently 12, but the .status.conditions[x].observedGeneration
is 9, the condition is out of date with respect to the current
state of the instance.
format: int64
minimum: 0
type: integer
reason:
description: reason contains a programmatic identifier indicating
the reason for the condition's last transition. Producers
of specific condition types may define expected values and
meanings for this field, and whether the values are considered
a guaranteed API. The value should be a CamelCase string.
This field may not be empty.
maxLength: 1024
minLength: 1
pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$
type: string
status:
description: status of the condition, one of True, False, Unknown.
enum:
- "True"
- "False"
- Unknown
type: string
type:
description: type of condition in CamelCase or in foo.example.com/CamelCase.
--- Many .condition.type values are consistent across resources
like Available, but because arbitrary conditions can be useful
(see .node.status.conditions), the ability to deconflict is
important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt)
maxLength: 316
pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
type: string
required:
- lastTransitionTime
- message
- reason
- status
- type
type: object
type: array
x-kubernetes-list-map-keys:
- type
x-kubernetes-list-type: map
phase:
default: Pending
description: Phase summarizes the overall status of the LDAPIdentityProvider.
enum:
- Pending
- Ready
- Error
type: string
type: object
required:
- spec
type: object
served: true
storage: true
subresources:
status: {}
status:
acceptedNames:
kind: ""
plural: ""
conditions: []
storedVersions: []

View File

@ -32,6 +32,8 @@ func addKnownTypes(scheme *runtime.Scheme) error {
scheme.AddKnownTypes(SchemeGroupVersion,
&OIDCIdentityProvider{},
&OIDCIdentityProviderList{},
&LDAPIdentityProvider{},
&LDAPIdentityProviderList{},
)
metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
return nil

View File

@ -0,0 +1,132 @@
// Copyright 2021 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package v1alpha1
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
type LDAPIdentityProviderPhase string
const (
// LDAPPhasePending is the default phase for newly-created LDAPIdentityProvider resources.
LDAPPhasePending LDAPIdentityProviderPhase = "Pending"
// LDAPPhaseReady is the phase for an LDAPIdentityProvider resource in a healthy state.
LDAPPhaseReady LDAPIdentityProviderPhase = "Ready"
// LDAPPhaseError is the phase for an LDAPIdentityProvider in an unhealthy state.
LDAPPhaseError LDAPIdentityProviderPhase = "Error"
)
// Status of an LDAP identity provider.
type LDAPIdentityProviderStatus struct {
// Phase summarizes the overall status of the LDAPIdentityProvider.
// +kubebuilder:default=Pending
// +kubebuilder:validation:Enum=Pending;Ready;Error
Phase LDAPIdentityProviderPhase `json:"phase,omitempty"`
// Represents the observations of an identity provider's current state.
// +patchMergeKey=type
// +patchStrategy=merge
// +listType=map
// +listMapKey=type
Conditions []Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"`
}
type LDAPIdentityProviderBind struct {
// SecretName contains the name of a namespace-local Secret object that provides the username and
// password for an LDAP bind user. This account will be used to perform LDAP searches. The Secret should be
// of type "kubernetes.io/basic-auth" which includes "username" and "password" keys. The username value
// should be the full DN of your bind account, e.g. "cn=bind-account,ou=users,dc=example,dc=com".
// The password must be non-empty.
// +kubebuilder:validation:MinLength=1
SecretName string `json:"secretName"`
}
type LDAPIdentityProviderUserSearchAttributes struct {
// Username specifies the name of attribute in the LDAP entry which whose value shall become the username
// of the user after a successful authentication. This would typically be the same attribute name used in
// the user search filter, although it can be different. E.g. "mail" or "uid" or "userPrincipalName".
// The value of this field is case-sensitive and must match the case of the attribute name returned by the LDAP
// server in the user's entry. Distinguished names can be used by specifying lower-case "dn". When this field
// is set to "dn" then the LDAPIdentityProviderUserSearch's Filter field cannot be blank, since the default
// value of "dn={}" would not work.
// +kubebuilder:validation:MinLength=1
Username string `json:"username,omitempty"`
// UID specifies the name of the attribute in the LDAP entry which whose value shall be used to uniquely
// identify the user within this LDAP provider after a successful authentication. E.g. "uidNumber" or "objectGUID".
// The value of this field is case-sensitive and must match the case of the attribute name returned by the LDAP
// server in the user's entry. Distinguished names can be used by specifying lower-case "dn".
// +kubebuilder:validation:MinLength=1
UID string `json:"uid,omitempty"`
}
type LDAPIdentityProviderUserSearch struct {
// Base is the DN that should be used as the search base when searching for users. E.g. "ou=users,dc=example,dc=com".
// +kubebuilder:validation:MinLength=1
Base string `json:"base,omitempty"`
// Filter is the LDAP search filter which should be applied when searching for users. The pattern "{}" must occur
// in the filter and will be dynamically replaced by the username for which the search is being run. E.g. "mail={}"
// or "&(objectClass=person)(uid={})". For more information about LDAP filters, see https://ldap.com/ldap-filters.
// Note that the dn (distinguished name) is not an attribute of an entry, so "dn={}" cannot be used.
// Optional. When not specified, the default will act as if the Filter were specified as the value from
// Attributes.Username appended by "={}". When the Attributes.Username is set to "dn" then the Filter must be
// explicitly specified, since the default value of "dn={}" would not work.
// +optional
Filter string `json:"filter,omitempty"`
// Attributes specifies how the user's information should be read from the LDAP entry which was found as
// the result of the user search.
// +optional
Attributes LDAPIdentityProviderUserSearchAttributes `json:"attributes,omitempty"`
}
// Spec for configuring an LDAP identity provider.
type LDAPIdentityProviderSpec struct {
// Host is the hostname of this LDAP identity provider, i.e., where to connect. For example: ldap.example.com:636.
// +kubebuilder:validation:MinLength=1
Host string `json:"host"`
// TLS contains the connection settings for how to establish the connection to the Host.
TLS *TLSSpec `json:"tls,omitempty"`
// Bind contains the configuration for how to provide access credentials during an initial bind to the LDAP server
// to be allowed to perform searches and binds to validate a user's credentials during a user's authentication attempt.
Bind LDAPIdentityProviderBind `json:"bind,omitempty"`
// UserSearch contains the configuration for searching for a user by name in the LDAP provider.
UserSearch LDAPIdentityProviderUserSearch `json:"userSearch,omitempty"`
}
// LDAPIdentityProvider describes the configuration of an upstream Lightweight Directory Access
// Protocol (LDAP) identity provider.
// +genclient
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// +kubebuilder:resource:categories=pinniped;pinniped-idp;pinniped-idps
// +kubebuilder:printcolumn:name="Host",type=string,JSONPath=`.spec.host`
// +kubebuilder:printcolumn:name="Status",type=string,JSONPath=`.status.phase`
// +kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp`
// +kubebuilder:subresource:status
type LDAPIdentityProvider struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
// Spec for configuring the identity provider.
Spec LDAPIdentityProviderSpec `json:"spec"`
// Status of the identity provider.
Status LDAPIdentityProviderStatus `json:"status,omitempty"`
}
// List of LDAPIdentityProvider objects.
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
type LDAPIdentityProviderList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []LDAPIdentityProvider `json:"items"`
}

View File

@ -28,6 +28,162 @@ func (in *Condition) DeepCopy() *Condition {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *LDAPIdentityProvider) DeepCopyInto(out *LDAPIdentityProvider) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
in.Spec.DeepCopyInto(&out.Spec)
in.Status.DeepCopyInto(&out.Status)
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LDAPIdentityProvider.
func (in *LDAPIdentityProvider) DeepCopy() *LDAPIdentityProvider {
if in == nil {
return nil
}
out := new(LDAPIdentityProvider)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *LDAPIdentityProvider) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *LDAPIdentityProviderBind) DeepCopyInto(out *LDAPIdentityProviderBind) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LDAPIdentityProviderBind.
func (in *LDAPIdentityProviderBind) DeepCopy() *LDAPIdentityProviderBind {
if in == nil {
return nil
}
out := new(LDAPIdentityProviderBind)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *LDAPIdentityProviderList) DeepCopyInto(out *LDAPIdentityProviderList) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ListMeta.DeepCopyInto(&out.ListMeta)
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]LDAPIdentityProvider, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LDAPIdentityProviderList.
func (in *LDAPIdentityProviderList) DeepCopy() *LDAPIdentityProviderList {
if in == nil {
return nil
}
out := new(LDAPIdentityProviderList)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *LDAPIdentityProviderList) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *LDAPIdentityProviderSpec) DeepCopyInto(out *LDAPIdentityProviderSpec) {
*out = *in
if in.TLS != nil {
in, out := &in.TLS, &out.TLS
*out = new(TLSSpec)
**out = **in
}
out.Bind = in.Bind
out.UserSearch = in.UserSearch
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LDAPIdentityProviderSpec.
func (in *LDAPIdentityProviderSpec) DeepCopy() *LDAPIdentityProviderSpec {
if in == nil {
return nil
}
out := new(LDAPIdentityProviderSpec)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *LDAPIdentityProviderStatus) DeepCopyInto(out *LDAPIdentityProviderStatus) {
*out = *in
if in.Conditions != nil {
in, out := &in.Conditions, &out.Conditions
*out = make([]Condition, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LDAPIdentityProviderStatus.
func (in *LDAPIdentityProviderStatus) DeepCopy() *LDAPIdentityProviderStatus {
if in == nil {
return nil
}
out := new(LDAPIdentityProviderStatus)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *LDAPIdentityProviderUserSearch) DeepCopyInto(out *LDAPIdentityProviderUserSearch) {
*out = *in
out.Attributes = in.Attributes
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LDAPIdentityProviderUserSearch.
func (in *LDAPIdentityProviderUserSearch) DeepCopy() *LDAPIdentityProviderUserSearch {
if in == nil {
return nil
}
out := new(LDAPIdentityProviderUserSearch)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *LDAPIdentityProviderUserSearchAttributes) DeepCopyInto(out *LDAPIdentityProviderUserSearchAttributes) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LDAPIdentityProviderUserSearchAttributes.
func (in *LDAPIdentityProviderUserSearchAttributes) DeepCopy() *LDAPIdentityProviderUserSearchAttributes {
if in == nil {
return nil
}
out := new(LDAPIdentityProviderUserSearchAttributes)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *OIDCAuthorizationConfig) DeepCopyInto(out *OIDCAuthorizationConfig) {
*out = *in

View File

@ -15,6 +15,10 @@ type FakeIDPV1alpha1 struct {
*testing.Fake
}
func (c *FakeIDPV1alpha1) LDAPIdentityProviders(namespace string) v1alpha1.LDAPIdentityProviderInterface {
return &FakeLDAPIdentityProviders{c, namespace}
}
func (c *FakeIDPV1alpha1) OIDCIdentityProviders(namespace string) v1alpha1.OIDCIdentityProviderInterface {
return &FakeOIDCIdentityProviders{c, namespace}
}

View File

@ -0,0 +1,129 @@
// Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
// Code generated by client-gen. DO NOT EDIT.
package fake
import (
"context"
v1alpha1 "go.pinniped.dev/generated/latest/apis/supervisor/idp/v1alpha1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
labels "k8s.io/apimachinery/pkg/labels"
schema "k8s.io/apimachinery/pkg/runtime/schema"
types "k8s.io/apimachinery/pkg/types"
watch "k8s.io/apimachinery/pkg/watch"
testing "k8s.io/client-go/testing"
)
// FakeLDAPIdentityProviders implements LDAPIdentityProviderInterface
type FakeLDAPIdentityProviders struct {
Fake *FakeIDPV1alpha1
ns string
}
var ldapidentityprovidersResource = schema.GroupVersionResource{Group: "idp.supervisor.pinniped.dev", Version: "v1alpha1", Resource: "ldapidentityproviders"}
var ldapidentityprovidersKind = schema.GroupVersionKind{Group: "idp.supervisor.pinniped.dev", Version: "v1alpha1", Kind: "LDAPIdentityProvider"}
// Get takes name of the lDAPIdentityProvider, and returns the corresponding lDAPIdentityProvider object, and an error if there is any.
func (c *FakeLDAPIdentityProviders) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.LDAPIdentityProvider, err error) {
obj, err := c.Fake.
Invokes(testing.NewGetAction(ldapidentityprovidersResource, c.ns, name), &v1alpha1.LDAPIdentityProvider{})
if obj == nil {
return nil, err
}
return obj.(*v1alpha1.LDAPIdentityProvider), err
}
// List takes label and field selectors, and returns the list of LDAPIdentityProviders that match those selectors.
func (c *FakeLDAPIdentityProviders) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.LDAPIdentityProviderList, err error) {
obj, err := c.Fake.
Invokes(testing.NewListAction(ldapidentityprovidersResource, ldapidentityprovidersKind, c.ns, opts), &v1alpha1.LDAPIdentityProviderList{})
if obj == nil {
return nil, err
}
label, _, _ := testing.ExtractFromListOptions(opts)
if label == nil {
label = labels.Everything()
}
list := &v1alpha1.LDAPIdentityProviderList{ListMeta: obj.(*v1alpha1.LDAPIdentityProviderList).ListMeta}
for _, item := range obj.(*v1alpha1.LDAPIdentityProviderList).Items {
if label.Matches(labels.Set(item.Labels)) {
list.Items = append(list.Items, item)
}
}
return list, err
}
// Watch returns a watch.Interface that watches the requested lDAPIdentityProviders.
func (c *FakeLDAPIdentityProviders) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) {
return c.Fake.
InvokesWatch(testing.NewWatchAction(ldapidentityprovidersResource, c.ns, opts))
}
// Create takes the representation of a lDAPIdentityProvider and creates it. Returns the server's representation of the lDAPIdentityProvider, and an error, if there is any.
func (c *FakeLDAPIdentityProviders) Create(ctx context.Context, lDAPIdentityProvider *v1alpha1.LDAPIdentityProvider, opts v1.CreateOptions) (result *v1alpha1.LDAPIdentityProvider, err error) {
obj, err := c.Fake.
Invokes(testing.NewCreateAction(ldapidentityprovidersResource, c.ns, lDAPIdentityProvider), &v1alpha1.LDAPIdentityProvider{})
if obj == nil {
return nil, err
}
return obj.(*v1alpha1.LDAPIdentityProvider), err
}
// Update takes the representation of a lDAPIdentityProvider and updates it. Returns the server's representation of the lDAPIdentityProvider, and an error, if there is any.
func (c *FakeLDAPIdentityProviders) Update(ctx context.Context, lDAPIdentityProvider *v1alpha1.LDAPIdentityProvider, opts v1.UpdateOptions) (result *v1alpha1.LDAPIdentityProvider, err error) {
obj, err := c.Fake.
Invokes(testing.NewUpdateAction(ldapidentityprovidersResource, c.ns, lDAPIdentityProvider), &v1alpha1.LDAPIdentityProvider{})
if obj == nil {
return nil, err
}
return obj.(*v1alpha1.LDAPIdentityProvider), err
}
// UpdateStatus was generated because the type contains a Status member.
// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus().
func (c *FakeLDAPIdentityProviders) UpdateStatus(ctx context.Context, lDAPIdentityProvider *v1alpha1.LDAPIdentityProvider, opts v1.UpdateOptions) (*v1alpha1.LDAPIdentityProvider, error) {
obj, err := c.Fake.
Invokes(testing.NewUpdateSubresourceAction(ldapidentityprovidersResource, "status", c.ns, lDAPIdentityProvider), &v1alpha1.LDAPIdentityProvider{})
if obj == nil {
return nil, err
}
return obj.(*v1alpha1.LDAPIdentityProvider), err
}
// Delete takes name of the lDAPIdentityProvider and deletes it. Returns an error if one occurs.
func (c *FakeLDAPIdentityProviders) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error {
_, err := c.Fake.
Invokes(testing.NewDeleteAction(ldapidentityprovidersResource, c.ns, name), &v1alpha1.LDAPIdentityProvider{})
return err
}
// DeleteCollection deletes a collection of objects.
func (c *FakeLDAPIdentityProviders) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error {
action := testing.NewDeleteCollectionAction(ldapidentityprovidersResource, c.ns, listOpts)
_, err := c.Fake.Invokes(action, &v1alpha1.LDAPIdentityProviderList{})
return err
}
// Patch applies the patch and returns the patched lDAPIdentityProvider.
func (c *FakeLDAPIdentityProviders) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.LDAPIdentityProvider, err error) {
obj, err := c.Fake.
Invokes(testing.NewPatchSubresourceAction(ldapidentityprovidersResource, c.ns, name, pt, data, subresources...), &v1alpha1.LDAPIdentityProvider{})
if obj == nil {
return nil, err
}
return obj.(*v1alpha1.LDAPIdentityProvider), err
}

View File

@ -5,4 +5,6 @@
package v1alpha1
type LDAPIdentityProviderExpansion interface{}
type OIDCIdentityProviderExpansion interface{}

View File

@ -13,6 +13,7 @@ import (
type IDPV1alpha1Interface interface {
RESTClient() rest.Interface
LDAPIdentityProvidersGetter
OIDCIdentityProvidersGetter
}
@ -21,6 +22,10 @@ type IDPV1alpha1Client struct {
restClient rest.Interface
}
func (c *IDPV1alpha1Client) LDAPIdentityProviders(namespace string) LDAPIdentityProviderInterface {
return newLDAPIdentityProviders(c, namespace)
}
func (c *IDPV1alpha1Client) OIDCIdentityProviders(namespace string) OIDCIdentityProviderInterface {
return newOIDCIdentityProviders(c, namespace)
}

View File

@ -0,0 +1,182 @@
// Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
// Code generated by client-gen. DO NOT EDIT.
package v1alpha1
import (
"context"
"time"
v1alpha1 "go.pinniped.dev/generated/latest/apis/supervisor/idp/v1alpha1"
scheme "go.pinniped.dev/generated/latest/client/supervisor/clientset/versioned/scheme"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
types "k8s.io/apimachinery/pkg/types"
watch "k8s.io/apimachinery/pkg/watch"
rest "k8s.io/client-go/rest"
)
// LDAPIdentityProvidersGetter has a method to return a LDAPIdentityProviderInterface.
// A group's client should implement this interface.
type LDAPIdentityProvidersGetter interface {
LDAPIdentityProviders(namespace string) LDAPIdentityProviderInterface
}
// LDAPIdentityProviderInterface has methods to work with LDAPIdentityProvider resources.
type LDAPIdentityProviderInterface interface {
Create(ctx context.Context, lDAPIdentityProvider *v1alpha1.LDAPIdentityProvider, opts v1.CreateOptions) (*v1alpha1.LDAPIdentityProvider, error)
Update(ctx context.Context, lDAPIdentityProvider *v1alpha1.LDAPIdentityProvider, opts v1.UpdateOptions) (*v1alpha1.LDAPIdentityProvider, error)
UpdateStatus(ctx context.Context, lDAPIdentityProvider *v1alpha1.LDAPIdentityProvider, opts v1.UpdateOptions) (*v1alpha1.LDAPIdentityProvider, error)
Delete(ctx context.Context, name string, opts v1.DeleteOptions) error
DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error
Get(ctx context.Context, name string, opts v1.GetOptions) (*v1alpha1.LDAPIdentityProvider, error)
List(ctx context.Context, opts v1.ListOptions) (*v1alpha1.LDAPIdentityProviderList, error)
Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error)
Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.LDAPIdentityProvider, err error)
LDAPIdentityProviderExpansion
}
// lDAPIdentityProviders implements LDAPIdentityProviderInterface
type lDAPIdentityProviders struct {
client rest.Interface
ns string
}
// newLDAPIdentityProviders returns a LDAPIdentityProviders
func newLDAPIdentityProviders(c *IDPV1alpha1Client, namespace string) *lDAPIdentityProviders {
return &lDAPIdentityProviders{
client: c.RESTClient(),
ns: namespace,
}
}
// Get takes name of the lDAPIdentityProvider, and returns the corresponding lDAPIdentityProvider object, and an error if there is any.
func (c *lDAPIdentityProviders) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.LDAPIdentityProvider, err error) {
result = &v1alpha1.LDAPIdentityProvider{}
err = c.client.Get().
Namespace(c.ns).
Resource("ldapidentityproviders").
Name(name).
VersionedParams(&options, scheme.ParameterCodec).
Do(ctx).
Into(result)
return
}
// List takes label and field selectors, and returns the list of LDAPIdentityProviders that match those selectors.
func (c *lDAPIdentityProviders) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.LDAPIdentityProviderList, err error) {
var timeout time.Duration
if opts.TimeoutSeconds != nil {
timeout = time.Duration(*opts.TimeoutSeconds) * time.Second
}
result = &v1alpha1.LDAPIdentityProviderList{}
err = c.client.Get().
Namespace(c.ns).
Resource("ldapidentityproviders").
VersionedParams(&opts, scheme.ParameterCodec).
Timeout(timeout).
Do(ctx).
Into(result)
return
}
// Watch returns a watch.Interface that watches the requested lDAPIdentityProviders.
func (c *lDAPIdentityProviders) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) {
var timeout time.Duration
if opts.TimeoutSeconds != nil {
timeout = time.Duration(*opts.TimeoutSeconds) * time.Second
}
opts.Watch = true
return c.client.Get().
Namespace(c.ns).
Resource("ldapidentityproviders").
VersionedParams(&opts, scheme.ParameterCodec).
Timeout(timeout).
Watch(ctx)
}
// Create takes the representation of a lDAPIdentityProvider and creates it. Returns the server's representation of the lDAPIdentityProvider, and an error, if there is any.
func (c *lDAPIdentityProviders) Create(ctx context.Context, lDAPIdentityProvider *v1alpha1.LDAPIdentityProvider, opts v1.CreateOptions) (result *v1alpha1.LDAPIdentityProvider, err error) {
result = &v1alpha1.LDAPIdentityProvider{}
err = c.client.Post().
Namespace(c.ns).
Resource("ldapidentityproviders").
VersionedParams(&opts, scheme.ParameterCodec).
Body(lDAPIdentityProvider).
Do(ctx).
Into(result)
return
}
// Update takes the representation of a lDAPIdentityProvider and updates it. Returns the server's representation of the lDAPIdentityProvider, and an error, if there is any.
func (c *lDAPIdentityProviders) Update(ctx context.Context, lDAPIdentityProvider *v1alpha1.LDAPIdentityProvider, opts v1.UpdateOptions) (result *v1alpha1.LDAPIdentityProvider, err error) {
result = &v1alpha1.LDAPIdentityProvider{}
err = c.client.Put().
Namespace(c.ns).
Resource("ldapidentityproviders").
Name(lDAPIdentityProvider.Name).
VersionedParams(&opts, scheme.ParameterCodec).
Body(lDAPIdentityProvider).
Do(ctx).
Into(result)
return
}
// UpdateStatus was generated because the type contains a Status member.
// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus().
func (c *lDAPIdentityProviders) UpdateStatus(ctx context.Context, lDAPIdentityProvider *v1alpha1.LDAPIdentityProvider, opts v1.UpdateOptions) (result *v1alpha1.LDAPIdentityProvider, err error) {
result = &v1alpha1.LDAPIdentityProvider{}
err = c.client.Put().
Namespace(c.ns).
Resource("ldapidentityproviders").
Name(lDAPIdentityProvider.Name).
SubResource("status").
VersionedParams(&opts, scheme.ParameterCodec).
Body(lDAPIdentityProvider).
Do(ctx).
Into(result)
return
}
// Delete takes name of the lDAPIdentityProvider and deletes it. Returns an error if one occurs.
func (c *lDAPIdentityProviders) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error {
return c.client.Delete().
Namespace(c.ns).
Resource("ldapidentityproviders").
Name(name).
Body(&opts).
Do(ctx).
Error()
}
// DeleteCollection deletes a collection of objects.
func (c *lDAPIdentityProviders) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error {
var timeout time.Duration
if listOpts.TimeoutSeconds != nil {
timeout = time.Duration(*listOpts.TimeoutSeconds) * time.Second
}
return c.client.Delete().
Namespace(c.ns).
Resource("ldapidentityproviders").
VersionedParams(&listOpts, scheme.ParameterCodec).
Timeout(timeout).
Body(&opts).
Do(ctx).
Error()
}
// Patch applies the patch and returns the patched lDAPIdentityProvider.
func (c *lDAPIdentityProviders) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.LDAPIdentityProvider, err error) {
result = &v1alpha1.LDAPIdentityProvider{}
err = c.client.Patch(pt).
Namespace(c.ns).
Resource("ldapidentityproviders").
Name(name).
SubResource(subresources...).
VersionedParams(&opts, scheme.ParameterCodec).
Body(data).
Do(ctx).
Into(result)
return
}

View File

@ -45,6 +45,8 @@ func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource
return &genericInformer{resource: resource.GroupResource(), informer: f.Config().V1alpha1().FederationDomains().Informer()}, nil
// Group=idp.supervisor.pinniped.dev, Version=v1alpha1
case idpv1alpha1.SchemeGroupVersion.WithResource("ldapidentityproviders"):
return &genericInformer{resource: resource.GroupResource(), informer: f.IDP().V1alpha1().LDAPIdentityProviders().Informer()}, nil
case idpv1alpha1.SchemeGroupVersion.WithResource("oidcidentityproviders"):
return &genericInformer{resource: resource.GroupResource(), informer: f.IDP().V1alpha1().OIDCIdentityProviders().Informer()}, nil

View File

@ -11,6 +11,8 @@ import (
// Interface provides access to all the informers in this group version.
type Interface interface {
// LDAPIdentityProviders returns a LDAPIdentityProviderInformer.
LDAPIdentityProviders() LDAPIdentityProviderInformer
// OIDCIdentityProviders returns a OIDCIdentityProviderInformer.
OIDCIdentityProviders() OIDCIdentityProviderInformer
}
@ -26,6 +28,11 @@ func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakList
return &version{factory: f, namespace: namespace, tweakListOptions: tweakListOptions}
}
// LDAPIdentityProviders returns a LDAPIdentityProviderInformer.
func (v *version) LDAPIdentityProviders() LDAPIdentityProviderInformer {
return &lDAPIdentityProviderInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions}
}
// OIDCIdentityProviders returns a OIDCIdentityProviderInformer.
func (v *version) OIDCIdentityProviders() OIDCIdentityProviderInformer {
return &oIDCIdentityProviderInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions}

View File

@ -0,0 +1,77 @@
// Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
// Code generated by informer-gen. DO NOT EDIT.
package v1alpha1
import (
"context"
time "time"
idpv1alpha1 "go.pinniped.dev/generated/latest/apis/supervisor/idp/v1alpha1"
versioned "go.pinniped.dev/generated/latest/client/supervisor/clientset/versioned"
internalinterfaces "go.pinniped.dev/generated/latest/client/supervisor/informers/externalversions/internalinterfaces"
v1alpha1 "go.pinniped.dev/generated/latest/client/supervisor/listers/idp/v1alpha1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
runtime "k8s.io/apimachinery/pkg/runtime"
watch "k8s.io/apimachinery/pkg/watch"
cache "k8s.io/client-go/tools/cache"
)
// LDAPIdentityProviderInformer provides access to a shared informer and lister for
// LDAPIdentityProviders.
type LDAPIdentityProviderInformer interface {
Informer() cache.SharedIndexInformer
Lister() v1alpha1.LDAPIdentityProviderLister
}
type lDAPIdentityProviderInformer struct {
factory internalinterfaces.SharedInformerFactory
tweakListOptions internalinterfaces.TweakListOptionsFunc
namespace string
}
// NewLDAPIdentityProviderInformer constructs a new informer for LDAPIdentityProvider type.
// Always prefer using an informer factory to get a shared informer instead of getting an independent
// one. This reduces memory footprint and number of connections to the server.
func NewLDAPIdentityProviderInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer {
return NewFilteredLDAPIdentityProviderInformer(client, namespace, resyncPeriod, indexers, nil)
}
// NewFilteredLDAPIdentityProviderInformer constructs a new informer for LDAPIdentityProvider type.
// Always prefer using an informer factory to get a shared informer instead of getting an independent
// one. This reduces memory footprint and number of connections to the server.
func NewFilteredLDAPIdentityProviderInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer {
return cache.NewSharedIndexInformer(
&cache.ListWatch{
ListFunc: func(options v1.ListOptions) (runtime.Object, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.IDPV1alpha1().LDAPIdentityProviders(namespace).List(context.TODO(), options)
},
WatchFunc: func(options v1.ListOptions) (watch.Interface, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.IDPV1alpha1().LDAPIdentityProviders(namespace).Watch(context.TODO(), options)
},
},
&idpv1alpha1.LDAPIdentityProvider{},
resyncPeriod,
indexers,
)
}
func (f *lDAPIdentityProviderInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer {
return NewFilteredLDAPIdentityProviderInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions)
}
func (f *lDAPIdentityProviderInformer) Informer() cache.SharedIndexInformer {
return f.factory.InformerFor(&idpv1alpha1.LDAPIdentityProvider{}, f.defaultInformer)
}
func (f *lDAPIdentityProviderInformer) Lister() v1alpha1.LDAPIdentityProviderLister {
return v1alpha1.NewLDAPIdentityProviderLister(f.Informer().GetIndexer())
}

View File

@ -5,6 +5,14 @@
package v1alpha1
// LDAPIdentityProviderListerExpansion allows custom methods to be added to
// LDAPIdentityProviderLister.
type LDAPIdentityProviderListerExpansion interface{}
// LDAPIdentityProviderNamespaceListerExpansion allows custom methods to be added to
// LDAPIdentityProviderNamespaceLister.
type LDAPIdentityProviderNamespaceListerExpansion interface{}
// OIDCIdentityProviderListerExpansion allows custom methods to be added to
// OIDCIdentityProviderLister.
type OIDCIdentityProviderListerExpansion interface{}

View File

@ -0,0 +1,86 @@
// Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
// Code generated by lister-gen. DO NOT EDIT.
package v1alpha1
import (
v1alpha1 "go.pinniped.dev/generated/latest/apis/supervisor/idp/v1alpha1"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/client-go/tools/cache"
)
// LDAPIdentityProviderLister helps list LDAPIdentityProviders.
// All objects returned here must be treated as read-only.
type LDAPIdentityProviderLister interface {
// List lists all LDAPIdentityProviders in the indexer.
// Objects returned here must be treated as read-only.
List(selector labels.Selector) (ret []*v1alpha1.LDAPIdentityProvider, err error)
// LDAPIdentityProviders returns an object that can list and get LDAPIdentityProviders.
LDAPIdentityProviders(namespace string) LDAPIdentityProviderNamespaceLister
LDAPIdentityProviderListerExpansion
}
// lDAPIdentityProviderLister implements the LDAPIdentityProviderLister interface.
type lDAPIdentityProviderLister struct {
indexer cache.Indexer
}
// NewLDAPIdentityProviderLister returns a new LDAPIdentityProviderLister.
func NewLDAPIdentityProviderLister(indexer cache.Indexer) LDAPIdentityProviderLister {
return &lDAPIdentityProviderLister{indexer: indexer}
}
// List lists all LDAPIdentityProviders in the indexer.
func (s *lDAPIdentityProviderLister) List(selector labels.Selector) (ret []*v1alpha1.LDAPIdentityProvider, err error) {
err = cache.ListAll(s.indexer, selector, func(m interface{}) {
ret = append(ret, m.(*v1alpha1.LDAPIdentityProvider))
})
return ret, err
}
// LDAPIdentityProviders returns an object that can list and get LDAPIdentityProviders.
func (s *lDAPIdentityProviderLister) LDAPIdentityProviders(namespace string) LDAPIdentityProviderNamespaceLister {
return lDAPIdentityProviderNamespaceLister{indexer: s.indexer, namespace: namespace}
}
// LDAPIdentityProviderNamespaceLister helps list and get LDAPIdentityProviders.
// All objects returned here must be treated as read-only.
type LDAPIdentityProviderNamespaceLister interface {
// List lists all LDAPIdentityProviders in the indexer for a given namespace.
// Objects returned here must be treated as read-only.
List(selector labels.Selector) (ret []*v1alpha1.LDAPIdentityProvider, err error)
// Get retrieves the LDAPIdentityProvider from the indexer for a given namespace and name.
// Objects returned here must be treated as read-only.
Get(name string) (*v1alpha1.LDAPIdentityProvider, error)
LDAPIdentityProviderNamespaceListerExpansion
}
// lDAPIdentityProviderNamespaceLister implements the LDAPIdentityProviderNamespaceLister
// interface.
type lDAPIdentityProviderNamespaceLister struct {
indexer cache.Indexer
namespace string
}
// List lists all LDAPIdentityProviders in the indexer for a given namespace.
func (s lDAPIdentityProviderNamespaceLister) List(selector labels.Selector) (ret []*v1alpha1.LDAPIdentityProvider, err error) {
err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) {
ret = append(ret, m.(*v1alpha1.LDAPIdentityProvider))
})
return ret, err
}
// Get retrieves the LDAPIdentityProvider from the indexer for a given namespace and name.
func (s lDAPIdentityProviderNamespaceLister) Get(name string) (*v1alpha1.LDAPIdentityProvider, error) {
obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name)
if err != nil {
return nil, err
}
if !exists {
return nil, errors.NewNotFound(v1alpha1.Resource("ldapidentityprovider"), name)
}
return obj.(*v1alpha1.LDAPIdentityProvider), nil
}

3
go.mod
View File

@ -6,7 +6,9 @@ require (
cloud.google.com/go v0.60.0 // indirect
github.com/MakeNowJust/heredoc/v2 v2.0.1
github.com/coreos/go-oidc/v3 v3.0.0
github.com/creack/pty v1.1.11
github.com/davecgh/go-spew v1.1.1
github.com/go-ldap/ldap/v3 v3.3.0
github.com/go-logr/logr v0.4.0
github.com/go-logr/stdr v0.4.0
github.com/go-openapi/spec v0.20.3
@ -30,6 +32,7 @@ require (
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d
gopkg.in/square/go-jose.v2 v2.5.1
k8s.io/api v0.21.0
k8s.io/apimachinery v0.21.0

8
go.sum
View File

@ -47,6 +47,8 @@ github.com/Azure/go-autorest/logger v0.2.0 h1:e4RVHVZKC5p6UANLJHkM4OfR1UKZPj8Wt8
github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8=
github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo=
github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c h1:/IBSNwUN8+eKzUzbJPqhK839ygXJ82sde8x3ogr6R28=
github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
@ -146,6 +148,7 @@ github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw=
github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/cucumber/godog v0.8.1/go.mod h1:vSh3r/lM+psC1BPXvdkSEuNjmXfpVqrMGYAElF6hxnA=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@ -198,12 +201,16 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-asn1-ber/asn1-ber v1.5.1 h1:pDbRAunXzIUXfx4CB2QJFv5IuPiuoW+sWvr/Us009o8=
github.com/go-asn1-ber/asn1-ber v1.5.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
github.com/go-bindata/go-bindata v3.1.1+incompatible/go.mod h1:xK8Dsgwmeed+BBsSy2XTopBn/8uK2HWuGSnA11C3Joo=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-ldap/ldap/v3 v3.3.0 h1:lwx+SJpgOHd8tG6SumBQZXCmNX51zM8B1cfxJ5gv4tQ=
github.com/go-ldap/ldap/v3 v3.3.0/go.mod h1:iYS1MdmrmceOJ1QOTnRXrIs7i3kloqtmGQjRvjKpyMg=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=
@ -1094,6 +1101,7 @@ golang.org/x/crypto v0.0.0-20200117160349-530e935923ad/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20200320181102-891825fb96df/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=

View File

@ -20,6 +20,34 @@ set -euo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "$ROOT"
use_oidc_upstream=no
use_ldap_upstream=no
while (("$#")); do
case "$1" in
--ldap)
use_ldap_upstream=yes
shift
;;
--oidc)
use_oidc_upstream=yes
shift
;;
-*)
log_error "Unsupported flag $1" >&2
exit 1
;;
*)
log_error "Unsupported positional arg $1" >&2
exit 1
;;
esac
done
if [[ "$use_oidc_upstream" == "no" && "$use_ldap_upstream" == "no" ]]; then
echo "Error: Please use --oidc or --ldap to specify which type of upstream identity provider(s) you would like"
exit 1
fi
# Read the env vars output by hack/prepare-for-integration-tests.sh
source /tmp/integration-test-env
@ -73,8 +101,9 @@ sleep 5
echo "Fetching FederationDomain discovery info..."
https_proxy="$PINNIPED_TEST_PROXY" curl -fLsS --cacert "$root_ca_crt_path" "$issuer/.well-known/openid-configuration" | jq .
# Make an OIDCIdentityProvider which uses Dex to provide identity.
cat <<EOF | kubectl apply --namespace "$PINNIPED_TEST_SUPERVISOR_NAMESPACE" -f -
if [[ "$use_oidc_upstream" == "yes" ]]; then
# Make an OIDCIdentityProvider which uses Dex to provide identity.
cat <<EOF | kubectl apply --namespace "$PINNIPED_TEST_SUPERVISOR_NAMESPACE" -f -
apiVersion: idp.supervisor.pinniped.dev/v1alpha1
kind: OIDCIdentityProvider
metadata:
@ -92,8 +121,8 @@ spec:
secretName: my-oidc-provider-client-secret
EOF
# Make a Secret for the above OIDCIdentityProvider to describe the OIDC client configured in Dex.
cat <<EOF | kubectl apply --namespace "$PINNIPED_TEST_SUPERVISOR_NAMESPACE" -f -
# Make a Secret for the above OIDCIdentityProvider to describe the OIDC client configured in Dex.
cat <<EOF | kubectl apply --namespace "$PINNIPED_TEST_SUPERVISOR_NAMESPACE" -f -
apiVersion: v1
kind: Secret
metadata:
@ -104,10 +133,50 @@ stringData:
type: "secrets.pinniped.dev/oidc-client"
EOF
# Grant the test user some RBAC permissions so we can play with kubectl as that user.
kubectl create clusterrolebinding test-user-can-view --clusterrole view \
--user "$PINNIPED_TEST_SUPERVISOR_UPSTREAM_OIDC_USERNAME" \
--dry-run=client --output yaml | kubectl apply -f -
# Grant the test user some RBAC permissions so we can play with kubectl as that user.
kubectl create clusterrolebinding oidc-test-user-can-view --clusterrole view \
--user "$PINNIPED_TEST_SUPERVISOR_UPSTREAM_OIDC_USERNAME" \
--dry-run=client --output yaml | kubectl apply -f -
fi
if [[ "$use_ldap_upstream" == "yes" ]]; then
# Make an LDAPIdentityProvider which uses OpenLDAP to provide identity.
cat <<EOF | kubectl apply --namespace "$PINNIPED_TEST_SUPERVISOR_NAMESPACE" -f -
apiVersion: idp.supervisor.pinniped.dev/v1alpha1
kind: LDAPIdentityProvider
metadata:
name: my-ldap-provider
spec:
host: "$PINNIPED_TEST_LDAP_HOST"
tls:
certificateAuthorityData: "$PINNIPED_TEST_LDAP_LDAPS_CA_BUNDLE"
bind:
secretName: my-ldap-service-account
userSearch:
base: "$PINNIPED_TEST_LDAP_USERS_SEARCH_BASE"
filter: "cn={}"
attributes:
uid: "$PINNIPED_TEST_LDAP_USER_UNIQUE_ID_ATTRIBUTE_NAME"
username: "$PINNIPED_TEST_LDAP_USER_EMAIL_ATTRIBUTE_NAME"
EOF
# Make a Secret for the above LDAPIdentityProvider to describe the bind account.
cat <<EOF | kubectl apply --namespace "$PINNIPED_TEST_SUPERVISOR_NAMESPACE" -f -
apiVersion: v1
kind: Secret
metadata:
name: my-ldap-service-account
stringData:
username: "$PINNIPED_TEST_LDAP_BIND_ACCOUNT_USERNAME"
password: "$PINNIPED_TEST_LDAP_BIND_ACCOUNT_PASSWORD"
type: "kubernetes.io/basic-auth"
EOF
# Grant the test user some RBAC permissions so we can play with kubectl as that user.
kubectl create clusterrolebinding ldap-test-user-can-view --clusterrole view \
--user "$PINNIPED_TEST_LDAP_USER_EMAIL_ATTRIBUTE_VALUE" \
--dry-run=client --output yaml | kubectl apply -f -
fi
# Make a JWTAuthenticator which respects JWTs from the Supervisor's issuer.
# The issuer URL must be accessible from within the cluster for OIDC discovery.
@ -124,27 +193,39 @@ spec:
EOF
echo "Waiting for JWTAuthenticator to initialize..."
# Our integration tests wait 10 seconds, so use that same value here.
sleep 10
sleep 5
# Compile the CLI.
go build ./cmd/pinniped
# Use the CLI to get the kubeconfig. Tell it that you don't want the browser to automatically open for logins.
./pinniped get kubeconfig --oidc-skip-browser >kubeconfig
https_proxy="$PINNIPED_TEST_PROXY" no_proxy="127.0.0.1" ./pinniped get kubeconfig --oidc-skip-browser >kubeconfig
# Clear the local CLI cache to ensure that the kubectl command below will need to perform a fresh login.
rm -f "$HOME"/.config/pinniped/sessions.yaml
rm -f "$HOME/.config/pinniped/sessions.yaml"
rm -f "$HOME/.config/pinniped/credentials.yaml"
echo
echo "Ready! 🚀"
echo "To be able to access the login URL shown below, start Chrome like this:"
echo " open -a \"Google Chrome\" --args --proxy-server=\"$PINNIPED_TEST_PROXY\""
echo "Then use these credentials at the Dex login page:"
echo " Username: $PINNIPED_TEST_SUPERVISOR_UPSTREAM_OIDC_USERNAME"
echo " Password: $PINNIPED_TEST_SUPERVISOR_UPSTREAM_OIDC_PASSWORD"
# Perform a login using the kubectl plugin. This should print the URL to be followed for the Dex login page.
if [[ "$use_oidc_upstream" == "yes" ]]; then
echo
echo "To be able to access the login URL shown below, start Chrome like this:"
echo " open -a \"Google Chrome\" --args --proxy-server=\"$PINNIPED_TEST_PROXY\""
echo "Then use these credentials at the Dex login page:"
echo " Username: $PINNIPED_TEST_SUPERVISOR_UPSTREAM_OIDC_USERNAME"
echo " Password: $PINNIPED_TEST_SUPERVISOR_UPSTREAM_OIDC_PASSWORD"
fi
if [[ "$use_ldap_upstream" == "yes" ]]; then
echo
echo "When prompted for username and password by the CLI, use these values:"
echo " Username: $PINNIPED_TEST_LDAP_USER_CN"
echo " Password: $PINNIPED_TEST_LDAP_USER_PASSWORD"
fi
# Perform a login using the kubectl plugin. This should print the URL to be followed for the Dex login page
# if using an OIDC upstream, or should prompt on the CLI for username/password if using an LDAP upstream.
echo
echo "Running: https_proxy=\"$PINNIPED_TEST_PROXY\" no_proxy=\"127.0.0.1\" kubectl --kubeconfig ./kubeconfig get pods -A"
https_proxy="$PINNIPED_TEST_PROXY" no_proxy="127.0.0.1" kubectl --kubeconfig ./kubeconfig get pods -A

View File

@ -0,0 +1,35 @@
// Copyright 2021 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
// Package authenticators contains authenticator interfaces.
package authenticators
import (
"context"
"k8s.io/apiserver/pkg/authentication/authenticator"
)
// This interface is similar to the k8s token authenticator, but works with username/passwords instead
// of a single token string.
//
// The return values should be as follows.
// 1. For a successful authentication:
// - A response which includes the username, uid, and groups in the userInfo. The username and uid must not be blank.
// - true
// - nil error
// 2. For an unsuccessful authentication, e.g. bad username or password:
// - nil response
// - false
// - nil error
// 3. For an unexpected error, e.g. a network problem:
// - nil response
// - false
// - an error
// Other combinations of return values must be avoided.
//
// See k8s.io/apiserver/pkg/authentication/authenticator/interfaces.go for the token authenticator
// interface, as well as the Response type.
type UserAuthenticator interface {
AuthenticateUser(ctx context.Context, username, password string) (*authenticator.Response, bool, error)
}

View File

@ -10,6 +10,7 @@ import (
"io/ioutil"
"strings"
"k8s.io/utils/pointer"
"sigs.k8s.io/yaml"
"go.pinniped.dev/internal/constable"
@ -69,27 +70,27 @@ func FromPath(path string) (*Config, error) {
func maybeSetAPIDefaults(apiConfig *APIConfigSpec) {
if apiConfig.ServingCertificateConfig.DurationSeconds == nil {
apiConfig.ServingCertificateConfig.DurationSeconds = int64Ptr(aboutAYear)
apiConfig.ServingCertificateConfig.DurationSeconds = pointer.Int64Ptr(aboutAYear)
}
if apiConfig.ServingCertificateConfig.RenewBeforeSeconds == nil {
apiConfig.ServingCertificateConfig.RenewBeforeSeconds = int64Ptr(about9Months)
apiConfig.ServingCertificateConfig.RenewBeforeSeconds = pointer.Int64Ptr(about9Months)
}
}
func maybeSetAPIGroupSuffixDefault(apiGroupSuffix **string) {
if *apiGroupSuffix == nil {
*apiGroupSuffix = stringPtr(groupsuffix.PinnipedDefaultSuffix)
*apiGroupSuffix = pointer.StringPtr(groupsuffix.PinnipedDefaultSuffix)
}
}
func maybeSetKubeCertAgentDefaults(cfg *KubeCertAgentSpec) {
if cfg.NamePrefix == nil {
cfg.NamePrefix = stringPtr("pinniped-kube-cert-agent-")
cfg.NamePrefix = pointer.StringPtr("pinniped-kube-cert-agent-")
}
if cfg.Image == nil {
cfg.Image = stringPtr("debian:latest")
cfg.Image = pointer.StringPtr("debian:latest")
}
}
@ -146,11 +147,3 @@ func validateAPI(apiConfig *APIConfigSpec) error {
func validateAPIGroupSuffix(apiGroupSuffix string) error {
return groupsuffix.Validate(apiGroupSuffix)
}
func int64Ptr(i int64) *int64 {
return &i
}
func stringPtr(s string) *string {
return &s
}

View File

@ -9,6 +9,7 @@ import (
"testing"
"github.com/stretchr/testify/require"
"k8s.io/utils/pointer"
"go.pinniped.dev/internal/here"
"go.pinniped.dev/internal/plog"
@ -55,15 +56,15 @@ func TestFromPath(t *testing.T) {
`),
wantConfig: &Config{
DiscoveryInfo: DiscoveryInfoSpec{
URL: stringPtr("https://some.discovery/url"),
URL: pointer.StringPtr("https://some.discovery/url"),
},
APIConfig: APIConfigSpec{
ServingCertificateConfig: ServingCertificateConfigSpec{
DurationSeconds: int64Ptr(3600),
RenewBeforeSeconds: int64Ptr(2400),
DurationSeconds: pointer.Int64Ptr(3600),
RenewBeforeSeconds: pointer.Int64Ptr(2400),
},
},
APIGroupSuffix: stringPtr("some.suffix.com"),
APIGroupSuffix: pointer.StringPtr("some.suffix.com"),
NamesConfig: NamesConfigSpec{
ServingCertificateSecret: "pinniped-concierge-api-tls-serving-certificate",
CredentialIssuer: "pinniped-config",
@ -80,8 +81,8 @@ func TestFromPath(t *testing.T) {
"myLabelKey2": "myLabelValue2",
},
KubeCertAgentConfig: KubeCertAgentSpec{
NamePrefix: stringPtr("kube-cert-agent-name-prefix-"),
Image: stringPtr("kube-cert-agent-image"),
NamePrefix: pointer.StringPtr("kube-cert-agent-name-prefix-"),
Image: pointer.StringPtr("kube-cert-agent-image"),
ImagePullSecrets: []string{"kube-cert-agent-image-pull-secret"},
},
LogLevel: plog.LevelDebug,
@ -106,11 +107,11 @@ func TestFromPath(t *testing.T) {
DiscoveryInfo: DiscoveryInfoSpec{
URL: nil,
},
APIGroupSuffix: stringPtr("pinniped.dev"),
APIGroupSuffix: pointer.StringPtr("pinniped.dev"),
APIConfig: APIConfigSpec{
ServingCertificateConfig: ServingCertificateConfigSpec{
DurationSeconds: int64Ptr(60 * 60 * 24 * 365), // about a year
RenewBeforeSeconds: int64Ptr(60 * 60 * 24 * 30 * 9), // about 9 months
DurationSeconds: pointer.Int64Ptr(60 * 60 * 24 * 365), // about a year
RenewBeforeSeconds: pointer.Int64Ptr(60 * 60 * 24 * 30 * 9), // about 9 months
},
},
NamesConfig: NamesConfigSpec{
@ -126,8 +127,8 @@ func TestFromPath(t *testing.T) {
},
Labels: map[string]string{},
KubeCertAgentConfig: KubeCertAgentSpec{
NamePrefix: stringPtr("pinniped-kube-cert-agent-"),
Image: stringPtr("debian:latest"),
NamePrefix: pointer.StringPtr("pinniped-kube-cert-agent-"),
Image: pointer.StringPtr("debian:latest"),
},
},
},

View File

@ -10,6 +10,7 @@ import (
"io/ioutil"
"strings"
"k8s.io/utils/pointer"
"sigs.k8s.io/yaml"
"go.pinniped.dev/internal/constable"
@ -54,7 +55,7 @@ func FromPath(path string) (*Config, error) {
func maybeSetAPIGroupSuffixDefault(apiGroupSuffix **string) {
if *apiGroupSuffix == nil {
*apiGroupSuffix = stringPtr(groupsuffix.PinnipedDefaultSuffix)
*apiGroupSuffix = pointer.StringPtr(groupsuffix.PinnipedDefaultSuffix)
}
}
@ -72,7 +73,3 @@ func validateNames(names *NamesConfigSpec) error {
}
return nil
}
func stringPtr(s string) *string {
return &s
}

View File

@ -8,6 +8,8 @@ import (
"os"
"testing"
"k8s.io/utils/pointer"
"github.com/stretchr/testify/require"
"go.pinniped.dev/internal/here"
@ -32,7 +34,7 @@ func TestFromPath(t *testing.T) {
defaultTLSCertificateSecret: my-secret-name
`),
wantConfig: &Config{
APIGroupSuffix: stringPtr("some.suffix.com"),
APIGroupSuffix: pointer.StringPtr("some.suffix.com"),
Labels: map[string]string{
"myLabelKey1": "myLabelValue1",
"myLabelKey2": "myLabelValue2",
@ -50,7 +52,7 @@ func TestFromPath(t *testing.T) {
defaultTLSCertificateSecret: my-secret-name
`),
wantConfig: &Config{
APIGroupSuffix: stringPtr("pinniped.dev"),
APIGroupSuffix: pointer.StringPtr("pinniped.dev"),
Labels: map[string]string{},
NamesConfig: NamesConfigSpec{
DefaultTLSCertificateSecret: "my-secret-name",

View File

@ -0,0 +1,68 @@
// Copyright 2021 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package conditionsutil
import (
"sort"
"github.com/go-logr/logr"
"k8s.io/apimachinery/pkg/api/equality"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"go.pinniped.dev/generated/latest/apis/supervisor/idp/v1alpha1"
)
// Merge merges conditions into conditionsToUpdate. If returns true if it merged any error conditions.
func Merge(conditions []*v1alpha1.Condition, observedGeneration int64, conditionsToUpdate *[]v1alpha1.Condition, log logr.Logger) bool {
hadErrorCondition := false
for i := range conditions {
cond := conditions[i].DeepCopy()
cond.LastTransitionTime = v1.Now()
cond.ObservedGeneration = observedGeneration
if mergeCondition(conditionsToUpdate, cond) {
log.Info("updated condition", "type", cond.Type, "status", cond.Status, "reason", cond.Reason, "message", cond.Message)
}
if cond.Status == v1alpha1.ConditionFalse {
hadErrorCondition = true
}
}
sort.SliceStable(*conditionsToUpdate, func(i, j int) bool {
return (*conditionsToUpdate)[i].Type < (*conditionsToUpdate)[j].Type
})
return hadErrorCondition
}
// mergeCondition merges a new v1alpha1.Condition into a slice of existing conditions. It returns true
// if the condition has meaningfully changed.
func mergeCondition(existing *[]v1alpha1.Condition, new *v1alpha1.Condition) bool {
// Find any existing condition with a matching type.
var old *v1alpha1.Condition
for i := range *existing {
if (*existing)[i].Type == new.Type {
old = &(*existing)[i]
continue
}
}
// If there is no existing condition of this type, append this one and we're done.
if old == nil {
*existing = append(*existing, *new)
return true
}
// Set the LastTransitionTime depending on whether the status has changed.
new = new.DeepCopy()
if old.Status == new.Status {
new.LastTransitionTime = old.LastTransitionTime
}
// If anything has actually changed, update the entry and return true.
if !equality.Semantic.DeepEqual(old, new) {
*old = *new
return true
}
// Otherwise the entry is already up to date.
return false
}

View File

@ -0,0 +1,372 @@
// Copyright 2021 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
// Package ldapupstreamwatcher implements a controller which watches LDAPIdentityProviders.
package ldapupstreamwatcher
import (
"context"
"crypto/x509"
"encoding/base64"
"fmt"
"time"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/equality"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
corev1informers "k8s.io/client-go/informers/core/v1"
"k8s.io/klog/v2/klogr"
"go.pinniped.dev/generated/latest/apis/supervisor/idp/v1alpha1"
pinnipedclientset "go.pinniped.dev/generated/latest/client/supervisor/clientset/versioned"
idpinformers "go.pinniped.dev/generated/latest/client/supervisor/informers/externalversions/idp/v1alpha1"
pinnipedcontroller "go.pinniped.dev/internal/controller"
"go.pinniped.dev/internal/controller/conditionsutil"
"go.pinniped.dev/internal/controller/supervisorconfig/upstreamwatchers"
"go.pinniped.dev/internal/controllerlib"
"go.pinniped.dev/internal/oidc/provider"
"go.pinniped.dev/internal/upstreamldap"
)
const (
ldapControllerName = "ldap-upstream-observer"
ldapBindAccountSecretType = corev1.SecretTypeBasicAuth
testLDAPConnectionTimeout = 90 * time.Second
// Constants related to conditions.
typeBindSecretValid = "BindSecretValid"
typeTLSConfigurationValid = "TLSConfigurationValid"
typeLDAPConnectionValid = "LDAPConnectionValid"
reasonLDAPConnectionError = "LDAPConnectionError"
noTLSConfigurationMessage = "no TLS configuration provided"
loadedTLSConfigurationMessage = "loaded TLS configuration"
)
// UpstreamLDAPIdentityProviderICache is a thread safe cache that holds a list of validated upstream LDAP IDP configurations.
type UpstreamLDAPIdentityProviderICache interface {
SetLDAPIdentityProviders([]provider.UpstreamLDAPIdentityProviderI)
}
type ldapWatcherController struct {
cache UpstreamLDAPIdentityProviderICache
validatedSecretVersionsCache *secretVersionCache
ldapDialer upstreamldap.LDAPDialer
client pinnipedclientset.Interface
ldapIdentityProviderInformer idpinformers.LDAPIdentityProviderInformer
secretInformer corev1informers.SecretInformer
}
// An in-memory cache with an entry for each LDAPIdentityProvider, to keep track of which ResourceVersion
// of the bind Secret was used during the most recent successful validation.
type secretVersionCache struct {
ResourceVersionsByName map[string]string
}
func newSecretVersionCache() *secretVersionCache {
return &secretVersionCache{ResourceVersionsByName: map[string]string{}}
}
// New instantiates a new controllerlib.Controller which will populate the provided UpstreamLDAPIdentityProviderICache.
func New(
idpCache UpstreamLDAPIdentityProviderICache,
client pinnipedclientset.Interface,
ldapIdentityProviderInformer idpinformers.LDAPIdentityProviderInformer,
secretInformer corev1informers.SecretInformer,
withInformer pinnipedcontroller.WithInformerOptionFunc,
) controllerlib.Controller {
return newInternal(
idpCache,
// start with an empty secretVersionCache
newSecretVersionCache(),
// nil means to use a real production dialer when creating objects to add to the cache
nil,
client,
ldapIdentityProviderInformer,
secretInformer,
withInformer,
)
}
// For test dependency injection purposes.
func newInternal(
idpCache UpstreamLDAPIdentityProviderICache,
validatedSecretVersionsCache *secretVersionCache,
ldapDialer upstreamldap.LDAPDialer,
client pinnipedclientset.Interface,
ldapIdentityProviderInformer idpinformers.LDAPIdentityProviderInformer,
secretInformer corev1informers.SecretInformer,
withInformer pinnipedcontroller.WithInformerOptionFunc,
) controllerlib.Controller {
c := ldapWatcherController{
cache: idpCache,
validatedSecretVersionsCache: validatedSecretVersionsCache,
ldapDialer: ldapDialer,
client: client,
ldapIdentityProviderInformer: ldapIdentityProviderInformer,
secretInformer: secretInformer,
}
return controllerlib.New(
controllerlib.Config{Name: ldapControllerName, Syncer: &c},
withInformer(
ldapIdentityProviderInformer,
pinnipedcontroller.MatchAnythingFilter(pinnipedcontroller.SingletonQueue()),
controllerlib.InformerOption{},
),
withInformer(
secretInformer,
pinnipedcontroller.MatchAnySecretOfTypeFilter(ldapBindAccountSecretType, pinnipedcontroller.SingletonQueue()),
controllerlib.InformerOption{},
),
)
}
// Sync implements controllerlib.Syncer.
func (c *ldapWatcherController) Sync(ctx controllerlib.Context) error {
actualUpstreams, err := c.ldapIdentityProviderInformer.Lister().List(labels.Everything())
if err != nil {
return fmt.Errorf("failed to list LDAPIdentityProviders: %w", err)
}
requeue := false
validatedUpstreams := make([]provider.UpstreamLDAPIdentityProviderI, 0, len(actualUpstreams))
for _, upstream := range actualUpstreams {
valid, requestedRequeue := c.validateUpstream(ctx.Context, upstream)
if valid != nil {
validatedUpstreams = append(validatedUpstreams, valid)
}
if requestedRequeue {
requeue = true
}
}
c.cache.SetLDAPIdentityProviders(validatedUpstreams)
if requeue {
return controllerlib.ErrSyntheticRequeue
}
return nil
}
func (c *ldapWatcherController) validateUpstream(ctx context.Context, upstream *v1alpha1.LDAPIdentityProvider) (p provider.UpstreamLDAPIdentityProviderI, requeue bool) {
spec := upstream.Spec
config := &upstreamldap.ProviderConfig{
Name: upstream.Name,
Host: spec.Host,
UserSearch: upstreamldap.UserSearchConfig{
Base: spec.UserSearch.Base,
Filter: spec.UserSearch.Filter,
UsernameAttribute: spec.UserSearch.Attributes.Username,
UIDAttribute: spec.UserSearch.Attributes.UID,
},
Dialer: c.ldapDialer,
}
conditions := []*v1alpha1.Condition{}
secretValidCondition, currentSecretVersion := c.validateSecret(upstream, config)
tlsValidCondition := c.validateTLSConfig(upstream, config)
conditions = append(conditions, secretValidCondition, tlsValidCondition)
// No point in trying to connect to the server if the config was already determined to be invalid.
var finishedConfigCondition *v1alpha1.Condition
if secretValidCondition.Status == v1alpha1.ConditionTrue && tlsValidCondition.Status == v1alpha1.ConditionTrue {
finishedConfigCondition = c.validateFinishedConfig(ctx, upstream, config, currentSecretVersion)
if finishedConfigCondition != nil {
conditions = append(conditions, finishedConfigCondition)
}
}
c.updateStatus(ctx, upstream, conditions)
switch {
case secretValidCondition.Status != v1alpha1.ConditionTrue || tlsValidCondition.Status != v1alpha1.ConditionTrue:
// Invalid provider, so do not load it into the cache.
p = nil
requeue = true
case finishedConfigCondition != nil && finishedConfigCondition.Status != v1alpha1.ConditionTrue:
// Error but load it into the cache anyway, treating this condition failure more like a warning.
p = upstreamldap.New(*config)
// Try again hoping that the condition will improve.
requeue = true
default:
// Fully validated provider, so load it into the cache.
p = upstreamldap.New(*config)
requeue = false
}
return p, requeue
}
func (c *ldapWatcherController) validateTLSConfig(upstream *v1alpha1.LDAPIdentityProvider, config *upstreamldap.ProviderConfig) *v1alpha1.Condition {
tlsSpec := upstream.Spec.TLS
if tlsSpec == nil {
return c.validTLSCondition(noTLSConfigurationMessage)
}
if len(tlsSpec.CertificateAuthorityData) == 0 {
return c.validTLSCondition(loadedTLSConfigurationMessage)
}
bundle, err := base64.StdEncoding.DecodeString(tlsSpec.CertificateAuthorityData)
if err != nil {
return c.invalidTLSCondition(fmt.Sprintf("certificateAuthorityData is invalid: %s", err.Error()))
}
ca := x509.NewCertPool()
ok := ca.AppendCertsFromPEM(bundle)
if !ok {
return c.invalidTLSCondition(fmt.Sprintf("certificateAuthorityData is invalid: %s", upstreamwatchers.ErrNoCertificates))
}
config.CABundle = bundle
return c.validTLSCondition(loadedTLSConfigurationMessage)
}
func (c *ldapWatcherController) validateFinishedConfig(ctx context.Context, upstream *v1alpha1.LDAPIdentityProvider, config *upstreamldap.ProviderConfig, currentSecretVersion string) *v1alpha1.Condition {
ldapProvider := upstreamldap.New(*config)
if c.hasPreviousSuccessfulConditionForCurrentSpecGenerationAndSecretVersion(upstream, currentSecretVersion) {
return nil
}
testConnectionTimeout, cancelFunc := context.WithTimeout(ctx, testLDAPConnectionTimeout)
defer cancelFunc()
condition := c.testConnection(testConnectionTimeout, upstream, config, ldapProvider, currentSecretVersion)
if condition.Status == v1alpha1.ConditionTrue {
// Remember (in-memory for this pod) that the controller has successfully validated the LDAP provider
// using this version of the Secret. This is for performance reasons, to avoid attempting to connect to
// the LDAP server more than is needed. If the pod restarts, it will attempt this validation again.
c.validatedSecretVersionsCache.ResourceVersionsByName[upstream.GetName()] = currentSecretVersion
}
return condition
}
func (c *ldapWatcherController) testConnection(
ctx context.Context,
upstream *v1alpha1.LDAPIdentityProvider,
config *upstreamldap.ProviderConfig,
ldapProvider *upstreamldap.Provider,
currentSecretVersion string,
) *v1alpha1.Condition {
err := ldapProvider.TestConnection(ctx)
if err != nil {
return &v1alpha1.Condition{
Type: typeLDAPConnectionValid,
Status: v1alpha1.ConditionFalse,
Reason: reasonLDAPConnectionError,
Message: fmt.Sprintf(`could not successfully connect to "%s" and bind as user "%s": %s`,
config.Host, config.BindUsername, err.Error()),
}
}
return &v1alpha1.Condition{
Type: typeLDAPConnectionValid,
Status: v1alpha1.ConditionTrue,
Reason: upstreamwatchers.ReasonSuccess,
Message: fmt.Sprintf(`successfully able to connect to "%s" and bind as user "%s" [validated with Secret "%s" at version "%s"]`,
config.Host, config.BindUsername, upstream.Spec.Bind.SecretName, currentSecretVersion),
}
}
func (c *ldapWatcherController) hasPreviousSuccessfulConditionForCurrentSpecGenerationAndSecretVersion(upstream *v1alpha1.LDAPIdentityProvider, currentSecretVersion string) bool {
currentGeneration := upstream.Generation
for _, cond := range upstream.Status.Conditions {
if cond.Type == typeLDAPConnectionValid && cond.Status == v1alpha1.ConditionTrue && cond.ObservedGeneration == currentGeneration {
// Found a previously successful condition for the current spec generation.
// Now figure out which version of the bind Secret was used during that previous validation, if any.
validatedSecretVersion := c.validatedSecretVersionsCache.ResourceVersionsByName[upstream.GetName()]
if validatedSecretVersion == currentSecretVersion {
return true
}
}
}
return false
}
func (c *ldapWatcherController) validTLSCondition(message string) *v1alpha1.Condition {
return &v1alpha1.Condition{
Type: typeTLSConfigurationValid,
Status: v1alpha1.ConditionTrue,
Reason: upstreamwatchers.ReasonSuccess,
Message: message,
}
}
func (c *ldapWatcherController) invalidTLSCondition(message string) *v1alpha1.Condition {
return &v1alpha1.Condition{
Type: typeTLSConfigurationValid,
Status: v1alpha1.ConditionFalse,
Reason: upstreamwatchers.ReasonInvalidTLSConfig,
Message: message,
}
}
func (c *ldapWatcherController) validateSecret(upstream *v1alpha1.LDAPIdentityProvider, config *upstreamldap.ProviderConfig) (*v1alpha1.Condition, string) {
secretName := upstream.Spec.Bind.SecretName
secret, err := c.secretInformer.Lister().Secrets(upstream.Namespace).Get(secretName)
if err != nil {
return &v1alpha1.Condition{
Type: typeBindSecretValid,
Status: v1alpha1.ConditionFalse,
Reason: upstreamwatchers.ReasonNotFound,
Message: err.Error(),
}, ""
}
if secret.Type != corev1.SecretTypeBasicAuth {
return &v1alpha1.Condition{
Type: typeBindSecretValid,
Status: v1alpha1.ConditionFalse,
Reason: upstreamwatchers.ReasonWrongType,
Message: fmt.Sprintf("referenced Secret %q has wrong type %q (should be %q)",
secretName, secret.Type, corev1.SecretTypeBasicAuth),
}, secret.ResourceVersion
}
config.BindUsername = string(secret.Data[corev1.BasicAuthUsernameKey])
config.BindPassword = string(secret.Data[corev1.BasicAuthPasswordKey])
if len(config.BindUsername) == 0 || len(config.BindPassword) == 0 {
return &v1alpha1.Condition{
Type: typeBindSecretValid,
Status: v1alpha1.ConditionFalse,
Reason: upstreamwatchers.ReasonMissingKeys,
Message: fmt.Sprintf("referenced Secret %q is missing required keys %q",
secretName, []string{corev1.BasicAuthUsernameKey, corev1.BasicAuthPasswordKey}),
}, secret.ResourceVersion
}
return &v1alpha1.Condition{
Type: typeBindSecretValid,
Status: v1alpha1.ConditionTrue,
Reason: upstreamwatchers.ReasonSuccess,
Message: "loaded bind secret",
}, secret.ResourceVersion
}
func (c *ldapWatcherController) updateStatus(ctx context.Context, upstream *v1alpha1.LDAPIdentityProvider, conditions []*v1alpha1.Condition) {
log := klogr.New().WithValues("namespace", upstream.Namespace, "name", upstream.Name)
updated := upstream.DeepCopy()
hadErrorCondition := conditionsutil.Merge(conditions, upstream.Generation, &updated.Status.Conditions, log)
updated.Status.Phase = v1alpha1.LDAPPhaseReady
if hadErrorCondition {
updated.Status.Phase = v1alpha1.LDAPPhaseError
}
if equality.Semantic.DeepEqual(upstream, updated) {
return // nothing to update
}
_, err := c.client.
IDPV1alpha1().
LDAPIdentityProviders(upstream.Namespace).
UpdateStatus(ctx, updated, metav1.UpdateOptions{})
if err != nil {
log.Error(err, "failed to update status")
}
}

View File

@ -0,0 +1,793 @@
// Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package ldapupstreamwatcher
import (
"context"
"encoding/base64"
"errors"
"fmt"
"sort"
"testing"
"time"
"github.com/golang/mock/gomock"
"github.com/stretchr/testify/require"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/informers"
"k8s.io/client-go/kubernetes/fake"
"go.pinniped.dev/generated/latest/apis/supervisor/idp/v1alpha1"
pinnipedfake "go.pinniped.dev/generated/latest/client/supervisor/clientset/versioned/fake"
pinnipedinformers "go.pinniped.dev/generated/latest/client/supervisor/informers/externalversions"
"go.pinniped.dev/internal/certauthority"
"go.pinniped.dev/internal/controllerlib"
"go.pinniped.dev/internal/mocks/mockldapconn"
"go.pinniped.dev/internal/oidc/provider"
"go.pinniped.dev/internal/testutil"
"go.pinniped.dev/internal/upstreamldap"
)
func TestLDAPUpstreamWatcherControllerFilterSecrets(t *testing.T) {
t.Parallel()
tests := []struct {
name string
secret metav1.Object
wantAdd bool
wantUpdate bool
wantDelete bool
}{
{
name: "a secret of the right type",
secret: &corev1.Secret{
Type: corev1.SecretTypeBasicAuth,
ObjectMeta: metav1.ObjectMeta{Name: "some-name", Namespace: "some-namespace"},
},
wantAdd: true,
wantUpdate: true,
wantDelete: true,
},
{
name: "a secret of the wrong type",
secret: &corev1.Secret{
Type: "this-is-the-wrong-type",
ObjectMeta: metav1.ObjectMeta{Name: "some-name", Namespace: "some-namespace"},
},
},
{
name: "resource of a data type which is not watched by this controller",
secret: &corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{Name: "some-name", Namespace: "some-namespace"},
},
},
}
for _, test := range tests {
test := test
t.Run(test.name, func(t *testing.T) {
t.Parallel()
fakePinnipedClient := pinnipedfake.NewSimpleClientset()
pinnipedInformers := pinnipedinformers.NewSharedInformerFactory(fakePinnipedClient, 0)
ldapIDPInformer := pinnipedInformers.IDP().V1alpha1().LDAPIdentityProviders()
fakeKubeClient := fake.NewSimpleClientset()
kubeInformers := informers.NewSharedInformerFactory(fakeKubeClient, 0)
secretInformer := kubeInformers.Core().V1().Secrets()
withInformer := testutil.NewObservableWithInformerOption()
New(nil, nil, ldapIDPInformer, secretInformer, withInformer.WithInformer)
unrelated := corev1.Secret{}
filter := withInformer.GetFilterForInformer(secretInformer)
require.Equal(t, test.wantAdd, filter.Add(test.secret))
require.Equal(t, test.wantUpdate, filter.Update(&unrelated, test.secret))
require.Equal(t, test.wantUpdate, filter.Update(test.secret, &unrelated))
require.Equal(t, test.wantDelete, filter.Delete(test.secret))
})
}
}
func TestLDAPUpstreamWatcherControllerFilterLDAPIdentityProviders(t *testing.T) {
t.Parallel()
tests := []struct {
name string
idp metav1.Object
wantAdd bool
wantUpdate bool
wantDelete bool
}{
{
name: "any LDAPIdentityProvider",
idp: &v1alpha1.LDAPIdentityProvider{
ObjectMeta: metav1.ObjectMeta{Name: "some-name", Namespace: "some-namespace"},
},
wantAdd: true,
wantUpdate: true,
wantDelete: true,
},
}
for _, test := range tests {
test := test
t.Run(test.name, func(t *testing.T) {
t.Parallel()
fakePinnipedClient := pinnipedfake.NewSimpleClientset()
pinnipedInformers := pinnipedinformers.NewSharedInformerFactory(fakePinnipedClient, 0)
ldapIDPInformer := pinnipedInformers.IDP().V1alpha1().LDAPIdentityProviders()
fakeKubeClient := fake.NewSimpleClientset()
kubeInformers := informers.NewSharedInformerFactory(fakeKubeClient, 0)
secretInformer := kubeInformers.Core().V1().Secrets()
withInformer := testutil.NewObservableWithInformerOption()
New(nil, nil, ldapIDPInformer, secretInformer, withInformer.WithInformer)
unrelated := corev1.Secret{}
filter := withInformer.GetFilterForInformer(ldapIDPInformer)
require.Equal(t, test.wantAdd, filter.Add(test.idp))
require.Equal(t, test.wantUpdate, filter.Update(&unrelated, test.idp))
require.Equal(t, test.wantUpdate, filter.Update(test.idp, &unrelated))
require.Equal(t, test.wantDelete, filter.Delete(test.idp))
})
}
}
// Wrap the func into a struct so the test can do deep equal assertions on instances of upstreamldap.Provider.
type comparableDialer struct {
upstreamldap.LDAPDialerFunc
}
func TestLDAPUpstreamWatcherControllerSync(t *testing.T) {
t.Parallel()
now := metav1.NewTime(time.Now().UTC())
const (
testNamespace = "test-namespace"
testName = "test-name"
testSecretName = "test-bind-secret"
testBindUsername = "test-bind-username"
testBindPassword = "test-bind-password"
testHost = "ldap.example.com:123"
testUserSearchBase = "test-user-search-base"
testUserSearchFilter = "test-user-search-filter"
testUsernameAttrName = "test-username-attr"
testUIDAttrName = "test-uid-attr"
)
testValidSecretData := map[string][]byte{"username": []byte(testBindUsername), "password": []byte(testBindPassword)}
testCA, err := certauthority.New("test CA", time.Minute)
require.NoError(t, err)
testCABundle := testCA.Bundle()
testCABundleBase64Encoded := base64.StdEncoding.EncodeToString(testCABundle)
validUpstream := &v1alpha1.LDAPIdentityProvider{
ObjectMeta: metav1.ObjectMeta{Name: testName, Namespace: testNamespace, Generation: 1234},
Spec: v1alpha1.LDAPIdentityProviderSpec{
Host: testHost,
TLS: &v1alpha1.TLSSpec{CertificateAuthorityData: testCABundleBase64Encoded},
Bind: v1alpha1.LDAPIdentityProviderBind{SecretName: testSecretName},
UserSearch: v1alpha1.LDAPIdentityProviderUserSearch{
Base: testUserSearchBase,
Filter: testUserSearchFilter,
Attributes: v1alpha1.LDAPIdentityProviderUserSearchAttributes{
Username: testUsernameAttrName,
UID: testUIDAttrName,
},
},
},
}
editedValidUpstream := func(editFunc func(*v1alpha1.LDAPIdentityProvider)) *v1alpha1.LDAPIdentityProvider {
deepCopy := validUpstream.DeepCopy()
editFunc(deepCopy)
return deepCopy
}
providerConfigForValidUpstream := &upstreamldap.ProviderConfig{
Name: testName,
Host: testHost,
CABundle: testCABundle,
BindUsername: testBindUsername,
BindPassword: testBindPassword,
UserSearch: upstreamldap.UserSearchConfig{
Base: testUserSearchBase,
Filter: testUserSearchFilter,
UsernameAttribute: testUsernameAttrName,
UIDAttribute: testUIDAttrName,
},
}
bindSecretValidTrueCondition := func(gen int64) v1alpha1.Condition {
return v1alpha1.Condition{
Type: "BindSecretValid",
Status: "True",
LastTransitionTime: now,
Reason: "Success",
Message: "loaded bind secret",
ObservedGeneration: gen,
}
}
ldapConnectionValidTrueCondition := func(gen int64, secretVersion string) v1alpha1.Condition {
return v1alpha1.Condition{
Type: "LDAPConnectionValid",
Status: "True",
LastTransitionTime: now,
Reason: "Success",
Message: fmt.Sprintf(
`successfully able to connect to "%s" and bind as user "%s" [validated with Secret "%s" at version "%s"]`,
testHost, testBindUsername, testSecretName, secretVersion),
ObservedGeneration: gen,
}
}
tlsConfigurationValidLoadedTrueCondition := func(gen int64) v1alpha1.Condition {
return v1alpha1.Condition{
Type: "TLSConfigurationValid",
Status: "True",
LastTransitionTime: now,
Reason: "Success",
Message: "loaded TLS configuration",
ObservedGeneration: gen,
}
}
allConditionsTrue := func(gen int64, secretVersion string) []v1alpha1.Condition {
return []v1alpha1.Condition{
bindSecretValidTrueCondition(gen),
ldapConnectionValidTrueCondition(gen, secretVersion),
tlsConfigurationValidLoadedTrueCondition(gen),
}
}
validBindUserSecret := func(secretVersion string) *corev1.Secret {
return &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{Name: testSecretName, Namespace: testNamespace, ResourceVersion: secretVersion},
Type: corev1.SecretTypeBasicAuth,
Data: testValidSecretData,
}
}
tests := []struct {
name string
initialValidatedSecretVersions map[string]string
inputUpstreams []runtime.Object
inputSecrets []runtime.Object
setupMocks func(conn *mockldapconn.MockConn)
dialError error
wantErr string
wantResultingCache []*upstreamldap.ProviderConfig
wantResultingUpstreams []v1alpha1.LDAPIdentityProvider
wantValidatedSecretVersions map[string]string
}{
{
name: "no LDAPIdentityProvider upstreams clears the cache",
wantResultingCache: []*upstreamldap.ProviderConfig{},
},
{
name: "one valid upstream updates the cache to include only that upstream",
inputUpstreams: []runtime.Object{validUpstream},
inputSecrets: []runtime.Object{validBindUserSecret("4242")},
setupMocks: func(conn *mockldapconn.MockConn) {
// Should perform a test dial and bind.
conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1)
conn.EXPECT().Close().Times(1)
},
wantResultingCache: []*upstreamldap.ProviderConfig{providerConfigForValidUpstream},
wantResultingUpstreams: []v1alpha1.LDAPIdentityProvider{{
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, Generation: 1234},
Status: v1alpha1.LDAPIdentityProviderStatus{
Phase: "Ready",
Conditions: allConditionsTrue(1234, "4242"),
},
}},
wantValidatedSecretVersions: map[string]string{testName: "4242"},
},
{
name: "missing secret",
inputUpstreams: []runtime.Object{validUpstream},
inputSecrets: []runtime.Object{},
wantErr: controllerlib.ErrSyntheticRequeue.Error(),
wantResultingCache: []*upstreamldap.ProviderConfig{},
wantResultingUpstreams: []v1alpha1.LDAPIdentityProvider{{
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, Generation: 1234},
Status: v1alpha1.LDAPIdentityProviderStatus{
Phase: "Error",
Conditions: []v1alpha1.Condition{
{
Type: "BindSecretValid",
Status: "False",
LastTransitionTime: now,
Reason: "SecretNotFound",
Message: fmt.Sprintf(`secret "%s" not found`, testSecretName),
ObservedGeneration: 1234,
},
tlsConfigurationValidLoadedTrueCondition(1234),
},
},
}},
},
{
name: "secret has wrong type",
inputUpstreams: []runtime.Object{validUpstream},
inputSecrets: []runtime.Object{&corev1.Secret{
ObjectMeta: metav1.ObjectMeta{Name: testSecretName, Namespace: testNamespace},
Type: "some-other-type",
Data: testValidSecretData,
}},
wantErr: controllerlib.ErrSyntheticRequeue.Error(),
wantResultingCache: []*upstreamldap.ProviderConfig{},
wantResultingUpstreams: []v1alpha1.LDAPIdentityProvider{{
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, Generation: 1234},
Status: v1alpha1.LDAPIdentityProviderStatus{
Phase: "Error",
Conditions: []v1alpha1.Condition{
{
Type: "BindSecretValid",
Status: "False",
LastTransitionTime: now,
Reason: "SecretWrongType",
Message: fmt.Sprintf(`referenced Secret "%s" has wrong type "some-other-type" (should be "kubernetes.io/basic-auth")`, testSecretName),
ObservedGeneration: 1234,
},
tlsConfigurationValidLoadedTrueCondition(1234),
},
},
}},
},
{
name: "secret is missing key",
inputUpstreams: []runtime.Object{validUpstream},
inputSecrets: []runtime.Object{&corev1.Secret{
ObjectMeta: metav1.ObjectMeta{Name: testSecretName, Namespace: testNamespace},
Type: corev1.SecretTypeBasicAuth,
}},
wantErr: controllerlib.ErrSyntheticRequeue.Error(),
wantResultingCache: []*upstreamldap.ProviderConfig{},
wantResultingUpstreams: []v1alpha1.LDAPIdentityProvider{{
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, Generation: 1234},
Status: v1alpha1.LDAPIdentityProviderStatus{
Phase: "Error",
Conditions: []v1alpha1.Condition{
{
Type: "BindSecretValid",
Status: "False",
LastTransitionTime: now,
Reason: "SecretMissingKeys",
Message: fmt.Sprintf(`referenced Secret "%s" is missing required keys ["username" "password"]`, testSecretName),
ObservedGeneration: 1234,
},
tlsConfigurationValidLoadedTrueCondition(1234),
},
},
}},
},
{
name: "CertificateAuthorityData is not base64 encoded",
inputUpstreams: []runtime.Object{editedValidUpstream(func(upstream *v1alpha1.LDAPIdentityProvider) {
upstream.Spec.TLS.CertificateAuthorityData = "this-is-not-base64-encoded"
})},
inputSecrets: []runtime.Object{validBindUserSecret("")},
wantErr: controllerlib.ErrSyntheticRequeue.Error(),
wantResultingCache: []*upstreamldap.ProviderConfig{},
wantResultingUpstreams: []v1alpha1.LDAPIdentityProvider{{
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, Generation: 1234},
Status: v1alpha1.LDAPIdentityProviderStatus{
Phase: "Error",
Conditions: []v1alpha1.Condition{
bindSecretValidTrueCondition(1234),
{
Type: "TLSConfigurationValid",
Status: "False",
LastTransitionTime: now,
Reason: "InvalidTLSConfig",
Message: "certificateAuthorityData is invalid: illegal base64 data at input byte 4",
ObservedGeneration: 1234,
},
},
},
}},
},
{
name: "CertificateAuthorityData is not valid pem data",
inputUpstreams: []runtime.Object{editedValidUpstream(func(upstream *v1alpha1.LDAPIdentityProvider) {
upstream.Spec.TLS.CertificateAuthorityData = base64.StdEncoding.EncodeToString([]byte("this is not pem data"))
})},
inputSecrets: []runtime.Object{validBindUserSecret("")},
wantErr: controllerlib.ErrSyntheticRequeue.Error(),
wantResultingCache: []*upstreamldap.ProviderConfig{},
wantResultingUpstreams: []v1alpha1.LDAPIdentityProvider{{
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, Generation: 1234},
Status: v1alpha1.LDAPIdentityProviderStatus{
Phase: "Error",
Conditions: []v1alpha1.Condition{
bindSecretValidTrueCondition(1234),
{
Type: "TLSConfigurationValid",
Status: "False",
LastTransitionTime: now,
Reason: "InvalidTLSConfig",
Message: "certificateAuthorityData is invalid: no certificates found",
ObservedGeneration: 1234,
},
},
},
}},
},
{
name: "nil TLS configuration is valid",
inputUpstreams: []runtime.Object{editedValidUpstream(func(upstream *v1alpha1.LDAPIdentityProvider) {
upstream.Spec.TLS = nil
})},
inputSecrets: []runtime.Object{validBindUserSecret("4242")},
setupMocks: func(conn *mockldapconn.MockConn) {
// Should perform a test dial and bind.
conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1)
conn.EXPECT().Close().Times(1)
},
wantResultingCache: []*upstreamldap.ProviderConfig{
{
Name: testName,
Host: testHost,
CABundle: nil,
BindUsername: testBindUsername,
BindPassword: testBindPassword,
UserSearch: upstreamldap.UserSearchConfig{
Base: testUserSearchBase,
Filter: testUserSearchFilter,
UsernameAttribute: testUsernameAttrName,
UIDAttribute: testUIDAttrName,
},
},
},
wantResultingUpstreams: []v1alpha1.LDAPIdentityProvider{{
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, Generation: 1234},
Status: v1alpha1.LDAPIdentityProviderStatus{
Phase: "Ready",
Conditions: []v1alpha1.Condition{
bindSecretValidTrueCondition(1234),
ldapConnectionValidTrueCondition(1234, "4242"),
{
Type: "TLSConfigurationValid",
Status: "True",
LastTransitionTime: now,
Reason: "Success",
Message: "no TLS configuration provided",
ObservedGeneration: 1234,
},
},
},
}},
wantValidatedSecretVersions: map[string]string{testName: "4242"},
},
{
name: "non-nil TLS configuration with empty CertificateAuthorityData is valid",
inputUpstreams: []runtime.Object{editedValidUpstream(func(upstream *v1alpha1.LDAPIdentityProvider) {
upstream.Spec.TLS.CertificateAuthorityData = ""
})},
inputSecrets: []runtime.Object{validBindUserSecret("4242")},
setupMocks: func(conn *mockldapconn.MockConn) {
// Should perform a test dial and bind.
conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1)
conn.EXPECT().Close().Times(1)
},
wantResultingCache: []*upstreamldap.ProviderConfig{
{
Name: testName,
Host: testHost,
CABundle: nil,
BindUsername: testBindUsername,
BindPassword: testBindPassword,
UserSearch: upstreamldap.UserSearchConfig{
Base: testUserSearchBase,
Filter: testUserSearchFilter,
UsernameAttribute: testUsernameAttrName,
UIDAttribute: testUIDAttrName,
},
},
},
wantResultingUpstreams: []v1alpha1.LDAPIdentityProvider{{
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, Generation: 1234},
Status: v1alpha1.LDAPIdentityProviderStatus{
Phase: "Ready",
Conditions: allConditionsTrue(1234, "4242"),
},
}},
wantValidatedSecretVersions: map[string]string{testName: "4242"},
},
{
name: "one valid upstream and one invalid upstream updates the cache to include only the valid upstream",
inputUpstreams: []runtime.Object{validUpstream, editedValidUpstream(func(upstream *v1alpha1.LDAPIdentityProvider) {
upstream.Name = "other-upstream"
upstream.Generation = 42
upstream.Spec.Bind.SecretName = "non-existent-secret"
})},
inputSecrets: []runtime.Object{validBindUserSecret("4242")},
setupMocks: func(conn *mockldapconn.MockConn) {
// Should perform a test dial and bind for the one valid upstream configuration.
conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1)
conn.EXPECT().Close().Times(1)
},
wantErr: controllerlib.ErrSyntheticRequeue.Error(),
wantResultingCache: []*upstreamldap.ProviderConfig{providerConfigForValidUpstream},
wantResultingUpstreams: []v1alpha1.LDAPIdentityProvider{
{
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: "other-upstream", Generation: 42},
Status: v1alpha1.LDAPIdentityProviderStatus{
Phase: "Error",
Conditions: []v1alpha1.Condition{
{
Type: "BindSecretValid",
Status: "False",
LastTransitionTime: now,
Reason: "SecretNotFound",
Message: fmt.Sprintf(`secret "%s" not found`, "non-existent-secret"),
ObservedGeneration: 42,
},
tlsConfigurationValidLoadedTrueCondition(42),
},
},
},
{
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, Generation: 1234},
Status: v1alpha1.LDAPIdentityProviderStatus{
Phase: "Ready",
Conditions: allConditionsTrue(1234, "4242"),
},
},
},
wantValidatedSecretVersions: map[string]string{testName: "4242"},
},
{
name: "when testing the connection to the LDAP server fails then the upstream is still added to the cache anyway (treated like a warning)",
inputUpstreams: []runtime.Object{validUpstream},
inputSecrets: []runtime.Object{validBindUserSecret("")},
setupMocks: func(conn *mockldapconn.MockConn) {
// Should perform a test dial and bind.
conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1).Return(errors.New("some bind error"))
conn.EXPECT().Close().Times(1)
},
wantErr: controllerlib.ErrSyntheticRequeue.Error(),
wantResultingCache: []*upstreamldap.ProviderConfig{providerConfigForValidUpstream},
wantResultingUpstreams: []v1alpha1.LDAPIdentityProvider{{
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, Generation: 1234},
Status: v1alpha1.LDAPIdentityProviderStatus{
Phase: "Error",
Conditions: []v1alpha1.Condition{
bindSecretValidTrueCondition(1234),
{
Type: "LDAPConnectionValid",
Status: "False",
LastTransitionTime: now,
Reason: "LDAPConnectionError",
Message: fmt.Sprintf(
`could not successfully connect to "%s" and bind as user "%s": error binding as "%s": some bind error`,
testHost, testBindUsername, testBindUsername),
ObservedGeneration: 1234,
},
tlsConfigurationValidLoadedTrueCondition(1234),
},
},
}},
},
{
name: "when the LDAP server connection was already validated for the current resource generation and secret version, then do not validate it again",
inputUpstreams: []runtime.Object{editedValidUpstream(func(upstream *v1alpha1.LDAPIdentityProvider) {
upstream.Generation = 1234
upstream.Status.Conditions = []v1alpha1.Condition{
ldapConnectionValidTrueCondition(1234, "4242"),
}
})},
inputSecrets: []runtime.Object{validBindUserSecret("4242")},
initialValidatedSecretVersions: map[string]string{testName: "4242"},
setupMocks: func(conn *mockldapconn.MockConn) {
// Should not perform a test dial and bind. No mocking here means the test will fail if Bind() or Close() are called.
},
wantResultingCache: []*upstreamldap.ProviderConfig{providerConfigForValidUpstream},
wantResultingUpstreams: []v1alpha1.LDAPIdentityProvider{{
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, Generation: 1234},
Status: v1alpha1.LDAPIdentityProviderStatus{
Phase: "Ready",
Conditions: allConditionsTrue(1234, "4242"),
},
}},
wantValidatedSecretVersions: map[string]string{testName: "4242"},
},
{
name: "when the LDAP server connection was validated for an older resource generation, then try to validate it again",
inputUpstreams: []runtime.Object{editedValidUpstream(func(upstream *v1alpha1.LDAPIdentityProvider) {
upstream.Generation = 1234 // current generation
upstream.Status.Conditions = []v1alpha1.Condition{
ldapConnectionValidTrueCondition(1233, "4242"), // older spec generation!
}
})},
inputSecrets: []runtime.Object{validBindUserSecret("4242")},
initialValidatedSecretVersions: map[string]string{testName: "4242"},
setupMocks: func(conn *mockldapconn.MockConn) {
// Should perform a test dial and bind.
conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1)
conn.EXPECT().Close().Times(1)
},
wantResultingCache: []*upstreamldap.ProviderConfig{providerConfigForValidUpstream},
wantResultingUpstreams: []v1alpha1.LDAPIdentityProvider{{
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, Generation: 1234},
Status: v1alpha1.LDAPIdentityProviderStatus{
Phase: "Ready",
Conditions: allConditionsTrue(1234, "4242"),
},
}},
wantValidatedSecretVersions: map[string]string{testName: "4242"},
},
{
name: "when the LDAP server connection validation previously failed for this resource generation, then try to validate it again",
inputUpstreams: []runtime.Object{editedValidUpstream(func(upstream *v1alpha1.LDAPIdentityProvider) {
upstream.Generation = 1234
upstream.Status.Conditions = []v1alpha1.Condition{
{
Type: "LDAPConnectionValid",
Status: "False", // failure!
LastTransitionTime: now,
Reason: "LDAPConnectionError",
Message: "some-error-message",
ObservedGeneration: 1234, // same (current) generation!
},
}
})},
inputSecrets: []runtime.Object{validBindUserSecret("4242")},
initialValidatedSecretVersions: map[string]string{testName: "1"},
setupMocks: func(conn *mockldapconn.MockConn) {
// Should perform a test dial and bind.
conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1)
conn.EXPECT().Close().Times(1)
},
wantResultingCache: []*upstreamldap.ProviderConfig{providerConfigForValidUpstream},
wantResultingUpstreams: []v1alpha1.LDAPIdentityProvider{{
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, Generation: 1234},
Status: v1alpha1.LDAPIdentityProviderStatus{
Phase: "Ready",
Conditions: allConditionsTrue(1234, "4242"),
},
}},
wantValidatedSecretVersions: map[string]string{testName: "4242"},
},
{
name: "when the LDAP server connection was already validated for this resource generation but the bind secret has changed, then try to validate it again",
inputUpstreams: []runtime.Object{editedValidUpstream(func(upstream *v1alpha1.LDAPIdentityProvider) {
upstream.Generation = 1234
upstream.Status.Conditions = []v1alpha1.Condition{
ldapConnectionValidTrueCondition(1234, "4241"), // same spec generation, old secret version
}
})},
inputSecrets: []runtime.Object{validBindUserSecret("4242")}, // newer secret version!
initialValidatedSecretVersions: map[string]string{testName: "4241"}, // old version was validated
setupMocks: func(conn *mockldapconn.MockConn) {
// Should perform a test dial and bind.
conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1)
conn.EXPECT().Close().Times(1)
},
wantResultingCache: []*upstreamldap.ProviderConfig{providerConfigForValidUpstream},
wantResultingUpstreams: []v1alpha1.LDAPIdentityProvider{{
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, Generation: 1234},
Status: v1alpha1.LDAPIdentityProviderStatus{
Phase: "Ready",
Conditions: allConditionsTrue(1234, "4242"),
},
}},
wantValidatedSecretVersions: map[string]string{testName: "4242"},
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
fakePinnipedClient := pinnipedfake.NewSimpleClientset(tt.inputUpstreams...)
pinnipedInformers := pinnipedinformers.NewSharedInformerFactory(fakePinnipedClient, 0)
fakeKubeClient := fake.NewSimpleClientset(tt.inputSecrets...)
kubeInformers := informers.NewSharedInformerFactory(fakeKubeClient, 0)
cache := provider.NewDynamicUpstreamIDPProvider()
cache.SetLDAPIdentityProviders([]provider.UpstreamLDAPIdentityProviderI{
upstreamldap.New(upstreamldap.ProviderConfig{Name: "initial-entry"}),
})
ctrl := gomock.NewController(t)
t.Cleanup(ctrl.Finish)
conn := mockldapconn.NewMockConn(ctrl)
if tt.setupMocks != nil {
tt.setupMocks(conn)
}
dialer := &comparableDialer{upstreamldap.LDAPDialerFunc(func(ctx context.Context, _ string) (upstreamldap.Conn, error) {
if tt.dialError != nil {
return nil, tt.dialError
}
return conn, nil
})}
validatedSecretVersionCache := newSecretVersionCache()
if tt.initialValidatedSecretVersions != nil {
validatedSecretVersionCache.ResourceVersionsByName = tt.initialValidatedSecretVersions
}
controller := newInternal(
cache,
validatedSecretVersionCache,
dialer,
fakePinnipedClient,
pinnipedInformers.IDP().V1alpha1().LDAPIdentityProviders(),
kubeInformers.Core().V1().Secrets(),
controllerlib.WithInformer,
)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
pinnipedInformers.Start(ctx.Done())
kubeInformers.Start(ctx.Done())
controllerlib.TestRunSynchronously(t, controller)
syncCtx := controllerlib.Context{Context: ctx, Key: controllerlib.Key{}}
if err := controllerlib.TestSync(t, controller, syncCtx); tt.wantErr != "" {
require.EqualError(t, err, tt.wantErr)
} else {
require.NoError(t, err)
}
actualIDPList := cache.GetLDAPIdentityProviders()
require.Equal(t, len(tt.wantResultingCache), len(actualIDPList))
for i := range actualIDPList {
actualIDP := actualIDPList[i].(*upstreamldap.Provider)
copyOfExpectedValue := *tt.wantResultingCache[i] // copy before edit to avoid race because these tests are run in parallel
// The dialer that was passed in to the controller's constructor should always have been
// passed through to the provider.
copyOfExpectedValue.Dialer = dialer
require.Equal(t, copyOfExpectedValue, actualIDP.GetConfig())
}
actualUpstreams, err := fakePinnipedClient.IDPV1alpha1().LDAPIdentityProviders(testNamespace).List(ctx, metav1.ListOptions{})
require.NoError(t, err)
// Assert on the expected Status of the upstreams. Preprocess the upstreams a bit so that they're easier to assert against.
normalizedActualUpstreams := normalizeLDAPUpstreams(actualUpstreams.Items, now)
require.Equal(t, len(tt.wantResultingUpstreams), len(normalizedActualUpstreams))
for i := range tt.wantResultingUpstreams {
// Require each separately to get a nice diff when the test fails.
require.Equal(t, tt.wantResultingUpstreams[i], normalizedActualUpstreams[i])
}
// Check that the controller remembered which version of the secret it most recently validated successfully with.
if tt.wantValidatedSecretVersions == nil {
tt.wantValidatedSecretVersions = map[string]string{}
}
require.Equal(t, tt.wantValidatedSecretVersions, validatedSecretVersionCache.ResourceVersionsByName)
})
}
}
func normalizeLDAPUpstreams(upstreams []v1alpha1.LDAPIdentityProvider, now metav1.Time) []v1alpha1.LDAPIdentityProvider {
result := make([]v1alpha1.LDAPIdentityProvider, 0, len(upstreams))
for _, u := range upstreams {
normalized := u.DeepCopy()
// We're only interested in comparing the status, so zero out the spec.
normalized.Spec = v1alpha1.LDAPIdentityProviderSpec{}
// Round down the LastTransitionTime values to `now` if they were just updated. This makes
// it much easier to encode assertions about the expected timestamps.
for i := range normalized.Status.Conditions {
if time.Since(normalized.Status.Conditions[i].LastTransitionTime.Time) < 5*time.Second {
normalized.Status.Conditions[i].LastTransitionTime = now
}
}
result = append(result, *normalized)
}
sort.SliceStable(result, func(i, j int) bool {
return result[i].Name < result[j].Name
})
return result
}

View File

@ -1,8 +1,8 @@
// Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
// Package upstreamwatcher implements a controller that watches OIDCIdentityProvider objects.
package upstreamwatcher
// Package oidcupstreamwatcher implements a controller which watches OIDCIdentityProviders.
package oidcupstreamwatcher
import (
"context"
@ -31,6 +31,8 @@ import (
idpinformers "go.pinniped.dev/generated/latest/client/supervisor/informers/externalversions/idp/v1alpha1"
"go.pinniped.dev/internal/constable"
pinnipedcontroller "go.pinniped.dev/internal/controller"
"go.pinniped.dev/internal/controller/conditionsutil"
"go.pinniped.dev/internal/controller/supervisorconfig/upstreamwatchers"
"go.pinniped.dev/internal/controllerlib"
"go.pinniped.dev/internal/oidc/provider"
"go.pinniped.dev/internal/upstreamoidc"
@ -38,7 +40,7 @@ import (
const (
// Setup for the name of our controller in logs.
controllerName = "upstream-observer"
oidcControllerName = "oidc-upstream-observer"
// Constants related to the client credentials Secret.
oidcClientSecretType corev1.SecretType = "secrets.pinniped.dev/oidc-client"
@ -47,27 +49,22 @@ const (
clientSecretDataKey = "clientSecret"
// Constants related to the OIDC provider discovery cache. These do not affect the cache of JWKS.
validatorCacheTTL = 15 * time.Minute
oidcValidatorCacheTTL = 15 * time.Minute
// Constants related to conditions.
typeClientCredsValid = "ClientCredentialsValid"
typeClientCredentialsValid = "ClientCredentialsValid"
typeOIDCDiscoverySucceeded = "OIDCDiscoverySucceeded"
reasonNotFound = "SecretNotFound"
reasonWrongType = "SecretWrongType"
reasonMissingKeys = "SecretMissingKeys"
reasonSuccess = "Success"
reasonUnreachable = "Unreachable"
reasonInvalidTLSConfig = "InvalidTLSConfig"
reasonInvalidResponse = "InvalidResponse"
reasonUnreachable = "Unreachable"
reasonInvalidResponse = "InvalidResponse"
// Errors that are generated by our reconcile process.
errFailureStatus = constable.Error("OIDCIdentityProvider has a failing condition")
errNoCertificates = constable.Error("no certificates found")
errOIDCFailureStatus = constable.Error("OIDCIdentityProvider has a failing condition")
)
// IDPCache is a thread safe cache that holds a list of validated upstream OIDC IDP configurations.
type IDPCache interface {
SetIDPList([]provider.UpstreamOIDCIdentityProviderI)
// UpstreamOIDCIdentityProviderICache is a thread safe cache that holds a list of validated upstream OIDC IDP configurations.
type UpstreamOIDCIdentityProviderICache interface {
SetOIDCIdentityProviders([]provider.UpstreamOIDCIdentityProviderI)
}
// lruValidatorCache caches the *oidc.Provider associated with a particular issuer/TLS configuration.
@ -87,7 +84,7 @@ func (c *lruValidatorCache) getProvider(spec *v1alpha1.OIDCIdentityProviderSpec)
}
func (c *lruValidatorCache) putProvider(spec *v1alpha1.OIDCIdentityProviderSpec, provider *oidc.Provider, client *http.Client) {
c.cache.Set(c.cacheKey(spec), &lruValidatorCacheEntry{provider: provider, client: client}, validatorCacheTTL)
c.cache.Set(c.cacheKey(spec), &lruValidatorCacheEntry{provider: provider, client: client}, oidcValidatorCacheTTL)
}
func (c *lruValidatorCache) cacheKey(spec *v1alpha1.OIDCIdentityProviderSpec) interface{} {
@ -99,8 +96,8 @@ func (c *lruValidatorCache) cacheKey(spec *v1alpha1.OIDCIdentityProviderSpec) in
return key
}
type controller struct {
cache IDPCache
type oidcWatcherController struct {
cache UpstreamOIDCIdentityProviderICache
log logr.Logger
client pinnipedclientset.Interface
oidcIdentityProviderInformer idpinformers.OIDCIdentityProviderInformer
@ -111,25 +108,25 @@ type controller struct {
}
}
// New instantiates a new controllerlib.Controller which will populate the provided IDPCache.
// New instantiates a new controllerlib.Controller which will populate the provided UpstreamOIDCIdentityProviderICache.
func New(
idpCache IDPCache,
idpCache UpstreamOIDCIdentityProviderICache,
client pinnipedclientset.Interface,
oidcIdentityProviderInformer idpinformers.OIDCIdentityProviderInformer,
secretInformer corev1informers.SecretInformer,
log logr.Logger,
withInformer pinnipedcontroller.WithInformerOptionFunc,
) controllerlib.Controller {
c := controller{
c := oidcWatcherController{
cache: idpCache,
log: log.WithName(controllerName),
log: log.WithName(oidcControllerName),
client: client,
oidcIdentityProviderInformer: oidcIdentityProviderInformer,
secretInformer: secretInformer,
validatorCache: &lruValidatorCache{cache: cache.NewExpiring()},
}
return controllerlib.New(
controllerlib.Config{Name: controllerName, Syncer: &c},
controllerlib.Config{Name: oidcControllerName, Syncer: &c},
withInformer(
oidcIdentityProviderInformer,
pinnipedcontroller.MatchAnythingFilter(pinnipedcontroller.SingletonQueue()),
@ -144,7 +141,7 @@ func New(
}
// Sync implements controllerlib.Syncer.
func (c *controller) Sync(ctx controllerlib.Context) error {
func (c *oidcWatcherController) Sync(ctx controllerlib.Context) error {
actualUpstreams, err := c.oidcIdentityProviderInformer.Lister().List(labels.Everything())
if err != nil {
return fmt.Errorf("failed to list OIDCIdentityProviders: %w", err)
@ -160,7 +157,7 @@ func (c *controller) Sync(ctx controllerlib.Context) error {
validatedUpstreams = append(validatedUpstreams, provider.UpstreamOIDCIdentityProviderI(valid))
}
}
c.cache.SetIDPList(validatedUpstreams)
c.cache.SetOIDCIdentityProviders(validatedUpstreams)
if requeue {
return controllerlib.ErrSyntheticRequeue
}
@ -169,7 +166,7 @@ func (c *controller) Sync(ctx controllerlib.Context) error {
// validateUpstream validates the provided v1alpha1.OIDCIdentityProvider and returns the validated configuration as a
// provider.UpstreamOIDCIdentityProvider. As a side effect, it also updates the status of the v1alpha1.OIDCIdentityProvider.
func (c *controller) validateUpstream(ctx controllerlib.Context, upstream *v1alpha1.OIDCIdentityProvider) *upstreamoidc.ProviderConfig {
func (c *oidcWatcherController) validateUpstream(ctx controllerlib.Context, upstream *v1alpha1.OIDCIdentityProvider) *upstreamoidc.ProviderConfig {
result := upstreamoidc.ProviderConfig{
Name: upstream.Name,
Config: &oauth2.Config{
@ -193,7 +190,7 @@ func (c *controller) validateUpstream(ctx controllerlib.Context, upstream *v1alp
"type", condition.Type,
"reason", condition.Reason,
"message", condition.Message,
).Error(errFailureStatus, "found failing condition")
).Error(errOIDCFailureStatus, "found failing condition")
}
}
if valid {
@ -203,16 +200,16 @@ func (c *controller) validateUpstream(ctx controllerlib.Context, upstream *v1alp
}
// validateSecret validates the .spec.client.secretName field and returns the appropriate ClientCredentialsValid condition.
func (c *controller) validateSecret(upstream *v1alpha1.OIDCIdentityProvider, result *upstreamoidc.ProviderConfig) *v1alpha1.Condition {
func (c *oidcWatcherController) validateSecret(upstream *v1alpha1.OIDCIdentityProvider, result *upstreamoidc.ProviderConfig) *v1alpha1.Condition {
secretName := upstream.Spec.Client.SecretName
// Fetch the Secret from informer cache.
secret, err := c.secretInformer.Lister().Secrets(upstream.Namespace).Get(secretName)
if err != nil {
return &v1alpha1.Condition{
Type: typeClientCredsValid,
Type: typeClientCredentialsValid,
Status: v1alpha1.ConditionFalse,
Reason: reasonNotFound,
Reason: upstreamwatchers.ReasonNotFound,
Message: err.Error(),
}
}
@ -220,9 +217,9 @@ func (c *controller) validateSecret(upstream *v1alpha1.OIDCIdentityProvider, res
// Validate the secret .type field.
if secret.Type != oidcClientSecretType {
return &v1alpha1.Condition{
Type: typeClientCredsValid,
Type: typeClientCredentialsValid,
Status: v1alpha1.ConditionFalse,
Reason: reasonWrongType,
Reason: upstreamwatchers.ReasonWrongType,
Message: fmt.Sprintf("referenced Secret %q has wrong type %q (should be %q)", secretName, secret.Type, oidcClientSecretType),
}
}
@ -232,9 +229,9 @@ func (c *controller) validateSecret(upstream *v1alpha1.OIDCIdentityProvider, res
clientSecret := secret.Data[clientSecretDataKey]
if len(clientID) == 0 || len(clientSecret) == 0 {
return &v1alpha1.Condition{
Type: typeClientCredsValid,
Type: typeClientCredentialsValid,
Status: v1alpha1.ConditionFalse,
Reason: reasonMissingKeys,
Reason: upstreamwatchers.ReasonMissingKeys,
Message: fmt.Sprintf("referenced Secret %q is missing required keys %q", secretName, []string{clientIDDataKey, clientSecretDataKey}),
}
}
@ -243,15 +240,15 @@ func (c *controller) validateSecret(upstream *v1alpha1.OIDCIdentityProvider, res
result.Config.ClientID = string(clientID)
result.Config.ClientSecret = string(clientSecret)
return &v1alpha1.Condition{
Type: typeClientCredsValid,
Type: typeClientCredentialsValid,
Status: v1alpha1.ConditionTrue,
Reason: reasonSuccess,
Reason: upstreamwatchers.ReasonSuccess,
Message: "loaded client credentials",
}
}
// validateIssuer validates the .spec.issuer field, performs OIDC discovery, and returns the appropriate OIDCDiscoverySucceeded condition.
func (c *controller) validateIssuer(ctx context.Context, upstream *v1alpha1.OIDCIdentityProvider, result *upstreamoidc.ProviderConfig) *v1alpha1.Condition {
func (c *oidcWatcherController) validateIssuer(ctx context.Context, upstream *v1alpha1.OIDCIdentityProvider, result *upstreamoidc.ProviderConfig) *v1alpha1.Condition {
// Get the provider and HTTP Client from cache if possible.
discoveredProvider, httpClient := c.validatorCache.getProvider(&upstream.Spec)
@ -262,7 +259,7 @@ func (c *controller) validateIssuer(ctx context.Context, upstream *v1alpha1.OIDC
return &v1alpha1.Condition{
Type: typeOIDCDiscoverySucceeded,
Status: v1alpha1.ConditionFalse,
Reason: reasonInvalidTLSConfig,
Reason: upstreamwatchers.ReasonInvalidTLSConfig,
Message: err.Error(),
}
}
@ -314,11 +311,35 @@ func (c *controller) validateIssuer(ctx context.Context, upstream *v1alpha1.OIDC
return &v1alpha1.Condition{
Type: typeOIDCDiscoverySucceeded,
Status: v1alpha1.ConditionTrue,
Reason: reasonSuccess,
Reason: upstreamwatchers.ReasonSuccess,
Message: "discovered issuer configuration",
}
}
func (c *oidcWatcherController) updateStatus(ctx context.Context, upstream *v1alpha1.OIDCIdentityProvider, conditions []*v1alpha1.Condition) {
log := c.log.WithValues("namespace", upstream.Namespace, "name", upstream.Name)
updated := upstream.DeepCopy()
hadErrorCondition := conditionsutil.Merge(conditions, upstream.Generation, &updated.Status.Conditions, log)
updated.Status.Phase = v1alpha1.PhaseReady
if hadErrorCondition {
updated.Status.Phase = v1alpha1.PhaseError
}
if equality.Semantic.DeepEqual(upstream, updated) {
return
}
_, err := c.client.
IDPV1alpha1().
OIDCIdentityProviders(upstream.Namespace).
UpdateStatus(ctx, updated, metav1.UpdateOptions{})
if err != nil {
log.Error(err, "failed to update status")
}
}
func getTLSConfig(upstream *v1alpha1.OIDCIdentityProvider) (*tls.Config, error) {
result := tls.Config{
MinVersion: tls.VersionTLS12,
@ -335,81 +356,12 @@ func getTLSConfig(upstream *v1alpha1.OIDCIdentityProvider) (*tls.Config, error)
result.RootCAs = x509.NewCertPool()
if !result.RootCAs.AppendCertsFromPEM(bundle) {
return nil, fmt.Errorf("spec.certificateAuthorityData is invalid: %w", errNoCertificates)
return nil, fmt.Errorf("spec.certificateAuthorityData is invalid: %w", upstreamwatchers.ErrNoCertificates)
}
return &result, nil
}
func (c *controller) updateStatus(ctx context.Context, upstream *v1alpha1.OIDCIdentityProvider, conditions []*v1alpha1.Condition) {
log := c.log.WithValues("namespace", upstream.Namespace, "name", upstream.Name)
updated := upstream.DeepCopy()
updated.Status.Phase = v1alpha1.PhaseReady
for i := range conditions {
cond := conditions[i].DeepCopy()
cond.LastTransitionTime = metav1.Now()
cond.ObservedGeneration = upstream.Generation
if mergeCondition(&updated.Status.Conditions, cond) {
log.Info("updated condition", "type", cond.Type, "status", cond.Status, "reason", cond.Reason, "message", cond.Message)
}
if cond.Status == v1alpha1.ConditionFalse {
updated.Status.Phase = v1alpha1.PhaseError
}
}
sort.SliceStable(updated.Status.Conditions, func(i, j int) bool {
return updated.Status.Conditions[i].Type < updated.Status.Conditions[j].Type
})
if equality.Semantic.DeepEqual(upstream, updated) {
return
}
_, err := c.client.
IDPV1alpha1().
OIDCIdentityProviders(upstream.Namespace).
UpdateStatus(ctx, updated, metav1.UpdateOptions{})
if err != nil {
log.Error(err, "failed to update status")
}
}
// mergeCondition merges a new v1alpha1.Condition into a slice of existing conditions. It returns true
// if the condition has meaningfully changed.
func mergeCondition(existing *[]v1alpha1.Condition, new *v1alpha1.Condition) bool {
// Find any existing condition with a matching type.
var old *v1alpha1.Condition
for i := range *existing {
if (*existing)[i].Type == new.Type {
old = &(*existing)[i]
continue
}
}
// If there is no existing condition of this type, append this one and we're done.
if old == nil {
*existing = append(*existing, *new)
return true
}
// Set the LastTransitionTime depending on whether the status has changed.
new = new.DeepCopy()
if old.Status == new.Status {
new.LastTransitionTime = old.LastTransitionTime
}
// If anything has actually changed, update the entry and return true.
if !equality.Semantic.DeepEqual(old, new) {
*old = *new
return true
}
// Otherwise the entry is already up to date.
return false
}
func computeScopes(additionalScopes []string) []string {
// First compute the unique set of scopes, including "openid" (de-duplicate).
set := make(map[string]bool, len(additionalScopes)+1)

View File

@ -1,7 +1,7 @@
// Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package upstreamwatcher
package oidcupstreamwatcher
import (
"context"
@ -24,14 +24,14 @@ import (
pinnipedfake "go.pinniped.dev/generated/latest/client/supervisor/clientset/versioned/fake"
pinnipedinformers "go.pinniped.dev/generated/latest/client/supervisor/informers/externalversions"
"go.pinniped.dev/internal/controllerlib"
"go.pinniped.dev/internal/oidc/oidctestutil"
"go.pinniped.dev/internal/oidc/provider"
"go.pinniped.dev/internal/testutil"
"go.pinniped.dev/internal/testutil/oidctestutil"
"go.pinniped.dev/internal/testutil/testlogger"
"go.pinniped.dev/internal/upstreamoidc"
)
func TestControllerFilterSecret(t *testing.T) {
func TestOIDCUpstreamWatcherControllerFilterSecret(t *testing.T) {
t.Parallel()
tests := []struct {
@ -76,7 +76,7 @@ func TestControllerFilterSecret(t *testing.T) {
kubeInformers := informers.NewSharedInformerFactory(fakeKubeClient, 0)
testLog := testlogger.New(t)
cache := provider.NewDynamicUpstreamIDPProvider()
cache.SetIDPList([]provider.UpstreamOIDCIdentityProviderI{
cache.SetOIDCIdentityProviders([]provider.UpstreamOIDCIdentityProviderI{
&upstreamoidc.ProviderConfig{Name: "initial-entry"},
})
secretInformer := kubeInformers.Core().V1().Secrets()
@ -101,7 +101,7 @@ func TestControllerFilterSecret(t *testing.T) {
}
}
func TestController(t *testing.T) {
func TestOIDCUpstreamWatcherControllerSync(t *testing.T) {
t.Parallel()
now := metav1.NewTime(time.Now().UTC())
earlier := metav1.NewTime(now.Add(-1 * time.Hour).UTC())
@ -150,9 +150,9 @@ func TestController(t *testing.T) {
inputSecrets: []runtime.Object{},
wantErr: controllerlib.ErrSyntheticRequeue.Error(),
wantLogs: []string{
`upstream-observer "level"=0 "msg"="updated condition" "name"="test-name" "namespace"="test-namespace" "message"="secret \"test-client-secret\" not found" "reason"="SecretNotFound" "status"="False" "type"="ClientCredentialsValid"`,
`upstream-observer "level"=0 "msg"="updated condition" "name"="test-name" "namespace"="test-namespace" "message"="discovered issuer configuration" "reason"="Success" "status"="True" "type"="OIDCDiscoverySucceeded"`,
`upstream-observer "msg"="found failing condition" "error"="OIDCIdentityProvider has a failing condition" "message"="secret \"test-client-secret\" not found" "name"="test-name" "namespace"="test-namespace" "reason"="SecretNotFound" "type"="ClientCredentialsValid"`,
`oidc-upstream-observer "level"=0 "msg"="updated condition" "name"="test-name" "namespace"="test-namespace" "message"="secret \"test-client-secret\" not found" "reason"="SecretNotFound" "status"="False" "type"="ClientCredentialsValid"`,
`oidc-upstream-observer "level"=0 "msg"="updated condition" "name"="test-name" "namespace"="test-namespace" "message"="discovered issuer configuration" "reason"="Success" "status"="True" "type"="OIDCDiscoverySucceeded"`,
`oidc-upstream-observer "msg"="found failing condition" "error"="OIDCIdentityProvider has a failing condition" "message"="secret \"test-client-secret\" not found" "name"="test-name" "namespace"="test-namespace" "reason"="SecretNotFound" "type"="ClientCredentialsValid"`,
},
wantResultingCache: []provider.UpstreamOIDCIdentityProviderI{},
wantResultingUpstreams: []v1alpha1.OIDCIdentityProvider{{
@ -196,9 +196,9 @@ func TestController(t *testing.T) {
}},
wantErr: controllerlib.ErrSyntheticRequeue.Error(),
wantLogs: []string{
`upstream-observer "level"=0 "msg"="updated condition" "name"="test-name" "namespace"="test-namespace" "message"="referenced Secret \"test-client-secret\" has wrong type \"some-other-type\" (should be \"secrets.pinniped.dev/oidc-client\")" "reason"="SecretWrongType" "status"="False" "type"="ClientCredentialsValid"`,
`upstream-observer "level"=0 "msg"="updated condition" "name"="test-name" "namespace"="test-namespace" "message"="discovered issuer configuration" "reason"="Success" "status"="True" "type"="OIDCDiscoverySucceeded"`,
`upstream-observer "msg"="found failing condition" "error"="OIDCIdentityProvider has a failing condition" "message"="referenced Secret \"test-client-secret\" has wrong type \"some-other-type\" (should be \"secrets.pinniped.dev/oidc-client\")" "name"="test-name" "namespace"="test-namespace" "reason"="SecretWrongType" "type"="ClientCredentialsValid"`,
`oidc-upstream-observer "level"=0 "msg"="updated condition" "name"="test-name" "namespace"="test-namespace" "message"="referenced Secret \"test-client-secret\" has wrong type \"some-other-type\" (should be \"secrets.pinniped.dev/oidc-client\")" "reason"="SecretWrongType" "status"="False" "type"="ClientCredentialsValid"`,
`oidc-upstream-observer "level"=0 "msg"="updated condition" "name"="test-name" "namespace"="test-namespace" "message"="discovered issuer configuration" "reason"="Success" "status"="True" "type"="OIDCDiscoverySucceeded"`,
`oidc-upstream-observer "msg"="found failing condition" "error"="OIDCIdentityProvider has a failing condition" "message"="referenced Secret \"test-client-secret\" has wrong type \"some-other-type\" (should be \"secrets.pinniped.dev/oidc-client\")" "name"="test-name" "namespace"="test-namespace" "reason"="SecretWrongType" "type"="ClientCredentialsValid"`,
},
wantResultingCache: []provider.UpstreamOIDCIdentityProviderI{},
wantResultingUpstreams: []v1alpha1.OIDCIdentityProvider{{
@ -241,9 +241,9 @@ func TestController(t *testing.T) {
}},
wantErr: controllerlib.ErrSyntheticRequeue.Error(),
wantLogs: []string{
`upstream-observer "level"=0 "msg"="updated condition" "name"="test-name" "namespace"="test-namespace" "message"="referenced Secret \"test-client-secret\" is missing required keys [\"clientID\" \"clientSecret\"]" "reason"="SecretMissingKeys" "status"="False" "type"="ClientCredentialsValid"`,
`upstream-observer "level"=0 "msg"="updated condition" "name"="test-name" "namespace"="test-namespace" "message"="discovered issuer configuration" "reason"="Success" "status"="True" "type"="OIDCDiscoverySucceeded"`,
`upstream-observer "msg"="found failing condition" "error"="OIDCIdentityProvider has a failing condition" "message"="referenced Secret \"test-client-secret\" is missing required keys [\"clientID\" \"clientSecret\"]" "name"="test-name" "namespace"="test-namespace" "reason"="SecretMissingKeys" "type"="ClientCredentialsValid"`,
`oidc-upstream-observer "level"=0 "msg"="updated condition" "name"="test-name" "namespace"="test-namespace" "message"="referenced Secret \"test-client-secret\" is missing required keys [\"clientID\" \"clientSecret\"]" "reason"="SecretMissingKeys" "status"="False" "type"="ClientCredentialsValid"`,
`oidc-upstream-observer "level"=0 "msg"="updated condition" "name"="test-name" "namespace"="test-namespace" "message"="discovered issuer configuration" "reason"="Success" "status"="True" "type"="OIDCDiscoverySucceeded"`,
`oidc-upstream-observer "msg"="found failing condition" "error"="OIDCIdentityProvider has a failing condition" "message"="referenced Secret \"test-client-secret\" is missing required keys [\"clientID\" \"clientSecret\"]" "name"="test-name" "namespace"="test-namespace" "reason"="SecretMissingKeys" "type"="ClientCredentialsValid"`,
},
wantResultingCache: []provider.UpstreamOIDCIdentityProviderI{},
wantResultingUpstreams: []v1alpha1.OIDCIdentityProvider{{
@ -289,9 +289,9 @@ func TestController(t *testing.T) {
}},
wantErr: controllerlib.ErrSyntheticRequeue.Error(),
wantLogs: []string{
`upstream-observer "level"=0 "msg"="updated condition" "name"="test-name" "namespace"="test-namespace" "message"="loaded client credentials" "reason"="Success" "status"="True" "type"="ClientCredentialsValid"`,
`upstream-observer "level"=0 "msg"="updated condition" "name"="test-name" "namespace"="test-namespace" "message"="spec.certificateAuthorityData is invalid: illegal base64 data at input byte 7" "reason"="InvalidTLSConfig" "status"="False" "type"="OIDCDiscoverySucceeded"`,
`upstream-observer "msg"="found failing condition" "error"="OIDCIdentityProvider has a failing condition" "message"="spec.certificateAuthorityData is invalid: illegal base64 data at input byte 7" "name"="test-name" "namespace"="test-namespace" "reason"="InvalidTLSConfig" "type"="OIDCDiscoverySucceeded"`,
`oidc-upstream-observer "level"=0 "msg"="updated condition" "name"="test-name" "namespace"="test-namespace" "message"="loaded client credentials" "reason"="Success" "status"="True" "type"="ClientCredentialsValid"`,
`oidc-upstream-observer "level"=0 "msg"="updated condition" "name"="test-name" "namespace"="test-namespace" "message"="spec.certificateAuthorityData is invalid: illegal base64 data at input byte 7" "reason"="InvalidTLSConfig" "status"="False" "type"="OIDCDiscoverySucceeded"`,
`oidc-upstream-observer "msg"="found failing condition" "error"="OIDCIdentityProvider has a failing condition" "message"="spec.certificateAuthorityData is invalid: illegal base64 data at input byte 7" "name"="test-name" "namespace"="test-namespace" "reason"="InvalidTLSConfig" "type"="OIDCDiscoverySucceeded"`,
},
wantResultingCache: []provider.UpstreamOIDCIdentityProviderI{},
wantResultingUpstreams: []v1alpha1.OIDCIdentityProvider{{
@ -337,9 +337,9 @@ func TestController(t *testing.T) {
}},
wantErr: controllerlib.ErrSyntheticRequeue.Error(),
wantLogs: []string{
`upstream-observer "level"=0 "msg"="updated condition" "name"="test-name" "namespace"="test-namespace" "message"="loaded client credentials" "reason"="Success" "status"="True" "type"="ClientCredentialsValid"`,
`upstream-observer "level"=0 "msg"="updated condition" "name"="test-name" "namespace"="test-namespace" "message"="spec.certificateAuthorityData is invalid: no certificates found" "reason"="InvalidTLSConfig" "status"="False" "type"="OIDCDiscoverySucceeded"`,
`upstream-observer "msg"="found failing condition" "error"="OIDCIdentityProvider has a failing condition" "message"="spec.certificateAuthorityData is invalid: no certificates found" "name"="test-name" "namespace"="test-namespace" "reason"="InvalidTLSConfig" "type"="OIDCDiscoverySucceeded"`,
`oidc-upstream-observer "level"=0 "msg"="updated condition" "name"="test-name" "namespace"="test-namespace" "message"="loaded client credentials" "reason"="Success" "status"="True" "type"="ClientCredentialsValid"`,
`oidc-upstream-observer "level"=0 "msg"="updated condition" "name"="test-name" "namespace"="test-namespace" "message"="spec.certificateAuthorityData is invalid: no certificates found" "reason"="InvalidTLSConfig" "status"="False" "type"="OIDCDiscoverySucceeded"`,
`oidc-upstream-observer "msg"="found failing condition" "error"="OIDCIdentityProvider has a failing condition" "message"="spec.certificateAuthorityData is invalid: no certificates found" "name"="test-name" "namespace"="test-namespace" "reason"="InvalidTLSConfig" "type"="OIDCDiscoverySucceeded"`,
},
wantResultingCache: []provider.UpstreamOIDCIdentityProviderI{},
wantResultingUpstreams: []v1alpha1.OIDCIdentityProvider{{
@ -382,10 +382,10 @@ func TestController(t *testing.T) {
}},
wantErr: controllerlib.ErrSyntheticRequeue.Error(),
wantLogs: []string{
`upstream-observer "msg"="failed to perform OIDC discovery" "error"="Get \"invalid-url-that-is-really-really-long/.well-known/openid-configuration\": unsupported protocol scheme \"\"" "issuer"="invalid-url-that-is-really-really-long" "name"="test-name" "namespace"="test-namespace"`,
`upstream-observer "level"=0 "msg"="updated condition" "name"="test-name" "namespace"="test-namespace" "message"="loaded client credentials" "reason"="Success" "status"="True" "type"="ClientCredentialsValid"`,
`upstream-observer "level"=0 "msg"="updated condition" "name"="test-name" "namespace"="test-namespace" "message"="failed to perform OIDC discovery against \"invalid-url-that-is-really-really-long\":\nGet \"invalid-url-that-is-really-really-long/.well-known/openid-configuration\": unsupported protocol [truncated 9 chars]" "reason"="Unreachable" "status"="False" "type"="OIDCDiscoverySucceeded"`,
`upstream-observer "msg"="found failing condition" "error"="OIDCIdentityProvider has a failing condition" "message"="failed to perform OIDC discovery against \"invalid-url-that-is-really-really-long\":\nGet \"invalid-url-that-is-really-really-long/.well-known/openid-configuration\": unsupported protocol [truncated 9 chars]" "name"="test-name" "namespace"="test-namespace" "reason"="Unreachable" "type"="OIDCDiscoverySucceeded"`,
`oidc-upstream-observer "msg"="failed to perform OIDC discovery" "error"="Get \"invalid-url-that-is-really-really-long/.well-known/openid-configuration\": unsupported protocol scheme \"\"" "issuer"="invalid-url-that-is-really-really-long" "name"="test-name" "namespace"="test-namespace"`,
`oidc-upstream-observer "level"=0 "msg"="updated condition" "name"="test-name" "namespace"="test-namespace" "message"="loaded client credentials" "reason"="Success" "status"="True" "type"="ClientCredentialsValid"`,
`oidc-upstream-observer "level"=0 "msg"="updated condition" "name"="test-name" "namespace"="test-namespace" "message"="failed to perform OIDC discovery against \"invalid-url-that-is-really-really-long\":\nGet \"invalid-url-that-is-really-really-long/.well-known/openid-configuration\": unsupported protocol [truncated 9 chars]" "reason"="Unreachable" "status"="False" "type"="OIDCDiscoverySucceeded"`,
`oidc-upstream-observer "msg"="found failing condition" "error"="OIDCIdentityProvider has a failing condition" "message"="failed to perform OIDC discovery against \"invalid-url-that-is-really-really-long\":\nGet \"invalid-url-that-is-really-really-long/.well-known/openid-configuration\": unsupported protocol [truncated 9 chars]" "name"="test-name" "namespace"="test-namespace" "reason"="Unreachable" "type"="OIDCDiscoverySucceeded"`,
},
wantResultingCache: []provider.UpstreamOIDCIdentityProviderI{},
wantResultingUpstreams: []v1alpha1.OIDCIdentityProvider{{
@ -430,9 +430,9 @@ Get "invalid-url-that-is-really-really-long/.well-known/openid-configuration": u
}},
wantErr: controllerlib.ErrSyntheticRequeue.Error(),
wantLogs: []string{
`upstream-observer "level"=0 "msg"="updated condition" "name"="test-name" "namespace"="test-namespace" "message"="loaded client credentials" "reason"="Success" "status"="True" "type"="ClientCredentialsValid"`,
`upstream-observer "level"=0 "msg"="updated condition" "name"="test-name" "namespace"="test-namespace" "message"="failed to parse authorization endpoint URL: parse \"%\": invalid URL escape \"%\"" "reason"="InvalidResponse" "status"="False" "type"="OIDCDiscoverySucceeded"`,
`upstream-observer "msg"="found failing condition" "error"="OIDCIdentityProvider has a failing condition" "message"="failed to parse authorization endpoint URL: parse \"%\": invalid URL escape \"%\"" "name"="test-name" "namespace"="test-namespace" "reason"="InvalidResponse" "type"="OIDCDiscoverySucceeded"`,
`oidc-upstream-observer "level"=0 "msg"="updated condition" "name"="test-name" "namespace"="test-namespace" "message"="loaded client credentials" "reason"="Success" "status"="True" "type"="ClientCredentialsValid"`,
`oidc-upstream-observer "level"=0 "msg"="updated condition" "name"="test-name" "namespace"="test-namespace" "message"="failed to parse authorization endpoint URL: parse \"%\": invalid URL escape \"%\"" "reason"="InvalidResponse" "status"="False" "type"="OIDCDiscoverySucceeded"`,
`oidc-upstream-observer "msg"="found failing condition" "error"="OIDCIdentityProvider has a failing condition" "message"="failed to parse authorization endpoint URL: parse \"%\": invalid URL escape \"%\"" "name"="test-name" "namespace"="test-namespace" "reason"="InvalidResponse" "type"="OIDCDiscoverySucceeded"`,
},
wantResultingCache: []provider.UpstreamOIDCIdentityProviderI{},
wantResultingUpstreams: []v1alpha1.OIDCIdentityProvider{{
@ -476,9 +476,9 @@ Get "invalid-url-that-is-really-really-long/.well-known/openid-configuration": u
}},
wantErr: controllerlib.ErrSyntheticRequeue.Error(),
wantLogs: []string{
`upstream-observer "level"=0 "msg"="updated condition" "name"="test-name" "namespace"="test-namespace" "message"="loaded client credentials" "reason"="Success" "status"="True" "type"="ClientCredentialsValid"`,
`upstream-observer "level"=0 "msg"="updated condition" "name"="test-name" "namespace"="test-namespace" "message"="authorization endpoint URL scheme must be \"https\", not \"http\"" "reason"="InvalidResponse" "status"="False" "type"="OIDCDiscoverySucceeded"`,
`upstream-observer "msg"="found failing condition" "error"="OIDCIdentityProvider has a failing condition" "message"="authorization endpoint URL scheme must be \"https\", not \"http\"" "name"="test-name" "namespace"="test-namespace" "reason"="InvalidResponse" "type"="OIDCDiscoverySucceeded"`,
`oidc-upstream-observer "level"=0 "msg"="updated condition" "name"="test-name" "namespace"="test-namespace" "message"="loaded client credentials" "reason"="Success" "status"="True" "type"="ClientCredentialsValid"`,
`oidc-upstream-observer "level"=0 "msg"="updated condition" "name"="test-name" "namespace"="test-namespace" "message"="authorization endpoint URL scheme must be \"https\", not \"http\"" "reason"="InvalidResponse" "status"="False" "type"="OIDCDiscoverySucceeded"`,
`oidc-upstream-observer "msg"="found failing condition" "error"="OIDCIdentityProvider has a failing condition" "message"="authorization endpoint URL scheme must be \"https\", not \"http\"" "name"="test-name" "namespace"="test-namespace" "reason"="InvalidResponse" "type"="OIDCDiscoverySucceeded"`,
},
wantResultingCache: []provider.UpstreamOIDCIdentityProviderI{},
wantResultingUpstreams: []v1alpha1.OIDCIdentityProvider{{
@ -529,8 +529,8 @@ Get "invalid-url-that-is-really-really-long/.well-known/openid-configuration": u
Data: testValidSecretData,
}},
wantLogs: []string{
`upstream-observer "level"=0 "msg"="updated condition" "name"="test-name" "namespace"="test-namespace" "message"="loaded client credentials" "reason"="Success" "status"="True" "type"="ClientCredentialsValid"`,
`upstream-observer "level"=0 "msg"="updated condition" "name"="test-name" "namespace"="test-namespace" "message"="discovered issuer configuration" "reason"="Success" "status"="True" "type"="OIDCDiscoverySucceeded"`,
`oidc-upstream-observer "level"=0 "msg"="updated condition" "name"="test-name" "namespace"="test-namespace" "message"="loaded client credentials" "reason"="Success" "status"="True" "type"="ClientCredentialsValid"`,
`oidc-upstream-observer "level"=0 "msg"="updated condition" "name"="test-name" "namespace"="test-namespace" "message"="discovered issuer configuration" "reason"="Success" "status"="True" "type"="OIDCDiscoverySucceeded"`,
},
wantResultingCache: []provider.UpstreamOIDCIdentityProviderI{
&oidctestutil.TestUpstreamOIDCIdentityProvider{
@ -578,8 +578,8 @@ Get "invalid-url-that-is-really-really-long/.well-known/openid-configuration": u
Data: testValidSecretData,
}},
wantLogs: []string{
`upstream-observer "level"=0 "msg"="updated condition" "name"="test-name" "namespace"="test-namespace" "message"="loaded client credentials" "reason"="Success" "status"="True" "type"="ClientCredentialsValid"`,
`upstream-observer "level"=0 "msg"="updated condition" "name"="test-name" "namespace"="test-namespace" "message"="discovered issuer configuration" "reason"="Success" "status"="True" "type"="OIDCDiscoverySucceeded"`,
`oidc-upstream-observer "level"=0 "msg"="updated condition" "name"="test-name" "namespace"="test-namespace" "message"="loaded client credentials" "reason"="Success" "status"="True" "type"="ClientCredentialsValid"`,
`oidc-upstream-observer "level"=0 "msg"="updated condition" "name"="test-name" "namespace"="test-namespace" "message"="discovered issuer configuration" "reason"="Success" "status"="True" "type"="OIDCDiscoverySucceeded"`,
},
wantResultingCache: []provider.UpstreamOIDCIdentityProviderI{
&oidctestutil.TestUpstreamOIDCIdentityProvider{
@ -627,8 +627,8 @@ Get "invalid-url-that-is-really-really-long/.well-known/openid-configuration": u
Data: testValidSecretData,
}},
wantLogs: []string{
`upstream-observer "level"=0 "msg"="updated condition" "name"="test-name" "namespace"="test-namespace" "message"="loaded client credentials" "reason"="Success" "status"="True" "type"="ClientCredentialsValid"`,
`upstream-observer "level"=0 "msg"="updated condition" "name"="test-name" "namespace"="test-namespace" "message"="discovered issuer configuration" "reason"="Success" "status"="True" "type"="OIDCDiscoverySucceeded"`,
`oidc-upstream-observer "level"=0 "msg"="updated condition" "name"="test-name" "namespace"="test-namespace" "message"="loaded client credentials" "reason"="Success" "status"="True" "type"="ClientCredentialsValid"`,
`oidc-upstream-observer "level"=0 "msg"="updated condition" "name"="test-name" "namespace"="test-namespace" "message"="discovered issuer configuration" "reason"="Success" "status"="True" "type"="OIDCDiscoverySucceeded"`,
},
wantResultingCache: []provider.UpstreamOIDCIdentityProviderI{
&oidctestutil.TestUpstreamOIDCIdentityProvider{
@ -669,10 +669,10 @@ Get "invalid-url-that-is-really-really-long/.well-known/openid-configuration": u
}},
wantErr: controllerlib.ErrSyntheticRequeue.Error(),
wantLogs: []string{
`upstream-observer "msg"="failed to perform OIDC discovery" "error"="oidc: issuer did not match the issuer returned by provider, expected \"` + testIssuerURL + `/ends-with-slash\" got \"` + testIssuerURL + `/ends-with-slash/\"" "issuer"="` + testIssuerURL + `/ends-with-slash" "name"="test-name" "namespace"="test-namespace"`,
`upstream-observer "level"=0 "msg"="updated condition" "name"="test-name" "namespace"="test-namespace" "message"="loaded client credentials" "reason"="Success" "status"="True" "type"="ClientCredentialsValid"`,
`upstream-observer "level"=0 "msg"="updated condition" "name"="test-name" "namespace"="test-namespace" "message"="failed to perform OIDC discovery against \"` + testIssuerURL + `/ends-with-slash\":\noidc: issuer did not match the issuer returned by provider, expected \"` + testIssuerURL + `/ends-with-slash\" got \"` + testIssuerURL + `/ends-with-slash/\"" "reason"="Unreachable" "status"="False" "type"="OIDCDiscoverySucceeded"`,
`upstream-observer "msg"="found failing condition" "error"="OIDCIdentityProvider has a failing condition" "message"="failed to perform OIDC discovery against \"` + testIssuerURL + `/ends-with-slash\":\noidc: issuer did not match the issuer returned by provider, expected \"` + testIssuerURL + `/ends-with-slash\" got \"` + testIssuerURL + `/ends-with-slash/\"" "name"="test-name" "namespace"="test-namespace" "reason"="Unreachable" "type"="OIDCDiscoverySucceeded"`,
`oidc-upstream-observer "msg"="failed to perform OIDC discovery" "error"="oidc: issuer did not match the issuer returned by provider, expected \"` + testIssuerURL + `/ends-with-slash\" got \"` + testIssuerURL + `/ends-with-slash/\"" "issuer"="` + testIssuerURL + `/ends-with-slash" "name"="test-name" "namespace"="test-namespace"`,
`oidc-upstream-observer "level"=0 "msg"="updated condition" "name"="test-name" "namespace"="test-namespace" "message"="loaded client credentials" "reason"="Success" "status"="True" "type"="ClientCredentialsValid"`,
`oidc-upstream-observer "level"=0 "msg"="updated condition" "name"="test-name" "namespace"="test-namespace" "message"="failed to perform OIDC discovery against \"` + testIssuerURL + `/ends-with-slash\":\noidc: issuer did not match the issuer returned by provider, expected \"` + testIssuerURL + `/ends-with-slash\" got \"` + testIssuerURL + `/ends-with-slash/\"" "reason"="Unreachable" "status"="False" "type"="OIDCDiscoverySucceeded"`,
`oidc-upstream-observer "msg"="found failing condition" "error"="OIDCIdentityProvider has a failing condition" "message"="failed to perform OIDC discovery against \"` + testIssuerURL + `/ends-with-slash\":\noidc: issuer did not match the issuer returned by provider, expected \"` + testIssuerURL + `/ends-with-slash\" got \"` + testIssuerURL + `/ends-with-slash/\"" "name"="test-name" "namespace"="test-namespace" "reason"="Unreachable" "type"="OIDCDiscoverySucceeded"`,
},
wantResultingCache: []provider.UpstreamOIDCIdentityProviderI{},
wantResultingUpstreams: []v1alpha1.OIDCIdentityProvider{{
@ -717,10 +717,10 @@ oidc: issuer did not match the issuer returned by provider, expected "` + testIs
}},
wantErr: controllerlib.ErrSyntheticRequeue.Error(),
wantLogs: []string{
`upstream-observer "msg"="failed to perform OIDC discovery" "error"="oidc: issuer did not match the issuer returned by provider, expected \"` + testIssuerURL + `/\" got \"` + testIssuerURL + `\"" "issuer"="` + testIssuerURL + `/" "name"="test-name" "namespace"="test-namespace"`,
`upstream-observer "level"=0 "msg"="updated condition" "name"="test-name" "namespace"="test-namespace" "message"="loaded client credentials" "reason"="Success" "status"="True" "type"="ClientCredentialsValid"`,
`upstream-observer "level"=0 "msg"="updated condition" "name"="test-name" "namespace"="test-namespace" "message"="failed to perform OIDC discovery against \"` + testIssuerURL + `/\":\noidc: issuer did not match the issuer returned by provider, expected \"` + testIssuerURL + `/\" got \"` + testIssuerURL + `\"" "reason"="Unreachable" "status"="False" "type"="OIDCDiscoverySucceeded"`,
`upstream-observer "msg"="found failing condition" "error"="OIDCIdentityProvider has a failing condition" "message"="failed to perform OIDC discovery against \"` + testIssuerURL + `/\":\noidc: issuer did not match the issuer returned by provider, expected \"` + testIssuerURL + `/\" got \"` + testIssuerURL + `\"" "name"="test-name" "namespace"="test-namespace" "reason"="Unreachable" "type"="OIDCDiscoverySucceeded"`,
`oidc-upstream-observer "msg"="failed to perform OIDC discovery" "error"="oidc: issuer did not match the issuer returned by provider, expected \"` + testIssuerURL + `/\" got \"` + testIssuerURL + `\"" "issuer"="` + testIssuerURL + `/" "name"="test-name" "namespace"="test-namespace"`,
`oidc-upstream-observer "level"=0 "msg"="updated condition" "name"="test-name" "namespace"="test-namespace" "message"="loaded client credentials" "reason"="Success" "status"="True" "type"="ClientCredentialsValid"`,
`oidc-upstream-observer "level"=0 "msg"="updated condition" "name"="test-name" "namespace"="test-namespace" "message"="failed to perform OIDC discovery against \"` + testIssuerURL + `/\":\noidc: issuer did not match the issuer returned by provider, expected \"` + testIssuerURL + `/\" got \"` + testIssuerURL + `\"" "reason"="Unreachable" "status"="False" "type"="OIDCDiscoverySucceeded"`,
`oidc-upstream-observer "msg"="found failing condition" "error"="OIDCIdentityProvider has a failing condition" "message"="failed to perform OIDC discovery against \"` + testIssuerURL + `/\":\noidc: issuer did not match the issuer returned by provider, expected \"` + testIssuerURL + `/\" got \"` + testIssuerURL + `\"" "name"="test-name" "namespace"="test-namespace" "reason"="Unreachable" "type"="OIDCDiscoverySucceeded"`,
},
wantResultingCache: []provider.UpstreamOIDCIdentityProviderI{},
wantResultingUpstreams: []v1alpha1.OIDCIdentityProvider{{
@ -758,7 +758,7 @@ oidc: issuer did not match the issuer returned by provider, expected "` + testIs
kubeInformers := informers.NewSharedInformerFactory(fakeKubeClient, 0)
testLog := testlogger.New(t)
cache := provider.NewDynamicUpstreamIDPProvider()
cache.SetIDPList([]provider.UpstreamOIDCIdentityProviderI{
cache.SetOIDCIdentityProviders([]provider.UpstreamOIDCIdentityProviderI{
&upstreamoidc.ProviderConfig{Name: "initial-entry"},
})
@ -787,7 +787,7 @@ oidc: issuer did not match the issuer returned by provider, expected "` + testIs
}
require.Equal(t, strings.Join(tt.wantLogs, "\n"), strings.Join(testLog.Lines(), "\n"))
actualIDPList := cache.GetIDPList()
actualIDPList := cache.GetOIDCIdentityProviders()
require.Equal(t, len(tt.wantResultingCache), len(actualIDPList))
for i := range actualIDPList {
actualIDP := actualIDPList[i].(*upstreamoidc.ProviderConfig)
@ -802,8 +802,8 @@ oidc: issuer did not match the issuer returned by provider, expected "` + testIs
actualUpstreams, err := fakePinnipedClient.IDPV1alpha1().OIDCIdentityProviders(testNamespace).List(ctx, metav1.ListOptions{})
require.NoError(t, err)
// Preprocess the set of upstreams a bit so that they're easier to assert against.
require.ElementsMatch(t, tt.wantResultingUpstreams, normalizeUpstreams(actualUpstreams.Items, now))
// Assert on the expected Status of the upstreams. Preprocess the upstreams a bit so that they're easier to assert against.
require.ElementsMatch(t, tt.wantResultingUpstreams, normalizeOIDCUpstreams(actualUpstreams.Items, now))
// Running the sync() a second time should be idempotent except for logs, and should return the same error.
// This also helps exercise code paths where the OIDC provider discovery hits cache.
@ -816,7 +816,7 @@ oidc: issuer did not match the issuer returned by provider, expected "` + testIs
}
}
func normalizeUpstreams(upstreams []v1alpha1.OIDCIdentityProvider, now metav1.Time) []v1alpha1.OIDCIdentityProvider {
func normalizeOIDCUpstreams(upstreams []v1alpha1.OIDCIdentityProvider, now metav1.Time) []v1alpha1.OIDCIdentityProvider {
result := make([]v1alpha1.OIDCIdentityProvider, 0, len(upstreams))
for _, u := range upstreams {
normalized := u.DeepCopy()

View File

@ -0,0 +1,16 @@
// Copyright 2021 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package upstreamwatchers
import "go.pinniped.dev/internal/constable"
const (
ReasonNotFound = "SecretNotFound"
ReasonWrongType = "SecretWrongType"
ReasonMissingKeys = "SecretMissingKeys"
ReasonSuccess = "Success"
ReasonInvalidTLSConfig = "InvalidTLSConfig"
ErrNoCertificates = constable.Error("no certificates found")
)

View File

@ -1,4 +1,4 @@
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
// Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package supervisorstorage

View File

@ -0,0 +1,21 @@
// Copyright 2021 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package fositestoragei
import (
"github.com/ory/fosite"
"github.com/ory/fosite/handler/oauth2"
"github.com/ory/fosite/handler/openid"
"github.com/ory/fosite/handler/pkce"
)
// This interface seems to be missing from Fosite.
// Not having this interface makes it a pain to avoid cyclical test dependencies, so we'll define it.
type AllFositeStorage interface {
fosite.ClientManager
oauth2.CoreStorage
oauth2.TokenRevocationStorage
openid.OpenIDConnectRequestStorage
pkce.PKCERequestStorage
}

Some files were not shown because too many files have changed in this diff Show More