1/* 2Copyright 2016 The Kubernetes Authors. 3 4Licensed under the Apache License, Version 2.0 (the "License"); 5you may not use this file except in compliance with the License. 6You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10Unless required by applicable law or agreed to in writing, software 11distributed under the License is distributed on an "AS IS" BASIS, 12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13See the License for the specific language governing permissions and 14limitations under the License. 15*/ 16 17package validation 18 19import ( 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 32func ValidateRBACName(name string, prefix bool) []string { 33 return path.IsValidPathSegmentName(name) 34} 35 36func 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 51func 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 58func ValidateClusterRole(role *rbac.ClusterRole) field.ErrorList { 59 allErrs := field.ErrorList{} 60 allErrs = append(allErrs, validation.ValidateObjectMeta(&role.ObjectMeta, false, ValidateRBACName, field.NewPath("metadata"))...) 61 62 for i, rule := range role.Rules { 63 if err := ValidatePolicyRule(rule, false, field.NewPath("rules").Index(i)); err != nil { 64 allErrs = append(allErrs, err...) 65 } 66 } 67 68 if role.AggregationRule != nil { 69 if len(role.AggregationRule.ClusterRoleSelectors) == 0 { 70 allErrs = append(allErrs, field.Required(field.NewPath("aggregationRule", "clusterRoleSelectors"), "at least one clusterRoleSelector required if aggregationRule is non-nil")) 71 } 72 for i, selector := range role.AggregationRule.ClusterRoleSelectors { 73 fieldPath := field.NewPath("aggregationRule", "clusterRoleSelectors").Index(i) 74 allErrs = append(allErrs, unversionedvalidation.ValidateLabelSelector(&selector, fieldPath)...) 75 76 selector, err := metav1.LabelSelectorAsSelector(&selector) 77 if err != nil { 78 allErrs = append(allErrs, field.Invalid(fieldPath, selector, "invalid label selector.")) 79 } 80 } 81 } 82 83 if len(allErrs) != 0 { 84 return allErrs 85 } 86 return nil 87} 88 89func ValidateClusterRoleUpdate(role *rbac.ClusterRole, oldRole *rbac.ClusterRole) field.ErrorList { 90 allErrs := ValidateClusterRole(role) 91 allErrs = append(allErrs, validation.ValidateObjectMetaUpdate(&role.ObjectMeta, &oldRole.ObjectMeta, field.NewPath("metadata"))...) 92 93 return allErrs 94} 95 96// ValidatePolicyRule is exported to allow types outside of the RBAC API group to embed a rbac.PolicyRule and reuse this validation logic 97func ValidatePolicyRule(rule rbac.PolicyRule, isNamespaced bool, fldPath *field.Path) field.ErrorList { 98 allErrs := field.ErrorList{} 99 if len(rule.Verbs) == 0 { 100 allErrs = append(allErrs, field.Required(fldPath.Child("verbs"), "verbs must contain at least one value")) 101 } 102 103 if len(rule.NonResourceURLs) > 0 { 104 if isNamespaced { 105 allErrs = append(allErrs, field.Invalid(fldPath.Child("nonResourceURLs"), rule.NonResourceURLs, "namespaced rules cannot apply to non-resource URLs")) 106 } 107 if len(rule.APIGroups) > 0 || len(rule.Resources) > 0 || len(rule.ResourceNames) > 0 { 108 allErrs = append(allErrs, field.Invalid(fldPath.Child("nonResourceURLs"), rule.NonResourceURLs, "rules cannot apply to both regular resources and non-resource URLs")) 109 } 110 return allErrs 111 } 112 113 if len(rule.APIGroups) == 0 { 114 allErrs = append(allErrs, field.Required(fldPath.Child("apiGroups"), "resource rules must supply at least one api group")) 115 } 116 if len(rule.Resources) == 0 { 117 allErrs = append(allErrs, field.Required(fldPath.Child("resources"), "resource rules must supply at least one resource")) 118 } 119 return allErrs 120} 121 122func ValidateRoleBinding(roleBinding *rbac.RoleBinding) field.ErrorList { 123 allErrs := field.ErrorList{} 124 allErrs = append(allErrs, validation.ValidateObjectMeta(&roleBinding.ObjectMeta, true, ValidateRBACName, field.NewPath("metadata"))...) 125 126 // TODO allow multiple API groups. For now, restrict to one, but I can envision other experimental roles in other groups taking 127 // advantage of the binding infrastructure 128 if roleBinding.RoleRef.APIGroup != rbac.GroupName { 129 allErrs = append(allErrs, field.NotSupported(field.NewPath("roleRef", "apiGroup"), roleBinding.RoleRef.APIGroup, []string{rbac.GroupName})) 130 } 131 132 switch roleBinding.RoleRef.Kind { 133 case "Role", "ClusterRole": 134 default: 135 allErrs = append(allErrs, field.NotSupported(field.NewPath("roleRef", "kind"), roleBinding.RoleRef.Kind, []string{"Role", "ClusterRole"})) 136 137 } 138 139 if len(roleBinding.RoleRef.Name) == 0 { 140 allErrs = append(allErrs, field.Required(field.NewPath("roleRef", "name"), "")) 141 } else { 142 for _, msg := range ValidateRBACName(roleBinding.RoleRef.Name, false) { 143 allErrs = append(allErrs, field.Invalid(field.NewPath("roleRef", "name"), roleBinding.RoleRef.Name, msg)) 144 } 145 } 146 147 subjectsPath := field.NewPath("subjects") 148 for i, subject := range roleBinding.Subjects { 149 allErrs = append(allErrs, ValidateRoleBindingSubject(subject, true, subjectsPath.Index(i))...) 150 } 151 152 return allErrs 153} 154 155func ValidateRoleBindingUpdate(roleBinding *rbac.RoleBinding, oldRoleBinding *rbac.RoleBinding) field.ErrorList { 156 allErrs := ValidateRoleBinding(roleBinding) 157 allErrs = append(allErrs, validation.ValidateObjectMetaUpdate(&roleBinding.ObjectMeta, &oldRoleBinding.ObjectMeta, field.NewPath("metadata"))...) 158 159 if oldRoleBinding.RoleRef != roleBinding.RoleRef { 160 allErrs = append(allErrs, field.Invalid(field.NewPath("roleRef"), roleBinding.RoleRef, "cannot change roleRef")) 161 } 162 163 return allErrs 164} 165 166func ValidateClusterRoleBinding(roleBinding *rbac.ClusterRoleBinding) field.ErrorList { 167 allErrs := field.ErrorList{} 168 allErrs = append(allErrs, validation.ValidateObjectMeta(&roleBinding.ObjectMeta, false, ValidateRBACName, field.NewPath("metadata"))...) 169 170 // TODO allow multiple API groups. For now, restrict to one, but I can envision other experimental roles in other groups taking 171 // advantage of the binding infrastructure 172 if roleBinding.RoleRef.APIGroup != rbac.GroupName { 173 allErrs = append(allErrs, field.NotSupported(field.NewPath("roleRef", "apiGroup"), roleBinding.RoleRef.APIGroup, []string{rbac.GroupName})) 174 } 175 176 switch roleBinding.RoleRef.Kind { 177 case "ClusterRole": 178 default: 179 allErrs = append(allErrs, field.NotSupported(field.NewPath("roleRef", "kind"), roleBinding.RoleRef.Kind, []string{"ClusterRole"})) 180 181 } 182 183 if len(roleBinding.RoleRef.Name) == 0 { 184 allErrs = append(allErrs, field.Required(field.NewPath("roleRef", "name"), "")) 185 } else { 186 for _, msg := range ValidateRBACName(roleBinding.RoleRef.Name, false) { 187 allErrs = append(allErrs, field.Invalid(field.NewPath("roleRef", "name"), roleBinding.RoleRef.Name, msg)) 188 } 189 } 190 191 subjectsPath := field.NewPath("subjects") 192 for i, subject := range roleBinding.Subjects { 193 allErrs = append(allErrs, ValidateRoleBindingSubject(subject, false, subjectsPath.Index(i))...) 194 } 195 196 return allErrs 197} 198 199func ValidateClusterRoleBindingUpdate(roleBinding *rbac.ClusterRoleBinding, oldRoleBinding *rbac.ClusterRoleBinding) field.ErrorList { 200 allErrs := ValidateClusterRoleBinding(roleBinding) 201 allErrs = append(allErrs, validation.ValidateObjectMetaUpdate(&roleBinding.ObjectMeta, &oldRoleBinding.ObjectMeta, field.NewPath("metadata"))...) 202 203 if oldRoleBinding.RoleRef != roleBinding.RoleRef { 204 allErrs = append(allErrs, field.Invalid(field.NewPath("roleRef"), roleBinding.RoleRef, "cannot change roleRef")) 205 } 206 207 return allErrs 208} 209 210// ValidateRoleBindingSubject is exported to allow types outside of the RBAC API group to embed a rbac.Subject and reuse this validation logic 211func ValidateRoleBindingSubject(subject rbac.Subject, isNamespaced bool, fldPath *field.Path) field.ErrorList { 212 allErrs := field.ErrorList{} 213 214 if len(subject.Name) == 0 { 215 allErrs = append(allErrs, field.Required(fldPath.Child("name"), "")) 216 } 217 218 switch subject.Kind { 219 case rbac.ServiceAccountKind: 220 if len(subject.Name) > 0 { 221 for _, msg := range validation.ValidateServiceAccountName(subject.Name, false) { 222 allErrs = append(allErrs, field.Invalid(fldPath.Child("name"), subject.Name, msg)) 223 } 224 } 225 if len(subject.APIGroup) > 0 { 226 allErrs = append(allErrs, field.NotSupported(fldPath.Child("apiGroup"), subject.APIGroup, []string{""})) 227 } 228 if !isNamespaced && len(subject.Namespace) == 0 { 229 allErrs = append(allErrs, field.Required(fldPath.Child("namespace"), "")) 230 } 231 232 case rbac.UserKind: 233 // TODO(ericchiang): What other restrictions on user name are there? 234 if subject.APIGroup != rbac.GroupName { 235 allErrs = append(allErrs, field.NotSupported(fldPath.Child("apiGroup"), subject.APIGroup, []string{rbac.GroupName})) 236 } 237 238 case rbac.GroupKind: 239 // TODO(ericchiang): What other restrictions on group 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 default: 245 allErrs = append(allErrs, field.NotSupported(fldPath.Child("kind"), subject.Kind, []string{rbac.ServiceAccountKind, rbac.UserKind, rbac.GroupKind})) 246 } 247 248 return allErrs 249} 250