diff --git a/docs/kubectl-config-set-cluster.md b/docs/kubectl-config-set-cluster.md index 59d2f363ed..2138d6d5da 100644 --- a/docs/kubectl-config-set-cluster.md +++ b/docs/kubectl-config-set-cluster.md @@ -7,7 +7,7 @@ Sets a cluster entry in .kubeconfig ``` Sets a cluster entry in .kubeconfig Specifying a name that already exists will merge new fields on top of existing values for those fields. - e.g. + e.g. kubectl config set-cluster e2e --certificate-authority=~/.kube/e2e/.kubernetes.ca.cert only sets the certificate-authority field on the e2e cluster entry without touching other values. @@ -15,6 +15,12 @@ Sets a cluster entry in .kubeconfig kubectl config set-cluster name [--server=server] [--certificate-authority=path/to/certficate/authority] [--api-version=apiversion] [--insecure-skip-tls-verify=true] +### Options + +``` + --embed-certs=false: embed-certs for the cluster entry in .kubeconfig +``` + ### Options inherrited from parent commands ``` diff --git a/docs/kubectl-config-set-credentials.md b/docs/kubectl-config-set-credentials.md index cf9de7eee4..0c23a4f020 100644 --- a/docs/kubectl-config-set-credentials.md +++ b/docs/kubectl-config-set-credentials.md @@ -28,6 +28,12 @@ Sets a user entry in .kubeconfig kubectl config set-credentials name [--auth-path=authfile] [--client-certificate=certfile] [--client-key=keyfile] [--token=bearer_token] [--username=basic_user] [--password=basic_password] +### Options + +``` + --embed-certs=false: embed client cert/key for the user entry in .kubeconfig +``` + ### Options inherrited from parent commands ``` diff --git a/docs/man/man1/kubectl-config-set-cluster.1 b/docs/man/man1/kubectl-config-set-cluster.1 index cc910db667..e6106ddf15 100644 --- a/docs/man/man1/kubectl-config-set-cluster.1 +++ b/docs/man/man1/kubectl-config-set-cluster.1 @@ -20,6 +20,12 @@ Sets a cluster entry in .kubeconfig only sets the certificate\-authority field on the e2e cluster entry without touching other values. +.SH OPTIONS +.PP +\fB\-\-embed\-certs\fP=false + embed\-certs for the cluster entry in .kubeconfig + + .SH OPTIONS INHERITED FROM PARENT COMMANDS .PP \fB\-\-alsologtostderr\fP=false diff --git a/docs/man/man1/kubectl-config-set-credentials.1 b/docs/man/man1/kubectl-config-set-credentials.1 index 8d67f146df..b2b62dd32f 100644 --- a/docs/man/man1/kubectl-config-set-credentials.1 +++ b/docs/man/man1/kubectl-config-set-credentials.1 @@ -45,6 +45,12 @@ Basic auth flags: Bearer token and basic auth are mutually exclusive. +.SH OPTIONS +.PP +\fB\-\-embed\-certs\fP=false + embed client cert/key for the user entry in .kubeconfig + + .SH OPTIONS INHERITED FROM PARENT COMMANDS .PP \fB\-\-alsologtostderr\fP=false diff --git a/pkg/client/clientcmd/overrides.go b/pkg/client/clientcmd/overrides.go index 0b2e01f3be..160444bf1a 100644 --- a/pkg/client/clientcmd/overrides.go +++ b/pkg/client/clientcmd/overrides.go @@ -81,6 +81,7 @@ const ( FlagCertFile = "client-certificate" FlagKeyFile = "client-key" FlagCAFile = "certificate-authority" + FlagEmbedCerts = "embed-certs" FlagBearerToken = "token" FlagUsername = "username" FlagPassword = "password" diff --git a/pkg/kubectl/cmd/config/config_test.go b/pkg/kubectl/cmd/config/config_test.go index 3be8f0ab1c..a528ae8aa2 100644 --- a/pkg/kubectl/cmd/config/config_test.go +++ b/pkg/kubectl/cmd/config/config_test.go @@ -167,6 +167,56 @@ func TestAdditionalAuth(t *testing.T) { test.run(t) } +func TestEmbedClientCert(t *testing.T) { + fakeCertFile, _ := ioutil.TempFile("", "") + defer os.Remove(fakeCertFile.Name()) + fakeData := []byte("fake-data") + ioutil.WriteFile(fakeCertFile.Name(), fakeData, 0600) + expectedConfig := newRedFederalCowHammerConfig() + authInfo := clientcmdapi.NewAuthInfo() + authInfo.ClientCertificateData = fakeData + expectedConfig.AuthInfos["another-user"] = *authInfo + + test := configCommandTest{ + args: []string{"set-credentials", "another-user", "--" + clientcmd.FlagCertFile + "=" + fakeCertFile.Name(), "--" + clientcmd.FlagEmbedCerts + "=true"}, + startingConfig: newRedFederalCowHammerConfig(), + expectedConfig: expectedConfig, + } + + test.run(t) +} + +func TestEmbedClientKey(t *testing.T) { + fakeKeyFile, _ := ioutil.TempFile("", "") + defer os.Remove(fakeKeyFile.Name()) + fakeData := []byte("fake-data") + ioutil.WriteFile(fakeKeyFile.Name(), fakeData, 0600) + expectedConfig := newRedFederalCowHammerConfig() + authInfo := clientcmdapi.NewAuthInfo() + authInfo.ClientKeyData = fakeData + expectedConfig.AuthInfos["another-user"] = *authInfo + + test := configCommandTest{ + args: []string{"set-credentials", "another-user", "--" + clientcmd.FlagKeyFile + "=" + fakeKeyFile.Name(), "--" + clientcmd.FlagEmbedCerts + "=true"}, + startingConfig: newRedFederalCowHammerConfig(), + expectedConfig: expectedConfig, + } + + test.run(t) +} + +func TestEmbedNoKeyOrCertDisallowed(t *testing.T) { + expectedConfig := newRedFederalCowHammerConfig() + test := configCommandTest{ + args: []string{"set-credentials", "another-user", "--" + clientcmd.FlagEmbedCerts + "=true"}, + startingConfig: newRedFederalCowHammerConfig(), + expectedConfig: expectedConfig, + expectedOutputs: []string{"--client-certificate", "--client-key", "embed"}, + } + + test.run(t) +} + func TestEmptyTokenAndCertAllowed(t *testing.T) { expectedConfig := newRedFederalCowHammerConfig() authInfo := clientcmdapi.NewAuthInfo() @@ -375,6 +425,45 @@ func TestInsecureClearsCA(t *testing.T) { test.run(t) } +func TestCADataClearsCA(t *testing.T) { + fakeCAFile, _ := ioutil.TempFile("", "") + defer os.Remove(fakeCAFile.Name()) + fakeData := []byte("cadata") + ioutil.WriteFile(fakeCAFile.Name(), fakeData, 0600) + + clusterInfoWithCAData := clientcmdapi.NewCluster() + clusterInfoWithCAData.CertificateAuthorityData = fakeData + + clusterInfoWithCA := clientcmdapi.NewCluster() + clusterInfoWithCA.CertificateAuthority = "cafile" + + startingConfig := newRedFederalCowHammerConfig() + startingConfig.Clusters["another-cluster"] = *clusterInfoWithCA + + expectedConfig := newRedFederalCowHammerConfig() + expectedConfig.Clusters["another-cluster"] = *clusterInfoWithCAData + + test := configCommandTest{ + args: []string{"set-cluster", "another-cluster", "--" + clientcmd.FlagCAFile + "=" + fakeCAFile.Name(), "--" + clientcmd.FlagEmbedCerts + "=true"}, + startingConfig: startingConfig, + expectedConfig: expectedConfig, + } + + test.run(t) +} + +func TestEmbedNoCADisallowed(t *testing.T) { + expectedConfig := newRedFederalCowHammerConfig() + test := configCommandTest{ + args: []string{"set-cluster", "another-cluster", "--" + clientcmd.FlagEmbedCerts + "=true"}, + startingConfig: newRedFederalCowHammerConfig(), + expectedConfig: expectedConfig, + expectedOutputs: []string{"--certificate-authority", "embed"}, + } + + test.run(t) +} + func TestCAAndInsecureDisallowed(t *testing.T) { test := configCommandTest{ args: []string{"set-cluster", "another-cluster", "--" + clientcmd.FlagCAFile + "=cafile", "--" + clientcmd.FlagInsecure + "=true"}, diff --git a/pkg/kubectl/cmd/config/create_authinfo.go b/pkg/kubectl/cmd/config/create_authinfo.go index 22860f1c59..114daaefed 100644 --- a/pkg/kubectl/cmd/config/create_authinfo.go +++ b/pkg/kubectl/cmd/config/create_authinfo.go @@ -20,6 +20,7 @@ import ( "errors" "fmt" "io" + "io/ioutil" "strings" "github.com/spf13/cobra" @@ -38,6 +39,7 @@ type createAuthInfoOptions struct { token util.StringFlag username util.StringFlag password util.StringFlag + embedCertData util.BoolFlag } func NewCmdConfigSetAuthInfo(out io.Writer, pathOptions *pathOptions) *cobra.Command { @@ -78,11 +80,12 @@ func NewCmdConfigSetAuthInfo(out io.Writer, pathOptions *pathOptions) *cobra.Com } cmd.Flags().Var(&options.authPath, clientcmd.FlagAuthPath, clientcmd.FlagAuthPath+" for the user entry in .kubeconfig") - cmd.Flags().Var(&options.clientCertificate, clientcmd.FlagCertFile, clientcmd.FlagCertFile+" for the user entry in .kubeconfig") - cmd.Flags().Var(&options.clientKey, clientcmd.FlagKeyFile, clientcmd.FlagKeyFile+" for the user entry in .kubeconfig") + cmd.Flags().Var(&options.clientCertificate, clientcmd.FlagCertFile, "path to "+clientcmd.FlagCertFile+" for the user entry in .kubeconfig") + cmd.Flags().Var(&options.clientKey, clientcmd.FlagKeyFile, "path to "+clientcmd.FlagKeyFile+" for the user entry in .kubeconfig") cmd.Flags().Var(&options.token, clientcmd.FlagBearerToken, clientcmd.FlagBearerToken+" for the user entry in .kubeconfig") cmd.Flags().Var(&options.username, clientcmd.FlagUsername, clientcmd.FlagUsername+" for the user entry in .kubeconfig") cmd.Flags().Var(&options.password, clientcmd.FlagPassword, clientcmd.FlagPassword+" for the user entry in .kubeconfig") + cmd.Flags().Var(&options.embedCertData, clientcmd.FlagEmbedCerts, "embed client cert/key for the user entry in .kubeconfig") return cmd } @@ -120,15 +123,27 @@ func (o *createAuthInfoOptions) modifyAuthInfo(existingAuthInfo clientcmdapi.Aut } if o.clientCertificate.Provided() { - modifiedAuthInfo.ClientCertificate = o.clientCertificate.Value() - if len(modifiedAuthInfo.ClientCertificate) > 0 { - modifiedAuthInfo.ClientCertificateData = nil + certPath := o.clientCertificate.Value() + if o.embedCertData.Value() { + modifiedAuthInfo.ClientCertificateData, _ = ioutil.ReadFile(certPath) + modifiedAuthInfo.ClientCertificate = "" + } else { + modifiedAuthInfo.ClientCertificate = certPath + if len(modifiedAuthInfo.ClientCertificate) > 0 { + modifiedAuthInfo.ClientCertificateData = nil + } } } if o.clientKey.Provided() { - modifiedAuthInfo.ClientKey = o.clientKey.Value() - if len(modifiedAuthInfo.ClientKey) > 0 { - modifiedAuthInfo.ClientKeyData = nil + keyPath := o.clientKey.Value() + if o.embedCertData.Value() { + modifiedAuthInfo.ClientKeyData, _ = ioutil.ReadFile(keyPath) + modifiedAuthInfo.ClientKey = "" + } else { + modifiedAuthInfo.ClientKey = keyPath + if len(modifiedAuthInfo.ClientKey) > 0 { + modifiedAuthInfo.ClientKeyData = nil + } } } @@ -185,6 +200,23 @@ func (o createAuthInfoOptions) validate() error { if len(methods) > 1 { return fmt.Errorf("You cannot specify more than one authentication method at the same time: %v", strings.Join(methods, ", ")) } + if o.embedCertData.Value() { + certPath := o.clientCertificate.Value() + keyPath := o.clientKey.Value() + if certPath == "" && keyPath == "" { + return fmt.Errorf("You must specify a --%s or --%s to embed", clientcmd.FlagCertFile, clientcmd.FlagKeyFile) + } + if certPath != "" { + if _, err := ioutil.ReadFile(certPath); err != nil { + return fmt.Errorf("Error reading %s data from %s: %v", clientcmd.FlagCertFile, certPath, err) + } + } + if keyPath != "" { + if _, err := ioutil.ReadFile(keyPath); err != nil { + return fmt.Errorf("Error reading %s data from %s: %v", clientcmd.FlagKeyFile, keyPath, err) + } + } + } return nil } diff --git a/pkg/kubectl/cmd/config/create_cluster.go b/pkg/kubectl/cmd/config/create_cluster.go index a1a2bde679..558cc97365 100644 --- a/pkg/kubectl/cmd/config/create_cluster.go +++ b/pkg/kubectl/cmd/config/create_cluster.go @@ -20,6 +20,7 @@ import ( "errors" "fmt" "io" + "io/ioutil" "github.com/spf13/cobra" @@ -35,6 +36,7 @@ type createClusterOptions struct { apiVersion util.StringFlag insecureSkipTLSVerify util.BoolFlag certificateAuthority util.StringFlag + embedCAData util.BoolFlag } func NewCmdConfigSetCluster(out io.Writer, pathOptions *pathOptions) *cobra.Command { @@ -45,7 +47,7 @@ func NewCmdConfigSetCluster(out io.Writer, pathOptions *pathOptions) *cobra.Comm Short: "Sets a cluster entry in .kubeconfig", Long: `Sets a cluster entry in .kubeconfig Specifying a name that already exists will merge new fields on top of existing values for those fields. - e.g. + e.g. kubectl config set-cluster e2e --certificate-authority=~/.kube/e2e/.kubernetes.ca.cert only sets the certificate-authority field on the e2e cluster entry without touching other values. `, @@ -66,7 +68,8 @@ func NewCmdConfigSetCluster(out io.Writer, pathOptions *pathOptions) *cobra.Comm cmd.Flags().Var(&options.server, clientcmd.FlagAPIServer, clientcmd.FlagAPIServer+" for the cluster entry in .kubeconfig") cmd.Flags().Var(&options.apiVersion, clientcmd.FlagAPIVersion, clientcmd.FlagAPIVersion+" for the cluster entry in .kubeconfig") cmd.Flags().Var(&options.insecureSkipTLSVerify, clientcmd.FlagInsecure, clientcmd.FlagInsecure+" for the cluster entry in .kubeconfig") - cmd.Flags().Var(&options.certificateAuthority, clientcmd.FlagCAFile, clientcmd.FlagCAFile+" for the cluster entry in .kubeconfig") + cmd.Flags().Var(&options.certificateAuthority, clientcmd.FlagCAFile, "path to "+clientcmd.FlagCAFile+" for the cluster entry in .kubeconfig") + cmd.Flags().Var(&options.embedCAData, clientcmd.FlagEmbedCerts, clientcmd.FlagEmbedCerts+" for the cluster entry in .kubeconfig") return cmd } @@ -116,11 +119,18 @@ func (o *createClusterOptions) modifyCluster(existingCluster clientcmdapi.Cluste } } if o.certificateAuthority.Provided() { - modifiedCluster.CertificateAuthority = o.certificateAuthority.Value() - // Specifying a certificate authority file clears certificate authority data and insecure mode - if modifiedCluster.CertificateAuthority != "" { - modifiedCluster.CertificateAuthorityData = nil + caPath := o.certificateAuthority.Value() + if o.embedCAData.Value() { + modifiedCluster.CertificateAuthorityData, _ = ioutil.ReadFile(caPath) modifiedCluster.InsecureSkipTLSVerify = false + modifiedCluster.CertificateAuthority = "" + } else { + modifiedCluster.CertificateAuthority = caPath + // Specifying a certificate authority file clears certificate authority data and insecure mode + if caPath != "" { + modifiedCluster.InsecureSkipTLSVerify = false + modifiedCluster.CertificateAuthorityData = nil + } } } @@ -145,6 +155,15 @@ func (o createClusterOptions) validate() error { if o.insecureSkipTLSVerify.Value() && o.certificateAuthority.Value() != "" { return errors.New("You cannot specify a certificate authority and insecure mode at the same time") } + if o.embedCAData.Value() { + caPath := o.certificateAuthority.Value() + if caPath == "" { + return fmt.Errorf("You must specify a --%s to embed", clientcmd.FlagCAFile) + } + if _, err := ioutil.ReadFile(caPath); err != nil { + return fmt.Errorf("Could not read %s data from %s: %v", clientcmd.FlagCAFile, caPath, err) + } + } return nil }