diff --git a/cmd/kubeadm/app/cmd/phases/certs.go b/cmd/kubeadm/app/cmd/phases/certs.go index 2fab5f098c1..0838b37482b 100644 --- a/cmd/kubeadm/app/cmd/phases/certs.go +++ b/cmd/kubeadm/app/cmd/phases/certs.go @@ -186,7 +186,7 @@ func getSANDescription(certSpec *certsphase.KubeadmCert) string { func addFlags(cmd *cobra.Command, cfgPath *string, cfg *kubeadmapiv1alpha3.InitConfiguration, addAPIFlags bool) { options.AddCertificateDirFlag(cmd.Flags(), &cfg.CertificatesDir) - options.AddKubeConfigFlag(cmd.Flags(), cfgPath) + options.AddConfigFlag(cmd.Flags(), cfgPath) if addAPIFlags { cmd.Flags().StringVar(&cfg.Networking.DNSDomain, "service-dns-domain", cfg.Networking.DNSDomain, "Alternative domain for services, to use for the API server serving cert") cmd.Flags().StringVar(&cfg.Networking.ServiceSubnet, "service-cidr", cfg.Networking.ServiceSubnet, "Alternative range of IP address for service VIPs, from which derives the internal API server VIP that will be added to the API Server serving cert") diff --git a/cmd/kubeadm/app/cmd/phases/certs/BUILD b/cmd/kubeadm/app/cmd/phases/certs/BUILD index b0ab2077b81..d75fdddeb09 100644 --- a/cmd/kubeadm/app/cmd/phases/certs/BUILD +++ b/cmd/kubeadm/app/cmd/phases/certs/BUILD @@ -16,6 +16,7 @@ go_library( "//cmd/kubeadm/app/util:go_default_library", "//cmd/kubeadm/app/util/config:go_default_library", "//cmd/kubeadm/app/util/kubeconfig:go_default_library", + "//pkg/util/normalizer:go_default_library", "//vendor/github.com/spf13/cobra:go_default_library", ], ) diff --git a/cmd/kubeadm/app/cmd/phases/certs/renew.go b/cmd/kubeadm/app/cmd/phases/certs/renew.go index e1ef98a09a3..9f56ef45f10 100644 --- a/cmd/kubeadm/app/cmd/phases/certs/renew.go +++ b/cmd/kubeadm/app/cmd/phases/certs/renew.go @@ -23,6 +23,7 @@ import ( kubeadmscheme "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/scheme" kubeadmapiv1alpha3 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha3" + "k8s.io/kubernetes/cmd/kubeadm/app/cmd/options" cmdutil "k8s.io/kubernetes/cmd/kubeadm/app/cmd/util" "k8s.io/kubernetes/cmd/kubeadm/app/constants" certsphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/certs" @@ -30,15 +31,28 @@ import ( kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util" configutil "k8s.io/kubernetes/cmd/kubeadm/app/util/config" kubeconfigutil "k8s.io/kubernetes/cmd/kubeadm/app/util/kubeconfig" - - "k8s.io/kubernetes/cmd/kubeadm/app/cmd/options" + "k8s.io/kubernetes/pkg/util/normalizer" ) +var ( + genericLongDesc = normalizer.LongDesc(` + Renews the %[1]s, and saves 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. +`) + allLongDesc = normalizer.LongDesc(` + Renews all known certificates necessary to run the control plan. Renewals are run unconditionally, regardless + of expiration date. Renewals can also be run individually for more control. +`) +) + +// NewCmdCertsRenewal creates a new `cert renew` command. func NewCmdCertsRenewal() *cobra.Command { cmd := &cobra.Command{ Use: "renew", - Short: "Renews all known certificates for kubeadm", - Long: "", // TODO EKF fill out + Short: "Renews certificates for a kubernetes cluster", + Long: cmdutil.MacroCommandLongDescription, + RunE: cmdutil.SubCmdRunE("renew"), } cmd.AddCommand(getRenewSubCommands()...) @@ -64,14 +78,31 @@ func getRenewSubCommands() []*cobra.Command { kubeadmutil.CheckErr(err) cmdList := []*cobra.Command{} + allCmds := []func() error{} for caCert, certs := range certTree { // Don't offer to renew CAs; would cause serious consequences for _, cert := range certs { - cmdList = append(cmdList, makeCommandForRenew(cert, caCert, cfg)) + cmd := makeCommandForRenew(cert, caCert, cfg) + cmdList = append(cmdList, cmd) + allCmds = append(allCmds, cmd.Execute) } } + allCmd := &cobra.Command{ + Use: "all", + Short: "renew all available certificates", + Long: allLongDesc, + Run: func(*cobra.Command, []string) { + for _, cmd := range allCmds { + err := cmd() + kubeadmutil.CheckErr(err) + } + }, + } + addFlags(allCmd, cfg) + + cmdList = append(cmdList, allCmd) return cmdList } @@ -87,7 +118,7 @@ func generateCertCommand(name, longName, baseName, caCertBaseName string, cfg *r return &cobra.Command{ Use: name, Short: fmt.Sprintf("Generates the %s", longName), - Long: "", // TODO EKF fill out + Long: fmt.Sprintf(genericLongDesc, longName, baseName), Run: func(cmd *cobra.Command, args []string) { internalcfg, err := configutil.ConfigFileAndDefaultsToInternalConfig(cfg.cfgPath, &cfg.cfg) kubeadmutil.CheckErr(err) diff --git a/cmd/kubeadm/app/cmd/phases/certs/renewal_test.go b/cmd/kubeadm/app/cmd/phases/certs/renewal_test.go index 217ff880e1b..81bc781b349 100644 --- a/cmd/kubeadm/app/cmd/phases/certs/renewal_test.go +++ b/cmd/kubeadm/app/cmd/phases/certs/renewal_test.go @@ -45,8 +45,7 @@ func TestCommandsGenerated(t *testing.T) { } expectedCommands := []string{ - // TODO(EKF): add `renew all` - // "renew", + "renew all", "renew apiserver", "renew apiserver-kubelet-client", @@ -82,44 +81,61 @@ func TestCommandsGenerated(t *testing.T) { func TestRunRenewCommands(t *testing.T) { tests := []struct { - command string - baseName string - caBaseName string + command string + baseNames []string + caBaseNames []string }{ { - command: "apiserver", - baseName: kubeadmconstants.APIServerCertAndKeyBaseName, - caBaseName: kubeadmconstants.CACertAndKeyBaseName, + command: "all", + baseNames: []string{ + kubeadmconstants.APIServerCertAndKeyBaseName, + kubeadmconstants.APIServerKubeletClientCertAndKeyBaseName, + kubeadmconstants.APIServerEtcdClientCertAndKeyBaseName, + kubeadmconstants.FrontProxyClientCertAndKeyBaseName, + kubeadmconstants.EtcdServerCertAndKeyBaseName, + kubeadmconstants.EtcdPeerCertAndKeyBaseName, + kubeadmconstants.EtcdHealthcheckClientCertAndKeyBaseName, + }, + caBaseNames: []string{ + kubeadmconstants.CACertAndKeyBaseName, + kubeadmconstants.FrontProxyCACertAndKeyBaseName, + kubeadmconstants.EtcdCACertAndKeyBaseName, + }, }, { - command: "apiserver-kubelet-client", - baseName: kubeadmconstants.APIServerKubeletClientCertAndKeyBaseName, - caBaseName: kubeadmconstants.CACertAndKeyBaseName, + command: "apiserver", + baseNames: []string{kubeadmconstants.APIServerCertAndKeyBaseName}, + caBaseNames: []string{kubeadmconstants.CACertAndKeyBaseName}, }, { - command: "apiserver-etcd-client", - baseName: kubeadmconstants.APIServerEtcdClientCertAndKeyBaseName, - caBaseName: kubeadmconstants.EtcdCACertAndKeyBaseName, + command: "apiserver-kubelet-client", + baseNames: []string{kubeadmconstants.APIServerKubeletClientCertAndKeyBaseName}, + caBaseNames: []string{kubeadmconstants.CACertAndKeyBaseName}, }, { - command: "front-proxy-client", - baseName: kubeadmconstants.FrontProxyClientCertAndKeyBaseName, - caBaseName: kubeadmconstants.FrontProxyCACertAndKeyBaseName, + command: "apiserver-etcd-client", + baseNames: []string{kubeadmconstants.APIServerEtcdClientCertAndKeyBaseName}, + caBaseNames: []string{kubeadmconstants.EtcdCACertAndKeyBaseName}, }, { - command: "etcd-server", - baseName: kubeadmconstants.EtcdServerCertAndKeyBaseName, - caBaseName: kubeadmconstants.EtcdCACertAndKeyBaseName, + command: "front-proxy-client", + baseNames: []string{kubeadmconstants.FrontProxyClientCertAndKeyBaseName}, + caBaseNames: []string{kubeadmconstants.FrontProxyCACertAndKeyBaseName}, }, { - command: "etcd-peer", - baseName: kubeadmconstants.EtcdPeerCertAndKeyBaseName, - caBaseName: kubeadmconstants.EtcdCACertAndKeyBaseName, + command: "etcd-server", + baseNames: []string{kubeadmconstants.EtcdServerCertAndKeyBaseName}, + caBaseNames: []string{kubeadmconstants.EtcdCACertAndKeyBaseName}, }, { - command: "etcd-healthcheck-client", - baseName: kubeadmconstants.EtcdHealthcheckClientCertAndKeyBaseName, - caBaseName: kubeadmconstants.EtcdCACertAndKeyBaseName, + command: "etcd-peer", + baseNames: []string{kubeadmconstants.EtcdPeerCertAndKeyBaseName}, + caBaseNames: []string{kubeadmconstants.EtcdCACertAndKeyBaseName}, + }, + { + command: "etcd-healthcheck-client", + baseNames: []string{kubeadmconstants.EtcdHealthcheckClientCertAndKeyBaseName}, + caBaseNames: []string{kubeadmconstants.EtcdCACertAndKeyBaseName}, }, } @@ -132,8 +148,10 @@ func TestRunRenewCommands(t *testing.T) { caCert, caKey := certstestutil.SetupCertificateAuthorithy(t) - if err := pkiutil.WriteCertAndKey(tmpDir, test.caBaseName, caCert, caKey); err != nil { - t.Fatalf("couldn't write out CA: %v", err) + for _, caBaseName := range test.caBaseNames { + if err := pkiutil.WriteCertAndKey(tmpDir, caBaseName, caCert, caKey); err != nil { + t.Fatalf("couldn't write out CA: %v", err) + } } certTmpl := x509.Certificate{ @@ -163,36 +181,40 @@ func TestRunRenewCommands(t *testing.T) { t.Fatalf("couldn't generate private key: %v", err) } - if err := pkiutil.WriteCertAndKey(tmpDir, test.baseName, cert, key); err != nil { - t.Fatalf("couldn't write out initial certificate") + for _, baseName := range test.baseNames { + if err := pkiutil.WriteCertAndKey(tmpDir, baseName, cert, key); err != nil { + t.Fatalf("couldn't write out initial certificate") + } } cmdtestutil.RunSubCommand(t, renewCmds, test.command, fmt.Sprintf("--cert-dir=%s", tmpDir)) - newCert, newKey, err := pkiutil.TryLoadCertAndKeyFromDisk(tmpDir, test.baseName) - if err != nil { - t.Fatalf("couldn't load renewed certificate: %v", err) - } + for _, baseName := range test.baseNames { + newCert, newKey, err := pkiutil.TryLoadCertAndKeyFromDisk(tmpDir, baseName) + if err != nil { + t.Fatalf("couldn't load renewed certificate: %v", err) + } - certstestutil.AssertCertificateIsSignedByCa(t, newCert, caCert) + certstestutil.AssertCertificateIsSignedByCa(t, newCert, caCert) - pool := x509.NewCertPool() - pool.AddCert(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) - } + _, 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) + } - pubKey, ok := newCert.PublicKey.(*rsa.PublicKey) - if !ok { - t.Errorf("unknown public key type %T", newCert.PublicKey) - } else if pubKey.N.Cmp(newKey.N) != 0 { - t.Error("private key does not match public key") + pubKey, ok := newCert.PublicKey.(*rsa.PublicKey) + if !ok { + t.Errorf("unknown public key type %T", newCert.PublicKey) + } else if pubKey.N.Cmp(newKey.N) != 0 { + t.Error("private key does not match public key") + } } }) diff --git a/cmd/kubeadm/app/phases/certs/renewal/BUILD b/cmd/kubeadm/app/phases/certs/renewal/BUILD index 172d78cc923..dfba2b89c4b 100644 --- a/cmd/kubeadm/app/phases/certs/renewal/BUILD +++ b/cmd/kubeadm/app/phases/certs/renewal/BUILD @@ -19,6 +19,7 @@ go_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/util/cert:go_default_library", + "//vendor/github.com/pkg/errors:go_default_library", ], ) diff --git a/cmd/kubeadm/app/phases/certs/renewal/certsapi.go b/cmd/kubeadm/app/phases/certs/renewal/certsapi.go index 5298a96402e..f49ddf69ca6 100644 --- a/cmd/kubeadm/app/phases/certs/renewal/certsapi.go +++ b/cmd/kubeadm/app/phases/certs/renewal/certsapi.go @@ -21,10 +21,11 @@ import ( "crypto/rsa" "crypto/x509" "crypto/x509/pkix" - "errors" "fmt" "time" + "github.com/pkg/errors" + certsapi "k8s.io/api/certificates/v1beta1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/fields" @@ -47,7 +48,7 @@ type CertsAPIRenewal struct { client certstype.CertificatesV1beta1Interface } -// NewCertsAPIRenawal takes a certificate pair to construct the Interface. +// NewCertsAPIRenawal takes a Kubernetes interface and returns a renewal Interface. func NewCertsAPIRenawal(client kubernetes.Interface) Interface { return &CertsAPIRenewal{ client: client.CertificatesV1beta1(), @@ -67,19 +68,19 @@ func (r *CertsAPIRenewal) Renew(cfg *certutil.Config) (*x509.Certificate, *rsa.P key, err := certutil.NewPrivateKey() if err != nil { - return nil, nil, fmt.Errorf("Couldn't create new private key: %v", err) + return nil, nil, errors.Wrap(err, "couldn't create new private key") } csr, err := x509.CreateCertificateRequest(rand.Reader, reqTmp, key) if err != nil { - return nil, nil, fmt.Errorf("Couldn't create csr: %v", err) + return nil, nil, errors.Wrap(err, "couldn't create certificate signing request") } usages := make([]certsapi.KeyUsage, len(cfg.Usages)) for i, usage := range cfg.Usages { certsAPIUsage, ok := usageMap[usage] if !ok { - return nil, nil, fmt.Errorf("unknown key usage %v", usage) + return nil, nil, fmt.Errorf("unknown key usage: %v", usage) } usages[i] = certsAPIUsage } @@ -96,7 +97,7 @@ func (r *CertsAPIRenewal) Renew(cfg *certutil.Config) (*x509.Certificate, *rsa.P req, err := r.client.CertificateSigningRequests().Create(k8sCSR) if err != nil { - return nil, nil, fmt.Errorf("couldn't create certificate signing request: %v", err) + return nil, nil, errors.Wrap(err, "couldn't create certificate signing request") } watcher, err := r.client.CertificateSigningRequests().Watch(metav1.ListOptions{ @@ -104,14 +105,14 @@ func (r *CertsAPIRenewal) Renew(cfg *certutil.Config) (*x509.Certificate, *rsa.P FieldSelector: fields.Set{"metadata.name": req.Name}.String(), }) if err != nil { - return nil, nil, fmt.Errorf("couldn't watch for certificate creation: %v", err) + return nil, nil, errors.Wrap(err, "couldn't watch for certificate creation") } defer watcher.Stop() select { case ev := <-watcher.ResultChan(): if ev.Type != watch.Modified { - return nil, nil, fmt.Errorf("unexpected event receieved: %q", ev.Type) + return nil, nil, fmt.Errorf("unexpected event received: %q", ev.Type) } case <-time.After(watchTimeout): return nil, nil, errors.New("timeout trying to sign certificate") @@ -124,12 +125,12 @@ func (r *CertsAPIRenewal) Renew(cfg *certutil.Config) (*x509.Certificate, *rsa.P // TODO: under what circumstances are there more than one? if status := req.Status.Conditions[0].Type; status != certsapi.CertificateApproved { - return nil, nil, fmt.Errorf("Unexpected certificate status %v", status) + return nil, nil, fmt.Errorf("unexpected certificate status: %v", status) } cert, err := x509.ParseCertificate(req.Status.Certificate) if err != nil { - return nil, nil, fmt.Errorf("couldn't parse issued certificate: %v", err) + return nil, nil, errors.Wrap(err, "couldn't parse issued certificate") } return cert, key, nil diff --git a/cmd/kubeadm/app/phases/certs/renewal/renewal.go b/cmd/kubeadm/app/phases/certs/renewal/renewal.go index 3b934668845..e6eb7e2a434 100644 --- a/cmd/kubeadm/app/phases/certs/renewal/renewal.go +++ b/cmd/kubeadm/app/phases/certs/renewal/renewal.go @@ -20,10 +20,13 @@ import ( "crypto/x509" "fmt" + "github.com/pkg/errors" certutil "k8s.io/client-go/util/cert" "k8s.io/kubernetes/cmd/kubeadm/app/phases/certs/pkiutil" ) +// RenewExistingCert loads a certificate file, uses the renew interface to renew it, +// and saves the resulting certificate and key over the old one. func RenewExistingCert(certsDir, baseName string, impl Interface) error { certificatePath, _ := pkiutil.PathsForCertAndKey(certsDir, baseName) certs, err := certutil.CertsFromFile(certificatePath) @@ -38,11 +41,11 @@ func RenewExistingCert(certsDir, baseName string, impl Interface) error { cfg := certToConfig(certs[0]) newCert, newKey, err := impl.Renew(cfg) if err != nil { - return fmt.Errorf("failed to renew certificate %s: %v", baseName, err) + return errors.Wrapf(err, "failed to renew certificate %s", baseName) } if err := pkiutil.WriteCertAndKey(certsDir, baseName, newCert, newKey); err != nil { - return fmt.Errorf("failed to write new certificate %s: %v", baseName, err) + return errors.Wrapf(err, "failed to write new certificate %s", baseName) } return nil } diff --git a/cmd/kubeadm/app/phases/certs/renewal/renewal_test.go b/cmd/kubeadm/app/phases/certs/renewal/renewal_test.go index 565c36d8760..bb31d10287c 100644 --- a/cmd/kubeadm/app/phases/certs/renewal/renewal_test.go +++ b/cmd/kubeadm/app/phases/certs/renewal/renewal_test.go @@ -128,7 +128,7 @@ func getCertReq(t *testing.T, caCert *x509.Certificate, caKey *rsa.PrivateKey) * }, Status: certsapi.CertificateSigningRequestStatus{ Conditions: []certsapi.CertificateSigningRequestCondition{ - certsapi.CertificateSigningRequestCondition{ + { Type: certsapi.CertificateApproved, }, },