91a1fec5cf
This flag is (for now) meant only to facilitate end-to-end testing, allowing us to force the "manual" login flow. If it ends up being useful we can un-hide it, but this seemed like the safest option to start with. There is also a corresponding `--oidc-skip-listen` on the `pinniped get kubeconfig` command. Signed-off-by: Matt Moyer <moyerm@vmware.com>
2443 lines
97 KiB
Go
2443 lines
97 KiB
Go
// Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package cmd
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/base64"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"net/http"
|
|
"path/filepath"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/runtime"
|
|
kubetesting "k8s.io/client-go/testing"
|
|
"k8s.io/client-go/tools/clientcmd"
|
|
|
|
conciergev1alpha1 "go.pinniped.dev/generated/latest/apis/concierge/authentication/v1alpha1"
|
|
configv1alpha1 "go.pinniped.dev/generated/latest/apis/concierge/config/v1alpha1"
|
|
conciergeclientset "go.pinniped.dev/generated/latest/client/concierge/clientset/versioned"
|
|
fakeconciergeclientset "go.pinniped.dev/generated/latest/client/concierge/clientset/versioned/fake"
|
|
"go.pinniped.dev/internal/certauthority"
|
|
"go.pinniped.dev/internal/here"
|
|
"go.pinniped.dev/internal/testutil"
|
|
"go.pinniped.dev/internal/testutil/testlogger"
|
|
)
|
|
|
|
func TestGetKubeconfig(t *testing.T) {
|
|
testOIDCCA, err := certauthority.New("Test CA", 1*time.Hour)
|
|
require.NoError(t, err)
|
|
tmpdir := testutil.TempDir(t)
|
|
testOIDCCABundlePath := filepath.Join(tmpdir, "testca.pem")
|
|
require.NoError(t, ioutil.WriteFile(testOIDCCABundlePath, testOIDCCA.Bundle(), 0600))
|
|
|
|
testConciergeCA, err := certauthority.New("Test Concierge CA", 1*time.Hour)
|
|
require.NoError(t, err)
|
|
testConciergeCABundlePath := filepath.Join(tmpdir, "testconciergeca.pem")
|
|
require.NoError(t, ioutil.WriteFile(testConciergeCABundlePath, testConciergeCA.Bundle(), 0600))
|
|
|
|
credentialIssuer := func() runtime.Object {
|
|
return &configv1alpha1.CredentialIssuer{
|
|
ObjectMeta: metav1.ObjectMeta{Name: "test-credential-issuer"},
|
|
Status: configv1alpha1.CredentialIssuerStatus{
|
|
Strategies: []configv1alpha1.CredentialIssuerStrategy{{
|
|
Type: configv1alpha1.KubeClusterSigningCertificateStrategyType,
|
|
Status: configv1alpha1.SuccessStrategyStatus,
|
|
Reason: configv1alpha1.FetchedKeyStrategyReason,
|
|
Frontend: &configv1alpha1.CredentialIssuerFrontend{
|
|
Type: configv1alpha1.TokenCredentialRequestAPIFrontendType,
|
|
TokenCredentialRequestAPIInfo: &configv1alpha1.TokenCredentialRequestAPIInfo{
|
|
Server: "https://concierge-endpoint.example.com",
|
|
CertificateAuthorityData: base64.StdEncoding.EncodeToString(testConciergeCA.Bundle()),
|
|
},
|
|
},
|
|
}},
|
|
},
|
|
}
|
|
}
|
|
|
|
jwtAuthenticator := func(issuerCABundle string, issuerURL string) runtime.Object {
|
|
return &conciergev1alpha1.JWTAuthenticator{
|
|
ObjectMeta: metav1.ObjectMeta{Name: "test-authenticator"},
|
|
Spec: conciergev1alpha1.JWTAuthenticatorSpec{
|
|
Issuer: issuerURL,
|
|
Audience: "test-audience",
|
|
TLS: &conciergev1alpha1.TLSSpec{
|
|
CertificateAuthorityData: base64.StdEncoding.EncodeToString([]byte(issuerCABundle)),
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
happyOIDCDiscoveryResponse := func(issuerURL string) string {
|
|
return here.Docf(`{
|
|
"issuer": "%s",
|
|
"other-key": "other-value",
|
|
"discovery.supervisor.pinniped.dev/v1alpha1": {
|
|
"pinniped_identity_providers_endpoint": "%s/v1alpha1/pinniped_identity_providers"
|
|
},
|
|
"another-key": "another-value"
|
|
}`, issuerURL, issuerURL)
|
|
}
|
|
|
|
onlyIssuerOIDCDiscoveryResponse := func(issuerURL string) string {
|
|
return here.Docf(`{
|
|
"issuer": "%s",
|
|
"other-key": "other-value"
|
|
}`, issuerURL)
|
|
}
|
|
|
|
tests := []struct {
|
|
name string
|
|
args func(string, string) []string
|
|
env map[string]string
|
|
getPathToSelfErr error
|
|
getClientsetErr error
|
|
conciergeObjects func(string, string) []runtime.Object
|
|
conciergeReactions []kubetesting.Reactor
|
|
oidcDiscoveryResponse func(string) string
|
|
oidcDiscoveryStatusCode int
|
|
idpsDiscoveryResponse string
|
|
idpsDiscoveryStatusCode int
|
|
wantLogs func(string, string) []string
|
|
wantError bool
|
|
wantStdout func(string, string) string
|
|
wantStderr func(string, string) string
|
|
wantOptionsCount int
|
|
wantAPIGroupSuffix string
|
|
}{
|
|
{
|
|
name: "help flag passed",
|
|
args: func(issuerCABundle string, issuerURL string) []string { return []string{"--help"} },
|
|
wantStdout: func(issuerCABundle string, issuerURL string) string {
|
|
return here.Doc(`
|
|
Generate a Pinniped-based kubeconfig for a cluster
|
|
|
|
Usage:
|
|
kubeconfig [flags]
|
|
|
|
Flags:
|
|
--concierge-api-group-suffix string Concierge API group suffix (default "pinniped.dev")
|
|
--concierge-authenticator-name string Concierge authenticator name (default: autodiscover)
|
|
--concierge-authenticator-type string Concierge authenticator type (e.g., 'webhook', 'jwt') (default: autodiscover)
|
|
--concierge-ca-bundle path Path to TLS certificate authority bundle (PEM format, optional, can be repeated) to use when connecting to the Concierge
|
|
--concierge-credential-issuer string Concierge CredentialIssuer object to use for autodiscovery (default: autodiscover)
|
|
--concierge-endpoint string API base for the Concierge endpoint
|
|
--concierge-mode mode Concierge mode of operation (default TokenCredentialRequestAPI)
|
|
--concierge-skip-wait Skip waiting for any pending Concierge strategies to become ready (default: false)
|
|
--credential-cache string Path to cluster-specific credentials cache
|
|
--generated-name-suffix string Suffix to append to generated cluster, context, user kubeconfig entries (default "-pinniped")
|
|
-h, --help help for kubeconfig
|
|
--kubeconfig string Path to kubeconfig file
|
|
--kubeconfig-context string Kubeconfig context name (default: current active context)
|
|
--no-concierge Generate a configuration which does not use the Concierge, but sends the credential to the cluster directly
|
|
--oidc-ca-bundle path Path to TLS certificate authority bundle (PEM format, optional, can be repeated)
|
|
--oidc-client-id string OpenID Connect client ID (default: autodiscover) (default "pinniped-cli")
|
|
--oidc-issuer string OpenID Connect issuer URL (default: autodiscover)
|
|
--oidc-listen-port uint16 TCP port for localhost listener (authorization code flow only)
|
|
--oidc-request-audience string Request a token with an alternate audience using RFC8693 token exchange
|
|
--oidc-scopes strings OpenID Connect scopes to request during login (default [offline_access,openid,pinniped:request-audience])
|
|
--oidc-session-cache string Path to OpenID Connect session cache file
|
|
--oidc-skip-browser During OpenID Connect login, skip opening the browser (just print the URL)
|
|
-o, --output string Output file path (default: stdout)
|
|
--skip-validation Skip final validation of the kubeconfig (default: false)
|
|
--static-token string Instead of doing an OIDC-based login, specify a static token
|
|
--static-token-env string Instead of doing an OIDC-based login, read a static token from the environment
|
|
--timeout duration Timeout for autodiscovery and validation (default 10m0s)
|
|
--upstream-identity-provider-name string The name of the upstream identity provider used during login with a Supervisor
|
|
--upstream-identity-provider-type string The type of the upstream identity provider used during login with a Supervisor (e.g. 'oidc', 'ldap')
|
|
`)
|
|
},
|
|
},
|
|
{
|
|
name: "invalid OIDC CA bundle path",
|
|
args: func(issuerCABundle string, issuerURL string) []string {
|
|
return []string{
|
|
"--oidc-ca-bundle", "./does/not/exist",
|
|
}
|
|
},
|
|
wantError: true,
|
|
wantStderr: func(issuerCABundle string, issuerURL string) string {
|
|
return `Error: invalid argument "./does/not/exist" for "--oidc-ca-bundle" flag: could not read CA bundle path: open ./does/not/exist: no such file or directory` + "\n"
|
|
},
|
|
},
|
|
{
|
|
name: "invalid Concierge CA bundle",
|
|
args: func(issuerCABundle string, issuerURL string) []string {
|
|
return []string{
|
|
"--kubeconfig", "./testdata/kubeconfig.yaml",
|
|
"--concierge-ca-bundle", "./does/not/exist",
|
|
}
|
|
},
|
|
wantError: true,
|
|
wantStderr: func(issuerCABundle string, issuerURL string) string {
|
|
return `Error: invalid argument "./does/not/exist" for "--concierge-ca-bundle" flag: could not read CA bundle path: open ./does/not/exist: no such file or directory` + "\n"
|
|
},
|
|
},
|
|
{
|
|
name: "invalid kubeconfig path",
|
|
args: func(issuerCABundle string, issuerURL string) []string {
|
|
return []string{
|
|
"--kubeconfig", "./does/not/exist",
|
|
}
|
|
},
|
|
wantError: true,
|
|
wantStderr: func(issuerCABundle string, issuerURL string) string {
|
|
return `Error: could not load --kubeconfig: stat ./does/not/exist: no such file or directory` + "\n"
|
|
},
|
|
},
|
|
{
|
|
name: "invalid kubeconfig context, missing",
|
|
args: func(issuerCABundle string, issuerURL string) []string {
|
|
return []string{
|
|
"--kubeconfig", "./testdata/kubeconfig.yaml",
|
|
"--kubeconfig-context", "invalid",
|
|
}
|
|
},
|
|
wantError: true,
|
|
wantStderr: func(issuerCABundle string, issuerURL string) string {
|
|
return `Error: could not load --kubeconfig/--kubeconfig-context: no such context "invalid"` + "\n"
|
|
},
|
|
},
|
|
{
|
|
name: "invalid kubeconfig context, missing cluster",
|
|
args: func(issuerCABundle string, issuerURL string) []string {
|
|
return []string{
|
|
"--kubeconfig", "./testdata/kubeconfig.yaml",
|
|
"--kubeconfig-context", "invalid-context-no-such-cluster",
|
|
}
|
|
},
|
|
wantError: true,
|
|
wantStderr: func(issuerCABundle string, issuerURL string) string {
|
|
return `Error: could not load --kubeconfig/--kubeconfig-context: no such cluster "invalid-cluster"` + "\n"
|
|
},
|
|
},
|
|
{
|
|
name: "invalid kubeconfig context, missing user",
|
|
args: func(issuerCABundle string, issuerURL string) []string {
|
|
return []string{
|
|
"--kubeconfig", "./testdata/kubeconfig.yaml",
|
|
"--kubeconfig-context", "invalid-context-no-such-user",
|
|
}
|
|
},
|
|
wantError: true,
|
|
wantStderr: func(issuerCABundle string, issuerURL string) string {
|
|
return `Error: could not load --kubeconfig/--kubeconfig-context: no such user "invalid-user"` + "\n"
|
|
},
|
|
},
|
|
{
|
|
name: "clientset creation failure",
|
|
args: func(issuerCABundle string, issuerURL string) []string {
|
|
return []string{
|
|
"--kubeconfig", "./testdata/kubeconfig.yaml",
|
|
}
|
|
},
|
|
getClientsetErr: fmt.Errorf("some kube error"),
|
|
wantError: true,
|
|
wantStderr: func(issuerCABundle string, issuerURL string) string {
|
|
return `Error: could not configure Kubernetes client: some kube error` + "\n"
|
|
},
|
|
},
|
|
{
|
|
name: "no credentialissuers",
|
|
args: func(issuerCABundle string, issuerURL string) []string {
|
|
return []string{
|
|
"--kubeconfig", "./testdata/kubeconfig.yaml",
|
|
}
|
|
},
|
|
wantError: true,
|
|
wantStderr: func(issuerCABundle string, issuerURL string) string {
|
|
return `Error: no CredentialIssuers were found` + "\n"
|
|
},
|
|
},
|
|
{
|
|
name: "credentialissuer not found",
|
|
args: func(issuerCABundle string, issuerURL string) []string {
|
|
return []string{
|
|
"--kubeconfig", "./testdata/kubeconfig.yaml",
|
|
"--concierge-credential-issuer", "does-not-exist",
|
|
}
|
|
},
|
|
conciergeObjects: func(issuerCABundle string, issuerURL string) []runtime.Object {
|
|
return []runtime.Object{
|
|
&configv1alpha1.CredentialIssuer{ObjectMeta: metav1.ObjectMeta{Name: "test-credential-issuer"}},
|
|
}
|
|
},
|
|
wantError: true,
|
|
wantStderr: func(issuerCABundle string, issuerURL string) string {
|
|
return `Error: credentialissuers.config.concierge.pinniped.dev "does-not-exist" not found` + "\n"
|
|
},
|
|
},
|
|
{
|
|
name: "webhook authenticator not found",
|
|
args: func(issuerCABundle string, issuerURL string) []string {
|
|
return []string{
|
|
"--kubeconfig", "./testdata/kubeconfig.yaml",
|
|
"--concierge-authenticator-type", "webhook",
|
|
"--concierge-authenticator-name", "test-authenticator",
|
|
}
|
|
},
|
|
conciergeObjects: func(issuerCABundle string, issuerURL string) []runtime.Object {
|
|
return []runtime.Object{
|
|
&configv1alpha1.CredentialIssuer{ObjectMeta: metav1.ObjectMeta{Name: "test-credential-issuer"}},
|
|
}
|
|
},
|
|
wantLogs: func(issuerCABundle string, issuerURL string) []string {
|
|
return []string{
|
|
`"level"=0 "msg"="discovered CredentialIssuer" "name"="test-credential-issuer"`,
|
|
}
|
|
},
|
|
wantError: true,
|
|
wantStderr: func(issuerCABundle string, issuerURL string) string {
|
|
return `Error: webhookauthenticators.authentication.concierge.pinniped.dev "test-authenticator" not found` + "\n"
|
|
},
|
|
},
|
|
{
|
|
name: "JWT authenticator not found",
|
|
args: func(issuerCABundle string, issuerURL string) []string {
|
|
return []string{
|
|
"--kubeconfig", "./testdata/kubeconfig.yaml",
|
|
"--concierge-authenticator-type", "jwt",
|
|
"--concierge-authenticator-name", "test-authenticator",
|
|
}
|
|
},
|
|
conciergeObjects: func(issuerCABundle string, issuerURL string) []runtime.Object {
|
|
return []runtime.Object{
|
|
&configv1alpha1.CredentialIssuer{ObjectMeta: metav1.ObjectMeta{Name: "test-credential-issuer"}},
|
|
}
|
|
},
|
|
wantLogs: func(issuerCABundle string, issuerURL string) []string {
|
|
return []string{
|
|
`"level"=0 "msg"="discovered CredentialIssuer" "name"="test-credential-issuer"`,
|
|
}
|
|
},
|
|
wantError: true,
|
|
wantStderr: func(issuerCABundle string, issuerURL string) string {
|
|
return `Error: jwtauthenticators.authentication.concierge.pinniped.dev "test-authenticator" not found` + "\n"
|
|
},
|
|
},
|
|
{
|
|
name: "invalid authenticator type",
|
|
args: func(issuerCABundle string, issuerURL string) []string {
|
|
return []string{
|
|
"--kubeconfig", "./testdata/kubeconfig.yaml",
|
|
"--concierge-authenticator-type", "invalid",
|
|
"--concierge-authenticator-name", "test-authenticator",
|
|
}
|
|
},
|
|
conciergeObjects: func(issuerCABundle string, issuerURL string) []runtime.Object {
|
|
return []runtime.Object{
|
|
&configv1alpha1.CredentialIssuer{ObjectMeta: metav1.ObjectMeta{Name: "test-credential-issuer"}},
|
|
}
|
|
},
|
|
wantLogs: func(issuerCABundle string, issuerURL string) []string {
|
|
return []string{
|
|
`"level"=0 "msg"="discovered CredentialIssuer" "name"="test-credential-issuer"`,
|
|
}
|
|
},
|
|
wantError: true,
|
|
wantStderr: func(issuerCABundle string, issuerURL string) string {
|
|
return `Error: invalid authenticator type "invalid", supported values are "webhook" and "jwt"` + "\n"
|
|
},
|
|
},
|
|
{
|
|
name: "fail to autodetect authenticator, listing jwtauthenticators fails",
|
|
args: func(issuerCABundle string, issuerURL string) []string {
|
|
return []string{
|
|
"--kubeconfig", "./testdata/kubeconfig.yaml",
|
|
}
|
|
},
|
|
conciergeObjects: func(issuerCABundle string, issuerURL string) []runtime.Object {
|
|
return []runtime.Object{
|
|
&configv1alpha1.CredentialIssuer{ObjectMeta: metav1.ObjectMeta{Name: "test-credential-issuer"}},
|
|
}
|
|
},
|
|
wantLogs: func(issuerCABundle string, issuerURL string) []string {
|
|
return []string{
|
|
`"level"=0 "msg"="discovered CredentialIssuer" "name"="test-credential-issuer"`,
|
|
}
|
|
},
|
|
conciergeReactions: []kubetesting.Reactor{
|
|
&kubetesting.SimpleReactor{
|
|
Verb: "*",
|
|
Resource: "jwtauthenticators",
|
|
Reaction: func(kubetesting.Action) (bool, runtime.Object, error) {
|
|
return true, nil, fmt.Errorf("some list error")
|
|
},
|
|
},
|
|
},
|
|
wantError: true,
|
|
wantStderr: func(issuerCABundle string, issuerURL string) string {
|
|
return `Error: failed to list JWTAuthenticator objects for autodiscovery: some list error` + "\n"
|
|
},
|
|
},
|
|
{
|
|
name: "fail to autodetect authenticator, listing webhookauthenticators fails",
|
|
args: func(issuerCABundle string, issuerURL string) []string {
|
|
return []string{
|
|
"--kubeconfig", "./testdata/kubeconfig.yaml",
|
|
}
|
|
},
|
|
conciergeObjects: func(issuerCABundle string, issuerURL string) []runtime.Object {
|
|
return []runtime.Object{
|
|
&configv1alpha1.CredentialIssuer{ObjectMeta: metav1.ObjectMeta{Name: "test-credential-issuer"}},
|
|
}
|
|
},
|
|
conciergeReactions: []kubetesting.Reactor{
|
|
&kubetesting.SimpleReactor{
|
|
Verb: "*",
|
|
Resource: "webhookauthenticators",
|
|
Reaction: func(kubetesting.Action) (bool, runtime.Object, error) {
|
|
return true, nil, fmt.Errorf("some list error")
|
|
},
|
|
},
|
|
},
|
|
wantLogs: func(issuerCABundle string, issuerURL string) []string {
|
|
return []string{
|
|
`"level"=0 "msg"="discovered CredentialIssuer" "name"="test-credential-issuer"`,
|
|
}
|
|
},
|
|
wantError: true,
|
|
wantStderr: func(issuerCABundle string, issuerURL string) string {
|
|
return `Error: failed to list WebhookAuthenticator objects for autodiscovery: some list error` + "\n"
|
|
},
|
|
},
|
|
{
|
|
name: "fail to autodetect authenticator, none found",
|
|
args: func(issuerCABundle string, issuerURL string) []string {
|
|
return []string{
|
|
"--kubeconfig", "./testdata/kubeconfig.yaml",
|
|
}
|
|
},
|
|
conciergeObjects: func(issuerCABundle string, issuerURL string) []runtime.Object {
|
|
return []runtime.Object{
|
|
&configv1alpha1.CredentialIssuer{ObjectMeta: metav1.ObjectMeta{Name: "test-credential-issuer"}},
|
|
}
|
|
},
|
|
wantLogs: func(issuerCABundle string, issuerURL string) []string {
|
|
return []string{
|
|
`"level"=0 "msg"="discovered CredentialIssuer" "name"="test-credential-issuer"`,
|
|
}
|
|
},
|
|
wantError: true,
|
|
wantStderr: func(issuerCABundle string, issuerURL string) string {
|
|
return `Error: no authenticators were found` + "\n"
|
|
},
|
|
},
|
|
{
|
|
name: "fail to autodetect authenticator, multiple found",
|
|
args: func(issuerCABundle string, issuerURL string) []string {
|
|
return []string{
|
|
"--kubeconfig", "./testdata/kubeconfig.yaml",
|
|
}
|
|
},
|
|
conciergeObjects: func(issuerCABundle string, issuerURL string) []runtime.Object {
|
|
return []runtime.Object{
|
|
&configv1alpha1.CredentialIssuer{ObjectMeta: metav1.ObjectMeta{Name: "test-credential-issuer"}},
|
|
&conciergev1alpha1.JWTAuthenticator{ObjectMeta: metav1.ObjectMeta{Name: "test-authenticator-1"}},
|
|
&conciergev1alpha1.JWTAuthenticator{ObjectMeta: metav1.ObjectMeta{Name: "test-authenticator-2"}},
|
|
&conciergev1alpha1.WebhookAuthenticator{ObjectMeta: metav1.ObjectMeta{Name: "test-authenticator-3"}},
|
|
&conciergev1alpha1.WebhookAuthenticator{ObjectMeta: metav1.ObjectMeta{Name: "test-authenticator-4"}},
|
|
}
|
|
},
|
|
wantLogs: func(issuerCABundle string, issuerURL string) []string {
|
|
return []string{
|
|
`"level"=0 "msg"="discovered CredentialIssuer" "name"="test-credential-issuer"`,
|
|
`"level"=0 "msg"="found JWTAuthenticator" "name"="test-authenticator-1"`,
|
|
`"level"=0 "msg"="found JWTAuthenticator" "name"="test-authenticator-2"`,
|
|
`"level"=0 "msg"="found WebhookAuthenticator" "name"="test-authenticator-3"`,
|
|
`"level"=0 "msg"="found WebhookAuthenticator" "name"="test-authenticator-4"`,
|
|
}
|
|
},
|
|
wantError: true,
|
|
wantStderr: func(issuerCABundle string, issuerURL string) string {
|
|
return `Error: multiple authenticators were found, so the --concierge-authenticator-type/--concierge-authenticator-name flags must be specified` + "\n"
|
|
},
|
|
},
|
|
{
|
|
name: "autodetect webhook authenticator, bad credential issuer with only failing strategy",
|
|
args: func(issuerCABundle string, issuerURL string) []string {
|
|
return []string{
|
|
"--kubeconfig", "./testdata/kubeconfig.yaml",
|
|
}
|
|
},
|
|
conciergeObjects: func(issuerCABundle string, issuerURL string) []runtime.Object {
|
|
return []runtime.Object{
|
|
&configv1alpha1.CredentialIssuer{
|
|
ObjectMeta: metav1.ObjectMeta{Name: "test-credential-issuer"},
|
|
Status: configv1alpha1.CredentialIssuerStatus{
|
|
Strategies: []configv1alpha1.CredentialIssuerStrategy{{
|
|
Type: "SomeType",
|
|
Status: configv1alpha1.ErrorStrategyStatus,
|
|
Reason: "SomeReason",
|
|
Message: "Some message",
|
|
}},
|
|
},
|
|
},
|
|
&conciergev1alpha1.WebhookAuthenticator{ObjectMeta: metav1.ObjectMeta{Name: "test-authenticator"}},
|
|
}
|
|
},
|
|
wantLogs: func(issuerCABundle string, issuerURL string) []string {
|
|
return []string{
|
|
`"level"=0 "msg"="discovered CredentialIssuer" "name"="test-credential-issuer"`,
|
|
`"level"=0 "msg"="found CredentialIssuer strategy" "message"="Some message" "reason"="SomeReason" "status"="Error" "type"="SomeType"`,
|
|
}
|
|
},
|
|
wantError: true,
|
|
wantStderr: func(issuerCABundle string, issuerURL string) string {
|
|
return `Error: could not autodiscover --concierge-mode` + "\n"
|
|
},
|
|
},
|
|
{
|
|
name: "autodetect webhook authenticator, bad credential issuer with invalid impersonation CA",
|
|
args: func(issuerCABundle string, issuerURL string) []string {
|
|
return []string{
|
|
"--kubeconfig", "./testdata/kubeconfig.yaml",
|
|
}
|
|
},
|
|
conciergeObjects: func(issuerCABundle string, issuerURL string) []runtime.Object {
|
|
return []runtime.Object{
|
|
&configv1alpha1.CredentialIssuer{
|
|
ObjectMeta: metav1.ObjectMeta{Name: "test-credential-issuer"},
|
|
Status: configv1alpha1.CredentialIssuerStatus{
|
|
Strategies: []configv1alpha1.CredentialIssuerStrategy{
|
|
{
|
|
Type: "SomeBrokenType",
|
|
Status: configv1alpha1.ErrorStrategyStatus,
|
|
Reason: "SomeFailureReason",
|
|
Message: "Some error message",
|
|
LastUpdateTime: metav1.Now(),
|
|
},
|
|
{
|
|
Type: "SomeUnknownType",
|
|
Status: configv1alpha1.SuccessStrategyStatus,
|
|
Reason: "SomeReason",
|
|
Message: "Some error message",
|
|
LastUpdateTime: metav1.Now(),
|
|
Frontend: &configv1alpha1.CredentialIssuerFrontend{
|
|
Type: "SomeUnknownFrontendType",
|
|
},
|
|
},
|
|
{
|
|
Type: "SomeType",
|
|
Status: configv1alpha1.SuccessStrategyStatus,
|
|
Reason: "SomeReason",
|
|
Message: "Some message",
|
|
LastUpdateTime: metav1.Now(),
|
|
Frontend: &configv1alpha1.CredentialIssuerFrontend{
|
|
Type: configv1alpha1.ImpersonationProxyFrontendType,
|
|
ImpersonationProxyInfo: &configv1alpha1.ImpersonationProxyInfo{
|
|
Endpoint: "https://impersonation-endpoint",
|
|
CertificateAuthorityData: "invalid-base-64",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
&conciergev1alpha1.WebhookAuthenticator{ObjectMeta: metav1.ObjectMeta{Name: "test-authenticator"}},
|
|
}
|
|
},
|
|
wantLogs: func(issuerCABundle string, issuerURL string) []string {
|
|
return []string{
|
|
`"level"=0 "msg"="discovered CredentialIssuer" "name"="test-credential-issuer"`,
|
|
`"level"=0 "msg"="discovered Concierge operating in impersonation proxy mode"`,
|
|
`"level"=0 "msg"="discovered Concierge endpoint" "endpoint"="https://impersonation-endpoint"`,
|
|
}
|
|
},
|
|
wantError: true,
|
|
wantStderr: func(issuerCABundle string, issuerURL string) string {
|
|
return `Error: autodiscovered Concierge CA bundle is invalid: illegal base64 data at input byte 7` + "\n"
|
|
},
|
|
},
|
|
{
|
|
name: "autodetect webhook authenticator, missing --oidc-issuer",
|
|
args: func(issuerCABundle string, issuerURL string) []string {
|
|
return []string{
|
|
"--kubeconfig", "./testdata/kubeconfig.yaml",
|
|
}
|
|
},
|
|
conciergeObjects: func(issuerCABundle string, issuerURL string) []runtime.Object {
|
|
return []runtime.Object{
|
|
credentialIssuer(),
|
|
&conciergev1alpha1.WebhookAuthenticator{ObjectMeta: metav1.ObjectMeta{Name: "test-authenticator"}},
|
|
}
|
|
},
|
|
wantLogs: func(issuerCABundle string, issuerURL string) []string {
|
|
return []string{
|
|
`"level"=0 "msg"="discovered CredentialIssuer" "name"="test-credential-issuer"`,
|
|
`"level"=0 "msg"="discovered Concierge operating in TokenCredentialRequest API mode"`,
|
|
`"level"=0 "msg"="discovered Concierge endpoint" "endpoint"="https://fake-server-url-value"`,
|
|
`"level"=0 "msg"="discovered Concierge certificate authority bundle" "roots"=0`,
|
|
`"level"=0 "msg"="discovered WebhookAuthenticator" "name"="test-authenticator"`,
|
|
}
|
|
},
|
|
wantError: true,
|
|
wantStderr: func(issuerCABundle string, issuerURL string) string {
|
|
return `Error: could not autodiscover --oidc-issuer and none was provided` + "\n"
|
|
},
|
|
},
|
|
{
|
|
name: "autodetect JWT authenticator, invalid TLS bundle",
|
|
args: func(issuerCABundle string, issuerURL string) []string {
|
|
return []string{
|
|
"--kubeconfig", "./testdata/kubeconfig.yaml",
|
|
}
|
|
},
|
|
conciergeObjects: func(issuerCABundle string, issuerURL string) []runtime.Object {
|
|
return []runtime.Object{
|
|
&configv1alpha1.CredentialIssuer{
|
|
ObjectMeta: metav1.ObjectMeta{Name: "test-credential-issuer"},
|
|
Status: configv1alpha1.CredentialIssuerStatus{
|
|
KubeConfigInfo: &configv1alpha1.CredentialIssuerKubeConfigInfo{
|
|
Server: "https://concierge-endpoint",
|
|
CertificateAuthorityData: "ZmFrZS1jZXJ0aWZpY2F0ZS1hdXRob3JpdHktZGF0YS12YWx1ZQ==",
|
|
},
|
|
Strategies: []configv1alpha1.CredentialIssuerStrategy{{
|
|
Type: configv1alpha1.KubeClusterSigningCertificateStrategyType,
|
|
Status: configv1alpha1.SuccessStrategyStatus,
|
|
Reason: configv1alpha1.FetchedKeyStrategyReason,
|
|
Message: "Successfully fetched key",
|
|
LastUpdateTime: metav1.Now(),
|
|
// Simulate a previous version of CredentialIssuer that's missing this Frontend field.
|
|
Frontend: nil,
|
|
}},
|
|
},
|
|
},
|
|
&conciergev1alpha1.JWTAuthenticator{
|
|
ObjectMeta: metav1.ObjectMeta{Name: "test-authenticator"},
|
|
Spec: conciergev1alpha1.JWTAuthenticatorSpec{
|
|
Issuer: issuerURL,
|
|
Audience: "some-test-audience",
|
|
TLS: &conciergev1alpha1.TLSSpec{
|
|
CertificateAuthorityData: "invalid-base64",
|
|
},
|
|
},
|
|
},
|
|
}
|
|
},
|
|
wantLogs: func(issuerCABundle string, issuerURL string) []string {
|
|
return []string{
|
|
`"level"=0 "msg"="discovered CredentialIssuer" "name"="test-credential-issuer"`,
|
|
`"level"=0 "msg"="discovered Concierge operating in TokenCredentialRequest API mode"`,
|
|
`"level"=0 "msg"="discovered Concierge endpoint" "endpoint"="https://fake-server-url-value"`,
|
|
`"level"=0 "msg"="discovered Concierge certificate authority bundle" "roots"=0`,
|
|
`"level"=0 "msg"="discovered JWTAuthenticator" "name"="test-authenticator"`,
|
|
fmt.Sprintf(`"level"=0 "msg"="discovered OIDC issuer" "issuer"="%s"`, issuerURL),
|
|
`"level"=0 "msg"="discovered OIDC audience" "audience"="some-test-audience"`,
|
|
}
|
|
},
|
|
wantError: true,
|
|
wantStderr: func(issuerCABundle string, issuerURL string) string {
|
|
return `Error: tried to autodiscover --oidc-ca-bundle, but JWTAuthenticator test-authenticator has invalid spec.tls.certificateAuthorityData: illegal base64 data at input byte 7` + "\n"
|
|
},
|
|
},
|
|
{
|
|
name: "fail to get self-path",
|
|
args: func(issuerCABundle string, issuerURL string) []string {
|
|
return []string{
|
|
"--kubeconfig", "./testdata/kubeconfig.yaml",
|
|
}
|
|
},
|
|
getPathToSelfErr: fmt.Errorf("some OS error"),
|
|
conciergeObjects: func(issuerCABundle string, issuerURL string) []runtime.Object {
|
|
return []runtime.Object{
|
|
credentialIssuer(),
|
|
jwtAuthenticator(issuerCABundle, issuerURL),
|
|
}
|
|
},
|
|
oidcDiscoveryResponse: onlyIssuerOIDCDiscoveryResponse,
|
|
wantLogs: func(issuerCABundle string, issuerURL string) []string {
|
|
return []string{
|
|
`"level"=0 "msg"="discovered CredentialIssuer" "name"="test-credential-issuer"`,
|
|
`"level"=0 "msg"="discovered Concierge operating in TokenCredentialRequest API mode"`,
|
|
`"level"=0 "msg"="discovered Concierge endpoint" "endpoint"="https://fake-server-url-value"`,
|
|
`"level"=0 "msg"="discovered Concierge certificate authority bundle" "roots"=0`,
|
|
`"level"=0 "msg"="discovered JWTAuthenticator" "name"="test-authenticator"`,
|
|
fmt.Sprintf(`"level"=0 "msg"="discovered OIDC issuer" "issuer"="%s"`, issuerURL),
|
|
`"level"=0 "msg"="discovered OIDC audience" "audience"="test-audience"`,
|
|
`"level"=0 "msg"="discovered OIDC CA bundle" "roots"=1`,
|
|
}
|
|
},
|
|
wantError: true,
|
|
wantStderr: func(issuerCABundle string, issuerURL string) string {
|
|
return `Error: could not determine the Pinniped executable path: some OS error` + "\n"
|
|
},
|
|
},
|
|
{
|
|
name: "invalid static token flags",
|
|
args: func(issuerCABundle string, issuerURL string) []string {
|
|
return []string{
|
|
"--kubeconfig", "./testdata/kubeconfig.yaml",
|
|
"--static-token", "test-token",
|
|
"--static-token-env", "TEST_TOKEN",
|
|
}
|
|
},
|
|
conciergeObjects: func(issuerCABundle string, issuerURL string) []runtime.Object {
|
|
return []runtime.Object{
|
|
credentialIssuer(),
|
|
&conciergev1alpha1.WebhookAuthenticator{ObjectMeta: metav1.ObjectMeta{Name: "test-authenticator"}},
|
|
}
|
|
},
|
|
wantLogs: func(issuerCABundle string, issuerURL string) []string {
|
|
return []string{
|
|
`"level"=0 "msg"="discovered CredentialIssuer" "name"="test-credential-issuer"`,
|
|
`"level"=0 "msg"="discovered Concierge operating in TokenCredentialRequest API mode"`,
|
|
`"level"=0 "msg"="discovered Concierge endpoint" "endpoint"="https://fake-server-url-value"`,
|
|
`"level"=0 "msg"="discovered Concierge certificate authority bundle" "roots"=0`,
|
|
`"level"=0 "msg"="discovered WebhookAuthenticator" "name"="test-authenticator"`,
|
|
}
|
|
},
|
|
wantError: true,
|
|
wantStderr: func(issuerCABundle string, issuerURL string) string {
|
|
return `Error: only one of --static-token and --static-token-env can be specified` + "\n"
|
|
},
|
|
},
|
|
{
|
|
name: "invalid API group suffix",
|
|
args: func(issuerCABundle string, issuerURL string) []string {
|
|
return []string{
|
|
"--concierge-api-group-suffix", ".starts.with.dot",
|
|
}
|
|
},
|
|
wantError: true,
|
|
wantStderr: func(issuerCABundle string, issuerURL string) string {
|
|
return `Error: invalid API group suffix: a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character (e.g. 'example.com', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*')` + "\n"
|
|
},
|
|
},
|
|
{
|
|
name: "when OIDC discovery document 400s",
|
|
args: func(issuerCABundle string, issuerURL string) []string {
|
|
return []string{
|
|
"--kubeconfig", "./testdata/kubeconfig.yaml",
|
|
"--skip-validation",
|
|
}
|
|
},
|
|
conciergeObjects: func(issuerCABundle string, issuerURL string) []runtime.Object {
|
|
return []runtime.Object{
|
|
credentialIssuer(),
|
|
jwtAuthenticator(issuerCABundle, issuerURL),
|
|
}
|
|
},
|
|
oidcDiscoveryStatusCode: http.StatusBadRequest,
|
|
wantLogs: func(issuerCABundle string, issuerURL string) []string {
|
|
return []string{
|
|
`"level"=0 "msg"="discovered CredentialIssuer" "name"="test-credential-issuer"`,
|
|
`"level"=0 "msg"="discovered Concierge operating in TokenCredentialRequest API mode"`,
|
|
`"level"=0 "msg"="discovered Concierge endpoint" "endpoint"="https://fake-server-url-value"`,
|
|
`"level"=0 "msg"="discovered Concierge certificate authority bundle" "roots"=0`,
|
|
`"level"=0 "msg"="discovered JWTAuthenticator" "name"="test-authenticator"`,
|
|
fmt.Sprintf(`"level"=0 "msg"="discovered OIDC issuer" "issuer"="%s"`, issuerURL),
|
|
`"level"=0 "msg"="discovered OIDC audience" "audience"="test-audience"`,
|
|
`"level"=0 "msg"="discovered OIDC CA bundle" "roots"=1`,
|
|
}
|
|
},
|
|
wantError: true,
|
|
wantStderr: func(issuerCABundle string, issuerURL string) string {
|
|
return "Error: while fetching OIDC discovery data from issuer: 400 Bad Request: {}\n"
|
|
},
|
|
},
|
|
{
|
|
name: "when OIDC discovery document lists the wrong issuer",
|
|
args: func(issuerCABundle string, issuerURL string) []string {
|
|
return []string{
|
|
"--kubeconfig", "./testdata/kubeconfig.yaml",
|
|
"--skip-validation",
|
|
}
|
|
},
|
|
conciergeObjects: func(issuerCABundle string, issuerURL string) []runtime.Object {
|
|
return []runtime.Object{
|
|
credentialIssuer(),
|
|
jwtAuthenticator(issuerCABundle, issuerURL),
|
|
}
|
|
},
|
|
oidcDiscoveryResponse: func(issuerURL string) string {
|
|
return here.Doc(`{
|
|
"issuer": "https://wrong-issuer.com"
|
|
}`)
|
|
},
|
|
wantLogs: func(issuerCABundle string, issuerURL string) []string {
|
|
return []string{
|
|
`"level"=0 "msg"="discovered CredentialIssuer" "name"="test-credential-issuer"`,
|
|
`"level"=0 "msg"="discovered Concierge operating in TokenCredentialRequest API mode"`,
|
|
`"level"=0 "msg"="discovered Concierge endpoint" "endpoint"="https://fake-server-url-value"`,
|
|
`"level"=0 "msg"="discovered Concierge certificate authority bundle" "roots"=0`,
|
|
`"level"=0 "msg"="discovered JWTAuthenticator" "name"="test-authenticator"`,
|
|
fmt.Sprintf(`"level"=0 "msg"="discovered OIDC issuer" "issuer"="%s"`, issuerURL),
|
|
`"level"=0 "msg"="discovered OIDC audience" "audience"="test-audience"`,
|
|
`"level"=0 "msg"="discovered OIDC CA bundle" "roots"=1`,
|
|
}
|
|
},
|
|
wantError: true,
|
|
wantStderr: func(issuerCABundle string, issuerURL string) string {
|
|
return fmt.Sprintf(
|
|
"Error: while fetching OIDC discovery data from issuer: oidc: issuer did not match the issuer returned by provider, expected \"%s\" got \"https://wrong-issuer.com\"\n",
|
|
issuerURL)
|
|
},
|
|
},
|
|
{
|
|
name: "when IDP discovery document returns any error",
|
|
args: func(issuerCABundle string, issuerURL string) []string {
|
|
return []string{
|
|
"--kubeconfig", "./testdata/kubeconfig.yaml",
|
|
"--skip-validation",
|
|
}
|
|
},
|
|
conciergeObjects: func(issuerCABundle string, issuerURL string) []runtime.Object {
|
|
return []runtime.Object{
|
|
credentialIssuer(),
|
|
jwtAuthenticator(issuerCABundle, issuerURL),
|
|
}
|
|
},
|
|
oidcDiscoveryResponse: happyOIDCDiscoveryResponse,
|
|
idpsDiscoveryStatusCode: http.StatusBadRequest,
|
|
wantLogs: func(issuerCABundle string, issuerURL string) []string {
|
|
return []string{
|
|
`"level"=0 "msg"="discovered CredentialIssuer" "name"="test-credential-issuer"`,
|
|
`"level"=0 "msg"="discovered Concierge operating in TokenCredentialRequest API mode"`,
|
|
`"level"=0 "msg"="discovered Concierge endpoint" "endpoint"="https://fake-server-url-value"`,
|
|
`"level"=0 "msg"="discovered Concierge certificate authority bundle" "roots"=0`,
|
|
`"level"=0 "msg"="discovered JWTAuthenticator" "name"="test-authenticator"`,
|
|
fmt.Sprintf(`"level"=0 "msg"="discovered OIDC issuer" "issuer"="%s"`, issuerURL),
|
|
`"level"=0 "msg"="discovered OIDC audience" "audience"="test-audience"`,
|
|
`"level"=0 "msg"="discovered OIDC CA bundle" "roots"=1`,
|
|
}
|
|
},
|
|
wantError: true,
|
|
wantStderr: func(issuerCABundle string, issuerURL string) string {
|
|
return "Error: unable to fetch IDP discovery data from issuer: unexpected http response status: 400 Bad Request\n"
|
|
},
|
|
},
|
|
{
|
|
name: "when IDP discovery document contains multiple pinniped_idps and no name or type flags are given",
|
|
args: func(issuerCABundle string, issuerURL string) []string {
|
|
return []string{
|
|
"--kubeconfig", "./testdata/kubeconfig.yaml",
|
|
"--skip-validation",
|
|
}
|
|
},
|
|
conciergeObjects: func(issuerCABundle string, issuerURL string) []runtime.Object {
|
|
return []runtime.Object{
|
|
credentialIssuer(),
|
|
jwtAuthenticator(issuerCABundle, issuerURL),
|
|
}
|
|
},
|
|
oidcDiscoveryResponse: happyOIDCDiscoveryResponse,
|
|
idpsDiscoveryResponse: here.Docf(`{
|
|
"pinniped_identity_providers": [
|
|
{"name": "some-ldap-idp", "type": "ldap"},
|
|
{"name": "some-oidc-idp", "type": "oidc"}
|
|
]
|
|
}`),
|
|
wantLogs: func(issuerCABundle string, issuerURL string) []string {
|
|
return []string{
|
|
`"level"=0 "msg"="discovered CredentialIssuer" "name"="test-credential-issuer"`,
|
|
`"level"=0 "msg"="discovered Concierge operating in TokenCredentialRequest API mode"`,
|
|
`"level"=0 "msg"="discovered Concierge endpoint" "endpoint"="https://fake-server-url-value"`,
|
|
`"level"=0 "msg"="discovered Concierge certificate authority bundle" "roots"=0`,
|
|
`"level"=0 "msg"="discovered JWTAuthenticator" "name"="test-authenticator"`,
|
|
fmt.Sprintf(`"level"=0 "msg"="discovered OIDC issuer" "issuer"="%s"`, issuerURL),
|
|
`"level"=0 "msg"="discovered OIDC audience" "audience"="test-audience"`,
|
|
`"level"=0 "msg"="discovered OIDC CA bundle" "roots"=1`,
|
|
}
|
|
},
|
|
wantError: true,
|
|
wantStderr: func(issuerCABundle string, issuerURL string) string {
|
|
return `Error: multiple Supervisor upstream identity providers were found, ` +
|
|
`so the --upstream-identity-provider-name/--upstream-identity-provider-type flags must be specified. ` +
|
|
`Found these upstreams: [{"name":"some-ldap-idp","type":"ldap"},{"name":"some-oidc-idp","type":"oidc"}]` + "\n"
|
|
},
|
|
},
|
|
{
|
|
name: "when OIDC discovery document is not valid JSON",
|
|
args: func(issuerCABundle string, issuerURL string) []string {
|
|
return []string{
|
|
"--kubeconfig", "./testdata/kubeconfig.yaml",
|
|
"--skip-validation",
|
|
}
|
|
},
|
|
conciergeObjects: func(issuerCABundle string, issuerURL string) []runtime.Object {
|
|
return []runtime.Object{
|
|
credentialIssuer(),
|
|
jwtAuthenticator(issuerCABundle, issuerURL),
|
|
}
|
|
},
|
|
oidcDiscoveryResponse: func(issuerURL string) string {
|
|
return "this is not valid JSON"
|
|
},
|
|
wantLogs: func(issuerCABundle string, issuerURL string) []string {
|
|
return []string{
|
|
`"level"=0 "msg"="discovered CredentialIssuer" "name"="test-credential-issuer"`,
|
|
`"level"=0 "msg"="discovered Concierge operating in TokenCredentialRequest API mode"`,
|
|
`"level"=0 "msg"="discovered Concierge endpoint" "endpoint"="https://fake-server-url-value"`,
|
|
`"level"=0 "msg"="discovered Concierge certificate authority bundle" "roots"=0`,
|
|
`"level"=0 "msg"="discovered JWTAuthenticator" "name"="test-authenticator"`,
|
|
fmt.Sprintf(`"level"=0 "msg"="discovered OIDC issuer" "issuer"="%s"`, issuerURL),
|
|
`"level"=0 "msg"="discovered OIDC audience" "audience"="test-audience"`,
|
|
`"level"=0 "msg"="discovered OIDC CA bundle" "roots"=1`,
|
|
}
|
|
},
|
|
wantError: true,
|
|
wantStderr: func(issuerCABundle string, issuerURL string) string {
|
|
return "Error: while fetching OIDC discovery data from issuer: oidc: failed to decode provider discovery object: got Content-Type = application/json, but could not unmarshal as JSON: invalid character 'h' in literal true (expecting 'r')\n"
|
|
},
|
|
},
|
|
{
|
|
name: "when IDP discovery document is not valid JSON",
|
|
args: func(issuerCABundle string, issuerURL string) []string {
|
|
return []string{
|
|
"--kubeconfig", "./testdata/kubeconfig.yaml",
|
|
"--skip-validation",
|
|
}
|
|
},
|
|
conciergeObjects: func(issuerCABundle string, issuerURL string) []runtime.Object {
|
|
return []runtime.Object{
|
|
credentialIssuer(),
|
|
jwtAuthenticator(issuerCABundle, issuerURL),
|
|
}
|
|
},
|
|
oidcDiscoveryResponse: happyOIDCDiscoveryResponse,
|
|
idpsDiscoveryResponse: "this is not valid JSON",
|
|
wantLogs: func(issuerCABundle string, issuerURL string) []string {
|
|
return []string{
|
|
`"level"=0 "msg"="discovered CredentialIssuer" "name"="test-credential-issuer"`,
|
|
`"level"=0 "msg"="discovered Concierge operating in TokenCredentialRequest API mode"`,
|
|
`"level"=0 "msg"="discovered Concierge endpoint" "endpoint"="https://fake-server-url-value"`,
|
|
`"level"=0 "msg"="discovered Concierge certificate authority bundle" "roots"=0`,
|
|
`"level"=0 "msg"="discovered JWTAuthenticator" "name"="test-authenticator"`,
|
|
fmt.Sprintf(`"level"=0 "msg"="discovered OIDC issuer" "issuer"="%s"`, issuerURL),
|
|
`"level"=0 "msg"="discovered OIDC audience" "audience"="test-audience"`,
|
|
`"level"=0 "msg"="discovered OIDC CA bundle" "roots"=1`,
|
|
}
|
|
},
|
|
wantError: true,
|
|
wantStderr: func(issuerCABundle string, issuerURL string) string {
|
|
return "Error: unable to fetch IDP discovery data from issuer: could not parse response JSON: invalid character 'h' in literal true (expecting 'r')\n"
|
|
},
|
|
},
|
|
{
|
|
name: "when tls information is missing from jwtauthenticator, errors because OIDC discovery fails",
|
|
args: func(issuerCABundle string, issuerURL string) []string {
|
|
return []string{
|
|
"--kubeconfig", "./testdata/kubeconfig.yaml",
|
|
"--skip-validation",
|
|
}
|
|
},
|
|
conciergeObjects: func(issuerCABundle string, issuerURL string) []runtime.Object {
|
|
return []runtime.Object{
|
|
credentialIssuer(),
|
|
&conciergev1alpha1.JWTAuthenticator{
|
|
ObjectMeta: metav1.ObjectMeta{Name: "test-authenticator"},
|
|
Spec: conciergev1alpha1.JWTAuthenticatorSpec{
|
|
Issuer: issuerURL,
|
|
Audience: "test-audience",
|
|
},
|
|
},
|
|
}
|
|
},
|
|
wantLogs: func(issuerCABundle string, issuerURL string) []string {
|
|
return []string{
|
|
`"level"=0 "msg"="discovered CredentialIssuer" "name"="test-credential-issuer"`,
|
|
`"level"=0 "msg"="discovered Concierge operating in TokenCredentialRequest API mode"`,
|
|
`"level"=0 "msg"="discovered Concierge endpoint" "endpoint"="https://fake-server-url-value"`,
|
|
`"level"=0 "msg"="discovered Concierge certificate authority bundle" "roots"=0`,
|
|
`"level"=0 "msg"="discovered JWTAuthenticator" "name"="test-authenticator"`,
|
|
fmt.Sprintf(`"level"=0 "msg"="discovered OIDC issuer" "issuer"="%s"`, issuerURL),
|
|
`"level"=0 "msg"="discovered OIDC audience" "audience"="test-audience"`,
|
|
}
|
|
},
|
|
wantError: true,
|
|
wantStderr: func(issuerCABundle string, issuerURL string) string {
|
|
return fmt.Sprintf("Error: while fetching OIDC discovery data from issuer: Get \"%s/.well-known/openid-configuration\": x509: certificate signed by unknown authority\n", issuerURL)
|
|
},
|
|
},
|
|
{
|
|
name: "when the issuer url is bad",
|
|
args: func(issuerCABundle string, issuerURL string) []string {
|
|
f := testutil.WriteStringToTempFile(t, "testca-*.pem", issuerCABundle)
|
|
return []string{
|
|
"--oidc-issuer", "https%://bad-issuer-url", // this url cannot be parsed
|
|
"--oidc-ca-bundle", f.Name(),
|
|
"--kubeconfig", "./testdata/kubeconfig.yaml",
|
|
"--skip-validation",
|
|
}
|
|
},
|
|
conciergeObjects: func(issuerCABundle string, issuerURL string) []runtime.Object {
|
|
return []runtime.Object{
|
|
credentialIssuer(),
|
|
&conciergev1alpha1.JWTAuthenticator{
|
|
ObjectMeta: metav1.ObjectMeta{Name: "test-authenticator"},
|
|
Spec: conciergev1alpha1.JWTAuthenticatorSpec{
|
|
Issuer: issuerURL,
|
|
Audience: "test-audience",
|
|
},
|
|
},
|
|
}
|
|
},
|
|
wantLogs: func(issuerCABundle string, issuerURL string) []string {
|
|
return []string{
|
|
`"level"=0 "msg"="discovered CredentialIssuer" "name"="test-credential-issuer"`,
|
|
`"level"=0 "msg"="discovered Concierge operating in TokenCredentialRequest API mode"`,
|
|
`"level"=0 "msg"="discovered Concierge endpoint" "endpoint"="https://fake-server-url-value"`,
|
|
`"level"=0 "msg"="discovered Concierge certificate authority bundle" "roots"=0`,
|
|
`"level"=0 "msg"="discovered JWTAuthenticator" "name"="test-authenticator"`,
|
|
`"level"=0 "msg"="discovered OIDC audience" "audience"="test-audience"`,
|
|
}
|
|
},
|
|
wantError: true,
|
|
wantStderr: func(issuerCABundle string, issuerURL string) string {
|
|
return `Error: while fetching OIDC discovery data from issuer: parse "https%://bad-issuer-url/.well-known/openid-configuration": first path segment in URL cannot contain colon` + "\n"
|
|
},
|
|
},
|
|
{
|
|
name: "when the IDP discovery url is bad",
|
|
args: func(issuerCABundle string, issuerURL string) []string {
|
|
return []string{
|
|
"--kubeconfig", "./testdata/kubeconfig.yaml",
|
|
"--skip-validation",
|
|
}
|
|
},
|
|
conciergeObjects: func(issuerCABundle string, issuerURL string) []runtime.Object {
|
|
return []runtime.Object{
|
|
credentialIssuer(),
|
|
jwtAuthenticator(issuerCABundle, issuerURL),
|
|
}
|
|
},
|
|
oidcDiscoveryResponse: func(issuerURL string) string {
|
|
return here.Docf(`{
|
|
"issuer": "%s",
|
|
"discovery.supervisor.pinniped.dev/v1alpha1": {
|
|
"pinniped_identity_providers_endpoint": "https%%://illegal_url"
|
|
}
|
|
}`, issuerURL)
|
|
},
|
|
wantLogs: func(issuerCABundle string, issuerURL string) []string {
|
|
return []string{
|
|
`"level"=0 "msg"="discovered CredentialIssuer" "name"="test-credential-issuer"`,
|
|
`"level"=0 "msg"="discovered Concierge operating in TokenCredentialRequest API mode"`,
|
|
`"level"=0 "msg"="discovered Concierge endpoint" "endpoint"="https://fake-server-url-value"`,
|
|
`"level"=0 "msg"="discovered Concierge certificate authority bundle" "roots"=0`,
|
|
`"level"=0 "msg"="discovered JWTAuthenticator" "name"="test-authenticator"`,
|
|
fmt.Sprintf(`"level"=0 "msg"="discovered OIDC issuer" "issuer"="%s"`, issuerURL),
|
|
`"level"=0 "msg"="discovered OIDC audience" "audience"="test-audience"`,
|
|
`"level"=0 "msg"="discovered OIDC CA bundle" "roots"=1`,
|
|
}
|
|
},
|
|
wantError: true,
|
|
wantStderr: func(issuerCABundle string, issuerURL string) string {
|
|
return `Error: while forming request to IDP discovery URL: parse "https%://illegal_url": first path segment in URL cannot contain colon` + "\n"
|
|
},
|
|
},
|
|
{
|
|
name: "supervisor upstream IDP discovery fails to resolve ambiguity when type is specified but name is not",
|
|
args: func(issuerCABundle string, issuerURL string) []string {
|
|
f := testutil.WriteStringToTempFile(t, "testca-*.pem", issuerCABundle)
|
|
return []string{
|
|
"--kubeconfig", "./testdata/kubeconfig.yaml",
|
|
"--skip-validation",
|
|
"--no-concierge",
|
|
"--oidc-issuer", issuerURL,
|
|
"--oidc-ca-bundle", f.Name(),
|
|
"--upstream-identity-provider-type", "ldap",
|
|
}
|
|
},
|
|
oidcDiscoveryResponse: happyOIDCDiscoveryResponse,
|
|
idpsDiscoveryResponse: here.Docf(`{
|
|
"pinniped_identity_providers": [
|
|
{"name": "some-ldap-idp", "type": "ldap"},
|
|
{"name": "some-other-ldap-idp", "type": "ldap"},
|
|
{"name": "some-oidc-idp", "type": "oidc"},
|
|
{"name": "some-other-oidc-idp", "type": "oidc"}
|
|
]
|
|
}`),
|
|
wantError: true,
|
|
wantStderr: func(issuerCABundle string, issuerURL string) string {
|
|
return `Error: multiple Supervisor upstream identity providers of type "ldap" were found,` +
|
|
` so the --upstream-identity-provider-name flag must be specified.` +
|
|
` Found these upstreams: [{"name":"some-ldap-idp","type":"ldap"},{"name":"some-other-ldap-idp","type":"ldap"},{"name":"some-oidc-idp","type":"oidc"},{"name":"some-other-oidc-idp","type":"oidc"}]` + "\n"
|
|
},
|
|
},
|
|
{
|
|
name: "supervisor upstream IDP discovery fails to resolve ambiguity when name is specified but type is not",
|
|
args: func(issuerCABundle string, issuerURL string) []string {
|
|
f := testutil.WriteStringToTempFile(t, "testca-*.pem", issuerCABundle)
|
|
return []string{
|
|
"--kubeconfig", "./testdata/kubeconfig.yaml",
|
|
"--skip-validation",
|
|
"--no-concierge",
|
|
"--oidc-issuer", issuerURL,
|
|
"--oidc-ca-bundle", f.Name(),
|
|
"--upstream-identity-provider-name", "my-idp",
|
|
}
|
|
},
|
|
oidcDiscoveryResponse: happyOIDCDiscoveryResponse,
|
|
idpsDiscoveryResponse: here.Docf(`{
|
|
"pinniped_identity_providers": [
|
|
{"name": "my-idp", "type": "ldap"},
|
|
{"name": "my-idp", "type": "oidc"},
|
|
{"name": "some-other-oidc-idp", "type": "oidc"}
|
|
]
|
|
}`),
|
|
wantError: true,
|
|
wantStderr: func(issuerCABundle string, issuerURL string) string {
|
|
return `Error: multiple Supervisor upstream identity providers with name "my-idp" were found,` +
|
|
` so the --upstream-identity-provider-type flag must be specified.` +
|
|
` Found these upstreams: [{"name":"my-idp","type":"ldap"},{"name":"my-idp","type":"oidc"},{"name":"some-other-oidc-idp","type":"oidc"}]` + "\n"
|
|
},
|
|
},
|
|
{
|
|
name: "supervisor upstream IDP discovery fails to find any matching idps when type is specified but name is not",
|
|
args: func(issuerCABundle string, issuerURL string) []string {
|
|
f := testutil.WriteStringToTempFile(t, "testca-*.pem", issuerCABundle)
|
|
return []string{
|
|
"--kubeconfig", "./testdata/kubeconfig.yaml",
|
|
"--skip-validation",
|
|
"--no-concierge",
|
|
"--oidc-issuer", issuerURL,
|
|
"--oidc-ca-bundle", f.Name(),
|
|
"--upstream-identity-provider-type", "ldap",
|
|
}
|
|
},
|
|
oidcDiscoveryResponse: happyOIDCDiscoveryResponse,
|
|
idpsDiscoveryResponse: here.Docf(`{
|
|
"pinniped_identity_providers": [
|
|
{"name": "some-oidc-idp", "type": "oidc"},
|
|
{"name": "some-other-oidc-idp", "type": "oidc"}
|
|
]
|
|
}`),
|
|
wantError: true,
|
|
wantStderr: func(issuerCABundle string, issuerURL string) string {
|
|
return `Error: no Supervisor upstream identity providers of type "ldap" were found.` +
|
|
` Found these upstreams: [{"name":"some-oidc-idp","type":"oidc"},{"name":"some-other-oidc-idp","type":"oidc"}]` + "\n"
|
|
},
|
|
},
|
|
{
|
|
name: "supervisor upstream IDP discovery fails to find any matching idps when name is specified but type is not",
|
|
args: func(issuerCABundle string, issuerURL string) []string {
|
|
f := testutil.WriteStringToTempFile(t, "testca-*.pem", issuerCABundle)
|
|
return []string{
|
|
"--kubeconfig", "./testdata/kubeconfig.yaml",
|
|
"--skip-validation",
|
|
"--no-concierge",
|
|
"--oidc-issuer", issuerURL,
|
|
"--oidc-ca-bundle", f.Name(),
|
|
"--upstream-identity-provider-name", "my-nonexistent-idp",
|
|
}
|
|
},
|
|
oidcDiscoveryResponse: happyOIDCDiscoveryResponse,
|
|
idpsDiscoveryResponse: here.Docf(`{
|
|
"pinniped_identity_providers": [
|
|
{"name": "some-oidc-idp", "type": "oidc"},
|
|
{"name": "some-other-oidc-idp", "type": "oidc"}
|
|
]
|
|
}`),
|
|
wantError: true,
|
|
wantStderr: func(issuerCABundle string, issuerURL string) string {
|
|
return `Error: no Supervisor upstream identity providers with name "my-nonexistent-idp" were found.` +
|
|
` Found these upstreams: [{"name":"some-oidc-idp","type":"oidc"},{"name":"some-other-oidc-idp","type":"oidc"}]` + "\n"
|
|
},
|
|
},
|
|
{
|
|
name: "valid static token",
|
|
args: func(issuerCABundle string, issuerURL string) []string {
|
|
return []string{
|
|
"--kubeconfig", "./testdata/kubeconfig.yaml",
|
|
"--static-token", "test-token",
|
|
"--skip-validation",
|
|
}
|
|
},
|
|
conciergeObjects: func(issuerCABundle string, issuerURL string) []runtime.Object {
|
|
return []runtime.Object{
|
|
credentialIssuer(),
|
|
&conciergev1alpha1.WebhookAuthenticator{ObjectMeta: metav1.ObjectMeta{Name: "test-authenticator"}},
|
|
}
|
|
},
|
|
wantLogs: func(issuerCABundle string, issuerURL string) []string {
|
|
return []string{
|
|
`"level"=0 "msg"="discovered CredentialIssuer" "name"="test-credential-issuer"`,
|
|
`"level"=0 "msg"="discovered Concierge operating in TokenCredentialRequest API mode"`,
|
|
`"level"=0 "msg"="discovered Concierge endpoint" "endpoint"="https://fake-server-url-value"`,
|
|
`"level"=0 "msg"="discovered Concierge certificate authority bundle" "roots"=0`,
|
|
`"level"=0 "msg"="discovered WebhookAuthenticator" "name"="test-authenticator"`,
|
|
}
|
|
},
|
|
wantStdout: func(issuerCABundle string, issuerURL string) string {
|
|
return here.Doc(`
|
|
apiVersion: v1
|
|
clusters:
|
|
- cluster:
|
|
certificate-authority-data: ZmFrZS1jZXJ0aWZpY2F0ZS1hdXRob3JpdHktZGF0YS12YWx1ZQ==
|
|
server: https://fake-server-url-value
|
|
name: kind-cluster-pinniped
|
|
contexts:
|
|
- context:
|
|
cluster: kind-cluster-pinniped
|
|
user: kind-user-pinniped
|
|
name: kind-context-pinniped
|
|
current-context: kind-context-pinniped
|
|
kind: Config
|
|
preferences: {}
|
|
users:
|
|
- name: kind-user-pinniped
|
|
user:
|
|
exec:
|
|
apiVersion: client.authentication.k8s.io/v1beta1
|
|
args:
|
|
- login
|
|
- static
|
|
- --enable-concierge
|
|
- --concierge-api-group-suffix=pinniped.dev
|
|
- --concierge-authenticator-name=test-authenticator
|
|
- --concierge-authenticator-type=webhook
|
|
- --concierge-endpoint=https://fake-server-url-value
|
|
- --concierge-ca-bundle-data=ZmFrZS1jZXJ0aWZpY2F0ZS1hdXRob3JpdHktZGF0YS12YWx1ZQ==
|
|
- --token=test-token
|
|
command: '.../path/to/pinniped'
|
|
env: []
|
|
provideClusterInfo: true
|
|
`)
|
|
},
|
|
},
|
|
{
|
|
name: "valid static token from env var",
|
|
args: func(issuerCABundle string, issuerURL string) []string {
|
|
return []string{
|
|
"--kubeconfig", "./testdata/kubeconfig.yaml",
|
|
"--static-token-env", "TEST_TOKEN",
|
|
"--skip-validation",
|
|
"--credential-cache", "",
|
|
}
|
|
},
|
|
conciergeObjects: func(issuerCABundle string, issuerURL string) []runtime.Object {
|
|
return []runtime.Object{
|
|
credentialIssuer(),
|
|
&conciergev1alpha1.WebhookAuthenticator{ObjectMeta: metav1.ObjectMeta{Name: "test-authenticator"}},
|
|
}
|
|
},
|
|
wantLogs: func(issuerCABundle string, issuerURL string) []string {
|
|
return []string{
|
|
`"level"=0 "msg"="discovered CredentialIssuer" "name"="test-credential-issuer"`,
|
|
`"level"=0 "msg"="discovered Concierge operating in TokenCredentialRequest API mode"`,
|
|
`"level"=0 "msg"="discovered Concierge endpoint" "endpoint"="https://fake-server-url-value"`,
|
|
`"level"=0 "msg"="discovered Concierge certificate authority bundle" "roots"=0`,
|
|
`"level"=0 "msg"="discovered WebhookAuthenticator" "name"="test-authenticator"`,
|
|
}
|
|
},
|
|
wantStdout: func(issuerCABundle string, issuerURL string) string {
|
|
return here.Doc(`
|
|
apiVersion: v1
|
|
clusters:
|
|
- cluster:
|
|
certificate-authority-data: ZmFrZS1jZXJ0aWZpY2F0ZS1hdXRob3JpdHktZGF0YS12YWx1ZQ==
|
|
server: https://fake-server-url-value
|
|
name: kind-cluster-pinniped
|
|
contexts:
|
|
- context:
|
|
cluster: kind-cluster-pinniped
|
|
user: kind-user-pinniped
|
|
name: kind-context-pinniped
|
|
current-context: kind-context-pinniped
|
|
kind: Config
|
|
preferences: {}
|
|
users:
|
|
- name: kind-user-pinniped
|
|
user:
|
|
exec:
|
|
apiVersion: client.authentication.k8s.io/v1beta1
|
|
args:
|
|
- login
|
|
- static
|
|
- --enable-concierge
|
|
- --concierge-api-group-suffix=pinniped.dev
|
|
- --concierge-authenticator-name=test-authenticator
|
|
- --concierge-authenticator-type=webhook
|
|
- --concierge-endpoint=https://fake-server-url-value
|
|
- --concierge-ca-bundle-data=ZmFrZS1jZXJ0aWZpY2F0ZS1hdXRob3JpdHktZGF0YS12YWx1ZQ==
|
|
- --credential-cache=
|
|
- --token-env=TEST_TOKEN
|
|
command: '.../path/to/pinniped'
|
|
env: []
|
|
provideClusterInfo: true
|
|
`)
|
|
},
|
|
},
|
|
{
|
|
name: "autodetect JWT authenticator",
|
|
args: func(issuerCABundle string, issuerURL string) []string {
|
|
return []string{
|
|
"--kubeconfig", "./testdata/kubeconfig.yaml",
|
|
"--skip-validation",
|
|
}
|
|
},
|
|
conciergeObjects: func(issuerCABundle string, issuerURL string) []runtime.Object {
|
|
return []runtime.Object{
|
|
credentialIssuer(),
|
|
jwtAuthenticator(issuerCABundle, issuerURL),
|
|
}
|
|
},
|
|
oidcDiscoveryResponse: onlyIssuerOIDCDiscoveryResponse,
|
|
wantLogs: func(issuerCABundle string, issuerURL string) []string {
|
|
return []string{
|
|
`"level"=0 "msg"="discovered CredentialIssuer" "name"="test-credential-issuer"`,
|
|
`"level"=0 "msg"="discovered Concierge operating in TokenCredentialRequest API mode"`,
|
|
`"level"=0 "msg"="discovered Concierge endpoint" "endpoint"="https://fake-server-url-value"`,
|
|
`"level"=0 "msg"="discovered Concierge certificate authority bundle" "roots"=0`,
|
|
`"level"=0 "msg"="discovered JWTAuthenticator" "name"="test-authenticator"`,
|
|
fmt.Sprintf(`"level"=0 "msg"="discovered OIDC issuer" "issuer"="%s"`, issuerURL),
|
|
`"level"=0 "msg"="discovered OIDC audience" "audience"="test-audience"`,
|
|
`"level"=0 "msg"="discovered OIDC CA bundle" "roots"=1`,
|
|
}
|
|
},
|
|
wantStdout: func(issuerCABundle string, issuerURL string) string {
|
|
return here.Docf(`
|
|
apiVersion: v1
|
|
clusters:
|
|
- cluster:
|
|
certificate-authority-data: ZmFrZS1jZXJ0aWZpY2F0ZS1hdXRob3JpdHktZGF0YS12YWx1ZQ==
|
|
server: https://fake-server-url-value
|
|
name: kind-cluster-pinniped
|
|
contexts:
|
|
- context:
|
|
cluster: kind-cluster-pinniped
|
|
user: kind-user-pinniped
|
|
name: kind-context-pinniped
|
|
current-context: kind-context-pinniped
|
|
kind: Config
|
|
preferences: {}
|
|
users:
|
|
- name: kind-user-pinniped
|
|
user:
|
|
exec:
|
|
apiVersion: client.authentication.k8s.io/v1beta1
|
|
args:
|
|
- login
|
|
- oidc
|
|
- --enable-concierge
|
|
- --concierge-api-group-suffix=pinniped.dev
|
|
- --concierge-authenticator-name=test-authenticator
|
|
- --concierge-authenticator-type=jwt
|
|
- --concierge-endpoint=https://fake-server-url-value
|
|
- --concierge-ca-bundle-data=ZmFrZS1jZXJ0aWZpY2F0ZS1hdXRob3JpdHktZGF0YS12YWx1ZQ==
|
|
- --issuer=%s
|
|
- --client-id=pinniped-cli
|
|
- --scopes=offline_access,openid,pinniped:request-audience
|
|
- --ca-bundle-data=%s
|
|
- --request-audience=test-audience
|
|
command: '.../path/to/pinniped'
|
|
env: []
|
|
provideClusterInfo: true
|
|
`,
|
|
issuerURL,
|
|
base64.StdEncoding.EncodeToString([]byte(issuerCABundle)))
|
|
},
|
|
},
|
|
{
|
|
|
|
name: "autodetect nothing, set a bunch of options",
|
|
args: func(issuerCABundle string, issuerURL string) []string {
|
|
f := testutil.WriteStringToTempFile(t, "testca-*.pem", issuerCABundle)
|
|
return []string{
|
|
"--kubeconfig", "./testdata/kubeconfig.yaml",
|
|
"--concierge-credential-issuer", "test-credential-issuer",
|
|
"--concierge-api-group-suffix", "tuna.io",
|
|
"--concierge-authenticator-type", "webhook",
|
|
"--concierge-authenticator-name", "test-authenticator",
|
|
"--concierge-mode", "TokenCredentialRequestAPI",
|
|
"--concierge-endpoint", "https://explicit-concierge-endpoint.example.com",
|
|
"--concierge-ca-bundle", testConciergeCABundlePath,
|
|
"--oidc-issuer", issuerURL,
|
|
"--oidc-skip-browser",
|
|
"--oidc-skip-listen",
|
|
"--oidc-listen-port", "1234",
|
|
"--oidc-ca-bundle", f.Name(),
|
|
"--oidc-session-cache", "/path/to/cache/dir/sessions.yaml",
|
|
"--oidc-debug-session-cache",
|
|
"--oidc-request-audience", "test-audience",
|
|
"--skip-validation",
|
|
"--generated-name-suffix", "-sso",
|
|
"--credential-cache", "/path/to/cache/dir/credentials.yaml",
|
|
}
|
|
},
|
|
conciergeObjects: func(issuerCABundle string, issuerURL string) []runtime.Object {
|
|
return []runtime.Object{
|
|
credentialIssuer(),
|
|
&conciergev1alpha1.WebhookAuthenticator{ObjectMeta: metav1.ObjectMeta{Name: "test-authenticator"}},
|
|
}
|
|
},
|
|
oidcDiscoveryResponse: onlyIssuerOIDCDiscoveryResponse,
|
|
wantLogs: func(issuerCABundle string, issuerURL string) []string { return nil },
|
|
wantStdout: func(issuerCABundle string, issuerURL string) string {
|
|
return here.Docf(`
|
|
apiVersion: v1
|
|
clusters:
|
|
- cluster:
|
|
certificate-authority-data: %s
|
|
server: https://explicit-concierge-endpoint.example.com
|
|
name: kind-cluster-sso
|
|
contexts:
|
|
- context:
|
|
cluster: kind-cluster-sso
|
|
user: kind-user-sso
|
|
name: kind-context-sso
|
|
current-context: kind-context-sso
|
|
kind: Config
|
|
preferences: {}
|
|
users:
|
|
- name: kind-user-sso
|
|
user:
|
|
exec:
|
|
apiVersion: client.authentication.k8s.io/v1beta1
|
|
args:
|
|
- login
|
|
- oidc
|
|
- --enable-concierge
|
|
- --concierge-api-group-suffix=tuna.io
|
|
- --concierge-authenticator-name=test-authenticator
|
|
- --concierge-authenticator-type=webhook
|
|
- --concierge-endpoint=https://explicit-concierge-endpoint.example.com
|
|
- --concierge-ca-bundle-data=%s
|
|
- --credential-cache=/path/to/cache/dir/credentials.yaml
|
|
- --issuer=%s
|
|
- --client-id=pinniped-cli
|
|
- --scopes=offline_access,openid,pinniped:request-audience
|
|
- --skip-browser
|
|
- --skip-listen
|
|
- --listen-port=1234
|
|
- --ca-bundle-data=%s
|
|
- --session-cache=/path/to/cache/dir/sessions.yaml
|
|
- --debug-session-cache
|
|
- --request-audience=test-audience
|
|
command: '.../path/to/pinniped'
|
|
env: []
|
|
provideClusterInfo: true
|
|
`,
|
|
base64.StdEncoding.EncodeToString(testConciergeCA.Bundle()),
|
|
base64.StdEncoding.EncodeToString(testConciergeCA.Bundle()),
|
|
issuerURL,
|
|
base64.StdEncoding.EncodeToString([]byte(issuerCABundle)),
|
|
)
|
|
},
|
|
wantAPIGroupSuffix: "tuna.io",
|
|
},
|
|
{
|
|
name: "configure impersonation proxy with autodiscovered JWT authenticator",
|
|
args: func(issuerCABundle string, issuerURL string) []string {
|
|
return []string{
|
|
"--kubeconfig", "./testdata/kubeconfig.yaml",
|
|
"--concierge-mode", "ImpersonationProxy",
|
|
"--skip-validation",
|
|
}
|
|
},
|
|
conciergeObjects: func(issuerCABundle string, issuerURL string) []runtime.Object {
|
|
return []runtime.Object{
|
|
&configv1alpha1.CredentialIssuer{
|
|
ObjectMeta: metav1.ObjectMeta{Name: "test-credential-issuer"},
|
|
Status: configv1alpha1.CredentialIssuerStatus{
|
|
Strategies: []configv1alpha1.CredentialIssuerStrategy{
|
|
// This TokenCredentialRequestAPI strategy would normally be chosen, but
|
|
// --concierge-mode=ImpersonationProxy should force it to be skipped.
|
|
{
|
|
Type: "SomeType",
|
|
Status: configv1alpha1.SuccessStrategyStatus,
|
|
Reason: "SomeReason",
|
|
Message: "Some message",
|
|
LastUpdateTime: metav1.Now(),
|
|
Frontend: &configv1alpha1.CredentialIssuerFrontend{
|
|
Type: configv1alpha1.TokenCredentialRequestAPIFrontendType,
|
|
TokenCredentialRequestAPIInfo: &configv1alpha1.TokenCredentialRequestAPIInfo{
|
|
Server: "https://token-credential-request-api-endpoint.test",
|
|
CertificateAuthorityData: "dGVzdC10Y3ItYXBpLWNh",
|
|
},
|
|
},
|
|
},
|
|
// The endpoint and CA from this impersonation proxy strategy should be autodiscovered.
|
|
{
|
|
Type: "SomeOtherType",
|
|
Status: configv1alpha1.SuccessStrategyStatus,
|
|
Reason: "SomeOtherReason",
|
|
Message: "Some other message",
|
|
LastUpdateTime: metav1.Now(),
|
|
Frontend: &configv1alpha1.CredentialIssuerFrontend{
|
|
Type: configv1alpha1.ImpersonationProxyFrontendType,
|
|
ImpersonationProxyInfo: &configv1alpha1.ImpersonationProxyInfo{
|
|
Endpoint: "https://impersonation-proxy-endpoint.test",
|
|
CertificateAuthorityData: base64.StdEncoding.EncodeToString(testConciergeCA.Bundle()),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
jwtAuthenticator(issuerCABundle, issuerURL),
|
|
}
|
|
},
|
|
oidcDiscoveryResponse: onlyIssuerOIDCDiscoveryResponse,
|
|
wantLogs: func(issuerCABundle string, issuerURL string) []string {
|
|
return []string{
|
|
`"level"=0 "msg"="discovered CredentialIssuer" "name"="test-credential-issuer"`,
|
|
`"level"=0 "msg"="discovered Concierge endpoint" "endpoint"="https://impersonation-proxy-endpoint.test"`,
|
|
`"level"=0 "msg"="discovered Concierge certificate authority bundle" "roots"=1`,
|
|
`"level"=0 "msg"="discovered JWTAuthenticator" "name"="test-authenticator"`,
|
|
fmt.Sprintf(`"level"=0 "msg"="discovered OIDC issuer" "issuer"="%s"`, issuerURL),
|
|
`"level"=0 "msg"="discovered OIDC audience" "audience"="test-audience"`,
|
|
`"level"=0 "msg"="discovered OIDC CA bundle" "roots"=1`,
|
|
}
|
|
},
|
|
wantStdout: func(issuerCABundle string, issuerURL string) string {
|
|
return here.Docf(`
|
|
apiVersion: v1
|
|
clusters:
|
|
- cluster:
|
|
certificate-authority-data: %s
|
|
server: https://impersonation-proxy-endpoint.test
|
|
name: kind-cluster-pinniped
|
|
contexts:
|
|
- context:
|
|
cluster: kind-cluster-pinniped
|
|
user: kind-user-pinniped
|
|
name: kind-context-pinniped
|
|
current-context: kind-context-pinniped
|
|
kind: Config
|
|
preferences: {}
|
|
users:
|
|
- name: kind-user-pinniped
|
|
user:
|
|
exec:
|
|
apiVersion: client.authentication.k8s.io/v1beta1
|
|
args:
|
|
- login
|
|
- oidc
|
|
- --enable-concierge
|
|
- --concierge-api-group-suffix=pinniped.dev
|
|
- --concierge-authenticator-name=test-authenticator
|
|
- --concierge-authenticator-type=jwt
|
|
- --concierge-endpoint=https://impersonation-proxy-endpoint.test
|
|
- --concierge-ca-bundle-data=%s
|
|
- --issuer=%s
|
|
- --client-id=pinniped-cli
|
|
- --scopes=offline_access,openid,pinniped:request-audience
|
|
- --ca-bundle-data=%s
|
|
- --request-audience=test-audience
|
|
command: '.../path/to/pinniped'
|
|
env: []
|
|
provideClusterInfo: true
|
|
`,
|
|
base64.StdEncoding.EncodeToString(testConciergeCA.Bundle()),
|
|
base64.StdEncoding.EncodeToString(testConciergeCA.Bundle()),
|
|
issuerURL,
|
|
base64.StdEncoding.EncodeToString([]byte(issuerCABundle)),
|
|
)
|
|
},
|
|
},
|
|
{
|
|
name: "autodetect impersonation proxy with autodiscovered JWT authenticator",
|
|
args: func(issuerCABundle string, issuerURL string) []string {
|
|
return []string{
|
|
"--kubeconfig", "./testdata/kubeconfig.yaml",
|
|
"--skip-validation",
|
|
}
|
|
},
|
|
conciergeObjects: func(issuerCABundle string, issuerURL string) []runtime.Object {
|
|
return []runtime.Object{
|
|
&configv1alpha1.CredentialIssuer{
|
|
ObjectMeta: metav1.ObjectMeta{Name: "test-credential-issuer"},
|
|
Status: configv1alpha1.CredentialIssuerStatus{
|
|
Strategies: []configv1alpha1.CredentialIssuerStrategy{
|
|
{
|
|
Type: "SomeType",
|
|
Status: configv1alpha1.SuccessStrategyStatus,
|
|
Reason: "SomeReason",
|
|
Message: "Some message",
|
|
LastUpdateTime: metav1.Now(),
|
|
Frontend: &configv1alpha1.CredentialIssuerFrontend{
|
|
Type: configv1alpha1.ImpersonationProxyFrontendType,
|
|
ImpersonationProxyInfo: &configv1alpha1.ImpersonationProxyInfo{
|
|
Endpoint: "https://impersonation-proxy-endpoint.test",
|
|
CertificateAuthorityData: "dGVzdC1jb25jaWVyZ2UtY2E=",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Type: "SomeOtherType",
|
|
Status: configv1alpha1.SuccessStrategyStatus,
|
|
Reason: "SomeOtherReason",
|
|
Message: "Some other message",
|
|
LastUpdateTime: metav1.Now(),
|
|
Frontend: &configv1alpha1.CredentialIssuerFrontend{
|
|
Type: configv1alpha1.ImpersonationProxyFrontendType,
|
|
ImpersonationProxyInfo: &configv1alpha1.ImpersonationProxyInfo{
|
|
Endpoint: "https://some-other-impersonation-endpoint",
|
|
CertificateAuthorityData: "dGVzdC1jb25jaWVyZ2UtY2E=",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
jwtAuthenticator(issuerCABundle, issuerURL),
|
|
}
|
|
},
|
|
oidcDiscoveryResponse: onlyIssuerOIDCDiscoveryResponse,
|
|
wantLogs: func(issuerCABundle string, issuerURL string) []string {
|
|
return []string{
|
|
`"level"=0 "msg"="discovered CredentialIssuer" "name"="test-credential-issuer"`,
|
|
`"level"=0 "msg"="discovered Concierge operating in impersonation proxy mode"`,
|
|
`"level"=0 "msg"="discovered Concierge endpoint" "endpoint"="https://impersonation-proxy-endpoint.test"`,
|
|
`"level"=0 "msg"="discovered Concierge certificate authority bundle" "roots"=0`,
|
|
`"level"=0 "msg"="discovered JWTAuthenticator" "name"="test-authenticator"`,
|
|
fmt.Sprintf(`"level"=0 "msg"="discovered OIDC issuer" "issuer"="%s"`, issuerURL),
|
|
`"level"=0 "msg"="discovered OIDC audience" "audience"="test-audience"`,
|
|
`"level"=0 "msg"="discovered OIDC CA bundle" "roots"=1`,
|
|
}
|
|
},
|
|
wantStdout: func(issuerCABundle string, issuerURL string) string {
|
|
return here.Docf(`
|
|
apiVersion: v1
|
|
clusters:
|
|
- cluster:
|
|
certificate-authority-data: dGVzdC1jb25jaWVyZ2UtY2E=
|
|
server: https://impersonation-proxy-endpoint.test
|
|
name: kind-cluster-pinniped
|
|
contexts:
|
|
- context:
|
|
cluster: kind-cluster-pinniped
|
|
user: kind-user-pinniped
|
|
name: kind-context-pinniped
|
|
current-context: kind-context-pinniped
|
|
kind: Config
|
|
preferences: {}
|
|
users:
|
|
- name: kind-user-pinniped
|
|
user:
|
|
exec:
|
|
apiVersion: client.authentication.k8s.io/v1beta1
|
|
args:
|
|
- login
|
|
- oidc
|
|
- --enable-concierge
|
|
- --concierge-api-group-suffix=pinniped.dev
|
|
- --concierge-authenticator-name=test-authenticator
|
|
- --concierge-authenticator-type=jwt
|
|
- --concierge-endpoint=https://impersonation-proxy-endpoint.test
|
|
- --concierge-ca-bundle-data=dGVzdC1jb25jaWVyZ2UtY2E=
|
|
- --issuer=%s
|
|
- --client-id=pinniped-cli
|
|
- --scopes=offline_access,openid,pinniped:request-audience
|
|
- --ca-bundle-data=%s
|
|
- --request-audience=test-audience
|
|
command: '.../path/to/pinniped'
|
|
env: []
|
|
provideClusterInfo: true
|
|
`,
|
|
issuerURL,
|
|
base64.StdEncoding.EncodeToString([]byte(issuerCABundle)))
|
|
},
|
|
},
|
|
{
|
|
name: "Find LDAP IDP in IDP discovery document, output ldap related flags",
|
|
args: func(issuerCABundle string, issuerURL string) []string {
|
|
return []string{
|
|
"--kubeconfig", "./testdata/kubeconfig.yaml",
|
|
"--skip-validation",
|
|
}
|
|
},
|
|
conciergeObjects: func(issuerCABundle string, issuerURL string) []runtime.Object {
|
|
return []runtime.Object{
|
|
credentialIssuer(),
|
|
jwtAuthenticator(issuerCABundle, issuerURL),
|
|
}
|
|
},
|
|
oidcDiscoveryResponse: happyOIDCDiscoveryResponse,
|
|
idpsDiscoveryResponse: here.Docf(`{
|
|
"pinniped_identity_providers": [
|
|
{"name": "some-ldap-idp", "type": "ldap"}
|
|
]
|
|
}`),
|
|
wantLogs: func(issuerCABundle string, issuerURL string) []string {
|
|
return []string{
|
|
`"level"=0 "msg"="discovered CredentialIssuer" "name"="test-credential-issuer"`,
|
|
`"level"=0 "msg"="discovered Concierge operating in TokenCredentialRequest API mode"`,
|
|
`"level"=0 "msg"="discovered Concierge endpoint" "endpoint"="https://fake-server-url-value"`,
|
|
`"level"=0 "msg"="discovered Concierge certificate authority bundle" "roots"=0`,
|
|
`"level"=0 "msg"="discovered JWTAuthenticator" "name"="test-authenticator"`,
|
|
fmt.Sprintf(`"level"=0 "msg"="discovered OIDC issuer" "issuer"="%s"`, issuerURL),
|
|
`"level"=0 "msg"="discovered OIDC audience" "audience"="test-audience"`,
|
|
`"level"=0 "msg"="discovered OIDC CA bundle" "roots"=1`,
|
|
}
|
|
},
|
|
wantStdout: func(issuerCABundle string, issuerURL string) string {
|
|
return here.Docf(`
|
|
apiVersion: v1
|
|
clusters:
|
|
- cluster:
|
|
certificate-authority-data: ZmFrZS1jZXJ0aWZpY2F0ZS1hdXRob3JpdHktZGF0YS12YWx1ZQ==
|
|
server: https://fake-server-url-value
|
|
name: kind-cluster-pinniped
|
|
contexts:
|
|
- context:
|
|
cluster: kind-cluster-pinniped
|
|
user: kind-user-pinniped
|
|
name: kind-context-pinniped
|
|
current-context: kind-context-pinniped
|
|
kind: Config
|
|
preferences: {}
|
|
users:
|
|
- name: kind-user-pinniped
|
|
user:
|
|
exec:
|
|
apiVersion: client.authentication.k8s.io/v1beta1
|
|
args:
|
|
- login
|
|
- oidc
|
|
- --enable-concierge
|
|
- --concierge-api-group-suffix=pinniped.dev
|
|
- --concierge-authenticator-name=test-authenticator
|
|
- --concierge-authenticator-type=jwt
|
|
- --concierge-endpoint=https://fake-server-url-value
|
|
- --concierge-ca-bundle-data=ZmFrZS1jZXJ0aWZpY2F0ZS1hdXRob3JpdHktZGF0YS12YWx1ZQ==
|
|
- --issuer=%s
|
|
- --client-id=pinniped-cli
|
|
- --scopes=offline_access,openid,pinniped:request-audience
|
|
- --ca-bundle-data=%s
|
|
- --request-audience=test-audience
|
|
- --upstream-identity-provider-name=some-ldap-idp
|
|
- --upstream-identity-provider-type=ldap
|
|
command: '.../path/to/pinniped'
|
|
env: []
|
|
provideClusterInfo: true
|
|
`,
|
|
issuerURL,
|
|
base64.StdEncoding.EncodeToString([]byte(issuerCABundle)))
|
|
},
|
|
},
|
|
{
|
|
name: "Find OIDC IDP in IDP discovery document, output oidc related flags",
|
|
args: func(issuerCABundle string, issuerURL string) []string {
|
|
return []string{
|
|
"--kubeconfig", "./testdata/kubeconfig.yaml",
|
|
"--skip-validation",
|
|
}
|
|
},
|
|
conciergeObjects: func(issuerCABundle string, issuerURL string) []runtime.Object {
|
|
return []runtime.Object{
|
|
credentialIssuer(),
|
|
jwtAuthenticator(issuerCABundle, issuerURL),
|
|
}
|
|
},
|
|
oidcDiscoveryResponse: happyOIDCDiscoveryResponse,
|
|
idpsDiscoveryResponse: here.Docf(`{
|
|
"pinniped_identity_providers": [
|
|
{"name": "some-oidc-idp", "type": "oidc"}
|
|
]
|
|
}`),
|
|
wantLogs: func(issuerCABundle string, issuerURL string) []string {
|
|
return []string{
|
|
`"level"=0 "msg"="discovered CredentialIssuer" "name"="test-credential-issuer"`,
|
|
`"level"=0 "msg"="discovered Concierge operating in TokenCredentialRequest API mode"`,
|
|
`"level"=0 "msg"="discovered Concierge endpoint" "endpoint"="https://fake-server-url-value"`,
|
|
`"level"=0 "msg"="discovered Concierge certificate authority bundle" "roots"=0`,
|
|
`"level"=0 "msg"="discovered JWTAuthenticator" "name"="test-authenticator"`,
|
|
fmt.Sprintf(`"level"=0 "msg"="discovered OIDC issuer" "issuer"="%s"`, issuerURL),
|
|
`"level"=0 "msg"="discovered OIDC audience" "audience"="test-audience"`,
|
|
`"level"=0 "msg"="discovered OIDC CA bundle" "roots"=1`,
|
|
}
|
|
},
|
|
wantStdout: func(issuerCABundle string, issuerURL string) string {
|
|
return here.Docf(`
|
|
apiVersion: v1
|
|
clusters:
|
|
- cluster:
|
|
certificate-authority-data: ZmFrZS1jZXJ0aWZpY2F0ZS1hdXRob3JpdHktZGF0YS12YWx1ZQ==
|
|
server: https://fake-server-url-value
|
|
name: kind-cluster-pinniped
|
|
contexts:
|
|
- context:
|
|
cluster: kind-cluster-pinniped
|
|
user: kind-user-pinniped
|
|
name: kind-context-pinniped
|
|
current-context: kind-context-pinniped
|
|
kind: Config
|
|
preferences: {}
|
|
users:
|
|
- name: kind-user-pinniped
|
|
user:
|
|
exec:
|
|
apiVersion: client.authentication.k8s.io/v1beta1
|
|
args:
|
|
- login
|
|
- oidc
|
|
- --enable-concierge
|
|
- --concierge-api-group-suffix=pinniped.dev
|
|
- --concierge-authenticator-name=test-authenticator
|
|
- --concierge-authenticator-type=jwt
|
|
- --concierge-endpoint=https://fake-server-url-value
|
|
- --concierge-ca-bundle-data=ZmFrZS1jZXJ0aWZpY2F0ZS1hdXRob3JpdHktZGF0YS12YWx1ZQ==
|
|
- --issuer=%s
|
|
- --client-id=pinniped-cli
|
|
- --scopes=offline_access,openid,pinniped:request-audience
|
|
- --ca-bundle-data=%s
|
|
- --request-audience=test-audience
|
|
- --upstream-identity-provider-name=some-oidc-idp
|
|
- --upstream-identity-provider-type=oidc
|
|
command: '.../path/to/pinniped'
|
|
env: []
|
|
provideClusterInfo: true
|
|
`,
|
|
issuerURL,
|
|
base64.StdEncoding.EncodeToString([]byte(issuerCABundle)))
|
|
},
|
|
},
|
|
{
|
|
name: "empty IDP list in IDP discovery document",
|
|
args: func(issuerCABundle string, issuerURL string) []string {
|
|
return []string{
|
|
"--kubeconfig", "./testdata/kubeconfig.yaml",
|
|
"--skip-validation",
|
|
}
|
|
},
|
|
conciergeObjects: func(issuerCABundle string, issuerURL string) []runtime.Object {
|
|
return []runtime.Object{
|
|
credentialIssuer(),
|
|
jwtAuthenticator(issuerCABundle, issuerURL),
|
|
}
|
|
},
|
|
oidcDiscoveryResponse: happyOIDCDiscoveryResponse,
|
|
idpsDiscoveryResponse: here.Docf(`{
|
|
"pinniped_identity_providers": []
|
|
}`),
|
|
wantLogs: func(issuerCABundle string, issuerURL string) []string {
|
|
return []string{
|
|
`"level"=0 "msg"="discovered CredentialIssuer" "name"="test-credential-issuer"`,
|
|
`"level"=0 "msg"="discovered Concierge operating in TokenCredentialRequest API mode"`,
|
|
`"level"=0 "msg"="discovered Concierge endpoint" "endpoint"="https://fake-server-url-value"`,
|
|
`"level"=0 "msg"="discovered Concierge certificate authority bundle" "roots"=0`,
|
|
`"level"=0 "msg"="discovered JWTAuthenticator" "name"="test-authenticator"`,
|
|
fmt.Sprintf(`"level"=0 "msg"="discovered OIDC issuer" "issuer"="%s"`, issuerURL),
|
|
`"level"=0 "msg"="discovered OIDC audience" "audience"="test-audience"`,
|
|
`"level"=0 "msg"="discovered OIDC CA bundle" "roots"=1`,
|
|
}
|
|
},
|
|
wantStdout: func(issuerCABundle string, issuerURL string) string {
|
|
return here.Docf(`
|
|
apiVersion: v1
|
|
clusters:
|
|
- cluster:
|
|
certificate-authority-data: ZmFrZS1jZXJ0aWZpY2F0ZS1hdXRob3JpdHktZGF0YS12YWx1ZQ==
|
|
server: https://fake-server-url-value
|
|
name: kind-cluster-pinniped
|
|
contexts:
|
|
- context:
|
|
cluster: kind-cluster-pinniped
|
|
user: kind-user-pinniped
|
|
name: kind-context-pinniped
|
|
current-context: kind-context-pinniped
|
|
kind: Config
|
|
preferences: {}
|
|
users:
|
|
- name: kind-user-pinniped
|
|
user:
|
|
exec:
|
|
apiVersion: client.authentication.k8s.io/v1beta1
|
|
args:
|
|
- login
|
|
- oidc
|
|
- --enable-concierge
|
|
- --concierge-api-group-suffix=pinniped.dev
|
|
- --concierge-authenticator-name=test-authenticator
|
|
- --concierge-authenticator-type=jwt
|
|
- --concierge-endpoint=https://fake-server-url-value
|
|
- --concierge-ca-bundle-data=ZmFrZS1jZXJ0aWZpY2F0ZS1hdXRob3JpdHktZGF0YS12YWx1ZQ==
|
|
- --issuer=%s
|
|
- --client-id=pinniped-cli
|
|
- --scopes=offline_access,openid,pinniped:request-audience
|
|
- --ca-bundle-data=%s
|
|
- --request-audience=test-audience
|
|
command: '.../path/to/pinniped'
|
|
env: []
|
|
provideClusterInfo: true
|
|
`,
|
|
issuerURL,
|
|
base64.StdEncoding.EncodeToString([]byte(issuerCABundle)))
|
|
},
|
|
},
|
|
{
|
|
name: "Supervisor discovery section is not listed in OIDC discovery document",
|
|
args: func(issuerCABundle string, issuerURL string) []string {
|
|
return []string{
|
|
"--kubeconfig", "./testdata/kubeconfig.yaml",
|
|
"--skip-validation",
|
|
}
|
|
},
|
|
conciergeObjects: func(issuerCABundle string, issuerURL string) []runtime.Object {
|
|
return []runtime.Object{
|
|
credentialIssuer(),
|
|
jwtAuthenticator(issuerCABundle, issuerURL),
|
|
}
|
|
},
|
|
oidcDiscoveryResponse: onlyIssuerOIDCDiscoveryResponse,
|
|
idpsDiscoveryStatusCode: http.StatusBadRequest, // IDPs endpoint shouldn't be called by this test
|
|
wantLogs: func(issuerCABundle string, issuerURL string) []string {
|
|
return []string{
|
|
`"level"=0 "msg"="discovered CredentialIssuer" "name"="test-credential-issuer"`,
|
|
`"level"=0 "msg"="discovered Concierge operating in TokenCredentialRequest API mode"`,
|
|
`"level"=0 "msg"="discovered Concierge endpoint" "endpoint"="https://fake-server-url-value"`,
|
|
`"level"=0 "msg"="discovered Concierge certificate authority bundle" "roots"=0`,
|
|
`"level"=0 "msg"="discovered JWTAuthenticator" "name"="test-authenticator"`,
|
|
fmt.Sprintf(`"level"=0 "msg"="discovered OIDC issuer" "issuer"="%s"`, issuerURL),
|
|
`"level"=0 "msg"="discovered OIDC audience" "audience"="test-audience"`,
|
|
`"level"=0 "msg"="discovered OIDC CA bundle" "roots"=1`,
|
|
}
|
|
},
|
|
wantStdout: func(issuerCABundle string, issuerURL string) string {
|
|
return here.Docf(`
|
|
apiVersion: v1
|
|
clusters:
|
|
- cluster:
|
|
certificate-authority-data: ZmFrZS1jZXJ0aWZpY2F0ZS1hdXRob3JpdHktZGF0YS12YWx1ZQ==
|
|
server: https://fake-server-url-value
|
|
name: kind-cluster-pinniped
|
|
contexts:
|
|
- context:
|
|
cluster: kind-cluster-pinniped
|
|
user: kind-user-pinniped
|
|
name: kind-context-pinniped
|
|
current-context: kind-context-pinniped
|
|
kind: Config
|
|
preferences: {}
|
|
users:
|
|
- name: kind-user-pinniped
|
|
user:
|
|
exec:
|
|
apiVersion: client.authentication.k8s.io/v1beta1
|
|
args:
|
|
- login
|
|
- oidc
|
|
- --enable-concierge
|
|
- --concierge-api-group-suffix=pinniped.dev
|
|
- --concierge-authenticator-name=test-authenticator
|
|
- --concierge-authenticator-type=jwt
|
|
- --concierge-endpoint=https://fake-server-url-value
|
|
- --concierge-ca-bundle-data=ZmFrZS1jZXJ0aWZpY2F0ZS1hdXRob3JpdHktZGF0YS12YWx1ZQ==
|
|
- --issuer=%s
|
|
- --client-id=pinniped-cli
|
|
- --scopes=offline_access,openid,pinniped:request-audience
|
|
- --ca-bundle-data=%s
|
|
- --request-audience=test-audience
|
|
command: '.../path/to/pinniped'
|
|
env: []
|
|
provideClusterInfo: true
|
|
`,
|
|
issuerURL,
|
|
base64.StdEncoding.EncodeToString([]byte(issuerCABundle)))
|
|
},
|
|
},
|
|
{
|
|
name: "IDP discovery endpoint is not listed in OIDC discovery document within the Supervisor discovery section",
|
|
args: func(issuerCABundle string, issuerURL string) []string {
|
|
return []string{
|
|
"--kubeconfig", "./testdata/kubeconfig.yaml",
|
|
"--skip-validation",
|
|
}
|
|
},
|
|
conciergeObjects: func(issuerCABundle string, issuerURL string) []runtime.Object {
|
|
return []runtime.Object{
|
|
credentialIssuer(),
|
|
jwtAuthenticator(issuerCABundle, issuerURL),
|
|
}
|
|
},
|
|
oidcDiscoveryResponse: func(issuerURL string) string {
|
|
return here.Docf(`{
|
|
"issuer": "%s",
|
|
"discovery.supervisor.pinniped.dev/v1alpha1": {
|
|
"wrong-key": "some-value"
|
|
}
|
|
}`, issuerURL)
|
|
},
|
|
idpsDiscoveryStatusCode: http.StatusBadRequest, // IDPs endpoint shouldn't be called by this test
|
|
wantLogs: func(issuerCABundle string, issuerURL string) []string {
|
|
return []string{
|
|
`"level"=0 "msg"="discovered CredentialIssuer" "name"="test-credential-issuer"`,
|
|
`"level"=0 "msg"="discovered Concierge operating in TokenCredentialRequest API mode"`,
|
|
`"level"=0 "msg"="discovered Concierge endpoint" "endpoint"="https://fake-server-url-value"`,
|
|
`"level"=0 "msg"="discovered Concierge certificate authority bundle" "roots"=0`,
|
|
`"level"=0 "msg"="discovered JWTAuthenticator" "name"="test-authenticator"`,
|
|
fmt.Sprintf(`"level"=0 "msg"="discovered OIDC issuer" "issuer"="%s"`, issuerURL),
|
|
`"level"=0 "msg"="discovered OIDC audience" "audience"="test-audience"`,
|
|
`"level"=0 "msg"="discovered OIDC CA bundle" "roots"=1`,
|
|
}
|
|
},
|
|
wantStdout: func(issuerCABundle string, issuerURL string) string {
|
|
return here.Docf(`
|
|
apiVersion: v1
|
|
clusters:
|
|
- cluster:
|
|
certificate-authority-data: ZmFrZS1jZXJ0aWZpY2F0ZS1hdXRob3JpdHktZGF0YS12YWx1ZQ==
|
|
server: https://fake-server-url-value
|
|
name: kind-cluster-pinniped
|
|
contexts:
|
|
- context:
|
|
cluster: kind-cluster-pinniped
|
|
user: kind-user-pinniped
|
|
name: kind-context-pinniped
|
|
current-context: kind-context-pinniped
|
|
kind: Config
|
|
preferences: {}
|
|
users:
|
|
- name: kind-user-pinniped
|
|
user:
|
|
exec:
|
|
apiVersion: client.authentication.k8s.io/v1beta1
|
|
args:
|
|
- login
|
|
- oidc
|
|
- --enable-concierge
|
|
- --concierge-api-group-suffix=pinniped.dev
|
|
- --concierge-authenticator-name=test-authenticator
|
|
- --concierge-authenticator-type=jwt
|
|
- --concierge-endpoint=https://fake-server-url-value
|
|
- --concierge-ca-bundle-data=ZmFrZS1jZXJ0aWZpY2F0ZS1hdXRob3JpdHktZGF0YS12YWx1ZQ==
|
|
- --issuer=%s
|
|
- --client-id=pinniped-cli
|
|
- --scopes=offline_access,openid,pinniped:request-audience
|
|
- --ca-bundle-data=%s
|
|
- --request-audience=test-audience
|
|
command: '.../path/to/pinniped'
|
|
env: []
|
|
provideClusterInfo: true
|
|
`,
|
|
issuerURL,
|
|
base64.StdEncoding.EncodeToString([]byte(issuerCABundle)))
|
|
},
|
|
},
|
|
{
|
|
name: "when upstream idp related flags are sent, pass them through",
|
|
args: func(issuerCABundle string, issuerURL string) []string {
|
|
return []string{
|
|
"--kubeconfig", "./testdata/kubeconfig.yaml",
|
|
"--skip-validation",
|
|
"--upstream-identity-provider-name=some-oidc-idp",
|
|
"--upstream-identity-provider-type=oidc",
|
|
}
|
|
},
|
|
conciergeObjects: func(issuerCABundle string, issuerURL string) []runtime.Object {
|
|
return []runtime.Object{
|
|
credentialIssuer(),
|
|
jwtAuthenticator(issuerCABundle, issuerURL),
|
|
}
|
|
},
|
|
oidcDiscoveryStatusCode: http.StatusNotFound,
|
|
wantLogs: func(issuerCABundle string, issuerURL string) []string {
|
|
return []string{
|
|
`"level"=0 "msg"="discovered CredentialIssuer" "name"="test-credential-issuer"`,
|
|
`"level"=0 "msg"="discovered Concierge operating in TokenCredentialRequest API mode"`,
|
|
`"level"=0 "msg"="discovered Concierge endpoint" "endpoint"="https://fake-server-url-value"`,
|
|
`"level"=0 "msg"="discovered Concierge certificate authority bundle" "roots"=0`,
|
|
`"level"=0 "msg"="discovered JWTAuthenticator" "name"="test-authenticator"`,
|
|
fmt.Sprintf(`"level"=0 "msg"="discovered OIDC issuer" "issuer"="%s"`, issuerURL),
|
|
`"level"=0 "msg"="discovered OIDC audience" "audience"="test-audience"`,
|
|
`"level"=0 "msg"="discovered OIDC CA bundle" "roots"=1`,
|
|
}
|
|
},
|
|
wantStdout: func(issuerCABundle string, issuerURL string) string {
|
|
return here.Docf(`
|
|
apiVersion: v1
|
|
clusters:
|
|
- cluster:
|
|
certificate-authority-data: ZmFrZS1jZXJ0aWZpY2F0ZS1hdXRob3JpdHktZGF0YS12YWx1ZQ==
|
|
server: https://fake-server-url-value
|
|
name: kind-cluster-pinniped
|
|
contexts:
|
|
- context:
|
|
cluster: kind-cluster-pinniped
|
|
user: kind-user-pinniped
|
|
name: kind-context-pinniped
|
|
current-context: kind-context-pinniped
|
|
kind: Config
|
|
preferences: {}
|
|
users:
|
|
- name: kind-user-pinniped
|
|
user:
|
|
exec:
|
|
apiVersion: client.authentication.k8s.io/v1beta1
|
|
args:
|
|
- login
|
|
- oidc
|
|
- --enable-concierge
|
|
- --concierge-api-group-suffix=pinniped.dev
|
|
- --concierge-authenticator-name=test-authenticator
|
|
- --concierge-authenticator-type=jwt
|
|
- --concierge-endpoint=https://fake-server-url-value
|
|
- --concierge-ca-bundle-data=ZmFrZS1jZXJ0aWZpY2F0ZS1hdXRob3JpdHktZGF0YS12YWx1ZQ==
|
|
- --issuer=%s
|
|
- --client-id=pinniped-cli
|
|
- --scopes=offline_access,openid,pinniped:request-audience
|
|
- --ca-bundle-data=%s
|
|
- --request-audience=test-audience
|
|
- --upstream-identity-provider-name=some-oidc-idp
|
|
- --upstream-identity-provider-type=oidc
|
|
command: '.../path/to/pinniped'
|
|
env: []
|
|
provideClusterInfo: true
|
|
`,
|
|
issuerURL,
|
|
base64.StdEncoding.EncodeToString([]byte(issuerCABundle)))
|
|
},
|
|
},
|
|
{
|
|
name: "when upstream IDP related flags are sent, pass them through even when IDP discovery shows a different IDP",
|
|
args: func(issuerCABundle string, issuerURL string) []string {
|
|
return []string{
|
|
"--kubeconfig", "./testdata/kubeconfig.yaml",
|
|
"--skip-validation",
|
|
"--upstream-identity-provider-name=some-oidc-idp",
|
|
"--upstream-identity-provider-type=oidc",
|
|
}
|
|
},
|
|
conciergeObjects: func(issuerCABundle string, issuerURL string) []runtime.Object {
|
|
return []runtime.Object{
|
|
credentialIssuer(),
|
|
jwtAuthenticator(issuerCABundle, issuerURL),
|
|
}
|
|
},
|
|
oidcDiscoveryResponse: happyOIDCDiscoveryResponse,
|
|
idpsDiscoveryResponse: here.Docf(`{
|
|
"pinniped_identity_providers": [
|
|
{"name": "some-other-ldap-idp", "type": "ldap"}
|
|
]
|
|
}`),
|
|
wantLogs: func(issuerCABundle string, issuerURL string) []string {
|
|
return []string{
|
|
`"level"=0 "msg"="discovered CredentialIssuer" "name"="test-credential-issuer"`,
|
|
`"level"=0 "msg"="discovered Concierge operating in TokenCredentialRequest API mode"`,
|
|
`"level"=0 "msg"="discovered Concierge endpoint" "endpoint"="https://fake-server-url-value"`,
|
|
`"level"=0 "msg"="discovered Concierge certificate authority bundle" "roots"=0`,
|
|
`"level"=0 "msg"="discovered JWTAuthenticator" "name"="test-authenticator"`,
|
|
fmt.Sprintf(`"level"=0 "msg"="discovered OIDC issuer" "issuer"="%s"`, issuerURL),
|
|
`"level"=0 "msg"="discovered OIDC audience" "audience"="test-audience"`,
|
|
`"level"=0 "msg"="discovered OIDC CA bundle" "roots"=1`,
|
|
}
|
|
},
|
|
wantStdout: func(issuerCABundle string, issuerURL string) string {
|
|
return here.Docf(`
|
|
apiVersion: v1
|
|
clusters:
|
|
- cluster:
|
|
certificate-authority-data: ZmFrZS1jZXJ0aWZpY2F0ZS1hdXRob3JpdHktZGF0YS12YWx1ZQ==
|
|
server: https://fake-server-url-value
|
|
name: kind-cluster-pinniped
|
|
contexts:
|
|
- context:
|
|
cluster: kind-cluster-pinniped
|
|
user: kind-user-pinniped
|
|
name: kind-context-pinniped
|
|
current-context: kind-context-pinniped
|
|
kind: Config
|
|
preferences: {}
|
|
users:
|
|
- name: kind-user-pinniped
|
|
user:
|
|
exec:
|
|
apiVersion: client.authentication.k8s.io/v1beta1
|
|
args:
|
|
- login
|
|
- oidc
|
|
- --enable-concierge
|
|
- --concierge-api-group-suffix=pinniped.dev
|
|
- --concierge-authenticator-name=test-authenticator
|
|
- --concierge-authenticator-type=jwt
|
|
- --concierge-endpoint=https://fake-server-url-value
|
|
- --concierge-ca-bundle-data=ZmFrZS1jZXJ0aWZpY2F0ZS1hdXRob3JpdHktZGF0YS12YWx1ZQ==
|
|
- --issuer=%s
|
|
- --client-id=pinniped-cli
|
|
- --scopes=offline_access,openid,pinniped:request-audience
|
|
- --ca-bundle-data=%s
|
|
- --request-audience=test-audience
|
|
- --upstream-identity-provider-name=some-oidc-idp
|
|
- --upstream-identity-provider-type=oidc
|
|
command: '.../path/to/pinniped'
|
|
env: []
|
|
provideClusterInfo: true
|
|
`,
|
|
issuerURL,
|
|
base64.StdEncoding.EncodeToString([]byte(issuerCABundle)))
|
|
},
|
|
},
|
|
{
|
|
name: "supervisor upstream IDP discovery still works when --no-concierge is used",
|
|
args: func(issuerCABundle string, issuerURL string) []string {
|
|
f := testutil.WriteStringToTempFile(t, "testca-*.pem", issuerCABundle)
|
|
return []string{
|
|
"--kubeconfig", "./testdata/kubeconfig.yaml",
|
|
"--skip-validation",
|
|
"--no-concierge",
|
|
"--oidc-issuer", issuerURL,
|
|
"--oidc-ca-bundle", f.Name(),
|
|
}
|
|
},
|
|
oidcDiscoveryResponse: happyOIDCDiscoveryResponse,
|
|
idpsDiscoveryResponse: here.Docf(`{
|
|
"pinniped_identity_providers": [
|
|
{"name": "some-ldap-idp", "type": "ldap"}
|
|
]
|
|
}`),
|
|
wantStdout: func(issuerCABundle string, issuerURL string) string {
|
|
return here.Docf(`
|
|
apiVersion: v1
|
|
clusters:
|
|
- cluster:
|
|
certificate-authority-data: ZmFrZS1jZXJ0aWZpY2F0ZS1hdXRob3JpdHktZGF0YS12YWx1ZQ==
|
|
server: https://fake-server-url-value
|
|
name: kind-cluster-pinniped
|
|
contexts:
|
|
- context:
|
|
cluster: kind-cluster-pinniped
|
|
user: kind-user-pinniped
|
|
name: kind-context-pinniped
|
|
current-context: kind-context-pinniped
|
|
kind: Config
|
|
preferences: {}
|
|
users:
|
|
- name: kind-user-pinniped
|
|
user:
|
|
exec:
|
|
apiVersion: client.authentication.k8s.io/v1beta1
|
|
args:
|
|
- login
|
|
- oidc
|
|
- --issuer=%s
|
|
- --client-id=pinniped-cli
|
|
- --scopes=offline_access,openid,pinniped:request-audience
|
|
- --ca-bundle-data=%s
|
|
- --upstream-identity-provider-name=some-ldap-idp
|
|
- --upstream-identity-provider-type=ldap
|
|
command: '.../path/to/pinniped'
|
|
env: []
|
|
provideClusterInfo: true
|
|
`,
|
|
issuerURL,
|
|
base64.StdEncoding.EncodeToString([]byte(issuerCABundle)))
|
|
},
|
|
},
|
|
{
|
|
name: "supervisor upstream IDP discovery resolves ambiguity when type is specified but name is not",
|
|
args: func(issuerCABundle string, issuerURL string) []string {
|
|
f := testutil.WriteStringToTempFile(t, "testca-*.pem", issuerCABundle)
|
|
return []string{
|
|
"--kubeconfig", "./testdata/kubeconfig.yaml",
|
|
"--skip-validation",
|
|
"--no-concierge",
|
|
"--oidc-issuer", issuerURL,
|
|
"--oidc-ca-bundle", f.Name(),
|
|
"--upstream-identity-provider-type", "ldap",
|
|
}
|
|
},
|
|
oidcDiscoveryResponse: happyOIDCDiscoveryResponse,
|
|
idpsDiscoveryResponse: here.Docf(`{
|
|
"pinniped_identity_providers": [
|
|
{"name": "some-ldap-idp", "type": "ldap"},
|
|
{"name": "some-oidc-idp", "type": "oidc"},
|
|
{"name": "some-other-oidc-idp", "type": "oidc"}
|
|
]
|
|
}`),
|
|
wantStdout: func(issuerCABundle string, issuerURL string) string {
|
|
return here.Docf(`
|
|
apiVersion: v1
|
|
clusters:
|
|
- cluster:
|
|
certificate-authority-data: ZmFrZS1jZXJ0aWZpY2F0ZS1hdXRob3JpdHktZGF0YS12YWx1ZQ==
|
|
server: https://fake-server-url-value
|
|
name: kind-cluster-pinniped
|
|
contexts:
|
|
- context:
|
|
cluster: kind-cluster-pinniped
|
|
user: kind-user-pinniped
|
|
name: kind-context-pinniped
|
|
current-context: kind-context-pinniped
|
|
kind: Config
|
|
preferences: {}
|
|
users:
|
|
- name: kind-user-pinniped
|
|
user:
|
|
exec:
|
|
apiVersion: client.authentication.k8s.io/v1beta1
|
|
args:
|
|
- login
|
|
- oidc
|
|
- --issuer=%s
|
|
- --client-id=pinniped-cli
|
|
- --scopes=offline_access,openid,pinniped:request-audience
|
|
- --ca-bundle-data=%s
|
|
- --upstream-identity-provider-name=some-ldap-idp
|
|
- --upstream-identity-provider-type=ldap
|
|
command: '.../path/to/pinniped'
|
|
env: []
|
|
provideClusterInfo: true
|
|
`,
|
|
issuerURL,
|
|
base64.StdEncoding.EncodeToString([]byte(issuerCABundle)))
|
|
},
|
|
},
|
|
{
|
|
name: "supervisor upstream IDP discovery resolves ambiguity when name is specified but type is not",
|
|
args: func(issuerCABundle string, issuerURL string) []string {
|
|
f := testutil.WriteStringToTempFile(t, "testca-*.pem", issuerCABundle)
|
|
return []string{
|
|
"--kubeconfig", "./testdata/kubeconfig.yaml",
|
|
"--skip-validation",
|
|
"--no-concierge",
|
|
"--oidc-issuer", issuerURL,
|
|
"--oidc-ca-bundle", f.Name(),
|
|
"--upstream-identity-provider-name", "some-ldap-idp",
|
|
}
|
|
},
|
|
oidcDiscoveryResponse: happyOIDCDiscoveryResponse,
|
|
idpsDiscoveryResponse: here.Docf(`{
|
|
"pinniped_identity_providers": [
|
|
{"name": "some-ldap-idp", "type": "ldap"},
|
|
{"name": "some-oidc-idp", "type": "oidc"},
|
|
{"name": "some-other-oidc-idp", "type": "oidc"}
|
|
]
|
|
}`),
|
|
wantStdout: func(issuerCABundle string, issuerURL string) string {
|
|
return here.Docf(`
|
|
apiVersion: v1
|
|
clusters:
|
|
- cluster:
|
|
certificate-authority-data: ZmFrZS1jZXJ0aWZpY2F0ZS1hdXRob3JpdHktZGF0YS12YWx1ZQ==
|
|
server: https://fake-server-url-value
|
|
name: kind-cluster-pinniped
|
|
contexts:
|
|
- context:
|
|
cluster: kind-cluster-pinniped
|
|
user: kind-user-pinniped
|
|
name: kind-context-pinniped
|
|
current-context: kind-context-pinniped
|
|
kind: Config
|
|
preferences: {}
|
|
users:
|
|
- name: kind-user-pinniped
|
|
user:
|
|
exec:
|
|
apiVersion: client.authentication.k8s.io/v1beta1
|
|
args:
|
|
- login
|
|
- oidc
|
|
- --issuer=%s
|
|
- --client-id=pinniped-cli
|
|
- --scopes=offline_access,openid,pinniped:request-audience
|
|
- --ca-bundle-data=%s
|
|
- --upstream-identity-provider-name=some-ldap-idp
|
|
- --upstream-identity-provider-type=ldap
|
|
command: '.../path/to/pinniped'
|
|
env: []
|
|
provideClusterInfo: true
|
|
`,
|
|
issuerURL,
|
|
base64.StdEncoding.EncodeToString([]byte(issuerCABundle)))
|
|
},
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
tt := tt
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
var issuerEndpointPtr *string
|
|
issuerCABundle, issuerEndpoint := testutil.TLSTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("content-type", "application/json")
|
|
switch r.URL.Path {
|
|
case "/.well-known/openid-configuration":
|
|
jsonResponseBody := "{}"
|
|
if tt.oidcDiscoveryResponse != nil {
|
|
jsonResponseBody = tt.oidcDiscoveryResponse(*issuerEndpointPtr)
|
|
}
|
|
if tt.oidcDiscoveryStatusCode == 0 {
|
|
tt.oidcDiscoveryStatusCode = http.StatusOK
|
|
}
|
|
w.WriteHeader(tt.oidcDiscoveryStatusCode)
|
|
_, err = w.Write([]byte(jsonResponseBody))
|
|
require.NoError(t, err)
|
|
case "/v1alpha1/pinniped_identity_providers":
|
|
jsonResponseBody := tt.idpsDiscoveryResponse
|
|
if tt.idpsDiscoveryResponse == "" {
|
|
jsonResponseBody = "{}"
|
|
}
|
|
if tt.idpsDiscoveryStatusCode == 0 {
|
|
tt.idpsDiscoveryStatusCode = http.StatusOK
|
|
}
|
|
w.WriteHeader(tt.idpsDiscoveryStatusCode)
|
|
_, err = w.Write([]byte(jsonResponseBody))
|
|
require.NoError(t, err)
|
|
default:
|
|
t.Fatalf("tried to call issuer at a path that wasn't one of the expected discovery endpoints.")
|
|
}
|
|
})
|
|
issuerEndpointPtr = &issuerEndpoint
|
|
|
|
testLog := testlogger.New(t)
|
|
cmd := kubeconfigCommand(kubeconfigDeps{
|
|
getPathToSelf: func() (string, error) {
|
|
if tt.getPathToSelfErr != nil {
|
|
return "", tt.getPathToSelfErr
|
|
}
|
|
return ".../path/to/pinniped", nil
|
|
},
|
|
getClientset: func(clientConfig clientcmd.ClientConfig, apiGroupSuffix string) (conciergeclientset.Interface, error) {
|
|
if tt.wantAPIGroupSuffix == "" {
|
|
require.Equal(t, "pinniped.dev", apiGroupSuffix) // "pinniped.dev" = api group suffix default
|
|
} else {
|
|
require.Equal(t, tt.wantAPIGroupSuffix, apiGroupSuffix)
|
|
}
|
|
if tt.getClientsetErr != nil {
|
|
return nil, tt.getClientsetErr
|
|
}
|
|
fake := fakeconciergeclientset.NewSimpleClientset()
|
|
if tt.conciergeObjects != nil {
|
|
fake = fakeconciergeclientset.NewSimpleClientset(tt.conciergeObjects(issuerCABundle, issuerEndpoint)...)
|
|
}
|
|
if len(tt.conciergeReactions) > 0 {
|
|
fake.ReactionChain = append(tt.conciergeReactions, fake.ReactionChain...)
|
|
}
|
|
return fake, nil
|
|
},
|
|
log: testLog,
|
|
})
|
|
require.NotNil(t, cmd)
|
|
|
|
var stdout, stderr bytes.Buffer
|
|
cmd.SetOut(&stdout)
|
|
cmd.SetErr(&stderr)
|
|
|
|
cmd.SetArgs(tt.args(issuerCABundle, issuerEndpoint))
|
|
|
|
err := cmd.Execute()
|
|
if tt.wantError {
|
|
require.Error(t, err)
|
|
} else {
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
var expectedLogs []string
|
|
if tt.wantLogs != nil {
|
|
expectedLogs = tt.wantLogs(issuerCABundle, issuerEndpoint)
|
|
}
|
|
testLog.Expect(expectedLogs)
|
|
|
|
expectedStdout := ""
|
|
if tt.wantStdout != nil {
|
|
expectedStdout = tt.wantStdout(issuerCABundle, issuerEndpoint)
|
|
}
|
|
require.Equal(t, expectedStdout, stdout.String(), "unexpected stdout")
|
|
|
|
expectedStderr := ""
|
|
if tt.wantStderr != nil {
|
|
expectedStderr = tt.wantStderr(issuerCABundle, issuerEndpoint)
|
|
}
|
|
require.Equal(t, expectedStderr, stderr.String(), "unexpected stderr")
|
|
})
|
|
}
|
|
}
|