// Copyright 2020 the Pinniped contributors. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 package integration import ( "context" "encoding/json" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "gopkg.in/square/go-jose.v2" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" configv1alpha1 "go.pinniped.dev/generated/1.19/apis/supervisor/config/v1alpha1" "go.pinniped.dev/test/library" ) func TestSupervisorOIDCKeys(t *testing.T) { env := library.IntegrationEnv(t) kubeClient := library.NewClientset(t) supervisorClient := library.NewSupervisorClientset(t) ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute) defer cancel() // Create our OPC under test. opc := library.CreateTestOIDCProvider(ctx, t, "", "") // Ensure a secret is created with the OPC's JWKS. var updatedOPC *configv1alpha1.OIDCProvider var err error assert.Eventually(t, func() bool { updatedOPC, err = supervisorClient. ConfigV1alpha1(). OIDCProviders(env.SupervisorNamespace). Get(ctx, opc.Name, metav1.GetOptions{}) return err == nil && updatedOPC.Status.JWKSSecret.Name != "" }, time.Second*10, time.Millisecond*500) require.NoError(t, err) require.NotEmpty(t, updatedOPC.Status.JWKSSecret.Name) // Ensure the secret actually exists. secret, err := kubeClient. CoreV1(). Secrets(env.SupervisorNamespace). Get(ctx, updatedOPC.Status.JWKSSecret.Name, metav1.GetOptions{}) require.NoError(t, err) // Ensure that the secret was labelled. for k, v := range env.SupervisorCustomLabels { require.Equalf(t, v, secret.Labels[k], "expected secret to have label `%s: %s`", k, v) } require.Equal(t, env.SupervisorAppName, secret.Labels["app"]) // Ensure the secret has an active key. jwkData, ok := secret.Data["activeJWK"] require.True(t, ok, "secret is missing active jwk") // Ensure the secret's active key is valid. var activeJWK jose.JSONWebKey require.NoError(t, json.Unmarshal(jwkData, &activeJWK)) require.True(t, activeJWK.Valid(), "active jwk is invalid") require.False(t, activeJWK.IsPublic(), "active jwk is public") // Ensure the secret has a JWKS. jwksData, ok := secret.Data["jwks"] require.True(t, ok, "secret is missing jwks") // Ensure the secret's JWKS is valid, public, and contains the singing key. var jwks jose.JSONWebKeySet require.NoError(t, json.Unmarshal(jwksData, &jwks)) foundActiveJWK := false for _, jwk := range jwks.Keys { require.Truef(t, jwk.Valid(), "jwk %s is invalid", jwk.KeyID) require.Truef(t, jwk.IsPublic(), "jwk %s is not public", jwk.KeyID) if jwk.KeyID == activeJWK.KeyID { foundActiveJWK = true } } require.True(t, foundActiveJWK, "could not find active JWK in JWKS: %s", jwks) // Ensure upon deleting the secret, it is eventually brought back. err = kubeClient. CoreV1(). Secrets(env.SupervisorNamespace). Delete(ctx, updatedOPC.Status.JWKSSecret.Name, metav1.DeleteOptions{}) require.NoError(t, err) assert.Eventually(t, func() bool { secret, err = kubeClient. CoreV1(). Secrets(env.SupervisorNamespace). Get(ctx, updatedOPC.Status.JWKSSecret.Name, metav1.GetOptions{}) return err == nil }, time.Second*10, time.Millisecond*500) require.NoError(t, err) // Upon deleting the OPC, the secret is deleted (we test this behavior in our uninstall tests). }