Skip to content

Commit fd11302

Browse files
authored
Merge pull request #6983 from ykakarap/certificate-rotation
✨ Automatically renew control plane machine certificates before expiration through machine repave
2 parents dc63ec0 + bac8e27 commit fd11302

29 files changed

+603
-22
lines changed

api/v1alpha3/conversion.go

+1
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ func (src *Machine) ConvertTo(dstRaw conversion.Hub) error {
9999

100100
dst.Spec.NodeDeletionTimeout = restored.Spec.NodeDeletionTimeout
101101
dst.Status.NodeInfo = restored.Status.NodeInfo
102+
dst.Status.CertificatesExpiryDate = restored.Status.CertificatesExpiryDate
102103
return nil
103104
}
104105

api/v1alpha3/zz_generated.conversion.go

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

api/v1alpha4/conversion.go

+6
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,7 @@ func (src *Machine) ConvertTo(dstRaw conversion.Hub) error {
159159
}
160160

161161
dst.Spec.NodeDeletionTimeout = restored.Spec.NodeDeletionTimeout
162+
dst.Status.CertificatesExpiryDate = restored.Status.CertificatesExpiryDate
162163
return nil
163164
}
164165

@@ -333,3 +334,8 @@ func Convert_v1beta1_ControlPlaneTopology_To_v1alpha4_ControlPlaneTopology(in *c
333334
// controlPlaneTopology.nodeDrainTimeout has been added with v1beta1.
334335
return autoConvert_v1beta1_ControlPlaneTopology_To_v1alpha4_ControlPlaneTopology(in, out, s)
335336
}
337+
338+
func Convert_v1beta1_MachineStatus_To_v1alpha4_MachineStatus(in *clusterv1.MachineStatus, out *MachineStatus, s apiconversion.Scope) error {
339+
// MachineStatus.CertificatesExpiryDate has been added in v1beta1.
340+
return autoConvert_v1beta1_MachineStatus_To_v1alpha4_MachineStatus(in, out, s)
341+
}

api/v1alpha4/zz_generated.conversion.go

+6-10
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

api/v1beta1/machine_types.go

+11
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,12 @@ const (
5050
// to pause reconciliation of deletion. These hooks will prevent removal of
5151
// an instance from an infrastructure provider until all are removed.
5252
PreTerminateDeleteHookAnnotationPrefix = "pre-terminate.delete.hook.machine.cluster.x-k8s.io"
53+
54+
// MachineCertificatesExpiryDateAnnotation annotation specifies the expiry date of the machine certificates in RFC3339 format.
55+
// This annotation can be used on control plane machines to trigger rollout before certificates expire.
56+
// This annotation can be set on BootstrapConfig or Machine objects. The value set on the Machine object takes precedence.
57+
// This annotation can only be used on Control Plane Machines.
58+
MachineCertificatesExpiryDateAnnotation = "machine.cluster.x-k8s.io/certificates-expiry"
5359
)
5460

