Generate more helpful context/cluster/user names in pinniped get kubeconfig

Before this change, the "context", "cluster", and "user" fields in generated kubeconfig YAML were always hardcoded to "pinniped". This could be confusing if you generated many kubeconfigs for different clusters.

After this change, the fields will be copied from their names in the original kubeconfig, suffixed with "-pinniped". This suffix can be overridden by setting the new `--generated-name-suffix` CLI flag.

The goal of this change is that you can distinguish between kubeconfigs generated for different clusters, as well as being able to distinguish between the Pinniped and original (admin) kubeconfigs for a cluster.

Signed-off-by: Matt Moyer <moyerm@vmware.com>
This commit is contained in:
Matt Moyer 2021-04-05 11:41:06 -05:00
parent 5add31d263
commit 4e25bcd4b2
No known key found for this signature in database
GPG Key ID: EAE88AD172C5AE2D
4 changed files with 119 additions and 70 deletions

View File

@ -86,6 +86,7 @@ type getKubeconfigParams struct {
staticTokenEnvName string staticTokenEnvName string
oidc getKubeconfigOIDCParams oidc getKubeconfigOIDCParams
concierge getKubeconfigConciergeParams concierge getKubeconfigConciergeParams
generatedNameSuffix string
} }
func kubeconfigCommand(deps kubeconfigDeps) *cobra.Command { func kubeconfigCommand(deps kubeconfigDeps) *cobra.Command {
@ -130,6 +131,7 @@ func kubeconfigCommand(deps kubeconfigDeps) *cobra.Command {
f.BoolVar(&flags.skipValidate, "skip-validation", false, "Skip final validation of the kubeconfig (default: false)") f.BoolVar(&flags.skipValidate, "skip-validation", false, "Skip final validation of the kubeconfig (default: false)")
f.DurationVar(&flags.timeout, "timeout", 10*time.Minute, "Timeout for autodiscovery and validation") 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.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")
mustMarkHidden(cmd, "oidc-debug-session-cache") mustMarkHidden(cmd, "oidc-debug-session-cache")
@ -178,15 +180,23 @@ func runGetKubeconfig(ctx context.Context, out io.Writer, deps kubeconfigDeps, f
if err != nil { if err != nil {
return fmt.Errorf("could not load --kubeconfig: %w", err) return fmt.Errorf("could not load --kubeconfig: %w", err)
} }
cluster, err := copyCurrentClusterFromExistingKubeConfig(currentKubeConfig, flags.kubeconfigContextOverride) currentKubeconfigNames, err := getCurrentContext(currentKubeConfig, flags)
if err != nil { if err != nil {
return fmt.Errorf("could not load --kubeconfig/--kubeconfig-context: %w", err) return fmt.Errorf("could not load --kubeconfig/--kubeconfig-context: %w", err)
} }
cluster := currentKubeConfig.Clusters[currentKubeconfigNames.ClusterName]
clientset, err := deps.getClientset(clientConfig, flags.concierge.apiGroupSuffix) clientset, err := deps.getClientset(clientConfig, flags.concierge.apiGroupSuffix)
if err != nil { if err != nil {
return fmt.Errorf("could not configure Kubernetes client: %w", err) return fmt.Errorf("could not configure Kubernetes client: %w", err)
} }
// Generate the new context/cluster/user names by appending the --generated-name-suffix to the original values.
newKubeconfigNames := &kubeconfigNames{
ContextName: currentKubeconfigNames.ContextName + flags.generatedNameSuffix,
UserName: currentKubeconfigNames.UserName + flags.generatedNameSuffix,
ClusterName: currentKubeconfigNames.ClusterName + flags.generatedNameSuffix,
}
if !flags.concierge.disabled { if !flags.concierge.disabled {
credentialIssuer, err := waitForCredentialIssuer(ctx, clientset, flags, deps) credentialIssuer, err := waitForCredentialIssuer(ctx, clientset, flags, deps)
if err != nil { if err != nil {
@ -236,7 +246,7 @@ func runGetKubeconfig(ctx context.Context, out io.Writer, deps kubeconfigDeps, f
execConfig.Args = append(execConfig.Args, "--token-env="+flags.staticTokenEnvName) execConfig.Args = append(execConfig.Args, "--token-env="+flags.staticTokenEnvName)
} }
kubeconfig := newExecKubeconfig(cluster, &execConfig) kubeconfig := newExecKubeconfig(cluster, &execConfig, newKubeconfigNames)
if err := validateKubeconfig(ctx, flags, kubeconfig, deps.log); err != nil { if err := validateKubeconfig(ctx, flags, kubeconfig, deps.log); err != nil {
return err return err
} }
@ -271,13 +281,33 @@ func runGetKubeconfig(ctx context.Context, out io.Writer, deps kubeconfigDeps, f
if flags.oidc.requestAudience != "" { if flags.oidc.requestAudience != "" {
execConfig.Args = append(execConfig.Args, "--request-audience="+flags.oidc.requestAudience) execConfig.Args = append(execConfig.Args, "--request-audience="+flags.oidc.requestAudience)
} }
kubeconfig := newExecKubeconfig(cluster, &execConfig) kubeconfig := newExecKubeconfig(cluster, &execConfig, newKubeconfigNames)
if err := validateKubeconfig(ctx, flags, kubeconfig, deps.log); err != nil { if err := validateKubeconfig(ctx, flags, kubeconfig, deps.log); err != nil {
return err return err
} }
return writeConfigAsYAML(out, kubeconfig) return writeConfigAsYAML(out, kubeconfig)
} }
type kubeconfigNames struct{ ContextName, UserName, ClusterName string }
func getCurrentContext(currentKubeConfig clientcmdapi.Config, flags getKubeconfigParams) (*kubeconfigNames, error) {
contextName := currentKubeConfig.CurrentContext
if flags.kubeconfigContextOverride != "" {
contextName = flags.kubeconfigContextOverride
}
ctx := currentKubeConfig.Contexts[contextName]
if ctx == nil {
return nil, fmt.Errorf("no such context %q", contextName)
}
if _, exists := currentKubeConfig.Clusters[ctx.Cluster]; !exists {
return nil, fmt.Errorf("no such cluster %q", ctx.Cluster)
}
if _, exists := currentKubeConfig.AuthInfos[ctx.AuthInfo]; !exists {
return nil, fmt.Errorf("no such user %q", ctx.AuthInfo)
}
return &kubeconfigNames{ContextName: contextName, UserName: ctx.AuthInfo, ClusterName: ctx.Cluster}, nil
}
func waitForCredentialIssuer(ctx context.Context, clientset conciergeclientset.Interface, flags getKubeconfigParams, deps kubeconfigDeps) (*configv1alpha1.CredentialIssuer, error) { func waitForCredentialIssuer(ctx context.Context, clientset conciergeclientset.Interface, flags getKubeconfigParams, deps kubeconfigDeps) (*configv1alpha1.CredentialIssuer, error) {
credentialIssuer, err := lookupCredentialIssuer(clientset, flags.concierge.credentialIssuer, deps.log) credentialIssuer, err := lookupCredentialIssuer(clientset, flags.concierge.credentialIssuer, deps.log)
if err != nil { if err != nil {
@ -461,15 +491,14 @@ func getConciergeFrontend(credentialIssuer *configv1alpha1.CredentialIssuer, mod
return nil, fmt.Errorf("could not find successful Concierge strategy matching --concierge-mode=%s", mode.String()) return nil, fmt.Errorf("could not find successful Concierge strategy matching --concierge-mode=%s", mode.String())
} }
func newExecKubeconfig(cluster *clientcmdapi.Cluster, execConfig *clientcmdapi.ExecConfig) clientcmdapi.Config { func newExecKubeconfig(cluster *clientcmdapi.Cluster, execConfig *clientcmdapi.ExecConfig, newNames *kubeconfigNames) clientcmdapi.Config {
const name = "pinniped"
return clientcmdapi.Config{ return clientcmdapi.Config{
Kind: "Config", Kind: "Config",
APIVersion: clientcmdapi.SchemeGroupVersion.Version, APIVersion: clientcmdapi.SchemeGroupVersion.Version,
Clusters: map[string]*clientcmdapi.Cluster{name: cluster}, Clusters: map[string]*clientcmdapi.Cluster{newNames.ClusterName: cluster},
AuthInfos: map[string]*clientcmdapi.AuthInfo{name: {Exec: execConfig}}, AuthInfos: map[string]*clientcmdapi.AuthInfo{newNames.UserName: {Exec: execConfig}},
Contexts: map[string]*clientcmdapi.Context{name: {Cluster: name, AuthInfo: name}}, Contexts: map[string]*clientcmdapi.Context{newNames.ContextName: {Cluster: newNames.ClusterName, AuthInfo: newNames.UserName}},
CurrentContext: name, CurrentContext: newNames.ContextName,
} }
} }
@ -560,18 +589,6 @@ func writeConfigAsYAML(out io.Writer, config clientcmdapi.Config) error {
return nil return nil
} }
func copyCurrentClusterFromExistingKubeConfig(currentKubeConfig clientcmdapi.Config, currentContextNameOverride string) (*clientcmdapi.Cluster, error) {
contextName := currentKubeConfig.CurrentContext
if currentContextNameOverride != "" {
contextName = currentContextNameOverride
}
ctx := currentKubeConfig.Contexts[contextName]
if ctx == nil {
return nil, fmt.Errorf("no such context %q", contextName)
}
return currentKubeConfig.Clusters[ctx.Cluster], nil
}
func validateKubeconfig(ctx context.Context, flags getKubeconfigParams, kubeconfig clientcmdapi.Config, log logr.Logger) error { func validateKubeconfig(ctx context.Context, flags getKubeconfigParams, kubeconfig clientcmdapi.Config, log logr.Logger) error {
if flags.skipValidate { if flags.skipValidate {
return nil return nil

View File

@ -73,6 +73,7 @@ func TestGetKubeconfig(t *testing.T) {
--concierge-endpoint string API base for the Concierge endpoint --concierge-endpoint string API base for the Concierge endpoint
--concierge-mode mode Concierge mode of operation (default TokenCredentialRequestAPI) --concierge-mode mode Concierge mode of operation (default TokenCredentialRequestAPI)
--concierge-skip-wait Skip waiting for any pending Concierge strategies to become ready (default: false) --concierge-skip-wait Skip waiting for any pending Concierge strategies to become ready (default: false)
--generated-name-suffix string Suffix to append to generated cluster, context, user kubeconfig entries (default "-pinniped")
-h, --help help for kubeconfig -h, --help help for kubeconfig
--kubeconfig string Path to kubeconfig file --kubeconfig string Path to kubeconfig file
--kubeconfig-context string Kubeconfig context name (default: current active context) --kubeconfig-context string Kubeconfig context name (default: current active context)
@ -133,7 +134,7 @@ func TestGetKubeconfig(t *testing.T) {
`), `),
}, },
{ {
name: "invalid kubeconfig context", name: "invalid kubeconfig context, missing",
args: []string{ args: []string{
"--kubeconfig", "./testdata/kubeconfig.yaml", "--kubeconfig", "./testdata/kubeconfig.yaml",
"--kubeconfig-context", "invalid", "--kubeconfig-context", "invalid",
@ -143,6 +144,28 @@ func TestGetKubeconfig(t *testing.T) {
Error: could not load --kubeconfig/--kubeconfig-context: no such context "invalid" Error: could not load --kubeconfig/--kubeconfig-context: no such context "invalid"
`), `),
}, },
{
name: "invalid kubeconfig context, missing cluster",
args: []string{
"--kubeconfig", "./testdata/kubeconfig.yaml",
"--kubeconfig-context", "invalid-context-no-such-cluster",
},
wantError: true,
wantStderr: here.Doc(`
Error: could not load --kubeconfig/--kubeconfig-context: no such cluster "invalid-cluster"
`),
},
{
name: "invalid kubeconfig context, missing user",
args: []string{
"--kubeconfig", "./testdata/kubeconfig.yaml",
"--kubeconfig-context", "invalid-context-no-such-user",
},
wantError: true,
wantStderr: here.Doc(`
Error: could not load --kubeconfig/--kubeconfig-context: no such user "invalid-user"
`),
},
{ {
name: "clientset creation failure", name: "clientset creation failure",
args: []string{ args: []string{
@ -584,17 +607,17 @@ func TestGetKubeconfig(t *testing.T) {
- cluster: - cluster:
certificate-authority-data: ZmFrZS1jZXJ0aWZpY2F0ZS1hdXRob3JpdHktZGF0YS12YWx1ZQ== certificate-authority-data: ZmFrZS1jZXJ0aWZpY2F0ZS1hdXRob3JpdHktZGF0YS12YWx1ZQ==
server: https://fake-server-url-value server: https://fake-server-url-value
name: pinniped name: kind-cluster-pinniped
contexts: contexts:
- context: - context:
cluster: pinniped cluster: kind-cluster-pinniped
user: pinniped user: kind-user-pinniped
name: pinniped name: kind-context-pinniped
current-context: pinniped current-context: kind-context-pinniped
kind: Config kind: Config
preferences: {} preferences: {}
users: users:
- name: pinniped - name: kind-user-pinniped
user: user:
exec: exec:
apiVersion: client.authentication.k8s.io/v1beta1 apiVersion: client.authentication.k8s.io/v1beta1
@ -653,17 +676,17 @@ func TestGetKubeconfig(t *testing.T) {
- cluster: - cluster:
certificate-authority-data: ZmFrZS1jZXJ0aWZpY2F0ZS1hdXRob3JpdHktZGF0YS12YWx1ZQ== certificate-authority-data: ZmFrZS1jZXJ0aWZpY2F0ZS1hdXRob3JpdHktZGF0YS12YWx1ZQ==
server: https://fake-server-url-value server: https://fake-server-url-value
name: pinniped name: kind-cluster-pinniped
contexts: contexts:
- context: - context:
cluster: pinniped cluster: kind-cluster-pinniped
user: pinniped user: kind-user-pinniped
name: pinniped name: kind-context-pinniped
current-context: pinniped current-context: kind-context-pinniped
kind: Config kind: Config
preferences: {} preferences: {}
users: users:
- name: pinniped - name: kind-user-pinniped
user: user:
exec: exec:
apiVersion: client.authentication.k8s.io/v1beta1 apiVersion: client.authentication.k8s.io/v1beta1
@ -733,17 +756,17 @@ func TestGetKubeconfig(t *testing.T) {
- cluster: - cluster:
certificate-authority-data: ZmFrZS1jZXJ0aWZpY2F0ZS1hdXRob3JpdHktZGF0YS12YWx1ZQ== certificate-authority-data: ZmFrZS1jZXJ0aWZpY2F0ZS1hdXRob3JpdHktZGF0YS12YWx1ZQ==
server: https://fake-server-url-value server: https://fake-server-url-value
name: pinniped name: kind-cluster-pinniped
contexts: contexts:
- context: - context:
cluster: pinniped cluster: kind-cluster-pinniped
user: pinniped user: kind-user-pinniped
name: pinniped name: kind-context-pinniped
current-context: pinniped current-context: kind-context-pinniped
kind: Config kind: Config
preferences: {} preferences: {}
users: users:
- name: pinniped - name: kind-user-pinniped
user: user:
exec: exec:
apiVersion: client.authentication.k8s.io/v1beta1 apiVersion: client.authentication.k8s.io/v1beta1
@ -785,6 +808,7 @@ func TestGetKubeconfig(t *testing.T) {
"--oidc-debug-session-cache", "--oidc-debug-session-cache",
"--oidc-request-audience", "test-audience", "--oidc-request-audience", "test-audience",
"--skip-validation", "--skip-validation",
"--generated-name-suffix", "-sso",
}, },
conciergeObjects: []runtime.Object{ conciergeObjects: []runtime.Object{
&configv1alpha1.CredentialIssuer{ &configv1alpha1.CredentialIssuer{
@ -815,17 +839,17 @@ func TestGetKubeconfig(t *testing.T) {
- cluster: - cluster:
certificate-authority-data: %s certificate-authority-data: %s
server: https://explicit-concierge-endpoint.example.com server: https://explicit-concierge-endpoint.example.com
name: pinniped name: kind-cluster-sso
contexts: contexts:
- context: - context:
cluster: pinniped cluster: kind-cluster-sso
user: pinniped user: kind-user-sso
name: pinniped name: kind-context-sso
current-context: pinniped current-context: kind-context-sso
kind: Config kind: Config
preferences: {} preferences: {}
users: users:
- name: pinniped - name: kind-user-sso
user: user:
exec: exec:
apiVersion: client.authentication.k8s.io/v1beta1 apiVersion: client.authentication.k8s.io/v1beta1
@ -929,17 +953,17 @@ func TestGetKubeconfig(t *testing.T) {
- cluster: - cluster:
certificate-authority-data: %s certificate-authority-data: %s
server: https://impersonation-proxy-endpoint.test server: https://impersonation-proxy-endpoint.test
name: pinniped name: kind-cluster-pinniped
contexts: contexts:
- context: - context:
cluster: pinniped cluster: kind-cluster-pinniped
user: pinniped user: kind-user-pinniped
name: pinniped name: kind-context-pinniped
current-context: pinniped current-context: kind-context-pinniped
kind: Config kind: Config
preferences: {} preferences: {}
users: users:
- name: pinniped - name: kind-user-pinniped
user: user:
exec: exec:
apiVersion: client.authentication.k8s.io/v1beta1 apiVersion: client.authentication.k8s.io/v1beta1
@ -1035,17 +1059,17 @@ func TestGetKubeconfig(t *testing.T) {
- cluster: - cluster:
certificate-authority-data: dGVzdC1jb25jaWVyZ2UtY2E= certificate-authority-data: dGVzdC1jb25jaWVyZ2UtY2E=
server: https://impersonation-proxy-endpoint.test server: https://impersonation-proxy-endpoint.test
name: pinniped name: kind-cluster-pinniped
contexts: contexts:
- context: - context:
cluster: pinniped cluster: kind-cluster-pinniped
user: pinniped user: kind-user-pinniped
name: pinniped name: kind-context-pinniped
current-context: pinniped current-context: kind-context-pinniped
kind: Config kind: Config
preferences: {} preferences: {}
users: users:
- name: pinniped - name: kind-user-pinniped
user: user:
exec: exec:
apiVersion: client.authentication.k8s.io/v1beta1 apiVersion: client.authentication.k8s.io/v1beta1

View File

@ -3,25 +3,33 @@ clusters:
- cluster: - cluster:
certificate-authority-data: ZmFrZS1jZXJ0aWZpY2F0ZS1hdXRob3JpdHktZGF0YS12YWx1ZQ== # fake-certificate-authority-data-value certificate-authority-data: ZmFrZS1jZXJ0aWZpY2F0ZS1hdXRob3JpdHktZGF0YS12YWx1ZQ== # fake-certificate-authority-data-value
server: https://fake-server-url-value server: https://fake-server-url-value
name: kind-kind name: kind-cluster
- cluster: - cluster:
certificate-authority-data: c29tZS1vdGhlci1mYWtlLWNlcnRpZmljYXRlLWF1dGhvcml0eS1kYXRhLXZhbHVl # some-other-fake-certificate-authority-data-value certificate-authority-data: c29tZS1vdGhlci1mYWtlLWNlcnRpZmljYXRlLWF1dGhvcml0eS1kYXRhLXZhbHVl # some-other-fake-certificate-authority-data-value
server: https://some-other-fake-server-url-value server: https://some-other-fake-server-url-value
name: some-other-cluster name: some-other-cluster
contexts: contexts:
- context: - context:
cluster: kind-kind cluster: kind-cluster
user: kind-kind user: kind-user
name: kind-kind name: kind-context
- context: - context:
cluster: some-other-cluster cluster: some-other-cluster
user: some-other-user user: some-other-user
name: some-other-context name: some-other-context
current-context: kind-kind - context:
cluster: invalid-cluster
user: some-other-user
name: invalid-context-no-such-cluster
- context:
cluster: some-other-cluster
user: invalid-user
name: invalid-context-no-such-user
current-context: kind-context
kind: Config kind: Config
preferences: {} preferences: {}
users: users:
- name: kind-kind - name: kind-user
user: user:
client-certificate-data: ZmFrZS1jbGllbnQtY2VydGlmaWNhdGUtZGF0YS12YWx1ZQ== # fake-client-certificate-data-value client-certificate-data: ZmFrZS1jbGllbnQtY2VydGlmaWNhdGUtZGF0YS12YWx1ZQ== # fake-client-certificate-data-value
client-key-data: ZmFrZS1jbGllbnQta2V5LWRhdGEtdmFsdWU= # fake-client-key-data-value client-key-data: ZmFrZS1jbGllbnQta2V5LWRhdGEtdmFsdWU= # fake-client-key-data-value

View File

@ -53,7 +53,7 @@ func TestWhoami(t *testing.T) {
wantStdout: here.Doc(` wantStdout: here.Doc(`
Current cluster info: Current cluster info:
Name: kind-kind Name: kind-cluster
URL: https://fake-server-url-value URL: https://fake-server-url-value
Current user info: Current user info:
@ -68,7 +68,7 @@ func TestWhoami(t *testing.T) {
wantStdout: here.Doc(` wantStdout: here.Doc(`
Current cluster info: Current cluster info:
Name: kind-kind Name: kind-cluster
URL: https://fake-server-url-value URL: https://fake-server-url-value
Current user info: Current user info:
@ -84,7 +84,7 @@ func TestWhoami(t *testing.T) {
wantStdout: here.Doc(` wantStdout: here.Doc(`
Current cluster info: Current cluster info:
Name: kind-kind Name: kind-cluster
URL: https://fake-server-url-value URL: https://fake-server-url-value
Current user info: Current user info:
@ -100,7 +100,7 @@ func TestWhoami(t *testing.T) {
wantStdout: here.Doc(` wantStdout: here.Doc(`
Current cluster info: Current cluster info:
Name: kind-kind Name: kind-cluster
URL: https://fake-server-url-value URL: https://fake-server-url-value
Current user info: Current user info:
@ -209,12 +209,12 @@ func TestWhoami(t *testing.T) {
name: "different kubeconfig context, but same as current", name: "different kubeconfig context, but same as current",
args: []string{ args: []string{
"--kubeconfig", "./testdata/kubeconfig.yaml", "--kubeconfig", "./testdata/kubeconfig.yaml",
"--kubeconfig-context", "kind-kind", "--kubeconfig-context", "kind-context",
}, },
wantStdout: here.Doc(` wantStdout: here.Doc(`
Current cluster info: Current cluster info:
Name: kind-kind Name: kind-cluster
URL: https://fake-server-url-value URL: https://fake-server-url-value
Current user info: Current user info: