From 271c006b6c773fa72bbf030bded01fdd446c3b2e Mon Sep 17 00:00:00 2001 From: Matt Moyer Date: Thu, 8 Apr 2021 16:00:21 -0500 Subject: [PATCH 1/3] Add --credential-cache flag to "pinniped get kubeconfig" and tweak usage messages. Signed-off-by: Matt Moyer --- cmd/pinniped/cmd/kubeconfig.go | 10 +++++++++- cmd/pinniped/cmd/kubeconfig_test.go | 5 +++++ cmd/pinniped/cmd/login_oidc.go | 2 +- cmd/pinniped/cmd/login_oidc_test.go | 2 +- cmd/pinniped/cmd/login_static.go | 2 +- cmd/pinniped/cmd/login_static_test.go | 2 +- 6 files changed, 18 insertions(+), 5 deletions(-) diff --git a/cmd/pinniped/cmd/kubeconfig.go b/cmd/pinniped/cmd/kubeconfig.go index 8e0051b6..50aee669 100644 --- a/cmd/pinniped/cmd/kubeconfig.go +++ b/cmd/pinniped/cmd/kubeconfig.go @@ -87,6 +87,8 @@ type getKubeconfigParams struct { oidc getKubeconfigOIDCParams concierge getKubeconfigConciergeParams generatedNameSuffix string + credentialCachePath string + credentialCachePathSet bool } func kubeconfigCommand(deps kubeconfigDeps) *cobra.Command { @@ -132,7 +134,7 @@ func kubeconfigCommand(deps kubeconfigDeps) *cobra.Command { f.DurationVar(&flags.timeout, "timeout", 10*time.Minute, "Timeout for autodiscovery and validation") f.StringVarP(&flags.outputPath, "output", "o", "", "Output file path (default: stdout)") f.StringVar(&flags.generatedNameSuffix, "generated-name-suffix", "-pinniped", "Suffix to append to generated cluster, context, user kubeconfig entries") - + f.StringVar(&flags.credentialCachePath, "credential-cache", "", "Path to cluster-specific credentials cache") mustMarkHidden(cmd, "oidc-debug-session-cache") mustMarkDeprecated(cmd, "concierge-namespace", "not needed anymore") @@ -147,6 +149,7 @@ func kubeconfigCommand(deps kubeconfigDeps) *cobra.Command { defer func() { _ = out.Close() }() cmd.SetOut(out) } + flags.credentialCachePathSet = cmd.Flags().Changed("credential-cache") return runGetKubeconfig(cmd.Context(), cmd.OutOrStdout(), deps, flags) } return cmd @@ -233,6 +236,11 @@ func runGetKubeconfig(ctx context.Context, out io.Writer, deps kubeconfigDeps, f cluster.CertificateAuthorityData = flags.concierge.caBundle } + // If --credential-cache is set, pass it through. + if flags.credentialCachePathSet { + execConfig.Args = append(execConfig.Args, "--credential-cache="+flags.credentialCachePath) + } + // If one of the --static-* flags was passed, output a config that runs `pinniped login static`. if flags.staticToken != "" || flags.staticTokenEnvName != "" { if flags.staticToken != "" && flags.staticTokenEnvName != "" { diff --git a/cmd/pinniped/cmd/kubeconfig_test.go b/cmd/pinniped/cmd/kubeconfig_test.go index c7342a54..52384bb4 100644 --- a/cmd/pinniped/cmd/kubeconfig_test.go +++ b/cmd/pinniped/cmd/kubeconfig_test.go @@ -73,6 +73,7 @@ func TestGetKubeconfig(t *testing.T) { --concierge-endpoint string API base for the Concierge endpoint --concierge-mode mode Concierge mode of operation (default TokenCredentialRequestAPI) --concierge-skip-wait Skip waiting for any pending Concierge strategies to become ready (default: false) + --credential-cache string Path to cluster-specific credentials cache --generated-name-suffix string Suffix to append to generated cluster, context, user kubeconfig entries (default "-pinniped") -h, --help help for kubeconfig --kubeconfig string Path to kubeconfig file @@ -642,6 +643,7 @@ func TestGetKubeconfig(t *testing.T) { "--kubeconfig", "./testdata/kubeconfig.yaml", "--static-token-env", "TEST_TOKEN", "--skip-validation", + "--credential-cache", "", }, conciergeObjects: []runtime.Object{ &configv1alpha1.CredentialIssuer{ @@ -699,6 +701,7 @@ func TestGetKubeconfig(t *testing.T) { - --concierge-authenticator-type=webhook - --concierge-endpoint=https://fake-server-url-value - --concierge-ca-bundle-data=ZmFrZS1jZXJ0aWZpY2F0ZS1hdXRob3JpdHktZGF0YS12YWx1ZQ== + - --credential-cache= - --token-env=TEST_TOKEN command: '.../path/to/pinniped' env: [] @@ -809,6 +812,7 @@ func TestGetKubeconfig(t *testing.T) { "--oidc-request-audience", "test-audience", "--skip-validation", "--generated-name-suffix", "-sso", + "--credential-cache", "/path/to/cache/dir/credentials.yaml", }, conciergeObjects: []runtime.Object{ &configv1alpha1.CredentialIssuer{ @@ -862,6 +866,7 @@ func TestGetKubeconfig(t *testing.T) { - --concierge-authenticator-type=webhook - --concierge-endpoint=https://explicit-concierge-endpoint.example.com - --concierge-ca-bundle-data=%s + - --credential-cache=/path/to/cache/dir/credentials.yaml - --issuer=https://example.com/issuer - --client-id=pinniped-cli - --scopes=offline_access,openid,pinniped:request-audience diff --git a/cmd/pinniped/cmd/login_oidc.go b/cmd/pinniped/cmd/login_oidc.go index 8395e6c7..4d751d35 100644 --- a/cmd/pinniped/cmd/login_oidc.go +++ b/cmd/pinniped/cmd/login_oidc.go @@ -97,7 +97,7 @@ func oidcLoginCommand(deps oidcLoginCommandDeps) *cobra.Command { cmd.Flags().StringVar(&flags.conciergeEndpoint, "concierge-endpoint", "", "API base for the Concierge endpoint") cmd.Flags().StringVar(&flags.conciergeCABundle, "concierge-ca-bundle-data", "", "CA bundle to use when connecting to the Concierge") cmd.Flags().StringVar(&flags.conciergeAPIGroupSuffix, "concierge-api-group-suffix", groupsuffix.PinnipedDefaultSuffix, "Concierge API group suffix") - cmd.Flags().StringVar(&flags.credentialCachePath, "credential-cache", filepath.Join(mustGetConfigDir(), "credentials.yaml"), "Cluster-specific credentials cache path (\"\" disables the cache)") + cmd.Flags().StringVar(&flags.credentialCachePath, "credential-cache", filepath.Join(mustGetConfigDir(), "credentials.yaml"), "Path to cluster-specific credentials cache (\"\" disables the cache)") mustMarkHidden(cmd, "debug-session-cache") mustMarkRequired(cmd, "issuer") diff --git a/cmd/pinniped/cmd/login_oidc_test.go b/cmd/pinniped/cmd/login_oidc_test.go index c26c0b46..8472f6af 100644 --- a/cmd/pinniped/cmd/login_oidc_test.go +++ b/cmd/pinniped/cmd/login_oidc_test.go @@ -64,7 +64,7 @@ func TestLoginOIDCCommand(t *testing.T) { --concierge-authenticator-type string Concierge authenticator type (e.g., 'webhook', 'jwt') --concierge-ca-bundle-data string CA bundle to use when connecting to the Concierge --concierge-endpoint string API base for the Concierge endpoint - --credential-cache string Cluster-specific credentials cache path ("" disables the cache) (default "` + cfgDir + `/credentials.yaml") + --credential-cache string Path to cluster-specific credentials cache ("" disables the cache) (default "` + cfgDir + `/credentials.yaml") --enable-concierge Use the Concierge to login -h, --help help for oidc --issuer string OpenID Connect issuer URL diff --git a/cmd/pinniped/cmd/login_static.go b/cmd/pinniped/cmd/login_static.go index 18a48e9d..da7ff8e6 100644 --- a/cmd/pinniped/cmd/login_static.go +++ b/cmd/pinniped/cmd/login_static.go @@ -72,7 +72,7 @@ func staticLoginCommand(deps staticLoginDeps) *cobra.Command { cmd.Flags().StringVar(&flags.conciergeEndpoint, "concierge-endpoint", "", "API base for the Concierge endpoint") cmd.Flags().StringVar(&flags.conciergeCABundle, "concierge-ca-bundle-data", "", "CA bundle to use when connecting to the Concierge") cmd.Flags().StringVar(&flags.conciergeAPIGroupSuffix, "concierge-api-group-suffix", groupsuffix.PinnipedDefaultSuffix, "Concierge API group suffix") - cmd.Flags().StringVar(&flags.credentialCachePath, "credential-cache", filepath.Join(mustGetConfigDir(), "credentials.yaml"), "Cluster-specific credentials cache path (\"\" disables the cache)") + cmd.Flags().StringVar(&flags.credentialCachePath, "credential-cache", filepath.Join(mustGetConfigDir(), "credentials.yaml"), "Path to cluster-specific credentials cache (\"\" disables the cache)") cmd.RunE = func(cmd *cobra.Command, args []string) error { return runStaticLogin(cmd.OutOrStdout(), deps, flags) } diff --git a/cmd/pinniped/cmd/login_static_test.go b/cmd/pinniped/cmd/login_static_test.go index 53739353..c2e6d3ea 100644 --- a/cmd/pinniped/cmd/login_static_test.go +++ b/cmd/pinniped/cmd/login_static_test.go @@ -57,7 +57,7 @@ func TestLoginStaticCommand(t *testing.T) { --concierge-authenticator-type string Concierge authenticator type (e.g., 'webhook', 'jwt') --concierge-ca-bundle-data string CA bundle to use when connecting to the Concierge --concierge-endpoint string API base for the Concierge endpoint - --credential-cache string Cluster-specific credentials cache path ("" disables the cache) (default "` + cfgDir + `/credentials.yaml") + --credential-cache string Path to cluster-specific credentials cache ("" disables the cache) (default "` + cfgDir + `/credentials.yaml") --enable-concierge Use the Concierge to login -h, --help help for static --token string Static token to present during login From 3b461572eae544c183426748a34a6cd4d6ac35b5 Mon Sep 17 00:00:00 2001 From: Matt Moyer Date: Thu, 8 Apr 2021 17:00:14 -0500 Subject: [PATCH 2/3] Add cluster info to cache key for cluster-specific credential cache. This isn't strictly necessary because we currently always have the concierge endpoint and CA as CLI flags, but it doesn't hurt and it's better to err on the side of _not_ reusing a cache entry. Signed-off-by: Matt Moyer --- cmd/pinniped/cmd/login.go | 14 ++++++++++++++ cmd/pinniped/cmd/login_oidc.go | 8 +++++--- cmd/pinniped/cmd/login_static.go | 12 +++++++----- 3 files changed, 26 insertions(+), 8 deletions(-) diff --git a/cmd/pinniped/cmd/login.go b/cmd/pinniped/cmd/login.go index e27442ee..d1d1d151 100644 --- a/cmd/pinniped/cmd/login.go +++ b/cmd/pinniped/cmd/login.go @@ -5,6 +5,8 @@ package cmd import ( "github.com/spf13/cobra" + clientauthv1beta1 "k8s.io/client-go/pkg/apis/clientauthentication/v1beta1" + "k8s.io/client-go/tools/auth/exec" ) //nolint: gochecknoglobals @@ -20,3 +22,15 @@ var loginCmd = &cobra.Command{ func init() { rootCmd.AddCommand(loginCmd) } + +func loadClusterInfo() *clientauthv1beta1.Cluster { + obj, _, err := exec.LoadExecCredentialFromEnv() + if err != nil { + return nil + } + cred, ok := obj.(*clientauthv1beta1.ExecCredential) + if !ok { + return nil + } + return cred.Spec.Cluster +} diff --git a/cmd/pinniped/cmd/login_oidc.go b/cmd/pinniped/cmd/login_oidc.go index 4d751d35..34ead8f8 100644 --- a/cmd/pinniped/cmd/login_oidc.go +++ b/cmd/pinniped/cmd/login_oidc.go @@ -167,11 +167,13 @@ func runOIDCLogin(cmd *cobra.Command, deps oidcLoginCommandDeps, flags oidcLogin opts = append(opts, oidcclient.WithClient(client)) } - // Look up cached credentials based on a hash of all the CLI arguments. + // Look up cached credentials based on a hash of all the CLI arguments and the cluster info. cacheKey := struct { - Args []string `json:"args"` + Args []string `json:"args"` + ClusterInfo *clientauthv1beta1.Cluster `json:"cluster"` }{ - Args: os.Args[1:], + Args: os.Args[1:], + ClusterInfo: loadClusterInfo(), } var credCache *execcredcache.Cache if flags.credentialCachePath != "" { diff --git a/cmd/pinniped/cmd/login_static.go b/cmd/pinniped/cmd/login_static.go index da7ff8e6..4b9ac2fd 100644 --- a/cmd/pinniped/cmd/login_static.go +++ b/cmd/pinniped/cmd/login_static.go @@ -117,13 +117,15 @@ func runStaticLogin(out io.Writer, deps staticLoginDeps, flags staticLoginParams } cred := tokenCredential(&oidctypes.Token{IDToken: &oidctypes.IDToken{Token: token}}) - // Look up cached credentials based on a hash of all the CLI arguments and the current token value. + // Look up cached credentials based on a hash of all the CLI arguments, the current token value, and the cluster info. cacheKey := struct { - Args []string `json:"args"` - Token string `json:"token"` + Args []string `json:"args"` + Token string `json:"token"` + ClusterInfo *clientauthv1beta1.Cluster `json:"cluster"` }{ - Args: os.Args[1:], - Token: token, + Args: os.Args[1:], + Token: token, + ClusterInfo: loadClusterInfo(), } var credCache *execcredcache.Cache if flags.credentialCachePath != "" { From b59a4f3fec3a038a0563e1d59ccc04bd50f58a57 Mon Sep 17 00:00:00 2001 From: Matt Moyer Date: Thu, 8 Apr 2021 18:14:21 -0500 Subject: [PATCH 3/3] Use a temporary directory for credential cache in CLI tests. This avoids polluting the main cache directory on developer machines. Signed-off-by: Matt Moyer --- test/integration/cli_test.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/integration/cli_test.go b/test/integration/cli_test.go index 1221a604..3bf37437 100644 --- a/test/integration/cli_test.go +++ b/test/integration/cli_test.go @@ -49,11 +49,13 @@ func TestCLIGetKubeconfigStaticToken(t *testing.T) { // Build pinniped CLI. pinnipedExe := library.PinnipedCLIPath(t) + credCacheDir := testutil.TempDir(t) stdout, stderr := runPinnipedCLI(t, nil, pinnipedExe, "get", "kubeconfig", "--static-token", env.TestUser.Token, "--concierge-api-group-suffix", env.APIGroupSuffix, "--concierge-authenticator-type", "webhook", "--concierge-authenticator-name", authenticator.Name, + "--credential-cache", credCacheDir+"/credentials.yaml", ) assert.Contains(t, stderr, "discovered CredentialIssuer") assert.Contains(t, stderr, "discovered Concierge endpoint") @@ -383,6 +385,7 @@ func oidcLoginCommand(ctx context.Context, t *testing.T, pinnipedExe string, ses "--scopes", "offline_access,openid,email,profile", "--listen-port", callbackURL.Port(), "--session-cache", sessionCachePath, + "--credential-cache", testutil.TempDir(t)+"/credentials.yaml", "--skip-browser", )