k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/pkg/controller/clusterroleaggregation/clusterroleaggregation_controller.go (about)

     1  /*
     2  Copyright 2017 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 clusterroleaggregation
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"sort"
    23  	"time"
    24  
    25  	rbacv1ac "k8s.io/client-go/applyconfigurations/rbac/v1"
    26  	"k8s.io/klog/v2"
    27  
    28  	rbacv1 "k8s.io/api/rbac/v1"
    29  	"k8s.io/apimachinery/pkg/api/equality"
    30  	"k8s.io/apimachinery/pkg/api/errors"
    31  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    32  	"k8s.io/apimachinery/pkg/labels"
    33  	utilruntime "k8s.io/apimachinery/pkg/util/runtime"
    34  	"k8s.io/apimachinery/pkg/util/wait"
    35  	rbacinformers "k8s.io/client-go/informers/rbac/v1"
    36  	rbacclient "k8s.io/client-go/kubernetes/typed/rbac/v1"
    37  	rbaclisters "k8s.io/client-go/listers/rbac/v1"
    38  	"k8s.io/client-go/tools/cache"
    39  	"k8s.io/client-go/util/workqueue"
    40  
    41  	"k8s.io/kubernetes/pkg/controller"
    42  )
    43  
    44  // ClusterRoleAggregationController is a controller to combine cluster roles
    45  type ClusterRoleAggregationController struct {
    46  	clusterRoleClient  rbacclient.ClusterRolesGetter
    47  	clusterRoleLister  rbaclisters.ClusterRoleLister
    48  	clusterRolesSynced cache.InformerSynced
    49  
    50  	syncHandler func(ctx context.Context, key string) error
    51  	queue       workqueue.TypedRateLimitingInterface[string]
    52  }
    53  
    54  // NewClusterRoleAggregation creates a new controller
    55  func NewClusterRoleAggregation(clusterRoleInformer rbacinformers.ClusterRoleInformer, clusterRoleClient rbacclient.ClusterRolesGetter) *ClusterRoleAggregationController {
    56  	c := &ClusterRoleAggregationController{
    57  		clusterRoleClient:  clusterRoleClient,
    58  		clusterRoleLister:  clusterRoleInformer.Lister(),
    59  		clusterRolesSynced: clusterRoleInformer.Informer().HasSynced,
    60  
    61  		queue: workqueue.NewTypedRateLimitingQueueWithConfig(
    62  			workqueue.DefaultTypedControllerRateLimiter[string](),
    63  			workqueue.TypedRateLimitingQueueConfig[string]{
    64  				Name: "ClusterRoleAggregator",
    65  			},
    66  		),
    67  	}
    68  	c.syncHandler = c.syncClusterRole
    69  
    70  	clusterRoleInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
    71  		AddFunc: func(obj interface{}) {
    72  			c.enqueue()
    73  		},
    74  		UpdateFunc: func(old, cur interface{}) {
    75  			c.enqueue()
    76  		},
    77  		DeleteFunc: func(uncast interface{}) {
    78  			c.enqueue()
    79  		},
    80  	})
    81  	return c
    82  }
    83  
    84  func (c *ClusterRoleAggregationController) syncClusterRole(ctx context.Context, key string) error {
    85  	_, name, err := cache.SplitMetaNamespaceKey(key)
    86  	if err != nil {
    87  		return err
    88  	}
    89  	sharedClusterRole, err := c.clusterRoleLister.Get(name)
    90  	if errors.IsNotFound(err) {
    91  		return nil
    92  	}
    93  	if err != nil {
    94  		return err
    95  	}
    96  	if sharedClusterRole.AggregationRule == nil {
    97  		return nil
    98  	}
    99  
   100  	newPolicyRules := []rbacv1.PolicyRule{}
   101  	for i := range sharedClusterRole.AggregationRule.ClusterRoleSelectors {
   102  		selector := sharedClusterRole.AggregationRule.ClusterRoleSelectors[i]
   103  		runtimeLabelSelector, err := metav1.LabelSelectorAsSelector(&selector)
   104  		if err != nil {
   105  			return err
   106  		}
   107  		clusterRoles, err := c.clusterRoleLister.List(runtimeLabelSelector)
   108  		if err != nil {
   109  			return err
   110  		}
   111  		sort.Sort(byName(clusterRoles))
   112  
   113  		for i := range clusterRoles {
   114  			if clusterRoles[i].Name == sharedClusterRole.Name {
   115  				continue
   116  			}
   117  
   118  			for j := range clusterRoles[i].Rules {
   119  				currRule := clusterRoles[i].Rules[j]
   120  				if !ruleExists(newPolicyRules, currRule) {
   121  					newPolicyRules = append(newPolicyRules, currRule)
   122  				}
   123  			}
   124  		}
   125  	}
   126  
   127  	if equality.Semantic.DeepEqual(newPolicyRules, sharedClusterRole.Rules) {
   128  		return nil
   129  	}
   130  
   131  	err = c.applyClusterRoles(ctx, sharedClusterRole.Name, newPolicyRules)
   132  	if errors.IsUnsupportedMediaType(err) { // TODO: Remove this fallback at least one release after ServerSideApply GA
   133  		// When Server Side Apply is not enabled, fallback to Update. This is required when running
   134  		// 1.21 since api-server can be 1.20 during the upgrade/downgrade.
   135  		// Since Server Side Apply is enabled by default in Beta, this fallback only kicks in
   136  		// if the feature has been disabled using its feature flag.
   137  		err = c.updateClusterRoles(ctx, sharedClusterRole, newPolicyRules)
   138  	}
   139  	return err
   140  }
   141  
   142  func (c *ClusterRoleAggregationController) applyClusterRoles(ctx context.Context, name string, newPolicyRules []rbacv1.PolicyRule) error {
   143  	clusterRoleApply := rbacv1ac.ClusterRole(name).
   144  		WithRules(toApplyPolicyRules(newPolicyRules)...)
   145  
   146  	opts := metav1.ApplyOptions{FieldManager: "clusterrole-aggregation-controller", Force: true}
   147  	_, err := c.clusterRoleClient.ClusterRoles().Apply(ctx, clusterRoleApply, opts)
   148  	return err
   149  }
   150  
   151  func (c *ClusterRoleAggregationController) updateClusterRoles(ctx context.Context, sharedClusterRole *rbacv1.ClusterRole, newPolicyRules []rbacv1.PolicyRule) error {
   152  	clusterRole := sharedClusterRole.DeepCopy()
   153  	clusterRole.Rules = nil
   154  	for _, rule := range newPolicyRules {
   155  		clusterRole.Rules = append(clusterRole.Rules, *rule.DeepCopy())
   156  	}
   157  	_, err := c.clusterRoleClient.ClusterRoles().Update(ctx, clusterRole, metav1.UpdateOptions{})
   158  	return err
   159  }
   160  
   161  func toApplyPolicyRules(rules []rbacv1.PolicyRule) []*rbacv1ac.PolicyRuleApplyConfiguration {
   162  	var result []*rbacv1ac.PolicyRuleApplyConfiguration
   163  	for _, rule := range rules {
   164  		result = append(result, toApplyPolicyRule(rule))
   165  	}
   166  	return result
   167  }
   168  
   169  func toApplyPolicyRule(rule rbacv1.PolicyRule) *rbacv1ac.PolicyRuleApplyConfiguration {
   170  	result := rbacv1ac.PolicyRule()
   171  	result.Resources = rule.Resources
   172  	result.ResourceNames = rule.ResourceNames
   173  	result.APIGroups = rule.APIGroups
   174  	result.NonResourceURLs = rule.NonResourceURLs
   175  	result.Verbs = rule.Verbs
   176  	return result
   177  }
   178  
   179  func ruleExists(haystack []rbacv1.PolicyRule, needle rbacv1.PolicyRule) bool {
   180  	for _, curr := range haystack {
   181  		if equality.Semantic.DeepEqual(curr, needle) {
   182  			return true
   183  		}
   184  	}
   185  	return false
   186  }
   187  
   188  // Run starts the controller and blocks until stopCh is closed.
   189  func (c *ClusterRoleAggregationController) Run(ctx context.Context, workers int) {
   190  	defer utilruntime.HandleCrash()
   191  	defer c.queue.ShutDown()
   192  
   193  	logger := klog.FromContext(ctx)
   194  	logger.Info("Starting ClusterRoleAggregator controller")
   195  	defer logger.Info("Shutting down ClusterRoleAggregator controller")
   196  
   197  	if !cache.WaitForNamedCacheSync("ClusterRoleAggregator", ctx.Done(), c.clusterRolesSynced) {
   198  		return
   199  	}
   200  
   201  	for i := 0; i < workers; i++ {
   202  		go wait.UntilWithContext(ctx, c.runWorker, time.Second)
   203  	}
   204  
   205  	<-ctx.Done()
   206  }
   207  
   208  func (c *ClusterRoleAggregationController) runWorker(ctx context.Context) {
   209  	for c.processNextWorkItem(ctx) {
   210  	}
   211  }
   212  
   213  func (c *ClusterRoleAggregationController) processNextWorkItem(ctx context.Context) bool {
   214  	dsKey, quit := c.queue.Get()
   215  	if quit {
   216  		return false
   217  	}
   218  	defer c.queue.Done(dsKey)
   219  
   220  	err := c.syncHandler(ctx, dsKey)
   221  	if err == nil {
   222  		c.queue.Forget(dsKey)
   223  		return true
   224  	}
   225  
   226  	utilruntime.HandleError(fmt.Errorf("%v failed with : %v", dsKey, err))
   227  	c.queue.AddRateLimited(dsKey)
   228  
   229  	return true
   230  }
   231  
   232  func (c *ClusterRoleAggregationController) enqueue() {
   233  	// this is unusual, but since the set of all clusterroles is small and we don't know the dependency
   234  	// graph, just queue up every thing each time.  This allows errors to be selectively retried if there
   235  	// is a problem updating a single role
   236  	allClusterRoles, err := c.clusterRoleLister.List(labels.Everything())
   237  	if err != nil {
   238  		utilruntime.HandleError(fmt.Errorf("Couldn't list all objects %v", err))
   239  		return
   240  	}
   241  	for _, clusterRole := range allClusterRoles {
   242  		// only queue ones that we may need to aggregate
   243  		if clusterRole.AggregationRule == nil {
   244  			continue
   245  		}
   246  		key, err := controller.KeyFunc(clusterRole)
   247  		if err != nil {
   248  			utilruntime.HandleError(fmt.Errorf("Couldn't get key for object %#v: %v", clusterRole, err))
   249  			return
   250  		}
   251  		c.queue.Add(key)
   252  	}
   253  }
   254  
   255  type byName []*rbacv1.ClusterRole
   256  
   257  func (a byName) Len() int           { return len(a) }
   258  func (a byName) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
   259  func (a byName) Less(i, j int) bool { return a[i].Name < a[j].Name }