diff --git a/test/integration/concierge_jwtauthenticator_status_test.go b/test/integration/concierge_jwtauthenticator_status_test.go index 8d9f61f00..4776eaff1 100644 --- a/test/integration/concierge_jwtauthenticator_status_test.go +++ b/test/integration/concierge_jwtauthenticator_status_test.go @@ -29,7 +29,7 @@ func TestConciergeJWTAuthenticatorStatus_Disruptive(t *testing.T) { run func(t *testing.T) }{ { - name: "valid spec with no errors and all good status conditions and phase", + name: "valid spec with no errors and all good status conditions and phase will result in a jwt authenticator that is ready", run: func(t *testing.T) { jwtAuthenticator := testlib.CreateTestJWTAuthenticator(ctx, t, v1alpha1.JWTAuthenticatorSpec{ Issuer: env.SupervisorUpstreamOIDC.Issuer, @@ -44,8 +44,116 @@ func TestConciergeJWTAuthenticatorStatus_Disruptive(t *testing.T) { jwtAuthenticator.Name, allSuccessfulJWTAuthenticatorConditions()) }, - }, { - name: "invalid with bad issuer", + }, + { + name: "valid spec with invalid CA in TLS config will result in a jwt authenticator that is not ready", + run: func(t *testing.T) { + jwtAuthenticator := testlib.CreateTestJWTAuthenticator(ctx, t, v1alpha1.JWTAuthenticatorSpec{ + Issuer: env.SupervisorUpstreamOIDC.Issuer, + Audience: "some-fake-audience", + TLS: &v1alpha1.TLSSpec{ + CertificateAuthorityData: "invalid base64-encoded data", + }, + }, v1alpha1.JWTAuthenticatorPhaseError) + + testlib.WaitForJWTAuthenticatorStatusConditions( + ctx, t, + jwtAuthenticator.Name, + replaceSomeConditions( + allSuccessfulJWTAuthenticatorConditions(), + []metav1.Condition{ + { + Type: "Ready", + Status: "False", + Reason: "NotReady", + Message: "the JWTAuthenticator is not ready: see other conditions for details", + }, { + Type: "AuthenticatorValid", + Status: "Unknown", + Reason: "UnableToValidate", + Message: "unable to validate; see other conditions for details", + }, { + Type: "JWKSURLValid", + Status: "Unknown", + Reason: "UnableToValidate", + Message: "unable to validate; see other conditions for details", + }, { + Type: "JWKSFetchValid", + Status: "Unknown", + Reason: "UnableToValidate", + Message: "unable to validate; see other conditions for details", + }, { + Type: "DiscoveryURLValid", + Status: "Unknown", + Reason: "UnableToValidate", + Message: "unable to validate; see other conditions for details", + }, { + Type: "TLSConfigurationValid", + Status: "False", + Reason: "InvalidTLSConfiguration", + Message: "invalid TLS configuration: illegal base64 data at input byte 7", + }, + }, + )) + }, + }, + { + name: "valid spec with valid CA in TLS config but does not match issuer server will result in a jwt authenticator that is not ready", + run: func(t *testing.T) { + jwtAuthenticator := testlib.CreateTestJWTAuthenticator(ctx, t, v1alpha1.JWTAuthenticatorSpec{ + Issuer: env.SupervisorUpstreamOIDC.Issuer, + Audience: "some-fake-audience", + // Some random generated cert + // Issuer: C=US, O=Pivotal + // No SAN provided + TLS: &v1alpha1.TLSSpec{ + CertificateAuthorityData: "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURVVENDQWptZ0F3SUJBZ0lWQUpzNStTbVRtaTJXeUI0bGJJRXBXaUs5a1RkUE1BMEdDU3FHU0liM0RRRUIKQ3dVQU1COHhDekFKQmdOVkJBWVRBbFZUTVJBd0RnWURWUVFLREFkUWFYWnZkR0ZzTUI0WERUSXdNRFV3TkRFMgpNamMxT0ZvWERUSTBNRFV3TlRFMk1qYzFPRm93SHpFTE1Ba0dBMVVFQmhNQ1ZWTXhFREFPQmdOVkJBb01CMUJwCmRtOTBZV3d3Z2dFaU1BMEdDU3FHU0liM0RRRUJBUVVBQTRJQkR3QXdnZ0VLQW9JQkFRRERZWmZvWGR4Z2NXTEMKZEJtbHB5a0tBaG9JMlBuUWtsVFNXMno1cGcwaXJjOGFRL1E3MXZzMTRZYStmdWtFTGlvOTRZYWw4R01DdVFrbApMZ3AvUEE5N1VYelhQNDBpK25iNXcwRGpwWWd2dU9KQXJXMno2MFRnWE5NSFh3VHk4ME1SZEhpUFVWZ0VZd0JpCmtkNThzdEFVS1Y1MnBQTU1reTJjNy9BcFhJNmRXR2xjalUvaFBsNmtpRzZ5dEw2REtGYjJQRWV3MmdJM3pHZ2IKOFVVbnA1V05DZDd2WjNVY0ZHNXlsZEd3aGc3cnZ4U1ZLWi9WOEhCMGJmbjlxamlrSVcxWFM4dzdpUUNlQmdQMApYZWhKZmVITlZJaTJtZlczNlVQbWpMdnVKaGpqNDIrdFBQWndvdDkzdWtlcEgvbWpHcFJEVm9wamJyWGlpTUYrCkYxdnlPNGMxQWdNQkFBR2pnWU13Z1lBd0hRWURWUjBPQkJZRUZNTWJpSXFhdVkwajRVWWphWDl0bDJzby9LQ1IKTUI4R0ExVWRJd1FZTUJhQUZNTWJpSXFhdVkwajRVWWphWDl0bDJzby9LQ1JNQjBHQTFVZEpRUVdNQlFHQ0NzRwpBUVVGQndNQ0JnZ3JCZ0VGQlFjREFUQVBCZ05WSFJNQkFmOEVCVEFEQVFIL01BNEdBMVVkRHdFQi93UUVBd0lCCkJqQU5CZ2txaGtpRzl3MEJBUXNGQUFPQ0FRRUFYbEh4M2tIMDZwY2NDTDlEVE5qTnBCYnlVSytGd2R6T2IwWFYKcmpNaGtxdHVmdEpUUnR5T3hKZ0ZKNXhUR3pCdEtKamcrVU1pczBOV0t0VDBNWThVMU45U2c5SDl0RFpHRHBjVQpxMlVRU0Y4dXRQMVR3dnJIUzIrdzB2MUoxdHgrTEFiU0lmWmJCV0xXQ21EODUzRlVoWlFZekkvYXpFM28vd0p1CmlPUklMdUpNUk5vNlBXY3VLZmRFVkhaS1RTWnk3a25FcHNidGtsN3EwRE91eUFWdG9HVnlkb3VUR0FOdFhXK2YKczNUSTJjKzErZXg3L2RZOEJGQTFzNWFUOG5vZnU3T1RTTzdiS1kzSkRBUHZOeFQzKzVZUXJwNGR1Nmh0YUFMbAppOHNaRkhidmxpd2EzdlhxL3p1Y2JEaHEzQzBhZnAzV2ZwRGxwSlpvLy9QUUFKaTZLQT09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K", + }, + }, v1alpha1.JWTAuthenticatorPhaseError) + + testlib.WaitForJWTAuthenticatorStatusConditions( + ctx, t, + jwtAuthenticator.Name, + replaceSomeConditions( + allSuccessfulJWTAuthenticatorConditions(), + []metav1.Condition{ + { + Type: "Ready", + Status: "False", + Reason: "NotReady", + Message: "the JWTAuthenticator is not ready: see other conditions for details", + }, { + Type: "AuthenticatorValid", + Status: "Unknown", + Reason: "UnableToValidate", + Message: "unable to validate; see other conditions for details", + }, { + Type: "JWKSURLValid", + Status: "Unknown", + Reason: "UnableToValidate", + Message: "unable to validate; see other conditions for details", + }, { + Type: "JWKSFetchValid", + Status: "Unknown", + Reason: "UnableToValidate", + Message: "unable to validate; see other conditions for details", + }, { + Type: "DiscoveryURLValid", + Status: "False", + Reason: "InvalidDiscoveryProbe", + Message: `could not perform oidc discovery on provider issuer: Get "` + env.SupervisorUpstreamOIDC.Issuer + `/.well-known/openid-configuration": tls: failed to verify certificate: x509: certificate signed by unknown authority`, + }, { + Type: "TLSConfigurationValid", + Status: "True", + Reason: "Success", + Message: "successfully parsed specified CA bundle", + }, + }, + )) + }, + }, + { + name: "invalid with bad issuer will result in a jwt authenticator that is not ready", run: func(t *testing.T) { fakeIssuerURL := "https://127.0.0.1:443/some-fake-issuer" jwtAuthenticator := testlib.CreateTestJWTAuthenticator(ctx, t, v1alpha1.JWTAuthenticatorSpec{ @@ -71,12 +179,17 @@ func TestConciergeJWTAuthenticatorStatus_Disruptive(t *testing.T) { Type: "AuthenticatorValid", Status: "Unknown", Reason: "UnableToValidate", - Message: "unable to validate; other issues present", + Message: "unable to validate; see other conditions for details", }, { Type: "JWKSURLValid", Status: "Unknown", Reason: "UnableToValidate", - Message: "unable to validate; other issues present", + Message: "unable to validate; see other conditions for details", + }, { + Type: "JWKSFetchValid", + Status: "Unknown", + Reason: "UnableToValidate", + Message: "unable to validate; see other conditions for details", }, { Type: "DiscoveryURLValid", Status: "False", @@ -91,12 +204,14 @@ func TestConciergeJWTAuthenticatorStatus_Disruptive(t *testing.T) { for _, test := range tests { tt := test t.Run(tt.name, func(t *testing.T) { + t.Parallel() tt.run(t) }) } } func TestConciergeJWTAuthenticatorCRDValidations_Parallel(t *testing.T) { + env := testlib.IntegrationEnv(t) jwtAuthenticatorClient := testlib.NewConciergeClientset(t).AuthenticationV1alpha1().JWTAuthenticators() ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute) @@ -107,9 +222,6 @@ func TestConciergeJWTAuthenticatorCRDValidations_Parallel(t *testing.T) { name string jwtAuthenticator *v1alpha1.JWTAuthenticator wantErr string - // some tests change the environment (api group suffix pinniped.dev->walrus.tld) so - // we need to be able to compare against several error strings - wantOneOfErr []string }{ { name: "issuer can not be empty string", @@ -120,12 +232,8 @@ func TestConciergeJWTAuthenticatorCRDValidations_Parallel(t *testing.T) { Audience: "fake-audience", }, }, - wantOneOfErr: []string{ - `JWTAuthenticator.authentication.concierge.pinniped.dev "` + objectMeta.Name + `" is invalid: ` + - `spec.issuer: Invalid value: "": spec.issuer in body should be at least 1 chars long`, - `JWTAuthenticator.authentication.concierge.walrus.tld "` + objectMeta.Name + `" is invalid: ` + - `spec.issuer: Invalid value: "": spec.issuer in body should be at least 1 chars long`, - }, + wantErr: `JWTAuthenticator.authentication.concierge.` + env.APIGroupSuffix + ` "` + objectMeta.Name + `" is invalid: ` + + `spec.issuer: Invalid value: "": spec.issuer in body should be at least 1 chars long`, }, { name: "audience can not be empty string", @@ -136,12 +244,8 @@ func TestConciergeJWTAuthenticatorCRDValidations_Parallel(t *testing.T) { Audience: "", }, }, - wantOneOfErr: []string{ - `JWTAuthenticator.authentication.concierge.pinniped.dev "` + objectMeta.Name + `" is invalid: ` + - `spec.audience: Invalid value: "": spec.audience in body should be at least 1 chars long`, - `JWTAuthenticator.authentication.concierge.walrus.tld "` + objectMeta.Name + `" is invalid: ` + - `spec.audience: Invalid value: "": spec.audience in body should be at least 1 chars long`, - }, + wantErr: `JWTAuthenticator.authentication.concierge.` + env.APIGroupSuffix + ` "` + objectMeta.Name + `" is invalid: ` + + `spec.audience: Invalid value: "": spec.audience in body should be at least 1 chars long`, }, { name: "issuer must be https", @@ -152,40 +256,36 @@ func TestConciergeJWTAuthenticatorCRDValidations_Parallel(t *testing.T) { Audience: "foo", }, }, - wantOneOfErr: []string{ - `JWTAuthenticator.authentication.concierge.pinniped.dev "` + objectMeta.Name + `" is invalid: ` + - `spec.issuer: Invalid value: "http://www.example.com": spec.issuer in body should match '^https://'`, - `JWTAuthenticator.authentication.concierge.walrus.tld "` + objectMeta.Name + `" is invalid: ` + - `spec.issuer: Invalid value: "http://www.example.com": spec.issuer in body should match '^https://'`, - }, + wantErr: `JWTAuthenticator.authentication.concierge.` + env.APIGroupSuffix + ` "` + objectMeta.Name + `" is invalid: ` + + `spec.issuer: Invalid value: "http://www.example.com": spec.issuer in body should match '^https://'`, }, { name: "minimum valid authenticator", jwtAuthenticator: &v1alpha1.JWTAuthenticator{ ObjectMeta: testlib.ObjectMetaWithRandomName(t, "jwtauthenticator"), Spec: v1alpha1.JWTAuthenticatorSpec{ - Issuer: "https://www.example.com", + Issuer: env.CLIUpstreamOIDC.Issuer, Audience: "foo", }, }, }, { - name: "minimum valid authenticator can have empty claims block", + name: "valid authenticator can have empty claims block", jwtAuthenticator: &v1alpha1.JWTAuthenticator{ ObjectMeta: testlib.ObjectMetaWithRandomName(t, "jwtauthenticator"), Spec: v1alpha1.JWTAuthenticatorSpec{ - Issuer: "https://www.example.com", + Issuer: env.CLIUpstreamOIDC.Issuer, Audience: "foo", Claims: v1alpha1.JWTTokenClaims{}, }, }, }, { - name: "minimum valid authenticator can have empty group claim and empty username claim", + name: "valid authenticator can have empty group claim and empty username claim", jwtAuthenticator: &v1alpha1.JWTAuthenticator{ ObjectMeta: testlib.ObjectMetaWithRandomName(t, "jwtauthenticator"), Spec: v1alpha1.JWTAuthenticatorSpec{ - Issuer: "https://www.example.com", + Issuer: env.CLIUpstreamOIDC.Issuer, Audience: "foo", Claims: v1alpha1.JWTTokenClaims{ Groups: "", @@ -195,11 +295,11 @@ func TestConciergeJWTAuthenticatorCRDValidations_Parallel(t *testing.T) { }, }, { - name: "minimum valid authenticator can have empty TLS block", + name: "valid authenticator can have empty TLS block", jwtAuthenticator: &v1alpha1.JWTAuthenticator{ ObjectMeta: testlib.ObjectMetaWithRandomName(t, "jwtauthenticator"), Spec: v1alpha1.JWTAuthenticatorSpec{ - Issuer: "https://www.example.com", + Issuer: env.CLIUpstreamOIDC.Issuer, Audience: "foo", Claims: v1alpha1.JWTTokenClaims{ Groups: "", @@ -210,11 +310,11 @@ func TestConciergeJWTAuthenticatorCRDValidations_Parallel(t *testing.T) { }, }, { - name: "minimum valid authenticator can have empty TLS CertificateAuthorityData", + name: "valid authenticator can have empty TLS CertificateAuthorityData", jwtAuthenticator: &v1alpha1.JWTAuthenticator{ ObjectMeta: testlib.ObjectMetaWithRandomName(t, "jwtauthenticator"), Spec: v1alpha1.JWTAuthenticatorSpec{ - Issuer: "https://www.example.com", + Issuer: env.CLIUpstreamOIDC.Issuer, Audience: "foo", Claims: v1alpha1.JWTTokenClaims{ Groups: "", @@ -241,22 +341,13 @@ func TestConciergeJWTAuthenticatorCRDValidations_Parallel(t *testing.T) { } }) - if tt.wantErr != "" && tt.wantOneOfErr != nil { - require.NoError(t, fmt.Errorf("test '%s' should not use both tt.wantErr and tt.wantOneOfErr", tt.name)) - } - - if tt.wantErr == "" && tt.wantOneOfErr == nil { - require.NoError(t, createErr) - } - if tt.wantErr != "" { wantErr := tt.wantErr require.EqualError(t, createErr, wantErr) + } else { + require.NoError(t, createErr) } - if tt.wantOneOfErr != nil { - wantOneOfErr := tt.wantOneOfErr - require.Contains(t, wantOneOfErr, createErr.Error()) - } + }) } } @@ -278,7 +369,11 @@ func allSuccessfulJWTAuthenticatorConditions() []metav1.Condition { Reason: "Success", Message: "issuer is a valid URL", }, { - + Type: "JWKSFetchValid", + Status: "True", + Reason: "Success", + Message: "successfully fetched jwks", + }, { Type: "JWKSURLValid", Status: "True", Reason: "Success", @@ -292,6 +387,6 @@ func allSuccessfulJWTAuthenticatorConditions() []metav1.Condition { Type: "TLSConfigurationValid", Status: "True", Reason: "Success", - Message: "valid TLS configuration", + Message: "successfully parsed specified CA bundle", }} } diff --git a/test/integration/e2e_test.go b/test/integration/e2e_test.go index 128efaec9..da42f9e4e 100644 --- a/test/integration/e2e_test.go +++ b/test/integration/e2e_test.go @@ -1,4 +1,4 @@ -// Copyright 2020-2023 the Pinniped contributors. All Rights Reserved. +// Copyright 2020-2024 the Pinniped contributors. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 package integration @@ -111,7 +111,7 @@ func TestE2EFullIntegration_Browser(t *testing.T) { ) // Create a JWTAuthenticator that will validate the tokens from the downstream issuer. - // if the FederationDomain is not Ready, the JWTAuthenticator cannot be ready, either. + // If the FederationDomain is not Ready, the JWTAuthenticator cannot be ready, either. clusterAudience := "test-cluster-" + testlib.RandHex(t, 8) authenticator := testlib.CreateTestJWTAuthenticator(topSetupCtx, t, authv1alpha.JWTAuthenticatorSpec{ Issuer: federationDomain.Spec.Issuer, diff --git a/test/testlib/client.go b/test/testlib/client.go index 396c734f4..4af9c52c9 100644 --- a/test/testlib/client.go +++ b/test/testlib/client.go @@ -1,4 +1,4 @@ -// Copyright 2020-2023 the Pinniped contributors. All Rights Reserved. +// Copyright 2020-2024 the Pinniped contributors. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 package testlib @@ -205,8 +205,7 @@ func CreateTestWebhookAuthenticator(ctx context.Context, t *testing.T) corev1.Ty } // CreateTestJWTAuthenticatorForCLIUpstream creates and returns a test JWTAuthenticator which will be automatically -// deleted at the end of the current test's lifetime. It returns a corev1.TypedLocalObjectReference which describes -// the test JWT authenticator within the test namespace. +// deleted at the end of the current test's lifetime. // // CreateTestJWTAuthenticatorForCLIUpstream gets the OIDC issuer info from IntegrationEnv().CLIUpstreamOIDC. func CreateTestJWTAuthenticatorForCLIUpstream(ctx context.Context, t *testing.T) *auth1alpha1.JWTAuthenticator { @@ -232,8 +231,7 @@ func CreateTestJWTAuthenticatorForCLIUpstream(ctx context.Context, t *testing.T) } // CreateTestJWTAuthenticator creates and returns a test JWTAuthenticator which will be automatically deleted -// at the end of the current test's lifetime. It returns a corev1.TypedLocalObjectReference which describes the test JWT -// authenticator within the test namespace. +// at the end of the current test's lifetime. func CreateTestJWTAuthenticator( ctx context.Context, t *testing.T,