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-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-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-13 22:26:32 +00:00
conditions = appendTransformsConstantsNamesUniqueCondition ( sets . Set [ string ] { } , conditions )
2023-07-14 19:06:20 +00:00
conditions = appendTransformsExpressionsValidCondition ( [ ] string { } , conditions )
2023-07-12 17:34:15 +00:00
2023-07-11 20:25:08 +00:00
return federationDomainIssuer , conditions , nil
}
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-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
pipeline , allExamplesPassed , conditions , err = c . makeTransformationPipelineAndEvaluateExamplesForIdentityProvider ( ctx , idp , index , conditions )
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
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-13 22:26:32 +00:00
conditions [ ] * configv1alpha1 . Condition ,
2023-07-14 19:06:20 +00:00
) ( * idtransform . TransformationPipeline , bool , [ ] * configv1alpha1 . Condition , error ) {
consts , conditions , err := c . makeTransformsConstants ( idp , conditions )
if err != nil {
return nil , false , nil , err
}
pipeline , conditions , err := c . makeTransformationPipeline ( idp , idpIndex , consts , conditions )
if err != nil {
return nil , false , nil , err
}
allExamplesPassed , conditions := c . evaluateExamples ( ctx , idp , pipeline , conditions )
return pipeline , allExamplesPassed , conditions , nil
}
func ( c * federationDomainWatcherController ) makeTransformsConstants (
idp configv1alpha1 . FederationDomainIdentityProvider ,
conditions [ ] * configv1alpha1 . Condition ,
) ( * celtransformer . TransformationConstants , [ ] * configv1alpha1 . Condition , 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-13 22:26:32 +00:00
return nil , 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-13 22:26:32 +00:00
conditions = appendTransformsConstantsNamesUniqueCondition ( duplicateConstNames , conditions )
2023-07-10 22:41:48 +00:00
2023-07-14 19:06:20 +00:00
return consts , conditions , nil
}
func ( c * federationDomainWatcherController ) makeTransformationPipeline (
idp configv1alpha1 . FederationDomainIdentityProvider ,
idpIndex int ,
consts * celtransformer . TransformationConstants ,
conditions [ ] * configv1alpha1 . Condition ,
) ( * idtransform . TransformationPipeline , [ ] * configv1alpha1 . Condition , error ) {
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-13 22:26:32 +00:00
return nil , 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
}
conditions = appendTransformsExpressionsValidCondition ( expressionsCompileErrors , conditions )
if len ( expressionsCompileErrors ) > 0 {
// One or more of the expressions did not compile, so we don't have a useful pipeline to return.
return nil , conditions , nil
}
return pipeline , conditions , nil
}
func ( c * federationDomainWatcherController ) evaluateExamples (
ctx context . Context ,
idp configv1alpha1 . FederationDomainIdentityProvider ,
pipeline * idtransform . TransformationPipeline ,
conditions [ ] * configv1alpha1 . Condition ,
) ( bool , [ ] * configv1alpha1 . Condition ) {
if pipeline == nil {
// TODO cannot evaluate examples, but still need to write a condition for it
return false , conditions
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 19:06:20 +00:00
examplesErrors := [ ] string { }
for examplesIndex , e := range idp . Transforms . Examples {
result , _ := pipeline . Evaluate ( ctx , e . Username , e . Groups )
2023-07-11 20:25:08 +00:00
// TODO: handle err
resultWasAuthRejected := ! result . AuthenticationAllowed
if e . Expects . Rejected && ! resultWasAuthRejected { //nolint:gocritic,nestif
// TODO: handle this failed example
2023-07-14 19:06:20 +00:00
examplesErrors = append ( examplesErrors , "TODO" )
2023-07-11 20:25:08 +00:00
plog . Warning ( "FederationDomain identity provider transformations example failed: expected authentication to be rejected but it was not" ,
"idpDisplayName" , idp . DisplayName ,
2023-07-14 19:06:20 +00:00
"exampleIndex" , examplesIndex ,
2023-07-11 20:25:08 +00:00
"expectedRejected" , e . Expects . Rejected ,
"actualRejectedResult" , resultWasAuthRejected ,
"expectedMessage" , e . Expects . Message ,
"actualMessageResult" , result . RejectedAuthenticationMessage ,
)
} else if ! e . Expects . Rejected && resultWasAuthRejected {
// TODO: handle this failed example
2023-07-14 19:06:20 +00:00
examplesErrors = append ( examplesErrors , "TODO" )
2023-07-11 20:25:08 +00:00
plog . Warning ( "FederationDomain identity provider transformations example failed: expected authentication not to be rejected but it was rejected" ,
"idpDisplayName" , idp . DisplayName ,
2023-07-14 19:06:20 +00:00
"exampleIndex" , examplesIndex ,
2023-07-11 20:25:08 +00:00
"expectedRejected" , e . Expects . Rejected ,
"actualRejectedResult" , resultWasAuthRejected ,
"expectedMessage" , e . Expects . Message ,
"actualMessageResult" , result . RejectedAuthenticationMessage ,
)
} else if e . Expects . Rejected && resultWasAuthRejected && e . Expects . Message != result . RejectedAuthenticationMessage {
// TODO: when expected message is blank, then treat it like it expects the default message
// TODO: handle this failed example
2023-07-14 19:06:20 +00:00
examplesErrors = append ( examplesErrors , "TODO" )
2023-07-11 20:25:08 +00:00
plog . Warning ( "FederationDomain identity provider transformations example failed: expected a different authentication rejection message" ,
"idpDisplayName" , idp . DisplayName ,
2023-07-14 19:06:20 +00:00
"exampleIndex" , examplesIndex ,
2023-07-11 20:25:08 +00:00
"expectedRejected" , e . Expects . Rejected ,
"actualRejectedResult" , resultWasAuthRejected ,
"expectedMessage" , e . Expects . Message ,
"actualMessageResult" , result . RejectedAuthenticationMessage ,
)
} else if result . AuthenticationAllowed {
// 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.
// TODO: when both of these fail, put both errors onto the status (not just the first one)
if e . Expects . Username != result . Username {
// TODO: handle this failed example
2023-07-14 19:06:20 +00:00
examplesErrors = append ( examplesErrors , "TODO" )
2023-07-11 20:25:08 +00:00
plog . Warning ( "FederationDomain identity provider transformations example failed: expected a different transformed username" ,
"idpDisplayName" , idp . DisplayName ,
2023-07-14 19:06:20 +00:00
"exampleIndex" , examplesIndex ,
2023-07-11 20:25:08 +00:00
"expectedUsername" , e . Expects . Username ,
"actualUsernameResult" , result . Username ,
)
}
if ! stringSlicesEqual ( e . Expects . Groups , result . Groups ) {
// TODO: Do we need to make this insensitive to ordering, or should the transformations evaluator be changed to always return sorted group names at the end of the pipeline?
// TODO: What happens if the user did not write any group expectation? Treat it like expecting an empty list of groups?
// TODO: handle this failed example
2023-07-14 19:06:20 +00:00
examplesErrors = append ( examplesErrors , "TODO" )
2023-07-11 20:25:08 +00:00
plog . Warning ( "FederationDomain identity provider transformations example failed: expected a different transformed groups list" ,
"idpDisplayName" , idp . DisplayName ,
2023-07-14 19:06:20 +00:00
"exampleIndex" , examplesIndex ,
2023-07-11 20:25:08 +00:00
"expectedGroups" , e . Expects . Groups ,
"actualGroupsResult" , result . Groups ,
)
}
2023-07-11 17:57:11 +00:00
}
}
2023-07-14 19:06:20 +00:00
return len ( examplesErrors ) == 0 , conditions
2023-07-11 20:25:08 +00:00
}
2023-07-12 20:15:52 +00:00
func ( c * federationDomainWatcherController ) sortedAllowedKinds ( ) [ ] string {
return sortAndQuote ( c . allowedKinds . UnsortedList ( ) )
}
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 {
Type : typeTransformsExpressionsValid ,
Status : configv1alpha1 . ConditionFalse ,
Reason : reasonInvalidTransformsExpressions ,
Message : fmt . Sprintf ( "the expressions specified by .spec.identityProviders[].transforms.expressions[] were invalid:\n\n%s" ,
strings . Join ( errors , "\n\n" ) ) ,
} )
} 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-12 20:15:52 +00:00
func appendIdentityProviderDuplicateDisplayNamesCondition ( duplicateDisplayNames sets . Set [ string ] , conditions [ ] * configv1alpha1 . Condition ) [ ] * configv1alpha1 . Condition {
2023-07-12 17:34:15 +00:00
if duplicateDisplayNames . Len ( ) > 0 {
conditions = append ( conditions , & configv1alpha1 . Condition {
2023-07-12 20:15:52 +00:00
Type : typeIdentityProvidersDisplayNamesUnique ,
2023-07-12 17:34:15 +00:00
Status : configv1alpha1 . ConditionFalse ,
Reason : reasonDuplicateDisplayNames ,
Message : fmt . Sprintf ( "the names specified by .spec.identityProviders[].displayName contain duplicates: %s" ,
2023-07-12 20:15:52 +00:00
strings . Join ( sortAndQuote ( duplicateDisplayNames . UnsortedList ( ) ) , ", " ) ) ,
2023-07-12 17:34:15 +00:00
} )
} else {
conditions = append ( conditions , & configv1alpha1 . Condition {
2023-07-12 20:15:52 +00:00
Type : typeIdentityProvidersDisplayNamesUnique ,
2023-07-12 17:34:15 +00:00
Status : configv1alpha1 . ConditionTrue ,
Reason : reasonSuccess ,
Message : "the names specified by .spec.identityProviders[].displayName are unique" ,
} )
}
return conditions
}
2023-07-13 22:26:32 +00:00
func appendTransformsConstantsNamesUniqueCondition ( duplicateConstNames sets . Set [ string ] , conditions [ ] * configv1alpha1 . Condition ) [ ] * configv1alpha1 . Condition {
if duplicateConstNames . Len ( ) > 0 {
conditions = append ( conditions , & configv1alpha1 . Condition {
Type : typeTransformsConstantsNamesUnique ,
Status : configv1alpha1 . ConditionFalse ,
Reason : reasonDuplicateConstantsNames ,
Message : fmt . Sprintf ( "the names specified by .spec.identityProviders[].transforms.constants[].name contain duplicates: %s" ,
strings . Join ( sortAndQuote ( duplicateConstNames . UnsortedList ( ) ) , ", " ) ) ,
} )
} else {
conditions = append ( conditions , & configv1alpha1 . Condition {
Type : typeTransformsConstantsNamesUnique ,
Status : configv1alpha1 . ConditionTrue ,
Reason : reasonSuccess ,
Message : "the names specified by .spec.identityProviders[].transforms.constants[].name are unique" ,
} )
}
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-07-12 20:15:52 +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
}
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-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
}
func stringSlicesEqual ( a [ ] string , b [ ] string ) bool {
if len ( a ) != len ( b ) {
return false
}
for i , itemFromA := range a {
if b [ i ] != itemFromA {
return false
}
}
return true
}