From 05b77fe99f5c57bad36a74c2e1986f678ac44a01 Mon Sep 17 00:00:00 2001 From: "Lubomir I. Ivanov" Date: Wed, 23 Sep 2020 03:27:50 +0300 Subject: [PATCH 1/2] kubeadm: warn but do not error out on missing CA keys on CP join - Modify validateCACertAndKey() to print warnings for missing keys instead of erroring out. - Update unit tests. This allows doing a CP node join in a case where the user has: - copied shared certificates to the new CP node, but not copied ca.key files, treating the cluster CAs as external - signed other required certificates in advance --- cmd/kubeadm/app/phases/certs/certs.go | 5 +++-- cmd/kubeadm/app/phases/certs/certs_test.go | 24 +++++++++++++++++++++- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/cmd/kubeadm/app/phases/certs/certs.go b/cmd/kubeadm/app/phases/certs/certs.go index 094f4303375..fb3f372e1cf 100644 --- a/cmd/kubeadm/app/phases/certs/certs.go +++ b/cmd/kubeadm/app/phases/certs/certs.go @@ -286,6 +286,7 @@ type certKeyLocation struct { // SharedCertificateExists verifies if the shared certificates - the certificates that must be // equal across control-plane nodes: ca.key, ca.crt, sa.key, sa.pub + etcd/ca.key, etcd/ca.crt if local/stacked etcd +// Missing keys are non-fatal and produce warnings. func SharedCertificateExists(cfg *kubeadmapi.ClusterConfiguration) (bool, error) { if err := validateCACertAndKey(certKeyLocation{cfg.CertificatesDir, kubeadmconstants.CACertAndKeyBaseName, "", "CA"}); err != nil { @@ -373,7 +374,7 @@ func validateCACert(l certKeyLocation) error { } // validateCACertAndKey tries to load a x509 certificate and private key from pkiDir, -// and validates that the cert is a CA +// and validates that the cert is a CA. Failure to load the key produces a warning. func validateCACertAndKey(l certKeyLocation) error { if err := validateCACert(l); err != nil { return err @@ -381,7 +382,7 @@ func validateCACertAndKey(l certKeyLocation) error { _, err := pkiutil.TryLoadKeyFromDisk(l.pkiDir, l.caBaseName) if err != nil { - return errors.Wrapf(err, "failure loading key for %s", l.uxName) + klog.Warningf("assuming external key for %s: %v", l.uxName, err) } return nil } diff --git a/cmd/kubeadm/app/phases/certs/certs_test.go b/cmd/kubeadm/app/phases/certs/certs_test.go index c1c43e3e9b8..e0e9436a4e4 100644 --- a/cmd/kubeadm/app/phases/certs/certs_test.go +++ b/cmd/kubeadm/app/phases/certs/certs_test.go @@ -401,6 +401,19 @@ func TestSharedCertificateExists(t *testing.T) { }, expectedError: true, }, + { + name: "missing ca.key", + files: certstestutil.PKIFiles{ + "ca.crt": caCert, + "front-proxy-ca.crt": caCert, + "front-proxy-ca.key": caKey, + "sa.pub": publicKey, + "sa.key": key, + "etcd/ca.crt": caCert, + "etcd/ca.key": caKey, + }, + expectedError: false, + }, { name: "missing sa.key", files: certstestutil.PKIFiles{ @@ -642,7 +655,7 @@ func TestValidateMethods(t *testing.T) { name: "validateCACertAndKey (key missing)", validateFunc: validateCACertAndKey, loc: certKeyLocation{caBaseName: "ca", baseName: "", uxName: "CA"}, - expectedSuccess: false, + expectedSuccess: true, }, { name: "validateSignedCert", @@ -666,6 +679,15 @@ func TestValidateMethods(t *testing.T) { loc: certKeyLocation{baseName: "sa", uxName: "service account"}, expectedSuccess: true, }, + { + name: "validatePrivatePublicKey (missing key)", + files: certstestutil.PKIFiles{ + "sa.pub": key.Public(), + }, + validateFunc: validatePrivatePublicKey, + loc: certKeyLocation{baseName: "sa", uxName: "service account"}, + expectedSuccess: false, + }, } for _, test := range tests { From 7c783fa374e1a48d88409ee7f9bd01836c40542f Mon Sep 17 00:00:00 2001 From: "Lubomir I. Ivanov" Date: Wed, 23 Sep 2020 04:18:29 +0300 Subject: [PATCH 2/2] kubeadm: make the CP join handling of kubeconfig similar to "init" The kubeconfig phase of "kubeadm init" detects external CA mode and skips the generation of kubeconfig files. The kubeconfig handling during control-plane join executes CreateJoinControlPlaneKubeConfigFiles() which requires the presence of ca.key when preparing the spec of a kubeconfig file and prevents usage of external CA mode. Modify CreateJoinControlPlaneKubeConfigFiles() to skip generating the kubeconfig files if external CA mode is detected. --- .../app/phases/kubeconfig/kubeconfig.go | 26 +++++++++++++++---- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/cmd/kubeadm/app/phases/kubeconfig/kubeconfig.go b/cmd/kubeadm/app/phases/kubeconfig/kubeconfig.go index 51bd42eec2b..008265481d8 100644 --- a/cmd/kubeadm/app/phases/kubeconfig/kubeconfig.go +++ b/cmd/kubeadm/app/phases/kubeconfig/kubeconfig.go @@ -67,15 +67,31 @@ type kubeConfigSpec struct { // CreateJoinControlPlaneKubeConfigFiles will create and write to disk the kubeconfig files required by kubeadm // join --control-plane workflow, plus the admin kubeconfig file used by the administrator and kubeadm itself; the // kubelet.conf file must not be created because it will be created and signed by the kubelet TLS bootstrap process. -// If any kubeconfig files already exists, it used only if evaluated equal; otherwise an error is returned. +// When not using external CA mode, if a kubeconfig file already exists it is used only if evaluated equal, +// otherwise an error is returned. For external CA mode, the creation of kubeconfig files is skipped. func CreateJoinControlPlaneKubeConfigFiles(outDir string, cfg *kubeadmapi.InitConfiguration) error { - return createKubeConfigFiles( - outDir, - cfg, + var externaCA bool + caKeyPath := filepath.Join(cfg.CertificatesDir, kubeadmconstants.CAKeyName) + if _, err := os.Stat(caKeyPath); os.IsNotExist(err) { + externaCA = true + } + + files := []string{ kubeadmconstants.AdminKubeConfigFileName, kubeadmconstants.ControllerManagerKubeConfigFileName, kubeadmconstants.SchedulerKubeConfigFileName, - ) + } + + for _, file := range files { + if externaCA { + fmt.Printf("[kubeconfig] External CA mode: Using user provided %s\n", file) + continue + } + if err := createKubeConfigFiles(outDir, cfg, file); err != nil { + return err + } + } + return nil } // CreateKubeConfigFile creates a kubeconfig file.