Merge pull request #68749 from liztio/renew-etcd-certs

Renew certificates as part of upgrade rather than recreating them
This commit is contained in:
k8s-ci-robot 2018-09-18 10:11:02 -07:00 committed by GitHub
commit e7eb26919b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 168 additions and 25 deletions

View File

@ -25,6 +25,7 @@ go_library(
"//cmd/kubeadm/app/phases/bootstraptoken/clusterinfo:go_default_library",
"//cmd/kubeadm/app/phases/bootstraptoken/node:go_default_library",
"//cmd/kubeadm/app/phases/certs:go_default_library",
"//cmd/kubeadm/app/phases/certs/renewal:go_default_library",
"//cmd/kubeadm/app/phases/controlplane:go_default_library",
"//cmd/kubeadm/app/phases/etcd:go_default_library",
"//cmd/kubeadm/app/phases/kubelet:go_default_library",
@ -79,12 +80,14 @@ go_test(
"//cmd/kubeadm/app/apis/kubeadm/validation:go_default_library",
"//cmd/kubeadm/app/constants:go_default_library",
"//cmd/kubeadm/app/phases/certs:go_default_library",
"//cmd/kubeadm/app/phases/certs/pkiutil:go_default_library",
"//cmd/kubeadm/app/phases/controlplane:go_default_library",
"//cmd/kubeadm/app/phases/etcd:go_default_library",
"//cmd/kubeadm/app/util/apiclient:go_default_library",
"//cmd/kubeadm/app/util/config:go_default_library",
"//cmd/kubeadm/app/util/etcd:go_default_library",
"//cmd/kubeadm/test:go_default_library",
"//cmd/kubeadm/test/certs:go_default_library",
"//pkg/util/version:go_default_library",
"//staging/src/k8s.io/api/apps/v1:go_default_library",
"//staging/src/k8s.io/api/core/v1:go_default_library",

View File

@ -25,6 +25,7 @@ import (
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
"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"
controlplanephase "k8s.io/kubernetes/cmd/kubeadm/app/phases/controlplane"
etcdphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/etcd"
"k8s.io/kubernetes/cmd/kubeadm/app/util"
@ -185,31 +186,8 @@ func upgradeComponent(component string, waiter apiclient.Waiter, pathMgr StaticP
}
}
if cfg.Etcd.Local != nil {
// ensure etcd certs are generated for etcd and kube-apiserver
if component == constants.Etcd || component == constants.KubeAPIServer {
caCert, caKey, err := certsphase.KubeadmCertEtcdCA.CreateAsCA(cfg)
if err != nil {
return fmt.Errorf("failed to upgrade the %s CA certificate and key: %v", constants.Etcd, err)
}
if component == constants.Etcd {
if err := certsphase.KubeadmCertEtcdServer.CreateFromCA(cfg, caCert, caKey); err != nil {
return fmt.Errorf("failed to upgrade the %s certificate and key: %v", constants.Etcd, err)
}
if err := certsphase.KubeadmCertEtcdPeer.CreateFromCA(cfg, caCert, caKey); err != nil {
return fmt.Errorf("failed to upgrade the %s peer certificate and key: %v", constants.Etcd, err)
}
if err := certsphase.KubeadmCertEtcdHealthcheck.CreateFromCA(cfg, caCert, caKey); err != nil {
return fmt.Errorf("failed to upgrade the %s healthcheck certificate and key: %v", constants.Etcd, err)
}
}
if component == constants.KubeAPIServer {
if err := certsphase.KubeadmCertEtcdAPIClient.CreateFromCA(cfg, caCert, caKey); err != nil {
return fmt.Errorf("failed to upgrade the %s %s-client certificate and key: %v", constants.KubeAPIServer, constants.Etcd, err)
}
}
}
if err := renewCerts(cfg, component); err != nil {
return fmt.Errorf("failed to renew certificates for component %q: %v", component, err)
}
// The old manifest is here; in the /etc/kubernetes/manifests/
@ -524,3 +502,35 @@ func rollbackEtcdData(cfg *kubeadmapi.InitConfiguration, pathMgr StaticPodPathMa
return nil
}
func renewCerts(cfg *kubeadmapi.InitConfiguration, component string) error {
if cfg.Etcd.Local != nil {
// ensure etcd certs are loaded for etcd and kube-apiserver
if component == constants.Etcd || component == constants.KubeAPIServer {
caCert, caKey, err := certsphase.LoadCertificateAuthority(cfg.CertificatesDir, certsphase.KubeadmCertEtcdCA.BaseName)
if err != nil {
return fmt.Errorf("failed to upgrade the %s CA certificate and key: %v", constants.Etcd, err)
}
renewer := renewal.NewFileRenewal(caCert, caKey)
if component == constants.Etcd {
for _, cert := range []*certsphase.KubeadmCert{
&certsphase.KubeadmCertEtcdServer,
&certsphase.KubeadmCertEtcdPeer,
&certsphase.KubeadmCertEtcdHealthcheck,
} {
if err := renewal.RenewExistingCert(cfg.CertificatesDir, cert.BaseName, renewer); err != nil {
return fmt.Errorf("failed to renew %s certificate and key: %v", cert.Name, err)
}
}
}
if component == constants.KubeAPIServer {
cert := certsphase.KubeadmCertEtcdAPIClient
if err := renewal.RenewExistingCert(cfg.CertificatesDir, cert.BaseName, renewer); err != nil {
return fmt.Errorf("failed to renew %s certificate and key: %v", cert.Name, err)
}
}
}
}
return nil
}

View File

@ -20,6 +20,7 @@ import (
"crypto/sha256"
"fmt"
"io/ioutil"
"math/big"
"os"
"path/filepath"
"strings"
@ -32,11 +33,14 @@ import (
"k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/validation"
"k8s.io/kubernetes/cmd/kubeadm/app/constants"
certsphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/certs"
"k8s.io/kubernetes/cmd/kubeadm/app/phases/certs/pkiutil"
controlplanephase "k8s.io/kubernetes/cmd/kubeadm/app/phases/controlplane"
etcdphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/etcd"
"k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient"
configutil "k8s.io/kubernetes/cmd/kubeadm/app/util/config"
etcdutil "k8s.io/kubernetes/cmd/kubeadm/app/util/etcd"
testutil "k8s.io/kubernetes/cmd/kubeadm/test"
certstestutil "k8s.io/kubernetes/cmd/kubeadm/test/certs"
)
const (
@ -456,6 +460,22 @@ func TestStaticPodControlPlane(t *testing.T) {
t.Fatalf("couldn't create config: %v", err)
}
// create the kubeadm etcd certs
caCert, caKey, err := certsphase.KubeadmCertEtcdCA.CreateAsCA(newcfg)
if err != nil {
t.Fatalf("couldn't create new CA certificate: %v", err)
}
for _, cert := range []*certsphase.KubeadmCert{
&certsphase.KubeadmCertEtcdServer,
&certsphase.KubeadmCertEtcdPeer,
&certsphase.KubeadmCertEtcdHealthcheck,
&certsphase.KubeadmCertEtcdAPIClient,
} {
if err := cert.CreateFromCA(newcfg, caCert, caKey); err != nil {
t.Fatalf("couldn't create certificate %s: %v", cert.Name, err)
}
}
actualErr := StaticPodControlPlane(
waiter,
pathMgr,
@ -606,3 +626,113 @@ func TestCleanupDirs(t *testing.T) {
})
}
}
func TestRenewCerts(t *testing.T) {
caCert, caKey := certstestutil.SetupCertificateAuthorithy(t)
t.Run("all certs exist, should be rotated", func(t *testing.T) {
})
tests := []struct {
name string
component string
skipCreateCA bool
shouldErrorOnRenew bool
certsShouldExist []*certsphase.KubeadmCert
}{
{
name: "all certs exist, should be rotated",
component: constants.Etcd,
certsShouldExist: []*certsphase.KubeadmCert{
&certsphase.KubeadmCertEtcdServer,
&certsphase.KubeadmCertEtcdPeer,
&certsphase.KubeadmCertEtcdHealthcheck,
},
},
{
name: "just renew API cert",
component: constants.KubeAPIServer,
certsShouldExist: []*certsphase.KubeadmCert{
&certsphase.KubeadmCertEtcdAPIClient,
},
},
{
name: "ignores other compnonents",
skipCreateCA: true,
component: constants.KubeScheduler,
},
{
name: "missing a cert to renew",
component: constants.Etcd,
shouldErrorOnRenew: true,
certsShouldExist: []*certsphase.KubeadmCert{
&certsphase.KubeadmCertEtcdServer,
&certsphase.KubeadmCertEtcdPeer,
},
},
{
name: "no CA, cannot continue",
component: constants.Etcd,
skipCreateCA: true,
shouldErrorOnRenew: true,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
// Setup up basic requities
tmpDir := testutil.SetupTempDir(t)
defer os.RemoveAll(tmpDir)
cfg := testutil.GetDefaultInternalConfig(t)
cfg.CertificatesDir = tmpDir
if !test.skipCreateCA {
if err := pkiutil.WriteCertAndKey(tmpDir, constants.EtcdCACertAndKeyBaseName, caCert, caKey); err != nil {
t.Fatalf("couldn't write out CA: %v", err)
}
}
// Create expected certs
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)
}
}
// 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
}
// Renew everything
err := renewCerts(cfg, test.component)
if test.shouldErrorOnRenew {
if err == nil {
t.Fatal("expected renewal error, got nothing")
}
// expected error, got error
return
}
if err != nil {
t.Fatalf("couldn't renew certificates: %v", err)
}
// See if the certificate serial numbers change
for kubeCert, cert := range certMaps {
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 {
t.Errorf("certifitate %v was not reissued", kubeCert.Name)
}
}
})
}
}