// Copyright 2020-2023 the Pinniped contributors. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 package supervisorconfig import ( "context" "errors" "fmt" "net/url" "reflect" "sync" "testing" "time" "github.com/sclevine/spec" "github.com/sclevine/spec/report" "github.com/stretchr/testify/require" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" coretesting "k8s.io/client-go/testing" clocktesting "k8s.io/utils/clock/testing" "go.pinniped.dev/generated/latest/apis/supervisor/config/v1alpha1" pinnipedfake "go.pinniped.dev/generated/latest/client/supervisor/clientset/versioned/fake" pinnipedinformers "go.pinniped.dev/generated/latest/client/supervisor/informers/externalversions" "go.pinniped.dev/internal/controllerlib" "go.pinniped.dev/internal/federationdomain/federationdomainproviders" "go.pinniped.dev/internal/testutil" ) func TestInformerFilters(t *testing.T) { spec.Run(t, "informer filters", func(t *testing.T, when spec.G, it spec.S) { var r *require.Assertions var observableWithInformerOption *testutil.ObservableWithInformerOption var configMapInformerFilter controllerlib.Filter it.Before(func() { r = require.New(t) observableWithInformerOption = testutil.NewObservableWithInformerOption() federationDomainInformer := pinnipedinformers.NewSharedInformerFactoryWithOptions(nil, 0).Config().V1alpha1().FederationDomains() oidcIdentityProviderInformer := pinnipedinformers.NewSharedInformerFactoryWithOptions(nil, 0).IDP().V1alpha1().OIDCIdentityProviders() ldapIdentityProviderInformer := pinnipedinformers.NewSharedInformerFactoryWithOptions(nil, 0).IDP().V1alpha1().LDAPIdentityProviders() adIdentityProviderInformer := pinnipedinformers.NewSharedInformerFactoryWithOptions(nil, 0).IDP().V1alpha1().ActiveDirectoryIdentityProviders() _ = NewFederationDomainWatcherController( nil, nil, nil, federationDomainInformer, oidcIdentityProviderInformer, ldapIdentityProviderInformer, adIdentityProviderInformer, observableWithInformerOption.WithInformer, // make it possible to observe the behavior of the Filters ) configMapInformerFilter = observableWithInformerOption.GetFilterForInformer(federationDomainInformer) }) when("watching FederationDomain objects", func() { var subject controllerlib.Filter var target, otherNamespace, otherName *v1alpha1.FederationDomain it.Before(func() { subject = configMapInformerFilter target = &v1alpha1.FederationDomain{ObjectMeta: metav1.ObjectMeta{Name: "some-name", Namespace: "some-namespace"}} otherNamespace = &v1alpha1.FederationDomain{ObjectMeta: metav1.ObjectMeta{Name: "some-name", Namespace: "other-namespace"}} otherName = &v1alpha1.FederationDomain{ObjectMeta: metav1.ObjectMeta{Name: "other-name", Namespace: "some-namespace"}} }) when("any FederationDomain changes", func() { it("returns true to trigger the sync method", func() { r.True(subject.Add(target)) r.True(subject.Add(otherName)) r.True(subject.Add(otherNamespace)) r.True(subject.Update(target, otherName)) r.True(subject.Update(otherName, otherName)) r.True(subject.Update(otherNamespace, otherName)) r.True(subject.Update(otherName, target)) r.True(subject.Update(otherName, otherName)) r.True(subject.Update(otherName, otherNamespace)) r.True(subject.Delete(target)) r.True(subject.Delete(otherName)) r.True(subject.Delete(otherNamespace)) }) }) }) }, spec.Parallel(), spec.Report(report.Terminal{})) } type fakeFederationDomainsSetter struct { SetFederationDomainsWasCalled bool FederationDomainsReceived []*federationdomainproviders.FederationDomainIssuer } func (f *fakeFederationDomainsSetter) SetFederationDomains(federationDomains ...*federationdomainproviders.FederationDomainIssuer) { f.SetFederationDomainsWasCalled = true f.FederationDomainsReceived = federationDomains } func TestSync(t *testing.T) { spec.Run(t, "Sync", func(t *testing.T, when spec.G, it spec.S) { const namespace = "some-namespace" var r *require.Assertions var subject controllerlib.Controller var pinnipedInformerClient *pinnipedfake.Clientset var pinnipedInformers pinnipedinformers.SharedInformerFactory var pinnipedAPIClient *pinnipedfake.Clientset var cancelContext context.Context var cancelContextCancelFunc context.CancelFunc var syncContext *controllerlib.Context var frozenNow time.Time var frozenMetav1Now metav1.Time var federationDomainsSetter *fakeFederationDomainsSetter var federationDomainGVR schema.GroupVersionResource var allHappyConditions func(issuer string, time metav1.Time, observedGeneration int64) []v1alpha1.Condition var happyReadyCondition func(issuer string, time metav1.Time, observedGeneration int64) v1alpha1.Condition var happyIssuerIsUniqueCondition, unknownIssuerIsUniqueCondition, sadIssuerIsUniqueCondition, happyOneTLSSecretPerIssuerHostnameCondition, unknownOneTLSSecretPerIssuerHostnameCondition, sadOneTLSSecretPerIssuerHostnameCondition, happyIssuerURLValidCondition, sadIssuerURLValidConditionCannotHaveQuery, sadIssuerURLValidConditionCannotParse, sadReadyCondition func(time metav1.Time, observedGeneration int64) v1alpha1.Condition // Defer starting the informers until the last possible moment so that the // nested Before's can keep adding things to the informer caches. var startInformersAndController = func() { // Set this at the last second to allow for injection of server override. subject = NewFederationDomainWatcherController( federationDomainsSetter, clocktesting.NewFakeClock(frozenNow), pinnipedAPIClient, pinnipedInformers.Config().V1alpha1().FederationDomains(), pinnipedInformers.IDP().V1alpha1().OIDCIdentityProviders(), pinnipedInformers.IDP().V1alpha1().LDAPIdentityProviders(), pinnipedInformers.IDP().V1alpha1().ActiveDirectoryIdentityProviders(), controllerlib.WithInformer, ) // Set this at the last second to support calling subject.Name(). syncContext = &controllerlib.Context{ Context: cancelContext, Name: subject.Name(), Key: controllerlib.Key{ Namespace: namespace, Name: "config-name", }, } // Must start informers before calling TestRunSynchronously() pinnipedInformers.Start(cancelContext.Done()) controllerlib.TestRunSynchronously(t, subject) } it.Before(func() { r = require.New(t) federationDomainsSetter = &fakeFederationDomainsSetter{} frozenNow = time.Date(2020, time.September, 23, 7, 42, 0, 0, time.Local) cancelContext, cancelContextCancelFunc = context.WithCancel(context.Background()) pinnipedInformerClient = pinnipedfake.NewSimpleClientset() pinnipedInformers = pinnipedinformers.NewSharedInformerFactory(pinnipedInformerClient, 0) pinnipedAPIClient = pinnipedfake.NewSimpleClientset() federationDomainGVR = schema.GroupVersionResource{ Group: v1alpha1.SchemeGroupVersion.Group, Version: v1alpha1.SchemeGroupVersion.Version, Resource: "federationdomains", } frozenMetav1Now = metav1.NewTime(frozenNow) happyReadyCondition = func(issuer string, time metav1.Time, observedGeneration int64) v1alpha1.Condition { return v1alpha1.Condition{ Type: "Ready", Status: "True", ObservedGeneration: observedGeneration, LastTransitionTime: time, Reason: "Success", Message: fmt.Sprintf("the FederationDomain is ready and its endpoints are available: "+ "the discovery endpoint is %s/.well-known/openid-configuration", issuer), } } sadReadyCondition = func(time metav1.Time, observedGeneration int64) v1alpha1.Condition { return v1alpha1.Condition{ Type: "Ready", Status: "False", ObservedGeneration: observedGeneration, LastTransitionTime: time, Reason: "NotReady", Message: "the FederationDomain is not ready: see other conditions for details", } } happyIssuerIsUniqueCondition = func(time metav1.Time, observedGeneration int64) v1alpha1.Condition { return v1alpha1.Condition{ Type: "IssuerIsUnique", Status: "True", ObservedGeneration: observedGeneration, LastTransitionTime: time, Reason: "Success", Message: "spec.issuer is unique among all FederationDomains", } } unknownIssuerIsUniqueCondition = func(time metav1.Time, observedGeneration int64) v1alpha1.Condition { return v1alpha1.Condition{ Type: "IssuerIsUnique", Status: "Unknown", ObservedGeneration: observedGeneration, LastTransitionTime: time, Reason: "UnableToValidate", Message: "unable to check if spec.issuer is unique among all FederationDomains because URL cannot be parsed", } } sadIssuerIsUniqueCondition = func(time metav1.Time, observedGeneration int64) v1alpha1.Condition { return v1alpha1.Condition{ Type: "IssuerIsUnique", Status: "False", ObservedGeneration: observedGeneration, LastTransitionTime: time, Reason: "DuplicateIssuer", Message: "multiple FederationDomains have the same spec.issuer URL: these URLs must be unique (can use different hosts or paths)", } } happyOneTLSSecretPerIssuerHostnameCondition = func(time metav1.Time, observedGeneration int64) v1alpha1.Condition { return v1alpha1.Condition{ Type: "OneTLSSecretPerIssuerHostname", Status: "True", ObservedGeneration: observedGeneration, LastTransitionTime: time, Reason: "Success", Message: "all FederationDomains are using the same TLS secret when using the same hostname in the spec.issuer URL", } } unknownOneTLSSecretPerIssuerHostnameCondition = func(time metav1.Time, observedGeneration int64) v1alpha1.Condition { return v1alpha1.Condition{ Type: "OneTLSSecretPerIssuerHostname", Status: "Unknown", ObservedGeneration: observedGeneration, LastTransitionTime: time, Reason: "UnableToValidate", 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", } } sadOneTLSSecretPerIssuerHostnameCondition = func(time metav1.Time, observedGeneration int64) v1alpha1.Condition { return v1alpha1.Condition{ Type: "OneTLSSecretPerIssuerHostname", Status: "False", ObservedGeneration: observedGeneration, LastTransitionTime: time, Reason: "DifferentSecretRefsFound", 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", } } happyIssuerURLValidCondition = func(time metav1.Time, observedGeneration int64) v1alpha1.Condition { return v1alpha1.Condition{ Type: "IssuerURLValid", Status: "True", ObservedGeneration: observedGeneration, LastTransitionTime: time, Reason: "Success", Message: "spec.issuer is a valid URL", } } sadIssuerURLValidConditionCannotHaveQuery = func(time metav1.Time, observedGeneration int64) v1alpha1.Condition { return v1alpha1.Condition{ Type: "IssuerURLValid", Status: "False", ObservedGeneration: observedGeneration, LastTransitionTime: time, Reason: "InvalidIssuerURL", Message: "issuer must not have query", } } sadIssuerURLValidConditionCannotParse = func(time metav1.Time, observedGeneration int64) v1alpha1.Condition { return v1alpha1.Condition{ Type: "IssuerURLValid", Status: "False", ObservedGeneration: observedGeneration, LastTransitionTime: time, Reason: "InvalidIssuerURL", Message: `could not parse issuer as URL: parse ":/host//path": missing protocol scheme`, } } allHappyConditions = func(issuer string, time metav1.Time, observedGeneration int64) []v1alpha1.Condition { return []v1alpha1.Condition{ happyIssuerIsUniqueCondition(time, observedGeneration), happyIssuerURLValidCondition(time, observedGeneration), happyOneTLSSecretPerIssuerHostnameCondition(time, observedGeneration), happyReadyCondition(issuer, time, observedGeneration), } } }) it.After(func() { cancelContextCancelFunc() }) when("there are some valid FederationDomains in the informer", func() { var ( federationDomain1 *v1alpha1.FederationDomain federationDomain2 *v1alpha1.FederationDomain ) it.Before(func() { federationDomain1 = &v1alpha1.FederationDomain{ ObjectMeta: metav1.ObjectMeta{Name: "config1", Namespace: namespace, Generation: 123}, Spec: v1alpha1.FederationDomainSpec{Issuer: "https://issuer1.com"}, } r.NoError(pinnipedAPIClient.Tracker().Add(federationDomain1)) r.NoError(pinnipedInformerClient.Tracker().Add(federationDomain1)) federationDomain2 = &v1alpha1.FederationDomain{ ObjectMeta: metav1.ObjectMeta{Name: "config2", Namespace: namespace, Generation: 123}, Spec: v1alpha1.FederationDomainSpec{Issuer: "https://issuer2.com"}, } r.NoError(pinnipedAPIClient.Tracker().Add(federationDomain2)) r.NoError(pinnipedInformerClient.Tracker().Add(federationDomain2)) }) it("calls the FederationDomainsSetter", func() { startInformersAndController() err := controllerlib.TestSync(t, subject, *syncContext) r.NoError(err) fd1, err := federationdomainproviders.NewFederationDomainIssuer(federationDomain1.Spec.Issuer, []*federationdomainproviders.FederationDomainIdentityProvider{}) r.NoError(err) fd2, err := federationdomainproviders.NewFederationDomainIssuer(federationDomain2.Spec.Issuer, []*federationdomainproviders.FederationDomainIdentityProvider{}) r.NoError(err) r.True(federationDomainsSetter.SetFederationDomainsWasCalled) r.ElementsMatch( []*federationdomainproviders.FederationDomainIssuer{ fd1, fd2, }, federationDomainsSetter.FederationDomainsReceived, ) }) it("updates the status to ready in the FederationDomains", func() { startInformersAndController() err := controllerlib.TestSync(t, subject, *syncContext) r.NoError(err) federationDomain1.Status.Phase = v1alpha1.FederationDomainPhaseReady federationDomain1.Status.Conditions = allHappyConditions(federationDomain1.Spec.Issuer, frozenMetav1Now, 123) federationDomain2.Status.Phase = v1alpha1.FederationDomainPhaseReady federationDomain2.Status.Conditions = allHappyConditions(federationDomain2.Spec.Issuer, frozenMetav1Now, 123) expectedActions := []coretesting.Action{ coretesting.NewUpdateSubresourceAction( federationDomainGVR, "status", federationDomain1.Namespace, federationDomain1, ), coretesting.NewUpdateSubresourceAction( federationDomainGVR, "status", federationDomain2.Namespace, federationDomain2, ), } r.ElementsMatch(expectedActions, pinnipedAPIClient.Actions()) }) when("one FederationDomain is already up to date", func() { it.Before(func() { federationDomain1.Status.Phase = v1alpha1.FederationDomainPhaseReady federationDomain1.Status.Conditions = allHappyConditions(federationDomain1.Spec.Issuer, frozenMetav1Now, 123) r.NoError(pinnipedAPIClient.Tracker().Update(federationDomainGVR, federationDomain1, federationDomain1.Namespace)) r.NoError(pinnipedInformerClient.Tracker().Update(federationDomainGVR, federationDomain1, federationDomain1.Namespace)) }) it("only updates the out-of-date FederationDomain", func() { startInformersAndController() err := controllerlib.TestSync(t, subject, *syncContext) r.NoError(err) federationDomain2.Status.Phase = v1alpha1.FederationDomainPhaseReady federationDomain2.Status.Conditions = allHappyConditions(federationDomain2.Spec.Issuer, frozenMetav1Now, 123) expectedActions := []coretesting.Action{ coretesting.NewUpdateSubresourceAction( federationDomainGVR, "status", federationDomain2.Namespace, federationDomain2, ), } r.ElementsMatch(expectedActions, pinnipedAPIClient.Actions()) }) it("calls the FederationDomainsSetter with both FederationDomain's", func() { startInformersAndController() err := controllerlib.TestSync(t, subject, *syncContext) r.NoError(err) fd1, err := federationdomainproviders.NewFederationDomainIssuer(federationDomain1.Spec.Issuer, []*federationdomainproviders.FederationDomainIdentityProvider{}) r.NoError(err) fd2, err := federationdomainproviders.NewFederationDomainIssuer(federationDomain2.Spec.Issuer, []*federationdomainproviders.FederationDomainIdentityProvider{}) r.NoError(err) r.True(federationDomainsSetter.SetFederationDomainsWasCalled) r.ElementsMatch( []*federationdomainproviders.FederationDomainIssuer{ fd1, fd2, }, federationDomainsSetter.FederationDomainsReceived, ) }) }) when("updating only one FederationDomain fails", func() { it.Before(func() { once := sync.Once{} pinnipedAPIClient.PrependReactor( "update", "federationdomains", func(_ coretesting.Action) (bool, runtime.Object, error) { var err error once.Do(func() { err = errors.New("some update error") }) return true, nil, err }, ) }) it("sets the FederationDomain that it could actually update in the API", func() { startInformersAndController() err := controllerlib.TestSync(t, subject, *syncContext) r.EqualError(err, "could not update status: some update error") fd1, err := federationdomainproviders.NewFederationDomainIssuer(federationDomain1.Spec.Issuer, []*federationdomainproviders.FederationDomainIdentityProvider{}) r.NoError(err) fd2, err := federationdomainproviders.NewFederationDomainIssuer(federationDomain2.Spec.Issuer, []*federationdomainproviders.FederationDomainIdentityProvider{}) r.NoError(err) r.True(federationDomainsSetter.SetFederationDomainsWasCalled) r.Len(federationDomainsSetter.FederationDomainsReceived, 1) r.True( reflect.DeepEqual(federationDomainsSetter.FederationDomainsReceived[0], fd1) || reflect.DeepEqual(federationDomainsSetter.FederationDomainsReceived[0], fd2), ) }) it("returns an error", func() { startInformersAndController() err := controllerlib.TestSync(t, subject, *syncContext) r.EqualError(err, "could not update status: some update error") federationDomain1.Status.Phase = v1alpha1.FederationDomainPhaseReady federationDomain1.Status.Conditions = allHappyConditions(federationDomain1.Spec.Issuer, frozenMetav1Now, 123) federationDomain2.Status.Phase = v1alpha1.FederationDomainPhaseReady federationDomain2.Status.Conditions = allHappyConditions(federationDomain2.Spec.Issuer, frozenMetav1Now, 123) expectedActions := []coretesting.Action{ coretesting.NewUpdateSubresourceAction( federationDomainGVR, "status", federationDomain1.Namespace, federationDomain1, ), coretesting.NewUpdateSubresourceAction( federationDomainGVR, "status", federationDomain2.Namespace, federationDomain2, ), } r.ElementsMatch(expectedActions, pinnipedAPIClient.Actions()) }) }) }) when("there are errors updating the FederationDomains", func() { var ( federationDomain *v1alpha1.FederationDomain ) it.Before(func() { federationDomain = &v1alpha1.FederationDomain{ ObjectMeta: metav1.ObjectMeta{Name: "config", Namespace: namespace, Generation: 123}, Spec: v1alpha1.FederationDomainSpec{Issuer: "https://issuer.com"}, } r.NoError(pinnipedAPIClient.Tracker().Add(federationDomain)) r.NoError(pinnipedInformerClient.Tracker().Add(federationDomain)) }) when("updating the FederationDomain fails", func() { it.Before(func() { pinnipedAPIClient.PrependReactor( "update", "federationdomains", func(_ coretesting.Action) (bool, runtime.Object, error) { return true, nil, errors.New("some update error") }, ) }) it("returns an error", func() { startInformersAndController() err := controllerlib.TestSync(t, subject, *syncContext) r.EqualError(err, "could not update status: some update error") federationDomain.Status.Phase = v1alpha1.FederationDomainPhaseReady federationDomain.Status.Conditions = allHappyConditions(federationDomain.Spec.Issuer, frozenMetav1Now, 123) expectedActions := []coretesting.Action{ coretesting.NewUpdateSubresourceAction( federationDomainGVR, "status", federationDomain.Namespace, federationDomain, ), } r.ElementsMatch(expectedActions, pinnipedAPIClient.Actions()) }) }) }) when("there are both valid and invalid FederationDomains in the informer", func() { var ( validFederationDomain *v1alpha1.FederationDomain invalidFederationDomain *v1alpha1.FederationDomain ) it.Before(func() { validFederationDomain = &v1alpha1.FederationDomain{ ObjectMeta: metav1.ObjectMeta{Name: "valid-config", Namespace: namespace, Generation: 123}, Spec: v1alpha1.FederationDomainSpec{Issuer: "https://valid-issuer.com"}, } r.NoError(pinnipedAPIClient.Tracker().Add(validFederationDomain)) r.NoError(pinnipedInformerClient.Tracker().Add(validFederationDomain)) invalidFederationDomain = &v1alpha1.FederationDomain{ ObjectMeta: metav1.ObjectMeta{Name: "invalid-config", Namespace: namespace, Generation: 123}, Spec: v1alpha1.FederationDomainSpec{Issuer: "https://invalid-issuer.com?some=query"}, } r.NoError(pinnipedAPIClient.Tracker().Add(invalidFederationDomain)) r.NoError(pinnipedInformerClient.Tracker().Add(invalidFederationDomain)) }) it("calls the FederationDomainsSetter with the valid FederationDomain", func() { startInformersAndController() err := controllerlib.TestSync(t, subject, *syncContext) r.NoError(err) validFederationDomain, err := federationdomainproviders.NewFederationDomainIssuer(validFederationDomain.Spec.Issuer, []*federationdomainproviders.FederationDomainIdentityProvider{}) r.NoError(err) r.True(federationDomainsSetter.SetFederationDomainsWasCalled) r.Equal( []*federationdomainproviders.FederationDomainIssuer{ validFederationDomain, }, federationDomainsSetter.FederationDomainsReceived, ) }) it("updates the status in each FederationDomain", func() { startInformersAndController() err := controllerlib.TestSync(t, subject, *syncContext) r.NoError(err) validFederationDomain.Status.Phase = v1alpha1.FederationDomainPhaseReady validFederationDomain.Status.Conditions = allHappyConditions(validFederationDomain.Spec.Issuer, frozenMetav1Now, 123) invalidFederationDomain.Status.Phase = v1alpha1.FederationDomainPhaseError invalidFederationDomain.Status.Conditions = []v1alpha1.Condition{ happyIssuerIsUniqueCondition(frozenMetav1Now, 123), sadIssuerURLValidConditionCannotHaveQuery(frozenMetav1Now, 123), happyOneTLSSecretPerIssuerHostnameCondition(frozenMetav1Now, 123), sadReadyCondition(frozenMetav1Now, 123), } expectedActions := []coretesting.Action{ coretesting.NewUpdateSubresourceAction( federationDomainGVR, "status", invalidFederationDomain.Namespace, invalidFederationDomain, ), coretesting.NewUpdateSubresourceAction( federationDomainGVR, "status", validFederationDomain.Namespace, validFederationDomain, ), } r.ElementsMatch(expectedActions, pinnipedAPIClient.Actions()) }) when("updating only the invalid FederationDomain fails", func() { it.Before(func() { pinnipedAPIClient.PrependReactor( "update", "federationdomains", func(action coretesting.Action) (bool, runtime.Object, error) { updateAction := action.(coretesting.UpdateActionImpl) federationDomain := updateAction.Object.(*v1alpha1.FederationDomain) if federationDomain.Name == validFederationDomain.Name { return true, nil, nil } return true, nil, errors.New("some update error") }, ) }) it("sets the FederationDomain that it could actually update in the API", func() { startInformersAndController() err := controllerlib.TestSync(t, subject, *syncContext) r.EqualError(err, "could not update status: some update error") validFederationDomain, err := federationdomainproviders.NewFederationDomainIssuer(validFederationDomain.Spec.Issuer, []*federationdomainproviders.FederationDomainIdentityProvider{}) r.NoError(err) r.True(federationDomainsSetter.SetFederationDomainsWasCalled) r.Equal( []*federationdomainproviders.FederationDomainIssuer{ validFederationDomain, }, federationDomainsSetter.FederationDomainsReceived, ) }) it("returns an error", func() { startInformersAndController() err := controllerlib.TestSync(t, subject, *syncContext) r.EqualError(err, "could not update status: some update error") validFederationDomain.Status.Phase = v1alpha1.FederationDomainPhaseReady validFederationDomain.Status.Conditions = allHappyConditions(validFederationDomain.Spec.Issuer, frozenMetav1Now, 123) invalidFederationDomain.Status.Phase = v1alpha1.FederationDomainPhaseError invalidFederationDomain.Status.Conditions = []v1alpha1.Condition{ happyIssuerIsUniqueCondition(frozenMetav1Now, 123), sadIssuerURLValidConditionCannotHaveQuery(frozenMetav1Now, 123), happyOneTLSSecretPerIssuerHostnameCondition(frozenMetav1Now, 123), sadReadyCondition(frozenMetav1Now, 123), } expectedActions := []coretesting.Action{ coretesting.NewUpdateSubresourceAction( federationDomainGVR, "status", invalidFederationDomain.Namespace, invalidFederationDomain, ), coretesting.NewUpdateSubresourceAction( federationDomainGVR, "status", validFederationDomain.Namespace, validFederationDomain, ), } r.ElementsMatch(expectedActions, pinnipedAPIClient.Actions()) }) }) }) when("there are FederationDomains with duplicate issuer names in the informer", func() { var ( federationDomainDuplicate1 *v1alpha1.FederationDomain federationDomainDuplicate2 *v1alpha1.FederationDomain federationDomain *v1alpha1.FederationDomain ) it.Before(func() { // Hostnames are case-insensitive, so consider them to be duplicates if they only differ by case. // Paths are case-sensitive, so having a path that differs only by case makes a new issuer. federationDomainDuplicate1 = &v1alpha1.FederationDomain{ ObjectMeta: metav1.ObjectMeta{Name: "duplicate1", Namespace: namespace, Generation: 123}, Spec: v1alpha1.FederationDomainSpec{Issuer: "https://iSSueR-duPlicAte.cOm/a"}, } r.NoError(pinnipedAPIClient.Tracker().Add(federationDomainDuplicate1)) r.NoError(pinnipedInformerClient.Tracker().Add(federationDomainDuplicate1)) federationDomainDuplicate2 = &v1alpha1.FederationDomain{ ObjectMeta: metav1.ObjectMeta{Name: "duplicate2", Namespace: namespace, Generation: 123}, Spec: v1alpha1.FederationDomainSpec{Issuer: "https://issuer-duplicate.com/a"}, } r.NoError(pinnipedAPIClient.Tracker().Add(federationDomainDuplicate2)) r.NoError(pinnipedInformerClient.Tracker().Add(federationDomainDuplicate2)) federationDomain = &v1alpha1.FederationDomain{ ObjectMeta: metav1.ObjectMeta{Name: "not-duplicate", Namespace: namespace, Generation: 123}, Spec: v1alpha1.FederationDomainSpec{Issuer: "https://issuer-duplicate.com/A"}, // different path } r.NoError(pinnipedAPIClient.Tracker().Add(federationDomain)) r.NoError(pinnipedInformerClient.Tracker().Add(federationDomain)) }) it("calls the FederationDomainsSetter with the non-duplicate", func() { startInformersAndController() err := controllerlib.TestSync(t, subject, *syncContext) r.NoError(err) nonDuplicateFederationDomain, err := federationdomainproviders.NewFederationDomainIssuer(federationDomain.Spec.Issuer, []*federationdomainproviders.FederationDomainIdentityProvider{}) r.NoError(err) r.True(federationDomainsSetter.SetFederationDomainsWasCalled) r.Equal( []*federationdomainproviders.FederationDomainIssuer{ nonDuplicateFederationDomain, }, federationDomainsSetter.FederationDomainsReceived, ) }) it("updates the statuses", func() { startInformersAndController() err := controllerlib.TestSync(t, subject, *syncContext) r.NoError(err) federationDomain.Status.Phase = v1alpha1.FederationDomainPhaseReady federationDomain.Status.Conditions = allHappyConditions(federationDomain.Spec.Issuer, frozenMetav1Now, 123) federationDomainDuplicate1.Status.Phase = v1alpha1.FederationDomainPhaseError federationDomainDuplicate1.Status.Conditions = []v1alpha1.Condition{ sadIssuerIsUniqueCondition(frozenMetav1Now, 123), happyIssuerURLValidCondition(frozenMetav1Now, 123), happyOneTLSSecretPerIssuerHostnameCondition(frozenMetav1Now, 123), sadReadyCondition(frozenMetav1Now, 123), } federationDomainDuplicate2.Status.Phase = v1alpha1.FederationDomainPhaseError federationDomainDuplicate2.Status.Conditions = []v1alpha1.Condition{ sadIssuerIsUniqueCondition(frozenMetav1Now, 123), happyIssuerURLValidCondition(frozenMetav1Now, 123), happyOneTLSSecretPerIssuerHostnameCondition(frozenMetav1Now, 123), sadReadyCondition(frozenMetav1Now, 123), } expectedActions := []coretesting.Action{ coretesting.NewUpdateSubresourceAction( federationDomainGVR, "status", federationDomainDuplicate1.Namespace, federationDomainDuplicate1, ), coretesting.NewUpdateSubresourceAction( federationDomainGVR, "status", federationDomainDuplicate2.Namespace, federationDomainDuplicate2, ), coretesting.NewUpdateSubresourceAction( federationDomainGVR, "status", federationDomain.Namespace, federationDomain, ), } r.ElementsMatch(expectedActions, pinnipedAPIClient.Actions()) }) }) when("there are FederationDomains with the same issuer DNS hostname using different secretNames", func() { var ( federationDomainSameIssuerAddress1 *v1alpha1.FederationDomain federationDomainSameIssuerAddress2 *v1alpha1.FederationDomain federationDomainDifferentIssuerAddress *v1alpha1.FederationDomain federationDomainWithInvalidIssuerURL *v1alpha1.FederationDomain ) it.Before(func() { federationDomainSameIssuerAddress1 = &v1alpha1.FederationDomain{ ObjectMeta: metav1.ObjectMeta{Name: "fd1", Namespace: namespace, Generation: 123}, Spec: v1alpha1.FederationDomainSpec{ Issuer: "https://iSSueR-duPlicAte-adDress.cOm/path1", TLS: &v1alpha1.FederationDomainTLSSpec{SecretName: "secret1"}, }, } r.NoError(pinnipedAPIClient.Tracker().Add(federationDomainSameIssuerAddress1)) r.NoError(pinnipedInformerClient.Tracker().Add(federationDomainSameIssuerAddress1)) federationDomainSameIssuerAddress2 = &v1alpha1.FederationDomain{ ObjectMeta: metav1.ObjectMeta{Name: "fd2", Namespace: namespace, Generation: 123}, Spec: v1alpha1.FederationDomainSpec{ // Validation treats these as the same DNS hostname even though they have different port numbers, // because SNI information on the incoming requests is not going to include port numbers. Issuer: "https://issuer-duplicate-address.com:1234/path2", TLS: &v1alpha1.FederationDomainTLSSpec{SecretName: "secret2"}, }, } r.NoError(pinnipedAPIClient.Tracker().Add(federationDomainSameIssuerAddress2)) r.NoError(pinnipedInformerClient.Tracker().Add(federationDomainSameIssuerAddress2)) federationDomainDifferentIssuerAddress = &v1alpha1.FederationDomain{ ObjectMeta: metav1.ObjectMeta{Name: "differentIssuerAddressFederationDomain", Namespace: namespace, Generation: 123}, Spec: v1alpha1.FederationDomainSpec{ Issuer: "https://issuer-not-duplicate.com", TLS: &v1alpha1.FederationDomainTLSSpec{SecretName: "secret1"}, }, } r.NoError(pinnipedAPIClient.Tracker().Add(federationDomainDifferentIssuerAddress)) r.NoError(pinnipedInformerClient.Tracker().Add(federationDomainDifferentIssuerAddress)) // Also add one with a URL that cannot be parsed to make sure that the error handling // for the duplicate issuers and secret names are not confused by invalid URLs. invalidIssuerURL := ":/host//path" _, err := url.Parse(invalidIssuerURL) //nolint:staticcheck // Yes, this URL is intentionally invalid. r.Error(err) federationDomainWithInvalidIssuerURL = &v1alpha1.FederationDomain{ ObjectMeta: metav1.ObjectMeta{Name: "invalidIssuerURLFederationDomain", Namespace: namespace, Generation: 123}, Spec: v1alpha1.FederationDomainSpec{ Issuer: invalidIssuerURL, TLS: &v1alpha1.FederationDomainTLSSpec{SecretName: "secret1"}, }, } r.NoError(pinnipedAPIClient.Tracker().Add(federationDomainWithInvalidIssuerURL)) r.NoError(pinnipedInformerClient.Tracker().Add(federationDomainWithInvalidIssuerURL)) }) it("calls the FederationDomainsSetter with the non-duplicate", func() { startInformersAndController() err := controllerlib.TestSync(t, subject, *syncContext) r.NoError(err) nonDuplicateFederationDomain, err := federationdomainproviders.NewFederationDomainIssuer(federationDomainDifferentIssuerAddress.Spec.Issuer, []*federationdomainproviders.FederationDomainIdentityProvider{}) r.NoError(err) r.True(federationDomainsSetter.SetFederationDomainsWasCalled) r.Equal( []*federationdomainproviders.FederationDomainIssuer{ nonDuplicateFederationDomain, }, federationDomainsSetter.FederationDomainsReceived, ) }) it("updates the statuses", func() { startInformersAndController() err := controllerlib.TestSync(t, subject, *syncContext) r.NoError(err) federationDomainDifferentIssuerAddress.Status.Phase = v1alpha1.FederationDomainPhaseReady federationDomainDifferentIssuerAddress.Status.Conditions = allHappyConditions(federationDomainDifferentIssuerAddress.Spec.Issuer, frozenMetav1Now, 123) federationDomainSameIssuerAddress1.Status.Phase = v1alpha1.FederationDomainPhaseError federationDomainSameIssuerAddress1.Status.Conditions = []v1alpha1.Condition{ happyIssuerIsUniqueCondition(frozenMetav1Now, 123), happyIssuerURLValidCondition(frozenMetav1Now, 123), sadOneTLSSecretPerIssuerHostnameCondition(frozenMetav1Now, 123), sadReadyCondition(frozenMetav1Now, 123), } federationDomainSameIssuerAddress2.Status.Phase = v1alpha1.FederationDomainPhaseError federationDomainSameIssuerAddress2.Status.Conditions = []v1alpha1.Condition{ happyIssuerIsUniqueCondition(frozenMetav1Now, 123), happyIssuerURLValidCondition(frozenMetav1Now, 123), sadOneTLSSecretPerIssuerHostnameCondition(frozenMetav1Now, 123), sadReadyCondition(frozenMetav1Now, 123), } federationDomainWithInvalidIssuerURL.Status.Phase = v1alpha1.FederationDomainPhaseError federationDomainWithInvalidIssuerURL.Status.Conditions = []v1alpha1.Condition{ unknownIssuerIsUniqueCondition(frozenMetav1Now, 123), sadIssuerURLValidConditionCannotParse(frozenMetav1Now, 123), unknownOneTLSSecretPerIssuerHostnameCondition(frozenMetav1Now, 123), sadReadyCondition(frozenMetav1Now, 123), } expectedActions := []coretesting.Action{ coretesting.NewUpdateSubresourceAction( federationDomainGVR, "status", federationDomainDifferentIssuerAddress.Namespace, federationDomainDifferentIssuerAddress, ), coretesting.NewUpdateSubresourceAction( federationDomainGVR, "status", federationDomainSameIssuerAddress1.Namespace, federationDomainSameIssuerAddress1, ), coretesting.NewUpdateSubresourceAction( federationDomainGVR, "status", federationDomainSameIssuerAddress2.Namespace, federationDomainSameIssuerAddress2, ), coretesting.NewUpdateSubresourceAction( federationDomainGVR, "status", federationDomainWithInvalidIssuerURL.Namespace, federationDomainWithInvalidIssuerURL, ), } r.ElementsMatch(expectedActions, pinnipedAPIClient.Actions()) }) }) when("there are no FederationDomains in the informer", func() { it("keeps waiting for one", func() { startInformersAndController() err := controllerlib.TestSync(t, subject, *syncContext) r.NoError(err) r.Empty(pinnipedAPIClient.Actions()) r.True(federationDomainsSetter.SetFederationDomainsWasCalled) r.Empty(federationDomainsSetter.FederationDomainsReceived) }) }) }, spec.Parallel(), spec.Report(report.Terminal{})) }