f1e63c55d4
- Add new optional ytt params for the Supervisor deployment. - When the Supervisor is making calls to an upstream OIDC provider, use these variables if they were provided. - These settings are integration tested in the main CI pipeline by sometimes setting them on deployments in certain cases, and then letting the existing integration tests (e.g. TestE2EFullIntegration) provide the coverage, so there are no explicit changes to the integration tests themselves in this commit.
910 lines
46 KiB
Go
910 lines
46 KiB
Go
// Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package oidcupstreamwatcher
|
|
|
|
import (
|
|
"context"
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/url"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
corev1 "k8s.io/api/core/v1"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/runtime"
|
|
"k8s.io/client-go/informers"
|
|
"k8s.io/client-go/kubernetes/fake"
|
|
|
|
"go.pinniped.dev/generated/latest/apis/supervisor/idp/v1alpha1"
|
|
pinnipedfake "go.pinniped.dev/generated/latest/client/supervisor/clientset/versioned/fake"
|
|
pinnipedinformers "go.pinniped.dev/generated/latest/client/supervisor/informers/externalversions"
|
|
"go.pinniped.dev/internal/controllerlib"
|
|
"go.pinniped.dev/internal/oidc/provider"
|
|
"go.pinniped.dev/internal/testutil"
|
|
"go.pinniped.dev/internal/testutil/oidctestutil"
|
|
"go.pinniped.dev/internal/testutil/testlogger"
|
|
"go.pinniped.dev/internal/upstreamoidc"
|
|
)
|
|
|
|
func TestOIDCUpstreamWatcherControllerFilterSecret(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
name string
|
|
secret metav1.Object
|
|
wantAdd bool
|
|
wantUpdate bool
|
|
wantDelete bool
|
|
}{
|
|
{
|
|
name: "a secret of the right type",
|
|
secret: &corev1.Secret{
|
|
Type: "secrets.pinniped.dev/oidc-client",
|
|
ObjectMeta: metav1.ObjectMeta{Name: "some-name", Namespace: "some-namespace"},
|
|
},
|
|
wantAdd: true,
|
|
wantUpdate: true,
|
|
wantDelete: true,
|
|
},
|
|
{
|
|
name: "a secret of the wrong type",
|
|
secret: &corev1.Secret{
|
|
Type: "secrets.pinniped.dev/not-the-oidc-client-type",
|
|
ObjectMeta: metav1.ObjectMeta{Name: "some-name", Namespace: "some-namespace"},
|
|
},
|
|
},
|
|
{
|
|
name: "resource of wrong data type",
|
|
secret: &corev1.Namespace{
|
|
ObjectMeta: metav1.ObjectMeta{Name: "some-name", Namespace: "some-namespace"},
|
|
},
|
|
},
|
|
}
|
|
for _, test := range tests {
|
|
test := test
|
|
t.Run(test.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
fakePinnipedClient := pinnipedfake.NewSimpleClientset()
|
|
pinnipedInformers := pinnipedinformers.NewSharedInformerFactory(fakePinnipedClient, 0)
|
|
fakeKubeClient := fake.NewSimpleClientset()
|
|
kubeInformers := informers.NewSharedInformerFactory(fakeKubeClient, 0)
|
|
testLog := testlogger.New(t)
|
|
cache := provider.NewDynamicUpstreamIDPProvider()
|
|
cache.SetOIDCIdentityProviders([]provider.UpstreamOIDCIdentityProviderI{
|
|
&upstreamoidc.ProviderConfig{Name: "initial-entry"},
|
|
})
|
|
secretInformer := kubeInformers.Core().V1().Secrets()
|
|
withInformer := testutil.NewObservableWithInformerOption()
|
|
|
|
New(
|
|
cache,
|
|
nil,
|
|
pinnipedInformers.IDP().V1alpha1().OIDCIdentityProviders(),
|
|
secretInformer,
|
|
testLog,
|
|
withInformer.WithInformer,
|
|
)
|
|
|
|
unrelated := corev1.Secret{}
|
|
filter := withInformer.GetFilterForInformer(secretInformer)
|
|
require.Equal(t, test.wantAdd, filter.Add(test.secret))
|
|
require.Equal(t, test.wantUpdate, filter.Update(&unrelated, test.secret))
|
|
require.Equal(t, test.wantUpdate, filter.Update(test.secret, &unrelated))
|
|
require.Equal(t, test.wantDelete, filter.Delete(test.secret))
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestOIDCUpstreamWatcherControllerSync(t *testing.T) {
|
|
t.Parallel()
|
|
now := metav1.NewTime(time.Now().UTC())
|
|
earlier := metav1.NewTime(now.Add(-1 * time.Hour).UTC())
|
|
|
|
// Start another test server that answers discovery successfully.
|
|
testIssuerCA, testIssuerURL := newTestIssuer(t)
|
|
testIssuerCABase64 := base64.StdEncoding.EncodeToString([]byte(testIssuerCA))
|
|
testIssuerAuthorizeURL, err := url.Parse("https://example.com/authorize")
|
|
require.NoError(t, err)
|
|
|
|
var (
|
|
testNamespace = "test-namespace"
|
|
testName = "test-name"
|
|
testSecretName = "test-client-secret"
|
|
testAdditionalScopes = []string{"scope1", "scope2", "scope3"}
|
|
testExpectedScopes = []string{"openid", "scope1", "scope2", "scope3"}
|
|
testClientID = "test-oidc-client-id"
|
|
testClientSecret = "test-oidc-client-secret"
|
|
testValidSecretData = map[string][]byte{"clientID": []byte(testClientID), "clientSecret": []byte(testClientSecret)}
|
|
testGroupsClaim = "test-groups-claim"
|
|
testUsernameClaim = "test-username-claim"
|
|
)
|
|
tests := []struct {
|
|
name string
|
|
inputUpstreams []runtime.Object
|
|
inputSecrets []runtime.Object
|
|
wantErr string
|
|
wantLogs []string
|
|
wantResultingCache []provider.UpstreamOIDCIdentityProviderI
|
|
wantResultingUpstreams []v1alpha1.OIDCIdentityProvider
|
|
}{
|
|
{
|
|
name: "no upstreams",
|
|
},
|
|
{
|
|
name: "missing secret",
|
|
inputUpstreams: []runtime.Object{&v1alpha1.OIDCIdentityProvider{
|
|
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName},
|
|
Spec: v1alpha1.OIDCIdentityProviderSpec{
|
|
Issuer: testIssuerURL,
|
|
TLS: &v1alpha1.TLSSpec{CertificateAuthorityData: testIssuerCABase64},
|
|
Client: v1alpha1.OIDCClient{SecretName: testSecretName},
|
|
AuthorizationConfig: v1alpha1.OIDCAuthorizationConfig{AdditionalScopes: testAdditionalScopes},
|
|
},
|
|
}},
|
|
inputSecrets: []runtime.Object{},
|
|
wantErr: controllerlib.ErrSyntheticRequeue.Error(),
|
|
wantLogs: []string{
|
|
`oidc-upstream-observer "level"=0 "msg"="updated condition" "name"="test-name" "namespace"="test-namespace" "message"="secret \"test-client-secret\" not found" "reason"="SecretNotFound" "status"="False" "type"="ClientCredentialsValid"`,
|
|
`oidc-upstream-observer "level"=0 "msg"="updated condition" "name"="test-name" "namespace"="test-namespace" "message"="discovered issuer configuration" "reason"="Success" "status"="True" "type"="OIDCDiscoverySucceeded"`,
|
|
`oidc-upstream-observer "msg"="found failing condition" "error"="OIDCIdentityProvider has a failing condition" "message"="secret \"test-client-secret\" not found" "name"="test-name" "namespace"="test-namespace" "reason"="SecretNotFound" "type"="ClientCredentialsValid"`,
|
|
},
|
|
wantResultingCache: []provider.UpstreamOIDCIdentityProviderI{},
|
|
wantResultingUpstreams: []v1alpha1.OIDCIdentityProvider{{
|
|
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName},
|
|
Status: v1alpha1.OIDCIdentityProviderStatus{
|
|
Phase: "Error",
|
|
Conditions: []v1alpha1.Condition{
|
|
{
|
|
Type: "ClientCredentialsValid",
|
|
Status: "False",
|
|
LastTransitionTime: now,
|
|
Reason: "SecretNotFound",
|
|
Message: `secret "test-client-secret" not found`,
|
|
},
|
|
{
|
|
Type: "OIDCDiscoverySucceeded",
|
|
Status: "True",
|
|
LastTransitionTime: now,
|
|
Reason: "Success",
|
|
Message: "discovered issuer configuration",
|
|
},
|
|
},
|
|
},
|
|
}},
|
|
},
|
|
{
|
|
name: "secret has wrong type",
|
|
inputUpstreams: []runtime.Object{&v1alpha1.OIDCIdentityProvider{
|
|
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName},
|
|
Spec: v1alpha1.OIDCIdentityProviderSpec{
|
|
Issuer: testIssuerURL,
|
|
TLS: &v1alpha1.TLSSpec{CertificateAuthorityData: testIssuerCABase64},
|
|
Client: v1alpha1.OIDCClient{SecretName: testSecretName},
|
|
AuthorizationConfig: v1alpha1.OIDCAuthorizationConfig{AdditionalScopes: testAdditionalScopes},
|
|
},
|
|
}},
|
|
inputSecrets: []runtime.Object{&corev1.Secret{
|
|
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testSecretName},
|
|
Type: "some-other-type",
|
|
Data: testValidSecretData,
|
|
}},
|
|
wantErr: controllerlib.ErrSyntheticRequeue.Error(),
|
|
wantLogs: []string{
|
|
`oidc-upstream-observer "level"=0 "msg"="updated condition" "name"="test-name" "namespace"="test-namespace" "message"="referenced Secret \"test-client-secret\" has wrong type \"some-other-type\" (should be \"secrets.pinniped.dev/oidc-client\")" "reason"="SecretWrongType" "status"="False" "type"="ClientCredentialsValid"`,
|
|
`oidc-upstream-observer "level"=0 "msg"="updated condition" "name"="test-name" "namespace"="test-namespace" "message"="discovered issuer configuration" "reason"="Success" "status"="True" "type"="OIDCDiscoverySucceeded"`,
|
|
`oidc-upstream-observer "msg"="found failing condition" "error"="OIDCIdentityProvider has a failing condition" "message"="referenced Secret \"test-client-secret\" has wrong type \"some-other-type\" (should be \"secrets.pinniped.dev/oidc-client\")" "name"="test-name" "namespace"="test-namespace" "reason"="SecretWrongType" "type"="ClientCredentialsValid"`,
|
|
},
|
|
wantResultingCache: []provider.UpstreamOIDCIdentityProviderI{},
|
|
wantResultingUpstreams: []v1alpha1.OIDCIdentityProvider{{
|
|
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName},
|
|
Status: v1alpha1.OIDCIdentityProviderStatus{
|
|
Phase: "Error",
|
|
Conditions: []v1alpha1.Condition{
|
|
{
|
|
Type: "ClientCredentialsValid",
|
|
Status: "False",
|
|
LastTransitionTime: now,
|
|
Reason: "SecretWrongType",
|
|
Message: `referenced Secret "test-client-secret" has wrong type "some-other-type" (should be "secrets.pinniped.dev/oidc-client")`,
|
|
},
|
|
{
|
|
Type: "OIDCDiscoverySucceeded",
|
|
Status: "True",
|
|
LastTransitionTime: now,
|
|
Reason: "Success",
|
|
Message: "discovered issuer configuration",
|
|
},
|
|
},
|
|
},
|
|
}},
|
|
},
|
|
{
|
|
name: "secret is missing key",
|
|
inputUpstreams: []runtime.Object{&v1alpha1.OIDCIdentityProvider{
|
|
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName},
|
|
Spec: v1alpha1.OIDCIdentityProviderSpec{
|
|
Issuer: testIssuerURL,
|
|
TLS: &v1alpha1.TLSSpec{CertificateAuthorityData: testIssuerCABase64},
|
|
Client: v1alpha1.OIDCClient{SecretName: testSecretName},
|
|
AuthorizationConfig: v1alpha1.OIDCAuthorizationConfig{AdditionalScopes: testAdditionalScopes},
|
|
},
|
|
}},
|
|
inputSecrets: []runtime.Object{&corev1.Secret{
|
|
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testSecretName},
|
|
Type: "secrets.pinniped.dev/oidc-client",
|
|
}},
|
|
wantErr: controllerlib.ErrSyntheticRequeue.Error(),
|
|
wantLogs: []string{
|
|
`oidc-upstream-observer "level"=0 "msg"="updated condition" "name"="test-name" "namespace"="test-namespace" "message"="referenced Secret \"test-client-secret\" is missing required keys [\"clientID\" \"clientSecret\"]" "reason"="SecretMissingKeys" "status"="False" "type"="ClientCredentialsValid"`,
|
|
`oidc-upstream-observer "level"=0 "msg"="updated condition" "name"="test-name" "namespace"="test-namespace" "message"="discovered issuer configuration" "reason"="Success" "status"="True" "type"="OIDCDiscoverySucceeded"`,
|
|
`oidc-upstream-observer "msg"="found failing condition" "error"="OIDCIdentityProvider has a failing condition" "message"="referenced Secret \"test-client-secret\" is missing required keys [\"clientID\" \"clientSecret\"]" "name"="test-name" "namespace"="test-namespace" "reason"="SecretMissingKeys" "type"="ClientCredentialsValid"`,
|
|
},
|
|
wantResultingCache: []provider.UpstreamOIDCIdentityProviderI{},
|
|
wantResultingUpstreams: []v1alpha1.OIDCIdentityProvider{{
|
|
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName},
|
|
Status: v1alpha1.OIDCIdentityProviderStatus{
|
|
Phase: "Error",
|
|
Conditions: []v1alpha1.Condition{
|
|
{
|
|
Type: "ClientCredentialsValid",
|
|
Status: "False",
|
|
LastTransitionTime: now,
|
|
Reason: "SecretMissingKeys",
|
|
Message: `referenced Secret "test-client-secret" is missing required keys ["clientID" "clientSecret"]`,
|
|
},
|
|
{
|
|
Type: "OIDCDiscoverySucceeded",
|
|
Status: "True",
|
|
LastTransitionTime: now,
|
|
Reason: "Success",
|
|
Message: "discovered issuer configuration",
|
|
},
|
|
},
|
|
},
|
|
}},
|
|
},
|
|
{
|
|
name: "TLS CA bundle is invalid base64",
|
|
inputUpstreams: []runtime.Object{&v1alpha1.OIDCIdentityProvider{
|
|
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: "test-name"},
|
|
Spec: v1alpha1.OIDCIdentityProviderSpec{
|
|
Issuer: testIssuerURL,
|
|
TLS: &v1alpha1.TLSSpec{
|
|
CertificateAuthorityData: "invalid-base64",
|
|
},
|
|
Client: v1alpha1.OIDCClient{SecretName: testSecretName},
|
|
AuthorizationConfig: v1alpha1.OIDCAuthorizationConfig{AdditionalScopes: append(testAdditionalScopes, "xyz", "openid")},
|
|
},
|
|
}},
|
|
inputSecrets: []runtime.Object{&corev1.Secret{
|
|
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testSecretName},
|
|
Type: "secrets.pinniped.dev/oidc-client",
|
|
Data: testValidSecretData,
|
|
}},
|
|
wantErr: controllerlib.ErrSyntheticRequeue.Error(),
|
|
wantLogs: []string{
|
|
`oidc-upstream-observer "level"=0 "msg"="updated condition" "name"="test-name" "namespace"="test-namespace" "message"="loaded client credentials" "reason"="Success" "status"="True" "type"="ClientCredentialsValid"`,
|
|
`oidc-upstream-observer "level"=0 "msg"="updated condition" "name"="test-name" "namespace"="test-namespace" "message"="spec.certificateAuthorityData is invalid: illegal base64 data at input byte 7" "reason"="InvalidTLSConfig" "status"="False" "type"="OIDCDiscoverySucceeded"`,
|
|
`oidc-upstream-observer "msg"="found failing condition" "error"="OIDCIdentityProvider has a failing condition" "message"="spec.certificateAuthorityData is invalid: illegal base64 data at input byte 7" "name"="test-name" "namespace"="test-namespace" "reason"="InvalidTLSConfig" "type"="OIDCDiscoverySucceeded"`,
|
|
},
|
|
wantResultingCache: []provider.UpstreamOIDCIdentityProviderI{},
|
|
wantResultingUpstreams: []v1alpha1.OIDCIdentityProvider{{
|
|
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName},
|
|
Status: v1alpha1.OIDCIdentityProviderStatus{
|
|
Phase: "Error",
|
|
Conditions: []v1alpha1.Condition{
|
|
{
|
|
Type: "ClientCredentialsValid",
|
|
Status: "True",
|
|
LastTransitionTime: now,
|
|
Reason: "Success",
|
|
Message: "loaded client credentials",
|
|
},
|
|
{
|
|
Type: "OIDCDiscoverySucceeded",
|
|
Status: "False",
|
|
LastTransitionTime: now,
|
|
Reason: "InvalidTLSConfig",
|
|
Message: `spec.certificateAuthorityData is invalid: illegal base64 data at input byte 7`,
|
|
},
|
|
},
|
|
},
|
|
}},
|
|
},
|
|
{
|
|
name: "TLS CA bundle does not have any certificates",
|
|
inputUpstreams: []runtime.Object{&v1alpha1.OIDCIdentityProvider{
|
|
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: "test-name"},
|
|
Spec: v1alpha1.OIDCIdentityProviderSpec{
|
|
Issuer: testIssuerURL,
|
|
TLS: &v1alpha1.TLSSpec{
|
|
CertificateAuthorityData: base64.StdEncoding.EncodeToString([]byte("not-a-pem-ca-bundle")),
|
|
},
|
|
Client: v1alpha1.OIDCClient{SecretName: testSecretName},
|
|
AuthorizationConfig: v1alpha1.OIDCAuthorizationConfig{AdditionalScopes: append(testAdditionalScopes, "xyz", "openid")},
|
|
},
|
|
}},
|
|
inputSecrets: []runtime.Object{&corev1.Secret{
|
|
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testSecretName},
|
|
Type: "secrets.pinniped.dev/oidc-client",
|
|
Data: testValidSecretData,
|
|
}},
|
|
wantErr: controllerlib.ErrSyntheticRequeue.Error(),
|
|
wantLogs: []string{
|
|
`oidc-upstream-observer "level"=0 "msg"="updated condition" "name"="test-name" "namespace"="test-namespace" "message"="loaded client credentials" "reason"="Success" "status"="True" "type"="ClientCredentialsValid"`,
|
|
`oidc-upstream-observer "level"=0 "msg"="updated condition" "name"="test-name" "namespace"="test-namespace" "message"="spec.certificateAuthorityData is invalid: no certificates found" "reason"="InvalidTLSConfig" "status"="False" "type"="OIDCDiscoverySucceeded"`,
|
|
`oidc-upstream-observer "msg"="found failing condition" "error"="OIDCIdentityProvider has a failing condition" "message"="spec.certificateAuthorityData is invalid: no certificates found" "name"="test-name" "namespace"="test-namespace" "reason"="InvalidTLSConfig" "type"="OIDCDiscoverySucceeded"`,
|
|
},
|
|
wantResultingCache: []provider.UpstreamOIDCIdentityProviderI{},
|
|
wantResultingUpstreams: []v1alpha1.OIDCIdentityProvider{{
|
|
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName},
|
|
Status: v1alpha1.OIDCIdentityProviderStatus{
|
|
Phase: "Error",
|
|
Conditions: []v1alpha1.Condition{
|
|
{
|
|
Type: "ClientCredentialsValid",
|
|
Status: "True",
|
|
LastTransitionTime: now,
|
|
Reason: "Success",
|
|
Message: "loaded client credentials",
|
|
},
|
|
{
|
|
Type: "OIDCDiscoverySucceeded",
|
|
Status: "False",
|
|
LastTransitionTime: now,
|
|
Reason: "InvalidTLSConfig",
|
|
Message: `spec.certificateAuthorityData is invalid: no certificates found`,
|
|
},
|
|
},
|
|
},
|
|
}},
|
|
},
|
|
{
|
|
name: "issuer is invalid URL",
|
|
inputUpstreams: []runtime.Object{&v1alpha1.OIDCIdentityProvider{
|
|
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName},
|
|
Spec: v1alpha1.OIDCIdentityProviderSpec{
|
|
Issuer: "invalid-url-that-is-really-really-long",
|
|
Client: v1alpha1.OIDCClient{SecretName: testSecretName},
|
|
AuthorizationConfig: v1alpha1.OIDCAuthorizationConfig{AdditionalScopes: testAdditionalScopes},
|
|
},
|
|
}},
|
|
inputSecrets: []runtime.Object{&corev1.Secret{
|
|
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testSecretName},
|
|
Type: "secrets.pinniped.dev/oidc-client",
|
|
Data: testValidSecretData,
|
|
}},
|
|
wantErr: controllerlib.ErrSyntheticRequeue.Error(),
|
|
wantLogs: []string{
|
|
`oidc-upstream-observer "msg"="failed to perform OIDC discovery" "error"="Get \"invalid-url-that-is-really-really-long/.well-known/openid-configuration\": unsupported protocol scheme \"\"" "issuer"="invalid-url-that-is-really-really-long" "name"="test-name" "namespace"="test-namespace"`,
|
|
`oidc-upstream-observer "level"=0 "msg"="updated condition" "name"="test-name" "namespace"="test-namespace" "message"="loaded client credentials" "reason"="Success" "status"="True" "type"="ClientCredentialsValid"`,
|
|
`oidc-upstream-observer "level"=0 "msg"="updated condition" "name"="test-name" "namespace"="test-namespace" "message"="failed to perform OIDC discovery against \"invalid-url-that-is-really-really-long\":\nGet \"invalid-url-that-is-really-really-long/.well-known/openid-configuration\": unsupported protocol [truncated 9 chars]" "reason"="Unreachable" "status"="False" "type"="OIDCDiscoverySucceeded"`,
|
|
`oidc-upstream-observer "msg"="found failing condition" "error"="OIDCIdentityProvider has a failing condition" "message"="failed to perform OIDC discovery against \"invalid-url-that-is-really-really-long\":\nGet \"invalid-url-that-is-really-really-long/.well-known/openid-configuration\": unsupported protocol [truncated 9 chars]" "name"="test-name" "namespace"="test-namespace" "reason"="Unreachable" "type"="OIDCDiscoverySucceeded"`,
|
|
},
|
|
wantResultingCache: []provider.UpstreamOIDCIdentityProviderI{},
|
|
wantResultingUpstreams: []v1alpha1.OIDCIdentityProvider{{
|
|
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName},
|
|
Status: v1alpha1.OIDCIdentityProviderStatus{
|
|
Phase: "Error",
|
|
Conditions: []v1alpha1.Condition{
|
|
{
|
|
Type: "ClientCredentialsValid",
|
|
Status: "True",
|
|
LastTransitionTime: now,
|
|
Reason: "Success",
|
|
Message: "loaded client credentials",
|
|
},
|
|
{
|
|
Type: "OIDCDiscoverySucceeded",
|
|
Status: "False",
|
|
LastTransitionTime: now,
|
|
Reason: "Unreachable",
|
|
Message: `failed to perform OIDC discovery against "invalid-url-that-is-really-really-long":
|
|
Get "invalid-url-that-is-really-really-long/.well-known/openid-configuration": unsupported protocol [truncated 9 chars]`,
|
|
},
|
|
},
|
|
},
|
|
}},
|
|
},
|
|
{
|
|
name: "issuer returns invalid authorize URL",
|
|
inputUpstreams: []runtime.Object{&v1alpha1.OIDCIdentityProvider{
|
|
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName},
|
|
Spec: v1alpha1.OIDCIdentityProviderSpec{
|
|
Issuer: testIssuerURL + "/invalid",
|
|
TLS: &v1alpha1.TLSSpec{CertificateAuthorityData: testIssuerCABase64},
|
|
Client: v1alpha1.OIDCClient{SecretName: testSecretName},
|
|
AuthorizationConfig: v1alpha1.OIDCAuthorizationConfig{AdditionalScopes: testAdditionalScopes},
|
|
},
|
|
}},
|
|
inputSecrets: []runtime.Object{&corev1.Secret{
|
|
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testSecretName},
|
|
Type: "secrets.pinniped.dev/oidc-client",
|
|
Data: testValidSecretData,
|
|
}},
|
|
wantErr: controllerlib.ErrSyntheticRequeue.Error(),
|
|
wantLogs: []string{
|
|
`oidc-upstream-observer "level"=0 "msg"="updated condition" "name"="test-name" "namespace"="test-namespace" "message"="loaded client credentials" "reason"="Success" "status"="True" "type"="ClientCredentialsValid"`,
|
|
`oidc-upstream-observer "level"=0 "msg"="updated condition" "name"="test-name" "namespace"="test-namespace" "message"="failed to parse authorization endpoint URL: parse \"%\": invalid URL escape \"%\"" "reason"="InvalidResponse" "status"="False" "type"="OIDCDiscoverySucceeded"`,
|
|
`oidc-upstream-observer "msg"="found failing condition" "error"="OIDCIdentityProvider has a failing condition" "message"="failed to parse authorization endpoint URL: parse \"%\": invalid URL escape \"%\"" "name"="test-name" "namespace"="test-namespace" "reason"="InvalidResponse" "type"="OIDCDiscoverySucceeded"`,
|
|
},
|
|
wantResultingCache: []provider.UpstreamOIDCIdentityProviderI{},
|
|
wantResultingUpstreams: []v1alpha1.OIDCIdentityProvider{{
|
|
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName},
|
|
Status: v1alpha1.OIDCIdentityProviderStatus{
|
|
Phase: "Error",
|
|
Conditions: []v1alpha1.Condition{
|
|
{
|
|
Type: "ClientCredentialsValid",
|
|
Status: "True",
|
|
LastTransitionTime: now,
|
|
Reason: "Success",
|
|
Message: "loaded client credentials",
|
|
},
|
|
{
|
|
Type: "OIDCDiscoverySucceeded",
|
|
Status: "False",
|
|
LastTransitionTime: now,
|
|
Reason: "InvalidResponse",
|
|
Message: `failed to parse authorization endpoint URL: parse "%": invalid URL escape "%"`,
|
|
},
|
|
},
|
|
},
|
|
}},
|
|
},
|
|
{
|
|
name: "issuer returns insecure authorize URL",
|
|
inputUpstreams: []runtime.Object{&v1alpha1.OIDCIdentityProvider{
|
|
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName},
|
|
Spec: v1alpha1.OIDCIdentityProviderSpec{
|
|
Issuer: testIssuerURL + "/insecure",
|
|
TLS: &v1alpha1.TLSSpec{CertificateAuthorityData: testIssuerCABase64},
|
|
Client: v1alpha1.OIDCClient{SecretName: testSecretName},
|
|
AuthorizationConfig: v1alpha1.OIDCAuthorizationConfig{AdditionalScopes: testAdditionalScopes},
|
|
},
|
|
}},
|
|
inputSecrets: []runtime.Object{&corev1.Secret{
|
|
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testSecretName},
|
|
Type: "secrets.pinniped.dev/oidc-client",
|
|
Data: testValidSecretData,
|
|
}},
|
|
wantErr: controllerlib.ErrSyntheticRequeue.Error(),
|
|
wantLogs: []string{
|
|
`oidc-upstream-observer "level"=0 "msg"="updated condition" "name"="test-name" "namespace"="test-namespace" "message"="loaded client credentials" "reason"="Success" "status"="True" "type"="ClientCredentialsValid"`,
|
|
`oidc-upstream-observer "level"=0 "msg"="updated condition" "name"="test-name" "namespace"="test-namespace" "message"="authorization endpoint URL scheme must be \"https\", not \"http\"" "reason"="InvalidResponse" "status"="False" "type"="OIDCDiscoverySucceeded"`,
|
|
`oidc-upstream-observer "msg"="found failing condition" "error"="OIDCIdentityProvider has a failing condition" "message"="authorization endpoint URL scheme must be \"https\", not \"http\"" "name"="test-name" "namespace"="test-namespace" "reason"="InvalidResponse" "type"="OIDCDiscoverySucceeded"`,
|
|
},
|
|
wantResultingCache: []provider.UpstreamOIDCIdentityProviderI{},
|
|
wantResultingUpstreams: []v1alpha1.OIDCIdentityProvider{{
|
|
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName},
|
|
Status: v1alpha1.OIDCIdentityProviderStatus{
|
|
Phase: "Error",
|
|
Conditions: []v1alpha1.Condition{
|
|
{
|
|
Type: "ClientCredentialsValid",
|
|
Status: "True",
|
|
LastTransitionTime: now,
|
|
Reason: "Success",
|
|
Message: "loaded client credentials",
|
|
},
|
|
{
|
|
Type: "OIDCDiscoverySucceeded",
|
|
Status: "False",
|
|
LastTransitionTime: now,
|
|
Reason: "InvalidResponse",
|
|
Message: `authorization endpoint URL scheme must be "https", not "http"`,
|
|
},
|
|
},
|
|
},
|
|
}},
|
|
},
|
|
{
|
|
name: "upstream becomes valid",
|
|
inputUpstreams: []runtime.Object{&v1alpha1.OIDCIdentityProvider{
|
|
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: "test-name"},
|
|
Spec: v1alpha1.OIDCIdentityProviderSpec{
|
|
Issuer: testIssuerURL,
|
|
TLS: &v1alpha1.TLSSpec{CertificateAuthorityData: testIssuerCABase64},
|
|
Client: v1alpha1.OIDCClient{SecretName: testSecretName},
|
|
AuthorizationConfig: v1alpha1.OIDCAuthorizationConfig{AdditionalScopes: append(testAdditionalScopes, "xyz", "openid")},
|
|
Claims: v1alpha1.OIDCClaims{Groups: testGroupsClaim, Username: testUsernameClaim},
|
|
},
|
|
Status: v1alpha1.OIDCIdentityProviderStatus{
|
|
Phase: "Error",
|
|
Conditions: []v1alpha1.Condition{
|
|
{Type: "ClientCredentialsValid", Status: "False", LastTransitionTime: earlier, Reason: "SomeError1", Message: "some previous error 1"},
|
|
{Type: "OIDCDiscoverySucceeded", Status: "False", LastTransitionTime: earlier, Reason: "SomeError2", Message: "some previous error 2"},
|
|
},
|
|
},
|
|
}},
|
|
inputSecrets: []runtime.Object{&corev1.Secret{
|
|
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testSecretName},
|
|
Type: "secrets.pinniped.dev/oidc-client",
|
|
Data: testValidSecretData,
|
|
}},
|
|
wantLogs: []string{
|
|
`oidc-upstream-observer "level"=0 "msg"="updated condition" "name"="test-name" "namespace"="test-namespace" "message"="loaded client credentials" "reason"="Success" "status"="True" "type"="ClientCredentialsValid"`,
|
|
`oidc-upstream-observer "level"=0 "msg"="updated condition" "name"="test-name" "namespace"="test-namespace" "message"="discovered issuer configuration" "reason"="Success" "status"="True" "type"="OIDCDiscoverySucceeded"`,
|
|
},
|
|
wantResultingCache: []provider.UpstreamOIDCIdentityProviderI{
|
|
&oidctestutil.TestUpstreamOIDCIdentityProvider{
|
|
Name: testName,
|
|
ClientID: testClientID,
|
|
AuthorizationURL: *testIssuerAuthorizeURL,
|
|
Scopes: append(testExpectedScopes, "xyz"),
|
|
UsernameClaim: testUsernameClaim,
|
|
GroupsClaim: testGroupsClaim,
|
|
},
|
|
},
|
|
wantResultingUpstreams: []v1alpha1.OIDCIdentityProvider{{
|
|
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName},
|
|
Status: v1alpha1.OIDCIdentityProviderStatus{
|
|
Phase: "Ready",
|
|
Conditions: []v1alpha1.Condition{
|
|
{Type: "ClientCredentialsValid", Status: "True", LastTransitionTime: now, Reason: "Success", Message: "loaded client credentials"},
|
|
{Type: "OIDCDiscoverySucceeded", Status: "True", LastTransitionTime: now, Reason: "Success", Message: "discovered issuer configuration"},
|
|
},
|
|
},
|
|
}},
|
|
},
|
|
{
|
|
name: "existing valid upstream",
|
|
inputUpstreams: []runtime.Object{&v1alpha1.OIDCIdentityProvider{
|
|
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, Generation: 1234},
|
|
Spec: v1alpha1.OIDCIdentityProviderSpec{
|
|
Issuer: testIssuerURL,
|
|
TLS: &v1alpha1.TLSSpec{CertificateAuthorityData: testIssuerCABase64},
|
|
Client: v1alpha1.OIDCClient{SecretName: testSecretName},
|
|
AuthorizationConfig: v1alpha1.OIDCAuthorizationConfig{AdditionalScopes: testAdditionalScopes},
|
|
Claims: v1alpha1.OIDCClaims{Groups: testGroupsClaim, Username: testUsernameClaim},
|
|
},
|
|
Status: v1alpha1.OIDCIdentityProviderStatus{
|
|
Phase: "Ready",
|
|
Conditions: []v1alpha1.Condition{
|
|
{Type: "ClientCredentialsValid", Status: "True", LastTransitionTime: earlier, Reason: "Success", Message: "loaded client credentials"},
|
|
{Type: "OIDCDiscoverySucceeded", Status: "True", LastTransitionTime: earlier, Reason: "Success", Message: "discovered issuer configuration"},
|
|
},
|
|
},
|
|
}},
|
|
inputSecrets: []runtime.Object{&corev1.Secret{
|
|
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testSecretName},
|
|
Type: "secrets.pinniped.dev/oidc-client",
|
|
Data: testValidSecretData,
|
|
}},
|
|
wantLogs: []string{
|
|
`oidc-upstream-observer "level"=0 "msg"="updated condition" "name"="test-name" "namespace"="test-namespace" "message"="loaded client credentials" "reason"="Success" "status"="True" "type"="ClientCredentialsValid"`,
|
|
`oidc-upstream-observer "level"=0 "msg"="updated condition" "name"="test-name" "namespace"="test-namespace" "message"="discovered issuer configuration" "reason"="Success" "status"="True" "type"="OIDCDiscoverySucceeded"`,
|
|
},
|
|
wantResultingCache: []provider.UpstreamOIDCIdentityProviderI{
|
|
&oidctestutil.TestUpstreamOIDCIdentityProvider{
|
|
Name: testName,
|
|
ClientID: testClientID,
|
|
AuthorizationURL: *testIssuerAuthorizeURL,
|
|
Scopes: testExpectedScopes,
|
|
UsernameClaim: testUsernameClaim,
|
|
GroupsClaim: testGroupsClaim,
|
|
},
|
|
},
|
|
wantResultingUpstreams: []v1alpha1.OIDCIdentityProvider{{
|
|
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, Generation: 1234},
|
|
Status: v1alpha1.OIDCIdentityProviderStatus{
|
|
Phase: "Ready",
|
|
Conditions: []v1alpha1.Condition{
|
|
{Type: "ClientCredentialsValid", Status: "True", LastTransitionTime: earlier, Reason: "Success", Message: "loaded client credentials", ObservedGeneration: 1234},
|
|
{Type: "OIDCDiscoverySucceeded", Status: "True", LastTransitionTime: earlier, Reason: "Success", Message: "discovered issuer configuration", ObservedGeneration: 1234},
|
|
},
|
|
},
|
|
}},
|
|
},
|
|
{
|
|
name: "existing valid upstream with trailing slash",
|
|
inputUpstreams: []runtime.Object{&v1alpha1.OIDCIdentityProvider{
|
|
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, Generation: 1234},
|
|
Spec: v1alpha1.OIDCIdentityProviderSpec{
|
|
Issuer: testIssuerURL + "/ends-with-slash/",
|
|
TLS: &v1alpha1.TLSSpec{CertificateAuthorityData: testIssuerCABase64},
|
|
Client: v1alpha1.OIDCClient{SecretName: testSecretName},
|
|
AuthorizationConfig: v1alpha1.OIDCAuthorizationConfig{AdditionalScopes: testAdditionalScopes},
|
|
Claims: v1alpha1.OIDCClaims{Groups: testGroupsClaim, Username: testUsernameClaim},
|
|
},
|
|
Status: v1alpha1.OIDCIdentityProviderStatus{
|
|
Phase: "Ready",
|
|
Conditions: []v1alpha1.Condition{
|
|
{Type: "ClientCredentialsValid", Status: "True", LastTransitionTime: earlier, Reason: "Success", Message: "loaded client credentials"},
|
|
{Type: "OIDCDiscoverySucceeded", Status: "True", LastTransitionTime: earlier, Reason: "Success", Message: "discovered issuer configuration"},
|
|
},
|
|
},
|
|
}},
|
|
inputSecrets: []runtime.Object{&corev1.Secret{
|
|
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testSecretName},
|
|
Type: "secrets.pinniped.dev/oidc-client",
|
|
Data: testValidSecretData,
|
|
}},
|
|
wantLogs: []string{
|
|
`oidc-upstream-observer "level"=0 "msg"="updated condition" "name"="test-name" "namespace"="test-namespace" "message"="loaded client credentials" "reason"="Success" "status"="True" "type"="ClientCredentialsValid"`,
|
|
`oidc-upstream-observer "level"=0 "msg"="updated condition" "name"="test-name" "namespace"="test-namespace" "message"="discovered issuer configuration" "reason"="Success" "status"="True" "type"="OIDCDiscoverySucceeded"`,
|
|
},
|
|
wantResultingCache: []provider.UpstreamOIDCIdentityProviderI{
|
|
&oidctestutil.TestUpstreamOIDCIdentityProvider{
|
|
Name: testName,
|
|
ClientID: testClientID,
|
|
AuthorizationURL: *testIssuerAuthorizeURL,
|
|
Scopes: testExpectedScopes,
|
|
UsernameClaim: testUsernameClaim,
|
|
GroupsClaim: testGroupsClaim,
|
|
},
|
|
},
|
|
wantResultingUpstreams: []v1alpha1.OIDCIdentityProvider{{
|
|
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, Generation: 1234},
|
|
Status: v1alpha1.OIDCIdentityProviderStatus{
|
|
Phase: "Ready",
|
|
Conditions: []v1alpha1.Condition{
|
|
{Type: "ClientCredentialsValid", Status: "True", LastTransitionTime: earlier, Reason: "Success", Message: "loaded client credentials", ObservedGeneration: 1234},
|
|
{Type: "OIDCDiscoverySucceeded", Status: "True", LastTransitionTime: earlier, Reason: "Success", Message: "discovered issuer configuration", ObservedGeneration: 1234},
|
|
},
|
|
},
|
|
}},
|
|
},
|
|
{
|
|
name: "issuer is invalid URL, missing trailing slash",
|
|
inputUpstreams: []runtime.Object{&v1alpha1.OIDCIdentityProvider{
|
|
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName},
|
|
Spec: v1alpha1.OIDCIdentityProviderSpec{
|
|
Issuer: testIssuerURL + "/ends-with-slash",
|
|
TLS: &v1alpha1.TLSSpec{CertificateAuthorityData: testIssuerCABase64},
|
|
Client: v1alpha1.OIDCClient{SecretName: testSecretName},
|
|
AuthorizationConfig: v1alpha1.OIDCAuthorizationConfig{AdditionalScopes: testAdditionalScopes},
|
|
},
|
|
}},
|
|
inputSecrets: []runtime.Object{&corev1.Secret{
|
|
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testSecretName},
|
|
Type: "secrets.pinniped.dev/oidc-client",
|
|
Data: testValidSecretData,
|
|
}},
|
|
wantErr: controllerlib.ErrSyntheticRequeue.Error(),
|
|
wantLogs: []string{
|
|
`oidc-upstream-observer "msg"="failed to perform OIDC discovery" "error"="oidc: issuer did not match the issuer returned by provider, expected \"` + testIssuerURL + `/ends-with-slash\" got \"` + testIssuerURL + `/ends-with-slash/\"" "issuer"="` + testIssuerURL + `/ends-with-slash" "name"="test-name" "namespace"="test-namespace"`,
|
|
`oidc-upstream-observer "level"=0 "msg"="updated condition" "name"="test-name" "namespace"="test-namespace" "message"="loaded client credentials" "reason"="Success" "status"="True" "type"="ClientCredentialsValid"`,
|
|
`oidc-upstream-observer "level"=0 "msg"="updated condition" "name"="test-name" "namespace"="test-namespace" "message"="failed to perform OIDC discovery against \"` + testIssuerURL + `/ends-with-slash\":\noidc: issuer did not match the issuer returned by provider, expected \"` + testIssuerURL + `/ends-with-slash\" got \"` + testIssuerURL + `/ends-with-slash/\"" "reason"="Unreachable" "status"="False" "type"="OIDCDiscoverySucceeded"`,
|
|
`oidc-upstream-observer "msg"="found failing condition" "error"="OIDCIdentityProvider has a failing condition" "message"="failed to perform OIDC discovery against \"` + testIssuerURL + `/ends-with-slash\":\noidc: issuer did not match the issuer returned by provider, expected \"` + testIssuerURL + `/ends-with-slash\" got \"` + testIssuerURL + `/ends-with-slash/\"" "name"="test-name" "namespace"="test-namespace" "reason"="Unreachable" "type"="OIDCDiscoverySucceeded"`,
|
|
},
|
|
wantResultingCache: []provider.UpstreamOIDCIdentityProviderI{},
|
|
wantResultingUpstreams: []v1alpha1.OIDCIdentityProvider{{
|
|
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName},
|
|
Status: v1alpha1.OIDCIdentityProviderStatus{
|
|
Phase: "Error",
|
|
Conditions: []v1alpha1.Condition{
|
|
{
|
|
Type: "ClientCredentialsValid",
|
|
Status: "True",
|
|
LastTransitionTime: now,
|
|
Reason: "Success",
|
|
Message: "loaded client credentials",
|
|
},
|
|
{
|
|
Type: "OIDCDiscoverySucceeded",
|
|
Status: "False",
|
|
LastTransitionTime: now,
|
|
Reason: "Unreachable",
|
|
Message: `failed to perform OIDC discovery against "` + testIssuerURL + `/ends-with-slash":
|
|
oidc: issuer did not match the issuer returned by provider, expected "` + testIssuerURL + `/ends-with-slash" got "` + testIssuerURL + `/ends-with-slash/"`,
|
|
},
|
|
},
|
|
},
|
|
}},
|
|
},
|
|
{
|
|
name: "issuer is invalid URL, extra trailing slash",
|
|
inputUpstreams: []runtime.Object{&v1alpha1.OIDCIdentityProvider{
|
|
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName},
|
|
Spec: v1alpha1.OIDCIdentityProviderSpec{
|
|
Issuer: testIssuerURL + "/",
|
|
TLS: &v1alpha1.TLSSpec{CertificateAuthorityData: testIssuerCABase64},
|
|
Client: v1alpha1.OIDCClient{SecretName: testSecretName},
|
|
AuthorizationConfig: v1alpha1.OIDCAuthorizationConfig{AdditionalScopes: testAdditionalScopes},
|
|
},
|
|
}},
|
|
inputSecrets: []runtime.Object{&corev1.Secret{
|
|
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testSecretName},
|
|
Type: "secrets.pinniped.dev/oidc-client",
|
|
Data: testValidSecretData,
|
|
}},
|
|
wantErr: controllerlib.ErrSyntheticRequeue.Error(),
|
|
wantLogs: []string{
|
|
`oidc-upstream-observer "msg"="failed to perform OIDC discovery" "error"="oidc: issuer did not match the issuer returned by provider, expected \"` + testIssuerURL + `/\" got \"` + testIssuerURL + `\"" "issuer"="` + testIssuerURL + `/" "name"="test-name" "namespace"="test-namespace"`,
|
|
`oidc-upstream-observer "level"=0 "msg"="updated condition" "name"="test-name" "namespace"="test-namespace" "message"="loaded client credentials" "reason"="Success" "status"="True" "type"="ClientCredentialsValid"`,
|
|
`oidc-upstream-observer "level"=0 "msg"="updated condition" "name"="test-name" "namespace"="test-namespace" "message"="failed to perform OIDC discovery against \"` + testIssuerURL + `/\":\noidc: issuer did not match the issuer returned by provider, expected \"` + testIssuerURL + `/\" got \"` + testIssuerURL + `\"" "reason"="Unreachable" "status"="False" "type"="OIDCDiscoverySucceeded"`,
|
|
`oidc-upstream-observer "msg"="found failing condition" "error"="OIDCIdentityProvider has a failing condition" "message"="failed to perform OIDC discovery against \"` + testIssuerURL + `/\":\noidc: issuer did not match the issuer returned by provider, expected \"` + testIssuerURL + `/\" got \"` + testIssuerURL + `\"" "name"="test-name" "namespace"="test-namespace" "reason"="Unreachable" "type"="OIDCDiscoverySucceeded"`,
|
|
},
|
|
wantResultingCache: []provider.UpstreamOIDCIdentityProviderI{},
|
|
wantResultingUpstreams: []v1alpha1.OIDCIdentityProvider{{
|
|
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName},
|
|
Status: v1alpha1.OIDCIdentityProviderStatus{
|
|
Phase: "Error",
|
|
Conditions: []v1alpha1.Condition{
|
|
{
|
|
Type: "ClientCredentialsValid",
|
|
Status: "True",
|
|
LastTransitionTime: now,
|
|
Reason: "Success",
|
|
Message: "loaded client credentials",
|
|
},
|
|
{
|
|
Type: "OIDCDiscoverySucceeded",
|
|
Status: "False",
|
|
LastTransitionTime: now,
|
|
Reason: "Unreachable",
|
|
Message: `failed to perform OIDC discovery against "` + testIssuerURL + `/":
|
|
oidc: issuer did not match the issuer returned by provider, expected "` + testIssuerURL + `/" got "` + testIssuerURL + `"`,
|
|
},
|
|
},
|
|
},
|
|
}},
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
tt := tt
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
fakePinnipedClient := pinnipedfake.NewSimpleClientset(tt.inputUpstreams...)
|
|
pinnipedInformers := pinnipedinformers.NewSharedInformerFactory(fakePinnipedClient, 0)
|
|
fakeKubeClient := fake.NewSimpleClientset(tt.inputSecrets...)
|
|
kubeInformers := informers.NewSharedInformerFactory(fakeKubeClient, 0)
|
|
testLog := testlogger.New(t)
|
|
cache := provider.NewDynamicUpstreamIDPProvider()
|
|
cache.SetOIDCIdentityProviders([]provider.UpstreamOIDCIdentityProviderI{
|
|
&upstreamoidc.ProviderConfig{Name: "initial-entry"},
|
|
})
|
|
|
|
controller := New(
|
|
cache,
|
|
fakePinnipedClient,
|
|
pinnipedInformers.IDP().V1alpha1().OIDCIdentityProviders(),
|
|
kubeInformers.Core().V1().Secrets(),
|
|
testLog,
|
|
controllerlib.WithInformer,
|
|
)
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
|
|
pinnipedInformers.Start(ctx.Done())
|
|
kubeInformers.Start(ctx.Done())
|
|
controllerlib.TestRunSynchronously(t, controller)
|
|
|
|
syncCtx := controllerlib.Context{Context: ctx, Key: controllerlib.Key{}}
|
|
|
|
if err := controllerlib.TestSync(t, controller, syncCtx); tt.wantErr != "" {
|
|
require.EqualError(t, err, tt.wantErr)
|
|
} else {
|
|
require.NoError(t, err)
|
|
}
|
|
require.Equal(t, strings.Join(tt.wantLogs, "\n"), strings.Join(testLog.Lines(), "\n"))
|
|
|
|
actualIDPList := cache.GetOIDCIdentityProviders()
|
|
require.Equal(t, len(tt.wantResultingCache), len(actualIDPList))
|
|
for i := range actualIDPList {
|
|
actualIDP := actualIDPList[i].(*upstreamoidc.ProviderConfig)
|
|
require.Equal(t, tt.wantResultingCache[i].GetName(), actualIDP.GetName())
|
|
require.Equal(t, tt.wantResultingCache[i].GetClientID(), actualIDP.GetClientID())
|
|
require.Equal(t, tt.wantResultingCache[i].GetAuthorizationURL().String(), actualIDP.GetAuthorizationURL().String())
|
|
require.Equal(t, tt.wantResultingCache[i].GetUsernameClaim(), actualIDP.GetUsernameClaim())
|
|
require.Equal(t, tt.wantResultingCache[i].GetGroupsClaim(), actualIDP.GetGroupsClaim())
|
|
require.ElementsMatch(t, tt.wantResultingCache[i].GetScopes(), actualIDP.GetScopes())
|
|
|
|
// We always want to use the proxy from env on these clients, so although the following assertions
|
|
// are a little hacky, this is a cheap way to test that we are using it.
|
|
actualTransport, ok := actualIDP.Client.Transport.(*http.Transport)
|
|
require.True(t, ok, "expected cached provider to have client with Transport of type *http.Transport")
|
|
httpProxyFromEnvFunction := reflect.ValueOf(http.ProxyFromEnvironment).Pointer()
|
|
actualTransportProxyFunction := reflect.ValueOf(actualTransport.Proxy).Pointer()
|
|
require.Equal(t, httpProxyFromEnvFunction, actualTransportProxyFunction,
|
|
"Transport should have used http.ProxyFromEnvironment as its Proxy func")
|
|
}
|
|
|
|
actualUpstreams, err := fakePinnipedClient.IDPV1alpha1().OIDCIdentityProviders(testNamespace).List(ctx, metav1.ListOptions{})
|
|
require.NoError(t, err)
|
|
|
|
// Assert on the expected Status of the upstreams. Preprocess the upstreams a bit so that they're easier to assert against.
|
|
require.ElementsMatch(t, tt.wantResultingUpstreams, normalizeOIDCUpstreams(actualUpstreams.Items, now))
|
|
|
|
// Running the sync() a second time should be idempotent except for logs, and should return the same error.
|
|
// This also helps exercise code paths where the OIDC provider discovery hits cache.
|
|
if err := controllerlib.TestSync(t, controller, syncCtx); tt.wantErr != "" {
|
|
require.EqualError(t, err, tt.wantErr)
|
|
} else {
|
|
require.NoError(t, err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func normalizeOIDCUpstreams(upstreams []v1alpha1.OIDCIdentityProvider, now metav1.Time) []v1alpha1.OIDCIdentityProvider {
|
|
result := make([]v1alpha1.OIDCIdentityProvider, 0, len(upstreams))
|
|
for _, u := range upstreams {
|
|
normalized := u.DeepCopy()
|
|
|
|
// We're only interested in comparing the status, so zero out the spec.
|
|
normalized.Spec = v1alpha1.OIDCIdentityProviderSpec{}
|
|
|
|
// Round down the LastTransitionTime values to `now` if they were just updated. This makes
|
|
// it much easier to encode assertions about the expected timestamps.
|
|
for i := range normalized.Status.Conditions {
|
|
if time.Since(normalized.Status.Conditions[i].LastTransitionTime.Time) < 5*time.Second {
|
|
normalized.Status.Conditions[i].LastTransitionTime = now
|
|
}
|
|
}
|
|
result = append(result, *normalized)
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
func newTestIssuer(t *testing.T) (string, string) {
|
|
mux := http.NewServeMux()
|
|
caBundlePEM, testURL := testutil.TLSTestServer(t, mux.ServeHTTP)
|
|
|
|
type providerJSON struct {
|
|
Issuer string `json:"issuer"`
|
|
AuthURL string `json:"authorization_endpoint"`
|
|
TokenURL string `json:"token_endpoint"`
|
|
JWKSURL string `json:"jwks_uri"`
|
|
}
|
|
|
|
// At the root of the server, serve an issuer with a valid discovery response.
|
|
mux.HandleFunc("/.well-known/openid-configuration", func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("content-type", "application/json")
|
|
_ = json.NewEncoder(w).Encode(&providerJSON{
|
|
Issuer: testURL,
|
|
AuthURL: "https://example.com/authorize",
|
|
})
|
|
})
|
|
|
|
// At "/invalid", serve an issuer that returns an invalid authorization URL (not parseable).
|
|
mux.HandleFunc("/invalid/.well-known/openid-configuration", func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("content-type", "application/json")
|
|
_ = json.NewEncoder(w).Encode(&providerJSON{
|
|
Issuer: testURL + "/invalid",
|
|
AuthURL: "%",
|
|
})
|
|
})
|
|
|
|
// At "/insecure", serve an issuer that returns an insecure authorization URL (not https://).
|
|
mux.HandleFunc("/insecure/.well-known/openid-configuration", func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("content-type", "application/json")
|
|
_ = json.NewEncoder(w).Encode(&providerJSON{
|
|
Issuer: testURL + "/insecure",
|
|
AuthURL: "http://example.com/authorize",
|
|
})
|
|
})
|
|
|
|
// handle the four issuer with trailing slash configs
|
|
|
|
// valid case in= out=
|
|
// handled above at the root of testURL
|
|
|
|
// valid case in=/ out=/
|
|
mux.HandleFunc("/ends-with-slash/.well-known/openid-configuration", func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("content-type", "application/json")
|
|
_ = json.NewEncoder(w).Encode(&providerJSON{
|
|
Issuer: testURL + "/ends-with-slash/",
|
|
AuthURL: "https://example.com/authorize",
|
|
})
|
|
})
|
|
|
|
// invalid case in= out=/
|
|
// can be tested using /ends-with-slash/ endpoint
|
|
|
|
// invalid case in=/ out=
|
|
// can be tested using root endpoint
|
|
|
|
return caBundlePEM, testURL
|
|
}
|