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"
"fmt"
"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.
@ -25,7 +27,7 @@ func (c *conciergeMode) String() string {
switch *c {
case modeImpersonationProxy:
return "ImpersonationProxy"
case modeTokenCredentialRequestAPI, modeUnknown:
case modeTokenCredentialRequestAPI:
return "TokenCredentialRequestAPI"
default:
return "TokenCredentialRequestAPI"
@ -51,3 +53,15 @@ func (c *conciergeMode) Set(s string) error {
func (c *conciergeMode) Type() string {
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"
"github.com/stretchr/testify/require"
configv1alpha1 "go.pinniped.dev/generated/latest/apis/concierge/config/v1alpha1"
)
func TestConciergeModeFlag(t *testing.T) {
var m conciergeMode
require.Equal(t, "mode", m.Type())
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.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.Equal(t, modeTokenCredentialRequestAPI, m)
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.Equal(t, modeTokenCredentialRequestAPI, m)
@ -26,6 +34,8 @@ func TestConciergeModeFlag(t *testing.T) {
require.NoError(t, m.Set("ImpersonationProxy"))
require.Equal(t, modeImpersonationProxy, m)
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.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 {
var conciergeCABundleData []byte
// Autodiscover the --concierge-mode.
if flags.concierge.mode == modeUnknown { //nolint:nestif
strategyLoop:
for _, strategy := range credentialIssuer.Status.Strategies {
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:
frontend, err := getConciergeFrontend(credentialIssuer, flags.concierge.mode)
if err != nil {
return err
}
flags.concierge.mode = modeImpersonationProxy
flags.concierge.endpoint = strategy.Frontend.ImpersonationProxyInfo.Endpoint
var err error
conciergeCABundleData, err = base64.StdEncoding.DecodeString(strategy.Frontend.ImpersonationProxyInfo.CertificateAuthorityData)
if err != nil {
return fmt.Errorf("autodiscovered Concierge CA bundle is invalid: %w", err)
}
log.Info("detected Concierge in impersonation proxy mode", "endpoint", strategy.Frontend.ImpersonationProxyInfo.Endpoint)
break strategyLoop
default:
// Skip any unknown frontend types.
}
}
if flags.concierge.mode == modeUnknown {
// Fall back to deprecated field for backwards compatibility.
if credentialIssuer.Status.KubeConfigInfo != nil {
flags.concierge.mode = modeTokenCredentialRequestAPI
} else {
return fmt.Errorf("could not autodiscover --concierge-mode and none was provided")
// Auto-set --concierge-mode if it wasn't explicitly set.
if flags.concierge.mode == modeUnknown {
switch frontend.Type {
case configv1alpha1.TokenCredentialRequestAPIFrontendType:
log.Info("discovered Concierge operating in TokenCredentialRequest API mode")
flags.concierge.mode = modeTokenCredentialRequestAPI
case configv1alpha1.ImpersonationProxyFrontendType:
log.Info("discovered Concierge operating in impersonation proxy mode")
flags.concierge.mode = modeImpersonationProxy
}
}
// Auto-set --concierge-endpoint if it wasn't explicitly set.
if flags.concierge.endpoint == "" {
switch frontend.Type {
case configv1alpha1.TokenCredentialRequestAPIFrontendType:
flags.concierge.endpoint = v1Cluster.Server
case configv1alpha1.ImpersonationProxyFrontendType:
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) {
@ -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 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
}
// If the --oidc-request-audience flag was not set explicitly, default it to the spec.audience field of the JWTAuthenticator.
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
}
@ -359,29 +372,11 @@ func configureConcierge(credentialIssuer *configv1alpha1.CredentialIssuer, authe
if err != nil {
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)
}
}
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.
execConfig.Args = append(execConfig.Args,
"--enable-concierge",
@ -393,14 +388,53 @@ func configureConcierge(credentialIssuer *configv1alpha1.CredentialIssuer, authe
"--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 {
log.Info("switching kubeconfig cluster to point at impersonation proxy endpoint", "endpoint", flags.concierge.endpoint)
v1Cluster.CertificateAuthorityData = conciergeCABundleData
v1Cluster.Server = flags.concierge.endpoint
// Point kubectl at the concierge endpoint.
v1Cluster.Server = flags.concierge.endpoint
v1Cluster.CertificateAuthorityData = conciergeCABundleData
return nil
}
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) {
@ -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")
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")
require.NoError(t, ioutil.WriteFile(testConciergeCABundlePath, []byte("test-concierge-ca"), 0600))
require.NoError(t, ioutil.WriteFile(testConciergeCABundlePath, testConciergeCA.Bundle(), 0600))
tests := []struct {
name string
@ -324,7 +326,7 @@ func TestGetKubeconfig(t *testing.T) {
},
wantError: true,
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{
`"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: here.Doc(`
@ -410,10 +414,10 @@ func TestGetKubeconfig(t *testing.T) {
},
wantLogs: []string{
`"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"="detected concierge endpoint" "endpoint"="https://fake-server-url-value"`,
`"level"=0 "msg"="detected concierge CA bundle" "length"=37`,
},
wantError: true,
wantStderr: here.Doc(`
@ -433,11 +437,22 @@ func TestGetKubeconfig(t *testing.T) {
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: "https://test-issuer.example.com",
Audience: "some-test-audience",
TLS: &conciergev1alpha1.TLSSpec{
CertificateAuthorityData: "invalid-base64",
},
@ -446,9 +461,12 @@ func TestGetKubeconfig(t *testing.T) {
},
wantLogs: []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"="detected OIDC issuer" "issuer"=""`,
`"level"=0 "msg"="detected OIDC audience" "audience"=""`,
`"level"=0 "msg"="discovered OIDC issuer" "issuer"="https://test-issuer.example.com"`,
`"level"=0 "msg"="discovered OIDC audience" "audience"="some-test-audience"`,
},
wantError: true,
wantStderr: here.Doc(`
@ -469,10 +487,18 @@ func TestGetKubeconfig(t *testing.T) {
&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.ImpersonationProxyStrategyType,
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"}},
@ -496,19 +522,28 @@ func TestGetKubeconfig(t *testing.T) {
&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.ImpersonationProxyStrategyType,
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"}},
},
wantLogs: []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.example.com"`,
`"level"=0 "msg"="discovered Concierge certificate authority bundle" "roots"=1`,
`"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,
wantStderr: here.Doc(`
@ -536,19 +571,28 @@ func TestGetKubeconfig(t *testing.T) {
&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,
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"}},
},
wantLogs: []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"`,
`"level"=0 "msg"="detected concierge endpoint" "endpoint"="https://fake-server-url-value"`,
`"level"=0 "msg"="detected concierge CA bundle" "length"=37`,
},
wantStdout: here.Doc(`
apiVersion: v1
@ -597,19 +641,28 @@ func TestGetKubeconfig(t *testing.T) {
&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,
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"}},
},
wantLogs: []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"`,
`"level"=0 "msg"="detected concierge endpoint" "endpoint"="https://fake-server-url-value"`,
`"level"=0 "msg"="detected concierge CA bundle" "length"=37`,
},
wantStdout: here.Doc(`
apiVersion: v1
@ -657,10 +710,18 @@ func TestGetKubeconfig(t *testing.T) {
&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,
Frontend: &configv1alpha1.CredentialIssuerFrontend{
Type: configv1alpha1.TokenCredentialRequestAPIFrontendType,
TokenCredentialRequestAPIInfo: &configv1alpha1.TokenCredentialRequestAPIInfo{
Server: "https://concierge-endpoint.example.com",
CertificateAuthorityData: base64.StdEncoding.EncodeToString(testConciergeCA.Bundle()),
},
},
}},
},
},
&conciergev1alpha1.JWTAuthenticator{
@ -676,12 +737,13 @@ func TestGetKubeconfig(t *testing.T) {
},
wantLogs: []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"="detected OIDC issuer" "issuer"="https://example.com/issuer"`,
`"level"=0 "msg"="detected OIDC audience" "audience"="test-audience"`,
`"level"=0 "msg"="detected OIDC CA bundle" "length"=587`,
`"level"=0 "msg"="detected concierge endpoint" "endpoint"="https://fake-server-url-value"`,
`"level"=0 "msg"="detected concierge CA bundle" "length"=37`,
`"level"=0 "msg"="discovered OIDC issuer" "issuer"="https://example.com/issuer"`,
`"level"=0 "msg"="discovered OIDC audience" "audience"="test-audience"`,
`"level"=0 "msg"="discovered OIDC CA bundle" "roots"=1`,
},
wantStdout: here.Docf(`
apiVersion: v1
@ -731,7 +793,8 @@ func TestGetKubeconfig(t *testing.T) {
"--concierge-api-group-suffix", "tuna.io",
"--concierge-authenticator-type", "webhook",
"--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,
"--oidc-issuer", "https://example.com/issuer",
"--oidc-skip-browser",
@ -746,22 +809,33 @@ func TestGetKubeconfig(t *testing.T) {
&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,
Frontend: &configv1alpha1.CredentialIssuerFrontend{
Type: configv1alpha1.TokenCredentialRequestAPIFrontendType,
TokenCredentialRequestAPIInfo: &configv1alpha1.TokenCredentialRequestAPIInfo{
Server: "https://concierge-endpoint.example.com",
CertificateAuthorityData: "dGVzdC10Y3ItYXBpLWNh",
},
},
}},
},
},
&conciergev1alpha1.WebhookAuthenticator{
ObjectMeta: metav1.ObjectMeta{Name: "test-authenticator"},
},
},
wantLogs: []string{
`"level"=0 "msg"="loaded Concierge certificate authority bundle" "roots"=1`,
},
wantStdout: here.Docf(`
apiVersion: v1
clusters:
- cluster:
certificate-authority-data: ZmFrZS1jZXJ0aWZpY2F0ZS1hdXRob3JpdHktZGF0YS12YWx1ZQ==
server: https://fake-server-url-value
certificate-authority-data: %s
server: https://explicit-concierge-endpoint.example.com
name: pinniped
contexts:
- context:
@ -783,8 +857,8 @@ func TestGetKubeconfig(t *testing.T) {
- --concierge-api-group-suffix=tuna.io
- --concierge-authenticator-name=test-authenticator
- --concierge-authenticator-type=webhook
- --concierge-endpoint=https://concierge-endpoint.example.com
- --concierge-ca-bundle-data=dGVzdC1jb25jaWVyZ2UtY2E=
- --concierge-endpoint=https://explicit-concierge-endpoint.example.com
- --concierge-ca-bundle-data=%s
- --concierge-mode=TokenCredentialRequestAPI
- --issuer=https://example.com/issuer
- --client-id=pinniped-cli
@ -798,22 +872,58 @@ func TestGetKubeconfig(t *testing.T) {
command: '.../path/to/pinniped'
env: []
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",
},
{
name: "configure impersonation proxy with autodetected JWT authenticator",
name: "configure impersonation proxy with autodiscovered JWT authenticator",
args: []string{
"--kubeconfig", "./testdata/kubeconfig.yaml",
"--concierge-ca-bundle", testConciergeCABundlePath,
"--concierge-endpoint", "https://impersonation-proxy-endpoint.test",
"--concierge-mode", "ImpersonationProxy",
"--skip-validation",
},
conciergeObjects: []runtime.Object{
&configv1alpha1.CredentialIssuer{
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{
ObjectMeta: metav1.ObjectMeta{Name: "test-authenticator"},
@ -828,17 +938,18 @@ func TestGetKubeconfig(t *testing.T) {
},
wantLogs: []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"`,
`"level"=0 "msg"="detected OIDC issuer" "issuer"="https://example.com/issuer"`,
`"level"=0 "msg"="detected OIDC audience" "audience"="test-audience"`,
`"level"=0 "msg"="detected OIDC CA bundle" "length"=587`,
`"level"=0 "msg"="switching kubeconfig cluster to point at impersonation proxy endpoint" "endpoint"="https://impersonation-proxy-endpoint.test"`,
`"level"=0 "msg"="discovered OIDC issuer" "issuer"="https://example.com/issuer"`,
`"level"=0 "msg"="discovered OIDC audience" "audience"="test-audience"`,
`"level"=0 "msg"="discovered OIDC CA bundle" "roots"=1`,
},
wantStdout: here.Docf(`
apiVersion: v1
clusters:
- cluster:
certificate-authority-data: dGVzdC1jb25jaWVyZ2UtY2E=
certificate-authority-data: %s
server: https://impersonation-proxy-endpoint.test
name: pinniped
contexts:
@ -862,7 +973,7 @@ func TestGetKubeconfig(t *testing.T) {
- --concierge-authenticator-name=test-authenticator
- --concierge-authenticator-type=jwt
- --concierge-endpoint=https://impersonation-proxy-endpoint.test
- --concierge-ca-bundle-data=dGVzdC1jb25jaWVyZ2UtY2E=
- --concierge-ca-bundle-data=%s
- --concierge-mode=ImpersonationProxy
- --issuer=https://example.com/issuer
- --client-id=pinniped-cli
@ -872,10 +983,14 @@ func TestGetKubeconfig(t *testing.T) {
command: '.../path/to/pinniped'
env: []
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{
"--kubeconfig", "./testdata/kubeconfig.yaml",
"--skip-validation",
@ -929,12 +1044,13 @@ func TestGetKubeconfig(t *testing.T) {
},
wantLogs: []string{
`"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"="detected OIDC issuer" "issuer"="https://example.com/issuer"`,
`"level"=0 "msg"="detected OIDC audience" "audience"="test-audience"`,
`"level"=0 "msg"="detected OIDC CA bundle" "length"=587`,
`"level"=0 "msg"="switching kubeconfig cluster to point at impersonation proxy endpoint" "endpoint"="https://impersonation-proxy-endpoint.test"`,
`"level"=0 "msg"="discovered OIDC issuer" "issuer"="https://example.com/issuer"`,
`"level"=0 "msg"="discovered OIDC audience" "audience"="test-audience"`,
`"level"=0 "msg"="discovered OIDC CA bundle" "roots"=1`,
},
wantStdout: here.Docf(`
apiVersion: v1