k8s.io/kubernetes@v1.29.3/pkg/apis/rbac/validation/validation.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 validation 18 19 import ( 20 "k8s.io/apimachinery/pkg/api/validation/path" 21 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 22 unversionedvalidation "k8s.io/apimachinery/pkg/apis/meta/v1/validation" 23 "k8s.io/apimachinery/pkg/util/validation/field" 24 "k8s.io/kubernetes/pkg/apis/core/validation" 25 "k8s.io/kubernetes/pkg/apis/rbac" 26 ) 27 28 // ValidateRBACName is exported to allow types outside of the RBAC API group to reuse this validation logic 29 // Minimal validation of names for roles and bindings. Identical to the validation for Openshift. See: 30 // * https://github.com/kubernetes/kubernetes/blob/60db507b279ce45bd16ea3db49bf181f2aeb3c3d/pkg/api/validation/name.go 31 // * https://github.com/openshift/origin/blob/388478c40e751c4295dcb9a44dd69e5ac65d0e3b/pkg/api/helpers.go 32 func ValidateRBACName(name string, prefix bool) []string { 33 return path.IsValidPathSegmentName(name) 34 } 35 36 func ValidateRole(role *rbac.Role) field.ErrorList { 37 allErrs := field.ErrorList{} 38 allErrs = append(allErrs, validation.ValidateObjectMeta(&role.ObjectMeta, true, ValidateRBACName, field.NewPath("metadata"))...) 39 40 for i, rule := range role.Rules { 41 if err := ValidatePolicyRule(rule, true, field.NewPath("rules").Index(i)); err != nil { 42 allErrs = append(allErrs, err...) 43 } 44 } 45 if len(allErrs) != 0 { 46 return allErrs 47 } 48 return nil 49 } 50 51 func ValidateRoleUpdate(role *rbac.Role, oldRole *rbac.Role) field.ErrorList { 52 allErrs := ValidateRole(role) 53 allErrs = append(allErrs, validation.ValidateObjectMetaUpdate(&role.ObjectMeta, &oldRole.ObjectMeta, field.NewPath("metadata"))...) 54 55 return allErrs 56 } 57 58 type ClusterRoleValidationOptions struct { 59 AllowInvalidLabelValueInSelector bool 60 } 61 62 func ValidateClusterRole(role *rbac.ClusterRole, opts ClusterRoleValidationOptions) field.ErrorList { 63 allErrs := field.ErrorList{} 64 allErrs = append(allErrs, validation.ValidateObjectMeta(&role.ObjectMeta, false, ValidateRBACName, field.NewPath("metadata"))...) 65 66 for i, rule := range role.Rules { 67 if err := ValidatePolicyRule(rule, false, field.NewPath("rules").Index(i)); err != nil { 68 allErrs = append(allErrs, err...) 69 } 70 } 71 72 labelSelectorValidationOptions := unversionedvalidation.LabelSelectorValidationOptions{AllowInvalidLabelValueInSelector: opts.AllowInvalidLabelValueInSelector} 73 74 if role.AggregationRule != nil { 75 if len(role.AggregationRule.ClusterRoleSelectors) == 0 { 76 allErrs = append(allErrs, field.Required(field.NewPath("aggregationRule", "clusterRoleSelectors"), "at least one clusterRoleSelector required if aggregationRule is non-nil")) 77 } 78 for i, selector := range role.AggregationRule.ClusterRoleSelectors { 79 fieldPath := field.NewPath("aggregationRule", "clusterRoleSelectors").Index(i) 80 allErrs = append(allErrs, unversionedvalidation.ValidateLabelSelector(&selector, labelSelectorValidationOptions, fieldPath)...) 81 82 selector, err := metav1.LabelSelectorAsSelector(&selector) 83 if err != nil { 84 allErrs = append(allErrs, field.Invalid(fieldPath, selector, "invalid label selector.")) 85 } 86 } 87 } 88 89 if len(allErrs) != 0 { 90 return allErrs 91 } 92 return nil 93 } 94 95 func ValidateClusterRoleUpdate(role *rbac.ClusterRole, oldRole *rbac.ClusterRole, opts ClusterRoleValidationOptions) field.ErrorList { 96 allErrs := ValidateClusterRole(role, opts) 97 allErrs = append(allErrs, validation.ValidateObjectMetaUpdate(&role.ObjectMeta, &oldRole.ObjectMeta, field.NewPath("metadata"))...) 98 99 return allErrs 100 } 101 102 // ValidatePolicyRule is exported to allow types outside of the RBAC API group to embed a rbac.PolicyRule and reuse this validation logic 103 func ValidatePolicyRule(rule rbac.PolicyRule, isNamespaced bool, fldPath *field.Path) field.ErrorList { 104 allErrs := field.ErrorList{} 105 if len(rule.Verbs) == 0 { 106 allErrs = append(allErrs, field.Required(fldPath.Child("verbs"), "verbs must contain at least one value")) 107 } 108 109 if len(rule.NonResourceURLs) > 0 { 110 if isNamespaced { 111 allErrs = append(allErrs, field.Invalid(fldPath.Child("nonResourceURLs"), rule.NonResourceURLs, "namespaced rules cannot apply to non-resource URLs")) 112 } 113 if len(rule.APIGroups) > 0 || len(rule.Resources) > 0 || len(rule.ResourceNames) > 0 { 114 allErrs = append(allErrs, field.Invalid(fldPath.Child("nonResourceURLs"), rule.NonResourceURLs, "rules cannot apply to both regular resources and non-resource URLs")) 115 } 116 return allErrs 117 } 118 119 if len(rule.APIGroups) == 0 { 120 allErrs = append(allErrs, field.Required(fldPath.Child("apiGroups"), "resource rules must supply at least one api group")) 121 } 122 if len(rule.Resources) == 0 { 123 allErrs = append(allErrs, field.Required(fldPath.Child("resources"), "resource rules must supply at least one resource")) 124 } 125 return allErrs 126 } 127 128 func ValidateRoleBinding(roleBinding *rbac.RoleBinding) field.ErrorList { 129 allErrs := field.ErrorList{} 130 allErrs = append(allErrs, validation.ValidateObjectMeta(&roleBinding.ObjectMeta, true, ValidateRBACName, field.NewPath("metadata"))...) 131 132 // TODO allow multiple API groups. For now, restrict to one, but I can envision other experimental roles in other groups taking 133 // advantage of the binding infrastructure 134 if roleBinding.RoleRef.APIGroup != rbac.GroupName { 135 allErrs = append(allErrs, field.NotSupported(field.NewPath("roleRef", "apiGroup"), roleBinding.RoleRef.APIGroup, []string{rbac.GroupName})) 136 } 137 138 switch roleBinding.RoleRef.Kind { 139 case "Role", "ClusterRole": 140 default: 141 allErrs = append(allErrs, field.NotSupported(field.NewPath("roleRef", "kind"), roleBinding.RoleRef.Kind, []string{"Role", "ClusterRole"})) 142 143 } 144 145 if len(roleBinding.RoleRef.Name) == 0 { 146 allErrs = append(allErrs, field.Required(field.NewPath("roleRef", "name"), "")) 147 } else { 148 for _, msg := range ValidateRBACName(roleBinding.RoleRef.Name, false) { 149 allErrs = append(allErrs, field.Invalid(field.NewPath("roleRef", "name"), roleBinding.RoleRef.Name, msg)) 150 } 151 } 152 153 subjectsPath := field.NewPath("subjects") 154 for i, subject := range roleBinding.Subjects { 155 allErrs = append(allErrs, ValidateRoleBindingSubject(subject, true, subjectsPath.Index(i))...) 156 } 157 158 return allErrs 159 } 160 161 func ValidateRoleBindingUpdate(roleBinding *rbac.RoleBinding, oldRoleBinding *rbac.RoleBinding) field.ErrorList { 162 allErrs := ValidateRoleBinding(roleBinding) 163 allErrs = append(allErrs, validation.ValidateObjectMetaUpdate(&roleBinding.ObjectMeta, &oldRoleBinding.ObjectMeta, field.NewPath("metadata"))...) 164 165 if oldRoleBinding.RoleRef != roleBinding.RoleRef { 166 allErrs = append(allErrs, field.Invalid(field.NewPath("roleRef"), roleBinding.RoleRef, "cannot change roleRef")) 167 } 168 169 return allErrs 170 } 171 172 func ValidateClusterRoleBinding(roleBinding *rbac.ClusterRoleBinding) field.ErrorList { 173 allErrs := field.ErrorList{} 174 allErrs = append(allErrs, validation.ValidateObjectMeta(&roleBinding.ObjectMeta, false, ValidateRBACName, field.NewPath("metadata"))...) 175 176 // TODO allow multiple API groups. For now, restrict to one, but I can envision other experimental roles in other groups taking 177 // advantage of the binding infrastructure 178 if roleBinding.RoleRef.APIGroup != rbac.GroupName { 179 allErrs = append(allErrs, field.NotSupported(field.NewPath("roleRef", "apiGroup"), roleBinding.RoleRef.APIGroup, []string{rbac.GroupName})) 180 } 181 182 switch roleBinding.RoleRef.Kind { 183 case "ClusterRole": 184 default: 185 allErrs = append(allErrs, field.NotSupported(field.NewPath("roleRef", "kind"), roleBinding.RoleRef.Kind, []string{"ClusterRole"})) 186 187 } 188 189 if len(roleBinding.RoleRef.Name) == 0 { 190 allErrs = append(allErrs, field.Required(field.NewPath("roleRef", "name"), "")) 191 } else { 192 for _, msg := range ValidateRBACName(roleBinding.RoleRef.Name, false) { 193 allErrs = append(allErrs, field.Invalid(field.NewPath("roleRef", "name"), roleBinding.RoleRef.Name, msg)) 194 } 195 } 196 197 subjectsPath := field.NewPath("subjects") 198 for i, subject := range roleBinding.Subjects { 199 allErrs = append(allErrs, ValidateRoleBindingSubject(subject, false, subjectsPath.Index(i))...) 200 } 201 202 return allErrs 203 } 204 205 func ValidateClusterRoleBindingUpdate(roleBinding *rbac.ClusterRoleBinding, oldRoleBinding *rbac.ClusterRoleBinding) field.ErrorList { 206 allErrs := ValidateClusterRoleBinding(roleBinding) 207 allErrs = append(allErrs, validation.ValidateObjectMetaUpdate(&roleBinding.ObjectMeta, &oldRoleBinding.ObjectMeta, field.NewPath("metadata"))...) 208 209 if oldRoleBinding.RoleRef != roleBinding.RoleRef { 210 allErrs = append(allErrs, field.Invalid(field.NewPath("roleRef"), roleBinding.RoleRef, "cannot change roleRef")) 211 } 212 213 return allErrs 214 } 215 216 // ValidateRoleBindingSubject is exported to allow types outside of the RBAC API group to embed a rbac.Subject and reuse this validation logic 217 func ValidateRoleBindingSubject(subject rbac.Subject, isNamespaced bool, fldPath *field.Path) field.ErrorList { 218 allErrs := field.ErrorList{} 219 220 if len(subject.Name) == 0 { 221 allErrs = append(allErrs, field.Required(fldPath.Child("name"), "")) 222 } 223 224 switch subject.Kind { 225 case rbac.ServiceAccountKind: 226 if len(subject.Name) > 0 { 227 for _, msg := range validation.ValidateServiceAccountName(subject.Name, false) { 228 allErrs = append(allErrs, field.Invalid(fldPath.Child("name"), subject.Name, msg)) 229 } 230 } 231 if len(subject.APIGroup) > 0 { 232 allErrs = append(allErrs, field.NotSupported(fldPath.Child("apiGroup"), subject.APIGroup, []string{""})) 233 } 234 if !isNamespaced && len(subject.Namespace) == 0 { 235 allErrs = append(allErrs, field.Required(fldPath.Child("namespace"), "")) 236 } 237 238 case rbac.UserKind: 239 // TODO(ericchiang): What other restrictions on user name are there? 240 if subject.APIGroup != rbac.GroupName { 241 allErrs = append(allErrs, field.NotSupported(fldPath.Child("apiGroup"), subject.APIGroup, []string{rbac.GroupName})) 242 } 243 244 case rbac.GroupKind: 245 // TODO(ericchiang): What other restrictions on group name are there? 246 if subject.APIGroup != rbac.GroupName { 247 allErrs = append(allErrs, field.NotSupported(fldPath.Child("apiGroup"), subject.APIGroup, []string{rbac.GroupName})) 248 } 249 250 default: 251 allErrs = append(allErrs, field.NotSupported(fldPath.Child("kind"), subject.Kind, []string{rbac.ServiceAccountKind, rbac.UserKind, rbac.GroupKind})) 252 } 253 254 return allErrs 255 }