Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions api/bootstrap/kubeadm/v1beta1/conversion.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,9 @@ func RestoreKubeadmConfigSpec(restored *bootstrapv1.KubeadmConfigSpec, dst *boot
dst.ClusterConfiguration.CACertificateValidityPeriodDays = restored.ClusterConfiguration.CACertificateValidityPeriodDays
}
}
if restored.ClusterConfiguration.EncryptionAlgorithm != "" {
dst.ClusterConfiguration.EncryptionAlgorithm = restored.ClusterConfiguration.EncryptionAlgorithm
}
}

func RestoreBoolIntentKubeadmConfigSpec(src *KubeadmConfigSpec, dst *bootstrapv1.KubeadmConfigSpec, hasRestored bool, restored *bootstrapv1.KubeadmConfigSpec) error {
Expand Down
1 change: 1 addition & 0 deletions api/bootstrap/kubeadm/v1beta1/zz_generated.conversion.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

24 changes: 24 additions & 0 deletions api/bootstrap/kubeadm/v1beta2/kubeadm_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,23 @@ const (
KubeadmConfigDataSecretNotAvailableReason = clusterv1.NotAvailableReason
)

// EncryptionAlgorithmType can define an asymmetric encryption algorithm type.
// +kubebuilder:validation:Enum=ECDSA-P256;ECDSA-P384;RSA-2048;RSA-3072;RSA-4096
type EncryptionAlgorithmType string

const (
// EncryptionAlgorithmECDSAP256 defines the ECDSA encryption algorithm type with curve P256.
EncryptionAlgorithmECDSAP256 EncryptionAlgorithmType = "ECDSA-P256"
// EncryptionAlgorithmECDSAP384 defines the ECDSA encryption algorithm type with curve P384.
EncryptionAlgorithmECDSAP384 EncryptionAlgorithmType = "ECDSA-P384"
// 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"
)

// InitConfiguration contains a list of elements that is specific "kubeadm init"-only runtime
// information.
// +kubebuilder:validation:MinProperties=1
Expand Down Expand Up @@ -199,6 +216,13 @@ type ClusterConfiguration struct {
// +kubebuilder:validation:Minimum=1
// +kubebuilder:validation:Maximum=36500
CACertificateValidityPeriodDays int32 `json:"caCertificateValidityPeriodDays,omitempty"`

// encryptionAlgorithm holds the type of asymmetric encryption algorithm used for keys and certificates.
// Can be one of "RSA-2048", "RSA-3072", "RSA-4096", "ECDSA-P256" or "ECDSA-P384".
// If not specified, Cluster API will use RSA-2048 as default.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// If not specified, Cluster API will use RSA-2048 as default.
// If not specified, Cluster API will use RSA-2048 as default.
// When this field is modified every certificate generated afterward will use the new
// encryptionAlgorithm. Existing CA certificates and service account keys are not rotated.

// This field is only supported with Kubernetes v1.31 or above.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did some testing and luckily noticed that ECDSA-P384 is only available on Kubernetes v1.34 or above
Let's add a note for that.

All others are fine as they have been around since v1.31: https://kubernetes.io/docs/reference/config-api/kubeadm-config.v1beta4/

// +optional
EncryptionAlgorithm EncryptionAlgorithmType `json:"encryptionAlgorithm,omitempty"`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's set this field to RSA-4096 in test/e2e/data/infrastructure-docker/main/clusterclass-quick-start-runtimesdk.yaml KubeadmControlPlaneTemplate for some free e2e test coverage

}

// IsDefined returns true if the ClusterConfiguration is defined.
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions bootstrap/kubeadm/types/upstreamv1beta3/conversion_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,7 @@ func hubClusterConfigurationFuzzer(obj *bootstrapv1.ClusterConfiguration, c rand

obj.CertificateValidityPeriodDays = 0
obj.CACertificateValidityPeriodDays = 0
obj.EncryptionAlgorithm = ""

for i, arg := range obj.APIServer.ExtraArgs {
if arg.Value == nil {
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion bootstrap/kubeadm/types/upstreamv1beta4/conversion.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,6 @@ func (dst *JoinConfiguration) ConvertFrom(srcRaw conversion.Hub) error {
func Convert_upstreamv1beta4_ClusterConfiguration_To_v1beta2_ClusterConfiguration(in *ClusterConfiguration, out *bootstrapv1.ClusterConfiguration, s apimachineryconversion.Scope) error {
// Following fields do not exist in CABPK v1beta1 version:
// - Proxy (Not supported yet)
// - EncryptionAlgorithm (Not supported yet)
if err := autoConvert_upstreamv1beta4_ClusterConfiguration_To_v1beta2_ClusterConfiguration(in, out, s); err != nil {
return err
}
Expand Down
1 change: 0 additions & 1 deletion bootstrap/kubeadm/types/upstreamv1beta4/conversion_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,6 @@ func spokeClusterConfigurationFuzzer(obj *ClusterConfiguration, c randfill.Conti
c.FillNoCustom(obj)

obj.Proxy = Proxy{}
obj.EncryptionAlgorithm = ""
obj.CertificateValidityPeriod = ptr.To[metav1.Duration](metav1.Duration{Duration: time.Duration(c.Int31n(3*365)+1) * time.Hour * 24})
obj.CACertificateValidityPeriod = ptr.To[metav1.Duration](metav1.Duration{Duration: time.Duration(c.Int31n(100*365)+1) * time.Hour * 24})

Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

20 changes: 11 additions & 9 deletions controlplane/kubeadm/internal/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import (
"k8s.io/client-go/rest"
"sigs.k8s.io/controller-runtime/pkg/client"

bootstrapv1 "sigs.k8s.io/cluster-api/api/bootstrap/kubeadm/v1beta2"
clusterv1 "sigs.k8s.io/cluster-api/api/core/v1beta2"
"sigs.k8s.io/cluster-api/controllers/clustercache"
"sigs.k8s.io/cluster-api/util/cache"
Expand All @@ -43,7 +44,7 @@ type ManagementCluster interface {

GetMachinesForCluster(ctx context.Context, cluster *clusterv1.Cluster, filters ...collections.Func) (collections.Machines, error)
GetMachinePoolsForCluster(ctx context.Context, cluster *clusterv1.Cluster) (*clusterv1.MachinePoolList, error)
GetWorkloadCluster(ctx context.Context, clusterKey client.ObjectKey) (WorkloadCluster, error)
GetWorkloadCluster(ctx context.Context, clusterKey client.ObjectKey, keyEncryptionAlgorithm bootstrapv1.EncryptionAlgorithmType) (WorkloadCluster, error)
}

// Management holds operations on the management cluster.
Expand All @@ -59,13 +60,14 @@ type Management struct {

// ClientCertEntry is an Entry for the Cache that stores the client cert.
type ClientCertEntry struct {
Cluster client.ObjectKey
ClientCert *tls.Certificate
Cluster client.ObjectKey
ClientCert *tls.Certificate
EncryptionAlgorithm bootstrapv1.EncryptionAlgorithmType
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's also add Cluster.UID to the struct and the cache key + set it below where we use ClientCertEntry

Otherwise we would re-use certs even through cluster re-creations

(was broken before this PR but it's an easy change so let's just include it here)

}

// Key returns the cache key of a ClientCertEntry.
func (r ClientCertEntry) Key() string {
return r.Cluster.String()
return fmt.Sprintf("%s/%s", r.Cluster.String(), r.EncryptionAlgorithm)
}

// RemoteClusterConnectionError represents a failure to connect to a remote cluster.
Expand All @@ -77,7 +79,7 @@ type RemoteClusterConnectionError struct {
// Error satisfies the error interface.
func (e *RemoteClusterConnectionError) Error() string { return e.Name + ": " + e.Err.Error() }

// Unwrap satisfies the unwrap error inteface.
// Unwrap satisfies the unwrap error interface.
func (e *RemoteClusterConnectionError) Unwrap() error { return e.Err }

// Get implements client.Reader.
Expand Down Expand Up @@ -111,7 +113,7 @@ func (m *Management) GetMachinePoolsForCluster(ctx context.Context, cluster *clu

// GetWorkloadCluster builds a cluster object.
// The cluster comes with an etcd client generator to connect to any etcd pod living on a managed machine.
func (m *Management) GetWorkloadCluster(ctx context.Context, clusterKey client.ObjectKey) (WorkloadCluster, error) {
func (m *Management) GetWorkloadCluster(ctx context.Context, clusterKey client.ObjectKey, keyEncryptionAlgorithm bootstrapv1.EncryptionAlgorithmType) (WorkloadCluster, error) {
// TODO(chuckha): Inject this dependency.
// TODO(chuckha): memoize this function. The workload client only exists as long as a reconciliation loop.
restConfig, err := m.ClusterCache.GetRESTConfig(ctx, clusterKey)
Expand Down Expand Up @@ -142,15 +144,15 @@ func (m *Management) GetWorkloadCluster(ctx context.Context, clusterKey client.O
// Get client cert from cache if possible, otherwise generate it and add it to the cache.
// TODO: When we implement ClusterConfiguration.EncryptionAlgorithm we should add it to
// the ClientCertEntries and make it part of the key.
Comment on lines 145 to 146
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// TODO: When we implement ClusterConfiguration.EncryptionAlgorithm we should add it to
// the ClientCertEntries and make it part of the key.

This is done now

if entry, ok := m.ClientCertCache.Has(ClientCertEntry{Cluster: clusterKey}.Key()); ok {
if entry, ok := m.ClientCertCache.Has(ClientCertEntry{Cluster: clusterKey, EncryptionAlgorithm: keyEncryptionAlgorithm}.Key()); ok {
clientCert = *entry.ClientCert
} else {
// The client cert expires after 10 years, but that's okay as the cache has a TTL of 1 day.
clientCert, err = generateClientCert(crtData, keyData)
clientCert, err = generateClientCert(crtData, keyData, keyEncryptionAlgorithm)
if err != nil {
return nil, err
}
m.ClientCertCache.Add(ClientCertEntry{Cluster: clusterKey, ClientCert: &clientCert})
m.ClientCertCache.Add(ClientCertEntry{Cluster: clusterKey, ClientCert: &clientCert, EncryptionAlgorithm: keyEncryptionAlgorithm})
}
} else {
clientCert, err = m.getAPIServerEtcdClientCert(ctx, clusterKey)
Expand Down
3 changes: 2 additions & 1 deletion controlplane/kubeadm/internal/cluster_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import (
"sigs.k8s.io/controller-runtime/pkg/controller"
"sigs.k8s.io/controller-runtime/pkg/reconcile"

bootstrapv1 "sigs.k8s.io/cluster-api/api/bootstrap/kubeadm/v1beta2"
clusterv1 "sigs.k8s.io/cluster-api/api/core/v1beta2"
"sigs.k8s.io/cluster-api/controllers/clustercache"
"sigs.k8s.io/cluster-api/controllers/remote"
Expand Down Expand Up @@ -249,7 +250,7 @@ func TestGetWorkloadCluster(t *testing.T) {
})
g.Expect(err).ToNot(HaveOccurred())

workloadCluster, err := m.GetWorkloadCluster(ctx, tt.clusterKey)
workloadCluster, err := m.GetWorkloadCluster(ctx, tt.clusterKey, bootstrapv1.EncryptionAlgorithmRSA2048)
if tt.expectErr {
g.Expect(err).To(HaveOccurred())
g.Expect(workloadCluster).To(BeNil())
Expand Down
11 changes: 10 additions & 1 deletion controlplane/kubeadm/internal/control_plane.go
Original file line number Diff line number Diff line change
Expand Up @@ -372,7 +372,7 @@ func (c *ControlPlane) GetWorkloadCluster(ctx context.Context) (WorkloadCluster,
return c.workloadCluster, nil
}

workloadCluster, err := c.managementCluster.GetWorkloadCluster(ctx, client.ObjectKeyFromObject(c.Cluster))
workloadCluster, err := c.managementCluster.GetWorkloadCluster(ctx, client.ObjectKeyFromObject(c.Cluster), c.GetKeyEncryptionAlgorithm())
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -467,3 +467,12 @@ func (c *ControlPlane) StatusToLogKeyAndValues(newMachine, deletedMachine *clust
"etcdMembers", strings.Join(etcdMembers, ", "),
}
}

// GetKeyEncryptionAlgorithm returns the control plane EncryptionAlgorithm.
// If its unset the default encryption algorithm is returned.
func (c *ControlPlane) GetKeyEncryptionAlgorithm() bootstrapv1.EncryptionAlgorithmType {
if c.KCP.Spec.KubeadmConfigSpec.ClusterConfiguration.EncryptionAlgorithm == "" {
return bootstrapv1.EncryptionAlgorithmRSA2048
}
return c.KCP.Spec.KubeadmConfigSpec.ClusterConfiguration.EncryptionAlgorithm
}
2 changes: 1 addition & 1 deletion controlplane/kubeadm/internal/controllers/fakes_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ func (f *fakeManagementCluster) List(ctx context.Context, list client.ObjectList
return f.Reader.List(ctx, list, opts...)
}

func (f *fakeManagementCluster) GetWorkloadCluster(_ context.Context, _ client.ObjectKey) (internal.WorkloadCluster, error) {
func (f *fakeManagementCluster) GetWorkloadCluster(_ context.Context, _ client.ObjectKey, _ bootstrapv1.EncryptionAlgorithmType) (internal.WorkloadCluster, error) {
return f.Workload, f.WorkloadErr
}

Expand Down
4 changes: 2 additions & 2 deletions controlplane/kubeadm/internal/controllers/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@ func (r *KubeadmControlPlaneReconciler) reconcileKubeconfig(ctx context.Context,
if endpoint.IsZero() {
return ctrl.Result{}, nil
}

controllerOwnerRef := *metav1.NewControllerRef(controlPlane.KCP, controlplanev1.GroupVersion.WithKind(kubeadmControlPlaneKind))
clusterName := util.ObjectKey(controlPlane.Cluster)
configSecret, err := secret.GetFromNamespacedName(ctx, r.SecretCachingClient, clusterName, secret.Kubeconfig)
Expand All @@ -64,6 +63,7 @@ func (r *KubeadmControlPlaneReconciler) reconcileKubeconfig(ctx context.Context,
clusterName,
endpoint.String(),
controllerOwnerRef,
kubeconfig.KeyEncryptionAlgorithm(controlPlane.GetKeyEncryptionAlgorithm()),
)
if errors.Is(createErr, kubeconfig.ErrDependentCertificateNotFound) {
return ctrl.Result{RequeueAfter: dependentCertRequeueAfter}, nil
Expand All @@ -90,7 +90,7 @@ func (r *KubeadmControlPlaneReconciler) reconcileKubeconfig(ctx context.Context,

if needsRotation {
log.Info("Rotating kubeconfig secret")
if err := kubeconfig.RegenerateSecret(ctx, r.Client, configSecret); err != nil {
if err := kubeconfig.RegenerateSecret(ctx, r.Client, configSecret, kubeconfig.KeyEncryptionAlgorithm(controlPlane.GetKeyEncryptionAlgorithm())); err != nil {
return ctrl.Result{}, errors.Wrap(err, "failed to regenerate kubeconfig")
}
}
Expand Down
3 changes: 2 additions & 1 deletion controlplane/kubeadm/internal/controllers/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,8 @@ func (r *KubeadmControlPlaneReconciler) updateControlPlane(
workloadCluster.UpdateAPIServerInKubeadmConfigMap(controlPlane.KCP.Spec.KubeadmConfigSpec.ClusterConfiguration.APIServer),
workloadCluster.UpdateControllerManagerInKubeadmConfigMap(controlPlane.KCP.Spec.KubeadmConfigSpec.ClusterConfiguration.ControllerManager),
workloadCluster.UpdateSchedulerInKubeadmConfigMap(controlPlane.KCP.Spec.KubeadmConfigSpec.ClusterConfiguration.Scheduler),
workloadCluster.UpdateCertificateValidityPeriodDays(controlPlane.KCP.Spec.KubeadmConfigSpec.ClusterConfiguration.CertificateValidityPeriodDays))
workloadCluster.UpdateCertificateValidityPeriodDays(controlPlane.KCP.Spec.KubeadmConfigSpec.ClusterConfiguration.CertificateValidityPeriodDays),
workloadCluster.UpdateEncryptionAlgorithm(controlPlane.KCP.Spec.KubeadmConfigSpec.ClusterConfiguration.EncryptionAlgorithm))

// Etcd local and external are mutually exclusive and they cannot be switched, once set.
if controlPlane.IsEtcdManaged() {
Expand Down
4 changes: 2 additions & 2 deletions controlplane/kubeadm/internal/filters_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -324,7 +324,7 @@ func TestMatchesKubeadmConfig(t *testing.T) {
+ CertificatesDir: "foo",
ImageRepository: "",
FeatureGates: nil,
... // 2 identical fields
... // 3 identical fields
},
InitConfiguration: {NodeRegistration: {ImagePullPolicy: "IfNotPresent"}},
JoinConfiguration: {NodeRegistration: {ImagePullPolicy: "IfNotPresent"}},
Expand Down Expand Up @@ -1659,7 +1659,7 @@ func TestUpToDate(t *testing.T) {
+ CertificatesDir: "bar",
ImageRepository: "",
FeatureGates: nil,
... // 2 identical fields
... // 3 identical fields
},
InitConfiguration: {NodeRegistration: {ImagePullPolicy: "IfNotPresent"}},
JoinConfiguration: {NodeRegistration: {ImagePullPolicy: "IfNotPresent"}},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ func (webhook *KubeadmControlPlane) ValidateUpdate(_ context.Context, oldObj, ne
{spec, kubeadmConfigSpec, clusterConfiguration, scheduler},
{spec, kubeadmConfigSpec, clusterConfiguration, scheduler, "*"},
{spec, kubeadmConfigSpec, clusterConfiguration, "certificateValidityPeriodDays"},
{spec, kubeadmConfigSpec, clusterConfiguration, "encryptionAlgorithm"},
// spec.kubeadmConfigSpec.initConfiguration
{spec, kubeadmConfigSpec, initConfiguration, nodeRegistration},
{spec, kubeadmConfigSpec, initConfiguration, nodeRegistration, "*"},
Expand Down
Loading