Files
pinniped/test/integration/supervisor_github_idp_test.go
Ryan Richard 4f661aaa69 pay attention to web proxy settings during connection probes
- WebhookAuthenticator will now detect the proxy setting and skip
  dialing the connection probe if it should go through a proxy
- GitHubIdentityProvider will avoid using tls.Dial altogether
  by instead making a real request to the GitHub API as its
  connection probe, because this will respect the proxy settings
2024-10-10 10:41:31 -07:00

747 lines
25 KiB
Go

// Copyright 2024 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package integration
import (
"context"
"encoding/base64"
"fmt"
"os"
"path/filepath"
"testing"
"time"
"github.com/stretchr/testify/require"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/utils/ptr"
idpv1alpha1 "go.pinniped.dev/generated/latest/apis/supervisor/idp/v1alpha1"
"go.pinniped.dev/internal/here"
"go.pinniped.dev/internal/testutil"
"go.pinniped.dev/test/testlib"
)
const generateGitHubNamePrefix = "integration-test-github-idp-"
func TestGitHubIDPStaticValidationOnCreate_Parallel(t *testing.T) {
adminClient := testlib.NewKubernetesClientset(t)
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute)
t.Cleanup(cancel)
namespaceClient := adminClient.CoreV1().Namespaces()
skipCELTests := !testutil.KubeServerMinorVersionAtLeastInclusive(t, adminClient.Discovery(), 26)
ns, err := namespaceClient.Create(ctx, &corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
GenerateName: generateGitHubNamePrefix,
},
}, metav1.CreateOptions{})
require.NoError(t, err)
t.Cleanup(func() {
require.NoError(t, namespaceClient.Delete(ctx, ns.Name, metav1.DeleteOptions{}))
})
gitHubIDPClient := testlib.NewSupervisorClientset(t).IDPV1alpha1().GitHubIdentityProviders(ns.Name)
tests := []struct {
name string
usesCELValidation bool
inputSpec idpv1alpha1.GitHubIdentityProviderSpec
wantSpec idpv1alpha1.GitHubIdentityProviderSpec
wantErr string
}{
{
name: "all fields set",
inputSpec: idpv1alpha1.GitHubIdentityProviderSpec{
GitHubAPI: idpv1alpha1.GitHubAPIConfig{
Host: ptr.To("some-host.example.com"),
TLS: &idpv1alpha1.TLSSpec{
CertificateAuthorityData: func() string {
return base64.StdEncoding.EncodeToString([]byte("-----BEGIN CERTIFICATE-----\ndata goes here"))
}(),
},
},
AllowAuthentication: idpv1alpha1.GitHubAllowAuthenticationSpec{
Organizations: idpv1alpha1.GitHubOrganizationsSpec{
Allowed: []string{
"org1",
"that-other-org",
},
Policy: ptr.To(idpv1alpha1.GitHubAllowedAuthOrganizationsPolicyOnlyUsersFromAllowedOrganizations),
},
},
Claims: idpv1alpha1.GitHubClaims{
Username: ptr.To(idpv1alpha1.GitHubUsernameLoginAndID),
Groups: ptr.To(idpv1alpha1.GitHubUseTeamSlugForGroupName),
},
Client: idpv1alpha1.GitHubClientSpec{
SecretName: "any-name-goes-here",
},
},
wantSpec: idpv1alpha1.GitHubIdentityProviderSpec{
GitHubAPI: idpv1alpha1.GitHubAPIConfig{
Host: ptr.To("some-host.example.com"),
TLS: &idpv1alpha1.TLSSpec{
CertificateAuthorityData: "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCmRhdGEgZ29lcyBoZXJl",
},
},
AllowAuthentication: idpv1alpha1.GitHubAllowAuthenticationSpec{
Organizations: idpv1alpha1.GitHubOrganizationsSpec{
Allowed: []string{
"org1",
"that-other-org",
},
Policy: ptr.To(idpv1alpha1.GitHubAllowedAuthOrganizationsPolicyOnlyUsersFromAllowedOrganizations),
},
},
Claims: idpv1alpha1.GitHubClaims{
Username: ptr.To(idpv1alpha1.GitHubUsernameLoginAndID),
Groups: ptr.To(idpv1alpha1.GitHubUseTeamSlugForGroupName),
},
Client: idpv1alpha1.GitHubClientSpec{
SecretName: "any-name-goes-here",
},
},
},
{
name: "minimum fields set - inherit defaults",
inputSpec: idpv1alpha1.GitHubIdentityProviderSpec{
AllowAuthentication: idpv1alpha1.GitHubAllowAuthenticationSpec{
Organizations: idpv1alpha1.GitHubOrganizationsSpec{
Policy: ptr.To(idpv1alpha1.GitHubAllowedAuthOrganizationsPolicyAllGitHubUsers),
},
},
Client: idpv1alpha1.GitHubClientSpec{
SecretName: "name-of-a-secret",
},
},
wantSpec: idpv1alpha1.GitHubIdentityProviderSpec{
GitHubAPI: idpv1alpha1.GitHubAPIConfig{
Host: ptr.To("github.com"),
},
AllowAuthentication: idpv1alpha1.GitHubAllowAuthenticationSpec{
Organizations: idpv1alpha1.GitHubOrganizationsSpec{
Policy: ptr.To(idpv1alpha1.GitHubAllowedAuthOrganizationsPolicyAllGitHubUsers),
},
},
Claims: idpv1alpha1.GitHubClaims{
Username: ptr.To(idpv1alpha1.GitHubUsernameLoginAndID),
Groups: ptr.To(idpv1alpha1.GitHubUseTeamSlugForGroupName),
},
Client: idpv1alpha1.GitHubClientSpec{
SecretName: "name-of-a-secret",
},
},
},
{
name: fmt.Sprintf(
"cannot set AllowedOrganizationsPolicy=%s and set AllowedOrganizations",
string(idpv1alpha1.GitHubAllowedAuthOrganizationsPolicyAllGitHubUsers)),
usesCELValidation: true,
inputSpec: idpv1alpha1.GitHubIdentityProviderSpec{
AllowAuthentication: idpv1alpha1.GitHubAllowAuthenticationSpec{
Organizations: idpv1alpha1.GitHubOrganizationsSpec{
Allowed: []string{
"some-org",
},
Policy: ptr.To(idpv1alpha1.GitHubAllowedAuthOrganizationsPolicyAllGitHubUsers),
},
},
Client: idpv1alpha1.GitHubClientSpec{
SecretName: "name-of-a-secret",
},
},
wantErr: "spec.allowAuthentication.organizations.policy must be 'OnlyUsersFromAllowedOrganizations' when spec.allowAuthentication.organizations.allowed has organizations listed",
},
{
name: fmt.Sprintf("spec.allowAuthentication.organizations.policy must be '%s' when spec.allowAuthentication.organizations.allowed is empty (nil)", string(idpv1alpha1.GitHubAllowedAuthOrganizationsPolicyAllGitHubUsers)),
usesCELValidation: true,
inputSpec: idpv1alpha1.GitHubIdentityProviderSpec{
AllowAuthentication: idpv1alpha1.GitHubAllowAuthenticationSpec{
Organizations: idpv1alpha1.GitHubOrganizationsSpec{
Policy: ptr.To(idpv1alpha1.GitHubAllowedAuthOrganizationsPolicyOnlyUsersFromAllowedOrganizations),
},
},
Client: idpv1alpha1.GitHubClientSpec{
SecretName: "name-of-a-secret",
},
},
wantErr: "spec.allowAuthentication.organizations.policy must be 'AllGitHubUsers' when spec.allowAuthentication.organizations.allowed is empty",
},
{
name: fmt.Sprintf("spec.allowAuthentication.organizations.policy must be '%s' when spec.allowAuthentication.organizations.allowed is empty", string(idpv1alpha1.GitHubAllowedAuthOrganizationsPolicyAllGitHubUsers)),
usesCELValidation: true,
inputSpec: idpv1alpha1.GitHubIdentityProviderSpec{
AllowAuthentication: idpv1alpha1.GitHubAllowAuthenticationSpec{
Organizations: idpv1alpha1.GitHubOrganizationsSpec{
Allowed: []string{},
Policy: ptr.To(idpv1alpha1.GitHubAllowedAuthOrganizationsPolicyOnlyUsersFromAllowedOrganizations),
},
},
Client: idpv1alpha1.GitHubClientSpec{
SecretName: "name-of-a-secret",
},
},
wantErr: "spec.allowAuthentication.organizations.policy must be 'AllGitHubUsers' when spec.allowAuthentication.organizations.allowed is empty",
},
{
name: "spec.client.secretName in body should be at least 1 chars long",
inputSpec: idpv1alpha1.GitHubIdentityProviderSpec{},
wantErr: "spec.client.secretName in body should be at least 1 chars long",
},
{
name: "spec.githubAPI.host in body should be at least 1 chars long",
inputSpec: idpv1alpha1.GitHubIdentityProviderSpec{
GitHubAPI: idpv1alpha1.GitHubAPIConfig{
Host: ptr.To(""),
},
AllowAuthentication: idpv1alpha1.GitHubAllowAuthenticationSpec{
Organizations: idpv1alpha1.GitHubOrganizationsSpec{
Policy: ptr.To(idpv1alpha1.GitHubAllowedAuthOrganizationsPolicyAllGitHubUsers),
},
},
Client: idpv1alpha1.GitHubClientSpec{
SecretName: "name-of-a-secret",
},
},
wantErr: "spec.githubAPI.host in body should be at least 1 chars long",
},
{
name: "duplicates not permitted in spec.allowAuthentication.organizations.allowed",
inputSpec: idpv1alpha1.GitHubIdentityProviderSpec{
AllowAuthentication: idpv1alpha1.GitHubAllowAuthenticationSpec{
Organizations: idpv1alpha1.GitHubOrganizationsSpec{
Allowed: []string{
"org1",
"org1",
},
Policy: ptr.To(idpv1alpha1.GitHubAllowedAuthOrganizationsPolicyOnlyUsersFromAllowedOrganizations),
},
},
Client: idpv1alpha1.GitHubClientSpec{
SecretName: "name-of-a-secret",
},
},
wantErr: `spec.allowAuthentication.organizations.allowed[1]: Duplicate value: "org1"`,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
if tt.usesCELValidation && skipCELTests {
t.Skip("CEL is not available for current K8s version")
}
input := &idpv1alpha1.GitHubIdentityProvider{
ObjectMeta: metav1.ObjectMeta{
GenerateName: generateGitHubNamePrefix,
},
Spec: tt.inputSpec,
}
outputGitHubIDP, err := gitHubIDPClient.Create(ctx, input, metav1.CreateOptions{})
if tt.wantErr == "" {
require.NoError(t, err)
require.Equal(t, tt.wantSpec, outputGitHubIDP.Spec)
} else {
require.ErrorContains(t, err, tt.wantErr)
}
})
}
}
func TestGitHubIDPSetsDefaultsWithKubectl_Parallel(t *testing.T) {
env := testlib.IntegrationEnv(t)
adminClient := testlib.NewKubernetesClientset(t)
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute)
t.Cleanup(cancel)
namespaceClient := adminClient.CoreV1().Namespaces()
ns, err := namespaceClient.Create(ctx, &corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
GenerateName: generateGitHubNamePrefix,
},
}, metav1.CreateOptions{})
require.NoError(t, err)
t.Cleanup(func() {
require.NoError(t, namespaceClient.Delete(ctx, ns.Name, metav1.DeleteOptions{}))
})
t.Logf("Created namespace %s", ns.Name)
idpName := generateGitHubNamePrefix + testlib.RandHex(t, 16)
githubIDPYaml := []byte(here.Doc(fmt.Sprintf(`
---
apiVersion: idp.supervisor.%s/v1alpha1
kind: GitHubIdentityProvider
metadata:
name: %s
namespace: %s
spec:
allowAuthentication:
organizations:
policy: AllGitHubUsers
client:
secretName: any-secret-name`, env.APIGroupSuffix, idpName, ns.Name)))
githubIDPYamlFilepath := filepath.Join(t.TempDir(), "github-idp.yaml")
require.NoError(t, os.WriteFile(githubIDPYamlFilepath, githubIDPYaml, 0600))
stdOut, stdErr := runTestKubectlCommand(t, "create", "-f", githubIDPYamlFilepath)
require.Equal(t, fmt.Sprintf("githubidentityprovider.idp.supervisor.%s/%s created\n", env.APIGroupSuffix, idpName), stdOut)
require.Empty(t, stdErr)
gitHubIDPClient := testlib.NewSupervisorClientset(t).IDPV1alpha1().GitHubIdentityProviders(ns.Name)
idp, err := gitHubIDPClient.Get(ctx, idpName, metav1.GetOptions{})
require.NoError(t, err)
require.Equal(t, idpv1alpha1.GitHubIdentityProviderSpec{
GitHubAPI: idpv1alpha1.GitHubAPIConfig{
Host: ptr.To("github.com"),
},
Claims: idpv1alpha1.GitHubClaims{
Username: ptr.To(idpv1alpha1.GitHubUsernameLoginAndID),
Groups: ptr.To(idpv1alpha1.GitHubUseTeamSlugForGroupName),
},
AllowAuthentication: idpv1alpha1.GitHubAllowAuthenticationSpec{
Organizations: idpv1alpha1.GitHubOrganizationsSpec{
Policy: ptr.To(idpv1alpha1.GitHubAllowedAuthOrganizationsPolicyAllGitHubUsers),
},
},
Client: idpv1alpha1.GitHubClientSpec{
SecretName: "any-secret-name",
},
}, idp.Spec)
}
func TestGitHubIDPPhaseAndConditions_Parallel(t *testing.T) {
// These operations must be performed in the Supervisor's namespace so that the controller can find GitHubIdentityProvider
supervisorNamespace := testlib.IntegrationEnv(t).SupervisorNamespace
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute)
t.Cleanup(cancel)
kubernetesClient := testlib.NewKubernetesClientset(t)
secretsClient := kubernetesClient.CoreV1().Secrets(supervisorNamespace)
gitHubIDPClient := testlib.NewSupervisorClientset(t).IDPV1alpha1().GitHubIdentityProviders(supervisorNamespace)
happySecretName := generateGitHubNamePrefix + testlib.RandHex(t, 16)
invalidSecretName := generateGitHubNamePrefix + testlib.RandHex(t, 16)
tests := []struct {
name string
secrets []*corev1.Secret // Secrets will be created first, and the first secret found will be listed as the configured GitHub Client secret
idps []*idpv1alpha1.GitHubIdentityProvider
wantPhase idpv1alpha1.GitHubIdentityProviderPhase
wantConditions []*metav1.Condition
}{
{
name: "Happy Path",
secrets: []*corev1.Secret{
{
ObjectMeta: metav1.ObjectMeta{
Name: happySecretName,
},
Type: "secrets.pinniped.dev/github-client",
Data: map[string][]byte{
"clientID": []byte("foo"),
"clientSecret": []byte("bar"),
},
},
},
idps: []*idpv1alpha1.GitHubIdentityProvider{
{
Spec: idpv1alpha1.GitHubIdentityProviderSpec{
GitHubAPI: idpv1alpha1.GitHubAPIConfig{
Host: ptr.To("github.com"),
},
AllowAuthentication: idpv1alpha1.GitHubAllowAuthenticationSpec{
Organizations: idpv1alpha1.GitHubOrganizationsSpec{
Policy: ptr.To(idpv1alpha1.GitHubAllowedAuthOrganizationsPolicyAllGitHubUsers),
},
},
},
},
},
wantPhase: idpv1alpha1.GitHubPhaseReady,
wantConditions: []*metav1.Condition{
{
Type: "ClaimsValid",
Status: metav1.ConditionTrue,
Reason: "Success",
Message: "spec.claims are valid",
},
{
Type: "ClientCredentialsSecretValid",
Status: metav1.ConditionTrue,
Reason: "Success",
Message: fmt.Sprintf("clientID and clientSecret have been read from spec.client.SecretName (%q)", happySecretName),
},
{
Type: "GitHubConnectionValid",
Status: metav1.ConditionTrue,
Reason: "Success",
Message: `probed connection to "api.github.com:443" for spec.githubAPI.host ("github.com"): host is reachable and TLS verification succeeds`,
},
{
Type: "HostValid",
Status: metav1.ConditionTrue,
Reason: "Success",
Message: `spec.githubAPI.host ("github.com") is valid`,
},
{
Type: "OrganizationsPolicyValid",
Status: metav1.ConditionTrue,
Reason: "Success",
Message: `spec.allowAuthentication.organizations.policy ("AllGitHubUsers") is valid`,
},
{
Type: "TLSConfigurationValid",
Status: metav1.ConditionTrue,
Reason: "Success",
Message: "spec.githubAPI.tls is valid: no TLS configuration provided: using default root CA bundle from container image",
},
},
},
{
name: "Invalid Client Secret",
secrets: []*corev1.Secret{
{
Type: "secrets.pinniped.dev/github-client",
ObjectMeta: metav1.ObjectMeta{
Name: invalidSecretName,
},
},
},
idps: []*idpv1alpha1.GitHubIdentityProvider{
{
Spec: idpv1alpha1.GitHubIdentityProviderSpec{
GitHubAPI: idpv1alpha1.GitHubAPIConfig{
Host: ptr.To("api.github.com"),
},
AllowAuthentication: idpv1alpha1.GitHubAllowAuthenticationSpec{
Organizations: idpv1alpha1.GitHubOrganizationsSpec{
Policy: ptr.To(idpv1alpha1.GitHubAllowedAuthOrganizationsPolicyAllGitHubUsers),
},
},
Client: idpv1alpha1.GitHubClientSpec{
SecretName: invalidSecretName,
},
},
},
},
wantPhase: idpv1alpha1.GitHubPhaseError,
wantConditions: []*metav1.Condition{
{
Type: "ClaimsValid",
Status: metav1.ConditionTrue,
Reason: "Success",
Message: "spec.claims are valid",
},
{
Type: "ClientCredentialsSecretValid",
Status: metav1.ConditionFalse,
Reason: "SecretNotFound",
Message: fmt.Sprintf(`missing key "clientID": secret from spec.client.SecretName (%q) must be found in namespace %q with type "secrets.pinniped.dev/github-client" and keys "clientID" and "clientSecret"`,
invalidSecretName,
supervisorNamespace),
},
{
Type: "GitHubConnectionValid",
Status: metav1.ConditionTrue,
Reason: "Success",
Message: `probed connection to "api.github.com:443" for spec.githubAPI.host ("api.github.com"): host is reachable and TLS verification succeeds`,
},
{
Type: "HostValid",
Status: metav1.ConditionTrue,
Reason: "Success",
Message: `spec.githubAPI.host ("api.github.com") is valid`,
},
{
Type: "OrganizationsPolicyValid",
Status: metav1.ConditionTrue,
Reason: "Success",
Message: `spec.allowAuthentication.organizations.policy ("AllGitHubUsers") is valid`,
},
{
Type: "TLSConfigurationValid",
Status: metav1.ConditionTrue,
Reason: "Success",
Message: `spec.githubAPI.tls is valid: no TLS configuration provided: using default root CA bundle from container image`,
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
var secretName string
for _, secret := range tt.secrets {
secret.GenerateName = generateGitHubNamePrefix
created, err := secretsClient.Create(ctx, secret, metav1.CreateOptions{})
require.NoError(t, err)
t.Cleanup(func() {
err := secretsClient.Delete(ctx, created.Name, metav1.DeleteOptions{})
require.NoError(t, err)
})
if secretName == "" {
secretName = created.Name
}
}
for _, idp := range tt.idps {
idp.Name = ""
idp.GenerateName = generateGitHubNamePrefix
idp.Spec.Client.SecretName = secretName
created, err := gitHubIDPClient.Create(ctx, idp, metav1.CreateOptions{})
require.NoError(t, err)
t.Cleanup(func() {
err := gitHubIDPClient.Delete(ctx, created.Name, metav1.DeleteOptions{})
require.NoError(t, err)
})
testlib.WaitForGitHubIDPPhase(ctx, t, gitHubIDPClient, created.Name, tt.wantPhase)
testlib.WaitForGitHubIdentityProviderStatusConditions(ctx, t, gitHubIDPClient, created.Name, tt.wantConditions)
}
})
}
}
func TestGitHubIDPInWrongNamespace_Parallel(t *testing.T) {
// The GitHubIdentityProvider must be in the same namespace as the controller
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute)
t.Cleanup(cancel)
kubernetesClient := testlib.NewKubernetesClientset(t)
namespaceClient := kubernetesClient.CoreV1().Namespaces()
otherNamespace, err := namespaceClient.Create(ctx, &corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
GenerateName: generateGitHubNamePrefix,
},
}, metav1.CreateOptions{})
require.NoError(t, err)
t.Cleanup(func() {
require.NoError(t, namespaceClient.Delete(ctx, otherNamespace.Name, metav1.DeleteOptions{}))
})
gitHubIDPClient := testlib.NewSupervisorClientset(t).IDPV1alpha1().GitHubIdentityProviders(otherNamespace.Name)
idp := &idpv1alpha1.GitHubIdentityProvider{
ObjectMeta: metav1.ObjectMeta{
GenerateName: generateGitHubNamePrefix,
Namespace: otherNamespace.Name,
},
Spec: idpv1alpha1.GitHubIdentityProviderSpec{
GitHubAPI: idpv1alpha1.GitHubAPIConfig{
Host: ptr.To("github.com"),
},
AllowAuthentication: idpv1alpha1.GitHubAllowAuthenticationSpec{
Organizations: idpv1alpha1.GitHubOrganizationsSpec{
Policy: ptr.To(idpv1alpha1.GitHubAllowedAuthOrganizationsPolicyAllGitHubUsers),
},
},
Client: idpv1alpha1.GitHubClientSpec{
SecretName: "does-not-matter",
},
},
}
createdIDP, err := gitHubIDPClient.Create(ctx, idp, metav1.CreateOptions{})
require.NoError(t, err)
t.Cleanup(func() {
err := gitHubIDPClient.Delete(ctx, createdIDP.Name, metav1.DeleteOptions{})
require.NoError(t, err)
})
// We require that there's never an error
// ... and that the status phase is never anything but Pending
// ... and that there are no status conditions
require.Never(t, func() bool {
idp, err := gitHubIDPClient.Get(ctx, createdIDP.Name, metav1.GetOptions{})
return err != nil && idp.Status.Phase != idpv1alpha1.GitHubPhasePending && len(idp.Status.Conditions) > 0
}, 2*time.Minute, 10*time.Second)
}
func TestGitHubIDPSecretInOtherNamespace_Parallel(t *testing.T) {
// The GitHubIdentityProvider must be in the same namespace as the controller
supervisorNamespace := testlib.IntegrationEnv(t).SupervisorNamespace
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute)
t.Cleanup(cancel)
kubernetesClient := testlib.NewKubernetesClientset(t)
gitHubIDPClient := testlib.NewSupervisorClientset(t).IDPV1alpha1().GitHubIdentityProviders(supervisorNamespace)
namespaceClient := kubernetesClient.CoreV1().Namespaces()
otherNamespace, err := namespaceClient.Create(ctx, &corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
GenerateName: generateGitHubNamePrefix,
},
}, metav1.CreateOptions{})
require.NoError(t, err)
t.Cleanup(func() {
require.NoError(t, namespaceClient.Delete(ctx, otherNamespace.Name, metav1.DeleteOptions{}))
})
secretsClient := kubernetesClient.CoreV1().Secrets(otherNamespace.Name)
secret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
GenerateName: generateGitHubNamePrefix,
Namespace: otherNamespace.Name,
},
Type: "secrets.pinniped.dev/github-client",
Data: map[string][]byte{
"clientID": []byte("foo"),
"clientSecret": []byte("bar"),
},
}
// This secret will be cleaned up when its namespace is deleted
createdSecret, err := secretsClient.Create(ctx, secret, metav1.CreateOptions{})
require.NoError(t, err)
idp := &idpv1alpha1.GitHubIdentityProvider{
ObjectMeta: metav1.ObjectMeta{
GenerateName: generateGitHubNamePrefix,
Namespace: supervisorNamespace,
},
Spec: idpv1alpha1.GitHubIdentityProviderSpec{
GitHubAPI: idpv1alpha1.GitHubAPIConfig{
Host: ptr.To("github.com"),
},
AllowAuthentication: idpv1alpha1.GitHubAllowAuthenticationSpec{
Organizations: idpv1alpha1.GitHubOrganizationsSpec{
Policy: ptr.To(idpv1alpha1.GitHubAllowedAuthOrganizationsPolicyAllGitHubUsers),
},
},
Client: idpv1alpha1.GitHubClientSpec{
SecretName: createdSecret.Name,
},
},
}
created, err := gitHubIDPClient.Create(ctx, idp, metav1.CreateOptions{})
require.NoError(t, err)
t.Cleanup(func() {
err := gitHubIDPClient.Delete(ctx, created.Name, metav1.DeleteOptions{})
require.NoError(t, err)
})
testlib.WaitForGitHubIDPPhase(ctx, t, gitHubIDPClient, created.Name, idpv1alpha1.GitHubPhaseError)
testlib.WaitForGitHubIdentityProviderStatusConditions(ctx, t, gitHubIDPClient, created.Name, []*metav1.Condition{
{
Type: "ClaimsValid",
Status: metav1.ConditionTrue,
Reason: "Success",
Message: "spec.claims are valid",
},
{
Type: "ClientCredentialsSecretValid",
Status: metav1.ConditionFalse,
Reason: "SecretNotFound",
Message: fmt.Sprintf(`secret %q not found: secret from spec.client.SecretName (%q) must be found in namespace %q with type "secrets.pinniped.dev/github-client" and keys "clientID" and "clientSecret"`,
idp.Spec.Client.SecretName,
idp.Spec.Client.SecretName,
supervisorNamespace),
},
{
Type: "GitHubConnectionValid",
Status: metav1.ConditionTrue,
Reason: "Success",
Message: `probed connection to "api.github.com:443" for spec.githubAPI.host ("github.com"): host is reachable and TLS verification succeeds`,
},
{
Type: "HostValid",
Status: metav1.ConditionTrue,
Reason: "Success",
Message: `spec.githubAPI.host ("github.com") is valid`,
},
{
Type: "OrganizationsPolicyValid",
Status: metav1.ConditionTrue,
Reason: "Success",
Message: `spec.allowAuthentication.organizations.policy ("AllGitHubUsers") is valid`,
},
{
Type: "TLSConfigurationValid",
Status: metav1.ConditionTrue,
Reason: "Success",
Message: "spec.githubAPI.tls is valid: no TLS configuration provided: using default root CA bundle from container image",
},
})
}
func TestGitHubIDPTooManyOrganizationsStaticValidationOnCreate_Parallel(t *testing.T) {
adminClient := testlib.NewKubernetesClientset(t)
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute)
t.Cleanup(cancel)
namespaceClient := adminClient.CoreV1().Namespaces()
ns, err := namespaceClient.Create(ctx, &corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
GenerateName: generateGitHubNamePrefix,
},
}, metav1.CreateOptions{})
require.NoError(t, err)
t.Cleanup(func() {
require.NoError(t, namespaceClient.Delete(ctx, ns.Name, metav1.DeleteOptions{}))
})
gitHubIDPClient := testlib.NewSupervisorClientset(t).IDPV1alpha1().GitHubIdentityProviders(ns.Name)
input := &idpv1alpha1.GitHubIdentityProvider{
ObjectMeta: metav1.ObjectMeta{
GenerateName: generateGitHubNamePrefix,
},
Spec: idpv1alpha1.GitHubIdentityProviderSpec{
AllowAuthentication: idpv1alpha1.GitHubAllowAuthenticationSpec{
Organizations: idpv1alpha1.GitHubOrganizationsSpec{
Allowed: func() []string {
orgs := make([]string, 100)
for i := range 100 {
orgs[i] = fmt.Sprintf("org-%d", i)
}
return orgs
}(),
Policy: ptr.To(idpv1alpha1.GitHubAllowedAuthOrganizationsPolicyOnlyUsersFromAllowedOrganizations),
},
},
Client: idpv1alpha1.GitHubClientSpec{
SecretName: "name-of-a-secret",
},
},
}
_, err = gitHubIDPClient.Create(ctx, input, metav1.CreateOptions{})
wantErr := "spec.allowAuthentication.organizations.allowed: Invalid value: 100: spec.allowAuthentication.organizations.allowed in body should have at most 64 items"
if testutil.KubeServerMinorVersionAtLeastInclusive(t, adminClient.Discovery(), 24) {
wantErr = "spec.allowAuthentication.organizations.allowed: Too many: 100: must have at most 64 items"
}
require.ErrorContains(t, err, wantErr)
}