5561
// ANCHOR: MachineSpec
@@ -171,6 +177,11 @@ type MachineStatus struct {
171177
// +optional
172178
Phase string `json:"phase,omitempty"`
173179

180+
// CertificatesExpiryDate is the expiry date of the machine certificates.
181+
// This value is only set for control plane machines.
182+
// +optional
183+
CertificatesExpiryDate *metav1.Time `json:"certificatesExpiryDate,omitempty"`
184+
174185
// BootstrapReady is the state of the bootstrap provider.
175186
// +optional
176187
BootstrapReady bool `json:"bootstrapReady"`

api/v1beta1/zz_generated.deepcopy.go

+4
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

api/v1beta1/zz_generated.openapi.go

+6
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

bootstrap/kubeadm/internal/controllers/kubeadmconfig_controller.go

+32
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,15 @@ const (
6666
const (
6767
// DefaultTokenTTL is the default TTL used for tokens.
6868
DefaultTokenTTL = 15 * time.Minute
69+
70+
// This hard-coded duration matches the hard-coded value used by kubeadm certificate generation.
71+
certificateExpiryDuration = 365 * 24 * time.Hour
6972
)
7073

74+
// now returns the current time.
75+
// This is defined as a variable so that it can be overridden in unit tests.
76+
var now = time.Now
77+
7178
// InitLocker is a lock that is used around kubeadm init.
7279
type InitLocker interface {
7380
Lock(ctx context.Context, cluster *clusterv1.Cluster, machine *clusterv1.Machine) bool
@@ -447,6 +454,7 @@ func (r *KubeadmConfigReconciler) handleClusterNotInitialized(ctx context.Contex
447454
conditions.MarkFalse(scope.Config, bootstrapv1.CertificatesAvailableCondition, bootstrapv1.CertificatesGenerationFailedReason, clusterv1.ConditionSeverityWarning, err.Error())
448455
return ctrl.Result{}, err
449456
}
457+
450458
conditions.MarkTrue(scope.Config, bootstrapv1.CertificatesAvailableCondition)
451459

452460
verbosityFlag := ""
@@ -503,6 +511,10 @@ func (r *KubeadmConfigReconciler) handleClusterNotInitialized(ctx context.Contex
503511
return ctrl.Result{}, err
504512
}
505513

514+
// Update the certificate expiration time in the config.
515+
// This annotation will be used by KCP to trigger control plane machines rollout before the certificate generated on the machine are going to expire.
516+
r.addCertificateExpiryAnnotation(scope.Config)
517+
506518
return ctrl.Result{}, nil
507519
}
508520

@@ -628,6 +640,7 @@ func (r *KubeadmConfigReconciler) joinControlplane(ctx context.Context, scope *S
628640
conditions.MarkFalse(scope.Config, bootstrapv1.CertificatesAvailableCondition, bootstrapv1.CertificatesCorruptedReason, clusterv1.ConditionSeverityError, err.Error())
629641
return ctrl.Result{}, err
630642
}
643+
631644
conditions.MarkTrue(scope.Config, bootstrapv1.CertificatesAvailableCondition)
632645

633646
// Ensure that joinConfiguration.Discovery is properly set for joining node on the current cluster.
@@ -703,6 +716,9 @@ func (r *KubeadmConfigReconciler) joinControlplane(ctx context.Context, scope *S
703716
return ctrl.Result{}, err
704717
}
705718

719+
// Update the certificate expiration time in the config.
720+
r.addCertificateExpiryAnnotation(scope.Config)
721+
706722
return ctrl.Result{}, nil
707723
}
708724

@@ -1020,3 +1036,19 @@ func (r *KubeadmConfigReconciler) storeBootstrapData(ctx context.Context, scope
10201036
conditions.MarkTrue(scope.Config, bootstrapv1.DataSecretAvailableCondition)
10211037
return nil
10221038
}
1039+
1040+
// addCertificateExpiryAnnotation sets the certificate expiration time as an
1041+
// annotation on KubeadmConfig, if it doesn't exist already.
1042+
// NOTE: the certificate expiry date stored in the annotation will be slightly different from the one
1043+
// actually used in the certificates - that depends on the exact time kubeadm runs on the machine-,
1044+
// but this approximation is acceptable given that it happens before the actual expiration date.
1045+
func (r *KubeadmConfigReconciler) addCertificateExpiryAnnotation(config *bootstrapv1.KubeadmConfig) {
1046+
annotations := config.GetAnnotations()
1047+
if annotations == nil {
1048+
annotations = map[string]string{}
1049+
}
1050+
if _, ok := annotations[clusterv1.MachineCertificatesExpiryDateAnnotation]; !ok {
1051+
annotations[clusterv1.MachineCertificatesExpiryDateAnnotation] = now().Add(certificateExpiryDuration).Format(time.RFC3339)
1052+
config.SetAnnotations(annotations)
1053+
}
1054+
}

bootstrap/kubeadm/internal/controllers/kubeadmconfig_controller_test.go

+42
Original file line numberDiff line numberDiff line change
@@ -2069,6 +2069,48 @@ func TestKubeadmConfigReconciler_ResolveUsers(t *testing.T) {
20692069
}
20702070
}
20712071

2072+
func TestKubeadmConfigReconciler_ReconcileCertificateExpiryTime(t *testing.T) {
2073+
fakeNow, _ := time.Parse(time.RFC3339, "2022-01-01T00:00:00Z")
2074+
now = func() time.Time {
2075+
return fakeNow
2076+
}
2077+
oneYearFromNow := "2023-01-01T00:00:00Z"
2078+
time2 := "2023-10-01T00:00:00Z"
2079+
2080+
tests := []struct {
2081+
name string
2082+
cfg *bootstrapv1.KubeadmConfig
2083+
wantTime string
2084+
}{
2085+
{
2086+
name: "set the expiry time to one year from now if the expiry time is not set",
2087+
cfg: &bootstrapv1.KubeadmConfig{},
2088+
wantTime: oneYearFromNow,
2089+
},
2090+
{
2091+
name: "do not change the expiry time if it is already set",
2092+
cfg: &bootstrapv1.KubeadmConfig{
2093+
ObjectMeta: metav1.ObjectMeta{
2094+
Annotations: map[string]string{
2095+
clusterv1.MachineCertificatesExpiryDateAnnotation: time2,
2096+
},
2097+
},
2098+
},
2099+
wantTime: time2,
2100+
},
2101+
}
2102+
2103+
for _, tt := range tests {
2104+
t.Run(tt.name, func(t *testing.T) {
2105+
g := NewWithT(t)
2106+
k := &KubeadmConfigReconciler{}
2107+
k.addCertificateExpiryAnnotation(tt.cfg)
2108+
annotations := tt.cfg.GetAnnotations()
2109+
g.Expect(annotations[clusterv1.MachineCertificatesExpiryDateAnnotation]).To(Equal(tt.wantTime))
2110+
})
2111+
}
2112+
}
2113+
20722114
// test utils.
20732115

