k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/pkg/apis/rbac/helpers.go (about) 1 /* 2 Copyright 2016 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 rbac 18 19 import ( 20 "fmt" 21 "strings" 22 23 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 24 "k8s.io/apimachinery/pkg/util/sets" 25 ) 26 27 // ResourceMatches returns the result of the rule.Resources matching. 28 func ResourceMatches(rule *PolicyRule, combinedRequestedResource, requestedSubresource string) bool { 29 for _, ruleResource := range rule.Resources { 30 // if everything is allowed, we match 31 if ruleResource == ResourceAll { 32 return true 33 } 34 // if we have an exact match, we match 35 if ruleResource == combinedRequestedResource { 36 return true 37 } 38 39 // We can also match a */subresource. 40 // if there isn't a subresource, then continue 41 if len(requestedSubresource) == 0 { 42 continue 43 } 44 // if the rule isn't in the format */subresource, then we don't match, continue 45 if len(ruleResource) == len(requestedSubresource)+2 && 46 strings.HasPrefix(ruleResource, "*/") && 47 strings.HasSuffix(ruleResource, requestedSubresource) { 48 return true 49 50 } 51 } 52 53 return false 54 } 55 56 // SubjectsStrings returns users, groups, serviceaccounts, unknown for display purposes. 57 func SubjectsStrings(subjects []Subject) ([]string, []string, []string, []string) { 58 users := []string{} 59 groups := []string{} 60 sas := []string{} 61 others := []string{} 62 63 for _, subject := range subjects { 64 switch subject.Kind { 65 case ServiceAccountKind: 66 sas = append(sas, fmt.Sprintf("%s/%s", subject.Namespace, subject.Name)) 67 68 case UserKind: 69 users = append(users, subject.Name) 70 71 case GroupKind: 72 groups = append(groups, subject.Name) 73 74 default: 75 others = append(others, fmt.Sprintf("%s/%s/%s", subject.Kind, subject.Namespace, subject.Name)) 76 } 77 } 78 79 return users, groups, sas, others 80 } 81 82 func (r PolicyRule) String() string { 83 return "PolicyRule" + r.CompactString() 84 } 85 86 // CompactString exposes a compact string representation for use in escalation error messages 87 func (r PolicyRule) CompactString() string { 88 formatStringParts := []string{} 89 formatArgs := []interface{}{} 90 if len(r.APIGroups) > 0 { 91 formatStringParts = append(formatStringParts, "APIGroups:%q") 92 formatArgs = append(formatArgs, r.APIGroups) 93 } 94 if len(r.Resources) > 0 { 95 formatStringParts = append(formatStringParts, "Resources:%q") 96 formatArgs = append(formatArgs, r.Resources) 97 } 98 if len(r.NonResourceURLs) > 0 { 99 formatStringParts = append(formatStringParts, "NonResourceURLs:%q") 100 formatArgs = append(formatArgs, r.NonResourceURLs) 101 } 102 if len(r.ResourceNames) > 0 { 103 formatStringParts = append(formatStringParts, "ResourceNames:%q") 104 formatArgs = append(formatArgs, r.ResourceNames) 105 } 106 if len(r.Verbs) > 0 { 107 formatStringParts = append(formatStringParts, "Verbs:%q") 108 formatArgs = append(formatArgs, r.Verbs) 109 } 110 formatString := "{" + strings.Join(formatStringParts, ", ") + "}" 111 return fmt.Sprintf(formatString, formatArgs...) 112 } 113 114 // PolicyRuleBuilder let's us attach methods. A no-no for API types. 115 // We use it to construct rules in code. It's more compact than trying to write them 116 // out in a literal and allows us to perform some basic checking during construction 117 // +k8s:deepcopy-gen=false 118 type PolicyRuleBuilder struct { 119 PolicyRule PolicyRule 120 } 121 122 // NewRule returns new PolicyRule made by input verbs. 123 func NewRule(verbs ...string) *PolicyRuleBuilder { 124 return &PolicyRuleBuilder{ 125 PolicyRule: PolicyRule{Verbs: sets.NewString(verbs...).List()}, 126 } 127 } 128 129 // Groups combines the PolicyRule.APIGroups and input groups. 130 func (r *PolicyRuleBuilder) Groups(groups ...string) *PolicyRuleBuilder { 131 r.PolicyRule.APIGroups = combine(r.PolicyRule.APIGroups, groups) 132 return r 133 } 134 135 // Resources combines the PolicyRule.Rule and input resources. 136 func (r *PolicyRuleBuilder) Resources(resources ...string) *PolicyRuleBuilder { 137 r.PolicyRule.Resources = combine(r.PolicyRule.Resources, resources) 138 return r 139 } 140 141 // Names combines the PolicyRule.ResourceNames and input names. 142 func (r *PolicyRuleBuilder) Names(names ...string) *PolicyRuleBuilder { 143 r.PolicyRule.ResourceNames = combine(r.PolicyRule.ResourceNames, names) 144 return r 145 } 146 147 // URLs combines the PolicyRule.NonResourceURLs and input urls. 148 func (r *PolicyRuleBuilder) URLs(urls ...string) *PolicyRuleBuilder { 149 r.PolicyRule.NonResourceURLs = combine(r.PolicyRule.NonResourceURLs, urls) 150 return r 151 } 152 153 // RuleOrDie calls the binding method and panics if there is an error. 154 func (r *PolicyRuleBuilder) RuleOrDie() PolicyRule { 155 ret, err := r.Rule() 156 if err != nil { 157 panic(err) 158 } 159 return ret 160 } 161 162 func combine(s1, s2 []string) []string { 163 s := sets.NewString(s1...) 164 s.Insert(s2...) 165 return s.List() 166 } 167 168 // Rule returns PolicyRule and error. 169 func (r *PolicyRuleBuilder) Rule() (PolicyRule, error) { 170 if len(r.PolicyRule.Verbs) == 0 { 171 return PolicyRule{}, fmt.Errorf("verbs are required: %#v", r.PolicyRule) 172 } 173 174 switch { 175 case len(r.PolicyRule.NonResourceURLs) > 0: 176 if len(r.PolicyRule.APIGroups) != 0 || len(r.PolicyRule.Resources) != 0 || len(r.PolicyRule.ResourceNames) != 0 { 177 return PolicyRule{}, fmt.Errorf("non-resource rule may not have apiGroups, resources, or resourceNames: %#v", r.PolicyRule) 178 } 179 case len(r.PolicyRule.Resources) > 0: 180 // resource rule may not have nonResourceURLs 181 182 if len(r.PolicyRule.APIGroups) == 0 { 183 // this a common bug 184 return PolicyRule{}, fmt.Errorf("resource rule must have apiGroups: %#v", r.PolicyRule) 185 } 186 // if resource names are set, then the verb must not be list, watch, create, or deletecollection 187 // since verbs are largely opaque, we don't want to accidentally prevent things like "impersonate", so 188 // we will backlist common mistakes, not whitelist acceptable options. 189 if len(r.PolicyRule.ResourceNames) != 0 { 190 illegalVerbs := []string{} 191 for _, verb := range r.PolicyRule.Verbs { 192 switch verb { 193 case "list", "watch", "create", "deletecollection": 194 illegalVerbs = append(illegalVerbs, verb) 195 } 196 } 197 if len(illegalVerbs) > 0 { 198 return PolicyRule{}, fmt.Errorf("verbs %v do not have names available: %#v", illegalVerbs, r.PolicyRule) 199 } 200 } 201 202 default: 203 return PolicyRule{}, fmt.Errorf("a rule must have either nonResourceURLs or resources: %#v", r.PolicyRule) 204 } 205 206 return r.PolicyRule, nil 207 } 208 209 // ClusterRoleBindingBuilder let's us attach methods. A no-no for API types. 210 // We use it to construct bindings in code. It's more compact than trying to write them 211 // out in a literal. 212 // +k8s:deepcopy-gen=false 213 type ClusterRoleBindingBuilder struct { 214 ClusterRoleBinding ClusterRoleBinding 215 } 216 217 // NewClusterBinding creates a ClusterRoleBinding builder that can be used 218 // to define the subjects of a cluster role binding. At least one of 219 // the `Groups`, `Users` or `SAs` method must be called before 220 // calling the `Binding*` methods. 221 func NewClusterBinding(clusterRoleName string) *ClusterRoleBindingBuilder { 222 return &ClusterRoleBindingBuilder{ 223 ClusterRoleBinding: ClusterRoleBinding{ 224 ObjectMeta: metav1.ObjectMeta{Name: clusterRoleName}, 225 RoleRef: RoleRef{ 226 APIGroup: GroupName, 227 Kind: "ClusterRole", 228 Name: clusterRoleName, 229 }, 230 }, 231 } 232 } 233 234 // Groups adds the specified groups as the subjects of the ClusterRoleBinding. 235 func (r *ClusterRoleBindingBuilder) Groups(groups ...string) *ClusterRoleBindingBuilder { 236 for _, group := range groups { 237 r.ClusterRoleBinding.Subjects = append(r.ClusterRoleBinding.Subjects, Subject{Kind: GroupKind, APIGroup: GroupName, Name: group}) 238 } 239 return r 240 } 241 242 // Users adds the specified users as the subjects of the ClusterRoleBinding. 243 func (r *ClusterRoleBindingBuilder) Users(users ...string) *ClusterRoleBindingBuilder { 244 for _, user := range users { 245 r.ClusterRoleBinding.Subjects = append(r.ClusterRoleBinding.Subjects, Subject{Kind: UserKind, APIGroup: GroupName, Name: user}) 246 } 247 return r 248 } 249 250 // SAs adds the specified sas as the subjects of the ClusterRoleBinding. 251 func (r *ClusterRoleBindingBuilder) SAs(namespace string, serviceAccountNames ...string) *ClusterRoleBindingBuilder { 252 for _, saName := range serviceAccountNames { 253 r.ClusterRoleBinding.Subjects = append(r.ClusterRoleBinding.Subjects, Subject{Kind: ServiceAccountKind, Namespace: namespace, Name: saName}) 254 } 255 return r 256 } 257 258 // BindingOrDie calls the binding method and panics if there is an error. 259 func (r *ClusterRoleBindingBuilder) BindingOrDie() ClusterRoleBinding { 260 ret, err := r.Binding() 261 if err != nil { 262 panic(err) 263 } 264 return ret 265 } 266 267 // Binding builds and returns the ClusterRoleBinding API object from the builder 268 // object. 269 func (r *ClusterRoleBindingBuilder) Binding() (ClusterRoleBinding, error) { 270 if len(r.ClusterRoleBinding.Subjects) == 0 { 271 return ClusterRoleBinding{}, fmt.Errorf("subjects are required: %#v", r.ClusterRoleBinding) 272 } 273 274 return r.ClusterRoleBinding, nil 275 } 276 277 // RoleBindingBuilder let's us attach methods. It is similar to 278 // ClusterRoleBindingBuilder above. 279 // +k8s:deepcopy-gen=false 280 type RoleBindingBuilder struct { 281 RoleBinding RoleBinding 282 } 283 284 // NewRoleBinding creates a RoleBinding builder that can be used 285 // to define the subjects of a role binding. At least one of 286 // the `Groups`, `Users` or `SAs` method must be called before 287 // calling the `Binding*` methods. 288 func NewRoleBinding(roleName, namespace string) *RoleBindingBuilder { 289 return &RoleBindingBuilder{ 290 RoleBinding: RoleBinding{ 291 ObjectMeta: metav1.ObjectMeta{ 292 Name: roleName, 293 Namespace: namespace, 294 }, 295 RoleRef: RoleRef{ 296 APIGroup: GroupName, 297 Kind: "Role", 298 Name: roleName, 299 }, 300 }, 301 } 302 } 303 304 // NewRoleBindingForClusterRole creates a RoleBinding builder that can be used 305 // to define the subjects of a cluster role binding. At least one of 306 // the `Groups`, `Users` or `SAs` method must be called before 307 // calling the `Binding*` methods. 308 func NewRoleBindingForClusterRole(roleName, namespace string) *RoleBindingBuilder { 309 return &RoleBindingBuilder{ 310 RoleBinding: RoleBinding{ 311 ObjectMeta: metav1.ObjectMeta{ 312 Name: roleName, 313 Namespace: namespace, 314 }, 315 RoleRef: RoleRef{ 316 APIGroup: GroupName, 317 Kind: "ClusterRole", 318 Name: roleName, 319 }, 320 }, 321 } 322 } 323 324 // Groups adds the specified groups as the subjects of the RoleBinding. 325 func (r *RoleBindingBuilder) Groups(groups ...string) *RoleBindingBuilder { 326 for _, group := range groups { 327 r.RoleBinding.Subjects = append(r.RoleBinding.Subjects, Subject{Kind: GroupKind, APIGroup: GroupName, Name: group}) 328 } 329 return r 330 } 331 332 // Users adds the specified users as the subjects of the RoleBinding. 333 func (r *RoleBindingBuilder) Users(users ...string) *RoleBindingBuilder { 334 for _, user := range users { 335 r.RoleBinding.Subjects = append(r.RoleBinding.Subjects, Subject{Kind: UserKind, APIGroup: GroupName, Name: user}) 336 } 337 return r 338 } 339 340 // SAs adds the specified service accounts as the subjects of the 341 // RoleBinding. 342 func (r *RoleBindingBuilder) SAs(namespace string, serviceAccountNames ...string) *RoleBindingBuilder { 343 for _, saName := range serviceAccountNames { 344 r.RoleBinding.Subjects = append(r.RoleBinding.Subjects, Subject{Kind: ServiceAccountKind, Namespace: namespace, Name: saName}) 345 } 346 return r 347 } 348 349 // BindingOrDie calls the binding method and panics if there is an error. 350 func (r *RoleBindingBuilder) BindingOrDie() RoleBinding { 351 ret, err := r.Binding() 352 if err != nil { 353 panic(err) 354 } 355 return ret 356 } 357 358 // Binding builds and returns the RoleBinding API object from the builder 359 // object. 360 func (r *RoleBindingBuilder) Binding() (RoleBinding, error) { 361 if len(r.RoleBinding.Subjects) == 0 { 362 return RoleBinding{}, fmt.Errorf("subjects are required: %#v", r.RoleBinding) 363 } 364 365 return r.RoleBinding, nil 366 } 367 368 // SortableRuleSlice is the slice of PolicyRule. 369 type SortableRuleSlice []PolicyRule 370 371 func (s SortableRuleSlice) Len() int { return len(s) } 372 func (s SortableRuleSlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] } 373 func (s SortableRuleSlice) Less(i, j int) bool { 374 return strings.Compare(s[i].String(), s[j].String()) < 0 375 }