Add diagnostic logging to "pinniped get kubeconfig".

These stderr logs should help clarify all the autodetection logic that's happening in a particular run.

Signed-off-by: Matt Moyer <moyerm@vmware.com>
This commit is contained in:
Matt Moyer 2021-03-05 15:52:17 -06:00
parent c4f6fd5b3c
commit 36bc679142
No known key found for this signature in database
GPG Key ID: EAE88AD172C5AE2D
2 changed files with 134 additions and 10 deletions

View File

@ -10,20 +10,22 @@ import (
"fmt"
"io"
"io/ioutil"
"log"
"os"
"strconv"
"strings"
"time"
"github.com/coreos/go-oidc/v3/oidc"
"github.com/go-logr/logr"
"github.com/go-logr/stdr"
"github.com/spf13/cobra"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
clientauthenticationv1beta1 "k8s.io/client-go/pkg/apis/clientauthentication/v1beta1"
_ "k8s.io/client-go/plugin/pkg/client/auth" // Adds handlers for various dynamic auth plugins in client-go
"k8s.io/client-go/tools/clientcmd"
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
_ "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"
configv1alpha1 "go.pinniped.dev/generated/latest/apis/concierge/config/v1alpha1"
conciergeclientset "go.pinniped.dev/generated/latest/client/concierge/clientset/versioned"
@ -34,6 +36,7 @@ import (
type kubeconfigDeps struct {
getPathToSelf func() (string, error)
getClientset func(clientConfig clientcmd.ClientConfig, apiGroupSuffix string) (conciergeclientset.Interface, error)
log logr.Logger
}
func kubeconfigRealDeps() kubeconfigDeps {
@ -53,6 +56,7 @@ func kubeconfigRealDeps() kubeconfigDeps {
}
return client.PinnipedConcierge, nil
},
log: stdr.New(log.New(os.Stderr, "", 0)),
}
}
@ -181,7 +185,7 @@ func runGetKubeconfig(out io.Writer, deps kubeconfigDeps, flags getKubeconfigPar
}
if !flags.concierge.disabled {
credentialIssuer, err := lookupCredentialIssuer(clientset, flags.concierge.credentialIssuer)
credentialIssuer, err := lookupCredentialIssuer(clientset, flags.concierge.credentialIssuer, deps.log)
if err != nil {
return err
}
@ -190,12 +194,13 @@ func runGetKubeconfig(out io.Writer, deps kubeconfigDeps, flags getKubeconfigPar
clientset,
flags.concierge.authenticatorType,
flags.concierge.authenticatorName,
deps.log,
)
if err != nil {
return err
}
if err := configureConcierge(credentialIssuer, authenticator, &flags, cluster, &oidcCABundle, &execConfig); err != nil {
if err := configureConcierge(credentialIssuer, authenticator, &flags, cluster, &oidcCABundle, &execConfig, deps.log); err != nil {
return err
}
}
@ -246,7 +251,7 @@ func runGetKubeconfig(out io.Writer, deps kubeconfigDeps, flags getKubeconfigPar
return writeConfigAsYAML(out, newExecKubeconfig(cluster, &execConfig))
}
func configureConcierge(credentialIssuer *configv1alpha1.CredentialIssuer, 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, log logr.Logger) error {
var conciergeCABundleData []byte
// Autodiscover the --concierge-mode.
@ -258,9 +263,11 @@ func configureConcierge(credentialIssuer *configv1alpha1.CredentialIssuer, authe
}
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
flags.concierge.endpoint = strategy.Frontend.ImpersonationProxyInfo.Endpoint
var err error
@ -268,6 +275,7 @@ func configureConcierge(credentialIssuer *configv1alpha1.CredentialIssuer, authe
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.
@ -288,6 +296,7 @@ func configureConcierge(credentialIssuer *configv1alpha1.CredentialIssuer, authe
// If the --concierge-authenticator-type/--concierge-authenticator-name flags were not set explicitly, set
// them to point at the discovered WebhookAuthenticator.
if flags.concierge.authenticatorType == "" && flags.concierge.authenticatorName == "" {
log.Info("discovered WebhookAuthenticator", "name", auth.Name)
flags.concierge.authenticatorType = "webhook"
flags.concierge.authenticatorName = auth.Name
}
@ -295,17 +304,20 @@ func configureConcierge(credentialIssuer *configv1alpha1.CredentialIssuer, authe
// If the --concierge-authenticator-type/--concierge-authenticator-name flags were not set explicitly, set
// them to point at the discovered JWTAuthenticator.
if flags.concierge.authenticatorType == "" && flags.concierge.authenticatorName == "" {
log.Info("discovered JWTAuthenticator", "name", auth.Name)
flags.concierge.authenticatorType = "jwt"
flags.concierge.authenticatorName = auth.Name
}
// 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)
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)
flags.oidc.requestAudience = auth.Spec.Audience
}
@ -316,16 +328,19 @@ 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))
*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})
@ -349,6 +364,7 @@ func configureConcierge(credentialIssuer *configv1alpha1.CredentialIssuer, authe
// 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
}
@ -383,7 +399,7 @@ func newExecKubeconfig(cluster *clientcmdapi.Cluster, execConfig *clientcmdapi.E
}
}
func lookupCredentialIssuer(clientset conciergeclientset.Interface, name string) (*configv1alpha1.CredentialIssuer, error) {
func lookupCredentialIssuer(clientset conciergeclientset.Interface, name string, log logr.Logger) (*configv1alpha1.CredentialIssuer, error) {
ctx, cancelFunc := context.WithTimeout(context.Background(), time.Second*20)
defer cancelFunc()
@ -403,10 +419,13 @@ func lookupCredentialIssuer(clientset conciergeclientset.Interface, name string)
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
result := &results.Items[0]
log.Info("discovered CredentialIssuer", "name", result.Name)
return result, nil
}
func lookupAuthenticator(clientset conciergeclientset.Interface, authType, authName string) (metav1.Object, error) {
func lookupAuthenticator(clientset conciergeclientset.Interface, authType, authName string, log logr.Logger) (metav1.Object, error) {
ctx, cancelFunc := context.WithTimeout(context.Background(), time.Second*20)
defer cancelFunc()
@ -444,6 +463,12 @@ func lookupAuthenticator(clientset conciergeclientset.Interface, authType, authN
return nil, fmt.Errorf("no authenticators were found")
}
if len(results) > 1 {
for _, jwtAuth := range jwtAuths.Items {
log.Info("found JWTAuthenticator", "name", jwtAuth.Name)
}
for _, webhook := range webhooks.Items {
log.Info("found WebhookAuthenticator", "name", webhook.Name)
}
return nil, fmt.Errorf("multiple authenticators were found, so the --concierge-authenticator-type/--concierge-authenticator-name flags must be specified")
}
return results[0], nil

View File

@ -26,6 +26,7 @@ import (
"go.pinniped.dev/internal/certauthority"
"go.pinniped.dev/internal/here"
"go.pinniped.dev/internal/testutil"
"go.pinniped.dev/internal/testutil/testlogger"
)
func TestGetKubeconfig(t *testing.T) {
@ -46,6 +47,7 @@ func TestGetKubeconfig(t *testing.T) {
getClientsetErr error
conciergeObjects []runtime.Object
conciergeReactions []kubetesting.Reactor
wantLogs []string
wantError bool
wantStdout string
wantStderr string
@ -171,6 +173,9 @@ func TestGetKubeconfig(t *testing.T) {
conciergeObjects: []runtime.Object{
&configv1alpha1.CredentialIssuer{ObjectMeta: metav1.ObjectMeta{Name: "test-credential-issuer"}},
},
wantLogs: []string{
`"level"=0 "msg"="discovered CredentialIssuer" "name"="test-credential-issuer"`,
},
wantError: true,
wantStderr: here.Doc(`
Error: webhookauthenticators.authentication.concierge.pinniped.dev "test-authenticator" not found
@ -186,6 +191,9 @@ func TestGetKubeconfig(t *testing.T) {
conciergeObjects: []runtime.Object{
&configv1alpha1.CredentialIssuer{ObjectMeta: metav1.ObjectMeta{Name: "test-credential-issuer"}},
},
wantLogs: []string{
`"level"=0 "msg"="discovered CredentialIssuer" "name"="test-credential-issuer"`,
},
wantError: true,
wantStderr: here.Doc(`
Error: jwtauthenticators.authentication.concierge.pinniped.dev "test-authenticator" not found
@ -201,6 +209,9 @@ func TestGetKubeconfig(t *testing.T) {
conciergeObjects: []runtime.Object{
&configv1alpha1.CredentialIssuer{ObjectMeta: metav1.ObjectMeta{Name: "test-credential-issuer"}},
},
wantLogs: []string{
`"level"=0 "msg"="discovered CredentialIssuer" "name"="test-credential-issuer"`,
},
wantError: true,
wantStderr: here.Doc(`
Error: invalid authenticator type "invalid", supported values are "webhook" and "jwt"
@ -214,6 +225,9 @@ func TestGetKubeconfig(t *testing.T) {
conciergeObjects: []runtime.Object{
&configv1alpha1.CredentialIssuer{ObjectMeta: metav1.ObjectMeta{Name: "test-credential-issuer"}},
},
wantLogs: []string{
`"level"=0 "msg"="discovered CredentialIssuer" "name"="test-credential-issuer"`,
},
conciergeReactions: []kubetesting.Reactor{
&kubetesting.SimpleReactor{
Verb: "*",
@ -245,6 +259,9 @@ func TestGetKubeconfig(t *testing.T) {
},
},
},
wantLogs: []string{
`"level"=0 "msg"="discovered CredentialIssuer" "name"="test-credential-issuer"`,
},
wantError: true,
wantStderr: here.Doc(`
Error: failed to list WebhookAuthenticator objects for autodiscovery: some list error
@ -258,6 +275,9 @@ func TestGetKubeconfig(t *testing.T) {
conciergeObjects: []runtime.Object{
&configv1alpha1.CredentialIssuer{ObjectMeta: metav1.ObjectMeta{Name: "test-credential-issuer"}},
},
wantLogs: []string{
`"level"=0 "msg"="discovered CredentialIssuer" "name"="test-credential-issuer"`,
},
wantError: true,
wantStderr: here.Doc(`
Error: no authenticators were found
@ -275,6 +295,13 @@ func TestGetKubeconfig(t *testing.T) {
&conciergev1alpha1.WebhookAuthenticator{ObjectMeta: metav1.ObjectMeta{Name: "test-authenticator-3"}},
&conciergev1alpha1.WebhookAuthenticator{ObjectMeta: metav1.ObjectMeta{Name: "test-authenticator-4"}},
},
wantLogs: []string{
`"level"=0 "msg"="discovered CredentialIssuer" "name"="test-credential-issuer"`,
`"level"=0 "msg"="found JWTAuthenticator" "name"="test-authenticator-1"`,
`"level"=0 "msg"="found JWTAuthenticator" "name"="test-authenticator-2"`,
`"level"=0 "msg"="found WebhookAuthenticator" "name"="test-authenticator-3"`,
`"level"=0 "msg"="found WebhookAuthenticator" "name"="test-authenticator-4"`,
},
wantError: true,
wantStderr: here.Doc(`
Error: multiple authenticators were found, so the --concierge-authenticator-type/--concierge-authenticator-name flags must be specified
@ -289,6 +316,9 @@ func TestGetKubeconfig(t *testing.T) {
&configv1alpha1.CredentialIssuer{ObjectMeta: metav1.ObjectMeta{Name: "test-credential-issuer"}},
&conciergev1alpha1.WebhookAuthenticator{ObjectMeta: metav1.ObjectMeta{Name: "test-authenticator"}},
},
wantLogs: []string{
`"level"=0 "msg"="discovered CredentialIssuer" "name"="test-credential-issuer"`,
},
wantError: true,
wantStderr: here.Doc(`
Error: could not autodiscover --concierge-mode and none was provided
@ -340,6 +370,9 @@ func TestGetKubeconfig(t *testing.T) {
},
&conciergev1alpha1.WebhookAuthenticator{ObjectMeta: metav1.ObjectMeta{Name: "test-authenticator"}},
},
wantLogs: []string{
`"level"=0 "msg"="discovered CredentialIssuer" "name"="test-credential-issuer"`,
},
wantError: true,
wantStderr: here.Doc(`
Error: autodiscovered Concierge CA bundle is invalid: illegal base64 data at input byte 7
@ -372,6 +405,13 @@ func TestGetKubeconfig(t *testing.T) {
},
&conciergev1alpha1.WebhookAuthenticator{ObjectMeta: metav1.ObjectMeta{Name: "test-authenticator"}},
},
wantLogs: []string{
`"level"=0 "msg"="discovered CredentialIssuer" "name"="test-credential-issuer"`,
`"level"=0 "msg"="detected Concierge in TokenCredentialRequest API mode"`,
`"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(`
Error: could not autodiscover --oidc-issuer and none was provided
@ -401,6 +441,12 @@ func TestGetKubeconfig(t *testing.T) {
},
},
},
wantLogs: []string{
`"level"=0 "msg"="discovered CredentialIssuer" "name"="test-credential-issuer"`,
`"level"=0 "msg"="discovered JWTAuthenticator" "name"="test-authenticator"`,
`"level"=0 "msg"="detected OIDC issuer" "issuer"=""`,
`"level"=0 "msg"="detected OIDC audience" "audience"=""`,
},
wantError: true,
wantStderr: here.Doc(`
Error: tried to autodiscover --oidc-ca-bundle, but JWTAuthenticator test-authenticator has invalid spec.tls.certificateAuthorityData: illegal base64 data at input byte 7
@ -428,6 +474,9 @@ func TestGetKubeconfig(t *testing.T) {
},
&conciergev1alpha1.WebhookAuthenticator{ObjectMeta: metav1.ObjectMeta{Name: "test-authenticator"}},
},
wantLogs: []string{
`"level"=0 "msg"="discovered CredentialIssuer" "name"="test-credential-issuer"`,
},
wantError: true,
wantStderr: here.Doc(`
Error: could not read --concierge-ca-bundle: open ./does/not/exist: no such file or directory
@ -452,6 +501,12 @@ func TestGetKubeconfig(t *testing.T) {
},
&conciergev1alpha1.WebhookAuthenticator{ObjectMeta: metav1.ObjectMeta{Name: "test-authenticator"}},
},
wantLogs: []string{
`"level"=0 "msg"="discovered CredentialIssuer" "name"="test-credential-issuer"`,
`"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(`
Error: only one of --static-token and --static-token-env can be specified
@ -485,6 +540,12 @@ func TestGetKubeconfig(t *testing.T) {
},
&conciergev1alpha1.WebhookAuthenticator{ObjectMeta: metav1.ObjectMeta{Name: "test-authenticator"}},
},
wantLogs: []string{
`"level"=0 "msg"="discovered CredentialIssuer" "name"="test-credential-issuer"`,
`"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
clusters:
@ -539,6 +600,12 @@ func TestGetKubeconfig(t *testing.T) {
},
&conciergev1alpha1.WebhookAuthenticator{ObjectMeta: metav1.ObjectMeta{Name: "test-authenticator"}},
},
wantLogs: []string{
`"level"=0 "msg"="discovered CredentialIssuer" "name"="test-credential-issuer"`,
`"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
clusters:
@ -601,6 +668,15 @@ func TestGetKubeconfig(t *testing.T) {
},
},
},
wantLogs: []string{
`"level"=0 "msg"="discovered CredentialIssuer" "name"="test-credential-issuer"`,
`"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`,
},
wantStdout: here.Docf(`
apiVersion: v1
clusters:
@ -645,9 +721,12 @@ func TestGetKubeconfig(t *testing.T) {
name: "autodetect nothing, set a bunch of options",
args: []string{
"--kubeconfig", "./testdata/kubeconfig.yaml",
"--concierge-credential-issuer", "test-credential-issuer",
"--concierge-api-group-suffix", "tuna.io",
"--concierge-authenticator-type", "webhook",
"--concierge-authenticator-name", "test-authenticator",
"--concierge-endpoint", "https://concierge-endpoint.example.com",
"--concierge-ca-bundle", testConciergeCABundlePath,
"--oidc-issuer", "https://example.com/issuer",
"--oidc-skip-browser",
"--oidc-listen-port", "1234",
@ -697,8 +776,8 @@ func TestGetKubeconfig(t *testing.T) {
- --concierge-api-group-suffix=tuna.io
- --concierge-authenticator-name=test-authenticator
- --concierge-authenticator-type=webhook
- --concierge-endpoint=https://fake-server-url-value
- --concierge-ca-bundle-data=ZmFrZS1jZXJ0aWZpY2F0ZS1hdXRob3JpdHktZGF0YS12YWx1ZQ==
- --concierge-endpoint=https://concierge-endpoint.example.com
- --concierge-ca-bundle-data=dGVzdC1jb25jaWVyZ2UtY2E=
- --concierge-mode=TokenCredentialRequestAPI
- --issuer=https://example.com/issuer
- --client-id=pinniped-cli
@ -739,6 +818,14 @@ func TestGetKubeconfig(t *testing.T) {
},
},
},
wantLogs: []string{
`"level"=0 "msg"="discovered CredentialIssuer" "name"="test-credential-issuer"`,
`"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"`,
},
wantStdout: here.Docf(`
apiVersion: v1
clusters:
@ -831,6 +918,15 @@ 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 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"`,
},
wantStdout: here.Docf(`
apiVersion: v1
clusters:
@ -875,6 +971,7 @@ func TestGetKubeconfig(t *testing.T) {
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
testLog := testlogger.New(t)
cmd := kubeconfigCommand(kubeconfigDeps{
getPathToSelf: func() (string, error) {
if tt.getPathToSelfErr != nil {
@ -897,6 +994,7 @@ func TestGetKubeconfig(t *testing.T) {
}
return fake, nil
},
log: testLog,
})
require.NotNil(t, cmd)
@ -910,6 +1008,7 @@ func TestGetKubeconfig(t *testing.T) {
} else {
require.NoError(t, err)
}
testLog.Expect(tt.wantLogs)
require.Equal(t, tt.wantStdout, stdout.String(), "unexpected stdout")
require.Equal(t, tt.wantStderr, stderr.String(), "unexpected stderr")
})