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.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 8395e6c7..34ead8f8 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") @@ -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_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..4b9ac2fd 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) } @@ -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 != "" { 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 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", )