234 lines
12 KiB
Go
234 lines
12 KiB
Go
// Copyright 2023 the Pinniped contributors. All Rights Reserved.
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package federationdomainproviders
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"k8s.io/apimachinery/pkg/types"
|
|
"k8s.io/apimachinery/pkg/util/sets"
|
|
|
|
"go.pinniped.dev/internal/federationdomain/idplister"
|
|
"go.pinniped.dev/internal/federationdomain/resolvedprovider"
|
|
"go.pinniped.dev/internal/idtransform"
|
|
"go.pinniped.dev/internal/psession"
|
|
)
|
|
|
|
// FederationDomainIdentityProvider represents an identity provider as configured in a FederationDomain's spec.
|
|
// All the fields are required and must be non-zero values. Note that this might be a reference to an IDP
|
|
// which is not currently loaded into the cache of available IDPs, e.g. due to the IDP's CR having validation errors.
|
|
type FederationDomainIdentityProvider struct {
|
|
DisplayName string
|
|
UID types.UID
|
|
Transforms *idtransform.TransformationPipeline
|
|
}
|
|
|
|
type FederationDomainIdentityProvidersFinderI interface {
|
|
FindDefaultIDP() (
|
|
*resolvedprovider.FederationDomainResolvedOIDCIdentityProvider,
|
|
*resolvedprovider.FederationDomainResolvedLDAPIdentityProvider,
|
|
error,
|
|
)
|
|
|
|
FindUpstreamIDPByDisplayName(upstreamIDPDisplayName string) (
|
|
*resolvedprovider.FederationDomainResolvedOIDCIdentityProvider,
|
|
*resolvedprovider.FederationDomainResolvedLDAPIdentityProvider,
|
|
error,
|
|
)
|
|
|
|
HasDefaultIDP() bool
|
|
IDPCount() int
|
|
}
|
|
|
|
type FederationDomainIdentityProvidersListerI interface {
|
|
GetOIDCIdentityProviders() []*resolvedprovider.FederationDomainResolvedOIDCIdentityProvider
|
|
GetLDAPIdentityProviders() []*resolvedprovider.FederationDomainResolvedLDAPIdentityProvider
|
|
GetActiveDirectoryIdentityProviders() []*resolvedprovider.FederationDomainResolvedLDAPIdentityProvider
|
|
}
|
|
|
|
type FederationDomainIdentityProvidersListerFinderI interface {
|
|
FederationDomainIdentityProvidersListerI
|
|
FederationDomainIdentityProvidersFinderI
|
|
}
|
|
|
|
// FederationDomainIdentityProvidersListerFinder implements FederationDomainIdentityProvidersListerFinderI.
|
|
var _ FederationDomainIdentityProvidersListerFinderI = (*FederationDomainIdentityProvidersListerFinder)(nil)
|
|
|
|
// FederationDomainIdentityProvidersListerFinder wraps an UpstreamIdentityProvidersLister. The lister which is being
|
|
// wrapped should contain all valid upstream providers that are currently defined in the Supervisor.
|
|
// FederationDomainIdentityProvidersListerFinder provides a lookup method which only looks up IDPs within those which
|
|
// have allowed resource IDs, and also uses display names (name aliases) instead of the actual resource names to do the
|
|
// lookups. It also provides list methods which only list the allowed identity providers (to be used by the IDP
|
|
// discovery endpoint, for example).
|
|
type FederationDomainIdentityProvidersListerFinder struct {
|
|
wrappedLister idplister.UpstreamIdentityProvidersLister
|
|
configuredIdentityProviders []*FederationDomainIdentityProvider
|
|
defaultIdentityProvider *FederationDomainIdentityProvider
|
|
idpDisplayNamesToResourceUIDsMap map[string]types.UID
|
|
allowedIDPResourceUIDs sets.Set[types.UID]
|
|
}
|
|
|
|
// NewFederationDomainIdentityProvidersListerFinder returns a new FederationDomainIdentityProvidersListerFinder
|
|
// which only lists those IDPs allowed by its parameter. Every FederationDomainIdentityProvider in the
|
|
// federationDomainIssuer parameter's IdentityProviders() list must have a unique DisplayName.
|
|
// Note that a single underlying IDP UID may be used by multiple FederationDomainIdentityProvider in the parameter.
|
|
// The wrapped lister should contain all valid upstream providers that are defined in the Supervisor, and is expected to
|
|
// be thread-safe and to change its contents over time. (Note that it should not contain any invalid or unready identity
|
|
// providers because the controllers that fill this cache should not put invalid or unready providers into the cache.)
|
|
// The FederationDomainIdentityProvidersListerFinder will filter out the ones that don't apply to this federation
|
|
// domain.
|
|
func NewFederationDomainIdentityProvidersListerFinder(
|
|
federationDomainIssuer *FederationDomainIssuer,
|
|
wrappedLister idplister.UpstreamIdentityProvidersLister,
|
|
) *FederationDomainIdentityProvidersListerFinder {
|
|
// Create a copy of the input slice so we won't need to worry about the caller accidentally changing it.
|
|
copyOfFederationDomainIdentityProviders := []*FederationDomainIdentityProvider{}
|
|
// Create a map and a set for quick lookups of the same data that was passed in via the
|
|
// federationDomainIssuer parameter.
|
|
allowedResourceUIDs := sets.New[types.UID]()
|
|
idpDisplayNamesToResourceUIDsMap := map[string]types.UID{}
|
|
for _, idp := range federationDomainIssuer.IdentityProviders() {
|
|
allowedResourceUIDs.Insert(idp.UID)
|
|
idpDisplayNamesToResourceUIDsMap[idp.DisplayName] = idp.UID
|
|
shallowCopyOfIDP := *idp
|
|
copyOfFederationDomainIdentityProviders = append(copyOfFederationDomainIdentityProviders, &shallowCopyOfIDP)
|
|
}
|
|
|
|
return &FederationDomainIdentityProvidersListerFinder{
|
|
wrappedLister: wrappedLister,
|
|
configuredIdentityProviders: copyOfFederationDomainIdentityProviders,
|
|
defaultIdentityProvider: federationDomainIssuer.DefaultIdentityProvider(),
|
|
idpDisplayNamesToResourceUIDsMap: idpDisplayNamesToResourceUIDsMap,
|
|
allowedIDPResourceUIDs: allowedResourceUIDs,
|
|
}
|
|
}
|
|
|
|
func (u *FederationDomainIdentityProvidersListerFinder) IDPCount() int {
|
|
return len(u.GetOIDCIdentityProviders()) + len(u.GetLDAPIdentityProviders()) + len(u.GetActiveDirectoryIdentityProviders())
|
|
}
|
|
|
|
// FindUpstreamIDPByDisplayName selects either an OIDC, LDAP, or ActiveDirectory IDP, or returns an error.
|
|
// It only considers the allowed IDPs while doing the lookup by display name.
|
|
// Note that ActiveDirectory and LDAP IDPs both return the same type, but with different SessionProviderType values.
|
|
func (u *FederationDomainIdentityProvidersListerFinder) FindUpstreamIDPByDisplayName(upstreamIDPDisplayName string) (
|
|
*resolvedprovider.FederationDomainResolvedOIDCIdentityProvider,
|
|
*resolvedprovider.FederationDomainResolvedLDAPIdentityProvider,
|
|
error,
|
|
) {
|
|
// Given a display name, look up the identity provider's UID for that display name.
|
|
idpUIDForDisplayName, ok := u.idpDisplayNamesToResourceUIDsMap[upstreamIDPDisplayName]
|
|
if !ok {
|
|
return nil, nil, fmt.Errorf("identity provider not found: %q", upstreamIDPDisplayName)
|
|
}
|
|
// Find the IDP with that UID. It could be any type, so look at all types to find it.
|
|
for _, p := range u.GetOIDCIdentityProviders() {
|
|
if p.Provider.GetResourceUID() == idpUIDForDisplayName {
|
|
return p, nil, nil
|
|
}
|
|
}
|
|
for _, p := range u.GetLDAPIdentityProviders() {
|
|
if p.Provider.GetResourceUID() == idpUIDForDisplayName {
|
|
return nil, p, nil
|
|
}
|
|
}
|
|
for _, p := range u.GetActiveDirectoryIdentityProviders() {
|
|
if p.Provider.GetResourceUID() == idpUIDForDisplayName {
|
|
return nil, p, nil
|
|
}
|
|
}
|
|
return nil, nil, fmt.Errorf("identity provider not available: %q", upstreamIDPDisplayName)
|
|
}
|
|
|
|
func (u *FederationDomainIdentityProvidersListerFinder) HasDefaultIDP() bool {
|
|
return u.defaultIdentityProvider != nil
|
|
}
|
|
|
|
// FindDefaultIDP works like FindUpstreamIDPByDisplayName, but finds the default IDP instead of finding by name.
|
|
// If there is no default IDP for this federation domain, then FindDefaultIDP will return an error.
|
|
// This can be used to handle the backwards compatibility mode where an authorization request could be made
|
|
// without specifying an IDP name, and there are no IDPs explicitly specified on the FederationDomain, and there
|
|
// is exactly one IDP CR defined in the Supervisor namespace.
|
|
func (u *FederationDomainIdentityProvidersListerFinder) FindDefaultIDP() (
|
|
*resolvedprovider.FederationDomainResolvedOIDCIdentityProvider,
|
|
*resolvedprovider.FederationDomainResolvedLDAPIdentityProvider,
|
|
error,
|
|
) {
|
|
if !u.HasDefaultIDP() {
|
|
return nil, nil, fmt.Errorf("identity provider not found: this federation domain does not have a default identity provider")
|
|
}
|
|
return u.FindUpstreamIDPByDisplayName(u.defaultIdentityProvider.DisplayName)
|
|
}
|
|
|
|
// GetOIDCIdentityProviders lists only the OIDC providers for this FederationDomain.
|
|
func (u *FederationDomainIdentityProvidersListerFinder) GetOIDCIdentityProviders() []*resolvedprovider.FederationDomainResolvedOIDCIdentityProvider {
|
|
// Get the cached providers once at the start in case they change during the rest of this function.
|
|
cachedProviders := u.wrappedLister.GetOIDCIdentityProviders()
|
|
providers := []*resolvedprovider.FederationDomainResolvedOIDCIdentityProvider{}
|
|
// Every configured identityProvider on the FederationDomain uses an objetRef to an underlying IDP CR that might
|
|
// be available as a provider in the wrapped cache. For each configured identityProvider/displayName...
|
|
for _, idp := range u.configuredIdentityProviders {
|
|
// Check if the IDP used by that displayName is in the cached available OIDC providers.
|
|
for _, p := range cachedProviders {
|
|
if idp.UID == p.GetResourceUID() {
|
|
// Found it, so append it to the result.
|
|
providers = append(providers, &resolvedprovider.FederationDomainResolvedOIDCIdentityProvider{
|
|
DisplayName: idp.DisplayName,
|
|
Provider: p,
|
|
SessionProviderType: psession.ProviderTypeOIDC,
|
|
Transforms: idp.Transforms,
|
|
})
|
|
}
|
|
}
|
|
}
|
|
return providers
|
|
}
|
|
|
|
// GetLDAPIdentityProviders lists only the LDAP providers for this FederationDomain.
|
|
func (u *FederationDomainIdentityProvidersListerFinder) GetLDAPIdentityProviders() []*resolvedprovider.FederationDomainResolvedLDAPIdentityProvider {
|
|
// Get the cached providers once at the start in case they change during the rest of this function.
|
|
cachedProviders := u.wrappedLister.GetLDAPIdentityProviders()
|
|
providers := []*resolvedprovider.FederationDomainResolvedLDAPIdentityProvider{}
|
|
// Every configured identityProvider on the FederationDomain uses an objetRef to an underlying IDP CR that might
|
|
// be available as a provider in the wrapped cache. For each configured identityProvider/displayName...
|
|
for _, idp := range u.configuredIdentityProviders {
|
|
// Check if the IDP used by that displayName is in the cached available LDAP providers.
|
|
for _, p := range cachedProviders {
|
|
if idp.UID == p.GetResourceUID() {
|
|
// Found it, so append it to the result.
|
|
providers = append(providers, &resolvedprovider.FederationDomainResolvedLDAPIdentityProvider{
|
|
DisplayName: idp.DisplayName,
|
|
Provider: p,
|
|
SessionProviderType: psession.ProviderTypeLDAP,
|
|
Transforms: idp.Transforms,
|
|
})
|
|
}
|
|
}
|
|
}
|
|
return providers
|
|
}
|
|
|
|
// GetActiveDirectoryIdentityProviders lists only the ActiveDirectory providers for this FederationDomain.
|
|
func (u *FederationDomainIdentityProvidersListerFinder) GetActiveDirectoryIdentityProviders() []*resolvedprovider.FederationDomainResolvedLDAPIdentityProvider {
|
|
// Get the cached providers once at the start in case they change during the rest of this function.
|
|
cachedProviders := u.wrappedLister.GetActiveDirectoryIdentityProviders()
|
|
providers := []*resolvedprovider.FederationDomainResolvedLDAPIdentityProvider{}
|
|
// Every configured identityProvider on the FederationDomain uses an objetRef to an underlying IDP CR that might
|
|
// be available as a provider in the wrapped cache. For each configured identityProvider/displayName...
|
|
for _, idp := range u.configuredIdentityProviders {
|
|
// Check if the IDP used by that displayName is in the cached available ActiveDirectory providers.
|
|
for _, p := range cachedProviders {
|
|
if idp.UID == p.GetResourceUID() {
|
|
// Found it, so append it to the result.
|
|
providers = append(providers, &resolvedprovider.FederationDomainResolvedLDAPIdentityProvider{
|
|
DisplayName: idp.DisplayName,
|
|
Provider: p,
|
|
SessionProviderType: psession.ProviderTypeActiveDirectory,
|
|
Transforms: idp.Transforms,
|
|
})
|
|
}
|
|
}
|
|
}
|
|
return providers
|
|
}
|