sigs.k8s.io/cluster-api@v1.7.1/controlplane/kubeadm/internal/workload_cluster_rbac.go (about)

     1  /*
     2  Copyright 2020 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package internal
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  
    23  	"github.com/blang/semver/v4"
    24  	"github.com/pkg/errors"
    25  	rbacv1 "k8s.io/api/rbac/v1"
    26  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    27  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    28  	"sigs.k8s.io/controller-runtime/pkg/client"
    29  
    30  	"sigs.k8s.io/cluster-api/util/version"
    31  )
    32  
    33  const (
    34  	// NodeBootstrapTokenAuthGroup specifies which group a Node Bootstrap Token should be authenticated in.
    35  	NodeBootstrapTokenAuthGroup = "system:bootstrappers:kubeadm:default-node-token"
    36  
    37  	// GetNodesClusterRoleName defines the name of the ClusterRole and ClusterRoleBinding to get nodes.
    38  	GetNodesClusterRoleName = "kubeadm:get-nodes"
    39  
    40  	// ClusterAdminsGroupAndClusterRoleBinding is the name of the Group used for kubeadm generated cluster
    41  	// admin credentials and the name of the ClusterRoleBinding that binds the same Group to the "cluster-admin"
    42  	// built-in ClusterRole.
    43  	ClusterAdminsGroupAndClusterRoleBinding = "kubeadm:cluster-admins"
    44  
    45  	// NodesGroup defines the well-known group for all nodes.
    46  	NodesGroup = "system:nodes"
    47  
    48  	// KubeletConfigMapRolePrefix defines base kubelet configuration ConfigMap role prefix.
    49  	KubeletConfigMapRolePrefix = "kubeadm:"
    50  
    51  	// KubeletConfigMapName defines base kubelet configuration ConfigMap name for kubeadm < 1.24.
    52  	KubeletConfigMapName = "kubelet-config-%d.%d"
    53  
    54  	// UnversionedKubeletConfigMapName defines base kubelet configuration ConfigMap for kubeadm >= 1.24.
    55  	UnversionedKubeletConfigMapName = "kubelet-config"
    56  )
    57  
    58  // EnsureResource creates a resoutce if the target resource doesn't exist. If the resource exists already, this function will ignore the resource instead.
    59  func (w *Workload) EnsureResource(ctx context.Context, obj client.Object) error {
    60  	testObj := obj.DeepCopyObject().(client.Object)
    61  	key := client.ObjectKeyFromObject(obj)
    62  	if err := w.Client.Get(ctx, key, testObj); err != nil && !apierrors.IsNotFound(err) {
    63  		return errors.Wrapf(err, "failed to determine if resource %s/%s already exists", key.Namespace, key.Name)
    64  	} else if err == nil {
    65  		// If object already exists, nothing left to do
    66  		return nil
    67  	}
    68  	if err := w.Client.Create(ctx, obj); err != nil {
    69  		if !apierrors.IsAlreadyExists(err) {
    70  			return errors.Wrapf(err, "unable to create resource %s/%s on workload cluster", key.Namespace, key.Name)
    71  		}
    72  	}
    73  	return nil
    74  }
    75  
    76  // AllowClusterAdminPermissions creates ClusterRoleBinding rules to use the kubeadm:cluster-admins Cluster Role created in Kubeadm v1.29.
    77  func (w *Workload) AllowClusterAdminPermissions(ctx context.Context, targetVersion semver.Version) error {
    78  	// We intentionally only parse major/minor/patch so that the subsequent code
    79  	// also already applies to pre-release versions of new releases.
    80  	// Do nothing for Kubernetes < 1.29.
    81  	if version.Compare(targetVersion, semver.Version{Major: 1, Minor: 29, Patch: 0}, version.WithoutPreReleases()) < 0 {
    82  		return nil
    83  	}
    84  	return w.EnsureResource(ctx, &rbacv1.ClusterRoleBinding{
    85  		ObjectMeta: metav1.ObjectMeta{
    86  			Name: ClusterAdminsGroupAndClusterRoleBinding,
    87  		},
    88  		RoleRef: rbacv1.RoleRef{
    89  			APIGroup: rbacv1.GroupName,
    90  			Kind:     "ClusterRole",
    91  			Name:     "cluster-admin",
    92  		},
    93  		Subjects: []rbacv1.Subject{
    94  			{
    95  				Kind: rbacv1.GroupKind,
    96  				Name: ClusterAdminsGroupAndClusterRoleBinding,
    97  			},
    98  		},
    99  	},
   100  	)
   101  }
   102  
   103  // AllowBootstrapTokensToGetNodes creates RBAC rules to allow Node Bootstrap Tokens to list nodes.
   104  func (w *Workload) AllowBootstrapTokensToGetNodes(ctx context.Context) error {
   105  	if err := w.EnsureResource(ctx, &rbacv1.ClusterRole{
   106  		ObjectMeta: metav1.ObjectMeta{
   107  			Name:      GetNodesClusterRoleName,
   108  			Namespace: metav1.NamespaceSystem,
   109  		},
   110  		Rules: []rbacv1.PolicyRule{
   111  			{
   112  				Verbs:     []string{"get"},
   113  				APIGroups: []string{""},
   114  				Resources: []string{"nodes"},
   115  			},
   116  		},
   117  	}); err != nil {
   118  		return err
   119  	}
   120  
   121  	return w.EnsureResource(ctx, &rbacv1.ClusterRoleBinding{
   122  		ObjectMeta: metav1.ObjectMeta{
   123  			Name:      GetNodesClusterRoleName,
   124  			Namespace: metav1.NamespaceSystem,
   125  		},
   126  		RoleRef: rbacv1.RoleRef{
   127  			APIGroup: rbacv1.GroupName,
   128  			Kind:     "ClusterRole",
   129  			Name:     GetNodesClusterRoleName,
   130  		},
   131  		Subjects: []rbacv1.Subject{
   132  			{
   133  				Kind: rbacv1.GroupKind,
   134  				Name: NodeBootstrapTokenAuthGroup,
   135  			},
   136  		},
   137  	})
   138  }
   139  
   140  func generateKubeletConfigName(version semver.Version) string {
   141  	majorMinor := semver.Version{Major: version.Major, Minor: version.Minor}
   142  	if majorMinor.GTE(minVerUnversionedKubeletConfig) {
   143  		return UnversionedKubeletConfigMapName
   144  	}
   145  	return fmt.Sprintf(KubeletConfigMapName, version.Major, version.Minor)
   146  }
   147  
   148  func generateKubeletConfigRoleName(version semver.Version) string {
   149  	return KubeletConfigMapRolePrefix + generateKubeletConfigName(version)
   150  }
   151  
   152  // ReconcileKubeletRBACBinding will create a RoleBinding for the new kubelet version during upgrades.
   153  // If the role binding already exists this function is a no-op.
   154  func (w *Workload) ReconcileKubeletRBACBinding(ctx context.Context, version semver.Version) error {
   155  	roleName := generateKubeletConfigRoleName(version)
   156  	return w.EnsureResource(ctx, &rbacv1.RoleBinding{
   157  		ObjectMeta: metav1.ObjectMeta{
   158  			Namespace: metav1.NamespaceSystem,
   159  			Name:      roleName,
   160  		},
   161  		Subjects: []rbacv1.Subject{
   162  			{
   163  				APIGroup: rbacv1.GroupName,
   164  				Kind:     rbacv1.GroupKind,
   165  				Name:     NodesGroup,
   166  			},
   167  			{
   168  				APIGroup: rbacv1.GroupName,
   169  				Kind:     rbacv1.GroupKind,
   170  				Name:     NodeBootstrapTokenAuthGroup,
   171  			},
   172  		},
   173  		RoleRef: rbacv1.RoleRef{
   174  			APIGroup: rbacv1.GroupName,
   175  			Kind:     "Role",
   176  			Name:     roleName,
   177  		},
   178  	})
   179  }
   180  
   181  // ReconcileKubeletRBACRole will create a Role for the new kubelet version during upgrades.
   182  // If the role already exists this function is a no-op.
   183  func (w *Workload) ReconcileKubeletRBACRole(ctx context.Context, version semver.Version) error {
   184  	return w.EnsureResource(ctx, &rbacv1.Role{
   185  		ObjectMeta: metav1.ObjectMeta{
   186  			Name:      generateKubeletConfigRoleName(version),
   187  			Namespace: metav1.NamespaceSystem,
   188  		},
   189  		Rules: []rbacv1.PolicyRule{
   190  			{
   191  				Verbs:         []string{"get"},
   192  				APIGroups:     []string{""},
   193  				Resources:     []string{"configmaps"},
   194  				ResourceNames: []string{generateKubeletConfigName(version)},
   195  			},
   196  		},
   197  	})
   198  }