kubeadm: enhance encryption algorithm support in v1beta4

Previous v1beta4 work added support for
ClusterConfiguration.EncryptionAlgorithm, however the possible
values were limited to just "RSA" (2048 key size) and "ECDSA" (P256).

Allow more arbitrary algorithm types, that can also include key size
or curve type encoded in the name:
"RSA-2048" (default), "RSA-3072", "RSA-4096" or "ECDSA-P256".

Update the deprecation notice of the PublicKeysECDSA FeatureGate
as ideally it should be removed only after v1beta3 is removed.
This commit is contained in:
Lubomir I. Ivanov 2024-01-31 18:20:09 +02:00
parent a9e4f5b786
commit 2cab79710d
13 changed files with 94 additions and 39 deletions

View File

@ -94,7 +94,7 @@ func fuzzClusterConfiguration(obj *kubeadm.ClusterConfiguration, c fuzz.Continue
obj.APIServer.ExtraEnvs = []kubeadm.EnvVar{}
obj.Scheduler.ExtraEnvs = []kubeadm.EnvVar{}
obj.Etcd.Local.ExtraEnvs = []kubeadm.EnvVar{}
obj.EncryptionAlgorithm = kubeadm.EncryptionAlgorithmRSA
obj.EncryptionAlgorithm = kubeadm.EncryptionAlgorithmRSA2048
obj.Proxy.Disabled = false
}

View File

