Add integration test: supervisor TLS termination and SNI virtual hosting

- Also reduce the minimum allowed TLS version to v1.2, because v1.3
  is not yet supported by some common clients, e.g. the default MacOS
  curl command
This commit is contained in:
Ryan Richard 2020-10-27 14:57:25 -07:00
parent eeb110761e
commit 1f1b6c884e
6 changed files with 261 additions and 88 deletions

View File

@ -206,10 +206,11 @@ func run(serverInstallationNamespace string, cfg *supervisor.Config) error {
//nolint: gosec // Intentionally binding to all network interfaces.
httpsListener, err := tls.Listen("tcp", ":443", &tls.Config{
MinVersion: tls.VersionTLS13,
MinVersion: tls.VersionTLS12, // Allow v1.2 because clients like the default `curl` on MacOS don't support 1.3 yet.
GetCertificate: func(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
klog.InfoS("GetCertificate called", "info.ServerName", info.ServerName)
return dynamicTLSCertProvider.GetTLSCert(strings.ToLower(info.ServerName)), nil
cert := dynamicTLSCertProvider.GetTLSCert(strings.ToLower(info.ServerName))
klog.InfoS("GetCertificate called for port 443", "info.ServerName", info.ServerName, "foundCert", cert != nil)
return cert, nil
},
})
if err != nil {

View File

@ -282,6 +282,7 @@ export PINNIPED_TEST_SUPERVISOR_NAMESPACE=${supervisor_namespace}
export PINNIPED_TEST_SUPERVISOR_APP_NAME=${supervisor_app_name}
export PINNIPED_TEST_SUPERVISOR_CUSTOM_LABELS='${supervisor_custom_labels}'
export PINNIPED_TEST_SUPERVISOR_HTTP_ADDRESS="127.0.0.1:12345"
export PINNIPED_TEST_SUPERVISOR_HTTPS_ADDRESS="localhost:12344"
export PINNIPED_TEST_CLI_OIDC_ISSUER=http://127.0.0.1:12346/dex
export PINNIPED_TEST_CLI_OIDC_CLIENT_ID=pinniped-cli
export PINNIPED_TEST_CLI_OIDC_LOCALHOST_PORT=48095

View File

@ -7,9 +7,11 @@ import (
"context"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"encoding/json"
"fmt"
"io/ioutil"
"net"
"net/http"
"net/url"
"strings"
@ -18,42 +20,93 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
"go.pinniped.dev/generated/1.19/apis/config/v1alpha1"
pinnipedclientset "go.pinniped.dev/generated/1.19/client/clientset/versioned"
"go.pinniped.dev/internal/certauthority"
"go.pinniped.dev/internal/here"
"go.pinniped.dev/test/library"
)
func TestSupervisorTLSTerminationWithSNI(t *testing.T) {
env := library.IntegrationEnv(t)
pinnipedClient := library.NewPinnipedClientset(t)
kubeClient := library.NewClientset(t)
ns := env.SupervisorNamespace
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
defer cancel()
temporarilyRemoveAllOIDCProviderConfigs(ctx, t, ns, pinnipedClient)
scheme := "https"
address := env.SupervisorHTTPSAddress // hostname and port for direct access to the supervisor's port 443
hostname1 := strings.Split(address, ":")[0]
issuer1 := fmt.Sprintf("%s://%s/issuer1", scheme, address)
sniCertificateSecretName1 := "integration-test-sni-cert-1"
// Create an OIDCProviderConfig with an sniCertificateSecretName.
oidcProviderConfig1 := library.CreateTestOIDCProvider(ctx, t, issuer1, sniCertificateSecretName1)
requireStatus(t, pinnipedClient, oidcProviderConfig1.Namespace, oidcProviderConfig1.Name, v1alpha1.SuccessOIDCProviderStatus)
// The sniCertificateSecretName Secret does not exist, so the endpoints should fail with TLS errors.
requireEndpointHasTLSErrorBecauseCertificatesAreNotReady(t, issuer1)
// Create the Secret.
ca1 := createSNICertificateSecret(ctx, t, ns, hostname1, sniCertificateSecretName1, kubeClient)
// Now that the Secret exists, we should be able to access the endpoints by hostname using the CA.
_ = requireDiscoveryEndpointsAreWorking(t, scheme, address, string(ca1.Bundle()), issuer1, nil)
// Update the config to take away the sniCertificateSecretName.
sniCertificateSecretName1update := "integration-test-sni-cert-1-update"
oidcProviderConfig1LatestVersion, err := pinnipedClient.ConfigV1alpha1().OIDCProviderConfigs(ns).Get(ctx, oidcProviderConfig1.Name, metav1.GetOptions{})
require.NoError(t, err)
oidcProviderConfig1LatestVersion.Spec.SNICertificateSecretName = sniCertificateSecretName1update
_, err = pinnipedClient.ConfigV1alpha1().OIDCProviderConfigs(ns).Update(ctx, oidcProviderConfig1LatestVersion, metav1.UpdateOptions{})
require.NoError(t, err)
// The the endpoints should fail with TLS errors again.
requireEndpointHasTLSErrorBecauseCertificatesAreNotReady(t, issuer1)
// Create a Secret at the updated name.
ca1update := createSNICertificateSecret(ctx, t, ns, hostname1, sniCertificateSecretName1update, kubeClient)
// Now that the Secret exists at the new name, we should be able to access the endpoints by hostname using the CA.
_ = requireDiscoveryEndpointsAreWorking(t, scheme, address, string(ca1update.Bundle()), issuer1, nil)
// To test SNI virtual hosting, send requests to discovery endpoints when the public address is different from the issuer name.
hostname2 := "some-issuer-host-and-port-that-doesnt-match-public-supervisor-address.com"
hostnamePort2 := "2684"
issuer2 := fmt.Sprintf("%s://%s:%s/issuer2", scheme, hostname2, hostnamePort2)
sniCertificateSecretName2 := "integration-test-sni-cert-2"
// Create an OIDCProviderConfig with an sniCertificateSecretName.
oidcProviderConfig2 := library.CreateTestOIDCProvider(ctx, t, issuer2, sniCertificateSecretName2)
requireStatus(t, pinnipedClient, oidcProviderConfig2.Namespace, oidcProviderConfig2.Name, v1alpha1.SuccessOIDCProviderStatus)
// Create the Secret.
ca2 := createSNICertificateSecret(ctx, t, ns, hostname2, sniCertificateSecretName2, kubeClient)
// Now that the Secret exists, we should be able to access the endpoints by hostname using the CA.
_ = requireDiscoveryEndpointsAreWorking(t, scheme, hostname2+":"+hostnamePort2, string(ca2.Bundle()), issuer2, map[string]string{
hostname2 + ":" + hostnamePort2: address,
})
}
func TestSupervisorOIDCDiscovery(t *testing.T) {
env := library.IntegrationEnv(t)
client := library.NewPinnipedClientset(t)
ns := env.SupervisorNamespace
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
defer cancel()
// Temporarily remove any existing OIDCProviderConfigs from the cluster so we can test from a clean slate.
originalConfigList, err := client.ConfigV1alpha1().OIDCProviderConfigs(ns).List(ctx, metav1.ListOptions{})
require.NoError(t, err)
for _, config := range originalConfigList.Items {
err := client.ConfigV1alpha1().OIDCProviderConfigs(ns).Delete(ctx, config.Name, metav1.DeleteOptions{})
require.NoError(t, err)
}
// When this test has finished, recreate any OIDCProviderConfigs that had existed on the cluster before this test.
t.Cleanup(func() {
cleanupCtx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
defer cancel()
for _, config := range originalConfigList.Items {
thisConfig := config
thisConfig.ResourceVersion = "" // Get rid of resource version since we can't create an object with one.
_, err := client.ConfigV1alpha1().OIDCProviderConfigs(ns).Create(cleanupCtx, &thisConfig, metav1.CreateOptions{})
require.NoError(t, err)
}
})
temporarilyRemoveAllOIDCProviderConfigs(ctx, t, ns, client)
tests := []struct {
Scheme string
@ -61,7 +114,7 @@ func TestSupervisorOIDCDiscovery(t *testing.T) {
CABundle string
}{
{Scheme: "http", Address: env.SupervisorHTTPAddress},
{Scheme: "https", Address: env.SupervisorHTTPSAddress, CABundle: env.SupervisorHTTPSCABundle},
{Scheme: "https", Address: env.SupervisorHTTPSIngressAddress, CABundle: env.SupervisorHTTPSIngressCABundle},
}
for _, test := range tests {
@ -98,7 +151,7 @@ func TestSupervisorOIDCDiscovery(t *testing.T) {
// When multiple OIDCProviderConfigs exist at the same time they each serve a unique discovery endpoint.
config3, jwks3 := requireCreatingOIDCProviderConfigCausesDiscoveryEndpointsToAppear(ctx, t, scheme, addr, caBundle, issuer3, client)
config4, jwks4 := requireCreatingOIDCProviderConfigCausesDiscoveryEndpointsToAppear(ctx, t, scheme, addr, caBundle, issuer4, client)
requireDiscoveryEndpointsAreWorking(t, scheme, addr, caBundle, issuer3) // discovery for issuer3 is still working after issuer4 started working
requireDiscoveryEndpointsAreWorking(t, scheme, addr, caBundle, issuer3, nil) // discovery for issuer3 is still working after issuer4 started working
// The auto-created JWK's were different from each other.
require.NotEqual(t, jwks3.Keys[0]["x"], jwks4.Keys[0]["x"])
require.NotEqual(t, jwks3.Keys[0]["y"], jwks4.Keys[0]["y"])
@ -106,7 +159,7 @@ func TestSupervisorOIDCDiscovery(t *testing.T) {
// Editing a provider to change the issuer name updates the endpoints that are being served.
updatedConfig4 := editOIDCProviderConfigIssuerName(t, config4, client, ns, issuer5)
requireDiscoveryEndpointsAreNotFound(t, scheme, addr, caBundle, issuer4)
jwks5 := requireDiscoveryEndpointsAreWorking(t, scheme, addr, caBundle, issuer5)
jwks5 := requireDiscoveryEndpointsAreWorking(t, scheme, addr, caBundle, issuer5, nil)
// The JWK did not change when the issuer name was updated.
require.Equal(t, jwks4.Keys[0], jwks5.Keys[0])
@ -116,14 +169,14 @@ func TestSupervisorOIDCDiscovery(t *testing.T) {
// When the same issuer is added twice, both issuers are marked as duplicates, and neither provider is serving.
config6Duplicate1, _ := requireCreatingOIDCProviderConfigCausesDiscoveryEndpointsToAppear(ctx, t, scheme, addr, caBundle, issuer6, client)
config6Duplicate2 := library.CreateTestOIDCProvider(ctx, t, issuer6)
config6Duplicate2 := library.CreateTestOIDCProvider(ctx, t, issuer6, "")
requireStatus(t, client, ns, config6Duplicate1.Name, v1alpha1.DuplicateOIDCProviderStatus)
requireStatus(t, client, ns, config6Duplicate2.Name, v1alpha1.DuplicateOIDCProviderStatus)
requireDiscoveryEndpointsAreNotFound(t, scheme, addr, caBundle, issuer6)
// If we delete the first duplicate issuer, the second duplicate issuer starts serving.
requireDelete(t, client, ns, config6Duplicate1.Name)
requireWellKnownEndpointIsWorking(t, scheme, addr, caBundle, issuer6)
requireWellKnownEndpointIsWorking(t, scheme, addr, caBundle, issuer6, nil)
requireStatus(t, client, ns, config6Duplicate2.Name, v1alpha1.SuccessOIDCProviderStatus)
// When we finally delete all issuers, the endpoint should be down.
@ -135,13 +188,74 @@ func TestSupervisorOIDCDiscovery(t *testing.T) {
requireDeletingOIDCProviderConfigCausesDiscoveryEndpointsToDisappear(t, config7, client, ns, scheme, addr, caBundle, issuer7)
// When we create a provider with an invalid issuer, the status is set to invalid.
badConfig := library.CreateTestOIDCProvider(ctx, t, badIssuer)
badConfig := library.CreateTestOIDCProvider(ctx, t, badIssuer, "")
requireStatus(t, client, ns, badConfig.Name, v1alpha1.InvalidOIDCProviderStatus)
requireDiscoveryEndpointsAreNotFound(t, scheme, addr, caBundle, badIssuer)
requireDeletingOIDCProviderConfigCausesDiscoveryEndpointsToDisappear(t, badConfig, client, ns, scheme, addr, caBundle, badIssuer)
}
}
func createSNICertificateSecret(ctx context.Context, t *testing.T, ns string, hostname string, sniCertificateSecretName string, kubeClient kubernetes.Interface) *certauthority.CA {
// Create a CA.
ca, err := certauthority.New(pkix.Name{CommonName: "Acme Corp"}, 1000*time.Hour)
require.NoError(t, err)
// Using the CA, create a TLS server cert.
tlsCert, err := ca.Issue(pkix.Name{CommonName: hostname}, []string{hostname}, 1000*time.Hour)
require.NoError(t, err)
// Write the serving cert to the SNI secret.
tlsCertChainPEM, tlsPrivateKeyPEM, err := certauthority.ToPEM(tlsCert)
require.NoError(t, err)
secret := corev1.Secret{
TypeMeta: metav1.TypeMeta{},
ObjectMeta: metav1.ObjectMeta{
Name: sniCertificateSecretName,
Namespace: ns,
},
StringData: map[string]string{
"tls.crt": string(tlsCertChainPEM),
"tls.key": string(tlsPrivateKeyPEM),
},
}
_, err = kubeClient.CoreV1().Secrets(ns).Create(ctx, &secret, metav1.CreateOptions{})
require.NoError(t, err)
// Delete the Secret when the test ends.
t.Cleanup(func() {
t.Helper()
deleteCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
err := kubeClient.CoreV1().Secrets(ns).Delete(deleteCtx, sniCertificateSecretName, metav1.DeleteOptions{})
require.NoError(t, err)
})
return ca
}
func temporarilyRemoveAllOIDCProviderConfigs(ctx context.Context, t *testing.T, ns string, client pinnipedclientset.Interface) {
// Temporarily remove any existing OIDCProviderConfigs from the cluster so we can test from a clean slate.
originalConfigList, err := client.ConfigV1alpha1().OIDCProviderConfigs(ns).List(ctx, metav1.ListOptions{})
require.NoError(t, err)
for _, config := range originalConfigList.Items {
err := client.ConfigV1alpha1().OIDCProviderConfigs(ns).Delete(ctx, config.Name, metav1.DeleteOptions{})
require.NoError(t, err)
}
// When this test has finished, recreate any OIDCProviderConfigs that had existed on the cluster before this test.
t.Cleanup(func() {
cleanupCtx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
defer cancel()
for _, config := range originalConfigList.Items {
thisConfig := config
thisConfig.ResourceVersion = "" // Get rid of resource version since we can't create an object with one.
_, err := client.ConfigV1alpha1().OIDCProviderConfigs(ns).Create(cleanupCtx, &thisConfig, metav1.CreateOptions{})
require.NoError(t, err)
}
})
}
func jwksURLForIssuer(scheme, host, path string) string {
return fmt.Sprintf("%s://%s/%s/jwks.json", scheme, host, strings.TrimPrefix(path, "/"))
}
@ -160,14 +274,14 @@ func requireDiscoveryEndpointsAreNotFound(t *testing.T, supervisorScheme, superv
func requireEndpointNotFound(t *testing.T, url, host, caBundle string) {
t.Helper()
httpClient := newHTTPClient(caBundle)
httpClient := newHTTPClient(t, caBundle, nil)
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
defer cancel()
requestNonExistentPath, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
require.NoError(t, err)
requestNonExistentPath.Header.Add("Host", host)
requestNonExistentPath.Host = host
var response *http.Response
assert.Eventually(t, func() bool {
@ -180,6 +294,23 @@ func requireEndpointNotFound(t *testing.T, url, host, caBundle string) {
require.NoError(t, err)
}
func requireEndpointHasTLSErrorBecauseCertificatesAreNotReady(t *testing.T, url string) {
t.Helper()
httpClient := newHTTPClient(t, "", nil)
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
defer cancel()
request, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
require.NoError(t, err)
assert.Eventually(t, func() bool {
_, err = httpClient.Do(request) //nolint:bodyclose
return err != nil && strings.Contains(err.Error(), "tls: unrecognized name")
}, 10*time.Second, 200*time.Millisecond)
require.Error(t, err)
require.EqualError(t, err, `Get "https://localhost:12344/issuer1": remote error: tls: unrecognized name`)
}
func requireCreatingOIDCProviderConfigCausesDiscoveryEndpointsToAppear(
ctx context.Context,
t *testing.T,
@ -188,15 +319,15 @@ func requireCreatingOIDCProviderConfigCausesDiscoveryEndpointsToAppear(
client pinnipedclientset.Interface,
) (*v1alpha1.OIDCProviderConfig, *ExpectedJWKSResponseFormat) {
t.Helper()
newOIDCProviderConfig := library.CreateTestOIDCProvider(ctx, t, issuerName)
jwksResult := requireDiscoveryEndpointsAreWorking(t, supervisorScheme, supervisorAddress, supervisorCABundle, issuerName)
newOIDCProviderConfig := library.CreateTestOIDCProvider(ctx, t, issuerName, "")
jwksResult := requireDiscoveryEndpointsAreWorking(t, supervisorScheme, supervisorAddress, supervisorCABundle, issuerName, nil)
requireStatus(t, client, newOIDCProviderConfig.Namespace, newOIDCProviderConfig.Name, v1alpha1.SuccessOIDCProviderStatus)
return newOIDCProviderConfig, jwksResult
}
func requireDiscoveryEndpointsAreWorking(t *testing.T, supervisorScheme, supervisorAddress, supervisorCABundle, issuerName string) *ExpectedJWKSResponseFormat {
requireWellKnownEndpointIsWorking(t, supervisorScheme, supervisorAddress, supervisorCABundle, issuerName)
jwksResult := requireJWKSEndpointIsWorking(t, supervisorScheme, supervisorAddress, supervisorCABundle, issuerName)
func requireDiscoveryEndpointsAreWorking(t *testing.T, supervisorScheme, supervisorAddress, supervisorCABundle, issuerName string, dnsOverrides map[string]string) *ExpectedJWKSResponseFormat {
requireWellKnownEndpointIsWorking(t, supervisorScheme, supervisorAddress, supervisorCABundle, issuerName, dnsOverrides)
jwksResult := requireJWKSEndpointIsWorking(t, supervisorScheme, supervisorAddress, supervisorCABundle, issuerName, dnsOverrides)
return jwksResult
}
@ -220,11 +351,11 @@ func requireDeletingOIDCProviderConfigCausesDiscoveryEndpointsToDisappear(
requireDiscoveryEndpointsAreNotFound(t, supervisorScheme, supervisorAddress, supervisorCABundle, issuerName)
}
func requireWellKnownEndpointIsWorking(t *testing.T, supervisorScheme, supervisorAddress, supervisorCABundle, issuerName string) {
func requireWellKnownEndpointIsWorking(t *testing.T, supervisorScheme, supervisorAddress, supervisorCABundle, issuerName string, dnsOverrides map[string]string) {
t.Helper()
issuerURL, err := url.Parse(issuerName)
require.NoError(t, err)
response, responseBody := requireSuccessEndpointResponse(t, wellKnownURLForIssuer(supervisorScheme, supervisorAddress, issuerURL.Path), issuerName, supervisorCABundle) //nolint:bodyclose
response, responseBody := requireSuccessEndpointResponse(t, wellKnownURLForIssuer(supervisorScheme, supervisorAddress, issuerURL.Path), issuerName, supervisorCABundle, dnsOverrides) //nolint:bodyclose
// Check that the response matches our expectations.
expectedResultTemplate := here.Doc(`{
@ -250,12 +381,17 @@ type ExpectedJWKSResponseFormat struct {
Keys []map[string]string
}
func requireJWKSEndpointIsWorking(t *testing.T, supervisorScheme, supervisorAddress, supervisorCABundle, issuerName string) *ExpectedJWKSResponseFormat {
func requireJWKSEndpointIsWorking(t *testing.T, supervisorScheme, supervisorAddress, supervisorCABundle, issuerName string, dnsOverrides map[string]string) *ExpectedJWKSResponseFormat {
t.Helper()
issuerURL, err := url.Parse(issuerName)
require.NoError(t, err)
response, responseBody := requireSuccessEndpointResponse(t, jwksURLForIssuer(supervisorScheme, supervisorAddress, issuerURL.Path), issuerName, supervisorCABundle) //nolint:bodyclose
response, responseBody := requireSuccessEndpointResponse(t, //nolint:bodyclose
jwksURLForIssuer(supervisorScheme, supervisorAddress, issuerURL.Path),
issuerName,
supervisorCABundle,
dnsOverrides,
)
var result ExpectedJWKSResponseFormat
err = json.Unmarshal([]byte(responseBody), &result)
@ -277,9 +413,10 @@ func requireJWKSEndpointIsWorking(t *testing.T, supervisorScheme, supervisorAddr
return &result
}
func requireSuccessEndpointResponse(t *testing.T, endpointURL, issuer, caBundle string) (*http.Response, string) {
func requireSuccessEndpointResponse(t *testing.T, endpointURL, issuer, caBundle string, dnsOverrides map[string]string) (*http.Response, string) {
t.Helper()
httpClient := newHTTPClient(caBundle)
httpClient := newHTTPClient(t, caBundle, dnsOverrides)
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
defer cancel()
@ -360,12 +497,33 @@ func requireStatus(t *testing.T, client pinnipedclientset.Interface, ns, name st
require.Equalf(t, status, opc.Status.Status, "unexpected status (message = '%s')", opc.Status.Message)
}
func newHTTPClient(caBundle string) *http.Client {
func newHTTPClient(t *testing.T, caBundle string, dnsOverrides map[string]string) *http.Client {
c := &http.Client{}
realDialer := &net.Dialer{}
overrideDialContext := func(ctx context.Context, network, addr string) (net.Conn, error) {
replacementAddr, hasKey := dnsOverrides[addr]
if hasKey {
t.Logf("DialContext replacing addr %s with %s", addr, replacementAddr)
addr = replacementAddr
} else if dnsOverrides != nil {
t.Fatal("dnsOverrides was provided but not used, which was probably a mistake")
}
return realDialer.DialContext(ctx, network, addr)
}
if caBundle != "" { // CA bundle is optional
caCertPool := x509.NewCertPool()
caCertPool.AppendCertsFromPEM([]byte(caBundle))
c.Transport = &http.Transport{TLSClientConfig: &tls.Config{MinVersion: tls.VersionTLS13, RootCAs: caCertPool}}
c.Transport = &http.Transport{
DialContext: overrideDialContext,
TLSClientConfig: &tls.Config{MinVersion: tls.VersionTLS13, RootCAs: caCertPool},
}
} else {
c.Transport = &http.Transport{
DialContext: overrideDialContext,
}
}
return c
}

View File

@ -27,7 +27,7 @@ func TestSupervisorOIDCKeys(t *testing.T) {
defer cancel()
// Create our OPC under test.
opc := library.CreateTestOIDCProvider(ctx, t, "")
opc := library.CreateTestOIDCProvider(ctx, t, "", "")
// Ensure a secret is created with the OPC's JWKS.
var updatedOPC *configv1alpha1.OIDCProviderConfig

View File

@ -165,7 +165,7 @@ func CreateTestWebhookIDP(ctx context.Context, t *testing.T) corev1.TypedLocalOb
//
// If the provided issuer is not the empty string, then it will be used for the
// OIDCProviderConfig.Spec.Issuer field. Else, a random issuer will be generated.
func CreateTestOIDCProvider(ctx context.Context, t *testing.T, issuer string) *configv1alpha1.OIDCProviderConfig {
func CreateTestOIDCProvider(ctx context.Context, t *testing.T, issuer, sniCertificateSecretName string) *configv1alpha1.OIDCProviderConfig {
t.Helper()
testEnv := IntegrationEnv(t)
@ -186,7 +186,8 @@ func CreateTestOIDCProvider(ctx context.Context, t *testing.T, issuer string) *c
Annotations: map[string]string{"pinniped.dev/testName": t.Name()},
},
Spec: configv1alpha1.OIDCProviderConfigSpec{
Issuer: issuer,
Issuer: issuer,
SNICertificateSecretName: sniCertificateSecretName,
},
}, metav1.CreateOptions{})
require.NoError(t, err, "could not create test OIDCProviderConfig")

View File

@ -26,17 +26,18 @@ const (
type TestEnv struct {
t *testing.T
ConciergeNamespace string `json:"conciergeNamespace"`
SupervisorNamespace string `json:"supervisorNamespace"`
ConciergeAppName string `json:"conciergeAppName"`
SupervisorAppName string `json:"supervisorAppName"`
SupervisorCustomLabels map[string]string `json:"supervisorCustomLabels"`
ConciergeCustomLabels map[string]string `json:"conciergeCustomLabels"`
Capabilities map[Capability]bool `json:"capabilities"`
TestWebhook idpv1alpha1.WebhookIdentityProviderSpec `json:"testWebhook"`
SupervisorHTTPAddress string `json:"supervisorHttpAddress"`
SupervisorHTTPSAddress string `json:"supervisorHttpsAddress"`
SupervisorHTTPSCABundle string `json:"supervisorHttpsCABundle"`
ConciergeNamespace string `json:"conciergeNamespace"`
SupervisorNamespace string `json:"supervisorNamespace"`
ConciergeAppName string `json:"conciergeAppName"`
SupervisorAppName string `json:"supervisorAppName"`
SupervisorCustomLabels map[string]string `json:"supervisorCustomLabels"`
ConciergeCustomLabels map[string]string `json:"conciergeCustomLabels"`
Capabilities map[Capability]bool `json:"capabilities"`
TestWebhook idpv1alpha1.WebhookIdentityProviderSpec `json:"testWebhook"`
SupervisorHTTPAddress string `json:"supervisorHttpAddress"`
SupervisorHTTPSAddress string `json:"supervisorHttpsAddress"`
SupervisorHTTPSIngressAddress string `json:"supervisorHttpsIngressAddress"`
SupervisorHTTPSIngressCABundle string `json:"supervisorHttpsIngressCABundle"`
TestUser struct {
Token string `json:"token"`
@ -75,51 +76,62 @@ func IntegrationEnv(t *testing.T) *TestEnv {
err := yaml.Unmarshal([]byte(capabilitiesDescriptionYAML), &result)
require.NoErrorf(t, err, "capabilities specification was invalid YAML")
needEnv := func(key string) string {
t.Helper()
value := os.Getenv(key)
require.NotEmptyf(t, value, "must specify %s env var for integration tests", key)
return value
}
loadEnvVars(t, &result)
result.ConciergeNamespace = needEnv("PINNIPED_TEST_CONCIERGE_NAMESPACE")
result.ConciergeAppName = needEnv("PINNIPED_TEST_CONCIERGE_APP_NAME")
result.TestUser.ExpectedUsername = needEnv("PINNIPED_TEST_USER_USERNAME")
result.TestUser.ExpectedGroups = strings.Split(strings.ReplaceAll(needEnv("PINNIPED_TEST_USER_GROUPS"), " ", ""), ",")
result.TestUser.Token = needEnv("PINNIPED_TEST_USER_TOKEN")
result.TestWebhook.Endpoint = needEnv("PINNIPED_TEST_WEBHOOK_ENDPOINT")
result.SupervisorNamespace = needEnv("PINNIPED_TEST_SUPERVISOR_NAMESPACE")
result.SupervisorAppName = needEnv("PINNIPED_TEST_SUPERVISOR_APP_NAME")
result.TestWebhook.TLS = &idpv1alpha1.TLSSpec{CertificateAuthorityData: needEnv("PINNIPED_TEST_WEBHOOK_CA_BUNDLE")}
result.t = t
return &result
}
func needEnv(t *testing.T, key string) string {
t.Helper()
value := os.Getenv(key)
require.NotEmptyf(t, value, "must specify %s env var for integration tests", key)
return value
}
func loadEnvVars(t *testing.T, result *TestEnv) {
t.Helper()
result.ConciergeNamespace = needEnv(t, "PINNIPED_TEST_CONCIERGE_NAMESPACE")
result.ConciergeAppName = needEnv(t, "PINNIPED_TEST_CONCIERGE_APP_NAME")
result.TestUser.ExpectedUsername = needEnv(t, "PINNIPED_TEST_USER_USERNAME")
result.TestUser.ExpectedGroups = strings.Split(strings.ReplaceAll(needEnv(t, "PINNIPED_TEST_USER_GROUPS"), " ", ""), ",")
result.TestUser.Token = needEnv(t, "PINNIPED_TEST_USER_TOKEN")
result.TestWebhook.Endpoint = needEnv(t, "PINNIPED_TEST_WEBHOOK_ENDPOINT")
result.SupervisorNamespace = needEnv(t, "PINNIPED_TEST_SUPERVISOR_NAMESPACE")
result.SupervisorAppName = needEnv(t, "PINNIPED_TEST_SUPERVISOR_APP_NAME")
result.TestWebhook.TLS = &idpv1alpha1.TLSSpec{CertificateAuthorityData: needEnv(t, "PINNIPED_TEST_WEBHOOK_CA_BUNDLE")}
result.SupervisorHTTPAddress = os.Getenv("PINNIPED_TEST_SUPERVISOR_HTTP_ADDRESS")
result.SupervisorHTTPSAddress = os.Getenv("PINNIPED_TEST_SUPERVISOR_HTTPS_ADDRESS")
result.SupervisorHTTPSIngressAddress = os.Getenv("PINNIPED_TEST_SUPERVISOR_HTTPS_INGRESS_ADDRESS")
result.SupervisorHTTPSIngressCABundle = os.Getenv("PINNIPED_TEST_SUPERVISOR_HTTPS_INGRESS_CA_BUNDLE") // optional
require.NotEmptyf(t,
result.SupervisorHTTPAddress+result.SupervisorHTTPSAddress,
"must specify either PINNIPED_TEST_SUPERVISOR_HTTP_ADDRESS or PINNIPED_TEST_SUPERVISOR_HTTPS_ADDRESS env var (or both) for integration tests",
result.SupervisorHTTPAddress+result.SupervisorHTTPSIngressAddress,
"must specify either PINNIPED_TEST_SUPERVISOR_HTTP_ADDRESS or PINNIPED_TEST_SUPERVISOR_HTTPS_INGRESS_ADDRESS env var (or both) for integration tests",
)
result.SupervisorHTTPSAddress = needEnv(t, "PINNIPED_TEST_SUPERVISOR_HTTPS_ADDRESS")
require.NotRegexp(t, "^[0-9]", result.SupervisorHTTPSAddress,
"PINNIPED_TEST_SUPERVISOR_HTTPS_ADDRESS must be a hostname with an optional port and cannot be an IP address",
)
result.SupervisorHTTPSCABundle = os.Getenv("PINNIPED_TEST_SUPERVISOR_HTTPS_CA_BUNDLE") // optional
conciergeCustomLabelsYAML := needEnv("PINNIPED_TEST_CONCIERGE_CUSTOM_LABELS")
conciergeCustomLabelsYAML := needEnv(t, "PINNIPED_TEST_CONCIERGE_CUSTOM_LABELS")
var conciergeCustomLabels map[string]string
err = yaml.Unmarshal([]byte(conciergeCustomLabelsYAML), &conciergeCustomLabels)
err := yaml.Unmarshal([]byte(conciergeCustomLabelsYAML), &conciergeCustomLabels)
require.NoErrorf(t, err, "PINNIPED_TEST_CONCIERGE_CUSTOM_LABELS must be a YAML map of string to string")
result.ConciergeCustomLabels = conciergeCustomLabels
require.NotEmpty(t, result.ConciergeCustomLabels, "PINNIPED_TEST_CONCIERGE_CUSTOM_LABELS cannot be empty")
supervisorCustomLabelsYAML := needEnv("PINNIPED_TEST_SUPERVISOR_CUSTOM_LABELS")
supervisorCustomLabelsYAML := needEnv(t, "PINNIPED_TEST_SUPERVISOR_CUSTOM_LABELS")
var supervisorCustomLabels map[string]string
err = yaml.Unmarshal([]byte(supervisorCustomLabelsYAML), &supervisorCustomLabels)
require.NoErrorf(t, err, "PINNIPED_TEST_SUPERVISOR_CUSTOM_LABELS must be a YAML map of string to string")
result.SupervisorCustomLabels = supervisorCustomLabels
require.NotEmpty(t, result.SupervisorCustomLabels, "PINNIPED_TEST_SUPERVISOR_CUSTOM_LABELS cannot be empty")
result.OIDCUpstream.Issuer = needEnv("PINNIPED_TEST_CLI_OIDC_ISSUER")
result.OIDCUpstream.ClientID = needEnv("PINNIPED_TEST_CLI_OIDC_CLIENT_ID")
result.OIDCUpstream.LocalhostPort, _ = strconv.Atoi(needEnv("PINNIPED_TEST_CLI_OIDC_LOCALHOST_PORT"))
result.OIDCUpstream.Username = needEnv("PINNIPED_TEST_CLI_OIDC_USERNAME")
result.OIDCUpstream.Password = needEnv("PINNIPED_TEST_CLI_OIDC_PASSWORD")
result.t = t
return &result
result.OIDCUpstream.Issuer = needEnv(t, "PINNIPED_TEST_CLI_OIDC_ISSUER")
result.OIDCUpstream.ClientID = needEnv(t, "PINNIPED_TEST_CLI_OIDC_CLIENT_ID")
result.OIDCUpstream.LocalhostPort, _ = strconv.Atoi(needEnv(t, "PINNIPED_TEST_CLI_OIDC_LOCALHOST_PORT"))
result.OIDCUpstream.Username = needEnv(t, "PINNIPED_TEST_CLI_OIDC_USERNAME")
result.OIDCUpstream.Password = needEnv(t, "PINNIPED_TEST_CLI_OIDC_PASSWORD")
}
func (e *TestEnv) HasCapability(cap Capability) bool {