20742116
// newWorkerMachineForCluster returns a Machine with the passed Cluster's information and a pre-configured name.

config/crd/bases/cluster.x-k8s.io_machines.yaml

+5
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

controlplane/kubeadm/api/v1alpha3/conversion.go

+2
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,8 @@ func (src *KubeadmControlPlane) ConvertTo(dstRaw conversion.Hub) error {
8282
dst.Spec.KubeadmConfigSpec.JoinConfiguration.SkipPhases = restored.Spec.KubeadmConfigSpec.JoinConfiguration.SkipPhases
8383
}
8484

85+
dst.Spec.RolloutBefore = restored.Spec.RolloutBefore
86+
8587
return nil
8688
}
8789

controlplane/kubeadm/api/v1alpha3/zz_generated.conversion.go

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

controlplane/kubeadm/api/v1alpha4/conversion.go

+8
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ func (src *KubeadmControlPlane) ConvertTo(dstRaw conversion.Hub) error {
6767
}
6868

6969
dst.Spec.MachineTemplate.NodeDeletionTimeout = restored.Spec.MachineTemplate.NodeDeletionTimeout
70+
dst.Spec.RolloutBefore = restored.Spec.RolloutBefore
7071

7172
return nil
7273
}
@@ -140,6 +141,8 @@ func (src *KubeadmControlPlaneTemplate) ConvertTo(dstRaw conversion.Hub) error {
140141
dst.Spec.Template.Spec.MachineTemplate.NodeDeletionTimeout = restored.Spec.Template.Spec.MachineTemplate.NodeDeletionTimeout
141142
}
142143

144+
dst.Spec.Template.Spec.RolloutBefore = restored.Spec.Template.Spec.RolloutBefore
145+
143146
return nil
144147
}
145148

@@ -226,3 +229,8 @@ func Convert_v1beta1_KubeadmControlPlaneMachineTemplate_To_v1alpha4_KubeadmContr
226229
// .NodeDrainTimeout was added in v1beta1.
227230
return autoConvert_v1beta1_KubeadmControlPlaneMachineTemplate_To_v1alpha4_KubeadmControlPlaneMachineTemplate(in, out, s)
228231
}
232+
233+
func Convert_v1beta1_KubeadmControlPlaneSpec_To_v1alpha4_KubeadmControlPlaneSpec(in *controlplanev1.KubeadmControlPlaneSpec, out *KubeadmControlPlaneSpec, scope apiconversion.Scope) error {
234+
// .RolloutBefore was added in v1beta1.
235+
return autoConvert_v1beta1_KubeadmControlPlaneSpec_To_v1alpha4_KubeadmControlPlaneSpec(in, out, scope)
236+
}

controlplane/kubeadm/api/v1alpha4/zz_generated.conversion.go

+6-10
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

controlplane/kubeadm/api/v1beta1/kubeadm_control_plane_types.go

+13-1
Original file line numberDiff line numberDiff line change
@@ -70,10 +70,14 @@ type KubeadmControlPlaneSpec struct {
7070
// to use for initializing and joining machines to the control plane.
7171
KubeadmConfigSpec bootstrapv1.KubeadmConfigSpec `json:"kubeadmConfigSpec"`
7272

73+
// RolloutBefore is a field to indicate a rollout should be performed
74+
// if the specified criteria is met.
75+
// +optional
76+
RolloutBefore *RolloutBefore `json:"rolloutBefore,omitempty"`
77+
7378
// RolloutAfter is a field to indicate a rollout should be performed
7479
// after the specified time even if no changes have been made to the
7580
// KubeadmControlPlane.
76-
//
7781
// +optional
7882
RolloutAfter *metav1.Time `json:"rolloutAfter,omitempty"`
7983

@@ -109,6 +113,14 @@ type KubeadmControlPlaneMachineTemplate struct {
109113
NodeDeletionTimeout *metav1.Duration `json:"nodeDeletionTimeout,omitempty"`
110114
}
111115

116+
// RolloutBefore describes when a rollout should be performed on the KCP machines.
117+
type RolloutBefore struct {
118+
// CertificatesExpiryDays indicates a rollout needs to be performed if the
119+
// certificates of the machine will expire within the specified days.
120+
// +optional
121+
CertificatesExpiryDays *int32 `json:"certificatesExpiryDays,omitempty"`
122+
}
123+
112124
// RolloutStrategy describes how to replace existing machines
113125
// with new ones.
114126
type RolloutStrategy struct {

0 commit comments

Comments
 (0)