@ -147,7 +147,7 @@ type ClusterConfiguration struct {
ClusterName string
// EncryptionAlgorithm holds the type of asymmetric encryption algorithm used for keys and certificates.
// Can be "RSA" (default algorithm, key size is 2048) or "ECDSA" (uses the P-256 elliptic curve).
// Can be one of "RSA-2048" (default), "RSA-3072", "RSA-4096" or "ECDSA-P256".
EncryptionAlgorithm EncryptionAlgorithmType
}
@ -433,9 +433,9 @@ func (cfg *ClusterConfiguration) EncryptionAlgorithmType() EncryptionAlgorithmTy
// TODO: remove this function when the feature gate is removed.
if enabled, ok := cfg.FeatureGates[features.PublicKeysECDSA]; ok {
if enabled {
return EncryptionAlgorithmECDSA
return EncryptionAlgorithmECDSAP256
}
return EncryptionAlgorithmRSA
return EncryptionAlgorithmRSA2048
}
return cfg.EncryptionAlgorithm
}
@ -570,10 +570,14 @@ type EnvVar struct {
type EncryptionAlgorithmType string
const (
// EncryptionAlgorithmECDSA defines the ECDSA encryption algorithm type.
EncryptionAlgorithmECDSA EncryptionAlgorithmType = "ECDSA"
// EncryptionAlgorithmRSA defines the RSA encryption algorithm type.
EncryptionAlgorithmRSA EncryptionAlgorithmType = "RSA"
// EncryptionAlgorithmECDSAP256 defines the ECDSA encryption algorithm type with curve P256.
EncryptionAlgorithmECDSAP256 EncryptionAlgorithmType = "ECDSA-P256"
// EncryptionAlgorithmRSA2048 defines the RSA encryption algorithm type with key size 2048 bits.
EncryptionAlgorithmRSA2048 EncryptionAlgorithmType = "RSA-2048"
// EncryptionAlgorithmRSA3072 defines the RSA encryption algorithm type with key size 3072 bits.
EncryptionAlgorithmRSA3072 EncryptionAlgorithmType = "RSA-3072"
// EncryptionAlgorithmRSA4096 defines the RSA encryption algorithm type with key size 4096 bits.
EncryptionAlgorithmRSA4096 EncryptionAlgorithmType = "RSA-4096"
)
// Timeouts holds various timeouts that apply to kubeadm commands.

View File

@ -66,7 +66,7 @@ func Convert_kubeadm_ClusterConfiguration_To_v1beta3_ClusterConfiguration(in *ku
func Convert_v1beta3_ClusterConfiguration_To_kubeadm_ClusterConfiguration(in *ClusterConfiguration, out *kubeadm.ClusterConfiguration, s conversion.Scope) error {
// Required to pass validation and fuzzer tests. The field is missing in v1beta3, thus we have to
// default it to a sane (default) value in the internal type.
out.EncryptionAlgorithm = kubeadm.EncryptionAlgorithmRSA
out.EncryptionAlgorithm = kubeadm.EncryptionAlgorithmRSA2048
return autoConvert_v1beta3_ClusterConfiguration_To_kubeadm_ClusterConfiguration(in, out, s)
}

View File

@ -60,7 +60,7 @@ const (
DefaultImagePullPolicy = corev1.PullIfNotPresent
// DefaultEncryptionAlgorithm is the default encryption algorithm.
DefaultEncryptionAlgorithm = EncryptionAlgorithmRSA
DefaultEncryptionAlgorithm = EncryptionAlgorithmRSA2048
)
func addDefaultingFuncs(scheme *runtime.Scheme) error {

View File

@ -33,8 +33,7 @@ limitations under the License.
// The change applies to `ClusterConfiguration` - `APIServer.ExtraArgs, `ControllerManager.ExtraArgs`,
// `Scheduler.ExtraArgs`, `Etcd.Local.ExtraArgs`. Also to `NodeRegistrationOptions.KubeletExtraArgs`.
// - Add `ClusterConfiguration.EncryptionAlgorithm` that can be used to set the asymmetric encryption algorithm
// used for this cluster's keys and certificates. Can be "RSA" (default algorithm, key size is 2048) or
// "ECDSA" (uses the P-256 elliptic curve).
// used for this cluster's keys and certificates. Can be one of "RSA-2048" (default), "RSA-3072", "RSA-4096" or "ECDSA-P256".
// - Add `ClusterConfiguration.DNS.Disabled` and `ClusterConfiguration.Proxy.Disabled` that can be used to disable
// the CoreDNS and kube-proxy addons during cluster initialization. Skipping the related addons phases,
// during cluster creation will set the same fields to `false`.

View File

@ -149,7 +149,7 @@ type ClusterConfiguration struct {
ClusterName string `json:"clusterName,omitempty"`
// EncryptionAlgorithm holds the type of asymmetric encryption algorithm used for keys and certificates.
// Can be "RSA" (default algorithm, key size is 2048) or "ECDSA" (uses the P-256 elliptic curve).
// Can be one of "RSA-2048" (default), "RSA-3072", "RSA-4096" or "ECDSA-P256".
// +optional
EncryptionAlgorithm EncryptionAlgorithmType `json:"encryptionAlgorithm,omitempty"`
}
@ -549,10 +549,14 @@ type EnvVar struct {
type EncryptionAlgorithmType string
const (
// EncryptionAlgorithmECDSA defines the ECDSA encryption algorithm type.
EncryptionAlgorithmECDSA EncryptionAlgorithmType = "ECDSA"
// EncryptionAlgorithmRSA defines the RSA encryption algorithm type.
EncryptionAlgorithmRSA EncryptionAlgorithmType = "RSA"
// EncryptionAlgorithmECDSAP256 defines the ECDSA encryption algorithm type with curve P256.
EncryptionAlgorithmECDSAP256 EncryptionAlgorithmType = "ECDSA-P256"
// EncryptionAlgorithmRSA2048 defines the RSA encryption algorithm type with key size 2048 bits.
EncryptionAlgorithmRSA2048 EncryptionAlgorithmType = "RSA-2048"
// EncryptionAlgorithmRSA3072 defines the RSA encryption algorithm type with key size 3072 bits.
EncryptionAlgorithmRSA3072 EncryptionAlgorithmType = "RSA-3072"
// EncryptionAlgorithmRSA4096 defines the RSA encryption algorithm type with key size 4096 bits.
EncryptionAlgorithmRSA4096 EncryptionAlgorithmType = "RSA-4096"
)
// Timeouts holds various timeouts that apply to kubeadm commands.

View File

@ -73,7 +73,7 @@ func ValidateClusterConfiguration(c *kubeadm.ClusterConfiguration) field.ErrorLi
allErrs = append(allErrs, ValidateHostPort(c.ControlPlaneEndpoint, field.NewPath("controlPlaneEndpoint"))...)
allErrs = append(allErrs, ValidateImageRepository(c.ImageRepository, field.NewPath("imageRepository"))...)
allErrs = append(allErrs, ValidateEtcd(&c.Etcd, field.NewPath("etcd"))...)
allErrs = append(allErrs, ValidateEncryptionAlgorithm(string(c.EncryptionAlgorithm), field.NewPath("encryptionAlgorithm"))...)
allErrs = append(allErrs, ValidateEncryptionAlgorithm(c.EncryptionAlgorithm, field.NewPath("encryptionAlgorithm"))...)
allErrs = append(allErrs, componentconfigs.Validate(c)...)
return allErrs
}
@ -341,11 +341,16 @@ func ValidateEtcd(e *kubeadm.Etcd, fldPath *field.Path) field.ErrorList {
}
// ValidateEncryptionAlgorithm validates the public key algorithm
func ValidateEncryptionAlgorithm(algo string, fldPath *field.Path) field.ErrorList {
func ValidateEncryptionAlgorithm(algo kubeadm.EncryptionAlgorithmType, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
if algo != string(kubeadm.EncryptionAlgorithmRSA) && algo != string(kubeadm.EncryptionAlgorithmECDSA) {
msg := fmt.Sprintf("Invalid encryption algorithm. Must be %q or %q",
kubeadm.EncryptionAlgorithmRSA, kubeadm.EncryptionAlgorithmECDSA)
knownAlgorithms := sets.New(
kubeadm.EncryptionAlgorithmECDSAP256,
kubeadm.EncryptionAlgorithmRSA2048,
kubeadm.EncryptionAlgorithmRSA3072,
kubeadm.EncryptionAlgorithmRSA4096,
)
if !knownAlgorithms.Has(algo) {
msg := fmt.Sprintf("Invalid encryption algorithm %q. Must be one of %v", algo, sets.List(knownAlgorithms))
allErrs = append(allErrs, field.Invalid(fldPath, algo, msg))
}
return allErrs

View File

@ -515,7 +515,7 @@ func TestValidateInitConfiguration(t *testing.T) {
DNSDomain: "cluster.local",
},
CertificatesDir: "/some/cert/dir",
EncryptionAlgorithm: kubeadmapi.EncryptionAlgorithmRSA,
EncryptionAlgorithm: kubeadmapi.EncryptionAlgorithmRSA2048,
},
NodeRegistration: kubeadmapi.NodeRegistrationOptions{Name: nodename, CRISocket: criPath},
}, false},
@ -531,7 +531,7 @@ func TestValidateInitConfiguration(t *testing.T) {
DNSDomain: "cluster.local",
},
CertificatesDir: "/some/cert/dir",
EncryptionAlgorithm: kubeadmapi.EncryptionAlgorithmRSA,
EncryptionAlgorithm: kubeadmapi.EncryptionAlgorithmRSA2048,
},
NodeRegistration: kubeadmapi.NodeRegistrationOptions{Name: nodename, CRISocket: criPath},
}, false},
@ -547,7 +547,7 @@ func TestValidateInitConfiguration(t *testing.T) {
DNSDomain: "cluster.local",
},
CertificatesDir: "/some/other/cert/dir",
EncryptionAlgorithm: kubeadmapi.EncryptionAlgorithmRSA,
EncryptionAlgorithm: kubeadmapi.EncryptionAlgorithmRSA2048,
},
}, false},
{"valid InitConfiguration with incorrect IPv4 pod subnet",
@ -563,7 +563,7 @@ func TestValidateInitConfiguration(t *testing.T) {
PodSubnet: "10.0.1.15",
},
CertificatesDir: "/some/other/cert/dir",
EncryptionAlgorithm: kubeadmapi.EncryptionAlgorithmRSA,
EncryptionAlgorithm: kubeadmapi.EncryptionAlgorithmRSA2048,
},
NodeRegistration: kubeadmapi.NodeRegistrationOptions{Name: nodename, CRISocket: criPath},
}, false},
@ -586,7 +586,7 @@ func TestValidateInitConfiguration(t *testing.T) {
PodSubnet: "10.0.1.15/16",
},
CertificatesDir: "/some/other/cert/dir",
EncryptionAlgorithm: kubeadmapi.EncryptionAlgorithmRSA,
EncryptionAlgorithm: kubeadmapi.EncryptionAlgorithmRSA2048,
},
NodeRegistration: kubeadmapi.NodeRegistrationOptions{Name: nodename, CRISocket: criPath},
}, true},
@ -608,7 +608,7 @@ func TestValidateInitConfiguration(t *testing.T) {
DNSDomain: "cluster.local",
},
CertificatesDir: "/some/other/cert/dir",
EncryptionAlgorithm: kubeadmapi.EncryptionAlgorithmECDSA,
EncryptionAlgorithm: kubeadmapi.EncryptionAlgorithmECDSAP256,
},
NodeRegistration: kubeadmapi.NodeRegistrationOptions{Name: nodename, CRISocket: criPath},
}, true},
@ -1197,11 +1197,13 @@ func TestValidateEtcd(t *testing.T) {
func TestValidateEncryptionAlgorithm(t *testing.T) {
var tests = []struct {
name string
algo string
algo kubeadmapi.EncryptionAlgorithmType
expectedErrors bool
}{
{name: "valid RSA", algo: string(kubeadmapi.EncryptionAlgorithmRSA), expectedErrors: false},
{name: "valid ECDSA", algo: string(kubeadmapi.EncryptionAlgorithmECDSA), expectedErrors: false},
{name: "valid RSA-2048", algo: kubeadmapi.EncryptionAlgorithmRSA2048, expectedErrors: false},
{name: "valid RSA-3072", algo: kubeadmapi.EncryptionAlgorithmRSA3072, expectedErrors: false},
{name: "valid RSA-4096", algo: kubeadmapi.EncryptionAlgorithmRSA4096, expectedErrors: false},
{name: "valid ECDSA-P256", algo: kubeadmapi.EncryptionAlgorithmECDSAP256, expectedErrors: false},
{name: "invalid algorithm", algo: "foo", expectedErrors: true},
{name: "empty algorithm returns an error", algo: "", expectedErrors: true},
}

View File

@ -43,8 +43,9 @@ const (
// InitFeatureGates are the default feature gates for the init command
var InitFeatureGates = FeatureList{
PublicKeysECDSA: {
FeatureSpec: featuregate.FeatureSpec{Default: false, PreRelease: featuregate.Deprecated},
DeprecationMessage: "The PublicKeysECDSA feature gate is deprecated and will be removed after the feature 'ClusterConfiguration.EncryptionAlgorithm' is added.",
FeatureSpec: featuregate.FeatureSpec{Default: false, PreRelease: featuregate.Deprecated},
DeprecationMessage: "The PublicKeysECDSA feature gate is deprecated and will be removed when v1beta3 is removed." +
" v1beta4 supports a new option 'ClusterConfiguration.EncryptionAlgorithm'.",
},
RootlessControlPlane: {FeatureSpec: featuregate.FeatureSpec{Default: false, PreRelease: featuregate.Alpha}},
EtcdLearnerMode: {FeatureSpec: featuregate.FeatureSpec{Default: true, PreRelease: featuregate.Beta}},

View File

@ -262,7 +262,7 @@ func TestCreateServiceAccountKeyAndPublicKeyFiles(t *testing.T) {
}
}
err := CreateServiceAccountKeyAndPublicKeyFiles(dir, kubeadmapi.EncryptionAlgorithmRSA)
err := CreateServiceAccountKeyAndPublicKeyFiles(dir, kubeadmapi.EncryptionAlgorithmRSA2048)
if (err != nil) != tt.expectedErr {
t.Fatalf("expected error: %v, got: %v, error: %v", tt.expectedErr, err != nil, err)
} else if tt.expectedErr {

View File

@ -57,7 +57,6 @@ const (
CertificateBlockType = "CERTIFICATE"
// RSAPrivateKeyBlockType is a possible value for pem.Block.Type.
RSAPrivateKeyBlockType = "RSA PRIVATE KEY"
rsaKeySize = 2048
)
// CertConfig is a wrapper around certutil.Config extending it with EncryptionAlgorithm.
@ -608,12 +607,32 @@ func EncodePublicKeyPEM(key crypto.PublicKey) ([]byte, error) {
// NewPrivateKey returns a new private key.
var NewPrivateKey = GeneratePrivateKey
// rsaKeySizeFromAlgorithmType takes a known RSA algorithm defined in the kubeadm API
// an returns its key size. For unknown types it returns 0. For an empty type it returns
// the default size of 2048.
func rsaKeySizeFromAlgorithmType(keyType kubeadmapi.EncryptionAlgorithmType) int {
switch keyType {
case kubeadmapi.EncryptionAlgorithmRSA2048, "":
return 2048
case kubeadmapi.EncryptionAlgorithmRSA3072:
return 3072
case kubeadmapi.EncryptionAlgorithmRSA4096:
return 4096
default:
return 0
}
}
// GeneratePrivateKey is the default function for generating private keys.
func GeneratePrivateKey(keyType kubeadmapi.EncryptionAlgorithmType) (crypto.Signer, error) {
if keyType == kubeadmapi.EncryptionAlgorithmECDSA {
if keyType == kubeadmapi.EncryptionAlgorithmECDSAP256 {
return ecdsa.GenerateKey(elliptic.P256(), cryptorand.Reader)
}
rsaKeySize := rsaKeySizeFromAlgorithmType(keyType)
if rsaKeySize == 0 {
return nil, errors.Errorf("cannot obtain key size from unknown RSA algorithm: %q", keyType)
}
return rsa.GenerateKey(cryptorand.Reader, rsaKeySize)
}

View File

@ -52,7 +52,7 @@ func TestMain(m *testing.M) {
Config: certutil.Config{
CommonName: "Root CA 1",
},
EncryptionAlgorithm: kubeadmapi.EncryptionAlgorithmRSA,
EncryptionAlgorithm: kubeadmapi.EncryptionAlgorithmRSA2048,
})
if err != nil {
panic(fmt.Sprintf("Failed generating Root CA: %v", err))
@ -141,7 +141,7 @@ func TestHasServerAuth(t *testing.T) {
CommonName: "test",
Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
},
EncryptionAlgorithm: kubeadmapi.EncryptionAlgorithmECDSA,
EncryptionAlgorithm: kubeadmapi.EncryptionAlgorithmECDSAP256,
},
expected: true,
},
@ -940,3 +940,24 @@ func TestVerifyCertChain(t *testing.T) {
})
}
}
func TestRSAKeySizeFromAlgorithmType(t *testing.T) {
var tests = []struct {
algorithm kubeadmapi.EncryptionAlgorithmType
expectedSize int
}{
{algorithm: "unknown", expectedSize: 0},
{algorithm: "", expectedSize: 2048},
{algorithm: kubeadmapi.EncryptionAlgorithmRSA2048, expectedSize: 2048},
{algorithm: kubeadmapi.EncryptionAlgorithmRSA3072, expectedSize: 3072},
{algorithm: kubeadmapi.EncryptionAlgorithmRSA4096, expectedSize: 4096},
}
for _, rt := range tests {
t.Run(string(rt.algorithm), func(t *testing.T) {
size := rsaKeySizeFromAlgorithmType(rt.algorithm)
if size != rt.expectedSize {
t.Errorf("expected size: %d, got: %d", rt.expectedSize, size)
}
})
}
}

View File

@ -108,7 +108,7 @@ func newPrivateKey(keyType kubeadmapi.EncryptionAlgorithmType) (crypto.Signer, e
keyName := ""
switch keyType {
case kubeadmapi.EncryptionAlgorithmECDSA:
case kubeadmapi.EncryptionAlgorithmECDSAP256:
ecdsa++
keyName = fmt.Sprintf("%d.ecdsa", ecdsa)
default: