// Copyright 2024 the Pinniped contributors. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 package integration import ( "fmt" "strings" "testing" "go.pinniped.dev/internal/here" "go.pinniped.dev/test/testlib" ) // TestTLSSpecKubeBuilderValidationSupervisor_Parallel tests kubebuilder validation // on the TLSSpec in Pinniped supervisor CRDs using OIDCIdentityProvider as an example. func TestTLSSpecKubeBuilderValidationSupervisor_Parallel(t *testing.T) { env := testlib.IntegrationEnv(t) oidcIDPTemplate := here.Doc(` apiVersion: idp.supervisor.%s/v1alpha1 kind: OIDCIdentityProvider metadata: name: %s spec: issuer: %s authorizationConfig: additionalScopes: [offline_access, email] allowPasswordGrant: true client: secretName: foo-bar-client-credentials %s `) ldapIDPTemplate := here.Doc(` apiVersion: idp.supervisor.%s/v1alpha1 kind: LDAPIdentityProvider metadata: name: %s spec: host: %s bind: secretName: foo-bar-bind-credentials userSearch: base: foo attributes: username: bar uid: baz %s `) activeDirectoryIDPTemplate := here.Doc(` apiVersion: idp.supervisor.%s/v1alpha1 kind: ActiveDirectoryIdentityProvider metadata: name: %s spec: host: %s bind: secretName: foo-bar-bind-credentials %s `) githubIDPTemplate := here.Doc(` apiVersion: idp.supervisor.%s/v1alpha1 kind: GitHubIdentityProvider metadata: name: %s spec: allowAuthentication: organizations: policy: AllGitHubUsers client: secretName: does-not-matter githubAPI: %s `) testCases := []struct { name string tlsYAML string expectedErrorSnippets []string expectedGitHubErrorSnippets []string }{ { name: "should disallow certificate authority data source with missing name", tlsYAML: here.Doc(` tls: certificateAuthorityDataSource: kind: Secret key: bar `), expectedErrorSnippets: []string{`The %s "%s" is invalid: spec.tls.certificateAuthorityDataSource.name: Required value`}, expectedGitHubErrorSnippets: []string{ `The %s "%s" is invalid:`, "spec.githubAPI.tls.certificateAuthorityDataSource.name: Required value", }, }, { name: "should disallow certificate authority data source with empty value for name", tlsYAML: here.Doc(` tls: certificateAuthorityDataSource: kind: Secret name: "" key: bar `), expectedErrorSnippets: []string{`The %s "%s" is invalid: spec.tls.certificateAuthorityDataSource.name: Invalid value: "": spec.tls.certificateAuthorityDataSource.name in body should be at least 1 chars long`}, expectedGitHubErrorSnippets: []string{`The %s "%s" is invalid: spec.githubAPI.tls.certificateAuthorityDataSource.name: Invalid value: "": spec.githubAPI.tls.certificateAuthorityDataSource.name in body should be at least 1 chars long`}, }, { name: "should disallow certificate authority data source with missing key", tlsYAML: here.Doc(` tls: certificateAuthorityDataSource: kind: Secret name: foo `), expectedErrorSnippets: []string{`The %s "%s" is invalid: spec.tls.certificateAuthorityDataSource.key: Required value`}, expectedGitHubErrorSnippets: []string{ `The %s "%s" is invalid:`, "spec.githubAPI.tls.certificateAuthorityDataSource.key: Required value", }, }, { name: "should disallow certificate authority data source with empty value for key", tlsYAML: here.Doc(` tls: certificateAuthorityDataSource: kind: Secret name: foo key: "" `), expectedErrorSnippets: []string{`The %s "%s" is invalid: spec.tls.certificateAuthorityDataSource.key: Invalid value: "": spec.tls.certificateAuthorityDataSource.key in body should be at least 1 chars long`}, expectedGitHubErrorSnippets: []string{`The %s "%s" is invalid: spec.githubAPI.tls.certificateAuthorityDataSource.key: Invalid value: "": spec.githubAPI.tls.certificateAuthorityDataSource.key in body should be at least 1 chars long`}, }, { name: "should disallow certificate authority data source with missing kind", tlsYAML: here.Doc(` tls: certificateAuthorityDataSource: name: foo key: bar `), expectedErrorSnippets: []string{`The %s "%s" is invalid: spec.tls.certificateAuthorityDataSource.kind: Required value`}, expectedGitHubErrorSnippets: []string{ `The %s "%s" is invalid:`, "spec.githubAPI.tls.certificateAuthorityDataSource.kind: Required value", }, }, { name: "should disallow certificate authority data source with empty value for kind", tlsYAML: here.Doc(` tls: certificateAuthorityDataSource: kind: "" name: foo key: bar `), expectedErrorSnippets: []string{`The %s "%s" is invalid: spec.tls.certificateAuthorityDataSource.kind: Unsupported value: "": supported values: "Secret", "ConfigMap"`}, expectedGitHubErrorSnippets: []string{ `The %s "%s" is invalid:`, `spec.githubAPI.tls.certificateAuthorityDataSource.kind: Unsupported value: "": supported values: "Secret", "ConfigMap"`, }, }, { name: "should disallow certificate authority data source with invalid kind", tlsYAML: here.Doc(` tls: certificateAuthorityDataSource: kind: sorcery name: foo key: bar `), expectedErrorSnippets: []string{`The %s "%s" is invalid: spec.tls.certificateAuthorityDataSource.kind: Unsupported value: "sorcery": supported values: "Secret", "ConfigMap"`}, expectedGitHubErrorSnippets: []string{ `The %s "%s" is invalid:`, `spec.githubAPI.tls.certificateAuthorityDataSource.kind: Unsupported value: "sorcery": supported values: "Secret", "ConfigMap"`, }, }, { name: "should create a custom resource passing all validations using a Secret source", tlsYAML: here.Doc(` tls: certificateAuthorityDataSource: kind: Secret name: foo key: bar `), expectedErrorSnippets: nil, expectedGitHubErrorSnippets: nil, }, { name: "should create a custom resource passing all validations using a ConfigMap source", tlsYAML: here.Doc(` tls: certificateAuthorityDataSource: kind: ConfigMap name: foo key: bar `), expectedErrorSnippets: nil, expectedGitHubErrorSnippets: nil, }, { name: "should create a custom resource without any tls spec", tlsYAML: "", expectedErrorSnippets: nil, expectedGitHubErrorSnippets: nil, }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { t.Parallel() // Further indent every line except for the first line by four spaces. // Use four spaces because that's what here.Doc uses. // Do not indent the first line because the template already indents it. indentedTLSYAML := strings.ReplaceAll(tc.tlsYAML, "\n", "\n ") t.Run("apply OIDC IDP", func(t *testing.T) { resourceName := "test-oidc-idp-" + testlib.RandHex(t, 7) yamlBytes := []byte(fmt.Sprintf(oidcIDPTemplate, env.APIGroupSuffix, resourceName, env.SupervisorUpstreamOIDC.Issuer, indentedTLSYAML)) stdOut, stdErr, err := performKubectlApply(t, resourceName, yamlBytes) requireKubectlApplyResult(t, stdOut, stdErr, err, fmt.Sprintf(`oidcidentityprovider.idp.supervisor.%s`, env.APIGroupSuffix), tc.expectedErrorSnippets, "OIDCIdentityProvider", resourceName, ) }) t.Run("apply LDAP IDP", func(t *testing.T) { resourceName := "test-ldap-idp-" + testlib.RandHex(t, 7) yamlBytes := []byte(fmt.Sprintf(ldapIDPTemplate, env.APIGroupSuffix, resourceName, env.SupervisorUpstreamLDAP.Host, indentedTLSYAML)) stdOut, stdErr, err := performKubectlApply(t, resourceName, yamlBytes) requireKubectlApplyResult(t, stdOut, stdErr, err, fmt.Sprintf(`ldapidentityprovider.idp.supervisor.%s`, env.APIGroupSuffix), tc.expectedErrorSnippets, "LDAPIdentityProvider", resourceName, ) }) t.Run("apply ActiveDirectory IDP", func(t *testing.T) { resourceName := "test-ad-idp-" + testlib.RandHex(t, 7) yamlBytes := []byte(fmt.Sprintf(activeDirectoryIDPTemplate, env.APIGroupSuffix, resourceName, env.SupervisorUpstreamLDAP.Host, indentedTLSYAML)) stdOut, stdErr, err := performKubectlApply(t, resourceName, yamlBytes) requireKubectlApplyResult(t, stdOut, stdErr, err, fmt.Sprintf(`activedirectoryidentityprovider.idp.supervisor.%s`, env.APIGroupSuffix), tc.expectedErrorSnippets, "ActiveDirectoryIdentityProvider", resourceName, ) }) t.Run("apply GitHub IDP", func(t *testing.T) { // GitHub is nested deeper indentedTLSYAMLForGitHub := strings.ReplaceAll(indentedTLSYAML, "\n", "\n ") resourceName := "test-github-idp-" + testlib.RandHex(t, 7) yamlBytes := []byte(fmt.Sprintf(githubIDPTemplate, env.APIGroupSuffix, resourceName, indentedTLSYAMLForGitHub)) stdOut, stdErr, err := performKubectlApply(t, resourceName, yamlBytes) requireKubectlApplyResult(t, stdOut, stdErr, err, fmt.Sprintf(`githubidentityprovider.idp.supervisor.%s`, env.APIGroupSuffix), tc.expectedGitHubErrorSnippets, "GitHubIdentityProvider", resourceName, ) }) }) } }