diff --git a/cmd/kubeadm/app/phases/certs/certlist.go b/cmd/kubeadm/app/phases/certs/certlist.go index 4874c2e10dc..54f473c03f1 100644 --- a/cmd/kubeadm/app/phases/certs/certlist.go +++ b/cmd/kubeadm/app/phases/certs/certlist.go @@ -62,7 +62,7 @@ func (k *KubeadmCert) CreateFromCA(ic *kubeadmapi.InitConfiguration, caCert *x50 if err != nil { return err } - writeCertificateFilesIfNotExist( + err = writeCertificateFilesIfNotExist( ic.CertificatesDir, k.BaseName, caCert, @@ -70,6 +70,10 @@ func (k *KubeadmCert) CreateFromCA(ic *kubeadmapi.InitConfiguration, caCert *x50 key, ) + if err != nil { + return fmt.Errorf("failed to write certificate %q: %v", k.Name, err) + } + return nil } @@ -108,9 +112,48 @@ func (t CertificateTree) CreateTree(ic *kubeadmapi.InitConfiguration) error { return err } - caCert, caKey, err := NewCACertAndKey(cfg) - if err != nil { - return err + var caKey *rsa.PrivateKey + + caCert, err := pkiutil.TryLoadCertFromDisk(ic.CertificatesDir, ca.BaseName) + if err == nil { + // Cert exists already, make sure it's valid + if !caCert.IsCA { + return fmt.Errorf("certificate %q is not a CA", ca.Name) + } + // Try and load a CA Key + caKey, err = pkiutil.TryLoadKeyFromDisk(ic.CertificatesDir, ca.BaseName) + if err != nil { + // If there's no CA key, make sure every certificate exists. + for _, leaf := range leaves { + cl := certKeyLocation{ + pkiDir: ic.CertificatesDir, + baseName: leaf.BaseName, + uxName: leaf.Name, + } + if err := validateSignedCertWithCA(cl, caCert); err != nil { + return fmt.Errorf("could not load expected certificate %q or validate the existence of key %q for it: %v", leaf.Name, ca.Name, err) + } + } + // CACert exists and all clients exist, continue to next CA. + continue + } + // CA key exists; just use that to create new certificates. + } else { + // CACert doesn't already exist, create a new cert and key. + caCert, caKey, err = NewCACertAndKey(cfg) + if err != nil { + return err + } + + err = writeCertificateAuthorithyFilesIfNotExist( + ic.CertificatesDir, + ca.BaseName, + caCert, + caKey, + ) + if err != nil { + return err + } } for _, leaf := range leaves { @@ -118,16 +161,6 @@ func (t CertificateTree) CreateTree(ic *kubeadmapi.InitConfiguration) error { return err } } - - err = writeCertificateAuthorithyFilesIfNotExist( - ic.CertificatesDir, - ca.BaseName, - caCert, - caKey, - ) - if err != nil { - return err - } } return nil } diff --git a/cmd/kubeadm/app/phases/certs/certs.go b/cmd/kubeadm/app/phases/certs/certs.go index 60ddf4d6b7b..20bf37c4429 100644 --- a/cmd/kubeadm/app/phases/certs/certs.go +++ b/cmd/kubeadm/app/phases/certs/certs.go @@ -396,6 +396,11 @@ func validateSignedCert(l certKeyLocation) error { return fmt.Errorf("failure loading certificate authority for %s: %v", l.uxName, err) } + return validateSignedCertWithCA(l, caCert) +} + +// validateSignedCertWithCA tries to load a certificate and validate it with the given caCert +func validateSignedCertWithCA(l certKeyLocation, caCert *x509.Certificate) error { // Try to load key and signed certificate signedCert, _, err := pkiutil.TryLoadCertAndKeyFromDisk(l.pkiDir, l.baseName) if err != nil { diff --git a/cmd/kubeadm/app/phases/certs/certs_test.go b/cmd/kubeadm/app/phases/certs/certs_test.go index 85697444448..cd936b01a11 100644 --- a/cmd/kubeadm/app/phases/certs/certs_test.go +++ b/cmd/kubeadm/app/phases/certs/certs_test.go @@ -374,6 +374,89 @@ func TestSharedCertificateExists(t *testing.T) { } } +func TestCreatePKIAssetsWithSparseCerts(t *testing.T) { + caCert, caKey := createCACert(t) + fpCACert, fpCAKey := createCACert(t) + etcdCACert, etcdCAKey := createCACert(t) + + fpCert, fpKey := createTestCert(t, fpCACert, fpCAKey) + + tests := []struct { + name string + files pkiFiles + expectError bool + }{ + { + name: "nothing present", + }, + { + name: "CAs already exist", + files: pkiFiles{ + "ca.crt": caCert, + "ca.key": caKey, + "front-proxy-ca.crt": fpCACert, + "front-proxy-ca.key": fpCAKey, + "etcd/ca.crt": etcdCACert, + "etcd/ca.key": etcdCAKey, + }, + }, + { + name: "CA certs only", + files: pkiFiles{ + "ca.crt": caCert, + "front-proxy-ca.crt": fpCACert, + "etcd/ca.crt": etcdCACert, + }, + expectError: true, + }, + { + name: "FrontProxyCA with certs", + files: pkiFiles{ + "ca.crt": caCert, + "ca.key": caKey, + "front-proxy-ca.crt": fpCACert, + "front-proxy-client.crt": fpCert, + "front-proxy-client.key": fpKey, + "etcd/ca.crt": etcdCACert, + "etcd/ca.key": etcdCAKey, + }, + }, + { + name: "FrontProxy certs missing CA", + files: pkiFiles{ + "front-proxy-client.crt": fpCert, + "front-proxy-client.key": fpKey, + }, + expectError: true, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + tmpdir := testutil.SetupTempDir(t) + defer os.RemoveAll(tmpdir) + + cfg := testutil.GetDefaultInternalConfig(t) + cfg.ClusterConfiguration.CertificatesDir = tmpdir + + writePKIFiles(t, tmpdir, test.files) + + err := CreatePKIAssets(cfg) + if err != nil { + if test.expectError { + return + } + t.Fatalf("Unexpected error: %v", err) + } + if test.expectError { + t.Fatal("Expected error from CreatePKIAssets, got none") + } + assertCertsExist(t, tmpdir) + }) + } + +} + func TestUsingExternalCA(t *testing.T) { tests := []struct { setupFuncs []func(cfg *kubeadmapi.InitConfiguration) error @@ -610,3 +693,24 @@ func deleteFrontProxyCAKey(cfg *kubeadmapi.InitConfiguration) error { } return nil } + +func assertCertsExist(t *testing.T, dir string) { + tree, err := GetDefaultCertList().AsMap().CertTree() + if err != nil { + t.Fatalf("unexpected error getting certificates: %v", err) + } + + for caCert, certs := range tree { + if err := validateCACert(certKeyLocation{dir, caCert.BaseName, "", caCert.Name}); err != nil { + t.Errorf("couldn't validate CA certificate %v: %v", caCert.Name, err) + // Don't bother validating child certs, but do try the other CAs + continue + } + + for _, cert := range certs { + if err := validateSignedCert(certKeyLocation{dir, caCert.BaseName, cert.BaseName, cert.Name}); err != nil { + t.Errorf("couldn't validate certificate %v: %v", cert.Name, err) + } + } + } +} diff --git a/cmd/kubeadm/test/BUILD b/cmd/kubeadm/test/BUILD index fc3b5745d50..c9d6b94b3f5 100644 --- a/cmd/kubeadm/test/BUILD +++ b/cmd/kubeadm/test/BUILD @@ -11,8 +11,10 @@ go_library( importpath = "k8s.io/kubernetes/cmd/kubeadm/test", deps = [ "//cmd/kubeadm/app/apis/kubeadm:go_default_library", + "//cmd/kubeadm/app/apis/kubeadm/v1alpha3:go_default_library", "//cmd/kubeadm/app/constants:go_default_library", "//cmd/kubeadm/app/phases/certs/pkiutil:go_default_library", + "//cmd/kubeadm/app/util/config:go_default_library", "//cmd/kubeadm/test/certs:go_default_library", "//vendor/github.com/renstrom/dedent:go_default_library", ], diff --git a/cmd/kubeadm/test/util.go b/cmd/kubeadm/test/util.go index 784f1d27fc4..1b55bdec405 100644 --- a/cmd/kubeadm/test/util.go +++ b/cmd/kubeadm/test/util.go @@ -26,8 +26,10 @@ import ( "github.com/renstrom/dedent" kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" + kubeadmapiv1alpha3 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha3" kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants" "k8s.io/kubernetes/cmd/kubeadm/app/phases/certs/pkiutil" + configutil "k8s.io/kubernetes/cmd/kubeadm/app/util/config" certtestutil "k8s.io/kubernetes/cmd/kubeadm/test/certs" ) @@ -140,3 +142,13 @@ func AssertFileExists(t *testing.T, dirName string, fileNames ...string) { } } } + +// GetDefaultInternalConfig returns a defaulted kubeadmapi.InitConfiguration +func GetDefaultInternalConfig(t *testing.T) *kubeadmapi.InitConfiguration { + internalcfg, err := configutil.ConfigFileAndDefaultsToInternalConfig("", &kubeadmapiv1alpha3.InitConfiguration{}) + if err != nil { + t.Fatalf("unexpected error getting default config: %v", err) + } + + return internalcfg +}