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 { 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.