From c5e736d93a2946f5e812691aad37e5692d4cd6ae Mon Sep 17 00:00:00 2001 From: Adrian Pedriza Date: Tue, 17 Dec 2024 18:16:34 +0100 Subject: [PATCH] Add workload cluster kubeconfig certs rotation Signed-off-by: Adrian Pedriza --- .../k0s_controlplane_controller.go | 41 +++++++++++++++++-- 1 file changed, 37 insertions(+), 4 deletions(-) diff --git a/internal/controller/controlplane/k0s_controlplane_controller.go b/internal/controller/controlplane/k0s_controlplane_controller.go index 774be676c..7797aeab8 100644 --- a/internal/controller/controlplane/k0s_controlplane_controller.go +++ b/internal/controller/controlplane/k0s_controlplane_controller.go @@ -45,6 +45,7 @@ import ( kubeadmbootstrapv1 "sigs.k8s.io/cluster-api/bootstrap/kubeadm/api/v1beta1" capiutil "sigs.k8s.io/cluster-api/util" "sigs.k8s.io/cluster-api/util/annotations" + "sigs.k8s.io/cluster-api/util/certs" "sigs.k8s.io/cluster-api/util/collections" "sigs.k8s.io/cluster-api/util/failuredomains" "sigs.k8s.io/cluster-api/util/kubeconfig" @@ -201,23 +202,50 @@ func (c *K0sController) Reconcile(ctx context.Context, req ctrl.Request) (res ct } func (c *K0sController) reconcileKubeconfig(ctx context.Context, cluster *clusterv1.Cluster, kcp *cpv1beta1.K0sControlPlane) error { + logger := log.FromContext(ctx, "cluster", cluster.Name, "kcp", kcp.Name) + if cluster.Spec.ControlPlaneEndpoint.IsZero() { return fmt.Errorf("control plane endpoint is not set: %w", ErrNotReady) } - secretName := secret.Name(cluster.Name, secret.Kubeconfig) - err := c.Client.Get(ctx, client.ObjectKey{Namespace: cluster.Namespace, Name: secretName}, &corev1.Secret{}) + kubeconfigSecrets := []*corev1.Secret{} + + // Always rotate certificates if needed. + defer func() { + for _, kc := range kubeconfigSecrets { + needsRotation, err := kubeconfig.NeedsClientCertRotation(kc, certs.ClientCertificateRenewalDuration) + if err != nil { + logger.Error(err, "Failed to check if certificate needs rotation.") + return + } + + if needsRotation { + logger.Info("Rotating kubeconfig secret", "Secret", kc.GetName()) + if err := kubeconfig.RegenerateSecret(ctx, c.Client, kc); err != nil { + logger.Error(err, "Failed to regenerate kubeconfig") + return + } + } + } + }() + + workloadClusterKubeconfigSecret, err := secret.GetFromNamespacedName(ctx, c.Client, capiutil.ObjectKey(cluster), secret.Kubeconfig) if err != nil { if apierrors.IsNotFound(err) { return kubeconfig.CreateSecret(ctx, c.Client, cluster) } + return err } + kubeconfigSecrets = append(kubeconfigSecrets, workloadClusterKubeconfigSecret) if kcp.Spec.K0sConfigSpec.Tunneling.Enabled { if kcp.Spec.K0sConfigSpec.Tunneling.Mode == "proxy" { + secretName := secret.Name(cluster.Name+"-proxied", secret.Kubeconfig) - err := c.Client.Get(ctx, client.ObjectKey{Namespace: cluster.Namespace, Name: secretName}, &corev1.Secret{}) + + proxiedKubeconfig := &corev1.Secret{} + err := c.Client.Get(ctx, client.ObjectKey{Namespace: cluster.Namespace, Name: secretName}, proxiedKubeconfig) if err != nil { if apierrors.IsNotFound(err) { kc, err := c.generateKubeconfig(ctx, cluster, fmt.Sprintf("https://%s", cluster.Spec.ControlPlaneEndpoint.String())) @@ -236,9 +264,13 @@ func (c *K0sController) reconcileKubeconfig(ctx context.Context, cluster *cluste } return err } + kubeconfigSecrets = append(kubeconfigSecrets, proxiedKubeconfig) + } else { secretName := secret.Name(cluster.Name+"-tunneled", secret.Kubeconfig) - err := c.Client.Get(ctx, client.ObjectKey{Namespace: cluster.Namespace, Name: secretName}, &corev1.Secret{}) + + tunneledKubeconfig := &corev1.Secret{} + err := c.Client.Get(ctx, client.ObjectKey{Namespace: cluster.Namespace, Name: secretName}, tunneledKubeconfig) if err != nil { if apierrors.IsNotFound(err) { kc, err := c.generateKubeconfig(ctx, cluster, fmt.Sprintf("https://%s:%d", kcp.Spec.K0sConfigSpec.Tunneling.ServerAddress, kcp.Spec.K0sConfigSpec.Tunneling.TunnelingNodePort)) @@ -253,6 +285,7 @@ func (c *K0sController) reconcileKubeconfig(ctx context.Context, cluster *cluste } return err } + kubeconfigSecrets = append(kubeconfigSecrets, tunneledKubeconfig) } }