From 6603cf6357470e72aa66ec25a93e538d447f6972 Mon Sep 17 00:00:00 2001 From: "Lubomir I. Ivanov" Date: Wed, 8 Dec 2021 20:37:58 +0200 Subject: [PATCH 1/2] kubeadm: validate local etcd certficates during expiration checks In case stacked etcd is used, the code that does expiration checks does not validate if the etcd CA is "external" (missing key) and if the etcd CA signed certificates are valid. Add a new function UsingExternalEtcdCA() similar to existing functions for the cluster CA and front-proxy CA, that performs the checks for missing etcd CA key and certificate validity. This function only runs for stacked etcd, since if etcd is external kubeadm does not track any certs signed by that etcd CA. This fixes a bug where the etcd CA will be reported as local even if the etcd/ca.key is missing during "certs check-expiration". --- cmd/kubeadm/app/phases/certs/certs.go | 32 +++++++++++++++++++ .../app/phases/certs/renewal/manager.go | 6 +++- 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/cmd/kubeadm/app/phases/certs/certs.go b/cmd/kubeadm/app/phases/certs/certs.go index 7e8a60bce91..738fccf1743 100644 --- a/cmd/kubeadm/app/phases/certs/certs.go +++ b/cmd/kubeadm/app/phases/certs/certs.go @@ -381,6 +381,38 @@ func UsingExternalFrontProxyCA(cfg *kubeadmapi.ClusterConfiguration) (bool, erro return true, nil } +// UsingExternalEtcdCA determines whether the user is relying on an external etcd CA. We currently implicitly determine this is the case +// when the etcd CA Cert is present but the etcd CA Key is not. +// In case we are using an external etcd CA, the function validates the certificates signed by etcd CA that should be provided by the user. +func UsingExternalEtcdCA(cfg *kubeadmapi.ClusterConfiguration) (bool, error) { + if err := validateCACert(certKeyLocation{cfg.CertificatesDir, kubeadmconstants.EtcdCACertAndKeyBaseName, "", "etcd CA"}); err != nil { + return false, err + } + + path := filepath.Join(cfg.CertificatesDir, kubeadmconstants.EtcdCAKeyName) + if _, err := os.Stat(path); !os.IsNotExist(err) { + return false, nil + } + + if err := validateSignedCert(certKeyLocation{cfg.CertificatesDir, kubeadmconstants.EtcdCACertAndKeyBaseName, kubeadmconstants.APIServerEtcdClientCertAndKeyBaseName, "apiserver etcd client"}); err != nil { + return true, err + } + + if err := validateSignedCert(certKeyLocation{cfg.CertificatesDir, kubeadmconstants.EtcdCACertAndKeyBaseName, kubeadmconstants.EtcdServerCertAndKeyBaseName, "etcd server"}); err != nil { + return true, err + } + + if err := validateSignedCert(certKeyLocation{cfg.CertificatesDir, kubeadmconstants.EtcdCACertAndKeyBaseName, kubeadmconstants.EtcdPeerCertAndKeyBaseName, "etcd peer"}); err != nil { + return true, err + } + + if err := validateSignedCert(certKeyLocation{cfg.CertificatesDir, kubeadmconstants.EtcdCACertAndKeyBaseName, kubeadmconstants.EtcdHealthcheckClientCertAndKeyBaseName, "etcd health-check client"}); err != nil { + return true, err + } + + return true, nil +} + // validateCACert tries to load a x509 certificate from pkiDir and validates that it is a CA func validateCACert(l certKeyLocation) error { // Check CA Cert diff --git a/cmd/kubeadm/app/phases/certs/renewal/manager.go b/cmd/kubeadm/app/phases/certs/renewal/manager.go index 09421d7e816..9f5e0d158f9 100644 --- a/cmd/kubeadm/app/phases/certs/renewal/manager.go +++ b/cmd/kubeadm/app/phases/certs/renewal/manager.go @@ -374,7 +374,11 @@ func (rm *Manager) IsExternallyManaged(caBaseName string) (bool, error) { } return externallyManaged, nil case kubeadmconstants.EtcdCACertAndKeyBaseName: - return false, nil + externallyManaged, err := certsphase.UsingExternalEtcdCA(rm.cfg) + if err != nil { + return false, errors.Wrapf(err, "Error checking external CA condition for %s certificate authority", caBaseName) + } + return externallyManaged, nil default: return false, errors.Errorf("unknown certificate authority %s", caBaseName) } From c78afc695b903405f318082521e6b9b7596a7a77 Mon Sep 17 00:00:00 2001 From: "Lubomir I. Ivanov" Date: Wed, 8 Dec 2021 20:50:23 +0200 Subject: [PATCH 2/2] kubeadm: print the CA of kubeconfig files in "check expiration" Apply a small fix to ensure the kubeconfig files that kubeadm manages have a CA when printed in the table of the "check expiration" command. "CAName" is the field used for that. In practice kubeconfig files can contain multiple credentials from different CAs, but this is not supported by kubeadm and there is a single cluster CA that signs the single client cert/key in kubeadm managed kubeconfigs. --- cmd/kubeadm/app/phases/certs/renewal/manager.go | 1 + 1 file changed, 1 insertion(+) diff --git a/cmd/kubeadm/app/phases/certs/renewal/manager.go b/cmd/kubeadm/app/phases/certs/renewal/manager.go index 9f5e0d158f9..764bcb01a80 100644 --- a/cmd/kubeadm/app/phases/certs/renewal/manager.go +++ b/cmd/kubeadm/app/phases/certs/renewal/manager.go @@ -166,6 +166,7 @@ func NewManager(cfg *kubeadmapi.ClusterConfiguration, kubernetesDir string) (*Ma LongName: kubeConfig.longName, FileName: kubeConfig.fileName, CABaseName: kubeadmconstants.CACertAndKeyBaseName, // all certificates in kubeConfig files are signed by the Kubernetes CA + CAName: kubeadmconstants.CACertAndKeyBaseName, readwriter: kubeConfigReadWriter, } }