diff --git a/controlplane/kubeadm/internal/controllers/fakes_test.go b/controlplane/kubeadm/internal/controllers/fakes_test.go index ebc04e404246..3c7348bc4831 100644 --- a/controlplane/kubeadm/internal/controllers/fakes_test.go +++ b/controlplane/kubeadm/internal/controllers/fakes_test.go @@ -96,6 +96,10 @@ func (f fakeWorkloadCluster) AllowBootstrapTokensToGetNodes(_ context.Context) e return nil } +func (f fakeWorkloadCluster) AllowClusterAdminPermissions(_ context.Context, _ semver.Version) error { + return nil +} + func (f fakeWorkloadCluster) ReconcileKubeletRBACRole(_ context.Context, _ semver.Version) error { return nil } diff --git a/controlplane/kubeadm/internal/controllers/upgrade.go b/controlplane/kubeadm/internal/controllers/upgrade.go index f51bce9d84b7..6abf136947ab 100644 --- a/controlplane/kubeadm/internal/controllers/upgrade.go +++ b/controlplane/kubeadm/internal/controllers/upgrade.go @@ -68,6 +68,11 @@ func (r *KubeadmControlPlaneReconciler) upgradeControlPlane( return ctrl.Result{}, errors.Wrap(err, "failed to set role and role binding for kubeadm") } + // Ensure kubeadm clusterRoleBinding for v1.29+ as per https://github.com/kubernetes/kubernetes/pull/121305 + if err := workloadCluster.AllowClusterAdminPermissions(ctx, parsedVersion); err != nil { + return ctrl.Result{}, errors.Wrap(err, "failed to set cluster-admin ClusterRoleBinding for kubeadm") + } + if err := workloadCluster.UpdateKubernetesVersionInKubeadmConfigMap(ctx, parsedVersion); err != nil { return ctrl.Result{}, errors.Wrap(err, "failed to update the kubernetes version in the kubeadm config map") } diff --git a/controlplane/kubeadm/internal/workload_cluster.go b/controlplane/kubeadm/internal/workload_cluster.go index 9d7de352b817..d4c41eb89f4c 100644 --- a/controlplane/kubeadm/internal/workload_cluster.go +++ b/controlplane/kubeadm/internal/workload_cluster.go @@ -120,6 +120,7 @@ type WorkloadCluster interface { RemoveNodeFromKubeadmConfigMap(ctx context.Context, nodeName string, version semver.Version) error ForwardEtcdLeadership(ctx context.Context, machine *clusterv1.Machine, leaderCandidate *clusterv1.Machine) error AllowBootstrapTokensToGetNodes(ctx context.Context) error + AllowClusterAdminPermissions(ctx context.Context, version semver.Version) error // State recovery tasks. ReconcileEtcdMembers(ctx context.Context, nodeNames []string, version semver.Version) ([]string, error) diff --git a/controlplane/kubeadm/internal/workload_cluster_rbac.go b/controlplane/kubeadm/internal/workload_cluster_rbac.go index eb22e348051b..8fc6c74c38ba 100644 --- a/controlplane/kubeadm/internal/workload_cluster_rbac.go +++ b/controlplane/kubeadm/internal/workload_cluster_rbac.go @@ -26,6 +26,8 @@ import ( apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/controller-runtime/pkg/client" + + "sigs.k8s.io/cluster-api/util/version" ) const ( @@ -35,6 +37,11 @@ const ( // GetNodesClusterRoleName defines the name of the ClusterRole and ClusterRoleBinding to get nodes. GetNodesClusterRoleName = "kubeadm:get-nodes" + // ClusterAdminsGroupAndClusterRoleBinding is the name of the Group used for kubeadm generated cluster + // admin credentials and the name of the ClusterRoleBinding that binds the same Group to the "cluster-admin" + // built-in ClusterRole. + ClusterAdminsGroupAndClusterRoleBinding = "kubeadm:cluster-admins" + // NodesGroup defines the well-known group for all nodes. NodesGroup = "system:nodes" @@ -66,6 +73,33 @@ func (w *Workload) EnsureResource(ctx context.Context, obj client.Object) error return nil } +// AllowClusterAdminPermissions creates ClusterRoleBinding rules to use the kubeadm:cluster-admins Cluster Role created in Kubeadm v1.29. +func (w *Workload) AllowClusterAdminPermissions(ctx context.Context, targetVersion semver.Version) error { + // We intentionally only parse major/minor/patch so that the subsequent code + // also already applies to pre-release versions of new releases. + // Do nothing for Kubernetes < 1.29. + if version.Compare(targetVersion, semver.Version{Major: 1, Minor: 29, Patch: 0}, version.WithoutPreReleases()) < 0 { + return nil + } + return w.EnsureResource(ctx, &rbacv1.ClusterRoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: ClusterAdminsGroupAndClusterRoleBinding, + }, + RoleRef: rbacv1.RoleRef{ + APIGroup: rbacv1.GroupName, + Kind: "ClusterRole", + Name: "cluster-admin", + }, + Subjects: []rbacv1.Subject{ + { + Kind: rbacv1.GroupKind, + Name: ClusterAdminsGroupAndClusterRoleBinding, + }, + }, + }, + ) +} + // AllowBootstrapTokensToGetNodes creates RBAC rules to allow Node Bootstrap Tokens to list nodes. func (w *Workload) AllowBootstrapTokensToGetNodes(ctx context.Context) error { if err := w.EnsureResource(ctx, &rbacv1.ClusterRole{