2023-05-08 21:07:38 +00:00
// Copyright 2020-2023 the Pinniped contributors. All Rights Reserved.
2020-10-08 02:18:34 +00:00
// SPDX-License-Identifier: Apache-2.0
package supervisorconfig
import (
2020-10-08 17:27:45 +00:00
"context"
"fmt"
2020-10-23 23:25:44 +00:00
"net/url"
2023-07-12 16:43:33 +00:00
"sort"
2020-10-23 23:25:44 +00:00
"strings"
2023-05-08 21:07:38 +00:00
"time"
2020-10-08 18:28:21 +00:00
2023-07-11 20:25:08 +00:00
corev1 "k8s.io/api/core/v1"
2023-06-30 20:43:40 +00:00
"k8s.io/apimachinery/pkg/api/equality"
2023-07-10 21:09:19 +00:00
"k8s.io/apimachinery/pkg/api/errors"
2020-10-08 17:27:45 +00:00
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2020-10-08 02:18:34 +00:00
"k8s.io/apimachinery/pkg/labels"
2023-05-08 21:07:38 +00:00
"k8s.io/apimachinery/pkg/types"
2023-07-10 21:09:19 +00:00
errorsutil "k8s.io/apimachinery/pkg/util/errors"
2023-07-12 16:43:33 +00:00
"k8s.io/apimachinery/pkg/util/sets"
2021-12-10 22:22:36 +00:00
"k8s.io/utils/clock"
2020-10-08 02:18:34 +00:00
2021-02-16 19:00:08 +00:00
configv1alpha1 "go.pinniped.dev/generated/latest/apis/supervisor/config/v1alpha1"
pinnipedclientset "go.pinniped.dev/generated/latest/client/supervisor/clientset/versioned"
configinformers "go.pinniped.dev/generated/latest/client/supervisor/informers/externalversions/config/v1alpha1"
2023-05-08 21:07:38 +00:00
idpinformers "go.pinniped.dev/generated/latest/client/supervisor/informers/externalversions/idp/v1alpha1"
"go.pinniped.dev/internal/celtransformer"
2020-10-08 02:18:34 +00:00
pinnipedcontroller "go.pinniped.dev/internal/controller"
2023-06-30 20:43:40 +00:00
"go.pinniped.dev/internal/controller/conditionsutil"
2020-10-08 02:18:34 +00:00
"go.pinniped.dev/internal/controllerlib"
2023-06-22 22:12:33 +00:00
"go.pinniped.dev/internal/federationdomain/federationdomainproviders"
2023-05-08 21:07:38 +00:00
"go.pinniped.dev/internal/idtransform"
2020-11-10 15:22:16 +00:00
"go.pinniped.dev/internal/plog"
2020-10-08 02:18:34 +00:00
)
2023-06-30 20:43:40 +00:00
const (
2023-07-12 20:15:52 +00:00
typeReady = "Ready"
typeIssuerURLValid = "IssuerURLValid"
typeOneTLSSecretPerIssuerHostname = "OneTLSSecretPerIssuerHostname"
typeIssuerIsUnique = "IssuerIsUnique"
typeIdentityProvidersFound = "IdentityProvidersFound"
typeIdentityProvidersDisplayNamesUnique = "IdentityProvidersDisplayNamesUnique"
typeIdentityProvidersAPIGroupSuffixValid = "IdentityProvidersObjectRefAPIGroupSuffixValid"
typeIdentityProvidersObjectRefKindValid = "IdentityProvidersObjectRefKindValid"
2023-07-13 22:26:32 +00:00
typeTransformsConstantsNamesUnique = "TransformsConstantsNamesUnique"
2023-07-14 19:06:20 +00:00
typeTransformsExpressionsValid = "TransformsExpressionsValid"
2023-07-14 23:50:43 +00:00
typeTransformsExamplesPassed = "TransformsExamplesPassed"
2023-06-30 20:43:40 +00:00
2023-07-10 21:09:19 +00:00
reasonSuccess = "Success"
reasonNotReady = "NotReady"
reasonUnableToValidate = "UnableToValidate"
reasonInvalidIssuerURL = "InvalidIssuerURL"
reasonDuplicateIssuer = "DuplicateIssuer"
reasonDifferentSecretRefsFound = "DifferentSecretRefsFound"
reasonLegacyConfigurationSuccess = "LegacyConfigurationSuccess"
reasonLegacyConfigurationIdentityProviderNotFound = "LegacyConfigurationIdentityProviderNotFound"
reasonIdentityProvidersObjectRefsNotFound = "IdentityProvidersObjectRefsNotFound"
reasonIdentityProviderNotSpecified = "IdentityProviderNotSpecified"
2023-07-12 16:43:33 +00:00
reasonDuplicateDisplayNames = "DuplicateDisplayNames"
2023-07-12 20:15:52 +00:00
reasonAPIGroupNameUnrecognized = "APIGroupUnrecognized"
reasonKindUnrecognized = "KindUnrecognized"
2023-07-13 22:26:32 +00:00
reasonDuplicateConstantsNames = "DuplicateConstantsNames"
2023-07-14 19:06:20 +00:00
reasonInvalidTransformsExpressions = "InvalidTransformsExpressions"
2023-07-14 23:50:43 +00:00
reasonTransformsExamplesFailed = "TransformsExamplesFailed"
2023-07-12 20:15:52 +00:00
kindLDAPIdentityProvider = "LDAPIdentityProvider"
kindOIDCIdentityProvider = "OIDCIdentityProvider"
kindActiveDirectoryIdentityProvider = "ActiveDirectoryIdentityProvider"
2023-06-30 20:43:40 +00:00
celTransformerMaxExpressionRuntime = 5 * time . Second
)
2023-07-10 21:09:19 +00:00
// FederationDomainsSetter can be notified of all known valid providers with its SetFederationDomains function.
2020-10-08 02:18:34 +00:00
// If there are no longer any valid issuers, then it can be called with no arguments.
// Implementations of this type should be thread-safe to support calls from multiple goroutines.
2023-06-13 21:20:39 +00:00
type FederationDomainsSetter interface {
2023-06-22 20:12:50 +00:00
SetFederationDomains ( federationDomains ... * federationdomainproviders . FederationDomainIssuer )
2020-10-08 02:18:34 +00:00
}
2020-12-16 22:27:09 +00:00
type federationDomainWatcherController struct {
2023-06-13 21:20:39 +00:00
federationDomainsSetter FederationDomainsSetter
2023-07-12 17:34:15 +00:00
apiGroup string
2023-06-13 21:20:39 +00:00
clock clock . Clock
client pinnipedclientset . Interface
2023-05-08 21:07:38 +00:00
federationDomainInformer configinformers . FederationDomainInformer
oidcIdentityProviderInformer idpinformers . OIDCIdentityProviderInformer
ldapIdentityProviderInformer idpinformers . LDAPIdentityProviderInformer
activeDirectoryIdentityProviderInformer idpinformers . ActiveDirectoryIdentityProviderInformer
2023-07-11 20:25:08 +00:00
celTransformer * celtransformer . CELTransformer
2023-07-12 20:15:52 +00:00
allowedKinds sets . Set [ string ]
2020-10-08 02:18:34 +00:00
}
2020-12-16 22:27:09 +00:00
// NewFederationDomainWatcherController creates a controllerlib.Controller that watches
// FederationDomain objects and notifies a callback object of the collection of provider configs.
func NewFederationDomainWatcherController (
2023-06-13 21:20:39 +00:00
federationDomainsSetter FederationDomainsSetter ,
2023-07-12 17:34:15 +00:00
apiGroupSuffix string ,
2020-10-08 17:27:45 +00:00
clock clock . Clock ,
client pinnipedclientset . Interface ,
2020-12-17 19:34:49 +00:00
federationDomainInformer configinformers . FederationDomainInformer ,
2023-05-08 21:07:38 +00:00
oidcIdentityProviderInformer idpinformers . OIDCIdentityProviderInformer ,
ldapIdentityProviderInformer idpinformers . LDAPIdentityProviderInformer ,
activeDirectoryIdentityProviderInformer idpinformers . ActiveDirectoryIdentityProviderInformer ,
2020-10-08 02:18:34 +00:00
withInformer pinnipedcontroller . WithInformerOptionFunc ,
) controllerlib . Controller {
2023-07-12 20:15:52 +00:00
allowedKinds := sets . New ( kindActiveDirectoryIdentityProvider , kindLDAPIdentityProvider , kindOIDCIdentityProvider )
2020-10-08 02:18:34 +00:00
return controllerlib . New (
controllerlib . Config {
2020-12-16 22:27:09 +00:00
Name : "FederationDomainWatcherController" ,
Syncer : & federationDomainWatcherController {
2023-06-13 21:20:39 +00:00
federationDomainsSetter : federationDomainsSetter ,
2023-07-12 17:34:15 +00:00
apiGroup : fmt . Sprintf ( "idp.supervisor.%s" , apiGroupSuffix ) ,
2023-05-08 21:07:38 +00:00
clock : clock ,
client : client ,
federationDomainInformer : federationDomainInformer ,
oidcIdentityProviderInformer : oidcIdentityProviderInformer ,
ldapIdentityProviderInformer : ldapIdentityProviderInformer ,
activeDirectoryIdentityProviderInformer : activeDirectoryIdentityProviderInformer ,
2023-07-12 20:15:52 +00:00
allowedKinds : allowedKinds ,
2020-10-08 02:18:34 +00:00
} ,
} ,
withInformer (
2020-12-17 19:34:49 +00:00
federationDomainInformer ,
2020-10-02 17:22:18 +00:00
pinnipedcontroller . MatchAnythingFilter ( pinnipedcontroller . SingletonQueue ( ) ) ,
2020-10-08 02:18:34 +00:00
controllerlib . InformerOption { } ,
) ,
2023-05-08 21:07:38 +00:00
withInformer (
oidcIdentityProviderInformer ,
// Since this controller only cares about IDP metadata names and UIDs (immutable fields),
// we only need to trigger Sync on creates and deletes.
pinnipedcontroller . MatchAnythingIgnoringUpdatesFilter ( pinnipedcontroller . SingletonQueue ( ) ) ,
controllerlib . InformerOption { } ,
) ,
withInformer (
ldapIdentityProviderInformer ,
// Since this controller only cares about IDP metadata names and UIDs (immutable fields),
// we only need to trigger Sync on creates and deletes.
pinnipedcontroller . MatchAnythingIgnoringUpdatesFilter ( pinnipedcontroller . SingletonQueue ( ) ) ,
controllerlib . InformerOption { } ,
) ,
withInformer (
activeDirectoryIdentityProviderInformer ,
// Since this controller only cares about IDP metadata names and UIDs (immutable fields),
// we only need to trigger Sync on creates and deletes.
pinnipedcontroller . MatchAnythingIgnoringUpdatesFilter ( pinnipedcontroller . SingletonQueue ( ) ) ,
controllerlib . InformerOption { } ,
) ,
2020-10-08 02:18:34 +00:00
)
}
// Sync implements controllerlib.Syncer.
2023-07-11 20:25:08 +00:00
func ( c * federationDomainWatcherController ) Sync ( ctx controllerlib . Context ) error {
2020-12-17 19:34:49 +00:00
federationDomains , err := c . federationDomainInformer . Lister ( ) . List ( labels . Everything ( ) )
2020-10-08 02:18:34 +00:00
if err != nil {
return err
}
2023-07-11 20:25:08 +00:00
if c . celTransformer == nil {
2023-07-11 22:42:34 +00:00
c . celTransformer , err = celtransformer . NewCELTransformer ( celTransformerMaxExpressionRuntime )
if err != nil {
return err // shouldn't really happen
}
2023-07-11 20:25:08 +00:00
}
// Process each FederationDomain to validate its spec and to turn it into a FederationDomainIssuer.
2023-07-14 19:06:20 +00:00
federationDomainIssuers , fdToConditionsMap , err := c . processAllFederationDomains ( ctx . Context , federationDomains )
2023-07-11 22:42:34 +00:00
if err != nil {
return err
}
2023-07-11 20:25:08 +00:00
// Load the endpoints of every valid FederationDomain. Removes the endpoints of any
// previous FederationDomains which no longer exist or are no longer valid.
c . federationDomainsSetter . SetFederationDomains ( federationDomainIssuers ... )
// Now that the endpoints of every valid FederationDomain are available, update the
// statuses. This allows clients to wait for Ready without any race conditions in the
// endpoints being available.
2021-02-05 17:56:05 +00:00
var errs [ ] error
2023-07-11 20:25:08 +00:00
for federationDomain , conditions := range fdToConditionsMap {
if err = c . updateStatus ( ctx . Context , federationDomain , conditions ) ; err != nil {
errs = append ( errs , fmt . Errorf ( "could not update status: %w" , err ) )
}
}
return errorsutil . NewAggregate ( errs )
}
func ( c * federationDomainWatcherController ) processAllFederationDomains (
2023-07-14 19:06:20 +00:00
ctx context . Context ,
2023-07-11 20:25:08 +00:00
federationDomains [ ] * configv1alpha1 . FederationDomain ,
) ( [ ] * federationdomainproviders . FederationDomainIssuer , map [ * configv1alpha1 . FederationDomain ] [ ] * configv1alpha1 . Condition , error ) {
2023-06-22 20:12:50 +00:00
federationDomainIssuers := make ( [ ] * federationdomainproviders . FederationDomainIssuer , 0 )
2023-07-11 17:57:11 +00:00
fdToConditionsMap := map [ * configv1alpha1 . FederationDomain ] [ ] * configv1alpha1 . Condition { }
2023-07-11 20:25:08 +00:00
crossDomainConfigValidator := newCrossFederationDomainConfigValidator ( federationDomains )
2023-06-30 20:43:40 +00:00
2020-12-17 19:34:49 +00:00
for _ , federationDomain := range federationDomains {
2023-07-11 20:25:08 +00:00
conditions := make ( [ ] * configv1alpha1 . Condition , 0 )
2020-10-23 23:25:44 +00:00
2023-06-30 20:43:40 +00:00
conditions = crossDomainConfigValidator . Validate ( federationDomain , conditions )
2020-10-08 17:27:45 +00:00
2023-07-14 19:06:20 +00:00
federationDomainIssuer , conditions , err := c . makeFederationDomainIssuer ( ctx , federationDomain , conditions )
2023-07-11 22:42:34 +00:00
if err != nil {
return nil , nil , err
}
2023-07-11 20:25:08 +00:00
// Now that we have determined the conditions, save them for after the loop.
// For a valid FederationDomain, want to update the conditions after we have
// made the FederationDomain's endpoints available.
fdToConditionsMap [ federationDomain ] = conditions
if ! hadErrorCondition ( conditions ) {
// Successfully validated the FederationDomain, so allow it to be loaded.
federationDomainIssuers = append ( federationDomainIssuers , federationDomainIssuer )
2023-05-08 21:07:38 +00:00
}
2023-07-11 20:25:08 +00:00
}
2023-05-08 21:07:38 +00:00
2023-07-11 20:25:08 +00:00
return federationDomainIssuers , fdToConditionsMap , nil
}
2023-07-10 21:09:19 +00:00
2023-07-11 20:25:08 +00:00
func ( c * federationDomainWatcherController ) makeFederationDomainIssuer (
2023-07-14 19:06:20 +00:00
ctx context . Context ,
2023-07-11 20:25:08 +00:00
federationDomain * configv1alpha1 . FederationDomain ,
conditions [ ] * configv1alpha1 . Condition ,
) ( * federationdomainproviders . FederationDomainIssuer , [ ] * configv1alpha1 . Condition , error ) {
2023-07-11 22:42:34 +00:00
var err error
2023-07-11 20:25:08 +00:00
// Create the list of IDPs for this FederationDomain.
// Don't worry if the IDP CRs themselves is phase=Ready because those which are not ready will not be loaded
// into the provider cache, so they cannot actually be used to authenticate.
var federationDomainIssuer * federationdomainproviders . FederationDomainIssuer
if len ( federationDomain . Spec . IdentityProviders ) == 0 {
2023-07-11 22:42:34 +00:00
federationDomainIssuer , conditions , err = c . makeLegacyFederationDomainIssuer ( federationDomain , conditions )
if err != nil {
return nil , nil , err
}
2023-07-11 20:25:08 +00:00
} else {
2023-07-14 19:06:20 +00:00
federationDomainIssuer , conditions , err = c . makeFederationDomainIssuerWithExplicitIDPs ( ctx , federationDomain , conditions )
2023-07-11 22:42:34 +00:00
if err != nil {
return nil , nil , err
}
2023-07-11 20:25:08 +00:00
}
2023-05-08 21:07:38 +00:00
2023-07-11 20:25:08 +00:00
return federationDomainIssuer , conditions , nil
}
2023-07-10 21:09:19 +00:00
2023-07-11 20:25:08 +00:00
func ( c * federationDomainWatcherController ) makeLegacyFederationDomainIssuer (
federationDomain * configv1alpha1 . FederationDomain ,
conditions [ ] * configv1alpha1 . Condition ,
) ( * federationdomainproviders . FederationDomainIssuer , [ ] * configv1alpha1 . Condition , error ) {
var defaultFederationDomainIdentityProvider * federationdomainproviders . FederationDomainIdentityProvider
// When the FederationDomain does not list any IDPs, then we might be in backwards compatibility mode.
2023-07-11 22:42:34 +00:00
oidcIdentityProviders , err := c . oidcIdentityProviderInformer . Lister ( ) . List ( labels . Everything ( ) )
if err != nil {
return nil , nil , err
}
ldapIdentityProviders , err := c . ldapIdentityProviderInformer . Lister ( ) . List ( labels . Everything ( ) )
if err != nil {
return nil , nil , err
}
activeDirectoryIdentityProviders , err := c . activeDirectoryIdentityProviderInformer . Lister ( ) . List ( labels . Everything ( ) )
if err != nil {
return nil , nil , err
}
2023-07-11 20:25:08 +00:00
// Check if that there is exactly one IDP defined in the Supervisor namespace of any IDP CRD type.
idpCRsCount := len ( oidcIdentityProviders ) + len ( ldapIdentityProviders ) + len ( activeDirectoryIdentityProviders )
switch {
case idpCRsCount == 1 :
foundIDPName := ""
// If so, default that IDP's DisplayName to be the same as its resource Name.
defaultFederationDomainIdentityProvider = & federationdomainproviders . FederationDomainIdentityProvider { }
switch {
case len ( oidcIdentityProviders ) == 1 :
defaultFederationDomainIdentityProvider . DisplayName = oidcIdentityProviders [ 0 ] . Name
defaultFederationDomainIdentityProvider . UID = oidcIdentityProviders [ 0 ] . UID
foundIDPName = oidcIdentityProviders [ 0 ] . Name
case len ( ldapIdentityProviders ) == 1 :
defaultFederationDomainIdentityProvider . DisplayName = ldapIdentityProviders [ 0 ] . Name
defaultFederationDomainIdentityProvider . UID = ldapIdentityProviders [ 0 ] . UID
foundIDPName = ldapIdentityProviders [ 0 ] . Name
case len ( activeDirectoryIdentityProviders ) == 1 :
defaultFederationDomainIdentityProvider . DisplayName = activeDirectoryIdentityProviders [ 0 ] . Name
defaultFederationDomainIdentityProvider . UID = activeDirectoryIdentityProviders [ 0 ] . UID
foundIDPName = activeDirectoryIdentityProviders [ 0 ] . Name
2023-07-10 21:09:19 +00:00
}
2023-07-11 20:25:08 +00:00
// Backwards compatibility mode always uses an empty identity transformation pipeline since no
// transformations are defined on the FederationDomain.
defaultFederationDomainIdentityProvider . Transforms = idtransform . NewTransformationPipeline ( )
conditions = append ( conditions , & configv1alpha1 . Condition {
Type : typeIdentityProvidersFound ,
Status : configv1alpha1 . ConditionTrue ,
Reason : reasonLegacyConfigurationSuccess ,
Message : fmt . Sprintf ( "no resources were specified by .spec.identityProviders[].objectRef but exactly one " +
"identity provider resource has been found: using %q as " +
"identity provider: please explicitly list identity providers in .spec.identityProviders " +
"(this legacy configuration mode may be removed in a future version of Pinniped)" , foundIDPName ) ,
} )
case idpCRsCount > 1 :
conditions = append ( conditions , & configv1alpha1 . Condition {
Type : typeIdentityProvidersFound ,
Status : configv1alpha1 . ConditionFalse ,
Reason : reasonIdentityProviderNotSpecified , // vs LegacyConfigurationIdentityProviderNotFound as this is more specific
Message : fmt . Sprintf ( "no resources were specified by .spec.identityProviders[].objectRef " +
"and %q identity provider resources have been found: " +
"please update .spec.identityProviders to specify which identity providers " +
"this federation domain should use" , idpCRsCount ) ,
} )
default :
conditions = append ( conditions , & configv1alpha1 . Condition {
Type : typeIdentityProvidersFound ,
Status : configv1alpha1 . ConditionFalse ,
Reason : reasonLegacyConfigurationIdentityProviderNotFound ,
Message : "no resources were specified by .spec.identityProviders[].objectRef and no identity provider " +
"resources have been found: please create an identity provider resource" ,
} )
}
2023-05-08 21:07:38 +00:00
2023-07-14 19:06:20 +00:00
// This is the constructor for the legacy backwards compatibility mode.
2023-07-11 20:25:08 +00:00
federationDomainIssuer , err := federationdomainproviders . NewFederationDomainIssuerWithDefaultIDP ( federationDomain . Spec . Issuer , defaultFederationDomainIdentityProvider )
conditions = appendIssuerURLValidCondition ( err , conditions )
2023-07-14 19:06:20 +00:00
// These conditions can only have errors when the list of IDPs is explicitly configured,
// and in this case there are no IDPs explicitly configured, so set these conditions all to have no errors.
2023-07-12 20:15:52 +00:00
conditions = appendIdentityProviderDuplicateDisplayNamesCondition ( sets . Set [ string ] { } , conditions )
conditions = appendIdentityProviderObjectRefAPIGroupSuffixCondition ( c . apiGroup , [ ] string { } , conditions )
conditions = appendIdentityProviderObjectRefKindCondition ( c . sortedAllowedKinds ( ) , [ ] string { } , conditions )
2023-07-18 22:00:58 +00:00
conditions = appendTransformsConstantsNamesUniqueCondition ( [ ] string { } , conditions )
2023-07-14 19:06:20 +00:00
conditions = appendTransformsExpressionsValidCondition ( [ ] string { } , conditions )
2023-07-14 23:50:43 +00:00
conditions = appendTransformsExamplesPassedCondition ( [ ] string { } , conditions )
2023-07-12 17:34:15 +00:00
2023-07-11 20:25:08 +00:00
return federationDomainIssuer , conditions , nil
}
2023-07-14 23:50:43 +00:00
//nolint:funlen
2023-07-11 20:25:08 +00:00
func ( c * federationDomainWatcherController ) makeFederationDomainIssuerWithExplicitIDPs (
2023-07-14 19:06:20 +00:00
ctx context . Context ,
2023-07-11 20:25:08 +00:00
federationDomain * configv1alpha1 . FederationDomain ,
conditions [ ] * configv1alpha1 . Condition ,
) ( * federationdomainproviders . FederationDomainIssuer , [ ] * configv1alpha1 . Condition , error ) {
federationDomainIdentityProviders := [ ] * federationdomainproviders . FederationDomainIdentityProvider { }
idpNotFoundIndices := [ ] int { }
2023-07-12 16:43:33 +00:00
displayNames := sets . Set [ string ] { }
duplicateDisplayNames := sets . Set [ string ] { }
2023-07-12 20:15:52 +00:00
badAPIGroupNames := [ ] string { }
badKinds := [ ] string { }
2023-07-18 22:00:58 +00:00
validationErrorMessages := & transformsValidationErrorMessages { }
2023-07-11 20:25:08 +00:00
for index , idp := range federationDomain . Spec . IdentityProviders {
2023-07-14 19:06:20 +00:00
idpIsValid := true
2023-07-12 20:15:52 +00:00
// The CRD requires the displayName field, and validates that it has at least one character,
// so here we only need to validate that they are unique.
2023-07-12 16:43:33 +00:00
if displayNames . Has ( idp . DisplayName ) {
duplicateDisplayNames . Insert ( idp . DisplayName )
2023-07-14 19:06:20 +00:00
idpIsValid = false
2023-07-12 16:43:33 +00:00
}
displayNames . Insert ( idp . DisplayName )
2023-07-12 20:15:52 +00:00
// The objectRef is a required field in the CRD, so it will always exist in practice.
// objectRef.name and objectRef.kind are required, but may be empty strings.
// objectRef.apiGroup is not required, however, so it may be nil or empty string.
canTryToFindIDP := true
apiGroup := ""
2023-07-12 17:34:15 +00:00
if idp . ObjectRef . APIGroup != nil {
apiGroup = * idp . ObjectRef . APIGroup
}
if apiGroup != c . apiGroup {
2023-07-12 20:15:52 +00:00
badAPIGroupNames = append ( badAPIGroupNames , apiGroup )
canTryToFindIDP = false
}
if ! c . allowedKinds . Has ( idp . ObjectRef . Kind ) {
badKinds = append ( badKinds , idp . ObjectRef . Kind )
canTryToFindIDP = false
2023-07-12 17:34:15 +00:00
}
2023-07-12 16:43:33 +00:00
2023-07-14 19:06:20 +00:00
// When the apiGroup and kind are valid, try to find the IDP CR.
2023-07-12 20:15:52 +00:00
var idpResourceUID types . UID
idpWasFound := false
if canTryToFindIDP {
var err error
// Validate that each objectRef resolves to an existing IDP. It does not matter if the IDP itself
// is phase=Ready, because it will not be loaded into the cache if not ready. For each objectRef
// that does not resolve, put an error on the FederationDomain status.
idpResourceUID , idpWasFound , err = c . findIDPsUIDByObjectRef ( idp . ObjectRef , federationDomain . Namespace )
if err != nil {
return nil , nil , err
}
2023-07-11 22:42:34 +00:00
}
2023-07-12 20:15:52 +00:00
if ! canTryToFindIDP || ! idpWasFound {
2023-07-11 20:25:08 +00:00
idpNotFoundIndices = append ( idpNotFoundIndices , index )
2023-07-14 19:06:20 +00:00
idpIsValid = false
2023-05-08 21:07:38 +00:00
}
2023-07-11 20:25:08 +00:00
2023-07-13 22:26:32 +00:00
var err error
var pipeline * idtransform . TransformationPipeline
2023-07-14 19:06:20 +00:00
var allExamplesPassed bool
2023-07-18 22:00:58 +00:00
pipeline , allExamplesPassed , err = c . makeTransformationPipelineAndEvaluateExamplesForIdentityProvider (
ctx , idp , index , validationErrorMessages )
2023-07-11 22:42:34 +00:00
if err != nil {
return nil , nil , err
}
2023-07-14 19:06:20 +00:00
if ! allExamplesPassed {
idpIsValid = false
}
if ! idpIsValid {
// Something about the IDP was not valid. Don't add it.
continue
}
2023-07-11 20:25:08 +00:00
2023-07-14 19:06:20 +00:00
// For a valid IDP (unique displayName, valid objectRef, valid transforms), add it to the list.
2023-07-11 20:25:08 +00:00
federationDomainIdentityProviders = append ( federationDomainIdentityProviders , & federationdomainproviders . FederationDomainIdentityProvider {
DisplayName : idp . DisplayName ,
UID : idpResourceUID ,
Transforms : pipeline ,
} )
}
if len ( idpNotFoundIndices ) != 0 {
msgs := [ ] string { }
for _ , idpNotFoundIndex := range idpNotFoundIndices {
msgs = append ( msgs , fmt . Sprintf ( ".spec.identityProviders[%d] with displayName %q" , idpNotFoundIndex ,
federationDomain . Spec . IdentityProviders [ idpNotFoundIndex ] . DisplayName ) )
2020-10-08 02:18:34 +00:00
}
2023-07-11 20:25:08 +00:00
conditions = append ( conditions , & configv1alpha1 . Condition {
Type : typeIdentityProvidersFound ,
Status : configv1alpha1 . ConditionFalse ,
Reason : reasonIdentityProvidersObjectRefsNotFound ,
Message : fmt . Sprintf ( ".spec.identityProviders[].objectRef identifies resource(s) that cannot be found: %s" ,
strings . Join ( msgs , ", " ) ) ,
} )
} else if len ( federationDomain . Spec . IdentityProviders ) != 0 {
conditions = append ( conditions , & configv1alpha1 . Condition {
Type : typeIdentityProvidersFound ,
Status : configv1alpha1 . ConditionTrue ,
Reason : reasonSuccess ,
Message : "the resources specified by .spec.identityProviders[].objectRef were found" ,
} )
}
2020-10-08 17:27:45 +00:00
2023-07-11 20:25:08 +00:00
// This is the constructor for any case other than the legacy case, including when there is an empty list of IDPs.
federationDomainIssuer , err := federationdomainproviders . NewFederationDomainIssuer ( federationDomain . Spec . Issuer , federationDomainIdentityProviders )
conditions = appendIssuerURLValidCondition ( err , conditions )
2020-12-17 19:34:49 +00:00
2023-07-12 20:15:52 +00:00
conditions = appendIdentityProviderDuplicateDisplayNamesCondition ( duplicateDisplayNames , conditions )
conditions = appendIdentityProviderObjectRefAPIGroupSuffixCondition ( c . apiGroup , badAPIGroupNames , conditions )
conditions = appendIdentityProviderObjectRefKindCondition ( c . sortedAllowedKinds ( ) , badKinds , conditions )
2023-07-12 17:34:15 +00:00
2023-07-18 22:00:58 +00:00
conditions = appendTransformsConstantsNamesUniqueCondition ( validationErrorMessages . errorsForConstants , conditions )
conditions = appendTransformsExpressionsValidCondition ( validationErrorMessages . errorsForExpressions , conditions )
conditions = appendTransformsExamplesPassedCondition ( validationErrorMessages . errorsForExamples , conditions )
2023-07-12 17:34:15 +00:00
return federationDomainIssuer , conditions , nil
2023-07-12 16:43:33 +00:00
}
2023-07-11 20:25:08 +00:00
func ( c * federationDomainWatcherController ) findIDPsUIDByObjectRef ( objectRef corev1 . TypedLocalObjectReference , namespace string ) ( types . UID , bool , error ) {
var idpResourceUID types . UID
var foundIDP metav1 . Object
var err error
switch objectRef . Kind {
2023-07-12 20:15:52 +00:00
case kindLDAPIdentityProvider :
2023-07-11 20:25:08 +00:00
foundIDP , err = c . ldapIdentityProviderInformer . Lister ( ) . LDAPIdentityProviders ( namespace ) . Get ( objectRef . Name )
2023-07-12 20:15:52 +00:00
case kindActiveDirectoryIdentityProvider :
2023-07-11 20:25:08 +00:00
foundIDP , err = c . activeDirectoryIdentityProviderInformer . Lister ( ) . ActiveDirectoryIdentityProviders ( namespace ) . Get ( objectRef . Name )
2023-07-12 20:15:52 +00:00
case kindOIDCIdentityProvider :
2023-07-11 20:25:08 +00:00
foundIDP , err = c . oidcIdentityProviderInformer . Lister ( ) . OIDCIdentityProviders ( namespace ) . Get ( objectRef . Name )
default :
2023-07-12 20:15:52 +00:00
// This shouldn't happen because this helper function is not called when the kind is invalid.
return "" , false , fmt . Errorf ( "unexpected kind: %s" , objectRef . Kind )
2023-07-11 20:25:08 +00:00
}
switch {
case err == nil :
idpResourceUID = foundIDP . GetUID ( )
case errors . IsNotFound ( err ) :
return "" , false , nil
default :
2023-07-12 20:15:52 +00:00
return "" , false , err // unexpected error from the informer
2023-07-11 20:25:08 +00:00
}
return idpResourceUID , true , nil
}
2023-07-14 19:06:20 +00:00
func ( c * federationDomainWatcherController ) makeTransformationPipelineAndEvaluateExamplesForIdentityProvider (
ctx context . Context ,
2023-07-11 20:25:08 +00:00
idp configv1alpha1 . FederationDomainIdentityProvider ,
2023-07-14 19:06:20 +00:00
idpIndex int ,
2023-07-18 22:00:58 +00:00
validationErrorMessages * transformsValidationErrorMessages ,
) ( * idtransform . TransformationPipeline , bool , error ) {
consts , errorsForConstants , err := c . makeTransformsConstantsForIdentityProvider ( idp , idpIndex )
2023-07-14 19:06:20 +00:00
if err != nil {
2023-07-18 22:00:58 +00:00
return nil , false , err
}
if len ( errorsForConstants ) > 0 {
validationErrorMessages . errorsForConstants = append ( validationErrorMessages . errorsForConstants , errorsForConstants )
2023-07-14 19:06:20 +00:00
}
2023-07-18 22:00:58 +00:00
pipeline , errorsForExpressions , err := c . makeTransformationPipelineForIdentityProvider ( idp , idpIndex , consts )
2023-07-14 19:06:20 +00:00
if err != nil {
2023-07-18 22:00:58 +00:00
return nil , false , err
}
if len ( errorsForExpressions ) > 0 {
validationErrorMessages . errorsForExpressions = append ( validationErrorMessages . errorsForExpressions , errorsForExpressions )
2023-07-14 19:06:20 +00:00
}
2023-07-18 22:00:58 +00:00
allExamplesPassed , errorsForExamples := c . evaluateExamplesForIdentityProvider ( ctx , idp , idpIndex , pipeline )
if len ( errorsForExamples ) > 0 {
validationErrorMessages . errorsForExamples = append ( validationErrorMessages . errorsForExamples , errorsForExamples )
}
2023-07-14 19:06:20 +00:00
2023-07-18 22:00:58 +00:00
return pipeline , allExamplesPassed , nil
2023-07-14 19:06:20 +00:00
}
2023-07-18 22:00:58 +00:00
func ( c * federationDomainWatcherController ) makeTransformsConstantsForIdentityProvider (
2023-07-14 19:06:20 +00:00
idp configv1alpha1 . FederationDomainIdentityProvider ,
2023-07-18 22:00:58 +00:00
idpIndex int ,
) ( * celtransformer . TransformationConstants , string , error ) {
2023-07-11 20:25:08 +00:00
consts := & celtransformer . TransformationConstants {
StringConstants : map [ string ] string { } ,
StringListConstants : map [ string ] [ ] string { } ,
}
2023-07-13 22:26:32 +00:00
constNames := sets . Set [ string ] { }
duplicateConstNames := sets . Set [ string ] { }
2023-07-11 20:25:08 +00:00
// Read all the declared constants.
2023-07-11 22:42:34 +00:00
for _ , constant := range idp . Transforms . Constants {
2023-07-13 22:26:32 +00:00
// The CRD requires the name field, and validates that it has at least one character,
// so here we only need to validate that they are unique.
if constNames . Has ( constant . Name ) {
duplicateConstNames . Insert ( constant . Name )
}
constNames . Insert ( constant . Name )
2023-07-11 22:42:34 +00:00
switch constant . Type {
2023-07-11 20:25:08 +00:00
case "string" :
2023-07-11 22:42:34 +00:00
consts . StringConstants [ constant . Name ] = constant . StringValue
2023-07-11 20:25:08 +00:00
case "stringList" :
2023-07-11 22:42:34 +00:00
consts . StringListConstants [ constant . Name ] = constant . StringListValue
2023-07-11 20:25:08 +00:00
default :
2023-07-11 22:42:34 +00:00
// This shouldn't really happen since the CRD validates it, but handle it as an error.
2023-07-18 22:00:58 +00:00
return nil , "" , fmt . Errorf ( "one of spec.identityProvider[].transforms.constants[].type is invalid: %q" , constant . Type )
2023-06-30 20:43:40 +00:00
}
2020-10-08 17:27:45 +00:00
}
2023-07-14 19:06:20 +00:00
2023-07-18 22:00:58 +00:00
if duplicateConstNames . Len ( ) > 0 {
return consts , fmt . Sprintf (
"the names specified by .spec.identityProviders[%d].transforms.constants[].name contain duplicates: %s" ,
idpIndex , strings . Join ( sortAndQuote ( duplicateConstNames . UnsortedList ( ) ) , ", " ) ) , nil
}
2023-07-10 22:41:48 +00:00
2023-07-18 22:00:58 +00:00
return consts , "" , nil
2023-07-14 19:06:20 +00:00
}
2023-07-18 22:00:58 +00:00
func ( c * federationDomainWatcherController ) makeTransformationPipelineForIdentityProvider (
2023-07-14 19:06:20 +00:00
idp configv1alpha1 . FederationDomainIdentityProvider ,
idpIndex int ,
consts * celtransformer . TransformationConstants ,
2023-07-18 22:00:58 +00:00
) ( * idtransform . TransformationPipeline , string , error ) {
2023-07-14 19:06:20 +00:00
pipeline := idtransform . NewTransformationPipeline ( )
expressionsCompileErrors := [ ] string { }
2023-07-11 20:25:08 +00:00
// Compile all the expressions and add them to the pipeline.
2023-07-14 19:06:20 +00:00
for exprIndex , expr := range idp . Transforms . Expressions {
2023-07-11 20:25:08 +00:00
var rawTransform celtransformer . CELTransformation
2023-07-11 22:42:34 +00:00
switch expr . Type {
2023-07-11 20:25:08 +00:00
case "username/v1" :
2023-07-11 22:42:34 +00:00
rawTransform = & celtransformer . UsernameTransformation { Expression : expr . Expression }
2023-07-11 20:25:08 +00:00
case "groups/v1" :
2023-07-11 22:42:34 +00:00
rawTransform = & celtransformer . GroupsTransformation { Expression : expr . Expression }
2023-07-11 20:25:08 +00:00
case "policy/v1" :
rawTransform = & celtransformer . AllowAuthenticationPolicy {
2023-07-11 22:42:34 +00:00
Expression : expr . Expression ,
RejectedAuthenticationMessage : expr . Message ,
2023-07-11 20:25:08 +00:00
}
default :
2023-07-11 22:42:34 +00:00
// This shouldn't really happen since the CRD validates it, but handle it as an error.
2023-07-18 22:00:58 +00:00
return nil , "" , fmt . Errorf ( "one of spec.identityProvider[].transforms.expressions[].type is invalid: %q" , expr . Type )
2023-07-11 20:25:08 +00:00
}
2023-07-14 19:06:20 +00:00
2023-07-11 20:25:08 +00:00
compiledTransform , err := c . celTransformer . CompileTransformation ( rawTransform , consts )
if err != nil {
2023-07-14 19:06:20 +00:00
expressionsCompileErrors = append ( expressionsCompileErrors ,
fmt . Sprintf ( "spec.identityProvider[%d].transforms.expressions[%d].expression was invalid:\n%s" ,
idpIndex , exprIndex , err . Error ( ) ) )
2023-07-11 20:25:08 +00:00
}
2023-07-14 19:06:20 +00:00
2023-07-11 20:25:08 +00:00
pipeline . AppendTransformation ( compiledTransform )
2023-07-14 19:06:20 +00:00
}
if len ( expressionsCompileErrors ) > 0 {
// One or more of the expressions did not compile, so we don't have a useful pipeline to return.
2023-07-18 22:00:58 +00:00
// Return the validation messages.
return nil , strings . Join ( expressionsCompileErrors , "\n\n" ) , nil
2023-07-14 19:06:20 +00:00
}
2023-07-18 22:00:58 +00:00
return pipeline , "" , nil
2023-07-14 19:06:20 +00:00
}
2023-07-18 22:00:58 +00:00
func ( c * federationDomainWatcherController ) evaluateExamplesForIdentityProvider (
2023-07-14 19:06:20 +00:00
ctx context . Context ,
idp configv1alpha1 . FederationDomainIdentityProvider ,
2023-07-14 23:50:43 +00:00
idpIndex int ,
2023-07-14 19:06:20 +00:00
pipeline * idtransform . TransformationPipeline ,
2023-07-18 22:00:58 +00:00
) ( bool , string ) {
2023-07-14 23:50:43 +00:00
const errorFmt = ".spec.identityProviders[%d].transforms.examples[%d] example failed:\nexpected: %s\nactual: %s"
examplesErrors := [ ] string { }
2023-07-14 19:06:20 +00:00
if pipeline == nil {
2023-07-14 23:50:43 +00:00
// Unable to evaluate the conditions where the pipeline of expressions was invalid.
2023-07-18 22:00:58 +00:00
return false , fmt . Sprintf (
"unable to check if the examples specified by .spec.identityProviders[%d].transforms.examples[] had errors because an expression was invalid" ,
idpIndex )
2023-07-11 20:25:08 +00:00
}
2020-10-08 17:27:45 +00:00
2023-07-11 20:25:08 +00:00
// Run all the provided transform examples. If any fail, put errors on the FederationDomain status.
2023-07-14 23:50:43 +00:00
for exIndex , e := range idp . Transforms . Examples {
result , err := pipeline . Evaluate ( ctx , e . Username , e . Groups )
if err != nil {
examplesErrors = append ( examplesErrors , fmt . Sprintf ( errorFmt , idpIndex , exIndex ,
"no transformation errors" ,
fmt . Sprintf ( "transformations resulted in an unexpected error %q" , err . Error ( ) ) ) )
continue
}
2023-07-11 20:25:08 +00:00
resultWasAuthRejected := ! result . AuthenticationAllowed
2023-07-14 23:50:43 +00:00
if e . Expects . Rejected && ! resultWasAuthRejected {
examplesErrors = append ( examplesErrors ,
fmt . Sprintf ( errorFmt , idpIndex , exIndex , "authentication to be rejected" , "authentication was not rejected" ) )
continue
}
if ! e . Expects . Rejected && resultWasAuthRejected {
examplesErrors = append ( examplesErrors , fmt . Sprintf ( errorFmt , idpIndex , exIndex ,
"authentication not to be rejected" ,
fmt . Sprintf ( "authentication was rejected with message %q" , result . RejectedAuthenticationMessage ) ) )
continue
}
expectedRejectionMessage := e . Expects . Message
if len ( expectedRejectionMessage ) == 0 {
expectedRejectionMessage = celtransformer . DefaultPolicyRejectedAuthMessage
}
if e . Expects . Rejected && resultWasAuthRejected && expectedRejectionMessage != result . RejectedAuthenticationMessage {
examplesErrors = append ( examplesErrors , fmt . Sprintf ( errorFmt , idpIndex , exIndex ,
fmt . Sprintf ( "authentication rejection message %q" , expectedRejectionMessage ) ,
fmt . Sprintf ( "authentication rejection message %q" , result . RejectedAuthenticationMessage ) ) )
continue
}
if result . AuthenticationAllowed {
2023-07-11 20:25:08 +00:00
// In the case where the user expected the auth to be allowed and it was allowed, then compare
// the expected username and group names to the actual username and group names.
if e . Expects . Username != result . Username {
2023-07-14 23:50:43 +00:00
examplesErrors = append ( examplesErrors , fmt . Sprintf ( errorFmt , idpIndex , exIndex ,
fmt . Sprintf ( "username %q" , e . Expects . Username ) ,
fmt . Sprintf ( "username %q" , result . Username ) ) )
}
expectedGroups := e . Expects . Groups
if expectedGroups == nil {
expectedGroups = [ ] string { }
2023-07-11 20:25:08 +00:00
}
2023-07-14 23:50:43 +00:00
if ! stringSetsEqual ( expectedGroups , result . Groups ) {
examplesErrors = append ( examplesErrors , fmt . Sprintf ( errorFmt , idpIndex , exIndex ,
fmt . Sprintf ( "groups [%s]" , strings . Join ( sortAndQuote ( expectedGroups ) , ", " ) ) ,
fmt . Sprintf ( "groups [%s]" , strings . Join ( sortAndQuote ( result . Groups ) , ", " ) ) ) )
2023-07-11 20:25:08 +00:00
}
2023-07-11 17:57:11 +00:00
}
}
2023-07-18 22:00:58 +00:00
if len ( examplesErrors ) > 0 {
return false , strings . Join ( examplesErrors , "\n\n" )
}
2023-07-11 20:25:08 +00:00
2023-07-18 22:00:58 +00:00
return true , ""
2023-07-12 20:15:52 +00:00
}
func appendIdentityProviderObjectRefKindCondition ( expectedKinds [ ] string , badSuffixNames [ ] string , conditions [ ] * configv1alpha1 . Condition ) [ ] * configv1alpha1 . Condition {
if len ( badSuffixNames ) > 0 {
2023-07-12 17:34:15 +00:00
conditions = append ( conditions , & configv1alpha1 . Condition {
2023-07-12 20:15:52 +00:00
Type : typeIdentityProvidersObjectRefKindValid ,
Status : configv1alpha1 . ConditionFalse ,
Reason : reasonKindUnrecognized ,
Message : fmt . Sprintf ( "the kinds specified by .spec.identityProviders[].objectRef.kind are not recognized (should be one of %s): %s" ,
strings . Join ( expectedKinds , ", " ) , strings . Join ( sortAndQuote ( badSuffixNames ) , ", " ) ) ,
} )
} else {
conditions = append ( conditions , & configv1alpha1 . Condition {
Type : typeIdentityProvidersObjectRefKindValid ,
Status : configv1alpha1 . ConditionTrue ,
Reason : reasonSuccess ,
Message : "the kinds specified by .spec.identityProviders[].objectRef.kind are recognized" ,
} )
}
return conditions
}
func appendIdentityProviderObjectRefAPIGroupSuffixCondition ( expectedSuffixName string , badSuffixNames [ ] string , conditions [ ] * configv1alpha1 . Condition ) [ ] * configv1alpha1 . Condition {
if len ( badSuffixNames ) > 0 {
conditions = append ( conditions , & configv1alpha1 . Condition {
Type : typeIdentityProvidersAPIGroupSuffixValid ,
2023-07-12 17:34:15 +00:00
Status : configv1alpha1 . ConditionFalse ,
Reason : reasonAPIGroupNameUnrecognized ,
Message : fmt . Sprintf ( "the API groups specified by .spec.identityProviders[].objectRef.apiGroup are not recognized (should be %q): %s" ,
2023-07-12 20:15:52 +00:00
expectedSuffixName , strings . Join ( sortAndQuote ( badSuffixNames ) , ", " ) ) ,
2023-07-12 17:34:15 +00:00
} )
} else {
conditions = append ( conditions , & configv1alpha1 . Condition {
2023-07-12 20:15:52 +00:00
Type : typeIdentityProvidersAPIGroupSuffixValid ,
2023-07-12 17:34:15 +00:00
Status : configv1alpha1 . ConditionTrue ,
Reason : reasonSuccess ,
Message : "the API groups specified by .spec.identityProviders[].objectRef.apiGroup are recognized" ,
} )
}
return conditions
2023-07-14 19:06:20 +00:00
}
func appendTransformsExpressionsValidCondition ( errors [ ] string , conditions [ ] * configv1alpha1 . Condition ) [ ] * configv1alpha1 . Condition {
if len ( errors ) > 0 {
conditions = append ( conditions , & configv1alpha1 . Condition {
2023-07-18 22:00:58 +00:00
Type : typeTransformsExpressionsValid ,
Status : configv1alpha1 . ConditionFalse ,
Reason : reasonInvalidTransformsExpressions ,
Message : strings . Join ( errors , "\n\n" ) ,
2023-07-14 19:06:20 +00:00
} )
} else {
conditions = append ( conditions , & configv1alpha1 . Condition {
Type : typeTransformsExpressionsValid ,
Status : configv1alpha1 . ConditionTrue ,
Reason : reasonSuccess ,
Message : "the expressions specified by .spec.identityProviders[].transforms.expressions[] are valid" ,
} )
}
return conditions
2023-07-12 17:34:15 +00:00
}
2023-07-14 23:50:43 +00:00
func appendTransformsExamplesPassedCondition ( errors [ ] string , conditions [ ] * configv1alpha1 . Condition ) [ ] * configv1alpha1 . Condition {
2023-07-18 22:00:58 +00:00
if len ( errors ) > 0 {
2023-07-14 23:50:43 +00:00
conditions = append ( conditions , & configv1alpha1 . Condition {
Type : typeTransformsExamplesPassed ,
2023-07-18 22:00:58 +00:00
Status : configv1alpha1 . ConditionFalse ,
Reason : reasonTransformsExamplesFailed ,
Message : strings . Join ( errors , "\n\n" ) ,
2023-07-14 23:50:43 +00:00
} )
2023-07-18 22:00:58 +00:00
} else {
2023-07-14 23:50:43 +00:00
conditions = append ( conditions , & configv1alpha1 . Condition {
Type : typeTransformsExamplesPassed ,
Status : configv1alpha1 . ConditionTrue ,
Reason : reasonSuccess ,
Message : "the examples specified by .spec.identityProviders[].transforms.examples[] had no errors" ,
} )
}
return conditions
}
2023-07-18 22:00:58 +00:00
func appendTransformsConstantsNamesUniqueCondition ( errors [ ] string , conditions [ ] * configv1alpha1 . Condition ) [ ] * configv1alpha1 . Condition {
if len ( errors ) > 0 {
2023-07-12 17:34:15 +00:00
conditions = append ( conditions , & configv1alpha1 . Condition {
2023-07-18 22:00:58 +00:00
Type : typeTransformsConstantsNamesUnique ,
Status : configv1alpha1 . ConditionFalse ,
Reason : reasonDuplicateConstantsNames ,
Message : strings . Join ( errors , "\n\n" ) ,
2023-07-12 17:34:15 +00:00
} )
} else {
conditions = append ( conditions , & configv1alpha1 . Condition {
2023-07-18 22:00:58 +00:00
Type : typeTransformsConstantsNamesUnique ,
2023-07-12 17:34:15 +00:00
Status : configv1alpha1 . ConditionTrue ,
Reason : reasonSuccess ,
2023-07-18 22:00:58 +00:00
Message : "the names specified by .spec.identityProviders[].transforms.constants[].name are unique" ,
2023-07-12 17:34:15 +00:00
} )
}
return conditions
}
2023-07-18 22:00:58 +00:00
func appendIdentityProviderDuplicateDisplayNamesCondition ( duplicateDisplayNames sets . Set [ string ] , conditions [ ] * configv1alpha1 . Condition ) [ ] * configv1alpha1 . Condition {
if duplicateDisplayNames . Len ( ) > 0 {
2023-07-13 22:26:32 +00:00
conditions = append ( conditions , & configv1alpha1 . Condition {
2023-07-18 22:00:58 +00:00
Type : typeIdentityProvidersDisplayNamesUnique ,
2023-07-13 22:26:32 +00:00
Status : configv1alpha1 . ConditionFalse ,
2023-07-18 22:00:58 +00:00
Reason : reasonDuplicateDisplayNames ,
Message : fmt . Sprintf ( "the names specified by .spec.identityProviders[].displayName contain duplicates: %s" ,
strings . Join ( sortAndQuote ( duplicateDisplayNames . UnsortedList ( ) ) , ", " ) ) ,
2023-07-13 22:26:32 +00:00
} )
} else {
conditions = append ( conditions , & configv1alpha1 . Condition {
2023-07-18 22:00:58 +00:00
Type : typeIdentityProvidersDisplayNamesUnique ,
2023-07-13 22:26:32 +00:00
Status : configv1alpha1 . ConditionTrue ,
Reason : reasonSuccess ,
2023-07-18 22:00:58 +00:00
Message : "the names specified by .spec.identityProviders[].displayName are unique" ,
2023-07-13 22:26:32 +00:00
} )
}
return conditions
}
2023-07-11 20:25:08 +00:00
func appendIssuerURLValidCondition ( err error , conditions [ ] * configv1alpha1 . Condition ) [ ] * configv1alpha1 . Condition {
if err != nil {
// Note that the FederationDomainIssuer constructors only validate the Issuer URL,
// so these are always issuer URL validation errors.
conditions = append ( conditions , & configv1alpha1 . Condition {
Type : typeIssuerURLValid ,
Status : configv1alpha1 . ConditionFalse ,
Reason : reasonInvalidIssuerURL ,
Message : err . Error ( ) ,
} )
} else {
conditions = append ( conditions , & configv1alpha1 . Condition {
Type : typeIssuerURLValid ,
Status : configv1alpha1 . ConditionTrue ,
Reason : reasonSuccess ,
Message : "spec.issuer is a valid URL" ,
} )
}
return conditions
2020-10-08 17:27:45 +00:00
}
2023-06-30 20:43:40 +00:00
func ( c * federationDomainWatcherController ) updateStatus (
ctx context . Context ,
federationDomain * configv1alpha1 . FederationDomain ,
conditions [ ] * configv1alpha1 . Condition ,
) error {
updated := federationDomain . DeepCopy ( )
if hadErrorCondition ( conditions ) {
updated . Status . Phase = configv1alpha1 . FederationDomainPhaseError
conditions = append ( conditions , & configv1alpha1 . Condition {
Type : typeReady ,
Status : configv1alpha1 . ConditionFalse ,
Reason : reasonNotReady ,
Message : "the FederationDomain is not ready: see other conditions for details" ,
} )
} else {
updated . Status . Phase = configv1alpha1 . FederationDomainPhaseReady
conditions = append ( conditions , & configv1alpha1 . Condition {
Type : typeReady ,
Status : configv1alpha1 . ConditionTrue ,
Reason : reasonSuccess ,
Message : fmt . Sprintf ( "the FederationDomain is ready and its endpoints are available: " +
"the discovery endpoint is %s/.well-known/openid-configuration" , federationDomain . Spec . Issuer ) ,
} )
2023-05-08 21:07:38 +00:00
}
2023-06-30 20:43:40 +00:00
_ = conditionsutil . MergeConfigConditions ( conditions ,
federationDomain . Generation , & updated . Status . Conditions , plog . New ( ) , metav1 . NewTime ( c . clock . Now ( ) ) )
if equality . Semantic . DeepEqual ( federationDomain , updated ) {
return nil
2023-05-08 21:07:38 +00:00
}
2023-06-30 20:43:40 +00:00
_ , err := c . client .
ConfigV1alpha1 ( ) .
FederationDomains ( federationDomain . Namespace ) .
UpdateStatus ( ctx , updated , metav1 . UpdateOptions { } )
return err
2023-05-08 21:07:38 +00:00
}
2023-07-18 22:00:58 +00:00
func sortAndQuote ( strs [ ] string ) [ ] string {
quoted := make ( [ ] string , 0 , len ( strs ) )
for _ , s := range strs {
quoted = append ( quoted , fmt . Sprintf ( "%q" , s ) )
}
sort . Strings ( quoted )
return quoted
}
func ( c * federationDomainWatcherController ) sortedAllowedKinds ( ) [ ] string {
return sortAndQuote ( c . allowedKinds . UnsortedList ( ) )
}
type transformsValidationErrorMessages struct {
errorsForConstants [ ] string
errorsForExpressions [ ] string
errorsForExamples [ ] string
}
2023-06-30 20:43:40 +00:00
type crossFederationDomainConfigValidator struct {
issuerCounts map [ string ] int
uniqueSecretNamesPerIssuerAddress map [ string ] map [ string ] bool
}
func issuerURLToHostnameKey ( issuerURL * url . URL ) string {
return lowercaseHostWithoutPort ( issuerURL )
}
func issuerURLToIssuerKey ( issuerURL * url . URL ) string {
return fmt . Sprintf ( "%s://%s%s" , issuerURL . Scheme , strings . ToLower ( issuerURL . Host ) , issuerURL . Path )
}
func ( v * crossFederationDomainConfigValidator ) Validate ( federationDomain * configv1alpha1 . FederationDomain , conditions [ ] * configv1alpha1 . Condition ) [ ] * configv1alpha1 . Condition {
issuerURL , urlParseErr := url . Parse ( federationDomain . Spec . Issuer )
if urlParseErr != nil {
// Don't write a condition about the issuer URL being invalid because that is added elsewhere in the controller.
conditions = append ( conditions , & configv1alpha1 . Condition {
Type : typeIssuerIsUnique ,
Status : configv1alpha1 . ConditionUnknown ,
Reason : reasonUnableToValidate ,
Message : "unable to check if spec.issuer is unique among all FederationDomains because URL cannot be parsed" ,
} )
conditions = append ( conditions , & configv1alpha1 . Condition {
Type : typeOneTLSSecretPerIssuerHostname ,
Status : configv1alpha1 . ConditionUnknown ,
Reason : reasonUnableToValidate ,
Message : "unable to check if all FederationDomains are using the same TLS secret when using the same hostname in the spec.issuer URL because URL cannot be parsed" ,
} )
return conditions
}
if issuerCount := v . issuerCounts [ issuerURLToIssuerKey ( issuerURL ) ] ; issuerCount > 1 {
conditions = append ( conditions , & configv1alpha1 . Condition {
Type : typeIssuerIsUnique ,
Status : configv1alpha1 . ConditionFalse ,
Reason : reasonDuplicateIssuer ,
Message : "multiple FederationDomains have the same spec.issuer URL: these URLs must be unique (can use different hosts or paths)" ,
} )
} else {
conditions = append ( conditions , & configv1alpha1 . Condition {
Type : typeIssuerIsUnique ,
Status : configv1alpha1 . ConditionTrue ,
Reason : reasonSuccess ,
Message : "spec.issuer is unique among all FederationDomains" ,
} )
}
if len ( v . uniqueSecretNamesPerIssuerAddress [ issuerURLToHostnameKey ( issuerURL ) ] ) > 1 {
conditions = append ( conditions , & configv1alpha1 . Condition {
Type : typeOneTLSSecretPerIssuerHostname ,
Status : configv1alpha1 . ConditionFalse ,
Reason : reasonDifferentSecretRefsFound ,
Message : "when different FederationDomains are using the same hostname in the spec.issuer URL then they must also use the same TLS secretRef: different secretRefs found" ,
} )
} else {
conditions = append ( conditions , & configv1alpha1 . Condition {
Type : typeOneTLSSecretPerIssuerHostname ,
Status : configv1alpha1 . ConditionTrue ,
Reason : reasonSuccess ,
Message : "all FederationDomains are using the same TLS secret when using the same hostname in the spec.issuer URL" ,
} )
}
return conditions
}
func newCrossFederationDomainConfigValidator ( federationDomains [ ] * configv1alpha1 . FederationDomain ) * crossFederationDomainConfigValidator {
// Make a map of issuer strings -> count of how many times we saw that issuer string.
// This will help us complain when there are duplicate issuer strings.
// Also make a helper function for forming keys into this map.
issuerCounts := make ( map [ string ] int )
// Make a map of issuer hostnames -> set of unique secret names. This will help us complain when
// multiple FederationDomains have the same issuer hostname (excluding port) but specify
// different TLS serving Secrets. Doesn't make sense to have the one address use more than one
// TLS cert. Ignore ports because SNI information on the incoming requests is not going to include
// port numbers. Also make a helper function for forming keys into this map.
uniqueSecretNamesPerIssuerAddress := make ( map [ string ] map [ string ] bool )
for _ , federationDomain := range federationDomains {
issuerURL , err := url . Parse ( federationDomain . Spec . Issuer )
2020-10-08 17:27:45 +00:00
if err != nil {
2023-06-30 20:43:40 +00:00
continue // Skip url parse errors because they will be handled in the Validate function.
2020-10-08 17:27:45 +00:00
}
2023-06-30 20:43:40 +00:00
issuerCounts [ issuerURLToIssuerKey ( issuerURL ) ] ++
setOfSecretNames := uniqueSecretNamesPerIssuerAddress [ issuerURLToHostnameKey ( issuerURL ) ]
if setOfSecretNames == nil {
setOfSecretNames = make ( map [ string ] bool )
uniqueSecretNamesPerIssuerAddress [ issuerURLToHostnameKey ( issuerURL ) ] = setOfSecretNames
}
if federationDomain . Spec . TLS != nil {
setOfSecretNames [ federationDomain . Spec . TLS . SecretName ] = true
2020-10-08 17:27:45 +00:00
}
2023-06-30 20:43:40 +00:00
}
2020-10-08 17:27:45 +00:00
2023-06-30 20:43:40 +00:00
return & crossFederationDomainConfigValidator {
issuerCounts : issuerCounts ,
uniqueSecretNamesPerIssuerAddress : uniqueSecretNamesPerIssuerAddress ,
}
2020-10-08 17:27:45 +00:00
}
2020-10-09 15:54:50 +00:00
2023-06-30 20:43:40 +00:00
func hadErrorCondition ( conditions [ ] * configv1alpha1 . Condition ) bool {
for _ , c := range conditions {
if c . Status != configv1alpha1 . ConditionTrue {
return true
}
}
return false
}
2023-07-14 23:50:43 +00:00
func stringSetsEqual ( a [ ] string , b [ ] string ) bool {
aSet := sets . New ( a ... )
bSet := sets . New ( b ... )
return aSet . Equal ( bSet )
2023-06-30 20:43:40 +00:00
}