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 }