From 109f5db5a33ebbf3daa7cf7deea33fe078dbf3a7 Mon Sep 17 00:00:00 2001 From: Dmitry Rozhkov Date: Fri, 21 Feb 2020 16:43:37 +0200 Subject: [PATCH] kubeadm: allow creating a cluster with ECDSA keys The selected key type is defined by kubeadm's --feature-gates option: if it contains PublicKeysECDSA=true then ECDSA keys will be generated and used. By default RSA keys are used still. Signed-off-by: Dmitry Rozhkov --- cmd/kubeadm/app/apis/kubeadm/BUILD | 1 + cmd/kubeadm/app/apis/kubeadm/types.go | 12 +++ cmd/kubeadm/app/cmd/phases/init/certs.go | 2 +- cmd/kubeadm/app/features/BUILD | 2 +- cmd/kubeadm/app/features/features.go | 5 +- cmd/kubeadm/app/features/features_test.go | 6 +- cmd/kubeadm/app/phases/certs/certlist.go | 95 +++++++++++-------- cmd/kubeadm/app/phases/certs/certlist_test.go | 13 ++- cmd/kubeadm/app/phases/certs/certs.go | 11 +-- cmd/kubeadm/app/phases/certs/certs_test.go | 8 +- .../app/phases/certs/renewal/apirenewer.go | 4 +- .../phases/certs/renewal/apirenewer_test.go | 28 +++--- .../app/phases/certs/renewal/filerenewer.go | 3 +- .../phases/certs/renewal/filerenewer_test.go | 13 ++- .../app/phases/certs/renewal/manager.go | 19 +++- .../app/phases/certs/renewal/manager_test.go | 20 ++-- .../phases/certs/renewal/readwriter_test.go | 16 ++-- .../app/phases/kubeconfig/kubeconfig.go | 10 +- cmd/kubeadm/app/util/certs/util.go | 18 ++-- cmd/kubeadm/app/util/pkiutil/pki_helpers.go | 31 ++++-- .../app/util/pkiutil/pki_helpers_test.go | 53 ++++++++--- cmd/kubeadm/test/cmd/init_test.go | 4 + 22 files changed, 241 insertions(+), 133 deletions(-) diff --git a/cmd/kubeadm/app/apis/kubeadm/BUILD b/cmd/kubeadm/app/apis/kubeadm/BUILD index 13f4762fbc4..ccdd7227c6e 100644 --- a/cmd/kubeadm/app/apis/kubeadm/BUILD +++ b/cmd/kubeadm/app/apis/kubeadm/BUILD @@ -19,6 +19,7 @@ go_library( ], importpath = "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm", deps = [ + "//cmd/kubeadm/app/features:go_default_library", "//staging/src/k8s.io/api/core/v1:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library", diff --git a/cmd/kubeadm/app/apis/kubeadm/types.go b/cmd/kubeadm/app/apis/kubeadm/types.go index 19074702338..765a6a08f24 100644 --- a/cmd/kubeadm/app/apis/kubeadm/types.go +++ b/cmd/kubeadm/app/apis/kubeadm/types.go @@ -17,8 +17,11 @@ limitations under the License. package kubeadm import ( + "crypto/x509" + v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/kubernetes/cmd/kubeadm/app/features" "k8s.io/apimachinery/pkg/runtime/schema" ) @@ -400,6 +403,15 @@ func (cfg *ClusterConfiguration) GetControlPlaneImageRepository() string { return cfg.ImageRepository } +// PublicKeyAlgorithm returns the type of encryption keys used in the cluster. +func (cfg *ClusterConfiguration) PublicKeyAlgorithm() x509.PublicKeyAlgorithm { + if features.Enabled(cfg.FeatureGates, features.PublicKeysECDSA) { + return x509.ECDSA + } + + return x509.RSA +} + // HostPathMount contains elements describing volumes that are mounted from the // host. type HostPathMount struct { diff --git a/cmd/kubeadm/app/cmd/phases/init/certs.go b/cmd/kubeadm/app/cmd/phases/init/certs.go index 0de2d48a15a..c7ea16044d3 100644 --- a/cmd/kubeadm/app/cmd/phases/init/certs.go +++ b/cmd/kubeadm/app/cmd/phases/init/certs.go @@ -200,7 +200,7 @@ func runCertsSa(c workflow.RunData) error { } // create the new service account key (or use existing) - return certsphase.CreateServiceAccountKeyAndPublicKeyFiles(data.CertificateWriteDir()) + return certsphase.CreateServiceAccountKeyAndPublicKeyFiles(data.CertificateWriteDir(), data.Cfg().ClusterConfiguration.PublicKeyAlgorithm()) } func runCerts(c workflow.RunData) error { diff --git a/cmd/kubeadm/app/features/BUILD b/cmd/kubeadm/app/features/BUILD index fee1cf774f8..075c4d59772 100644 --- a/cmd/kubeadm/app/features/BUILD +++ b/cmd/kubeadm/app/features/BUILD @@ -35,7 +35,7 @@ go_test( srcs = ["features_test.go"], embed = [":go_default_library"], deps = [ - "//cmd/kubeadm/app/constants:go_default_library", + "//staging/src/k8s.io/apimachinery/pkg/util/version:go_default_library", "//staging/src/k8s.io/component-base/featuregate:go_default_library", ], ) diff --git a/cmd/kubeadm/app/features/features.go b/cmd/kubeadm/app/features/features.go index dee21ecc5d3..0d2299581e5 100644 --- a/cmd/kubeadm/app/features/features.go +++ b/cmd/kubeadm/app/features/features.go @@ -30,11 +30,14 @@ import ( const ( // IPv6DualStack is expected to be alpha in v1.16 IPv6DualStack = "IPv6DualStack" + // PublicKeysECDSA is expected to be alpha in v1.19 + PublicKeysECDSA = "PublicKeysECDSA" ) // InitFeatureGates are the default feature gates for the init command var InitFeatureGates = FeatureList{ - IPv6DualStack: {FeatureSpec: featuregate.FeatureSpec{Default: false, PreRelease: featuregate.Alpha}}, + IPv6DualStack: {FeatureSpec: featuregate.FeatureSpec{Default: false, PreRelease: featuregate.Alpha}}, + PublicKeysECDSA: {FeatureSpec: featuregate.FeatureSpec{Default: false, PreRelease: featuregate.Alpha}}, } // Feature represents a feature being gated diff --git a/cmd/kubeadm/app/features/features_test.go b/cmd/kubeadm/app/features/features_test.go index 5c5ec72ffe9..81d29f3e6a8 100644 --- a/cmd/kubeadm/app/features/features_test.go +++ b/cmd/kubeadm/app/features/features_test.go @@ -20,8 +20,8 @@ import ( "reflect" "testing" + "k8s.io/apimachinery/pkg/util/version" "k8s.io/component-base/featuregate" - "k8s.io/kubernetes/cmd/kubeadm/app/constants" ) func TestKnownFeatures(t *testing.T) { @@ -129,7 +129,7 @@ func TestNewFeatureGate(t *testing.T) { func TestValidateVersion(t *testing.T) { var someFeatures = FeatureList{ "feature1": {FeatureSpec: featuregate.FeatureSpec{Default: false, PreRelease: featuregate.Beta}}, - "feature2": {FeatureSpec: featuregate.FeatureSpec{Default: true, PreRelease: featuregate.Alpha}, MinimumVersion: constants.MinimumControlPlaneVersion.WithPreRelease("alpha.1")}, + "feature2": {FeatureSpec: featuregate.FeatureSpec{Default: true, PreRelease: featuregate.Alpha}, MinimumVersion: version.MustParseSemantic("v1.17.0").WithPreRelease("alpha.1")}, } var tests = []struct { @@ -146,7 +146,7 @@ func TestValidateVersion(t *testing.T) { { name: "min version but correct value given", requestedFeatures: map[string]bool{"feature2": true}, - requestedVersion: constants.MinimumControlPlaneVersion.String(), + requestedVersion: "v1.17.0", expectedError: false, }, { diff --git a/cmd/kubeadm/app/phases/certs/certlist.go b/cmd/kubeadm/app/phases/certs/certlist.go index 2cd31b97527..4bbbd28d2ce 100644 --- a/cmd/kubeadm/app/phases/certs/certlist.go +++ b/cmd/kubeadm/app/phases/certs/certlist.go @@ -28,7 +28,7 @@ import ( "k8s.io/kubernetes/cmd/kubeadm/app/util/pkiutil" ) -type configMutatorsFunc func(*kubeadmapi.InitConfiguration, *certutil.Config) error +type configMutatorsFunc func(*kubeadmapi.InitConfiguration, *pkiutil.CertConfig) error // KubeadmCert represents a certificate that Kubeadm will create to function properly. type KubeadmCert struct { @@ -39,17 +39,18 @@ type KubeadmCert struct { // Some attributes will depend on the InitConfiguration, only known at runtime. // These functions will be run in series, passed both the InitConfiguration and a cert Config. configMutators []configMutatorsFunc - config certutil.Config + config pkiutil.CertConfig } // GetConfig returns the definition for the given cert given the provided InitConfiguration -func (k *KubeadmCert) GetConfig(ic *kubeadmapi.InitConfiguration) (*certutil.Config, error) { +func (k *KubeadmCert) GetConfig(ic *kubeadmapi.InitConfiguration) (*pkiutil.CertConfig, error) { for _, f := range k.configMutators { if err := f(ic, &k.config); err != nil { return nil, err } } + k.config.PublicKeyAlgorithm = ic.ClusterConfiguration.PublicKeyAlgorithm() return &k.config, nil } @@ -239,8 +240,10 @@ var ( Name: "ca", LongName: "self-signed Kubernetes CA to provision identities for other Kubernetes components", BaseName: kubeadmconstants.CACertAndKeyBaseName, - config: certutil.Config{ - CommonName: "kubernetes", + config: pkiutil.CertConfig{ + Config: certutil.Config{ + CommonName: "kubernetes", + }, }, } // KubeadmCertAPIServer is the definition of the cert used to serve the Kubernetes API. @@ -249,9 +252,11 @@ var ( LongName: "certificate for serving the Kubernetes API", BaseName: kubeadmconstants.APIServerCertAndKeyBaseName, CAName: "ca", - config: certutil.Config{ - CommonName: kubeadmconstants.APIServerCertCommonName, - Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + config: pkiutil.CertConfig{ + Config: certutil.Config{ + CommonName: kubeadmconstants.APIServerCertCommonName, + Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + }, }, configMutators: []configMutatorsFunc{ makeAltNamesMutator(pkiutil.GetAPIServerAltNames), @@ -263,10 +268,12 @@ var ( LongName: "certificate for the API server to connect to kubelet", BaseName: kubeadmconstants.APIServerKubeletClientCertAndKeyBaseName, CAName: "ca", - config: certutil.Config{ - CommonName: kubeadmconstants.APIServerKubeletClientCertCommonName, - Organization: []string{kubeadmconstants.SystemPrivilegedGroup}, - Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, + config: pkiutil.CertConfig{ + Config: certutil.Config{ + CommonName: kubeadmconstants.APIServerKubeletClientCertCommonName, + Organization: []string{kubeadmconstants.SystemPrivilegedGroup}, + Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, + }, }, } @@ -275,8 +282,10 @@ var ( Name: "front-proxy-ca", LongName: "self-signed CA to provision identities for front proxy", BaseName: kubeadmconstants.FrontProxyCACertAndKeyBaseName, - config: certutil.Config{ - CommonName: "front-proxy-ca", + config: pkiutil.CertConfig{ + Config: certutil.Config{ + CommonName: "front-proxy-ca", + }, }, } @@ -286,9 +295,11 @@ var ( BaseName: kubeadmconstants.FrontProxyClientCertAndKeyBaseName, LongName: "certificate for the front proxy client", CAName: "front-proxy-ca", - config: certutil.Config{ - CommonName: kubeadmconstants.FrontProxyClientCertCommonName, - Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, + config: pkiutil.CertConfig{ + Config: certutil.Config{ + CommonName: kubeadmconstants.FrontProxyClientCertCommonName, + Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, + }, }, } @@ -297,8 +308,10 @@ var ( Name: "etcd-ca", LongName: "self-signed CA to provision identities for etcd", BaseName: kubeadmconstants.EtcdCACertAndKeyBaseName, - config: certutil.Config{ - CommonName: "etcd-ca", + config: pkiutil.CertConfig{ + Config: certutil.Config{ + CommonName: "etcd-ca", + }, }, } // KubeadmCertEtcdServer is the definition of the cert used to serve etcd to clients. @@ -307,12 +320,14 @@ var ( LongName: "certificate for serving etcd", BaseName: kubeadmconstants.EtcdServerCertAndKeyBaseName, CAName: "etcd-ca", - config: certutil.Config{ - // TODO: etcd 3.2 introduced an undocumented requirement for ClientAuth usage on the - // server cert: https://github.com/coreos/etcd/issues/9785#issuecomment-396715692 - // Once the upstream issue is resolved, this should be returned to only allowing - // ServerAuth usage. - Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}, + config: pkiutil.CertConfig{ + Config: certutil.Config{ + // TODO: etcd 3.2 introduced an undocumented requirement for ClientAuth usage on the + // server cert: https://github.com/coreos/etcd/issues/9785#issuecomment-396715692 + // Once the upstream issue is resolved, this should be returned to only allowing + // ServerAuth usage. + Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}, + }, }, configMutators: []configMutatorsFunc{ makeAltNamesMutator(pkiutil.GetEtcdAltNames), @@ -325,8 +340,10 @@ var ( LongName: "certificate for etcd nodes to communicate with each other", BaseName: kubeadmconstants.EtcdPeerCertAndKeyBaseName, CAName: "etcd-ca", - config: certutil.Config{ - Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}, + config: pkiutil.CertConfig{ + Config: certutil.Config{ + Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}, + }, }, configMutators: []configMutatorsFunc{ makeAltNamesMutator(pkiutil.GetEtcdPeerAltNames), @@ -339,10 +356,12 @@ var ( LongName: "certificate for liveness probes to healthcheck etcd", BaseName: kubeadmconstants.EtcdHealthcheckClientCertAndKeyBaseName, CAName: "etcd-ca", - config: certutil.Config{ - CommonName: kubeadmconstants.EtcdHealthcheckClientCertCommonName, - Organization: []string{kubeadmconstants.SystemPrivilegedGroup}, - Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, + config: pkiutil.CertConfig{ + Config: certutil.Config{ + CommonName: kubeadmconstants.EtcdHealthcheckClientCertCommonName, + Organization: []string{kubeadmconstants.SystemPrivilegedGroup}, + Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, + }, }, } // KubeadmCertEtcdAPIClient is the definition of the cert used by the API server to access etcd. @@ -351,16 +370,18 @@ var ( LongName: "certificate the apiserver uses to access etcd", BaseName: kubeadmconstants.APIServerEtcdClientCertAndKeyBaseName, CAName: "etcd-ca", - config: certutil.Config{ - CommonName: kubeadmconstants.APIServerEtcdClientCertCommonName, - Organization: []string{kubeadmconstants.SystemPrivilegedGroup}, - Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, + config: pkiutil.CertConfig{ + Config: certutil.Config{ + CommonName: kubeadmconstants.APIServerEtcdClientCertCommonName, + Organization: []string{kubeadmconstants.SystemPrivilegedGroup}, + Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, + }, }, } ) func makeAltNamesMutator(f func(*kubeadmapi.InitConfiguration) (*certutil.AltNames, error)) configMutatorsFunc { - return func(mc *kubeadmapi.InitConfiguration, cc *certutil.Config) error { + return func(mc *kubeadmapi.InitConfiguration, cc *pkiutil.CertConfig) error { altNames, err := f(mc) if err != nil { return err @@ -371,7 +392,7 @@ func makeAltNamesMutator(f func(*kubeadmapi.InitConfiguration) (*certutil.AltNam } func setCommonNameToNodeName() configMutatorsFunc { - return func(mc *kubeadmapi.InitConfiguration, cc *certutil.Config) error { + return func(mc *kubeadmapi.InitConfiguration, cc *pkiutil.CertConfig) error { cc.CommonName = mc.NodeRegistration.Name return nil } diff --git a/cmd/kubeadm/app/phases/certs/certlist_test.go b/cmd/kubeadm/app/phases/certs/certlist_test.go index 345c589cb03..c1d99bc85a4 100644 --- a/cmd/kubeadm/app/phases/certs/certlist_test.go +++ b/cmd/kubeadm/app/phases/certs/certlist_test.go @@ -27,6 +27,7 @@ import ( certutil "k8s.io/client-go/util/cert" kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" + "k8s.io/kubernetes/cmd/kubeadm/app/util/pkiutil" ) func TestCertListOrder(t *testing.T) { @@ -160,16 +161,18 @@ func TestCreateCertificateChain(t *testing.T) { caCfg := Certificates{ { - config: certutil.Config{}, + config: pkiutil.CertConfig{}, Name: "test-ca", BaseName: "test-ca", }, { - config: certutil.Config{ - AltNames: certutil.AltNames{ - DNSNames: []string{"test-domain.space"}, + config: pkiutil.CertConfig{ + Config: certutil.Config{ + AltNames: certutil.AltNames{ + DNSNames: []string{"test-domain.space"}, + }, + Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, }, - Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, }, configMutators: []configMutatorsFunc{ setCommonNameToNodeName(), diff --git a/cmd/kubeadm/app/phases/certs/certs.go b/cmd/kubeadm/app/phases/certs/certs.go index 50bffd1b0a4..aa9bca6c1d3 100644 --- a/cmd/kubeadm/app/phases/certs/certs.go +++ b/cmd/kubeadm/app/phases/certs/certs.go @@ -24,7 +24,6 @@ import ( "path/filepath" "github.com/pkg/errors" - certutil "k8s.io/client-go/util/cert" "k8s.io/client-go/util/keyutil" "k8s.io/klog" kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" @@ -61,12 +60,12 @@ func CreatePKIAssets(cfg *kubeadmapi.InitConfiguration) error { fmt.Printf("[certs] Valid certificates and keys now exist in %q\n", cfg.CertificatesDir) // Service accounts are not x509 certs, so handled separately - return CreateServiceAccountKeyAndPublicKeyFiles(cfg.CertificatesDir) + return CreateServiceAccountKeyAndPublicKeyFiles(cfg.CertificatesDir, cfg.ClusterConfiguration.PublicKeyAlgorithm()) } // CreateServiceAccountKeyAndPublicKeyFiles creates new public/private key files for signing service account users. // If the sa public/private key files already exist in the target folder, they are used only if evaluated equals; otherwise an error is returned. -func CreateServiceAccountKeyAndPublicKeyFiles(certsDir string) error { +func CreateServiceAccountKeyAndPublicKeyFiles(certsDir string, keyType x509.PublicKeyAlgorithm) error { klog.V(1).Infoln("creating new public/private key files for signing service account users") _, err := keyutil.PrivateKeyFromFile(filepath.Join(certsDir, kubeadmconstants.ServiceAccountPrivateKeyName)) if err == nil { @@ -80,7 +79,7 @@ func CreateServiceAccountKeyAndPublicKeyFiles(certsDir string) error { } // The key does NOT exist, let's generate it now - key, err := pkiutil.NewPrivateKey() + key, err := pkiutil.NewPrivateKey(keyType) if err != nil { return err } @@ -215,7 +214,7 @@ func writeCertificateAuthorityFilesIfNotExist(pkiDir string, baseName string, ca // If there already is a certificate file at the given path; kubeadm tries to load it and check if the values in the // existing and the expected certificate equals. If they do; kubeadm will just skip writing the file as it's up-to-date, // otherwise this function returns an error. -func writeCertificateFilesIfNotExist(pkiDir string, baseName string, signingCert *x509.Certificate, cert *x509.Certificate, key crypto.Signer, cfg *certutil.Config) error { +func writeCertificateFilesIfNotExist(pkiDir string, baseName string, signingCert *x509.Certificate, cert *x509.Certificate, key crypto.Signer, cfg *pkiutil.CertConfig) error { // Checks if the signed certificate exists in the PKI directory if pkiutil.CertOrKeyExist(pkiDir, baseName) { @@ -426,7 +425,7 @@ func validatePrivatePublicKey(l certKeyLocation) error { // validateCertificateWithConfig makes sure that a given certificate is valid at // least for the SANs defined in the configuration. -func validateCertificateWithConfig(cert *x509.Certificate, baseName string, cfg *certutil.Config) error { +func validateCertificateWithConfig(cert *x509.Certificate, baseName string, cfg *pkiutil.CertConfig) error { for _, dnsName := range cfg.AltNames.DNSNames { if err := cert.VerifyHostname(dnsName); err != nil { return errors.Wrapf(err, "certificate %s is invalid", baseName) diff --git a/cmd/kubeadm/app/phases/certs/certs_test.go b/cmd/kubeadm/app/phases/certs/certs_test.go index 0703c2be654..660da8b0f59 100644 --- a/cmd/kubeadm/app/phases/certs/certs_test.go +++ b/cmd/kubeadm/app/phases/certs/certs_test.go @@ -42,8 +42,10 @@ import ( func createTestCSR(t *testing.T) (*x509.CertificateRequest, crypto.Signer) { csr, key, err := pkiutil.NewCSRAndKey( - &certutil.Config{ - CommonName: "testCert", + &pkiutil.CertConfig{ + Config: certutil.Config{ + CommonName: "testCert", + }, }) if err != nil { t.Fatalf("couldn't create test cert: %v", err) @@ -344,7 +346,7 @@ func TestCreateServiceAccountKeyAndPublicKeyFiles(t *testing.T) { } } - err := CreateServiceAccountKeyAndPublicKeyFiles(dir) + err := CreateServiceAccountKeyAndPublicKeyFiles(dir, x509.RSA) if (err != nil) != tt.expectedErr { t.Fatalf("expected error: %v, got: %v, error: %v", tt.expectedErr, err != nil, err) } else if tt.expectedErr { diff --git a/cmd/kubeadm/app/phases/certs/renewal/apirenewer.go b/cmd/kubeadm/app/phases/certs/renewal/apirenewer.go index e92d8ef686a..f1188c8634b 100644 --- a/cmd/kubeadm/app/phases/certs/renewal/apirenewer.go +++ b/cmd/kubeadm/app/phases/certs/renewal/apirenewer.go @@ -52,7 +52,7 @@ func NewAPIRenewer(client clientset.Interface) *APIRenewer { } // Renew a certificate using the K8s certificate API -func (r *APIRenewer) Renew(cfg *certutil.Config) (*x509.Certificate, crypto.Signer, error) { +func (r *APIRenewer) Renew(cfg *pkiutil.CertConfig) (*x509.Certificate, crypto.Signer, error) { reqTmp := &x509.CertificateRequest{ Subject: pkix.Name{ CommonName: cfg.CommonName, @@ -62,7 +62,7 @@ func (r *APIRenewer) Renew(cfg *certutil.Config) (*x509.Certificate, crypto.Sign IPAddresses: cfg.AltNames.IPs, } - key, err := pkiutil.NewPrivateKey() + key, err := pkiutil.NewPrivateKey(cfg.PublicKeyAlgorithm) if err != nil { return nil, nil, errors.Wrap(err, "couldn't create new private key") } diff --git a/cmd/kubeadm/app/phases/certs/renewal/apirenewer_test.go b/cmd/kubeadm/app/phases/certs/renewal/apirenewer_test.go index 761db0bd04a..2720057a441 100644 --- a/cmd/kubeadm/app/phases/certs/renewal/apirenewer_test.go +++ b/cmd/kubeadm/app/phases/certs/renewal/apirenewer_test.go @@ -33,7 +33,9 @@ import ( ) func TestAPIRenewer(t *testing.T) { - caCertCfg := &certutil.Config{CommonName: "kubernetes"} + caCertCfg := &pkiutil.CertConfig{ + Config: certutil.Config{CommonName: "kubernetes"}, + } caCert, caKey, err := pkiutil.NewCertificateAuthority(caCertCfg) if err != nil { t.Fatalf("couldn't create CA: %v", err) @@ -55,12 +57,14 @@ func TestAPIRenewer(t *testing.T) { // override the timeout so tests are faster watchTimeout = time.Second - certCfg := &certutil.Config{ - CommonName: "test-certs", - AltNames: certutil.AltNames{ - DNSNames: []string{"test-domain.space"}, + certCfg := &pkiutil.CertConfig{ + Config: certutil.Config{ + CommonName: "test-certs", + AltNames: certutil.AltNames{ + DNSNames: []string{"test-domain.space"}, + }, + Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, }, - Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, } renewer := &APIRenewer{ @@ -92,12 +96,14 @@ func defaultReactionFunc(obj runtime.Object) k8stesting.ReactionFunc { } func getCertReq(t *testing.T, caCert *x509.Certificate, caKey crypto.Signer) *certsapi.CertificateSigningRequest { - cert, _, err := pkiutil.NewCertAndKey(caCert, caKey, &certutil.Config{ - CommonName: "testcert", - AltNames: certutil.AltNames{ - DNSNames: []string{"test-domain.space"}, + cert, _, err := pkiutil.NewCertAndKey(caCert, caKey, &pkiutil.CertConfig{ + Config: certutil.Config{ + CommonName: "testcert", + AltNames: certutil.AltNames{ + DNSNames: []string{"test-domain.space"}, + }, + Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, }, - Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, }) if err != nil { t.Fatalf("couldn't generate cert: %v", err) diff --git a/cmd/kubeadm/app/phases/certs/renewal/filerenewer.go b/cmd/kubeadm/app/phases/certs/renewal/filerenewer.go index 5a71393d136..af92ac58778 100644 --- a/cmd/kubeadm/app/phases/certs/renewal/filerenewer.go +++ b/cmd/kubeadm/app/phases/certs/renewal/filerenewer.go @@ -20,7 +20,6 @@ import ( "crypto" "crypto/x509" - certutil "k8s.io/client-go/util/cert" "k8s.io/kubernetes/cmd/kubeadm/app/util/pkiutil" ) @@ -39,6 +38,6 @@ func NewFileRenewer(caCert *x509.Certificate, caKey crypto.Signer) *FileRenewer } // Renew a certificate using a given CA cert and key -func (r *FileRenewer) Renew(cfg *certutil.Config) (*x509.Certificate, crypto.Signer, error) { +func (r *FileRenewer) Renew(cfg *pkiutil.CertConfig) (*x509.Certificate, crypto.Signer, error) { return pkiutil.NewCertAndKey(r.caCert, r.caKey, cfg) } diff --git a/cmd/kubeadm/app/phases/certs/renewal/filerenewer_test.go b/cmd/kubeadm/app/phases/certs/renewal/filerenewer_test.go index 341e8cfc3bb..371ffbf64c2 100644 --- a/cmd/kubeadm/app/phases/certs/renewal/filerenewer_test.go +++ b/cmd/kubeadm/app/phases/certs/renewal/filerenewer_test.go @@ -21,6 +21,7 @@ import ( "testing" certutil "k8s.io/client-go/util/cert" + pkiutil "k8s.io/kubernetes/cmd/kubeadm/app/util/pkiutil" ) func TestFileRenewer(t *testing.T) { @@ -28,12 +29,14 @@ func TestFileRenewer(t *testing.T) { fr := NewFileRenewer(testCACert, testCAKey) // renews a certificate - certCfg := &certutil.Config{ - CommonName: "test-certs", - AltNames: certutil.AltNames{ - DNSNames: []string{"test-domain.space"}, + certCfg := &pkiutil.CertConfig{ + Config: certutil.Config{ + CommonName: "test-certs", + AltNames: certutil.AltNames{ + DNSNames: []string{"test-domain.space"}, + }, + Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, }, - Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, } cert, _, err := fr.Renew(certCfg) diff --git a/cmd/kubeadm/app/phases/certs/renewal/manager.go b/cmd/kubeadm/app/phases/certs/renewal/manager.go index 09807e071af..c7245e50ed4 100644 --- a/cmd/kubeadm/app/phases/certs/renewal/manager.go +++ b/cmd/kubeadm/app/phases/certs/renewal/manager.go @@ -224,7 +224,10 @@ func (rm *Manager) RenewUsingLocalCA(name string) (bool, error) { } // extract the certificate config - cfg := certToConfig(cert) + cfg := &pkiutil.CertConfig{ + Config: certToConfig(cert), + PublicKeyAlgorithm: rm.cfg.PublicKeyAlgorithm(), + } // reads the CA caCert, caKey, err := certsphase.LoadCertificateAuthority(rm.cfg.CertificatesDir, handler.CABaseName) @@ -264,7 +267,10 @@ func (rm *Manager) RenewUsingCSRAPI(name string, client clientset.Interface) err } // extract the certificate config - cfg := certToConfig(cert) + cfg := &pkiutil.CertConfig{ + Config: certToConfig(cert), + PublicKeyAlgorithm: rm.cfg.PublicKeyAlgorithm(), + } // create a new certificate with the same config newCert, newKey, err := NewAPIRenewer(client).Renew(cfg) @@ -298,7 +304,10 @@ func (rm *Manager) CreateRenewCSR(name, outdir string) error { } // extracts the certificate config - cfg := certToConfig(cert) + cfg := &pkiutil.CertConfig{ + Config: certToConfig(cert), + PublicKeyAlgorithm: rm.cfg.PublicKeyAlgorithm(), + } // generates the CSR request and save it csr, key, err := pkiutil.NewCSRAndKey(cfg) @@ -407,8 +416,8 @@ func (rm *Manager) IsExternallyManaged(caBaseName string) (bool, error) { } } -func certToConfig(cert *x509.Certificate) *certutil.Config { - return &certutil.Config{ +func certToConfig(cert *x509.Certificate) certutil.Config { + return certutil.Config{ CommonName: cert.Subject.CommonName, Organization: cert.Subject.Organization, AltNames: certutil.AltNames{ diff --git a/cmd/kubeadm/app/phases/certs/renewal/manager_test.go b/cmd/kubeadm/app/phases/certs/renewal/manager_test.go index ff2cb3572e8..85b1325a7ac 100644 --- a/cmd/kubeadm/app/phases/certs/renewal/manager_test.go +++ b/cmd/kubeadm/app/phases/certs/renewal/manager_test.go @@ -34,18 +34,22 @@ import ( ) var ( - testCACertCfg = &certutil.Config{CommonName: "kubernetes"} + testCACertCfg = &pkiutil.CertConfig{ + Config: certutil.Config{CommonName: "kubernetes"}, + } testCACert, testCAKey, _ = pkiutil.NewCertificateAuthority(testCACertCfg) - testCertCfg = &certutil.Config{ - CommonName: "test-common-name", - Organization: []string{"sig-cluster-lifecycle"}, - AltNames: certutil.AltNames{ - IPs: []net.IP{net.ParseIP("10.100.0.1")}, - DNSNames: []string{"test-domain.space"}, + testCertCfg = &pkiutil.CertConfig{ + Config: certutil.Config{ + CommonName: "test-common-name", + Organization: []string{"sig-cluster-lifecycle"}, + AltNames: certutil.AltNames{ + IPs: []net.IP{net.ParseIP("10.100.0.1")}, + DNSNames: []string{"test-domain.space"}, + }, + Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, }, - Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, } ) diff --git a/cmd/kubeadm/app/phases/certs/renewal/readwriter_test.go b/cmd/kubeadm/app/phases/certs/renewal/readwriter_test.go index 94bc07fc05e..d19c6e58138 100644 --- a/cmd/kubeadm/app/phases/certs/renewal/readwriter_test.go +++ b/cmd/kubeadm/app/phases/certs/renewal/readwriter_test.go @@ -154,13 +154,15 @@ func writeTestCertificate(t *testing.T, dir, name string, caCert *x509.Certifica // writeTestKubeconfig is a utility for creating a test kubeconfig with an embedded certificate func writeTestKubeconfig(t *testing.T, dir, name string, caCert *x509.Certificate, caKey crypto.Signer) *x509.Certificate { - cfg := &certutil.Config{ - CommonName: "test-common-name", - Organization: []string{"sig-cluster-lifecycle"}, - Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, - AltNames: certutil.AltNames{ - IPs: []net.IP{net.ParseIP("10.100.0.1")}, - DNSNames: []string{"test-domain.space"}, + cfg := &pkiutil.CertConfig{ + Config: certutil.Config{ + CommonName: "test-common-name", + Organization: []string{"sig-cluster-lifecycle"}, + Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, + AltNames: certutil.AltNames{ + IPs: []net.IP{net.ParseIP("10.100.0.1")}, + DNSNames: []string{"test-domain.space"}, + }, }, } cert, key, err := pkiutil.NewCertAndKey(caCert, caKey, cfg) diff --git a/cmd/kubeadm/app/phases/kubeconfig/kubeconfig.go b/cmd/kubeadm/app/phases/kubeconfig/kubeconfig.go index 7ee3f36b348..4266c3cc22f 100644 --- a/cmd/kubeadm/app/phases/kubeconfig/kubeconfig.go +++ b/cmd/kubeadm/app/phases/kubeconfig/kubeconfig.go @@ -182,10 +182,12 @@ func buildKubeConfigFromSpec(spec *kubeConfigSpec, clustername string) (*clientc } // otherwise, create a client certs - clientCertConfig := certutil.Config{ - CommonName: spec.ClientName, - Organization: spec.ClientCertAuth.Organizations, - Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, + clientCertConfig := pkiutil.CertConfig{ + Config: certutil.Config{ + CommonName: spec.ClientName, + Organization: spec.ClientCertAuth.Organizations, + Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, + }, } clientCert, clientKey, err := pkiutil.NewCertAndKey(spec.CACert, spec.ClientCertAuth.CAKey, &clientCertConfig) if err != nil { diff --git a/cmd/kubeadm/app/util/certs/util.go b/cmd/kubeadm/app/util/certs/util.go index 9f96c046b7b..51af8543dff 100644 --- a/cmd/kubeadm/app/util/certs/util.go +++ b/cmd/kubeadm/app/util/certs/util.go @@ -32,7 +32,9 @@ import ( // SetupCertificateAuthority is a utility function for kubeadm testing that creates a // CertificateAuthority cert/key pair func SetupCertificateAuthority(t *testing.T) (*x509.Certificate, crypto.Signer) { - caCert, caKey, err := pkiutil.NewCertificateAuthority(&certutil.Config{CommonName: "kubernetes"}) + caCert, caKey, err := pkiutil.NewCertificateAuthority(&pkiutil.CertConfig{ + Config: certutil.Config{CommonName: "kubernetes"}, + }) if err != nil { t.Fatalf("failure while generating CA certificate and key: %v", err) } @@ -132,7 +134,7 @@ func AssertCertificateHasIPAddresses(t *testing.T, cert *x509.Certificate, IPAdd // CreateCACert creates a generic CA cert. func CreateCACert(t *testing.T) (*x509.Certificate, crypto.Signer) { - certCfg := &certutil.Config{CommonName: "kubernetes"} + certCfg := &pkiutil.CertConfig{Config: certutil.Config{CommonName: "kubernetes"}} cert, key, err := pkiutil.NewCertificateAuthority(certCfg) if err != nil { t.Fatalf("couldn't create CA: %v", err) @@ -141,11 +143,13 @@ func CreateCACert(t *testing.T) (*x509.Certificate, crypto.Signer) { } // CreateTestCert makes a generic certificate with the given CA and alternative names. -func CreateTestCert(t *testing.T, caCert *x509.Certificate, caKey crypto.Signer, altNames certutil.AltNames) (*x509.Certificate, crypto.Signer, *certutil.Config) { - config := &certutil.Config{ - CommonName: "testCert", - Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageAny}, - AltNames: altNames, +func CreateTestCert(t *testing.T, caCert *x509.Certificate, caKey crypto.Signer, altNames certutil.AltNames) (*x509.Certificate, crypto.Signer, *pkiutil.CertConfig) { + config := &pkiutil.CertConfig{ + Config: certutil.Config{ + CommonName: "testCert", + Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageAny}, + AltNames: altNames, + }, } cert, key, err := pkiutil.NewCertAndKey(caCert, caKey, config) if err != nil { diff --git a/cmd/kubeadm/app/util/pkiutil/pki_helpers.go b/cmd/kubeadm/app/util/pkiutil/pki_helpers.go index c5ccaeabbf7..dbcab85bf3f 100644 --- a/cmd/kubeadm/app/util/pkiutil/pki_helpers.go +++ b/cmd/kubeadm/app/util/pkiutil/pki_helpers.go @@ -19,6 +19,7 @@ package pkiutil import ( "crypto" "crypto/ecdsa" + "crypto/elliptic" cryptorand "crypto/rand" "crypto/rsa" "crypto/x509" @@ -56,14 +57,20 @@ const ( rsaKeySize = 2048 ) +// CertConfig is a wrapper around certutil.Config extending it with PublicKeyAlgorithm. +type CertConfig struct { + certutil.Config + PublicKeyAlgorithm x509.PublicKeyAlgorithm +} + // NewCertificateAuthority creates new certificate and private key for the certificate authority -func NewCertificateAuthority(config *certutil.Config) (*x509.Certificate, crypto.Signer, error) { - key, err := NewPrivateKey() +func NewCertificateAuthority(config *CertConfig) (*x509.Certificate, crypto.Signer, error) { + key, err := NewPrivateKey(config.PublicKeyAlgorithm) if err != nil { return nil, nil, errors.Wrap(err, "unable to create private key while generating CA certificate") } - cert, err := certutil.NewSelfSignedCACert(*config, key) + cert, err := certutil.NewSelfSignedCACert(config.Config, key) if err != nil { return nil, nil, errors.Wrap(err, "unable to create self-signed CA certificate") } @@ -72,8 +79,8 @@ func NewCertificateAuthority(config *certutil.Config) (*x509.Certificate, crypto } // NewCertAndKey creates new certificate and key by passing the certificate authority certificate and key -func NewCertAndKey(caCert *x509.Certificate, caKey crypto.Signer, config *certutil.Config) (*x509.Certificate, crypto.Signer, error) { - key, err := NewPrivateKey() +func NewCertAndKey(caCert *x509.Certificate, caKey crypto.Signer, config *CertConfig) (*x509.Certificate, crypto.Signer, error) { + key, err := NewPrivateKey(config.PublicKeyAlgorithm) if err != nil { return nil, nil, errors.Wrap(err, "unable to create private key") } @@ -87,8 +94,8 @@ func NewCertAndKey(caCert *x509.Certificate, caKey crypto.Signer, config *certut } // NewCSRAndKey generates a new key and CSR and that could be signed to create the given certificate -func NewCSRAndKey(config *certutil.Config) (*x509.CertificateRequest, crypto.Signer, error) { - key, err := NewPrivateKey() +func NewCSRAndKey(config *CertConfig) (*x509.CertificateRequest, crypto.Signer, error) { + key, err := NewPrivateKey(config.PublicKeyAlgorithm) if err != nil { return nil, nil, errors.Wrap(err, "unable to create private key") } @@ -496,7 +503,7 @@ func CertificateRequestFromFile(file string) (*x509.CertificateRequest, error) { } // NewCSR creates a new CSR -func NewCSR(cfg certutil.Config, key crypto.Signer) (*x509.CertificateRequest, error) { +func NewCSR(cfg CertConfig, key crypto.Signer) (*x509.CertificateRequest, error) { template := &x509.CertificateRequest{ Subject: pkix.Name{ CommonName: cfg.CommonName, @@ -538,12 +545,16 @@ func EncodePublicKeyPEM(key crypto.PublicKey) ([]byte, error) { } // NewPrivateKey creates an RSA private key -func NewPrivateKey() (crypto.Signer, error) { +func NewPrivateKey(keyType x509.PublicKeyAlgorithm) (crypto.Signer, error) { + if keyType == x509.ECDSA { + return ecdsa.GenerateKey(elliptic.P256(), cryptorand.Reader) + } + return rsa.GenerateKey(cryptorand.Reader, rsaKeySize) } // NewSignedCert creates a signed certificate using the given CA certificate and key -func NewSignedCert(cfg *certutil.Config, key crypto.Signer, caCert *x509.Certificate, caKey crypto.Signer) (*x509.Certificate, error) { +func NewSignedCert(cfg *CertConfig, key crypto.Signer, caCert *x509.Certificate, caKey crypto.Signer) (*x509.Certificate, error) { serial, err := cryptorand.Int(cryptorand.Reader, new(big.Int).SetInt64(math.MaxInt64)) if err != nil { return nil, err diff --git a/cmd/kubeadm/app/util/pkiutil/pki_helpers_test.go b/cmd/kubeadm/app/util/pkiutil/pki_helpers_test.go index 9702246dcbd..2c7832f73f9 100644 --- a/cmd/kubeadm/app/util/pkiutil/pki_helpers_test.go +++ b/cmd/kubeadm/app/util/pkiutil/pki_helpers_test.go @@ -33,7 +33,9 @@ import ( ) func TestNewCertificateAuthority(t *testing.T) { - cert, key, err := NewCertificateAuthority(&certutil.Config{CommonName: "kubernetes"}) + cert, key, err := NewCertificateAuthority(&CertConfig{ + Config: certutil.Config{CommonName: "kubernetes"}, + }) if cert == nil { t.Error("failed NewCertificateAuthority, cert == nil") @@ -86,10 +88,12 @@ func TestNewCertAndKey(t *testing.T) { t.Fatalf("Couldn't create Private Key") } caCert := &x509.Certificate{} - config := &certutil.Config{ - CommonName: "test", - Organization: []string{"test"}, - Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, + config := &CertConfig{ + Config: certutil.Config{ + CommonName: "test", + Organization: []string{"test"}, + Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, + }, } _, _, actual := NewCertAndKey(caCert, caKey, config) if (actual == nil) != rt.expected { @@ -104,26 +108,41 @@ func TestNewCertAndKey(t *testing.T) { } func TestHasServerAuth(t *testing.T) { - caCert, caKey, _ := NewCertificateAuthority(&certutil.Config{CommonName: "kubernetes"}) + caCert, caKey, _ := NewCertificateAuthority(&CertConfig{Config: certutil.Config{CommonName: "kubernetes"}}) var tests = []struct { name string - config certutil.Config + config CertConfig expected bool }{ { name: "has ServerAuth", - config: certutil.Config{ - CommonName: "test", - Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + config: CertConfig{ + Config: certutil.Config{ + CommonName: "test", + Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + }, + }, + expected: true, + }, + { + name: "has ServerAuth ECDSA", + config: CertConfig{ + Config: certutil.Config{ + CommonName: "test", + Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + }, + PublicKeyAlgorithm: x509.ECDSA, }, expected: true, }, { name: "doesn't have ServerAuth", - config: certutil.Config{ - CommonName: "test", - Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, + config: CertConfig{ + Config: certutil.Config{ + CommonName: "test", + Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, + }, }, expected: false, }, @@ -285,7 +304,9 @@ func TestTryLoadCertAndKeyFromDisk(t *testing.T) { } defer os.RemoveAll(tmpdir) - caCert, caKey, err := NewCertificateAuthority(&certutil.Config{CommonName: "kubernetes"}) + caCert, caKey, err := NewCertificateAuthority(&CertConfig{ + Config: certutil.Config{CommonName: "kubernetes"}, + }) if err != nil { t.Errorf( "failed to create cert and key with an error: %v", @@ -340,7 +361,9 @@ func TestTryLoadCertFromDisk(t *testing.T) { } defer os.RemoveAll(tmpdir) - caCert, _, err := NewCertificateAuthority(&certutil.Config{CommonName: "kubernetes"}) + caCert, _, err := NewCertificateAuthority(&CertConfig{ + Config: certutil.Config{CommonName: "kubernetes"}, + }) if err != nil { t.Errorf( "failed to create cert and key with an error: %v", diff --git a/cmd/kubeadm/test/cmd/init_test.go b/cmd/kubeadm/test/cmd/init_test.go index 62971a72304..e51c944599a 100644 --- a/cmd/kubeadm/test/cmd/init_test.go +++ b/cmd/kubeadm/test/cmd/init_test.go @@ -356,6 +356,10 @@ func TestCmdInitFeatureGates(t *testing.T) { name: "feature gate IPv6DualStack=true", args: "--feature-gates=IPv6DualStack=true", }, + { + name: "feature gate PublicKeysECDSA=true", + args: "--feature-gates=PublicKeysECDSA=true", + }, } for _, rt := range initTest {