Add renew all command

This commit is contained in:
liz 2018-08-30 10:34:44 -04:00
parent 76be5ca581
commit d21ed1a1f7
No known key found for this signature in database
GPG Key ID: 42D1F3A8C4A02586
8 changed files with 129 additions and 70 deletions

View File

@ -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")

View File

@ -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",
],
)

View File

@ -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)

View File

@ -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")
}
}
})

View File

@ -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",
],
)

View File

@ -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

View File

@ -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
}

View File

@ -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,
},
},