From cf7f8acae2d7d1bf84a017d69e919dec719a151a Mon Sep 17 00:00:00 2001 From: fabriziopandini Date: Mon, 6 May 2019 16:48:24 +0200 Subject: [PATCH 1/2] renew-embedded-certs --- cmd/kubeadm/app/cmd/alpha/certs.go | 174 +++++++++--- cmd/kubeadm/app/cmd/alpha/certs_test.go | 259 +++++++++++------- .../app/phases/certs/renewal/renewal.go | 69 +++++ .../app/phases/certs/renewal/renewal_test.go | 121 ++++++++ cmd/kubeadm/app/phases/upgrade/staticpods.go | 51 +++- .../app/phases/upgrade/staticpods_test.go | 139 ++++++++-- 6 files changed, 640 insertions(+), 173 deletions(-) diff --git a/cmd/kubeadm/app/cmd/alpha/certs.go b/cmd/kubeadm/app/cmd/alpha/certs.go index f5640bfed38..fbd3b007748 100644 --- a/cmd/kubeadm/app/cmd/alpha/certs.go +++ b/cmd/kubeadm/app/cmd/alpha/certs.go @@ -24,6 +24,7 @@ import ( kubeadmapiv1beta2 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1beta2" "k8s.io/kubernetes/cmd/kubeadm/app/cmd/options" cmdutil "k8s.io/kubernetes/cmd/kubeadm/app/cmd/util" + "k8s.io/kubernetes/cmd/kubeadm/app/constants" kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants" certsphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/certs" "k8s.io/kubernetes/cmd/kubeadm/app/phases/certs/renewal" @@ -34,11 +35,17 @@ import ( ) var ( - genericLongDesc = normalizer.LongDesc(` + genericCertRenewLongDesc = normalizer.LongDesc(` Renew the %[1]s, and save them into %[2]s.cert and %[2]s.key files. Extra attributes such as SANs will be based on the existing certificates, there is no need to resupply them. `) + genericCertRenewEmbeddedLongDesc = normalizer.LongDesc(` +Renew the certificate embedded in the kubeconfig file %s. + +Kubeconfig attributes and certificate extra attributes such as SANs will be based on the existing kubeconfig/certificates, there is no need to resupply them. +`) + allLongDesc = normalizer.LongDesc(` Renew all known certificates necessary to run the control plane. Renewals are run unconditionally, regardless of expiration date. Renewals can also be run individually for more control. @@ -66,7 +73,7 @@ func newCmdCertsRenewal() *cobra.Command { RunE: cmdutil.SubCmdRunE("renew"), } - cmd.AddCommand(getRenewSubCommands()...) + cmd.AddCommand(getRenewSubCommands(kubeadmconstants.KubernetesDir)...) return cmd } @@ -80,8 +87,15 @@ type renewConfig struct { csrPath string } -func getRenewSubCommands() []*cobra.Command { - cfg := &renewConfig{} +func getRenewSubCommands(kdir string) []*cobra.Command { + cfg := &renewConfig{ + cfg: kubeadmapiv1beta2.InitConfiguration{ + ClusterConfiguration: kubeadmapiv1beta2.ClusterConfiguration{ + // Setting kubernetes version to a default value in order to allow a not necessary internet lookup + KubernetesVersion: constants.CurrentKubernetesVersion.String(), + }, + }, + } // Default values for the cobra help text kubeadmscheme.Scheme.Default(&cfg.cfg) @@ -95,9 +109,11 @@ func getRenewSubCommands() []*cobra.Command { // Don't offer to renew CAs; would cause serious consequences for _, cert := range certs { // get the cobra.Command skeleton for this command - cmd := generateRenewalCommand(cert, cfg) + cmd := generateCertRenewalCommand(cert, cfg) // get the implementation of renewing this certificate - renewalFunc := generateRenewalFunction(cert, caCert, cfg) + renewalFunc := func(cert *certsphase.KubeadmCert, caCert *certsphase.KubeadmCert) func() { + return func() { renewCert(cert, caCert, cfg) } + }(cert, caCert) // install the implementation into the command cmd.Run = func(*cobra.Command, []string) { renewalFunc() } cmdList = append(cmdList, cmd) @@ -106,6 +122,27 @@ func getRenewSubCommands() []*cobra.Command { } } + kubeconfigs := []string{ + kubeadmconstants.AdminKubeConfigFileName, + kubeadmconstants.ControllerManagerKubeConfigFileName, + kubeadmconstants.SchedulerKubeConfigFileName, + //NB. we are escluding KubeletKubeConfig from renewal because management of this certificate is delegated to kubelet + } + + for _, k := range kubeconfigs { + // get the cobra.Command skeleton for this command + cmd := generateEmbeddedCertRenewalCommand(k, cfg) + // get the implementation of renewing this certificate + renewalFunc := func(kdir, k string) func() { + return func() { renewEmbeddedCert(kdir, k, cfg) } + }(kdir, k) + // install the implementation into the command + cmd.Run = func(*cobra.Command, []string) { renewalFunc() } + cmdList = append(cmdList, cmd) + // Collect renewal functions for `renew all` + funcList = append(funcList, renewalFunc) + } + allCmd := &cobra.Command{ Use: "all", Short: "Renew all available certificates", @@ -131,53 +168,100 @@ func addFlags(cmd *cobra.Command, cfg *renewConfig) { cmd.Flags().BoolVar(&cfg.useAPI, "use-api", cfg.useAPI, "Use the Kubernetes certificate API to renew certificates") } -func generateRenewalFunction(cert *certsphase.KubeadmCert, caCert *certsphase.KubeadmCert, cfg *renewConfig) func() { - return func() { - internalcfg, err := configutil.LoadOrDefaultInitConfiguration(cfg.cfgPath, &cfg.cfg) +func renewCert(cert *certsphase.KubeadmCert, caCert *certsphase.KubeadmCert, cfg *renewConfig) { + internalcfg, err := configutil.LoadOrDefaultInitConfiguration(cfg.cfgPath, &cfg.cfg) + kubeadmutil.CheckErr(err) + + // if the renewal operation is set to generate only CSR request + if cfg.useCSR { + // trigger CSR generation in the csrPath, or if this one is missing, in the CertificateDir + path := cfg.csrPath + if path == "" { + path = cfg.cfg.CertificatesDir + } + err := certsphase.CreateCSR(cert, internalcfg, path) + kubeadmutil.CheckErr(err) + return + } + + // otherwise, the renewal operation has to actually renew a certificate + + var externalCA bool + switch caCert.BaseName { + case kubeadmconstants.CACertAndKeyBaseName: + // Check if an external CA is provided by the user (when the CA Cert is present but the CA Key is not) + externalCA, _ = certsphase.UsingExternalCA(&internalcfg.ClusterConfiguration) + case kubeadmconstants.FrontProxyCACertAndKeyBaseName: + // Check if an external Front-Proxy CA is provided by the user (when the Front-Proxy CA Cert is present but the Front-Proxy CA Key is not) + externalCA, _ = certsphase.UsingExternalFrontProxyCA(&internalcfg.ClusterConfiguration) + default: + externalCA = false + } + + if !externalCA { + renewer, err := getRenewer(cfg, caCert.BaseName) kubeadmutil.CheckErr(err) - if cfg.useCSR { - path := cfg.csrPath - if path == "" { - path = cfg.cfg.CertificatesDir - } - err := certsphase.CreateCSR(cert, internalcfg, path) - kubeadmutil.CheckErr(err) - return - } + err = renewal.RenewExistingCert(internalcfg.CertificatesDir, cert.BaseName, renewer) + kubeadmutil.CheckErr(err) - var externalCA bool - switch caCert.BaseName { - case kubeadmconstants.CACertAndKeyBaseName: - // Check if an external CA is provided by the user (when the CA Cert is present but the CA Key is not) - externalCA, _ = certsphase.UsingExternalCA(&internalcfg.ClusterConfiguration) - case kubeadmconstants.FrontProxyCACertAndKeyBaseName: - // Check if an external Front-Proxy CA is provided by the user (when the Front-Proxy CA Cert is present but the Front-Proxy CA Key is not) - externalCA, _ = certsphase.UsingExternalFrontProxyCA(&internalcfg.ClusterConfiguration) - default: - externalCA = false - } - - if !externalCA { - renewer, err := getRenewer(cfg, caCert.BaseName) - kubeadmutil.CheckErr(err) - - err = renewal.RenewExistingCert(internalcfg.CertificatesDir, cert.BaseName, renewer) - kubeadmutil.CheckErr(err) - - fmt.Printf("Certificate %s renewed\n", cert.Name) - return - } - - fmt.Printf("Detected external %s, certificate %s can't be renewed\n", cert.CAName, cert.Name) + fmt.Printf("Certificate %s renewed\n", cert.Name) + return } + + fmt.Printf("Detected external %s, certificate %s can't be renewed\n", cert.CAName, cert.Name) } -func generateRenewalCommand(cert *certsphase.KubeadmCert, cfg *renewConfig) *cobra.Command { +func renewEmbeddedCert(kdir, k string, cfg *renewConfig) { + internalcfg, err := configutil.LoadOrDefaultInitConfiguration(cfg.cfgPath, &cfg.cfg) + kubeadmutil.CheckErr(err) + + // if the renewal operation is set to generate only CSR request + if cfg.useCSR { + // trigger CSR generation in the csrPath, or if this one is missing, in the CertificateDir + path := cfg.csrPath + if path == "" { + path = cfg.cfg.CertificatesDir + } + err := certsphase.CreateCSR(nil, internalcfg, path) + kubeadmutil.CheckErr(err) + return + } + + // otherwise, the renewal operation has to actually renew a certificate + + // Check if an external CA is provided by the user (when the CA Cert is present but the CA Key is not) + externalCA, _ := certsphase.UsingExternalCA(&internalcfg.ClusterConfiguration) + + if !externalCA { + renewer, err := getRenewer(cfg, certsphase.KubeadmCertRootCA.BaseName) + kubeadmutil.CheckErr(err) + + err = renewal.RenewEmbeddedClientCert(kdir, k, renewer) + kubeadmutil.CheckErr(err) + + fmt.Printf("Certificate embedded in %s renewed\n", k) + return + } + + fmt.Printf("Detected external CA, certificate embedded in %s can't be renewed\n", k) +} + +func generateCertRenewalCommand(cert *certsphase.KubeadmCert, cfg *renewConfig) *cobra.Command { cmd := &cobra.Command{ Use: cert.Name, - Short: fmt.Sprintf("Generate the %s", cert.LongName), - Long: fmt.Sprintf(genericLongDesc, cert.LongName, cert.BaseName), + Short: fmt.Sprintf("Renew the %s", cert.LongName), + Long: fmt.Sprintf(genericCertRenewLongDesc, cert.LongName, cert.BaseName), + } + addFlags(cmd, cfg) + return cmd +} + +func generateEmbeddedCertRenewalCommand(k string, cfg *renewConfig) *cobra.Command { + cmd := &cobra.Command{ + Use: k, + Short: fmt.Sprintf("Renew the certificate embedded in %s", k), + Long: fmt.Sprintf(genericCertRenewEmbeddedLongDesc, k), } addFlags(cmd, cfg) return cmd diff --git a/cmd/kubeadm/app/cmd/alpha/certs_test.go b/cmd/kubeadm/app/cmd/alpha/certs_test.go index d337308b8e8..ed5a11b7f98 100644 --- a/cmd/kubeadm/app/cmd/alpha/certs_test.go +++ b/cmd/kubeadm/app/cmd/alpha/certs_test.go @@ -17,13 +17,11 @@ limitations under the License. package alpha import ( - "crypto/rand" - "crypto/rsa" + "crypto" "crypto/x509" - "crypto/x509/pkix" "fmt" - "math/big" "os" + "path/filepath" "strings" "testing" "time" @@ -31,7 +29,8 @@ import ( "github.com/spf13/cobra" kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants" "k8s.io/kubernetes/cmd/kubeadm/app/phases/certs" - certstestutil "k8s.io/kubernetes/cmd/kubeadm/app/util/certs" + certsphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/certs" + kubeconfigphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/kubeconfig" "k8s.io/kubernetes/cmd/kubeadm/app/util/pkiutil" testutil "k8s.io/kubernetes/cmd/kubeadm/test" cmdtestutil "k8s.io/kubernetes/cmd/kubeadm/test/cmd" @@ -81,144 +80,198 @@ func TestCommandsGenerated(t *testing.T) { func TestRunRenewCommands(t *testing.T) { tests := []struct { - command string - baseNames []string - caBaseNames []string + command string + CAs []*certsphase.KubeadmCert + Certs []*certsphase.KubeadmCert + KubeconfigFiles []string }{ { command: "all", - baseNames: []string{ - kubeadmconstants.APIServerCertAndKeyBaseName, - kubeadmconstants.APIServerKubeletClientCertAndKeyBaseName, - kubeadmconstants.APIServerEtcdClientCertAndKeyBaseName, - kubeadmconstants.FrontProxyClientCertAndKeyBaseName, - kubeadmconstants.EtcdServerCertAndKeyBaseName, - kubeadmconstants.EtcdPeerCertAndKeyBaseName, - kubeadmconstants.EtcdHealthcheckClientCertAndKeyBaseName, + CAs: []*certsphase.KubeadmCert{ + &certsphase.KubeadmCertRootCA, + &certsphase.KubeadmCertFrontProxyCA, + &certsphase.KubeadmCertEtcdCA, }, - caBaseNames: []string{ - kubeadmconstants.CACertAndKeyBaseName, - kubeadmconstants.FrontProxyCACertAndKeyBaseName, - kubeadmconstants.EtcdCACertAndKeyBaseName, + Certs: []*certsphase.KubeadmCert{ + &certsphase.KubeadmCertAPIServer, + &certsphase.KubeadmCertKubeletClient, + &certsphase.KubeadmCertFrontProxyClient, + &certsphase.KubeadmCertEtcdAPIClient, + &certsphase.KubeadmCertEtcdServer, + &certsphase.KubeadmCertEtcdPeer, + &certsphase.KubeadmCertEtcdHealthcheck, + }, + KubeconfigFiles: []string{ + kubeadmconstants.AdminKubeConfigFileName, + kubeadmconstants.SchedulerKubeConfigFileName, + kubeadmconstants.ControllerManagerKubeConfigFileName, }, }, { - command: "apiserver", - baseNames: []string{kubeadmconstants.APIServerCertAndKeyBaseName}, - caBaseNames: []string{kubeadmconstants.CACertAndKeyBaseName}, + command: "apiserver", + CAs: []*certsphase.KubeadmCert{ + &certsphase.KubeadmCertRootCA, + }, + Certs: []*certsphase.KubeadmCert{ + &certsphase.KubeadmCertAPIServer, + }, }, { - command: "apiserver-kubelet-client", - baseNames: []string{kubeadmconstants.APIServerKubeletClientCertAndKeyBaseName}, - caBaseNames: []string{kubeadmconstants.CACertAndKeyBaseName}, + command: "apiserver-kubelet-client", + CAs: []*certsphase.KubeadmCert{ + &certsphase.KubeadmCertRootCA, + }, + Certs: []*certsphase.KubeadmCert{ + &certsphase.KubeadmCertKubeletClient, + }, }, { - command: "apiserver-etcd-client", - baseNames: []string{kubeadmconstants.APIServerEtcdClientCertAndKeyBaseName}, - caBaseNames: []string{kubeadmconstants.EtcdCACertAndKeyBaseName}, + command: "apiserver-etcd-client", + CAs: []*certsphase.KubeadmCert{ + &certsphase.KubeadmCertEtcdCA, + }, + Certs: []*certsphase.KubeadmCert{ + &certsphase.KubeadmCertEtcdAPIClient, + }, }, { - command: "front-proxy-client", - baseNames: []string{kubeadmconstants.FrontProxyClientCertAndKeyBaseName}, - caBaseNames: []string{kubeadmconstants.FrontProxyCACertAndKeyBaseName}, + command: "front-proxy-client", + CAs: []*certsphase.KubeadmCert{ + &certsphase.KubeadmCertFrontProxyCA, + }, + Certs: []*certsphase.KubeadmCert{ + &certsphase.KubeadmCertFrontProxyClient, + }, }, { - command: "etcd-server", - baseNames: []string{kubeadmconstants.EtcdServerCertAndKeyBaseName}, - caBaseNames: []string{kubeadmconstants.EtcdCACertAndKeyBaseName}, + command: "etcd-server", + CAs: []*certsphase.KubeadmCert{ + &certsphase.KubeadmCertEtcdCA, + }, + Certs: []*certsphase.KubeadmCert{ + &certsphase.KubeadmCertEtcdServer, + }, }, { - command: "etcd-peer", - baseNames: []string{kubeadmconstants.EtcdPeerCertAndKeyBaseName}, - caBaseNames: []string{kubeadmconstants.EtcdCACertAndKeyBaseName}, + command: "etcd-peer", + CAs: []*certsphase.KubeadmCert{ + &certsphase.KubeadmCertEtcdCA, + }, + Certs: []*certsphase.KubeadmCert{ + &certsphase.KubeadmCertEtcdPeer, + }, }, { - command: "etcd-healthcheck-client", - baseNames: []string{kubeadmconstants.EtcdHealthcheckClientCertAndKeyBaseName}, - caBaseNames: []string{kubeadmconstants.EtcdCACertAndKeyBaseName}, + command: "etcd-healthcheck-client", + CAs: []*certsphase.KubeadmCert{ + &certsphase.KubeadmCertEtcdCA, + }, + Certs: []*certsphase.KubeadmCert{ + &certsphase.KubeadmCertEtcdHealthcheck, + }, + }, + { + command: "admin.conf", + CAs: []*certsphase.KubeadmCert{ + &certsphase.KubeadmCertRootCA, + }, + KubeconfigFiles: []string{ + kubeadmconstants.AdminKubeConfigFileName, + }, + }, + { + command: "scheduler.conf", + CAs: []*certsphase.KubeadmCert{ + &certsphase.KubeadmCertRootCA, + }, + KubeconfigFiles: []string{ + kubeadmconstants.SchedulerKubeConfigFileName, + }, + }, + { + command: "controller-manager.conf", + CAs: []*certsphase.KubeadmCert{ + &certsphase.KubeadmCertRootCA, + }, + KubeconfigFiles: []string{ + kubeadmconstants.ControllerManagerKubeConfigFileName, + }, }, } - renewCmds := getRenewSubCommands() - for _, test := range tests { t.Run(test.command, func(t *testing.T) { tmpDir := testutil.SetupTempDir(t) defer os.RemoveAll(tmpDir) - caCert, caKey := certstestutil.SetupCertificateAuthorithy(t) + cfg := testutil.GetDefaultInternalConfig(t) + cfg.CertificatesDir = tmpDir - for _, caBaseName := range test.caBaseNames { - if err := pkiutil.WriteCertAndKey(tmpDir, caBaseName, caCert, caKey); err != nil { - t.Fatalf("couldn't write out CA: %v", err) + // Generate all the CA + CACerts := map[string]*x509.Certificate{} + CAKeys := map[string]crypto.Signer{} + for _, ca := range test.CAs { + caCert, caKey, err := ca.CreateAsCA(cfg) + if err != nil { + t.Fatalf("couldn't write out CA %s: %v", ca.Name, err) } + CACerts[ca.Name] = caCert + CAKeys[ca.Name] = caKey } - certTmpl := x509.Certificate{ - Subject: pkix.Name{ - CommonName: "test-cert", - Organization: []string{"sig-cluster-lifecycle"}, - }, - DNSNames: []string{"test-domain.space"}, - SerialNumber: new(big.Int).SetInt64(0), - NotBefore: time.Now().Add(-time.Hour * 24 * 365), - NotAfter: time.Now().Add(-time.Hour), - KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, - ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, - } - - key, err := rsa.GenerateKey(rand.Reader, 2048) - if err != nil { - t.Fatalf("couldn't generate private key: %v", err) - } - - certDERBytes, err := x509.CreateCertificate(rand.Reader, &certTmpl, caCert, key.Public(), caKey) - if err != nil { - t.Fatalf("couldn't generate private key: %v", err) - } - cert, err := x509.ParseCertificate(certDERBytes) - if err != nil { - t.Fatalf("couldn't generate private key: %v", err) - } - - for _, baseName := range test.baseNames { - if err := pkiutil.WriteCertAndKey(tmpDir, baseName, cert, key); err != nil { - t.Fatalf("couldn't write out initial certificate") + // Generate all the signed certificates (and store creation time) + createTime := map[string]time.Time{} + for _, cert := range test.Certs { + caCert := CACerts[cert.CAName] + caKey := CAKeys[cert.CAName] + if err := cert.CreateFromCA(cfg, caCert, caKey); err != nil { + t.Fatalf("couldn't write certificate %s: %v", cert.Name, err) } + + file, err := os.Stat(filepath.Join(tmpDir, fmt.Sprintf("%s.crt", cert.BaseName))) + if err != nil { + t.Fatalf("couldn't get certificate %s: %v", cert.Name, err) + } + createTime[cert.Name] = file.ModTime() } + // Generate all the kubeconfig files with embedded certs(and store creation time) + for _, kubeConfig := range test.KubeconfigFiles { + if err := kubeconfigphase.CreateKubeConfigFile(kubeConfig, tmpDir, cfg); err != nil { + t.Fatalf("couldn't create kubeconfig %q: %v", kubeConfig, err) + } + file, err := os.Stat(filepath.Join(tmpDir, kubeConfig)) + if err != nil { + t.Fatalf("couldn't get kubeconfig %s: %v", kubeConfig, err) + } + createTime[kubeConfig] = file.ModTime() + } + + // exec renew + renewCmds := getRenewSubCommands(tmpDir) cmdtestutil.RunSubCommand(t, renewCmds, test.command, fmt.Sprintf("--cert-dir=%s", tmpDir)) - for _, baseName := range test.baseNames { - newCert, newKey, err := pkiutil.TryLoadCertAndKeyFromDisk(tmpDir, baseName) + // read renewed certificates and check the file is modified + for _, cert := range test.Certs { + file, err := os.Stat(filepath.Join(tmpDir, fmt.Sprintf("%s.crt", cert.BaseName))) if err != nil { - t.Fatalf("couldn't load renewed certificate: %v", err) + t.Fatalf("couldn't get certificate %s: %v", cert.Name, err) } - - certstestutil.AssertCertificateIsSignedByCa(t, newCert, caCert) - - pool := x509.NewCertPool() - pool.AddCert(caCert) - - _, err = newCert.Verify(x509.VerifyOptions{ - DNSName: "test-domain.space", - Roots: pool, - KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, - }) - if err != nil { - t.Errorf("couldn't verify renewed cert: %v", err) - } - - switch pubKey := newCert.PublicKey.(type) { - case *rsa.PublicKey: - if pubKey.N.Cmp(newKey.(*rsa.PrivateKey).N) != 0 { - t.Error("private key does not match public key") - } - default: - t.Errorf("unknown public key type %T", newCert.PublicKey) + if createTime[cert.Name] == file.ModTime() { + t.Errorf("certificate %s was not renewed as expected", cert.Name) } } + // ead renewed kubeconfig files and check the file is modified + for _, kubeConfig := range test.KubeconfigFiles { + file, err := os.Stat(filepath.Join(tmpDir, kubeConfig)) + if err != nil { + t.Fatalf("couldn't get kubeconfig %s: %v", kubeConfig, err) + } + if createTime[kubeConfig] == file.ModTime() { + t.Errorf("kubeconfig %s was not renewed as expected", kubeConfig) + } + } }) } } @@ -228,7 +281,7 @@ func TestRenewUsingCSR(t *testing.T) { defer os.RemoveAll(tmpDir) cert := &certs.KubeadmCertEtcdServer - renewCmds := getRenewSubCommands() + renewCmds := getRenewSubCommands(tmpDir) cmdtestutil.RunSubCommand(t, renewCmds, cert.Name, "--csr-only", "--csr-dir="+tmpDir) if _, _, err := pkiutil.TryLoadCSRAndKeyFromDisk(tmpDir, cert.BaseName); err != nil { diff --git a/cmd/kubeadm/app/phases/certs/renewal/renewal.go b/cmd/kubeadm/app/phases/certs/renewal/renewal.go index 7c8950c7e3c..37dbbfb902e 100644 --- a/cmd/kubeadm/app/phases/certs/renewal/renewal.go +++ b/cmd/kubeadm/app/phases/certs/renewal/renewal.go @@ -18,9 +18,12 @@ package renewal import ( "crypto/x509" + "path/filepath" "github.com/pkg/errors" + "k8s.io/client-go/tools/clientcmd" certutil "k8s.io/client-go/util/cert" + "k8s.io/client-go/util/keyutil" "k8s.io/kubernetes/cmd/kubeadm/app/util/pkiutil" ) @@ -49,6 +52,72 @@ func RenewExistingCert(certsDir, baseName string, impl Interface) error { return nil } +// RenewEmbeddedClientCert loads a kubeconfig file, uses the renew interface to renew the client certificate +// embedded in it, and then saves the resulting kubeconfig and key over the old one. +func RenewEmbeddedClientCert(kubeConfigFileDir, kubeConfigFileName string, impl Interface) error { + kubeConfigFilePath := filepath.Join(kubeConfigFileDir, kubeConfigFileName) + + // try to load the kubeconfig file + kubeconfig, err := clientcmd.LoadFromFile(kubeConfigFilePath) + if err != nil { + return errors.Wrapf(err, "failed to load kubeconfig file %s", kubeConfigFilePath) + } + + // get current context + if _, ok := kubeconfig.Contexts[kubeconfig.CurrentContext]; !ok { + return errors.Errorf("invalid kubeconfig file %s: missing context %s", kubeConfigFilePath, kubeconfig.CurrentContext) + } + + // get cluster info for current context and ensure a server certificate is embedded in it + clusterName := kubeconfig.Contexts[kubeconfig.CurrentContext].Cluster + if _, ok := kubeconfig.Clusters[clusterName]; !ok { + return errors.Errorf("invalid kubeconfig file %s: missing cluster %s", kubeConfigFilePath, clusterName) + } + + cluster := kubeconfig.Clusters[clusterName] + if len(cluster.CertificateAuthorityData) == 0 { + return errors.Errorf("kubeconfig file %s does not have and embedded server certificate", kubeConfigFilePath) + } + + // get auth info for current context and ensure a client certificate is embedded in it + authInfoName := kubeconfig.Contexts[kubeconfig.CurrentContext].AuthInfo + if _, ok := kubeconfig.AuthInfos[authInfoName]; !ok { + return errors.Errorf("invalid kubeconfig file %s: missing authInfo %s", kubeConfigFilePath, authInfoName) + } + + authInfo := kubeconfig.AuthInfos[authInfoName] + if len(authInfo.ClientCertificateData) == 0 { + return errors.Errorf("kubeconfig file %s does not have and embedded client certificate", kubeConfigFilePath) + } + + // parse the client certificate, retrive the cert config and then renew it + certs, err := certutil.ParseCertsPEM(authInfo.ClientCertificateData) + if err != nil { + return errors.Wrapf(err, "kubeconfig file %s does not contain a valid client certificate", kubeConfigFilePath) + } + + cfg := certToConfig(certs[0]) + + newCert, newKey, err := impl.Renew(cfg) + if err != nil { + return errors.Wrapf(err, "failed to renew certificate embedded in %s", kubeConfigFilePath) + } + + // encodes the new key + encodedClientKey, err := keyutil.MarshalPrivateKeyToPEM(newKey) + if err != nil { + return errors.Wrapf(err, "failed to marshal private key to PEM") + } + + // create a kubeconfig copy with the new client certs + newConfig := kubeconfig.DeepCopy() + newConfig.AuthInfos[authInfoName].ClientKeyData = encodedClientKey + newConfig.AuthInfos[authInfoName].ClientCertificateData = pkiutil.EncodeCertPEM(newCert) + + // writes the kubeconfig to disk + return clientcmd.WriteToFile(*newConfig, kubeConfigFilePath) +} + func certToConfig(cert *x509.Certificate) *certutil.Config { return &certutil.Config{ CommonName: cert.Subject.CommonName, diff --git a/cmd/kubeadm/app/phases/certs/renewal/renewal_test.go b/cmd/kubeadm/app/phases/certs/renewal/renewal_test.go index 61b4dcb6237..1418e26254a 100644 --- a/cmd/kubeadm/app/phases/certs/renewal/renewal_test.go +++ b/cmd/kubeadm/app/phases/certs/renewal/renewal_test.go @@ -17,11 +17,13 @@ limitations under the License. package renewal import ( + "bytes" "crypto" "crypto/x509" "crypto/x509/pkix" "net" "os" + "path/filepath" "testing" "time" @@ -31,8 +33,11 @@ import ( "k8s.io/apimachinery/pkg/watch" fakecerts "k8s.io/client-go/kubernetes/typed/certificates/v1beta1/fake" k8stesting "k8s.io/client-go/testing" + "k8s.io/client-go/tools/clientcmd" certutil "k8s.io/client-go/util/cert" + "k8s.io/client-go/util/keyutil" certtestutil "k8s.io/kubernetes/cmd/kubeadm/app/util/certs" + kubeconfigutil "k8s.io/kubernetes/cmd/kubeadm/app/util/kubeconfig" "k8s.io/kubernetes/cmd/kubeadm/app/util/pkiutil" testutil "k8s.io/kubernetes/cmd/kubeadm/test" ) @@ -186,6 +191,7 @@ func TestCertToConfig(t *testing.T) { } func TestRenewExistingCert(t *testing.T) { + // creates a CA, a certificate, and save it to a file cfg := &certutil.Config{ CommonName: "test-common-name", Organization: []string{"sig-cluster-lifecycle"}, @@ -214,21 +220,136 @@ func TestRenewExistingCert(t *testing.T) { t.Fatalf("couldn't write out certificate") } + // makes some time pass + time.Sleep(1 * time.Second) + + // renew the certificate renewer := NewFileRenewal(caCert, caKey) if err := RenewExistingCert(dir, "server", renewer); err != nil { t.Fatalf("couldn't renew certificate: %v", err) } + // reads the renewed certificate newCert, err := pkiutil.TryLoadCertFromDisk(dir, "server") if err != nil { t.Fatalf("couldn't load created certificate: %v", err) } + // check the new certificate is changed, has an newer expiration date, but preserve all the + // other attributes + if newCert.SerialNumber.Cmp(cert.SerialNumber) == 0 { t.Fatal("expected new certificate, but renewed certificate has same serial number") } + if !newCert.NotAfter.After(cert.NotAfter) { + t.Fatalf("expected new certificate with updated expiration, but renewed certificate has the same serial number: saw %s, expected greather than %s", newCert.NotAfter, cert.NotAfter) + } + + certtestutil.AssertCertificateIsSignedByCa(t, newCert, caCert) + certtestutil.AssertCertificateHasClientAuthUsage(t, newCert) + certtestutil.AssertCertificateHasOrganizations(t, newCert, cfg.Organization...) + certtestutil.AssertCertificateHasCommonName(t, newCert, cfg.CommonName) + certtestutil.AssertCertificateHasDNSNames(t, newCert, cfg.AltNames.DNSNames...) + certtestutil.AssertCertificateHasIPAddresses(t, newCert, cfg.AltNames.IPs...) +} + +func TestRenewEmbeddedClientCert(t *testing.T) { + // creates a CA, a client certificate, and then embeds it into a kubeconfig file + caCertCfg := &certutil.Config{CommonName: "kubernetes"} + caCert, caKey, err := pkiutil.NewCertificateAuthority(caCertCfg) + if err != nil { + t.Fatalf("couldn't create CA: %v", err) + } + + cfg := &certutil.Config{ + CommonName: "test-common-name", + Organization: []string{"sig-cluster-lifecycle"}, + Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, + AltNames: certutil.AltNames{ + IPs: []net.IP{net.ParseIP("10.100.0.1")}, + DNSNames: []string{"test-domain.space"}, + }, + } + cert, key, err := pkiutil.NewCertAndKey(caCert, caKey, cfg) + if err != nil { + t.Fatalf("couldn't generate certificate: %v", err) + } + + encodedClientKey, err := keyutil.MarshalPrivateKeyToPEM(key) + if err != nil { + t.Fatalf("failed to marshal private key to PEM: %v", err) + } + + certificateAuthorityData := pkiutil.EncodeCertPEM(caCert) + + config := kubeconfigutil.CreateWithCerts( + "https://localhost:1234", + "kubernetes-test", + "user-test", + certificateAuthorityData, + encodedClientKey, + pkiutil.EncodeCertPEM(cert), + ) + + dir := testutil.SetupTempDir(t) + defer os.RemoveAll(dir) + + kubeconfigPath := filepath.Join(dir, "k.conf") + + if err := clientcmd.WriteToFile(*config, kubeconfigPath); err != nil { + t.Fatalf("couldn't write out certificate") + } + + // makes some time pass + time.Sleep(1 * time.Second) + + // renew the embedded certificate + renewer := NewFileRenewal(caCert, caKey) + + if err := RenewEmbeddedClientCert(dir, "k.conf", renewer); err != nil { + t.Fatalf("couldn't renew embedded certificate: %v", err) + } + + // reads the kubeconfig file and gets the renewed certificate + newConfig, err := clientcmd.LoadFromFile(kubeconfigPath) + if err != nil { + t.Fatalf("failed to load kubeconfig file %s: %v", kubeconfigPath, err) + } + + if newConfig.Contexts[config.CurrentContext].Cluster != "kubernetes-test" { + t.Fatalf("invalid cluster. expected kubernetes-test, saw %s", newConfig.Contexts[config.CurrentContext].Cluster) + } + + cluster := newConfig.Clusters["kubernetes-test"] + if !bytes.Equal(cluster.CertificateAuthorityData, certificateAuthorityData) { + t.Fatalf("invalid cluster. CertificateAuthorityData does not contain expected value") + } + + if newConfig.Contexts[config.CurrentContext].AuthInfo != "user-test" { + t.Fatalf("invalid AuthInfo. expected user-test, saw %s", newConfig.Contexts[config.CurrentContext].AuthInfo) + } + + authInfo := newConfig.AuthInfos["user-test"] + + newCerts, err := certutil.ParseCertsPEM(authInfo.ClientCertificateData) + if err != nil { + t.Fatalf("couldn't load created certificate: %v", err) + } + + // check the new certificate is changed, has an newer expiration date, but preserve all the + // other attributes + + newCert := newCerts[0] + if newCert.SerialNumber.Cmp(cert.SerialNumber) == 0 { + t.Fatal("expected new certificate, but renewed certificate has same serial number") + } + + if !newCert.NotAfter.After(cert.NotAfter) { + t.Fatalf("expected new certificate with updated expiration, but renewed certificate has same serial number: saw %s, expected greather than %s", newCert.NotAfter, cert.NotAfter) + } + certtestutil.AssertCertificateIsSignedByCa(t, newCert, caCert) certtestutil.AssertCertificateHasClientAuthUsage(t, newCert) certtestutil.AssertCertificateHasOrganizations(t, newCert, cfg.Organization...) diff --git a/cmd/kubeadm/app/phases/upgrade/staticpods.go b/cmd/kubeadm/app/phases/upgrade/staticpods.go index 7e3fd4d8848..db9537655fa 100644 --- a/cmd/kubeadm/app/phases/upgrade/staticpods.go +++ b/cmd/kubeadm/app/phases/upgrade/staticpods.go @@ -202,8 +202,8 @@ func upgradeComponent(component string, renewCerts bool, waiter apiclient.Waiter // if certificate renewal should be performed if renewCerts { // renew all the certificates used by the current component - if err := renewCertsByComponent(cfg, component); err != nil { - return errors.Wrapf(err, "failed to renew certificates for component %q", component) + if err := renewCertsByComponent(cfg, constants.KubernetesDir, component); err != nil { + return rollbackOldManifests(recoverManifests, errors.Wrapf(err, "failed to renew certificates for component %q", component), pathMgr, recoverEtcd) } } @@ -450,6 +450,14 @@ func StaticPodControlPlane(client clientset.Interface, waiter apiclient.Waiter, } } + if renewCerts { + // renew the certificate embedded in the admin.conf file + err := renewEmbeddedCertsByName(cfg, constants.KubernetesDir, constants.AdminKubeConfigFileName) + if err != nil { + return rollbackOldManifests(recoverManifests, errors.Wrapf(err, "failed to upgrade the %s certificates", constants.AdminKubeConfigFileName), pathMgr, false) + } + } + // Remove the temporary directories used on a best-effort (don't fail if the calls error out) // The calls are set here by design; we should _not_ use "defer" above as that would remove the directories // even in the "fail and rollback" case, where we want the directories preserved for the user. @@ -495,7 +503,7 @@ func rollbackEtcdData(cfg *kubeadmapi.InitConfiguration, pathMgr StaticPodPathMa // renewCertsByComponent takes charge of renewing certificates used by a specific component before // the static pod of the component is upgraded -func renewCertsByComponent(cfg *kubeadmapi.InitConfiguration, component string) error { +func renewCertsByComponent(cfg *kubeadmapi.InitConfiguration, kubernetesDir, component string) error { // if the cluster is using a local etcd if cfg.Etcd.Local != nil { if component == constants.Etcd || component == constants.KubeAPIServer { @@ -576,5 +584,42 @@ func renewCertsByComponent(cfg *kubeadmapi.InitConfiguration, component string) } } } + if component == constants.KubeControllerManager { + // renew the certificate embedded in the controller-manager.conf file + err := renewEmbeddedCertsByName(cfg, kubernetesDir, constants.ControllerManagerKubeConfigFileName) + if err != nil { + return errors.Wrapf(err, "failed to upgrade the %s certificates", constants.ControllerManagerKubeConfigFileName) + } + } + if component == constants.KubeScheduler { + // renew the certificate embedded in the scheduler.conf file + err := renewEmbeddedCertsByName(cfg, kubernetesDir, constants.SchedulerKubeConfigFileName) + if err != nil { + return errors.Wrapf(err, "failed to upgrade the %s certificates", constants.SchedulerKubeConfigFileName) + } + } + return nil +} + +func renewEmbeddedCertsByName(cfg *kubeadmapi.InitConfiguration, kubernetesDir, kubeConfigFile string) error { + // Checks if an external CA is provided by the user (when the CA Cert is present but the CA Key is not) + // if not, then CA is managed by kubeadm, so it is possible to renew all the certificates signed by ca + // and used by the apis server (the apiserver certificate and the apiserver-kubelet-client certificate) + externalCA, _ := certsphase.UsingExternalCA(&cfg.ClusterConfiguration) + if !externalCA { + // try to load ca + caCert, caKey, err := certsphase.LoadCertificateAuthority(cfg.CertificatesDir, certsphase.KubeadmCertRootCA.BaseName) + if err != nil { + return errors.Wrapf(err, "failed to upgrade the %s certificates", kubeConfigFile) + } + // create a renewer for certificates signed by CA + renewer := renewal.NewFileRenewal(caCert, caKey) + // renew the certificate embedded in the controller-manager.conf file + fmt.Printf("[upgrade/staticpods] Renewing certificate embedded in %q \n", kubeConfigFile) + if err := renewal.RenewEmbeddedClientCert(kubernetesDir, kubeConfigFile, renewer); err != nil { + return errors.Wrapf(err, "failed to renew certificate embedded in %s", kubeConfigFile) + } + } + return nil } diff --git a/cmd/kubeadm/app/phases/upgrade/staticpods_test.go b/cmd/kubeadm/app/phases/upgrade/staticpods_test.go index 70a9c230194..64e6e4fcf3a 100644 --- a/cmd/kubeadm/app/phases/upgrade/staticpods_test.go +++ b/cmd/kubeadm/app/phases/upgrade/staticpods_test.go @@ -18,6 +18,7 @@ package upgrade import ( "crypto/sha256" + "crypto/x509" "fmt" "io/ioutil" "math/big" @@ -31,11 +32,15 @@ import ( "github.com/coreos/etcd/pkg/transport" "github.com/pkg/errors" + "k8s.io/client-go/tools/clientcmd" + certutil "k8s.io/client-go/util/cert" kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" "k8s.io/kubernetes/cmd/kubeadm/app/constants" + kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants" certsphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/certs" controlplanephase "k8s.io/kubernetes/cmd/kubeadm/app/phases/controlplane" etcdphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/etcd" + kubeconfigphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/kubeconfig" "k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient" certstestutil "k8s.io/kubernetes/cmd/kubeadm/app/util/certs" configutil "k8s.io/kubernetes/cmd/kubeadm/app/util/config" @@ -318,6 +323,7 @@ func TestStaticPodControlPlane(t *testing.T) { description string waitErrsToReturn map[string]error moveFileFunc func(string, string) error + skipKubeConfig string expectedErr bool manifestShouldChange bool }{ @@ -424,6 +430,34 @@ func TestStaticPodControlPlane(t *testing.T) { expectedErr: true, manifestShouldChange: false, }, + { + description: "any cert renew error should result in a rollback and an abort; even though this is the last component (kube-apiserver and kube-controller-manager healthy)", + waitErrsToReturn: map[string]error{ + waitForHashes: nil, + waitForHashChange: nil, + waitForPodsWithLabel: nil, + }, + moveFileFunc: func(oldPath, newPath string) error { + return os.Rename(oldPath, newPath) + }, + skipKubeConfig: kubeadmconstants.SchedulerKubeConfigFileName, + expectedErr: true, + manifestShouldChange: false, + }, + { + description: "any cert renew error should result in a rollback and an abort; even though this is admin.conf (kube-apiserver and kube-controller-manager and kube-scheduler healthy)", + waitErrsToReturn: map[string]error{ + waitForHashes: nil, + waitForHashChange: nil, + waitForPodsWithLabel: nil, + }, + moveFileFunc: func(oldPath, newPath string) error { + return os.Rename(oldPath, newPath) + }, + skipKubeConfig: kubeadmconstants.AdminKubeConfigFileName, + expectedErr: true, + manifestShouldChange: false, + }, } for _, rt := range tests { @@ -463,6 +497,19 @@ func TestStaticPodControlPlane(t *testing.T) { t.Logf("Wrote certs to %s\n", oldcfg.CertificatesDir) + for _, kubeConfig := range []string{ + kubeadmconstants.AdminKubeConfigFileName, + kubeadmconstants.SchedulerKubeConfigFileName, + kubeadmconstants.ControllerManagerKubeConfigFileName, + } { + if rt.skipKubeConfig == kubeConfig { + continue + } + if err := kubeconfigphase.CreateKubeConfigFile(kubeConfig, constants.KubernetesDir, oldcfg); err != nil { + t.Fatalf("couldn't create kubeconfig %q: %v", kubeConfig, err) + } + } + // Initialize the directory with v1.7 manifests; should then be upgraded to v1.8 using the method err = controlplanephase.CreateInitStaticPodManifestFiles(pathMgr.RealManifestDir(), oldcfg) if err != nil { @@ -642,16 +689,17 @@ func TestRenewCertsByComponent(t *testing.T) { caCert, caKey := certstestutil.SetupCertificateAuthorithy(t) tests := []struct { - name string - component string - externalCA bool - externalFrontProxyCA bool - skipCreateEtcdCA bool - shouldErrorOnRenew bool - certsShouldExist []*certsphase.KubeadmCert + name string + component string + externalCA bool + externalFrontProxyCA bool + skipCreateEtcdCA bool + shouldErrorOnRenew bool + certsShouldExist []*certsphase.KubeadmCert + kubeConfigShouldExist []string }{ { - name: "all certs exist, should be rotated for etcd", + name: "all CA exist, all certs should be rotated for etcd", component: constants.Etcd, certsShouldExist: []*certsphase.KubeadmCert{ &certsphase.KubeadmCertEtcdServer, @@ -660,7 +708,7 @@ func TestRenewCertsByComponent(t *testing.T) { }, }, { - name: "all certs exist, should be rotated for apiserver", + name: "all CA exist, all certs should be rotated for apiserver", component: constants.KubeAPIServer, certsShouldExist: []*certsphase.KubeadmCert{ &certsphase.KubeadmCertEtcdAPIClient, @@ -670,7 +718,7 @@ func TestRenewCertsByComponent(t *testing.T) { }, }, { - name: "external CA, renew only certificates not signed by CA", + name: "external CA, renew only certificates not signed by CA for apiserver", component: constants.KubeAPIServer, certsShouldExist: []*certsphase.KubeadmCert{ &certsphase.KubeadmCertEtcdAPIClient, @@ -679,7 +727,7 @@ func TestRenewCertsByComponent(t *testing.T) { externalCA: true, }, { - name: "external front-proxy-CA, renew only certificates not signed by front-proxy-CA", + name: "external front-proxy-CA, renew only certificates not signed by front-proxy-CA for apiserver", component: constants.KubeAPIServer, certsShouldExist: []*certsphase.KubeadmCert{ &certsphase.KubeadmCertEtcdAPIClient, @@ -689,8 +737,18 @@ func TestRenewCertsByComponent(t *testing.T) { externalFrontProxyCA: true, }, { - name: "ignores other compnonents", + name: "all CA exist, should be rotated for scheduler", component: constants.KubeScheduler, + kubeConfigShouldExist: []string{ + kubeadmconstants.SchedulerKubeConfigFileName, + }, + }, + { + name: "all CA exist, should be rotated for controller manager", + component: constants.KubeControllerManager, + kubeConfigShouldExist: []string{ + kubeadmconstants.ControllerManagerKubeConfigFileName, + }, }, { name: "missing a cert to renew", @@ -736,25 +794,36 @@ func TestRenewCertsByComponent(t *testing.T) { } } - // Create expected certs + certMaps := make(map[string]big.Int) + + // Create expected certs and load to recorde the serial numbers for _, kubeCert := range test.certsShouldExist { if err := kubeCert.CreateFromCA(cfg, caCert, caKey); err != nil { - t.Fatalf("couldn't renew certificate %q: %v", kubeCert.Name, err) + t.Fatalf("couldn't create certificate %q: %v", kubeCert.Name, err) } - } - // Load expected certs to check if serial numbers changes - certMaps := make(map[*certsphase.KubeadmCert]big.Int) - for _, kubeCert := range test.certsShouldExist { cert, err := pkiutil.TryLoadCertFromDisk(tmpDir, kubeCert.BaseName) if err != nil { t.Fatalf("couldn't load certificate %q: %v", kubeCert.Name, err) } - certMaps[kubeCert] = *cert.SerialNumber + certMaps[kubeCert.Name] = *cert.SerialNumber + } + + // Create expected kubeconfigs + for _, kubeConfig := range test.kubeConfigShouldExist { + if err := kubeconfigphase.CreateKubeConfigFile(kubeConfig, tmpDir, cfg); err != nil { + t.Fatalf("couldn't create kubeconfig %q: %v", kubeConfig, err) + } + + newCerts, err := getEmbeddedCerts(tmpDir, kubeConfig) + if err != nil { + t.Fatalf("error reading embedded certs from %s: %v", kubeConfig, err) + } + certMaps[kubeConfig] = *newCerts[0].SerialNumber } // Renew everything - err := renewCertsByComponent(cfg, test.component) + err := renewCertsByComponent(cfg, tmpDir, test.component) if test.shouldErrorOnRenew { if err == nil { t.Fatal("expected renewal error, got nothing") @@ -767,17 +836,43 @@ func TestRenewCertsByComponent(t *testing.T) { } // See if the certificate serial numbers change - for kubeCert, cert := range certMaps { + for _, kubeCert := range test.certsShouldExist { newCert, err := pkiutil.TryLoadCertFromDisk(tmpDir, kubeCert.BaseName) if err != nil { t.Errorf("couldn't load new certificate %q: %v", kubeCert.Name, err) continue } - if cert.Cmp(newCert.SerialNumber) == 0 { + oldSerial, _ := certMaps[kubeCert.Name] + if oldSerial.Cmp(newCert.SerialNumber) == 0 { t.Errorf("certifitate %v was not reissued", kubeCert.Name) } } + + // See if the embedded certificate serial numbers change + for _, kubeConfig := range test.kubeConfigShouldExist { + newCerts, err := getEmbeddedCerts(tmpDir, kubeConfig) + if err != nil { + t.Fatalf("error reading embedded certs from %s: %v", kubeConfig, err) + } + oldSerial, _ := certMaps[kubeConfig] + if oldSerial.Cmp(newCerts[0].SerialNumber) == 0 { + t.Errorf("certifitate %v was not reissued", kubeConfig) + } + } }) } } + +func getEmbeddedCerts(tmpDir, kubeConfig string) ([]*x509.Certificate, error) { + kubeconfigPath := filepath.Join(tmpDir, kubeConfig) + newConfig, err := clientcmd.LoadFromFile(kubeconfigPath) + if err != nil { + return nil, errors.Wrapf(err, "failed to load kubeconfig file %s", kubeconfigPath) + } + + authInfoName := newConfig.Contexts[newConfig.CurrentContext].AuthInfo + authInfo := newConfig.AuthInfos[authInfoName] + + return certutil.ParseCertsPEM(authInfo.ClientCertificateData) +} From 3076644f0b54721349f46192e439e609f5a7ab0d Mon Sep 17 00:00:00 2001 From: fabriziopandini Date: Mon, 6 May 2019 16:48:35 +0200 Subject: [PATCH 2/2] autogenerated --- cmd/kubeadm/app/cmd/alpha/BUILD | 2 +- cmd/kubeadm/app/phases/certs/renewal/BUILD | 5 +++++ cmd/kubeadm/app/phases/upgrade/BUILD | 3 +++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/cmd/kubeadm/app/cmd/alpha/BUILD b/cmd/kubeadm/app/cmd/alpha/BUILD index e9c036ae40b..6484f318f2c 100644 --- a/cmd/kubeadm/app/cmd/alpha/BUILD +++ b/cmd/kubeadm/app/cmd/alpha/BUILD @@ -62,7 +62,7 @@ go_test( deps = [ "//cmd/kubeadm/app/constants:go_default_library", "//cmd/kubeadm/app/phases/certs:go_default_library", - "//cmd/kubeadm/app/util/certs:go_default_library", + "//cmd/kubeadm/app/phases/kubeconfig:go_default_library", "//cmd/kubeadm/app/util/pkiutil:go_default_library", "//cmd/kubeadm/test:go_default_library", "//cmd/kubeadm/test/cmd:go_default_library", diff --git a/cmd/kubeadm/app/phases/certs/renewal/BUILD b/cmd/kubeadm/app/phases/certs/renewal/BUILD index 626aad15750..404d2291dfc 100644 --- a/cmd/kubeadm/app/phases/certs/renewal/BUILD +++ b/cmd/kubeadm/app/phases/certs/renewal/BUILD @@ -16,8 +16,10 @@ go_library( "//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//staging/src/k8s.io/client-go/kubernetes:go_default_library", "//staging/src/k8s.io/client-go/kubernetes/typed/certificates/v1beta1:go_default_library", + "//staging/src/k8s.io/client-go/tools/clientcmd:go_default_library", "//staging/src/k8s.io/client-go/util/cert:go_default_library", "//staging/src/k8s.io/client-go/util/certificate/csr:go_default_library", + "//staging/src/k8s.io/client-go/util/keyutil:go_default_library", "//vendor/github.com/pkg/errors:go_default_library", ], ) @@ -31,6 +33,7 @@ go_test( embed = [":go_default_library"], deps = [ "//cmd/kubeadm/app/util/certs:go_default_library", + "//cmd/kubeadm/app/util/kubeconfig:go_default_library", "//cmd/kubeadm/app/util/pkiutil:go_default_library", "//cmd/kubeadm/test:go_default_library", "//staging/src/k8s.io/api/certificates/v1beta1:go_default_library", @@ -39,7 +42,9 @@ go_test( "//staging/src/k8s.io/apimachinery/pkg/watch:go_default_library", "//staging/src/k8s.io/client-go/kubernetes/typed/certificates/v1beta1/fake:go_default_library", "//staging/src/k8s.io/client-go/testing:go_default_library", + "//staging/src/k8s.io/client-go/tools/clientcmd:go_default_library", "//staging/src/k8s.io/client-go/util/cert:go_default_library", + "//staging/src/k8s.io/client-go/util/keyutil:go_default_library", ], ) diff --git a/cmd/kubeadm/app/phases/upgrade/BUILD b/cmd/kubeadm/app/phases/upgrade/BUILD index 40dc8e70b7b..0df07487a3e 100644 --- a/cmd/kubeadm/app/phases/upgrade/BUILD +++ b/cmd/kubeadm/app/phases/upgrade/BUILD @@ -78,6 +78,7 @@ go_test( "//cmd/kubeadm/app/phases/certs:go_default_library", "//cmd/kubeadm/app/phases/controlplane:go_default_library", "//cmd/kubeadm/app/phases/etcd:go_default_library", + "//cmd/kubeadm/app/phases/kubeconfig:go_default_library", "//cmd/kubeadm/app/util/apiclient:go_default_library", "//cmd/kubeadm/app/util/certs:go_default_library", "//cmd/kubeadm/app/util/config:go_default_library", @@ -89,6 +90,8 @@ go_test( "//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/util/version:go_default_library", "//staging/src/k8s.io/client-go/kubernetes/fake:go_default_library", + "//staging/src/k8s.io/client-go/tools/clientcmd:go_default_library", + "//staging/src/k8s.io/client-go/util/cert:go_default_library", "//vendor/github.com/coreos/etcd/clientv3:go_default_library", "//vendor/github.com/coreos/etcd/pkg/transport:go_default_library", "//vendor/github.com/pkg/errors:go_default_library",