Skip to content
Merged
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
6 changes: 5 additions & 1 deletion controlplane/kubeadm/internal/controllers/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -506,8 +506,12 @@ func (r *KubeadmControlPlaneReconciler) reconcile(ctx context.Context, controlPl
return ctrl.Result{Requeue: true}, nil
}

parsedVersionForPermissions, err := semver.ParseTolerant(controlPlane.KCP.Spec.Version)
if err != nil {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Shouldn't we check for nil

return ctrl.Result{}, errors.Wrapf(err, "failed to parse kubernetes version %q", controlPlane.KCP.Spec.Version)
}
// Ensure kubeadm role bindings for v1.18+
if err := workloadCluster.AllowBootstrapTokensToGetNodes(ctx); err != nil {
if err := workloadCluster.EnsureKubeadmPermissions(ctx, parsedVersionForPermissions); err != nil {
return ctrl.Result{}, errors.Wrap(err, "failed to set role and role binding for kubeadm")
}

Expand Down
12 changes: 12 additions & 0 deletions controlplane/kubeadm/internal/controllers/controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,11 @@ import (
"github.com/pkg/errors"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
rbacv1 "k8s.io/api/rbac/v1"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/client-go/tools/clientcmd"
Expand Down Expand Up @@ -3743,6 +3746,15 @@ func TestObjectsPendingDelete(t *testing.T) {
// test utils.

func newFakeClient(initObjs ...client.Object) client.Client {
// Use a new scheme to avoid side effects if multiple tests are sharing the same global scheme.
scheme := runtime.NewScheme()
_ = appsv1.AddToScheme(scheme)
_ = corev1.AddToScheme(scheme)
_ = rbacv1.AddToScheme(scheme)
_ = apiextensionsv1.AddToScheme(scheme)
_ = clusterv1.AddToScheme(scheme)
_ = bootstrapv1.AddToScheme(scheme)
_ = controlplanev1.AddToScheme(scheme)
return &fakeClient{
startTime: time.Now(),
Client: fake.NewClientBuilder().WithObjects(initObjs...).WithStatusSubresource(&controlplanev1.KubeadmControlPlane{}).Build(),
Expand Down
20 changes: 3 additions & 17 deletions controlplane/kubeadm/internal/controllers/upgrade.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,23 +55,9 @@ func (r *KubeadmControlPlaneReconciler) upgradeControlPlane(
return ctrl.Result{}, errors.Wrapf(err, "failed to parse kubernetes version %q", controlPlane.KCP.Spec.Version)
}

if err := workloadCluster.ReconcileKubeletRBACRole(ctx, parsedVersion); err != nil {
return ctrl.Result{}, errors.Wrap(err, "failed to reconcile the remote kubelet RBAC role")
}

if err := workloadCluster.ReconcileKubeletRBACBinding(ctx, parsedVersion); err != nil {
return ctrl.Result{}, errors.Wrap(err, "failed to reconcile the remote kubelet RBAC binding")
}

// Ensure kubeadm cluster role & bindings for v1.18+
// as per https://github.com/kubernetes/kubernetes/commit/b117a928a6c3f650931bdac02a41fca6680548c4
if err := workloadCluster.AllowBootstrapTokensToGetNodes(ctx); err != nil {
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")
// Creates ClusterRoleBinding and ClusterRoles introduced by new versions of kubeadm.
if err := workloadCluster.EnsureKubeadmPermissions(ctx, parsedVersion); err != nil {
return ctrl.Result{}, errors.Wrap(err, "failed to update control plane: failed to set cluster-admin ClusterRoleBinding for kubeadm")
}

kubeadmCMMutators := make([]func(*bootstrapv1.ClusterConfiguration), 0)
Expand Down
4 changes: 4 additions & 0 deletions controlplane/kubeadm/internal/controllers/upgrade_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,9 @@ func TestKubeadmControlPlaneReconciler_RolloutStrategy_ScaleUp(t *testing.T) {
Management: &internal.Management{Client: env},
Workload: &fakeWorkloadCluster{
Status: internal.ClusterStatus{Nodes: 1},
Workload: &internal.Workload{

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

why?

Client: env,
},
},
},
managementClusterUncached: &fakeManagementCluster{
Expand Down Expand Up @@ -229,6 +232,7 @@ func TestKubeadmControlPlaneReconciler_RolloutStrategy_ScaleDown(t *testing.T) {
}
fakeClient := newFakeClient(objs...)
fmc.Reader = fakeClient
fmc.Workload.Workload = &internal.Workload{Client: fakeClient}
r := &KubeadmControlPlaneReconciler{
Client: fakeClient,
SecretCachingClient: fakeClient,
Expand Down
3 changes: 1 addition & 2 deletions controlplane/kubeadm/internal/workload_cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,7 @@ type WorkloadCluster interface {
UpdateCoreDNS(ctx context.Context, kcp *controlplanev1.KubeadmControlPlane, version semver.Version) error
RemoveEtcdMemberForMachine(ctx context.Context, machine *clusterv1.Machine) 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
EnsureKubeadmPermissions(ctx context.Context, version semver.Version) error
UpdateClusterConfiguration(ctx context.Context, version semver.Version, mutators ...func(*bootstrapv1.ClusterConfiguration)) error

// State recovery tasks.
Expand Down
56 changes: 47 additions & 9 deletions controlplane/kubeadm/internal/workload_cluster_rbac.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,14 @@ const (

// UnversionedKubeletConfigMapName defines base kubelet configuration ConfigMap for kubeadm >= 1.24.
UnversionedKubeletConfigMapName = "kubelet-config"
// KubeletAPIAdminClusterRoleBindingName is the name of the ClusterRoleBinding for the apiserver kubelet client.
KubeletAPIAdminClusterRoleBindingName = "kubeadm:apiserver-kubelet-client"

// KubeletAPIAdminClusterRoleName is the name of the built-in ClusterRole for kubelet API access.
KubeletAPIAdminClusterRoleName = "system:kubelet-api-admin"

// APIServerKubeletClientCertCommonName defines kubelet client certificate common name (CN).
APIServerKubeletClientCertCommonName = "kube-apiserver-kubelet-client"
)

// EnsureResource creates a resoutce if the target resource doesn't exist. If the resource exists already, this function will ignore the resource instead.
Expand All @@ -73,15 +81,22 @@ 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 {
// EnsureKubeadmPermissions creates ClusterRoleBinding and ClusterRoles introduced by new versions of kubeadm.
func (w *Workload) EnsureKubeadmPermissions(ctx context.Context, targetVersion semver.Version) error {
// Note: this code mimics the changes that kubeadm upgrade is doing.
// Cluster API must run the corresponding code when the user are upgrading to the minor where kubeadm introduced the change,
// including also patch releases. This is why the upper bound is the minor where a change was introduced plus one.
// Also, Cluster API applies new cluster roles when upgrading to releases older than when the changes
// have been introduced to kubeadm, so upgrade will keep working also in case the changes are backported to older versions.
if version.Compare(targetVersion, semver.Version{Major: 1, Minor: 38, Patch: 0}, version.WithoutPreReleases()) >= 0 {
return nil
}
return w.EnsureResource(ctx, &rbacv1.ClusterRoleBinding{

// Kubeadm added this role with K8s 1.29 when introducing
// a cleaner split between kubeadm:cluster-admins and system:masters.
// Rif https://github.com/kubernetes/kubernetes/pull/121305
// This change can be dropped when the min supported Kubernetes version in Cluster API >= 1.30.
err := w.EnsureResource(ctx, &rbacv1.ClusterRoleBinding{
ObjectMeta: metav1.ObjectMeta{
Name: ClusterAdminsGroupAndClusterRoleBinding,
},
Expand All @@ -96,8 +111,31 @@ func (w *Workload) AllowClusterAdminPermissions(ctx context.Context, targetVersi
Name: ClusterAdminsGroupAndClusterRoleBinding,
},
},
},
)
})
if err != nil {
return err
}

// Kubeadm introduced this role with K8s 1.37 when reducing
// the scope of the credential provided to the API server for accessing kubelet.
// Rif https://github.com/kubernetes/kubernetes/pull/138957.
// This change can be dropped when the min supported Kubernetes version in Cluster API >=1.38.
return w.EnsureResource(ctx, &rbacv1.ClusterRoleBinding{
ObjectMeta: metav1.ObjectMeta{
Name: KubeletAPIAdminClusterRoleBindingName,
},
RoleRef: rbacv1.RoleRef{
APIGroup: rbacv1.GroupName,
Kind: "ClusterRole",
Name: KubeletAPIAdminClusterRoleName,
},
Subjects: []rbacv1.Subject{
{
Kind: rbacv1.UserKind,
Name: APIServerKubeletClientCertCommonName,
},
},
})
}

// AllowBootstrapTokensToGetNodes creates RBAC rules to allow Node Bootstrap Tokens to list nodes.
Expand Down
Loading
Loading