From 02ed1aee71bf7389d578f9ac27cb4ed82a3730a2 Mon Sep 17 00:00:00 2001 From: "Lubomir I. Ivanov" Date: Fri, 7 Jun 2024 19:31:04 +0300 Subject: [PATCH 1/2] kubeadm: fix the generation of ECDSA keys in kubeconfig files When the PublicKeysECDSA feature gate is used or the new v1beta4.ClusterConfiguration.EncryptionAlgorithm field is used with "ECDSA-P256" as value, make sure that this is reflected in the "cert spec" used to generate private keys and they end up as "EC keys". --- .../app/phases/kubeconfig/kubeconfig.go | 49 +++++++++++-------- .../app/phases/kubeconfig/kubeconfig_test.go | 8 ++- 2 files changed, 36 insertions(+), 21 deletions(-) diff --git a/cmd/kubeadm/app/phases/kubeconfig/kubeconfig.go b/cmd/kubeadm/app/phases/kubeconfig/kubeconfig.go index 2031f1f710a..42acb89bc62 100644 --- a/cmd/kubeadm/app/phases/kubeconfig/kubeconfig.go +++ b/cmd/kubeadm/app/phases/kubeconfig/kubeconfig.go @@ -66,12 +66,13 @@ type tokenAuth struct { // kubeConfigSpec struct holds info required to build a KubeConfig object type kubeConfigSpec struct { - CACert *x509.Certificate - APIServer string - ClientName string - ClientCertNotAfter time.Time - TokenAuth *tokenAuth `datapolicy:"token"` - ClientCertAuth *clientCertAuth `datapolicy:"security-key"` + CACert *x509.Certificate + APIServer string + ClientName string + ClientCertNotAfter time.Time + TokenAuth *tokenAuth `datapolicy:"token"` + ClientCertAuth *clientCertAuth `datapolicy:"security-key"` + EncryptionAlgorithm kubeadmapi.EncryptionAlgorithmType } // CreateJoinControlPlaneKubeConfigFiles will create and write to disk the kubeconfig files required by kubeadm @@ -212,7 +213,8 @@ func newClientCertConfigFromKubeConfigSpec(spec *kubeConfigSpec) pkiutil.CertCon Organization: spec.ClientCertAuth.Organizations, Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, }, - NotAfter: spec.ClientCertNotAfter, + NotAfter: spec.ClientCertNotAfter, + EncryptionAlgorithm: spec.EncryptionAlgorithm, } } @@ -324,7 +326,8 @@ func WriteKubeConfigWithClientCert(out io.Writer, cfg *kubeadmapi.InitConfigurat CAKey: caKey, Organizations: organizations, }, - ClientCertNotAfter: notAfter, + ClientCertNotAfter: notAfter, + EncryptionAlgorithm: cfg.ClusterConfiguration.EncryptionAlgorithmType(), } return writeKubeConfigFromSpec(out, spec, cfg.ClusterName) @@ -353,7 +356,8 @@ func WriteKubeConfigWithToken(out io.Writer, cfg *kubeadmapi.InitConfiguration, TokenAuth: &tokenAuth{ Token: token, }, - ClientCertNotAfter: notAfter, + ClientCertNotAfter: notAfter, + EncryptionAlgorithm: cfg.ClusterConfiguration.EncryptionAlgorithmType(), } return writeKubeConfigFromSpec(out, spec, cfg.ClusterName) @@ -452,7 +456,8 @@ func getKubeConfigSpecsBase(cfg *kubeadmapi.InitConfiguration) (map[string]*kube ClientCertAuth: &clientCertAuth{ Organizations: []string{kubeadmconstants.ClusterAdminsGroupAndClusterRoleBinding}, }, - ClientCertNotAfter: notAfter, + ClientCertNotAfter: notAfter, + EncryptionAlgorithm: cfg.ClusterConfiguration.EncryptionAlgorithmType(), }, kubeadmconstants.SuperAdminKubeConfigFileName: { APIServer: controlPlaneEndpoint, @@ -460,7 +465,8 @@ func getKubeConfigSpecsBase(cfg *kubeadmapi.InitConfiguration) (map[string]*kube ClientCertAuth: &clientCertAuth{ Organizations: []string{kubeadmconstants.SystemPrivilegedGroup}, }, - ClientCertNotAfter: notAfter, + ClientCertNotAfter: notAfter, + EncryptionAlgorithm: cfg.ClusterConfiguration.EncryptionAlgorithmType(), }, kubeadmconstants.KubeletKubeConfigFileName: { APIServer: controlPlaneEndpoint, @@ -468,19 +474,22 @@ func getKubeConfigSpecsBase(cfg *kubeadmapi.InitConfiguration) (map[string]*kube ClientCertAuth: &clientCertAuth{ Organizations: []string{kubeadmconstants.NodesGroup}, }, - ClientCertNotAfter: notAfter, + ClientCertNotAfter: notAfter, + EncryptionAlgorithm: cfg.ClusterConfiguration.EncryptionAlgorithmType(), }, kubeadmconstants.ControllerManagerKubeConfigFileName: { - APIServer: localAPIEndpoint, - ClientName: kubeadmconstants.ControllerManagerUser, - ClientCertAuth: &clientCertAuth{}, - ClientCertNotAfter: notAfter, + APIServer: localAPIEndpoint, + ClientName: kubeadmconstants.ControllerManagerUser, + ClientCertAuth: &clientCertAuth{}, + ClientCertNotAfter: notAfter, + EncryptionAlgorithm: cfg.ClusterConfiguration.EncryptionAlgorithmType(), }, kubeadmconstants.SchedulerKubeConfigFileName: { - APIServer: localAPIEndpoint, - ClientName: kubeadmconstants.SchedulerUser, - ClientCertAuth: &clientCertAuth{}, - ClientCertNotAfter: notAfter, + APIServer: localAPIEndpoint, + ClientName: kubeadmconstants.SchedulerUser, + ClientCertAuth: &clientCertAuth{}, + ClientCertNotAfter: notAfter, + EncryptionAlgorithm: cfg.ClusterConfiguration.EncryptionAlgorithmType(), }, }, nil } diff --git a/cmd/kubeadm/app/phases/kubeconfig/kubeconfig_test.go b/cmd/kubeadm/app/phases/kubeconfig/kubeconfig_test.go index 8d5ba5cbbeb..f36dee1d3ba 100644 --- a/cmd/kubeadm/app/phases/kubeconfig/kubeconfig_test.go +++ b/cmd/kubeadm/app/phases/kubeconfig/kubeconfig_test.go @@ -84,7 +84,8 @@ func TestGetKubeConfigSpecs(t *testing.T) { { LocalAPIEndpoint: kubeadmapi.APIEndpoint{AdvertiseAddress: "1.2.3.4", BindPort: 1234}, ClusterConfiguration: kubeadmapi.ClusterConfiguration{ - CertificatesDir: pkidir, + CertificatesDir: pkidir, + EncryptionAlgorithm: kubeadmapi.EncryptionAlgorithmECDSAP256, }, NodeRegistration: kubeadmapi.NodeRegistrationOptions{Name: "valid-node-name"}, }, @@ -180,6 +181,11 @@ func TestGetKubeConfigSpecs(t *testing.T) { t.Errorf("getKubeConfigSpecs for %s Organizations is %v, expected %v", assertion.kubeConfigFile, spec.ClientCertAuth.Organizations, assertion.organizations) } + // Assert EncryptionAlgorithm + if spec.EncryptionAlgorithm != cfg.EncryptionAlgorithm { + t.Errorf("getKubeConfigSpecs for %s EncryptionAlgorithm is %s, expected %s", assertion.kubeConfigFile, spec.EncryptionAlgorithm, cfg.EncryptionAlgorithm) + } + // Asserts InitConfiguration values injected into spec controlPlaneEndpoint, err := kubeadmutil.GetControlPlaneEndpoint(cfg.ControlPlaneEndpoint, &cfg.LocalAPIEndpoint) if err != nil { From 40d185637c137984c57ce56e8e6d2f78993f02cc Mon Sep 17 00:00:00 2001 From: "Lubomir I. Ivanov" Date: Tue, 11 Jun 2024 12:35:39 +0300 Subject: [PATCH 2/2] kubeadm: add UT for ClusterConfiguration.EncryptionAlgorithmType() --- cmd/kubeadm/app/apis/kubeadm/types_test.go | 71 ++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 cmd/kubeadm/app/apis/kubeadm/types_test.go diff --git a/cmd/kubeadm/app/apis/kubeadm/types_test.go b/cmd/kubeadm/app/apis/kubeadm/types_test.go new file mode 100644 index 00000000000..241dd7d66e0 --- /dev/null +++ b/cmd/kubeadm/app/apis/kubeadm/types_test.go @@ -0,0 +1,71 @@ +/* +Copyright 2024 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package kubeadm + +import ( + "testing" + + "k8s.io/kubernetes/cmd/kubeadm/app/features" +) + +func TestClusterConfigurationEncryptionAlgorithmType(t *testing.T) { + tests := []struct { + name string + cfg *ClusterConfiguration + expectedResult EncryptionAlgorithmType + }{ + { + name: "feature gate is set to true, return ECDSA-P256", + cfg: &ClusterConfiguration{ + FeatureGates: map[string]bool{ + features.PublicKeysECDSA: true, + }, + EncryptionAlgorithm: EncryptionAlgorithmRSA4096, + }, + expectedResult: EncryptionAlgorithmECDSAP256, + }, + { + name: "feature gate is set to false, return the default RSA-2048", + cfg: &ClusterConfiguration{ + FeatureGates: map[string]bool{ + features.PublicKeysECDSA: false, + }, + }, + expectedResult: EncryptionAlgorithmRSA2048, + }, + { + name: "feature gate is not set, return the field value", + cfg: &ClusterConfiguration{ + EncryptionAlgorithm: EncryptionAlgorithmRSA4096, + }, + expectedResult: EncryptionAlgorithmRSA4096, + }, + { + name: "feature gate and field are not set, return empty string", + cfg: &ClusterConfiguration{}, + expectedResult: "", + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + if result := tc.cfg.EncryptionAlgorithmType(); result != tc.expectedResult { + t.Errorf("expected result: %s, got: %s", tc.expectedResult, result) + } + }) + } +}