Add --concierge-credential-issuer flag to "pinniped get kubeconfig" command.

This flag selects a CredentialIssuer to use when detecting what mode the Concierge is in on a cluster. If not specified, the command will look for a single CredentialIssuer. If there are multiple, then the flag is required.

Signed-off-by: Matt Moyer <moyerm@vmware.com>
This commit is contained in:
Matt Moyer 2021-02-25 14:16:40 -06:00
parent 1c7c22352f
commit f937ae2c07
No known key found for this signature in database
GPG Key ID: EAE88AD172C5AE2D
6 changed files with 307 additions and 26 deletions

View File

@ -16,6 +16,7 @@ type conciergeMode int
var _ flag.Value = new(conciergeMode) var _ flag.Value = new(conciergeMode)
const ( const (
modeUnknown conciergeMode = iota
modeTokenCredentialRequestAPI conciergeMode = iota modeTokenCredentialRequestAPI conciergeMode = iota
modeImpersonationProxy conciergeMode = iota modeImpersonationProxy conciergeMode = iota
) )
@ -32,6 +33,10 @@ func (c *conciergeMode) String() string {
} }
func (c *conciergeMode) Set(s string) error { func (c *conciergeMode) Set(s string) error {
if strings.EqualFold(s, "") {
*c = modeUnknown
return nil
}
if strings.EqualFold(s, "TokenCredentialRequestAPI") { if strings.EqualFold(s, "TokenCredentialRequestAPI") {
*c = modeTokenCredentialRequestAPI *c = modeTokenCredentialRequestAPI
return nil return nil

View File

@ -12,7 +12,7 @@ import (
func TestConciergeModeFlag(t *testing.T) { func TestConciergeModeFlag(t *testing.T) {
var m conciergeMode var m conciergeMode
require.Equal(t, "mode", m.Type()) require.Equal(t, "mode", m.Type())
require.Equal(t, modeTokenCredentialRequestAPI, m) require.Equal(t, modeUnknown, m)
require.EqualError(t, m.Set("foo"), `invalid mode "foo", valid modes are TokenCredentialRequestAPI and ImpersonationProxy`) require.EqualError(t, m.Set("foo"), `invalid mode "foo", valid modes are TokenCredentialRequestAPI and ImpersonationProxy`)
require.NoError(t, m.Set("TokenCredentialRequestAPI")) require.NoError(t, m.Set("TokenCredentialRequestAPI"))

View File

@ -25,6 +25,7 @@ import (
_ "k8s.io/client-go/plugin/pkg/client/auth" // Adds handlers for various dynamic auth plugins in client-go _ "k8s.io/client-go/plugin/pkg/client/auth" // Adds handlers for various dynamic auth plugins in client-go
conciergev1alpha1 "go.pinniped.dev/generated/latest/apis/concierge/authentication/v1alpha1" 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" conciergeclientset "go.pinniped.dev/generated/latest/client/concierge/clientset/versioned"
"go.pinniped.dev/internal/groupsuffix" "go.pinniped.dev/internal/groupsuffix"
"go.pinniped.dev/internal/kubeclient" "go.pinniped.dev/internal/kubeclient"
@ -74,6 +75,7 @@ type getKubeconfigOIDCParams struct {
type getKubeconfigConciergeParams struct { type getKubeconfigConciergeParams struct {
disabled bool disabled bool
credentialIssuer string
authenticatorName string authenticatorName string
authenticatorType string authenticatorType string
apiGroupSuffix string apiGroupSuffix string
@ -109,6 +111,7 @@ func kubeconfigCommand(deps kubeconfigDeps) *cobra.Command {
f.BoolVar(&flags.concierge.disabled, "no-concierge", false, "Generate a configuration which does not use the Concierge, but sends the credential to the cluster directly") f.BoolVar(&flags.concierge.disabled, "no-concierge", false, "Generate a configuration which does not use the Concierge, but sends the credential to the cluster directly")
f.StringVar(&namespace, "concierge-namespace", "pinniped-concierge", "Namespace in which the Concierge was installed") f.StringVar(&namespace, "concierge-namespace", "pinniped-concierge", "Namespace in which the Concierge was installed")
f.StringVar(&flags.concierge.credentialIssuer, "concierge-credential-issuer", "", "Concierge CredentialIssuer object to use for autodiscovery (default: autodiscover)")
f.StringVar(&flags.concierge.authenticatorType, "concierge-authenticator-type", "", "Concierge authenticator type (e.g., 'webhook', 'jwt') (default: autodiscover)") f.StringVar(&flags.concierge.authenticatorType, "concierge-authenticator-type", "", "Concierge authenticator type (e.g., 'webhook', 'jwt') (default: autodiscover)")
f.StringVar(&flags.concierge.authenticatorName, "concierge-authenticator-name", "", "Concierge authenticator name (default: autodiscover)") f.StringVar(&flags.concierge.authenticatorName, "concierge-authenticator-name", "", "Concierge authenticator name (default: autodiscover)")
f.StringVar(&flags.concierge.apiGroupSuffix, "concierge-api-group-suffix", groupsuffix.PinnipedDefaultSuffix, "Concierge API group suffix") f.StringVar(&flags.concierge.apiGroupSuffix, "concierge-api-group-suffix", groupsuffix.PinnipedDefaultSuffix, "Concierge API group suffix")
@ -178,6 +181,11 @@ func runGetKubeconfig(out io.Writer, deps kubeconfigDeps, flags getKubeconfigPar
} }
if !flags.concierge.disabled { if !flags.concierge.disabled {
credentialIssuer, err := lookupCredentialIssuer(clientset, flags.concierge.credentialIssuer)
if err != nil {
return err
}
authenticator, err := lookupAuthenticator( authenticator, err := lookupAuthenticator(
clientset, clientset,
flags.concierge.authenticatorType, flags.concierge.authenticatorType,
@ -186,7 +194,8 @@ func runGetKubeconfig(out io.Writer, deps kubeconfigDeps, flags getKubeconfigPar
if err != nil { if err != nil {
return err return err
} }
if err := configureConcierge(authenticator, &flags, cluster, &oidcCABundle, &execConfig); err != nil {
if err := configureConcierge(credentialIssuer, authenticator, &flags, cluster, &oidcCABundle, &execConfig); err != nil {
return err return err
} }
} }
@ -209,7 +218,7 @@ func runGetKubeconfig(out io.Writer, deps kubeconfigDeps, flags getKubeconfigPar
// Otherwise continue to parse the OIDC-related flags and output a config that runs `pinniped login oidc`. // Otherwise continue to parse the OIDC-related flags and output a config that runs `pinniped login oidc`.
execConfig.Args = append([]string{"login", "oidc"}, execConfig.Args...) execConfig.Args = append([]string{"login", "oidc"}, execConfig.Args...)
if flags.oidc.issuer == "" { if flags.oidc.issuer == "" {
return fmt.Errorf("could not autodiscover --oidc-issuer, and none was provided") return fmt.Errorf("could not autodiscover --oidc-issuer and none was provided")
} }
execConfig.Args = append(execConfig.Args, execConfig.Args = append(execConfig.Args,
"--issuer="+flags.oidc.issuer, "--issuer="+flags.oidc.issuer,
@ -237,18 +246,30 @@ func runGetKubeconfig(out io.Writer, deps kubeconfigDeps, flags getKubeconfigPar
return writeConfigAsYAML(out, newExecKubeconfig(cluster, &execConfig)) return writeConfigAsYAML(out, newExecKubeconfig(cluster, &execConfig))
} }
func configureConcierge(authenticator metav1.Object, flags *getKubeconfigParams, v1Cluster *clientcmdapi.Cluster, oidcCABundle *string, execConfig *clientcmdapi.ExecConfig) error { func configureConcierge(credentialIssuer *configv1alpha1.CredentialIssuer, authenticator metav1.Object, flags *getKubeconfigParams, v1Cluster *clientcmdapi.Cluster, oidcCABundle *string, execConfig *clientcmdapi.ExecConfig) error {
var conciergeCABundleData []byte
if flags.concierge.mode == modeImpersonationProxy { // Autodiscover the --concierge-mode.
// TODO what to do if --use-impersonation-proxy is set but flags.concierge.caBundlePath is not??? if flags.concierge.mode == modeUnknown {
// TODO dont do this twice
conciergeCaBundleData, err := loadCABundlePaths([]string{flags.concierge.caBundlePath}) if credentialIssuer.Status.KubeConfigInfo != nil {
if err != nil { // Prefer the TokenCredentialRequest API if available.
return fmt.Errorf("could not read --concierge-ca-bundle: %w", err) flags.concierge.mode = modeTokenCredentialRequestAPI
} else if credentialIssuer.Status.ImpersonationProxyInfo != nil {
// Otherwise prefer the impersonation proxy if it seems configured.
flags.concierge.mode = modeImpersonationProxy
} else {
return fmt.Errorf("could not autodiscover --concierge-mode and none was provided")
}
} }
v1Cluster.CertificateAuthorityData = []byte(conciergeCaBundleData) if flags.concierge.mode == modeImpersonationProxy && credentialIssuer.Status.ImpersonationProxyInfo != nil {
v1Cluster.Server = flags.concierge.endpoint flags.concierge.endpoint = credentialIssuer.Status.ImpersonationProxyInfo.Endpoint
var err error
conciergeCABundleData, err = base64.StdEncoding.DecodeString(credentialIssuer.Status.ImpersonationProxyInfo.CertificateAuthorityData)
if err != nil {
return fmt.Errorf("autodiscovered Concierge CA bundle is invalid: %w", err)
}
} }
switch auth := authenticator.(type) { switch auth := authenticator.(type) {
@ -292,15 +313,16 @@ func configureConcierge(authenticator metav1.Object, flags *getKubeconfigParams,
flags.concierge.endpoint = v1Cluster.Server flags.concierge.endpoint = v1Cluster.Server
} }
var encodedConciergeCaBundleData string if conciergeCABundleData == nil {
if flags.concierge.caBundlePath == "" { if flags.concierge.caBundlePath == "" {
encodedConciergeCaBundleData = base64.StdEncoding.EncodeToString(v1Cluster.CertificateAuthorityData) conciergeCABundleData = v1Cluster.CertificateAuthorityData
} else { } else {
conciergeCaBundleData, err := loadCABundlePaths([]string{flags.concierge.caBundlePath}) caBundleString, err := loadCABundlePaths([]string{flags.concierge.caBundlePath})
if err != nil { if err != nil {
return fmt.Errorf("could not read --concierge-ca-bundle: %w", err) return fmt.Errorf("could not read --concierge-ca-bundle: %w", err)
} }
encodedConciergeCaBundleData = base64.StdEncoding.EncodeToString([]byte(conciergeCaBundleData)) conciergeCABundleData = []byte(caBundleString)
}
} }
// Append the flags to configure the Concierge credential exchange at runtime. // Append the flags to configure the Concierge credential exchange at runtime.
@ -310,9 +332,16 @@ func configureConcierge(authenticator metav1.Object, flags *getKubeconfigParams,
"--concierge-authenticator-name="+flags.concierge.authenticatorName, "--concierge-authenticator-name="+flags.concierge.authenticatorName,
"--concierge-authenticator-type="+flags.concierge.authenticatorType, "--concierge-authenticator-type="+flags.concierge.authenticatorType,
"--concierge-endpoint="+flags.concierge.endpoint, "--concierge-endpoint="+flags.concierge.endpoint,
"--concierge-ca-bundle-data="+encodedConciergeCaBundleData, "--concierge-ca-bundle-data="+base64.StdEncoding.EncodeToString(conciergeCABundleData),
"--concierge-mode="+flags.concierge.mode.String(), "--concierge-mode="+flags.concierge.mode.String(),
) )
// If we're in impersonation proxy mode, the main server endpoint for the kubeconfig also needs to point to the proxy
if flags.concierge.mode == modeImpersonationProxy {
v1Cluster.CertificateAuthorityData = conciergeCABundleData
v1Cluster.Server = flags.concierge.endpoint
}
return nil return nil
} }
@ -343,6 +372,29 @@ func newExecKubeconfig(cluster *clientcmdapi.Cluster, execConfig *clientcmdapi.E
} }
} }
func lookupCredentialIssuer(clientset conciergeclientset.Interface, name string) (*configv1alpha1.CredentialIssuer, error) {
ctx, cancelFunc := context.WithTimeout(context.Background(), time.Second*20)
defer cancelFunc()
// If the name is specified, get that object.
if name != "" {
return clientset.ConfigV1alpha1().CredentialIssuers().Get(ctx, name, metav1.GetOptions{})
}
// Otherwise list all the available CredentialIssuers and hope there's just a single one
results, err := clientset.ConfigV1alpha1().CredentialIssuers().List(ctx, metav1.ListOptions{})
if err != nil {
return nil, fmt.Errorf("failed to list CredentialIssuer objects for autodiscovery: %w", err)
}
if len(results.Items) == 0 {
return nil, fmt.Errorf("no CredentialIssuers were found")
}
if len(results.Items) > 1 {
return nil, fmt.Errorf("multiple CredentialIssuers were found, so the --concierge-credential-issuer flag must be specified")
}
return &results.Items[0], nil
}
func lookupAuthenticator(clientset conciergeclientset.Interface, authType, authName string) (metav1.Object, error) { func lookupAuthenticator(clientset conciergeclientset.Interface, authType, authName string) (metav1.Object, error) {
ctx, cancelFunc := context.WithTimeout(context.Background(), time.Second*20) ctx, cancelFunc := context.WithTimeout(context.Background(), time.Second*20)
defer cancelFunc() defer cancelFunc()

View File

@ -20,6 +20,7 @@ import (
"k8s.io/client-go/tools/clientcmd" "k8s.io/client-go/tools/clientcmd"
conciergev1alpha1 "go.pinniped.dev/generated/latest/apis/concierge/authentication/v1alpha1" 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" conciergeclientset "go.pinniped.dev/generated/latest/client/concierge/clientset/versioned"
fakeconciergeclientset "go.pinniped.dev/generated/latest/client/concierge/clientset/versioned/fake" fakeconciergeclientset "go.pinniped.dev/generated/latest/client/concierge/clientset/versioned/fake"
"go.pinniped.dev/internal/certauthority" "go.pinniped.dev/internal/certauthority"
@ -65,6 +66,7 @@ func TestGetKubeconfig(t *testing.T) {
--concierge-authenticator-name string Concierge authenticator name (default: autodiscover) --concierge-authenticator-name string Concierge authenticator name (default: autodiscover)
--concierge-authenticator-type string Concierge authenticator type (e.g., 'webhook', 'jwt') (default: autodiscover) --concierge-authenticator-type string Concierge authenticator type (e.g., 'webhook', 'jwt') (default: autodiscover)
--concierge-ca-bundle string Path to TLS certificate authority bundle (PEM format, optional, can be repeated) to use when connecting to the Concierge --concierge-ca-bundle string 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-endpoint string API base for the Concierge endpoint
--concierge-mode mode Concierge mode of operation (default TokenCredentialRequestAPI) --concierge-mode mode Concierge mode of operation (default TokenCredentialRequestAPI)
-h, --help help for kubeconfig -h, --help help for kubeconfig
@ -134,6 +136,31 @@ func TestGetKubeconfig(t *testing.T) {
Error: could not configure Kubernetes client: some kube error Error: could not configure Kubernetes client: some kube error
`), `),
}, },
{
name: "no credentialissuers",
args: []string{
"--kubeconfig", "./testdata/kubeconfig.yaml",
},
wantError: true,
wantStderr: here.Doc(`
Error: no CredentialIssuers were found
`),
},
{
name: "credentialissuer not found",
args: []string{
"--kubeconfig", "./testdata/kubeconfig.yaml",
"--concierge-credential-issuer", "does-not-exist",
},
conciergeObjects: []runtime.Object{
&configv1alpha1.CredentialIssuer{ObjectMeta: metav1.ObjectMeta{Name: "test-credential-issuer"}},
},
wantError: true,
wantStderr: here.Doc(`
Error: credentialissuers.config.concierge.pinniped.dev "does-not-exist" not found
`),
},
{ {
name: "webhook authenticator not found", name: "webhook authenticator not found",
args: []string{ args: []string{
@ -141,6 +168,9 @@ func TestGetKubeconfig(t *testing.T) {
"--concierge-authenticator-type", "webhook", "--concierge-authenticator-type", "webhook",
"--concierge-authenticator-name", "test-authenticator", "--concierge-authenticator-name", "test-authenticator",
}, },
conciergeObjects: []runtime.Object{
&configv1alpha1.CredentialIssuer{ObjectMeta: metav1.ObjectMeta{Name: "test-credential-issuer"}},
},
wantError: true, wantError: true,
wantStderr: here.Doc(` wantStderr: here.Doc(`
Error: webhookauthenticators.authentication.concierge.pinniped.dev "test-authenticator" not found Error: webhookauthenticators.authentication.concierge.pinniped.dev "test-authenticator" not found
@ -153,6 +183,9 @@ func TestGetKubeconfig(t *testing.T) {
"--concierge-authenticator-type", "jwt", "--concierge-authenticator-type", "jwt",
"--concierge-authenticator-name", "test-authenticator", "--concierge-authenticator-name", "test-authenticator",
}, },
conciergeObjects: []runtime.Object{
&configv1alpha1.CredentialIssuer{ObjectMeta: metav1.ObjectMeta{Name: "test-credential-issuer"}},
},
wantError: true, wantError: true,
wantStderr: here.Doc(` wantStderr: here.Doc(`
Error: jwtauthenticators.authentication.concierge.pinniped.dev "test-authenticator" not found Error: jwtauthenticators.authentication.concierge.pinniped.dev "test-authenticator" not found
@ -165,6 +198,9 @@ func TestGetKubeconfig(t *testing.T) {
"--concierge-authenticator-type", "invalid", "--concierge-authenticator-type", "invalid",
"--concierge-authenticator-name", "test-authenticator", "--concierge-authenticator-name", "test-authenticator",
}, },
conciergeObjects: []runtime.Object{
&configv1alpha1.CredentialIssuer{ObjectMeta: metav1.ObjectMeta{Name: "test-credential-issuer"}},
},
wantError: true, wantError: true,
wantStderr: here.Doc(` wantStderr: here.Doc(`
Error: invalid authenticator type "invalid", supported values are "webhook" and "jwt" Error: invalid authenticator type "invalid", supported values are "webhook" and "jwt"
@ -175,6 +211,9 @@ func TestGetKubeconfig(t *testing.T) {
args: []string{ args: []string{
"--kubeconfig", "./testdata/kubeconfig.yaml", "--kubeconfig", "./testdata/kubeconfig.yaml",
}, },
conciergeObjects: []runtime.Object{
&configv1alpha1.CredentialIssuer{ObjectMeta: metav1.ObjectMeta{Name: "test-credential-issuer"}},
},
conciergeReactions: []kubetesting.Reactor{ conciergeReactions: []kubetesting.Reactor{
&kubetesting.SimpleReactor{ &kubetesting.SimpleReactor{
Verb: "*", Verb: "*",
@ -194,6 +233,9 @@ func TestGetKubeconfig(t *testing.T) {
args: []string{ args: []string{
"--kubeconfig", "./testdata/kubeconfig.yaml", "--kubeconfig", "./testdata/kubeconfig.yaml",
}, },
conciergeObjects: []runtime.Object{
&configv1alpha1.CredentialIssuer{ObjectMeta: metav1.ObjectMeta{Name: "test-credential-issuer"}},
},
conciergeReactions: []kubetesting.Reactor{ conciergeReactions: []kubetesting.Reactor{
&kubetesting.SimpleReactor{ &kubetesting.SimpleReactor{
Verb: "*", Verb: "*",
@ -213,6 +255,9 @@ func TestGetKubeconfig(t *testing.T) {
args: []string{ args: []string{
"--kubeconfig", "./testdata/kubeconfig.yaml", "--kubeconfig", "./testdata/kubeconfig.yaml",
}, },
conciergeObjects: []runtime.Object{
&configv1alpha1.CredentialIssuer{ObjectMeta: metav1.ObjectMeta{Name: "test-credential-issuer"}},
},
wantError: true, wantError: true,
wantStderr: here.Doc(` wantStderr: here.Doc(`
Error: no authenticators were found Error: no authenticators were found
@ -224,6 +269,7 @@ func TestGetKubeconfig(t *testing.T) {
"--kubeconfig", "./testdata/kubeconfig.yaml", "--kubeconfig", "./testdata/kubeconfig.yaml",
}, },
conciergeObjects: []runtime.Object{ conciergeObjects: []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-1"}},
&conciergev1alpha1.JWTAuthenticator{ObjectMeta: metav1.ObjectMeta{Name: "test-authenticator-2"}}, &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-3"}},
@ -234,17 +280,62 @@ func TestGetKubeconfig(t *testing.T) {
Error: multiple authenticators were found, so the --concierge-authenticator-type/--concierge-authenticator-name flags must be specified Error: multiple authenticators were found, so the --concierge-authenticator-type/--concierge-authenticator-name flags must be specified
`), `),
}, },
{
name: "autodetect webhook authenticator, bad credential issuer with no status",
args: []string{
"--kubeconfig", "./testdata/kubeconfig.yaml",
},
conciergeObjects: []runtime.Object{
&configv1alpha1.CredentialIssuer{ObjectMeta: metav1.ObjectMeta{Name: "test-credential-issuer"}},
&conciergev1alpha1.WebhookAuthenticator{ObjectMeta: metav1.ObjectMeta{Name: "test-authenticator"}},
},
wantError: true,
wantStderr: here.Doc(`
Error: could not autodiscover --concierge-mode and none was provided
`),
},
{
name: "autodetect webhook authenticator, bad credential issuer with invalid impersonation CA",
args: []string{
"--kubeconfig", "./testdata/kubeconfig.yaml",
},
conciergeObjects: []runtime.Object{
&configv1alpha1.CredentialIssuer{
ObjectMeta: metav1.ObjectMeta{Name: "test-credential-issuer"},
Status: configv1alpha1.CredentialIssuerStatus{
ImpersonationProxyInfo: &configv1alpha1.CredentialIssuerImpersonationProxyInfo{
Endpoint: "https://impersonation-endpoint",
CertificateAuthorityData: "invalid-base-64",
},
},
},
&conciergev1alpha1.WebhookAuthenticator{ObjectMeta: metav1.ObjectMeta{Name: "test-authenticator"}},
},
wantError: true,
wantStderr: here.Doc(`
Error: autodiscovered Concierge CA bundle is invalid: illegal base64 data at input byte 7
`),
},
{ {
name: "autodetect webhook authenticator, missing --oidc-issuer", name: "autodetect webhook authenticator, missing --oidc-issuer",
args: []string{ args: []string{
"--kubeconfig", "./testdata/kubeconfig.yaml", "--kubeconfig", "./testdata/kubeconfig.yaml",
}, },
conciergeObjects: []runtime.Object{ conciergeObjects: []runtime.Object{
&configv1alpha1.CredentialIssuer{
ObjectMeta: metav1.ObjectMeta{Name: "test-credential-issuer"},
Status: configv1alpha1.CredentialIssuerStatus{
KubeConfigInfo: &configv1alpha1.CredentialIssuerKubeConfigInfo{
Server: "https://concierge-endpoint",
CertificateAuthorityData: "ZmFrZS1jZXJ0aWZpY2F0ZS1hdXRob3JpdHktZGF0YS12YWx1ZQ==",
},
},
},
&conciergev1alpha1.WebhookAuthenticator{ObjectMeta: metav1.ObjectMeta{Name: "test-authenticator"}}, &conciergev1alpha1.WebhookAuthenticator{ObjectMeta: metav1.ObjectMeta{Name: "test-authenticator"}},
}, },
wantError: true, wantError: true,
wantStderr: here.Doc(` wantStderr: here.Doc(`
Error: could not autodiscover --oidc-issuer, and none was provided Error: could not autodiscover --oidc-issuer and none was provided
`), `),
}, },
{ {
@ -253,6 +344,15 @@ func TestGetKubeconfig(t *testing.T) {
"--kubeconfig", "./testdata/kubeconfig.yaml", "--kubeconfig", "./testdata/kubeconfig.yaml",
}, },
conciergeObjects: []runtime.Object{ conciergeObjects: []runtime.Object{
&configv1alpha1.CredentialIssuer{
ObjectMeta: metav1.ObjectMeta{Name: "test-credential-issuer"},
Status: configv1alpha1.CredentialIssuerStatus{
KubeConfigInfo: &configv1alpha1.CredentialIssuerKubeConfigInfo{
Server: "https://concierge-endpoint",
CertificateAuthorityData: "ZmFrZS1jZXJ0aWZpY2F0ZS1hdXRob3JpdHktZGF0YS12YWx1ZQ==",
},
},
},
&conciergev1alpha1.JWTAuthenticator{ &conciergev1alpha1.JWTAuthenticator{
ObjectMeta: metav1.ObjectMeta{Name: "test-authenticator"}, ObjectMeta: metav1.ObjectMeta{Name: "test-authenticator"},
Spec: conciergev1alpha1.JWTAuthenticatorSpec{ Spec: conciergev1alpha1.JWTAuthenticatorSpec{
@ -268,7 +368,7 @@ func TestGetKubeconfig(t *testing.T) {
`), `),
}, },
{ {
name: " invalid concierge ca bundle", name: "invalid concierge ca bundle",
args: []string{ args: []string{
"--kubeconfig", "./testdata/kubeconfig.yaml", "--kubeconfig", "./testdata/kubeconfig.yaml",
"--concierge-ca-bundle", "./does/not/exist", "--concierge-ca-bundle", "./does/not/exist",
@ -278,6 +378,15 @@ func TestGetKubeconfig(t *testing.T) {
"--concierge-mode", "ImpersonationProxy", "--concierge-mode", "ImpersonationProxy",
}, },
conciergeObjects: []runtime.Object{ conciergeObjects: []runtime.Object{
&configv1alpha1.CredentialIssuer{
ObjectMeta: metav1.ObjectMeta{Name: "test-credential-issuer"},
Status: configv1alpha1.CredentialIssuerStatus{
KubeConfigInfo: &configv1alpha1.CredentialIssuerKubeConfigInfo{
Server: "https://concierge-endpoint",
CertificateAuthorityData: "ZmFrZS1jZXJ0aWZpY2F0ZS1hdXRob3JpdHktZGF0YS12YWx1ZQ==",
},
},
},
&conciergev1alpha1.WebhookAuthenticator{ObjectMeta: metav1.ObjectMeta{Name: "test-authenticator"}}, &conciergev1alpha1.WebhookAuthenticator{ObjectMeta: metav1.ObjectMeta{Name: "test-authenticator"}},
}, },
wantError: true, wantError: true,
@ -293,6 +402,15 @@ func TestGetKubeconfig(t *testing.T) {
"--static-token-env", "TEST_TOKEN", "--static-token-env", "TEST_TOKEN",
}, },
conciergeObjects: []runtime.Object{ conciergeObjects: []runtime.Object{
&configv1alpha1.CredentialIssuer{
ObjectMeta: metav1.ObjectMeta{Name: "test-credential-issuer"},
Status: configv1alpha1.CredentialIssuerStatus{
KubeConfigInfo: &configv1alpha1.CredentialIssuerKubeConfigInfo{
Server: "https://concierge-endpoint",
CertificateAuthorityData: "ZmFrZS1jZXJ0aWZpY2F0ZS1hdXRob3JpdHktZGF0YS12YWx1ZQ==",
},
},
},
&conciergev1alpha1.WebhookAuthenticator{ObjectMeta: metav1.ObjectMeta{Name: "test-authenticator"}}, &conciergev1alpha1.WebhookAuthenticator{ObjectMeta: metav1.ObjectMeta{Name: "test-authenticator"}},
}, },
wantError: true, wantError: true,
@ -317,6 +435,15 @@ func TestGetKubeconfig(t *testing.T) {
"--static-token", "test-token", "--static-token", "test-token",
}, },
conciergeObjects: []runtime.Object{ conciergeObjects: []runtime.Object{
&configv1alpha1.CredentialIssuer{
ObjectMeta: metav1.ObjectMeta{Name: "test-credential-issuer"},
Status: configv1alpha1.CredentialIssuerStatus{
KubeConfigInfo: &configv1alpha1.CredentialIssuerKubeConfigInfo{
Server: "https://concierge-endpoint",
CertificateAuthorityData: "ZmFrZS1jZXJ0aWZpY2F0ZS1hdXRob3JpdHktZGF0YS12YWx1ZQ==",
},
},
},
&conciergev1alpha1.WebhookAuthenticator{ObjectMeta: metav1.ObjectMeta{Name: "test-authenticator"}}, &conciergev1alpha1.WebhookAuthenticator{ObjectMeta: metav1.ObjectMeta{Name: "test-authenticator"}},
}, },
wantStdout: here.Doc(` wantStdout: here.Doc(`
@ -362,6 +489,15 @@ func TestGetKubeconfig(t *testing.T) {
"--static-token-env", "TEST_TOKEN", "--static-token-env", "TEST_TOKEN",
}, },
conciergeObjects: []runtime.Object{ conciergeObjects: []runtime.Object{
&configv1alpha1.CredentialIssuer{
ObjectMeta: metav1.ObjectMeta{Name: "test-credential-issuer"},
Status: configv1alpha1.CredentialIssuerStatus{
KubeConfigInfo: &configv1alpha1.CredentialIssuerKubeConfigInfo{
Server: "https://concierge-endpoint",
CertificateAuthorityData: "ZmFrZS1jZXJ0aWZpY2F0ZS1hdXRob3JpdHktZGF0YS12YWx1ZQ==",
},
},
},
&conciergev1alpha1.WebhookAuthenticator{ObjectMeta: metav1.ObjectMeta{Name: "test-authenticator"}}, &conciergev1alpha1.WebhookAuthenticator{ObjectMeta: metav1.ObjectMeta{Name: "test-authenticator"}},
}, },
wantStdout: here.Doc(` wantStdout: here.Doc(`
@ -406,6 +542,15 @@ func TestGetKubeconfig(t *testing.T) {
"--kubeconfig", "./testdata/kubeconfig.yaml", "--kubeconfig", "./testdata/kubeconfig.yaml",
}, },
conciergeObjects: []runtime.Object{ conciergeObjects: []runtime.Object{
&configv1alpha1.CredentialIssuer{
ObjectMeta: metav1.ObjectMeta{Name: "test-credential-issuer"},
Status: configv1alpha1.CredentialIssuerStatus{
KubeConfigInfo: &configv1alpha1.CredentialIssuerKubeConfigInfo{
Server: "https://concierge-endpoint",
CertificateAuthorityData: "ZmFrZS1jZXJ0aWZpY2F0ZS1hdXRob3JpdHktZGF0YS12YWx1ZQ==",
},
},
},
&conciergev1alpha1.JWTAuthenticator{ &conciergev1alpha1.JWTAuthenticator{
ObjectMeta: metav1.ObjectMeta{Name: "test-authenticator"}, ObjectMeta: metav1.ObjectMeta{Name: "test-authenticator"},
Spec: conciergev1alpha1.JWTAuthenticatorSpec{ Spec: conciergev1alpha1.JWTAuthenticatorSpec{
@ -473,6 +618,15 @@ func TestGetKubeconfig(t *testing.T) {
"--oidc-request-audience", "test-audience", "--oidc-request-audience", "test-audience",
}, },
conciergeObjects: []runtime.Object{ conciergeObjects: []runtime.Object{
&configv1alpha1.CredentialIssuer{
ObjectMeta: metav1.ObjectMeta{Name: "test-credential-issuer"},
Status: configv1alpha1.CredentialIssuerStatus{
KubeConfigInfo: &configv1alpha1.CredentialIssuerKubeConfigInfo{
Server: "https://concierge-endpoint",
CertificateAuthorityData: "ZmFrZS1jZXJ0aWZpY2F0ZS1hdXRob3JpdHktZGF0YS12YWx1ZQ==",
},
},
},
&conciergev1alpha1.WebhookAuthenticator{ &conciergev1alpha1.WebhookAuthenticator{
ObjectMeta: metav1.ObjectMeta{Name: "test-authenticator"}, ObjectMeta: metav1.ObjectMeta{Name: "test-authenticator"},
}, },
@ -531,6 +685,76 @@ func TestGetKubeconfig(t *testing.T) {
"--concierge-mode", "ImpersonationProxy", "--concierge-mode", "ImpersonationProxy",
}, },
conciergeObjects: []runtime.Object{ conciergeObjects: []runtime.Object{
&configv1alpha1.CredentialIssuer{
ObjectMeta: metav1.ObjectMeta{Name: "test-credential-issuer"},
Status: configv1alpha1.CredentialIssuerStatus{},
},
&conciergev1alpha1.JWTAuthenticator{
ObjectMeta: metav1.ObjectMeta{Name: "test-authenticator"},
Spec: conciergev1alpha1.JWTAuthenticatorSpec{
Issuer: "https://example.com/issuer",
Audience: "test-audience",
TLS: &conciergev1alpha1.TLSSpec{
CertificateAuthorityData: base64.StdEncoding.EncodeToString(testOIDCCA.Bundle()),
},
},
},
},
wantStdout: here.Docf(`
apiVersion: v1
clusters:
- cluster:
certificate-authority-data: dGVzdC1jb25jaWVyZ2UtY2E=
server: https://impersonation-proxy-endpoint.test
name: pinniped
contexts:
- context:
cluster: pinniped
user: pinniped
name: pinniped
current-context: pinniped
kind: Config
preferences: {}
users:
- name: 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=
- --concierge-mode=ImpersonationProxy
- --issuer=https://example.com/issuer
- --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(testOIDCCA.Bundle())),
},
{
name: "autodetect impersonation proxy with autodetected JWT authenticator",
args: []string{
"--kubeconfig", "./testdata/kubeconfig.yaml",
},
conciergeObjects: []runtime.Object{
&configv1alpha1.CredentialIssuer{
ObjectMeta: metav1.ObjectMeta{Name: "test-credential-issuer"},
Status: configv1alpha1.CredentialIssuerStatus{
ImpersonationProxyInfo: &configv1alpha1.CredentialIssuerImpersonationProxyInfo{
Endpoint: "https://impersonation-proxy-endpoint.test",
CertificateAuthorityData: "dGVzdC1jb25jaWVyZ2UtY2E=",
},
},
},
&conciergev1alpha1.JWTAuthenticator{ &conciergev1alpha1.JWTAuthenticator{
ObjectMeta: metav1.ObjectMeta{Name: "test-authenticator"}, ObjectMeta: metav1.ObjectMeta{Name: "test-authenticator"},
Spec: conciergev1alpha1.JWTAuthenticatorSpec{ Spec: conciergev1alpha1.JWTAuthenticatorSpec{
@ -604,7 +828,7 @@ func TestGetKubeconfig(t *testing.T) {
} }
fake := fakeconciergeclientset.NewSimpleClientset(tt.conciergeObjects...) fake := fakeconciergeclientset.NewSimpleClientset(tt.conciergeObjects...)
if len(tt.conciergeReactions) > 0 { if len(tt.conciergeReactions) > 0 {
fake.ReactionChain = tt.conciergeReactions fake.ReactionChain = append(tt.conciergeReactions, fake.ReactionChain...)
} }
return fake, nil return fake, nil
}, },

View File

@ -190,7 +190,7 @@ func runOIDCLogin(cmd *cobra.Command, deps oidcLoginCommandDeps, flags oidcLogin
// The exact behavior depends on in which mode the Concierge is operating. // The exact behavior depends on in which mode the Concierge is operating.
switch flags.conciergeMode { switch flags.conciergeMode {
case modeTokenCredentialRequestAPI: case modeUnknown, modeTokenCredentialRequestAPI:
// do a credential exchange request // do a credential exchange request
cred, err := deps.exchangeToken(ctx, concierge, token.IDToken.Token) cred, err := deps.exchangeToken(ctx, concierge, token.IDToken.Token)
if err != nil { if err != nil {

View File

@ -122,7 +122,7 @@ func runStaticLogin(out io.Writer, deps staticLoginDeps, flags staticLoginParams
// If the concierge is enabled, we need to do extra steps. // If the concierge is enabled, we need to do extra steps.
switch flags.conciergeMode { switch flags.conciergeMode {
case modeTokenCredentialRequestAPI: case modeUnknown, modeTokenCredentialRequestAPI:
// do a credential exchange request // do a credential exchange request
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel() defer cancel()