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
oidc getKubeconfigOIDCParams
concierge getKubeconfigConciergeParams
generatedNameSuffix string
}
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.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")
mustMarkHidden(cmd, "oidc-debug-session-cache")
@ -178,15 +180,23 @@ func runGetKubeconfig(ctx context.Context, out io.Writer, deps kubeconfigDeps, f
if err != nil {
return fmt.Errorf("could not load --kubeconfig: %w", err)
}
cluster, err := copyCurrentClusterFromExistingKubeConfig(currentKubeConfig, flags.kubeconfigContextOverride)
currentKubeconfigNames, err := getCurrentContext(currentKubeConfig, flags)
if err != nil {
return fmt.Errorf("could not load --kubeconfig/--kubeconfig-context: %w", err)
}
cluster := currentKubeConfig.Clusters[currentKubeconfigNames.ClusterName]
clientset, err := deps.getClientset(clientConfig, flags.concierge.apiGroupSuffix)
if err != nil {
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 {
credentialIssuer, err := waitForCredentialIssuer(ctx, clientset, flags, deps)
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)
}
kubeconfig := newExecKubeconfig(cluster, &execConfig)
kubeconfig := newExecKubeconfig(cluster, &execConfig, newKubeconfigNames)
if err := validateKubeconfig(ctx, flags, kubeconfig, deps.log); err != nil {
return err
}
@ -271,13 +281,33 @@ func runGetKubeconfig(ctx context.Context, out io.Writer, deps kubeconfigDeps, f
if 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 {
return err
}
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) {
credentialIssuer, err := lookupCredentialIssuer(clientset, flags.concierge.credentialIssuer, deps.log)
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())
}
func newExecKubeconfig(cluster *clientcmdapi.Cluster, execConfig *clientcmdapi.ExecConfig) clientcmdapi.Config {
const name = "pinniped"
func newExecKubeconfig(cluster *clientcmdapi.Cluster, execConfig *clientcmdapi.ExecConfig, newNames *kubeconfigNames) clientcmdapi.Config {
return clientcmdapi.Config{
Kind: "Config",
APIVersion: clientcmdapi.SchemeGroupVersion.Version,
Clusters: map[string]*clientcmdapi.Cluster{name: cluster},
AuthInfos: map[string]*clientcmdapi.AuthInfo{name: {Exec: execConfig}},
Contexts: map[string]*clientcmdapi.Context{name: {Cluster: name, AuthInfo: name}},
CurrentContext: name,
Clusters: map[string]*clientcmdapi.Cluster{newNames.ClusterName: cluster},
AuthInfos: map[string]*clientcmdapi.AuthInfo{newNames.UserName: {Exec: execConfig}},
Contexts: map[string]*clientcmdapi.Context{newNames.ContextName: {Cluster: newNames.ClusterName, AuthInfo: newNames.UserName}},
CurrentContext: newNames.ContextName,
}
}
@ -560,18 +589,6 @@ func writeConfigAsYAML(out io.Writer, config clientcmdapi.Config) error {
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 {
if flags.skipValidate {
return nil

View File

@ -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)
--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
--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{
"--kubeconfig", "./testdata/kubeconfig.yaml",
"--kubeconfig-context", "invalid",
@ -143,6 +144,28 @@ func TestGetKubeconfig(t *testing.T) {
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",
args: []string{
@ -584,17 +607,17 @@ func TestGetKubeconfig(t *testing.T) {
- cluster:
certificate-authority-data: ZmFrZS1jZXJ0aWZpY2F0ZS1hdXRob3JpdHktZGF0YS12YWx1ZQ==
server: https://fake-server-url-value
name: pinniped
name: kind-cluster-pinniped
contexts:
- context:
cluster: pinniped
user: pinniped
name: pinniped
current-context: pinniped
cluster: kind-cluster-pinniped
user: kind-user-pinniped
name: kind-context-pinniped
current-context: kind-context-pinniped
kind: Config
preferences: {}
users:
- name: pinniped
- name: kind-user-pinniped
user:
exec:
apiVersion: client.authentication.k8s.io/v1beta1
@ -653,17 +676,17 @@ func TestGetKubeconfig(t *testing.T) {
- cluster:
certificate-authority-data: ZmFrZS1jZXJ0aWZpY2F0ZS1hdXRob3JpdHktZGF0YS12YWx1ZQ==
server: https://fake-server-url-value
name: pinniped
name: kind-cluster-pinniped
contexts:
- context:
cluster: pinniped
user: pinniped
name: pinniped
current-context: pinniped
cluster: kind-cluster-pinniped
user: kind-user-pinniped
name: kind-context-pinniped
current-context: kind-context-pinniped
kind: Config
preferences: {}
users:
- name: pinniped
- name: kind-user-pinniped
user:
exec:
apiVersion: client.authentication.k8s.io/v1beta1
@ -733,17 +756,17 @@ func TestGetKubeconfig(t *testing.T) {
- cluster:
certificate-authority-data: ZmFrZS1jZXJ0aWZpY2F0ZS1hdXRob3JpdHktZGF0YS12YWx1ZQ==
server: https://fake-server-url-value
name: pinniped
name: kind-cluster-pinniped
contexts:
- context:
cluster: pinniped
user: pinniped
name: pinniped
current-context: pinniped
cluster: kind-cluster-pinniped
user: kind-user-pinniped
name: kind-context-pinniped
current-context: kind-context-pinniped
kind: Config
preferences: {}
users:
- name: pinniped
- name: kind-user-pinniped
user:
exec:
apiVersion: client.authentication.k8s.io/v1beta1
@ -785,6 +808,7 @@ func TestGetKubeconfig(t *testing.T) {
"--oidc-debug-session-cache",
"--oidc-request-audience", "test-audience",
"--skip-validation",
"--generated-name-suffix", "-sso",
},
conciergeObjects: []runtime.Object{
&configv1alpha1.CredentialIssuer{
@ -815,17 +839,17 @@ func TestGetKubeconfig(t *testing.T) {
- cluster:
certificate-authority-data: %s
server: https://explicit-concierge-endpoint.example.com
name: pinniped
name: kind-cluster-sso
contexts:
- context:
cluster: pinniped
user: pinniped
name: pinniped
current-context: pinniped
cluster: kind-cluster-sso
user: kind-user-sso
name: kind-context-sso
current-context: kind-context-sso
kind: Config
preferences: {}
users:
- name: pinniped
- name: kind-user-sso
user:
exec:
apiVersion: client.authentication.k8s.io/v1beta1
@ -929,17 +953,17 @@ func TestGetKubeconfig(t *testing.T) {
- cluster:
certificate-authority-data: %s
server: https://impersonation-proxy-endpoint.test
name: pinniped
name: kind-cluster-pinniped
contexts:
- context:
cluster: pinniped
user: pinniped
name: pinniped
current-context: pinniped
cluster: kind-cluster-pinniped
user: kind-user-pinniped
name: kind-context-pinniped
current-context: kind-context-pinniped
kind: Config
preferences: {}
users:
- name: pinniped
- name: kind-user-pinniped
user:
exec:
apiVersion: client.authentication.k8s.io/v1beta1
@ -1035,17 +1059,17 @@ func TestGetKubeconfig(t *testing.T) {
- cluster:
certificate-authority-data: dGVzdC1jb25jaWVyZ2UtY2E=
server: https://impersonation-proxy-endpoint.test
name: pinniped
name: kind-cluster-pinniped
contexts:
- context:
cluster: pinniped
user: pinniped
name: pinniped
current-context: pinniped
cluster: kind-cluster-pinniped
user: kind-user-pinniped
name: kind-context-pinniped
current-context: kind-context-pinniped
kind: Config
preferences: {}
users:
- name: pinniped
- name: kind-user-pinniped
user:
exec:
apiVersion: client.authentication.k8s.io/v1beta1

View File

@ -3,25 +3,33 @@ clusters:
- cluster:
certificate-authority-data: ZmFrZS1jZXJ0aWZpY2F0ZS1hdXRob3JpdHktZGF0YS12YWx1ZQ== # fake-certificate-authority-data-value
server: https://fake-server-url-value
name: kind-kind
name: kind-cluster
- cluster:
certificate-authority-data: c29tZS1vdGhlci1mYWtlLWNlcnRpZmljYXRlLWF1dGhvcml0eS1kYXRhLXZhbHVl # some-other-fake-certificate-authority-data-value
server: https://some-other-fake-server-url-value
name: some-other-cluster
contexts:
- context:
cluster: kind-kind
user: kind-kind
name: kind-kind
cluster: kind-cluster
user: kind-user
name: kind-context
- context:
cluster: some-other-cluster
user: some-other-user
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
preferences: {}
users:
- name: kind-kind
- name: kind-user
user:
client-certificate-data: ZmFrZS1jbGllbnQtY2VydGlmaWNhdGUtZGF0YS12YWx1ZQ== # fake-client-certificate-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(`
Current cluster info:
Name: kind-kind
Name: kind-cluster
URL: https://fake-server-url-value
Current user info:
@ -68,7 +68,7 @@ func TestWhoami(t *testing.T) {
wantStdout: here.Doc(`
Current cluster info:
Name: kind-kind
Name: kind-cluster
URL: https://fake-server-url-value
Current user info:
@ -84,7 +84,7 @@ func TestWhoami(t *testing.T) {
wantStdout: here.Doc(`
Current cluster info:
Name: kind-kind
Name: kind-cluster
URL: https://fake-server-url-value
Current user info:
@ -100,7 +100,7 @@ func TestWhoami(t *testing.T) {
wantStdout: here.Doc(`
Current cluster info:
Name: kind-kind
Name: kind-cluster
URL: https://fake-server-url-value
Current user info:
@ -209,12 +209,12 @@ func TestWhoami(t *testing.T) {
name: "different kubeconfig context, but same as current",
args: []string{
"--kubeconfig", "./testdata/kubeconfig.yaml",
"--kubeconfig-context", "kind-kind",
"--kubeconfig-context", "kind-context",
},
wantStdout: here.Doc(`
Current cluster info:
Name: kind-kind
Name: kind-cluster
URL: https://fake-server-url-value
Current user info: