From 57712220a1462eeedd4787a2a5bd9bde8e3ebb3d Mon Sep 17 00:00:00 2001 From: Richard Wall Date: Wed, 8 Jul 2020 11:38:00 +0100 Subject: [PATCH] Add new helper functions for creating keys, kubeconfig and CSR files Signed-off-by: Richard Wall --- cmd/kubeadm/app/phases/certs/certlist.go | 65 ++++++ .../app/phases/kubeconfig/kubeconfig.go | 190 +++++++++++++----- cmd/kubeadm/app/util/pkiutil/pki_helpers.go | 20 +- 3 files changed, 218 insertions(+), 57 deletions(-) diff --git a/cmd/kubeadm/app/phases/certs/certlist.go b/cmd/kubeadm/app/phases/certs/certlist.go index 4bbbd28d2ce..7b1a57c02fb 100644 --- a/cmd/kubeadm/app/phases/certs/certlist.go +++ b/cmd/kubeadm/app/phases/certs/certlist.go @@ -28,6 +28,11 @@ import ( "k8s.io/kubernetes/cmd/kubeadm/app/util/pkiutil" ) +const ( + errInvalid = "invalid argument" + errExist = "file already exists" +) + type configMutatorsFunc func(*kubeadmapi.InitConfiguration, *pkiutil.CertConfig) error // KubeadmCert represents a certificate that Kubeadm will create to function properly. @@ -397,3 +402,63 @@ func setCommonNameToNodeName() configMutatorsFunc { return nil } } + +// leafCertificates returns non-CA certificates from the supplied Certificates. +func leafCertificates(c Certificates) (Certificates, error) { + certTree, err := c.AsMap().CertTree() + if err != nil { + return nil, err + } + + var out Certificates + for _, leafCertificates := range certTree { + out = append(out, leafCertificates...) + } + return out, nil +} + +func createKeyAndCSR(kubeadmConfig *kubeadmapi.InitConfiguration, cert *KubeadmCert) error { + if kubeadmConfig == nil { + return errors.Errorf("%s: kubeadmConfig was nil", errInvalid) + } + if cert == nil { + return errors.Errorf("%s: cert was nil", errInvalid) + } + certDir := kubeadmConfig.CertificatesDir + name := cert.BaseName + if pkiutil.CSROrKeyExist(certDir, name) { + return errors.Errorf("%s: key or CSR %s/%s", errExist, certDir, name) + } + cfg, err := cert.GetConfig(kubeadmConfig) + if err != nil { + return err + } + csr, key, err := pkiutil.NewCSRAndKey(cfg) + if err != nil { + return err + } + err = pkiutil.WriteKey(certDir, name, key) + if err != nil { + return err + } + err = pkiutil.WriteCSR(certDir, name, csr) + if err != nil { + return err + } + return nil +} + +// CreateDefaultKeysAndCSRFiles is used in ExternalCA mode to create key files +// and adjacent CSR files. +func CreateDefaultKeysAndCSRFiles(config *kubeadmapi.InitConfiguration) error { + certificates, err := leafCertificates(GetDefaultCertList()) + if err != nil { + return err + } + for _, cert := range certificates { + if err := createKeyAndCSR(config, cert); err != nil { + return err + } + } + return nil +} diff --git a/cmd/kubeadm/app/phases/kubeconfig/kubeconfig.go b/cmd/kubeadm/app/phases/kubeconfig/kubeconfig.go index fd165e07357..dbc2aed5b16 100644 --- a/cmd/kubeadm/app/phases/kubeconfig/kubeconfig.go +++ b/cmd/kubeadm/app/phases/kubeconfig/kubeconfig.go @@ -26,6 +26,7 @@ import ( "path/filepath" "github.com/pkg/errors" + "k8s.io/client-go/tools/clientcmd" clientcmdapi "k8s.io/client-go/tools/clientcmd/api" certutil "k8s.io/client-go/util/cert" @@ -34,9 +35,13 @@ import ( kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants" kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util" - pkiutil "k8s.io/kubernetes/cmd/kubeadm/app/util/pkiutil" - kubeconfigutil "k8s.io/kubernetes/cmd/kubeadm/app/util/kubeconfig" + pkiutil "k8s.io/kubernetes/cmd/kubeadm/app/util/pkiutil" +) + +const ( + errInvalid = "invalid argument" + errExist = "file already exists" ) // clientCertAuth struct holds info required to build a client certificate to provide authentication info in a kubeconfig object @@ -103,7 +108,7 @@ func createKubeConfigFiles(outDir string, cfg *kubeadmapi.InitConfiguration, kub return err } - // writes the kubeconfig to disk if it not exists + // writes the kubeconfig to disk if it does not exist if err = createKubeConfigFileIfNotExists(outDir, kubeConfigFileName, config); err != nil { return err } @@ -113,57 +118,21 @@ func createKubeConfigFiles(outDir string, cfg *kubeadmapi.InitConfiguration, kub } // getKubeConfigSpecs returns all KubeConfigSpecs actualized to the context of the current InitConfiguration -// NB. this methods holds the information about how kubeadm creates kubeconfig files. +// NB. this method holds the information about how kubeadm creates kubeconfig files. func getKubeConfigSpecs(cfg *kubeadmapi.InitConfiguration) (map[string]*kubeConfigSpec, error) { - caCert, caKey, err := pkiutil.TryLoadCertAndKeyFromDisk(cfg.CertificatesDir, kubeadmconstants.CACertAndKeyBaseName) if err != nil { return nil, errors.Wrap(err, "couldn't create a kubeconfig; the CA files couldn't be loaded") } - - controlPlaneEndpoint, err := kubeadmutil.GetControlPlaneEndpoint(cfg.ControlPlaneEndpoint, &cfg.LocalAPIEndpoint) + configs, err := getKubeConfigSpecsBase(cfg) if err != nil { return nil, err } - - var kubeConfigSpec = map[string]*kubeConfigSpec{ - kubeadmconstants.AdminKubeConfigFileName: { - CACert: caCert, - APIServer: controlPlaneEndpoint, - ClientName: "kubernetes-admin", - ClientCertAuth: &clientCertAuth{ - CAKey: caKey, - Organizations: []string{kubeadmconstants.SystemPrivilegedGroup}, - }, - }, - kubeadmconstants.KubeletKubeConfigFileName: { - CACert: caCert, - APIServer: controlPlaneEndpoint, - ClientName: fmt.Sprintf("%s%s", kubeadmconstants.NodesUserPrefix, cfg.NodeRegistration.Name), - ClientCertAuth: &clientCertAuth{ - CAKey: caKey, - Organizations: []string{kubeadmconstants.NodesGroup}, - }, - }, - kubeadmconstants.ControllerManagerKubeConfigFileName: { - CACert: caCert, - APIServer: controlPlaneEndpoint, - ClientName: kubeadmconstants.ControllerManagerUser, - ClientCertAuth: &clientCertAuth{ - CAKey: caKey, - }, - }, - kubeadmconstants.SchedulerKubeConfigFileName: { - CACert: caCert, - APIServer: controlPlaneEndpoint, - ClientName: kubeadmconstants.SchedulerUser, - ClientCertAuth: &clientCertAuth{ - CAKey: caKey, - }, - }, + for _, spec := range configs { + spec.CACert = caCert + spec.ClientCertAuth.CAKey = caKey } - - return kubeConfigSpec, nil + return configs, nil } // buildKubeConfigFromSpec creates a kubeconfig object for the given kubeConfigSpec @@ -182,13 +151,8 @@ func buildKubeConfigFromSpec(spec *kubeConfigSpec, clustername string) (*clientc } // otherwise, create a client certs - clientCertConfig := pkiutil.CertConfig{ - Config: certutil.Config{ - CommonName: spec.ClientName, - Organization: spec.ClientCertAuth.Organizations, - Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, - }, - } + clientCertConfig := newClientCertConfigFromKubeConfigSpec(spec) + clientCert, clientKey, err := pkiutil.NewCertAndKey(spec.CACert, spec.ClientCertAuth.CAKey, &clientCertConfig) if err != nil { return nil, errors.Wrapf(err, "failure while creating %s client certificate", spec.ClientName) @@ -209,6 +173,16 @@ func buildKubeConfigFromSpec(spec *kubeConfigSpec, clustername string) (*clientc ), nil } +func newClientCertConfigFromKubeConfigSpec(spec *kubeConfigSpec) pkiutil.CertConfig { + return pkiutil.CertConfig{ + Config: certutil.Config{ + CommonName: spec.ClientName, + Organization: spec.ClientCertAuth.Organizations, + Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, + }, + } +} + // validateKubeConfig check if the kubeconfig file exist and has the expected CA and server URL func validateKubeConfig(outDir, filename string, config *clientcmdapi.Config) error { kubeConfigFilePath := filepath.Join(outDir, filename) @@ -386,3 +360,115 @@ func ValidateKubeconfigsForExternalCA(outDir string, cfg *kubeadmapi.InitConfigu } return nil } + +func getKubeConfigSpecsBase(cfg *kubeadmapi.InitConfiguration) (map[string]*kubeConfigSpec, error) { + apiServer, err := kubeadmutil.GetControlPlaneEndpoint(cfg.ControlPlaneEndpoint, &cfg.LocalAPIEndpoint) + if err != nil { + return nil, err + } + return map[string]*kubeConfigSpec{ + kubeadmconstants.AdminKubeConfigFileName: { + APIServer: apiServer, + ClientName: "kubernetes-admin", + ClientCertAuth: &clientCertAuth{ + Organizations: []string{kubeadmconstants.SystemPrivilegedGroup}, + }, + }, + kubeadmconstants.KubeletKubeConfigFileName: { + APIServer: apiServer, + ClientName: fmt.Sprintf("%s%s", kubeadmconstants.NodesUserPrefix, cfg.NodeRegistration.Name), + ClientCertAuth: &clientCertAuth{ + Organizations: []string{kubeadmconstants.NodesGroup}, + }, + }, + kubeadmconstants.ControllerManagerKubeConfigFileName: { + APIServer: apiServer, + ClientName: kubeadmconstants.ControllerManagerUser, + ClientCertAuth: &clientCertAuth{}, + }, + kubeadmconstants.SchedulerKubeConfigFileName: { + APIServer: apiServer, + ClientName: kubeadmconstants.SchedulerUser, + ClientCertAuth: &clientCertAuth{}, + }, + }, nil +} + +func createKubeConfigAndCSR(kubeConfigDir string, kubeadmConfig *kubeadmapi.InitConfiguration, name string, spec *kubeConfigSpec) error { + if kubeConfigDir == "" { + return errors.Errorf("%s: kubeConfigDir was empty", errInvalid) + } + if kubeadmConfig == nil { + return errors.Errorf("%s: kubeadmConfig was nil", errInvalid) + } + if name == "" { + return errors.Errorf("%s: name was empty", errInvalid) + } + if spec == nil { + return errors.Errorf("%s: spec was nil", errInvalid) + } + kubeConfigPath := filepath.Join(kubeConfigDir, name) + if _, err := os.Stat(kubeConfigPath); err == nil { + return errors.Errorf("%s: kube config: %s", errExist, kubeConfigPath) + } else if !os.IsNotExist(err) { + return errors.Wrapf(err, "unexpected error while checking if file exists: %s", kubeConfigPath) + } + if pkiutil.CSROrKeyExist(kubeConfigDir, name) { + return errors.Errorf("%s: csr: %s", errExist, kubeConfigPath) + } + + clientCertConfig := newClientCertConfigFromKubeConfigSpec(spec) + + clientKey, err := pkiutil.NewPrivateKey(clientCertConfig.PublicKeyAlgorithm) + if err != nil { + return err + } + clientCSR, err := pkiutil.NewCSR(clientCertConfig, clientKey) + if err != nil { + return err + } + + encodedClientKey, err := keyutil.MarshalPrivateKeyToPEM(clientKey) + if err != nil { + return err + } + + var ( + emptyCACert []byte + emptyClientCert []byte + ) + + // create a kubeconfig with the client certs + config := kubeconfigutil.CreateWithCerts( + spec.APIServer, + kubeadmConfig.ClusterName, + spec.ClientName, + emptyCACert, + encodedClientKey, + emptyClientCert, + ) + + if err := kubeconfigutil.WriteToDisk(kubeConfigPath, config); err != nil { + return err + } + // Write CSR to disk + if err := pkiutil.WriteCSR(kubeConfigDir, name, clientCSR); err != nil { + return err + } + return nil +} + +// CreateDefaultKubeConfigsAndCSRFiles is used in ExternalCA mode to create +// kubeconfig files and adjacent CSR files. +func CreateDefaultKubeConfigsAndCSRFiles(kubeConfigDir string, kubeadmConfig *kubeadmapi.InitConfiguration) error { + kubeConfigs, err := getKubeConfigSpecsBase(kubeadmConfig) + if err != nil { + return err + } + for name, spec := range kubeConfigs { + if err := createKubeConfigAndCSR(kubeConfigDir, kubeadmConfig, name, spec); err != nil { + return err + } + } + return nil +} diff --git a/cmd/kubeadm/app/util/pkiutil/pki_helpers.go b/cmd/kubeadm/app/util/pkiutil/pki_helpers.go index dbcab85bf3f..a2d103ccfa8 100644 --- a/cmd/kubeadm/app/util/pkiutil/pki_helpers.go +++ b/cmd/kubeadm/app/util/pkiutil/pki_helpers.go @@ -290,16 +290,14 @@ func TryLoadKeyFromDisk(pkiPath, name string) (crypto.Signer, error) { // TryLoadCSRAndKeyFromDisk tries to load the CSR and key from the disk func TryLoadCSRAndKeyFromDisk(pkiPath, name string) (*x509.CertificateRequest, crypto.Signer, error) { - csrPath := pathForCSR(pkiPath, name) - - csr, err := CertificateRequestFromFile(csrPath) + csr, err := TryLoadCSRFromDisk(pkiPath, name) if err != nil { - return nil, nil, errors.Wrapf(err, "couldn't load the certificate request %s", csrPath) + return nil, nil, errors.Wrap(err, "could not load CSR file") } key, err := TryLoadKeyFromDisk(pkiPath, name) if err != nil { - return nil, nil, errors.Wrap(err, "couldn't load key file") + return nil, nil, errors.Wrap(err, "could not load key file") } return csr, key, nil @@ -334,6 +332,18 @@ func TryLoadPrivatePublicKeyFromDisk(pkiPath, name string) (*rsa.PrivateKey, *rs return k, p, nil } +// TryLoadCSRFromDisk tries to load the CSR from the disk +func TryLoadCSRFromDisk(pkiPath, name string) (*x509.CertificateRequest, error) { + csrPath := pathForCSR(pkiPath, name) + + csr, err := CertificateRequestFromFile(csrPath) + if err != nil { + return nil, errors.Wrapf(err, "could not load the CSR %s", csrPath) + } + + return csr, nil +} + // PathsForCertAndKey returns the paths for the certificate and key given the path and basename. func PathsForCertAndKey(pkiPath, name string) (string, string) { return pathForCert(pkiPath, name), pathForKey(pkiPath, name)