From 22a8bea7db4af19faa5e5a90dc953a997f550dad Mon Sep 17 00:00:00 2001 From: onuryilmaz Date: Wed, 4 Feb 2026 18:37:36 +0100 Subject: [PATCH] kubelogin: add separate cache dirs when connector_id is used --- cmd/cluster-version.go | 16 ++++++++-------- cmd/sync.go | 23 ++++++++++++++++++++--- cmd/sync_test.go | 37 ++++++++++++++++++++++++++++++++++++- 3 files changed, 64 insertions(+), 12 deletions(-) diff --git a/cmd/cluster-version.go b/cmd/cluster-version.go index e78186e..f418cca 100644 --- a/cmd/cluster-version.go +++ b/cmd/cluster-version.go @@ -42,7 +42,7 @@ func runClusterVersion(cmd *cobra.Command, args []string) error { if err != nil { // 2) Fallback to authenticated if !hasAuth(cfg) { - return fmt.Errorf("no authentication methods found in your kubeconfig. please authenticate (`kubelogin`, etc.) and try again.") + return fmt.Errorf("no authentication methods found in your kubeconfig. please authenticate (`kubelogin`, etc.) and try again") } clientset, cerr := kubernetes.NewForConfig(cfg) @@ -73,7 +73,7 @@ func hasAuth(cfg *rest.Config) bool { if cfg.Username != "" && cfg.Password != "" { return true } - if len(cfg.TLSClientConfig.CertData) > 0 || cfg.TLSClientConfig.CertFile != "" { + if len(cfg.CertData) > 0 || cfg.CertFile != "" { return true } if cfg.ExecProvider != nil { @@ -94,19 +94,19 @@ func getUnauthenticatedVersion(cfg *rest.Config) (*version.Info, error) { // build TLS config tlsCfg := &tls.Config{} - if cfg.TLSClientConfig.Insecure { + if cfg.Insecure { tlsCfg.InsecureSkipVerify = true } // trust the same CA if provided - if len(cfg.TLSClientConfig.CAData) > 0 { + if len(cfg.CAData) > 0 { pool := x509.NewCertPool() - if ok := pool.AppendCertsFromPEM(cfg.TLSClientConfig.CAData); !ok { + if ok := pool.AppendCertsFromPEM(cfg.CAData); !ok { return nil, fmt.Errorf("failed to append CA data") } tlsCfg.RootCAs = pool - } else if cfg.TLSClientConfig.CAFile != "" { - pem, err := os.ReadFile(cfg.TLSClientConfig.CAFile) + } else if cfg.CAFile != "" { + pem, err := os.ReadFile(cfg.CAFile) if err != nil { return nil, err } @@ -122,7 +122,7 @@ func getUnauthenticatedVersion(cfg *rest.Config) (*version.Info, error) { if err != nil { return nil, err } - defer resp.Body.Close() + defer func() { _ = resp.Body.Close() }() if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("unexpected HTTP status: %s", resp.Status) diff --git a/cmd/sync.go b/cmd/sync.go index a252f01..ba19960 100644 --- a/cmd/sync.go +++ b/cmd/sync.go @@ -34,13 +34,16 @@ var ( authType string kubeloginPath string kubeloginExtraArgs []string + kubeloginTokenCacheDir string ) func init() { syncCmd.Flags().StringVarP(&greenhouseClusterKubeconfig, "greenhouse-cluster-kubeconfig", "k", clientcmd.RecommendedHomeFile, "kubeconfig file path for Greenhouse cluster") syncCmd.Flags().StringVarP(&greenhouseClusterContext, "greenhouse-cluster-context", "c", "", "context in greenhouse-cluster-kubeconfig, the context in the file is used if this flag is not set") syncCmd.Flags().StringVarP(&greenhouseClusterNamespace, "greenhouse-cluster-namespace", "n", "", "namespace for greenhouse-cluster-kubeconfig, it is the same value as Greenhouse organization") - syncCmd.MarkFlagRequired("greenhouse-cluster-namespace") + if err := syncCmd.MarkFlagRequired("greenhouse-cluster-namespace"); err != nil { + panic(err) + } syncCmd.Flags().StringVarP(&remoteClusterKubeconfig, "remote-cluster-kubeconfig", "r", clientcmd.RecommendedHomeFile, "kubeconfig file path for remote clusters") syncCmd.Flags().StringVar(&remoteClusterName, "remote-cluster-name", "", "name of the remote cluster, if not set all clusters are retrieved") syncCmd.Flags().StringVar(&prefix, "prefix", "cloudctl", "prefix for kubeconfig entries. it is used to separate and manage the entries of this tool only") @@ -50,6 +53,7 @@ func init() { syncCmd.Flags().StringVar(&authType, "auth-type", "auth-provider", "authentication config style to write for users: auth-provider or exec-plugin") syncCmd.Flags().StringVar(&kubeloginPath, "kubelogin-path", "kubelogin", "path to kubelogin command when using exec-plugin auth-type") syncCmd.Flags().StringSliceVar(&kubeloginExtraArgs, "kubelogin-extra-args", nil, "extra arguments to pass to kubelogin exec plugin") + syncCmd.Flags().StringVar(&kubeloginTokenCacheDir, "kubelogin-token-cache-dir", "$(HOME)/.kube/cache/oidc-login", "token cache directory for kubelogin") } var syncCmd = &cobra.Command{ @@ -177,7 +181,7 @@ func buildIncomingKubeconfig(items []v1alpha1.ClusterKubeconfig) (*clientcmdapi. Exec: &clientcmdapi.ExecConfig{ APIVersion: "client.authentication.k8s.io/v1", Command: kubeloginPath, - Args: buildKubeloginArgs(authItem.AuthInfo.AuthProvider.Config, kubeloginExtraArgs), + Args: buildKubeloginArgs(authItem.AuthInfo.AuthProvider.Config, kubeloginExtraArgs, kubeloginTokenCacheDir), InteractiveMode: clientcmdapi.IfAvailableExecInteractiveMode, }, } @@ -355,7 +359,7 @@ func generateAuthInfoKey(authInfo *clientcmdapi.AuthInfo) string { } // buildKubeloginArgs constructs kubelogin arguments from an oidc auth-provider config and extra args -func buildKubeloginArgs(cfg map[string]string, extra []string) []string { +func buildKubeloginArgs(cfg map[string]string, extra []string, tokenCacheDir string) []string { args := []string{"get-token"} if v := cfg["idp-issuer-url"]; v != "" { args = append(args, "--oidc-issuer-url="+v) @@ -376,6 +380,19 @@ func buildKubeloginArgs(cfg map[string]string, extra []string) []string { } if v := cfg["auth-request-extra-params"]; v != "" { args = append(args, "--oidc-auth-request-extra-params="+v) + // If connector_id is used, use a separate token cache directory to avoid collisions + // between multiple users on the same machine. + // See https://github.com/int128/kubelogin/issues/29 + for _, param := range strings.Split(v, ",") { + kv := strings.SplitN(param, "=", 2) + if len(kv) == 2 && strings.TrimSpace(kv[0]) == "connector_id" { + connectorID := strings.TrimSpace(kv[1]) + if connectorID != "" { + args = append(args, fmt.Sprintf("--token-cache-dir=%s/%s", tokenCacheDir, connectorID)) + } + break + } + } } // allow caller to inject additional flags if len(extra) > 0 { diff --git a/cmd/sync_test.go b/cmd/sync_test.go index 3e3f491..e7c8c9f 100644 --- a/cmd/sync_test.go +++ b/cmd/sync_test.go @@ -226,6 +226,10 @@ func TestSyncFlags_AuthTypeAndKubeloginDefaults(t *testing.T) { g.Expect(fExtra).ToNot(BeNil()) // StringSliceVar defaults to [] if nil; DefValue is representation of default (empty) g.Expect(fExtra.DefValue).To(Or(Equal("[]"), Equal(""))) + + fCache := syncCmd.Flags().Lookup("kubelogin-token-cache-dir") + g.Expect(fCache).ToNot(BeNil()) + g.Expect(fCache.DefValue).To(Equal("$(HOME)/.kube/cache/oidc-login")) } func TestBuildKubeloginArgs_MappingAndExtras(t *testing.T) { @@ -240,7 +244,7 @@ func TestBuildKubeloginArgs_MappingAndExtras(t *testing.T) { } extra := []string{"--v=4", "--token-cache-dir=/tmp/k"} - args := buildKubeloginArgs(cfg, extra) + args := buildKubeloginArgs(cfg, extra, "$(HOME)/.kube/cache/oidc-login") // Starts with subcommand g.Expect(args[0]).To(Equal("get-token")) @@ -260,6 +264,37 @@ func TestBuildKubeloginArgs_MappingAndExtras(t *testing.T) { g.Expect(args[len(args)-2:]).To(Equal(extra)) } +func TestBuildKubeloginArgs_ConnectorID(t *testing.T) { + g := NewWithT(t) + + cfg := map[string]string{ + "idp-issuer-url": "https://issuer.example.com", + "client-id": "cid", + "auth-request-extra-params": "connector_id=my-id", + } + + args := buildKubeloginArgs(cfg, nil, "$(HOME)/.kube/cache/oidc-login") + + g.Expect(args).To(ContainElement("--oidc-auth-request-extra-params=connector_id=my-id")) + g.Expect(args).To(ContainElement(HavePrefix("--token-cache-dir="))) + g.Expect(args).To(ContainElement(ContainSubstring("my-id"))) +} + +func TestBuildKubeloginArgs_ConnectorIDInMultiParams(t *testing.T) { + g := NewWithT(t) + + cfg := map[string]string{ + "idp-issuer-url": "https://issuer.example.com", + "client-id": "cid", + "auth-request-extra-params": "foo=bar,connector_id=another-id,baz=qux", + } + + args := buildKubeloginArgs(cfg, nil, "/custom/cache") + + g.Expect(args).To(ContainElement("--oidc-auth-request-extra-params=foo=bar,connector_id=another-id,baz=qux")) + g.Expect(args).To(ContainElement("--token-cache-dir=/custom/cache/another-id")) +} + func TestAuthInfoEqual_ExecBased(t *testing.T) { g := NewWithT(t)