Rework "pinniped get kubeconfig" so that --concierge-mode can be used even when auto-discovering other parameters.

Signed-off-by: Matt Moyer <moyerm@vmware.com>
This commit is contained in:
Matt Moyer 2021-03-08 11:43:56 -06:00
parent 49ec16038c
commit 389cd3486b
No known key found for this signature in database
GPG Key ID: EAE88AD172C5AE2D
4 changed files with 306 additions and 126 deletions

View File

@ -7,6 +7,8 @@ import (
"flag" "flag"
"fmt" "fmt"
"strings" "strings"
configv1alpha1 "go.pinniped.dev/generated/latest/apis/concierge/config/v1alpha1"
) )
// conciergeMode represents the method by which we should connect to the Concierge on a cluster during login. // conciergeMode represents the method by which we should connect to the Concierge on a cluster during login.
@ -25,7 +27,7 @@ func (c *conciergeMode) String() string {
switch *c { switch *c {
case modeImpersonationProxy: case modeImpersonationProxy:
return "ImpersonationProxy" return "ImpersonationProxy"
case modeTokenCredentialRequestAPI, modeUnknown: case modeTokenCredentialRequestAPI:
return "TokenCredentialRequestAPI" return "TokenCredentialRequestAPI"
default: default:
return "TokenCredentialRequestAPI" return "TokenCredentialRequestAPI"
@ -51,3 +53,15 @@ func (c *conciergeMode) Set(s string) error {
func (c *conciergeMode) Type() string { func (c *conciergeMode) Type() string {
return "mode" return "mode"
} }
// MatchesFrontend returns true iff the flag matches the type of the provided frontend.
func (c *conciergeMode) MatchesFrontend(frontend *configv1alpha1.CredentialIssuerFrontend) bool {
switch *c {
case modeImpersonationProxy:
return frontend.Type == configv1alpha1.ImpersonationProxyFrontendType
case modeTokenCredentialRequestAPI:
return frontend.Type == configv1alpha1.TokenCredentialRequestAPIFrontendType
default:
return true
}
}

View File

@ -7,17 +7,25 @@ import (
"testing" "testing"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
configv1alpha1 "go.pinniped.dev/generated/latest/apis/concierge/config/v1alpha1"
) )
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, modeUnknown, m) require.Equal(t, modeUnknown, m)
require.NoError(t, m.Set(""))
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.True(t, m.MatchesFrontend(&configv1alpha1.CredentialIssuerFrontend{Type: configv1alpha1.TokenCredentialRequestAPIFrontendType}))
require.True(t, m.MatchesFrontend(&configv1alpha1.CredentialIssuerFrontend{Type: configv1alpha1.ImpersonationProxyFrontendType}))
require.NoError(t, m.Set("TokenCredentialRequestAPI")) require.NoError(t, m.Set("TokenCredentialRequestAPI"))
require.Equal(t, modeTokenCredentialRequestAPI, m) require.Equal(t, modeTokenCredentialRequestAPI, m)
require.Equal(t, "TokenCredentialRequestAPI", m.String()) require.Equal(t, "TokenCredentialRequestAPI", m.String())
require.True(t, m.MatchesFrontend(&configv1alpha1.CredentialIssuerFrontend{Type: configv1alpha1.TokenCredentialRequestAPIFrontendType}))
require.False(t, m.MatchesFrontend(&configv1alpha1.CredentialIssuerFrontend{Type: configv1alpha1.ImpersonationProxyFrontendType}))
require.NoError(t, m.Set("tokencredentialrequestapi")) require.NoError(t, m.Set("tokencredentialrequestapi"))
require.Equal(t, modeTokenCredentialRequestAPI, m) require.Equal(t, modeTokenCredentialRequestAPI, m)
@ -26,6 +34,8 @@ func TestConciergeModeFlag(t *testing.T) {
require.NoError(t, m.Set("ImpersonationProxy")) require.NoError(t, m.Set("ImpersonationProxy"))
require.Equal(t, modeImpersonationProxy, m) require.Equal(t, modeImpersonationProxy, m)
require.Equal(t, "ImpersonationProxy", m.String()) require.Equal(t, "ImpersonationProxy", m.String())
require.False(t, m.MatchesFrontend(&configv1alpha1.CredentialIssuerFrontend{Type: configv1alpha1.TokenCredentialRequestAPIFrontendType}))
require.True(t, m.MatchesFrontend(&configv1alpha1.CredentialIssuerFrontend{Type: configv1alpha1.ImpersonationProxyFrontendType}))
require.NoError(t, m.Set("impersonationproxy")) require.NoError(t, m.Set("impersonationproxy"))
require.Equal(t, modeImpersonationProxy, m) require.Equal(t, modeImpersonationProxy, m)

View File

@ -283,43 +283,56 @@ func runGetKubeconfig(ctx context.Context, out io.Writer, deps kubeconfigDeps, f
} }
func configureConcierge(credentialIssuer *configv1alpha1.CredentialIssuer, authenticator metav1.Object, flags *getKubeconfigParams, v1Cluster *clientcmdapi.Cluster, oidcCABundle *string, execConfig *clientcmdapi.ExecConfig, log logr.Logger) error { func configureConcierge(credentialIssuer *configv1alpha1.CredentialIssuer, authenticator metav1.Object, flags *getKubeconfigParams, v1Cluster *clientcmdapi.Cluster, oidcCABundle *string, execConfig *clientcmdapi.ExecConfig, log logr.Logger) error {
var conciergeCABundleData []byte
// Autodiscover the --concierge-mode. // Autodiscover the --concierge-mode.
if flags.concierge.mode == modeUnknown { //nolint:nestif frontend, err := getConciergeFrontend(credentialIssuer, flags.concierge.mode)
strategyLoop: if err != nil {
for _, strategy := range credentialIssuer.Status.Strategies { return err
if strategy.Status != configv1alpha1.SuccessStrategyStatus || strategy.Frontend == nil { }
continue
}
switch strategy.Frontend.Type {
case configv1alpha1.TokenCredentialRequestAPIFrontendType:
log.Info("detected Concierge in TokenCredentialRequest API mode")
flags.concierge.mode = modeTokenCredentialRequestAPI
break strategyLoop
case configv1alpha1.ImpersonationProxyFrontendType:
flags.concierge.mode = modeImpersonationProxy // Auto-set --concierge-mode if it wasn't explicitly set.
flags.concierge.endpoint = strategy.Frontend.ImpersonationProxyInfo.Endpoint if flags.concierge.mode == modeUnknown {
var err error switch frontend.Type {
conciergeCABundleData, err = base64.StdEncoding.DecodeString(strategy.Frontend.ImpersonationProxyInfo.CertificateAuthorityData) case configv1alpha1.TokenCredentialRequestAPIFrontendType:
if err != nil { log.Info("discovered Concierge operating in TokenCredentialRequest API mode")
return fmt.Errorf("autodiscovered Concierge CA bundle is invalid: %w", err) flags.concierge.mode = modeTokenCredentialRequestAPI
} case configv1alpha1.ImpersonationProxyFrontendType:
log.Info("detected Concierge in impersonation proxy mode", "endpoint", strategy.Frontend.ImpersonationProxyInfo.Endpoint) log.Info("discovered Concierge operating in impersonation proxy mode")
break strategyLoop flags.concierge.mode = modeImpersonationProxy
default: }
// Skip any unknown frontend types. }
}
} // Auto-set --concierge-endpoint if it wasn't explicitly set.
if flags.concierge.mode == modeUnknown { if flags.concierge.endpoint == "" {
// Fall back to deprecated field for backwards compatibility. switch frontend.Type {
if credentialIssuer.Status.KubeConfigInfo != nil { case configv1alpha1.TokenCredentialRequestAPIFrontendType:
flags.concierge.mode = modeTokenCredentialRequestAPI flags.concierge.endpoint = v1Cluster.Server
} else { case configv1alpha1.ImpersonationProxyFrontendType:
return fmt.Errorf("could not autodiscover --concierge-mode and none was provided") flags.concierge.endpoint = frontend.ImpersonationProxyInfo.Endpoint
}
log.Info("discovered Concierge endpoint", "endpoint", flags.concierge.endpoint)
}
// Load specified --concierge-ca-bundle or autodiscover a value.
var conciergeCABundleData []byte
if flags.concierge.caBundlePath != "" {
caBundleString, err := loadCABundlePaths([]string{flags.concierge.caBundlePath})
if err != nil {
return fmt.Errorf("could not read --concierge-ca-bundle: %w", err)
}
conciergeCABundleData = []byte(caBundleString)
log.Info("loaded Concierge certificate authority bundle", "roots", countCACerts(conciergeCABundleData))
} else {
switch frontend.Type {
case configv1alpha1.TokenCredentialRequestAPIFrontendType:
conciergeCABundleData = v1Cluster.CertificateAuthorityData
case configv1alpha1.ImpersonationProxyFrontendType:
var err error
conciergeCABundleData, err = base64.StdEncoding.DecodeString(frontend.ImpersonationProxyInfo.CertificateAuthorityData)
if err != nil {
return fmt.Errorf("autodiscovered Concierge CA bundle is invalid: %w", err)
} }
} }
log.Info("discovered Concierge certificate authority bundle", "roots", countCACerts(conciergeCABundleData))
} }
switch auth := authenticator.(type) { switch auth := authenticator.(type) {
@ -342,13 +355,13 @@ func configureConcierge(credentialIssuer *configv1alpha1.CredentialIssuer, authe
// If the --oidc-issuer flag was not set explicitly, default it to the spec.issuer field of the JWTAuthenticator. // If the --oidc-issuer flag was not set explicitly, default it to the spec.issuer field of the JWTAuthenticator.
if flags.oidc.issuer == "" { if flags.oidc.issuer == "" {
log.Info("detected OIDC issuer", "issuer", auth.Spec.Issuer) log.Info("discovered OIDC issuer", "issuer", auth.Spec.Issuer)
flags.oidc.issuer = auth.Spec.Issuer flags.oidc.issuer = auth.Spec.Issuer
} }
// If the --oidc-request-audience flag was not set explicitly, default it to the spec.audience field of the JWTAuthenticator. // If the --oidc-request-audience flag was not set explicitly, default it to the spec.audience field of the JWTAuthenticator.
if flags.oidc.requestAudience == "" { if flags.oidc.requestAudience == "" {
log.Info("detected OIDC audience", "audience", auth.Spec.Audience) log.Info("discovered OIDC audience", "audience", auth.Spec.Audience)
flags.oidc.requestAudience = auth.Spec.Audience flags.oidc.requestAudience = auth.Spec.Audience
} }
@ -359,29 +372,11 @@ func configureConcierge(credentialIssuer *configv1alpha1.CredentialIssuer, authe
if err != nil { if err != nil {
return fmt.Errorf("tried to autodiscover --oidc-ca-bundle, but JWTAuthenticator %s has invalid spec.tls.certificateAuthorityData: %w", auth.Name, err) return fmt.Errorf("tried to autodiscover --oidc-ca-bundle, but JWTAuthenticator %s has invalid spec.tls.certificateAuthorityData: %w", auth.Name, err)
} }
log.Info("detected OIDC CA bundle", "length", len(decoded)) log.Info("discovered OIDC CA bundle", "roots", countCACerts(decoded))
*oidcCABundle = string(decoded) *oidcCABundle = string(decoded)
} }
} }
if flags.concierge.endpoint == "" {
log.Info("detected concierge endpoint", "endpoint", v1Cluster.Server)
flags.concierge.endpoint = v1Cluster.Server
}
if conciergeCABundleData == nil {
if flags.concierge.caBundlePath == "" {
log.Info("detected concierge CA bundle", "length", len(v1Cluster.CertificateAuthorityData))
conciergeCABundleData = v1Cluster.CertificateAuthorityData
} else {
caBundleString, err := loadCABundlePaths([]string{flags.concierge.caBundlePath})
if err != nil {
return fmt.Errorf("could not read --concierge-ca-bundle: %w", err)
}
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.
execConfig.Args = append(execConfig.Args, execConfig.Args = append(execConfig.Args,
"--enable-concierge", "--enable-concierge",
@ -393,14 +388,53 @@ func configureConcierge(credentialIssuer *configv1alpha1.CredentialIssuer, authe
"--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 // Point kubectl at the concierge endpoint.
if flags.concierge.mode == modeImpersonationProxy { v1Cluster.Server = flags.concierge.endpoint
log.Info("switching kubeconfig cluster to point at impersonation proxy endpoint", "endpoint", flags.concierge.endpoint) v1Cluster.CertificateAuthorityData = conciergeCABundleData
v1Cluster.CertificateAuthorityData = conciergeCABundleData return nil
v1Cluster.Server = flags.concierge.endpoint }
func getConciergeFrontend(credentialIssuer *configv1alpha1.CredentialIssuer, mode conciergeMode) (*configv1alpha1.CredentialIssuerFrontend, error) {
for _, strategy := range credentialIssuer.Status.Strategies {
// Skip unhealthy strategies.
if strategy.Status != configv1alpha1.SuccessStrategyStatus {
continue
}
// Backfill the .status.strategies[].frontend field from .status.kubeConfigInfo for backwards compatibility.
if strategy.Type == configv1alpha1.KubeClusterSigningCertificateStrategyType && strategy.Frontend == nil && credentialIssuer.Status.KubeConfigInfo != nil {
strategy = *strategy.DeepCopy()
strategy.Frontend = &configv1alpha1.CredentialIssuerFrontend{
Type: configv1alpha1.TokenCredentialRequestAPIFrontendType,
TokenCredentialRequestAPIInfo: &configv1alpha1.TokenCredentialRequestAPIInfo{
Server: credentialIssuer.Status.KubeConfigInfo.Server,
CertificateAuthorityData: credentialIssuer.Status.KubeConfigInfo.CertificateAuthorityData,
},
}
}
// If the strategy frontend is still nil, skip.
if strategy.Frontend == nil {
continue
}
// Skip any unknown frontend types.
switch strategy.Frontend.Type {
case configv1alpha1.TokenCredentialRequestAPIFrontendType, configv1alpha1.ImpersonationProxyFrontendType:
default:
continue
}
// Skip strategies that don't match --concierge-mode.
if !mode.MatchesFrontend(strategy.Frontend) {
continue
}
return strategy.Frontend, nil
} }
return nil if mode == modeUnknown {
return nil, fmt.Errorf("could not autodiscover --concierge-mode")
}
return nil, fmt.Errorf("could not find successful Concierge strategy matching --concierge-mode=%s", mode.String())
} }
func loadCABundlePaths(paths []string) (string, error) { func loadCABundlePaths(paths []string) (string, error) {
@ -614,3 +648,9 @@ func validateKubeconfig(ctx context.Context, flags getKubeconfigParams, kubeconf
} }
} }
} }
func countCACerts(pemData []byte) int {
pool := x509.NewCertPool()
pool.AppendCertsFromPEM(pemData)
return len(pool.Subjects())
}

View File

@ -36,8 +36,10 @@ func TestGetKubeconfig(t *testing.T) {
testOIDCCABundlePath := filepath.Join(tmpdir, "testca.pem") testOIDCCABundlePath := filepath.Join(tmpdir, "testca.pem")
require.NoError(t, ioutil.WriteFile(testOIDCCABundlePath, testOIDCCA.Bundle(), 0600)) require.NoError(t, ioutil.WriteFile(testOIDCCABundlePath, testOIDCCA.Bundle(), 0600))
testConciergeCA, err := certauthority.New(pkix.Name{CommonName: "Test Concierge CA"}, 1*time.Hour)
require.NoError(t, err)
testConciergeCABundlePath := filepath.Join(tmpdir, "testconciergeca.pem") testConciergeCABundlePath := filepath.Join(tmpdir, "testconciergeca.pem")
require.NoError(t, ioutil.WriteFile(testConciergeCABundlePath, []byte("test-concierge-ca"), 0600)) require.NoError(t, ioutil.WriteFile(testConciergeCABundlePath, testConciergeCA.Bundle(), 0600))
tests := []struct { tests := []struct {
name string name string
@ -324,7 +326,7 @@ func TestGetKubeconfig(t *testing.T) {
}, },
wantError: true, wantError: true,
wantStderr: here.Doc(` wantStderr: here.Doc(`
Error: could not autodiscover --concierge-mode and none was provided Error: could not autodiscover --concierge-mode
`), `),
}, },
{ {
@ -375,6 +377,8 @@ func TestGetKubeconfig(t *testing.T) {
}, },
wantLogs: []string{ wantLogs: []string{
`"level"=0 "msg"="discovered CredentialIssuer" "name"="test-credential-issuer"`, `"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, wantError: true,
wantStderr: here.Doc(` wantStderr: here.Doc(`
@ -410,10 +414,10 @@ func TestGetKubeconfig(t *testing.T) {
}, },
wantLogs: []string{ wantLogs: []string{
`"level"=0 "msg"="discovered CredentialIssuer" "name"="test-credential-issuer"`, `"level"=0 "msg"="discovered CredentialIssuer" "name"="test-credential-issuer"`,
`"level"=0 "msg"="detected Concierge in TokenCredentialRequest API mode"`, `"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"`, `"level"=0 "msg"="discovered WebhookAuthenticator" "name"="test-authenticator"`,
`"level"=0 "msg"="detected concierge endpoint" "endpoint"="https://fake-server-url-value"`,
`"level"=0 "msg"="detected concierge CA bundle" "length"=37`,
}, },
wantError: true, wantError: true,
wantStderr: here.Doc(` wantStderr: here.Doc(`
@ -433,11 +437,22 @@ func TestGetKubeconfig(t *testing.T) {
Server: "https://concierge-endpoint", Server: "https://concierge-endpoint",
CertificateAuthorityData: "ZmFrZS1jZXJ0aWZpY2F0ZS1hdXRob3JpdHktZGF0YS12YWx1ZQ==", 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{ &conciergev1alpha1.JWTAuthenticator{
ObjectMeta: metav1.ObjectMeta{Name: "test-authenticator"}, ObjectMeta: metav1.ObjectMeta{Name: "test-authenticator"},
Spec: conciergev1alpha1.JWTAuthenticatorSpec{ Spec: conciergev1alpha1.JWTAuthenticatorSpec{
Issuer: "https://test-issuer.example.com",
Audience: "some-test-audience",
TLS: &conciergev1alpha1.TLSSpec{ TLS: &conciergev1alpha1.TLSSpec{
CertificateAuthorityData: "invalid-base64", CertificateAuthorityData: "invalid-base64",
}, },
@ -446,9 +461,12 @@ func TestGetKubeconfig(t *testing.T) {
}, },
wantLogs: []string{ wantLogs: []string{
`"level"=0 "msg"="discovered CredentialIssuer" "name"="test-credential-issuer"`, `"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 JWTAuthenticator" "name"="test-authenticator"`,
`"level"=0 "msg"="detected OIDC issuer" "issuer"=""`, `"level"=0 "msg"="discovered OIDC issuer" "issuer"="https://test-issuer.example.com"`,
`"level"=0 "msg"="detected OIDC audience" "audience"=""`, `"level"=0 "msg"="discovered OIDC audience" "audience"="some-test-audience"`,
}, },
wantError: true, wantError: true,
wantStderr: here.Doc(` wantStderr: here.Doc(`
@ -469,10 +487,18 @@ func TestGetKubeconfig(t *testing.T) {
&configv1alpha1.CredentialIssuer{ &configv1alpha1.CredentialIssuer{
ObjectMeta: metav1.ObjectMeta{Name: "test-credential-issuer"}, ObjectMeta: metav1.ObjectMeta{Name: "test-credential-issuer"},
Status: configv1alpha1.CredentialIssuerStatus{ Status: configv1alpha1.CredentialIssuerStatus{
KubeConfigInfo: &configv1alpha1.CredentialIssuerKubeConfigInfo{ Strategies: []configv1alpha1.CredentialIssuerStrategy{{
Server: "https://concierge-endpoint", Type: configv1alpha1.ImpersonationProxyStrategyType,
CertificateAuthorityData: "ZmFrZS1jZXJ0aWZpY2F0ZS1hdXRob3JpdHktZGF0YS12YWx1ZQ==", Status: configv1alpha1.SuccessStrategyStatus,
}, Reason: configv1alpha1.ListeningStrategyReason,
Frontend: &configv1alpha1.CredentialIssuerFrontend{
Type: configv1alpha1.ImpersonationProxyFrontendType,
ImpersonationProxyInfo: &configv1alpha1.ImpersonationProxyInfo{
Endpoint: "https://impersonation-proxy-endpoint.example.com",
CertificateAuthorityData: base64.StdEncoding.EncodeToString(testConciergeCA.Bundle()),
},
},
}},
}, },
}, },
&conciergev1alpha1.WebhookAuthenticator{ObjectMeta: metav1.ObjectMeta{Name: "test-authenticator"}}, &conciergev1alpha1.WebhookAuthenticator{ObjectMeta: metav1.ObjectMeta{Name: "test-authenticator"}},
@ -496,19 +522,28 @@ func TestGetKubeconfig(t *testing.T) {
&configv1alpha1.CredentialIssuer{ &configv1alpha1.CredentialIssuer{
ObjectMeta: metav1.ObjectMeta{Name: "test-credential-issuer"}, ObjectMeta: metav1.ObjectMeta{Name: "test-credential-issuer"},
Status: configv1alpha1.CredentialIssuerStatus{ Status: configv1alpha1.CredentialIssuerStatus{
KubeConfigInfo: &configv1alpha1.CredentialIssuerKubeConfigInfo{ Strategies: []configv1alpha1.CredentialIssuerStrategy{{
Server: "https://concierge-endpoint", Type: configv1alpha1.ImpersonationProxyStrategyType,
CertificateAuthorityData: "ZmFrZS1jZXJ0aWZpY2F0ZS1hdXRob3JpdHktZGF0YS12YWx1ZQ==", Status: configv1alpha1.SuccessStrategyStatus,
}, Reason: configv1alpha1.ListeningStrategyReason,
Frontend: &configv1alpha1.CredentialIssuerFrontend{
Type: configv1alpha1.ImpersonationProxyFrontendType,
ImpersonationProxyInfo: &configv1alpha1.ImpersonationProxyInfo{
Endpoint: "https://impersonation-proxy-endpoint.example.com",
CertificateAuthorityData: base64.StdEncoding.EncodeToString(testConciergeCA.Bundle()),
},
},
}},
}, },
}, },
&conciergev1alpha1.WebhookAuthenticator{ObjectMeta: metav1.ObjectMeta{Name: "test-authenticator"}}, &conciergev1alpha1.WebhookAuthenticator{ObjectMeta: metav1.ObjectMeta{Name: "test-authenticator"}},
}, },
wantLogs: []string{ wantLogs: []string{
`"level"=0 "msg"="discovered CredentialIssuer" "name"="test-credential-issuer"`, `"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.example.com"`,
`"level"=0 "msg"="discovered Concierge certificate authority bundle" "roots"=1`,
`"level"=0 "msg"="discovered WebhookAuthenticator" "name"="test-authenticator"`, `"level"=0 "msg"="discovered WebhookAuthenticator" "name"="test-authenticator"`,
`"level"=0 "msg"="detected concierge endpoint" "endpoint"="https://fake-server-url-value"`,
`"level"=0 "msg"="detected concierge CA bundle" "length"=37`,
}, },
wantError: true, wantError: true,
wantStderr: here.Doc(` wantStderr: here.Doc(`
@ -536,19 +571,28 @@ func TestGetKubeconfig(t *testing.T) {
&configv1alpha1.CredentialIssuer{ &configv1alpha1.CredentialIssuer{
ObjectMeta: metav1.ObjectMeta{Name: "test-credential-issuer"}, ObjectMeta: metav1.ObjectMeta{Name: "test-credential-issuer"},
Status: configv1alpha1.CredentialIssuerStatus{ Status: configv1alpha1.CredentialIssuerStatus{
KubeConfigInfo: &configv1alpha1.CredentialIssuerKubeConfigInfo{ Strategies: []configv1alpha1.CredentialIssuerStrategy{{
Server: "https://concierge-endpoint", Type: configv1alpha1.KubeClusterSigningCertificateStrategyType,
CertificateAuthorityData: "ZmFrZS1jZXJ0aWZpY2F0ZS1hdXRob3JpdHktZGF0YS12YWx1ZQ==", 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()),
},
},
}},
}, },
}, },
&conciergev1alpha1.WebhookAuthenticator{ObjectMeta: metav1.ObjectMeta{Name: "test-authenticator"}}, &conciergev1alpha1.WebhookAuthenticator{ObjectMeta: metav1.ObjectMeta{Name: "test-authenticator"}},
}, },
wantLogs: []string{ wantLogs: []string{
`"level"=0 "msg"="discovered CredentialIssuer" "name"="test-credential-issuer"`, `"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"`, `"level"=0 "msg"="discovered WebhookAuthenticator" "name"="test-authenticator"`,
`"level"=0 "msg"="detected concierge endpoint" "endpoint"="https://fake-server-url-value"`,
`"level"=0 "msg"="detected concierge CA bundle" "length"=37`,
}, },
wantStdout: here.Doc(` wantStdout: here.Doc(`
apiVersion: v1 apiVersion: v1
@ -597,19 +641,28 @@ func TestGetKubeconfig(t *testing.T) {
&configv1alpha1.CredentialIssuer{ &configv1alpha1.CredentialIssuer{
ObjectMeta: metav1.ObjectMeta{Name: "test-credential-issuer"}, ObjectMeta: metav1.ObjectMeta{Name: "test-credential-issuer"},
Status: configv1alpha1.CredentialIssuerStatus{ Status: configv1alpha1.CredentialIssuerStatus{
KubeConfigInfo: &configv1alpha1.CredentialIssuerKubeConfigInfo{ Strategies: []configv1alpha1.CredentialIssuerStrategy{{
Server: "https://concierge-endpoint", Type: configv1alpha1.KubeClusterSigningCertificateStrategyType,
CertificateAuthorityData: "ZmFrZS1jZXJ0aWZpY2F0ZS1hdXRob3JpdHktZGF0YS12YWx1ZQ==", 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()),
},
},
}},
}, },
}, },
&conciergev1alpha1.WebhookAuthenticator{ObjectMeta: metav1.ObjectMeta{Name: "test-authenticator"}}, &conciergev1alpha1.WebhookAuthenticator{ObjectMeta: metav1.ObjectMeta{Name: "test-authenticator"}},
}, },
wantLogs: []string{ wantLogs: []string{
`"level"=0 "msg"="discovered CredentialIssuer" "name"="test-credential-issuer"`, `"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"`, `"level"=0 "msg"="discovered WebhookAuthenticator" "name"="test-authenticator"`,
`"level"=0 "msg"="detected concierge endpoint" "endpoint"="https://fake-server-url-value"`,
`"level"=0 "msg"="detected concierge CA bundle" "length"=37`,
}, },
wantStdout: here.Doc(` wantStdout: here.Doc(`
apiVersion: v1 apiVersion: v1
@ -657,10 +710,18 @@ func TestGetKubeconfig(t *testing.T) {
&configv1alpha1.CredentialIssuer{ &configv1alpha1.CredentialIssuer{
ObjectMeta: metav1.ObjectMeta{Name: "test-credential-issuer"}, ObjectMeta: metav1.ObjectMeta{Name: "test-credential-issuer"},
Status: configv1alpha1.CredentialIssuerStatus{ Status: configv1alpha1.CredentialIssuerStatus{
KubeConfigInfo: &configv1alpha1.CredentialIssuerKubeConfigInfo{ Strategies: []configv1alpha1.CredentialIssuerStrategy{{
Server: "https://concierge-endpoint", Type: configv1alpha1.KubeClusterSigningCertificateStrategyType,
CertificateAuthorityData: "ZmFrZS1jZXJ0aWZpY2F0ZS1hdXRob3JpdHktZGF0YS12YWx1ZQ==", 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()),
},
},
}},
}, },
}, },
&conciergev1alpha1.JWTAuthenticator{ &conciergev1alpha1.JWTAuthenticator{
@ -676,12 +737,13 @@ func TestGetKubeconfig(t *testing.T) {
}, },
wantLogs: []string{ wantLogs: []string{
`"level"=0 "msg"="discovered CredentialIssuer" "name"="test-credential-issuer"`, `"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 JWTAuthenticator" "name"="test-authenticator"`,
`"level"=0 "msg"="detected OIDC issuer" "issuer"="https://example.com/issuer"`, `"level"=0 "msg"="discovered OIDC issuer" "issuer"="https://example.com/issuer"`,
`"level"=0 "msg"="detected OIDC audience" "audience"="test-audience"`, `"level"=0 "msg"="discovered OIDC audience" "audience"="test-audience"`,
`"level"=0 "msg"="detected OIDC CA bundle" "length"=587`, `"level"=0 "msg"="discovered OIDC CA bundle" "roots"=1`,
`"level"=0 "msg"="detected concierge endpoint" "endpoint"="https://fake-server-url-value"`,
`"level"=0 "msg"="detected concierge CA bundle" "length"=37`,
}, },
wantStdout: here.Docf(` wantStdout: here.Docf(`
apiVersion: v1 apiVersion: v1
@ -731,7 +793,8 @@ func TestGetKubeconfig(t *testing.T) {
"--concierge-api-group-suffix", "tuna.io", "--concierge-api-group-suffix", "tuna.io",
"--concierge-authenticator-type", "webhook", "--concierge-authenticator-type", "webhook",
"--concierge-authenticator-name", "test-authenticator", "--concierge-authenticator-name", "test-authenticator",
"--concierge-endpoint", "https://concierge-endpoint.example.com", "--concierge-mode", "TokenCredentialRequestAPI",
"--concierge-endpoint", "https://explicit-concierge-endpoint.example.com",
"--concierge-ca-bundle", testConciergeCABundlePath, "--concierge-ca-bundle", testConciergeCABundlePath,
"--oidc-issuer", "https://example.com/issuer", "--oidc-issuer", "https://example.com/issuer",
"--oidc-skip-browser", "--oidc-skip-browser",
@ -746,22 +809,33 @@ func TestGetKubeconfig(t *testing.T) {
&configv1alpha1.CredentialIssuer{ &configv1alpha1.CredentialIssuer{
ObjectMeta: metav1.ObjectMeta{Name: "test-credential-issuer"}, ObjectMeta: metav1.ObjectMeta{Name: "test-credential-issuer"},
Status: configv1alpha1.CredentialIssuerStatus{ Status: configv1alpha1.CredentialIssuerStatus{
KubeConfigInfo: &configv1alpha1.CredentialIssuerKubeConfigInfo{ Strategies: []configv1alpha1.CredentialIssuerStrategy{{
Server: "https://concierge-endpoint", Type: configv1alpha1.KubeClusterSigningCertificateStrategyType,
CertificateAuthorityData: "ZmFrZS1jZXJ0aWZpY2F0ZS1hdXRob3JpdHktZGF0YS12YWx1ZQ==", Status: configv1alpha1.SuccessStrategyStatus,
}, Reason: configv1alpha1.FetchedKeyStrategyReason,
Frontend: &configv1alpha1.CredentialIssuerFrontend{
Type: configv1alpha1.TokenCredentialRequestAPIFrontendType,
TokenCredentialRequestAPIInfo: &configv1alpha1.TokenCredentialRequestAPIInfo{
Server: "https://concierge-endpoint.example.com",
CertificateAuthorityData: "dGVzdC10Y3ItYXBpLWNh",
},
},
}},
}, },
}, },
&conciergev1alpha1.WebhookAuthenticator{ &conciergev1alpha1.WebhookAuthenticator{
ObjectMeta: metav1.ObjectMeta{Name: "test-authenticator"}, ObjectMeta: metav1.ObjectMeta{Name: "test-authenticator"},
}, },
}, },
wantLogs: []string{
`"level"=0 "msg"="loaded Concierge certificate authority bundle" "roots"=1`,
},
wantStdout: here.Docf(` wantStdout: here.Docf(`
apiVersion: v1 apiVersion: v1
clusters: clusters:
- cluster: - cluster:
certificate-authority-data: ZmFrZS1jZXJ0aWZpY2F0ZS1hdXRob3JpdHktZGF0YS12YWx1ZQ== certificate-authority-data: %s
server: https://fake-server-url-value server: https://explicit-concierge-endpoint.example.com
name: pinniped name: pinniped
contexts: contexts:
- context: - context:
@ -783,8 +857,8 @@ func TestGetKubeconfig(t *testing.T) {
- --concierge-api-group-suffix=tuna.io - --concierge-api-group-suffix=tuna.io
- --concierge-authenticator-name=test-authenticator - --concierge-authenticator-name=test-authenticator
- --concierge-authenticator-type=webhook - --concierge-authenticator-type=webhook
- --concierge-endpoint=https://concierge-endpoint.example.com - --concierge-endpoint=https://explicit-concierge-endpoint.example.com
- --concierge-ca-bundle-data=dGVzdC1jb25jaWVyZ2UtY2E= - --concierge-ca-bundle-data=%s
- --concierge-mode=TokenCredentialRequestAPI - --concierge-mode=TokenCredentialRequestAPI
- --issuer=https://example.com/issuer - --issuer=https://example.com/issuer
- --client-id=pinniped-cli - --client-id=pinniped-cli
@ -798,22 +872,58 @@ func TestGetKubeconfig(t *testing.T) {
command: '.../path/to/pinniped' command: '.../path/to/pinniped'
env: [] env: []
provideClusterInfo: true provideClusterInfo: true
`, base64.StdEncoding.EncodeToString(testOIDCCA.Bundle())), `,
base64.StdEncoding.EncodeToString(testConciergeCA.Bundle()),
base64.StdEncoding.EncodeToString(testConciergeCA.Bundle()),
base64.StdEncoding.EncodeToString(testOIDCCA.Bundle()),
),
wantAPIGroupSuffix: "tuna.io", wantAPIGroupSuffix: "tuna.io",
}, },
{ {
name: "configure impersonation proxy with autodetected JWT authenticator", name: "configure impersonation proxy with autodiscovered JWT authenticator",
args: []string{ args: []string{
"--kubeconfig", "./testdata/kubeconfig.yaml", "--kubeconfig", "./testdata/kubeconfig.yaml",
"--concierge-ca-bundle", testConciergeCABundlePath,
"--concierge-endpoint", "https://impersonation-proxy-endpoint.test",
"--concierge-mode", "ImpersonationProxy", "--concierge-mode", "ImpersonationProxy",
"--skip-validation", "--skip-validation",
}, },
conciergeObjects: []runtime.Object{ conciergeObjects: []runtime.Object{
&configv1alpha1.CredentialIssuer{ &configv1alpha1.CredentialIssuer{
ObjectMeta: metav1.ObjectMeta{Name: "test-credential-issuer"}, ObjectMeta: metav1.ObjectMeta{Name: "test-credential-issuer"},
Status: configv1alpha1.CredentialIssuerStatus{}, 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()),
},
},
},
},
},
}, },
&conciergev1alpha1.JWTAuthenticator{ &conciergev1alpha1.JWTAuthenticator{
ObjectMeta: metav1.ObjectMeta{Name: "test-authenticator"}, ObjectMeta: metav1.ObjectMeta{Name: "test-authenticator"},
@ -828,17 +938,18 @@ func TestGetKubeconfig(t *testing.T) {
}, },
wantLogs: []string{ wantLogs: []string{
`"level"=0 "msg"="discovered CredentialIssuer" "name"="test-credential-issuer"`, `"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"`, `"level"=0 "msg"="discovered JWTAuthenticator" "name"="test-authenticator"`,
`"level"=0 "msg"="detected OIDC issuer" "issuer"="https://example.com/issuer"`, `"level"=0 "msg"="discovered OIDC issuer" "issuer"="https://example.com/issuer"`,
`"level"=0 "msg"="detected OIDC audience" "audience"="test-audience"`, `"level"=0 "msg"="discovered OIDC audience" "audience"="test-audience"`,
`"level"=0 "msg"="detected OIDC CA bundle" "length"=587`, `"level"=0 "msg"="discovered OIDC CA bundle" "roots"=1`,
`"level"=0 "msg"="switching kubeconfig cluster to point at impersonation proxy endpoint" "endpoint"="https://impersonation-proxy-endpoint.test"`,
}, },
wantStdout: here.Docf(` wantStdout: here.Docf(`
apiVersion: v1 apiVersion: v1
clusters: clusters:
- cluster: - cluster:
certificate-authority-data: dGVzdC1jb25jaWVyZ2UtY2E= certificate-authority-data: %s
server: https://impersonation-proxy-endpoint.test server: https://impersonation-proxy-endpoint.test
name: pinniped name: pinniped
contexts: contexts:
@ -862,7 +973,7 @@ func TestGetKubeconfig(t *testing.T) {
- --concierge-authenticator-name=test-authenticator - --concierge-authenticator-name=test-authenticator
- --concierge-authenticator-type=jwt - --concierge-authenticator-type=jwt
- --concierge-endpoint=https://impersonation-proxy-endpoint.test - --concierge-endpoint=https://impersonation-proxy-endpoint.test
- --concierge-ca-bundle-data=dGVzdC1jb25jaWVyZ2UtY2E= - --concierge-ca-bundle-data=%s
- --concierge-mode=ImpersonationProxy - --concierge-mode=ImpersonationProxy
- --issuer=https://example.com/issuer - --issuer=https://example.com/issuer
- --client-id=pinniped-cli - --client-id=pinniped-cli
@ -872,10 +983,14 @@ func TestGetKubeconfig(t *testing.T) {
command: '.../path/to/pinniped' command: '.../path/to/pinniped'
env: [] env: []
provideClusterInfo: true provideClusterInfo: true
`, base64.StdEncoding.EncodeToString(testOIDCCA.Bundle())), `,
base64.StdEncoding.EncodeToString(testConciergeCA.Bundle()),
base64.StdEncoding.EncodeToString(testConciergeCA.Bundle()),
base64.StdEncoding.EncodeToString(testOIDCCA.Bundle()),
),
}, },
{ {
name: "autodetect impersonation proxy with autodetected JWT authenticator", name: "autodetect impersonation proxy with autodiscovered JWT authenticator",
args: []string{ args: []string{
"--kubeconfig", "./testdata/kubeconfig.yaml", "--kubeconfig", "./testdata/kubeconfig.yaml",
"--skip-validation", "--skip-validation",
@ -929,12 +1044,13 @@ func TestGetKubeconfig(t *testing.T) {
}, },
wantLogs: []string{ wantLogs: []string{
`"level"=0 "msg"="discovered CredentialIssuer" "name"="test-credential-issuer"`, `"level"=0 "msg"="discovered CredentialIssuer" "name"="test-credential-issuer"`,
`"level"=0 "msg"="detected Concierge in impersonation proxy mode" "endpoint"="https://impersonation-proxy-endpoint.test"`, `"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"`, `"level"=0 "msg"="discovered JWTAuthenticator" "name"="test-authenticator"`,
`"level"=0 "msg"="detected OIDC issuer" "issuer"="https://example.com/issuer"`, `"level"=0 "msg"="discovered OIDC issuer" "issuer"="https://example.com/issuer"`,
`"level"=0 "msg"="detected OIDC audience" "audience"="test-audience"`, `"level"=0 "msg"="discovered OIDC audience" "audience"="test-audience"`,
`"level"=0 "msg"="detected OIDC CA bundle" "length"=587`, `"level"=0 "msg"="discovered OIDC CA bundle" "roots"=1`,
`"level"=0 "msg"="switching kubeconfig cluster to point at impersonation proxy endpoint" "endpoint"="https://impersonation-proxy-endpoint.test"`,
}, },
wantStdout: here.Docf(` wantStdout: here.Docf(`
apiVersion: v1 apiVersion: v1