// Copyright 2020 the Pinniped contributors. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 package integration import ( "context" "encoding/base64" "testing" "time" "github.com/stretchr/testify/require" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "go.pinniped.dev/generated/1.19/apis/supervisor/idp/v1alpha1" "go.pinniped.dev/test/library" ) func TestSupervisorUpstreamOIDCDiscovery(t *testing.T) { env := library.IntegrationEnv(t) t.Run("invalid missing secret and bad issuer", func(t *testing.T) { t.Parallel() spec := v1alpha1.UpstreamOIDCProviderSpec{ Issuer: "https://127.0.0.1:444444/issuer", Client: v1alpha1.OIDCClient{ SecretName: "does-not-exist", }, } upstream := makeTestUpstream(t, spec, v1alpha1.PhaseError) expectUpstreamConditions(t, upstream, []v1alpha1.Condition{ { Type: "ClientCredentialsValid", Status: v1alpha1.ConditionFalse, Reason: "SecretNotFound", Message: `secret "does-not-exist" not found`, }, { Type: "OIDCDiscoverySucceeded", Status: v1alpha1.ConditionFalse, Reason: "Unreachable", Message: `failed to perform OIDC discovery against "https://127.0.0.1:444444/issuer"`, }, }) }) t.Run("valid", func(t *testing.T) { t.Parallel() spec := v1alpha1.UpstreamOIDCProviderSpec{ Issuer: env.SupervisorTestUpstream.Issuer, TLS: &v1alpha1.TLSSpec{ CertificateAuthorityData: base64.StdEncoding.EncodeToString([]byte(env.SupervisorTestUpstream.CABundle)), }, AuthorizationConfig: v1alpha1.OIDCAuthorizationConfig{ AdditionalScopes: []string{"email", "profile"}, }, Client: v1alpha1.OIDCClient{ SecretName: makeTestClientCredsSecret(t, "test-client-id", "test-client-secret").Name, }, } upstream := makeTestUpstream(t, spec, v1alpha1.PhaseReady) expectUpstreamConditions(t, upstream, []v1alpha1.Condition{ { Type: "ClientCredentialsValid", Status: v1alpha1.ConditionTrue, Reason: "Success", Message: "loaded client credentials", }, { Type: "OIDCDiscoverySucceeded", Status: v1alpha1.ConditionTrue, Reason: "Success", Message: "discovered issuer configuration", }, }) }) } func expectUpstreamConditions(t *testing.T, upstream *v1alpha1.UpstreamOIDCProvider, expected []v1alpha1.Condition) { t.Helper() normalized := make([]v1alpha1.Condition, 0, len(upstream.Status.Conditions)) for _, c := range upstream.Status.Conditions { c.ObservedGeneration = 0 c.LastTransitionTime = metav1.Time{} normalized = append(normalized, c) } require.ElementsMatch(t, expected, normalized) } func makeTestClientCredsSecret(t *testing.T, clientID string, clientSecret string) *corev1.Secret { t.Helper() env := library.IntegrationEnv(t) client := library.NewClientset(t) ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() created, err := client.CoreV1().Secrets(env.SupervisorNamespace).Create(ctx, &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ Namespace: env.SupervisorNamespace, GenerateName: "test-client-creds-", Labels: map[string]string{"pinniped.dev/test": ""}, Annotations: map[string]string{"pinniped.dev/testName": t.Name()}, }, Type: "secrets.pinniped.dev/oidc-client", StringData: map[string]string{ "clientID": clientID, "clientSecret": clientSecret, }, }, metav1.CreateOptions{}) require.NoError(t, err) t.Cleanup(func() { err := client.CoreV1().Secrets(env.SupervisorNamespace).Delete(context.Background(), created.Name, metav1.DeleteOptions{}) require.NoError(t, err) }) t.Logf("created test client credentials Secret %s", created.Name) return created } func makeTestUpstream(t *testing.T, spec v1alpha1.UpstreamOIDCProviderSpec, expectedPhase v1alpha1.UpstreamOIDCProviderPhase) *v1alpha1.UpstreamOIDCProvider { t.Helper() env := library.IntegrationEnv(t) client := library.NewSupervisorClientset(t) ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute) defer cancel() // Create the UpstreamOIDCProvider using GenerateName to get a random name. created, err := client.IDPV1alpha1(). UpstreamOIDCProviders(env.SupervisorNamespace). Create(ctx, &v1alpha1.UpstreamOIDCProvider{ ObjectMeta: metav1.ObjectMeta{ Namespace: env.SupervisorNamespace, GenerateName: "test-upstream-", Labels: map[string]string{"pinniped.dev/test": ""}, Annotations: map[string]string{"pinniped.dev/testName": t.Name()}, }, Spec: spec, }, metav1.CreateOptions{}) require.NoError(t, err) // Always clean this up after this point. t.Cleanup(func() { err := client.IDPV1alpha1(). UpstreamOIDCProviders(env.SupervisorNamespace). Delete(context.Background(), created.Name, metav1.DeleteOptions{}) require.NoError(t, err) }) t.Logf("created test UpstreamOIDCProvider %s", created.Name) // Wait for the UpstreamOIDCProvider to enter the expected phase (or time out). var result *v1alpha1.UpstreamOIDCProvider require.Eventuallyf(t, func() bool { var err error result, err = client.IDPV1alpha1(). UpstreamOIDCProviders(created.Namespace).Get(ctx, created.Name, metav1.GetOptions{}) require.NoError(t, err) return result.Status.Phase == expectedPhase }, 60*time.Second, 1*time.Second, "expected the UpstreamOIDCProvider to go into phase %s", expectedPhase) return result }