validation.go 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250
  1. /*
  2. Copyright 2016 The Kubernetes Authors.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package validation
  14. import (
  15. "k8s.io/apimachinery/pkg/api/validation/path"
  16. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  17. unversionedvalidation "k8s.io/apimachinery/pkg/apis/meta/v1/validation"
  18. "k8s.io/apimachinery/pkg/util/validation/field"
  19. "k8s.io/kubernetes/pkg/apis/core/validation"
  20. "k8s.io/kubernetes/pkg/apis/rbac"
  21. )
  22. // ValidateRBACName is exported to allow types outside of the RBAC API group to reuse this validation logic
  23. // Minimal validation of names for roles and bindings. Identical to the validation for Openshift. See:
  24. // * https://github.com/kubernetes/kubernetes/blob/60db50/pkg/api/validation/name.go
  25. // * https://github.com/openshift/origin/blob/388478/pkg/api/helpers.go
  26. func ValidateRBACName(name string, prefix bool) []string {
  27. return path.IsValidPathSegmentName(name)
  28. }
  29. func ValidateRole(role *rbac.Role) field.ErrorList {
  30. allErrs := field.ErrorList{}
  31. allErrs = append(allErrs, validation.ValidateObjectMeta(&role.ObjectMeta, true, ValidateRBACName, field.NewPath("metadata"))...)
  32. for i, rule := range role.Rules {
  33. if err := ValidatePolicyRule(rule, true, field.NewPath("rules").Index(i)); err != nil {
  34. allErrs = append(allErrs, err...)
  35. }
  36. }
  37. if len(allErrs) != 0 {
  38. return allErrs
  39. }
  40. return nil
  41. }
  42. func ValidateRoleUpdate(role *rbac.Role, oldRole *rbac.Role) field.ErrorList {
  43. allErrs := ValidateRole(role)
  44. allErrs = append(allErrs, validation.ValidateObjectMetaUpdate(&role.ObjectMeta, &oldRole.ObjectMeta, field.NewPath("metadata"))...)
  45. return allErrs
  46. }
  47. func ValidateClusterRole(role *rbac.ClusterRole) field.ErrorList {
  48. allErrs := field.ErrorList{}
  49. allErrs = append(allErrs, validation.ValidateObjectMeta(&role.ObjectMeta, false, ValidateRBACName, field.NewPath("metadata"))...)
  50. for i, rule := range role.Rules {
  51. if err := ValidatePolicyRule(rule, false, field.NewPath("rules").Index(i)); err != nil {
  52. allErrs = append(allErrs, err...)
  53. }
  54. }
  55. if role.AggregationRule != nil {
  56. if len(role.AggregationRule.ClusterRoleSelectors) == 0 {
  57. allErrs = append(allErrs, field.Required(field.NewPath("aggregationRule", "clusterRoleSelectors"), "at least one clusterRoleSelector required if aggregationRule is non-nil"))
  58. }
  59. for i, selector := range role.AggregationRule.ClusterRoleSelectors {
  60. fieldPath := field.NewPath("aggregationRule", "clusterRoleSelectors").Index(i)
  61. allErrs = append(allErrs, unversionedvalidation.ValidateLabelSelector(&selector, fieldPath)...)
  62. selector, err := metav1.LabelSelectorAsSelector(&selector)
  63. if err != nil {
  64. allErrs = append(allErrs, field.Invalid(fieldPath, selector, "invalid label selector."))
  65. }
  66. }
  67. }
  68. if len(allErrs) != 0 {
  69. return allErrs
  70. }
  71. return nil
  72. }
  73. func ValidateClusterRoleUpdate(role *rbac.ClusterRole, oldRole *rbac.ClusterRole) field.ErrorList {
  74. allErrs := ValidateClusterRole(role)
  75. allErrs = append(allErrs, validation.ValidateObjectMetaUpdate(&role.ObjectMeta, &oldRole.ObjectMeta, field.NewPath("metadata"))...)
  76. return allErrs
  77. }
  78. // ValidatePolicyRule is exported to allow types outside of the RBAC API group to embed a rbac.PolicyRule and reuse this validation logic
  79. func ValidatePolicyRule(rule rbac.PolicyRule, isNamespaced bool, fldPath *field.Path) field.ErrorList {
  80. allErrs := field.ErrorList{}
  81. if len(rule.Verbs) == 0 {
  82. allErrs = append(allErrs, field.Required(fldPath.Child("verbs"), "verbs must contain at least one value"))
  83. }
  84. if len(rule.NonResourceURLs) > 0 {
  85. if isNamespaced {
  86. allErrs = append(allErrs, field.Invalid(fldPath.Child("nonResourceURLs"), rule.NonResourceURLs, "namespaced rules cannot apply to non-resource URLs"))
  87. }
  88. if len(rule.APIGroups) > 0 || len(rule.Resources) > 0 || len(rule.ResourceNames) > 0 {
  89. allErrs = append(allErrs, field.Invalid(fldPath.Child("nonResourceURLs"), rule.NonResourceURLs, "rules cannot apply to both regular resources and non-resource URLs"))
  90. }
  91. return allErrs
  92. }
  93. if len(rule.APIGroups) == 0 {
  94. allErrs = append(allErrs, field.Required(fldPath.Child("apiGroups"), "resource rules must supply at least one api group"))
  95. }
  96. if len(rule.Resources) == 0 {
  97. allErrs = append(allErrs, field.Required(fldPath.Child("resources"), "resource rules must supply at least one resource"))
  98. }
  99. return allErrs
  100. }
  101. func ValidateRoleBinding(roleBinding *rbac.RoleBinding) field.ErrorList {
  102. allErrs := field.ErrorList{}
  103. allErrs = append(allErrs, validation.ValidateObjectMeta(&roleBinding.ObjectMeta, true, ValidateRBACName, field.NewPath("metadata"))...)
  104. // TODO allow multiple API groups. For now, restrict to one, but I can envision other experimental roles in other groups taking
  105. // advantage of the binding infrastructure
  106. if roleBinding.RoleRef.APIGroup != rbac.GroupName {
  107. allErrs = append(allErrs, field.NotSupported(field.NewPath("roleRef", "apiGroup"), roleBinding.RoleRef.APIGroup, []string{rbac.GroupName}))
  108. }
  109. switch roleBinding.RoleRef.Kind {
  110. case "Role", "ClusterRole":
  111. default:
  112. allErrs = append(allErrs, field.NotSupported(field.NewPath("roleRef", "kind"), roleBinding.RoleRef.Kind, []string{"Role", "ClusterRole"}))
  113. }
  114. if len(roleBinding.RoleRef.Name) == 0 {
  115. allErrs = append(allErrs, field.Required(field.NewPath("roleRef", "name"), ""))
  116. } else {
  117. for _, msg := range ValidateRBACName(roleBinding.RoleRef.Name, false) {
  118. allErrs = append(allErrs, field.Invalid(field.NewPath("roleRef", "name"), roleBinding.RoleRef.Name, msg))
  119. }
  120. }
  121. subjectsPath := field.NewPath("subjects")
  122. for i, subject := range roleBinding.Subjects {
  123. allErrs = append(allErrs, ValidateRoleBindingSubject(subject, true, subjectsPath.Index(i))...)
  124. }
  125. return allErrs
  126. }
  127. func ValidateRoleBindingUpdate(roleBinding *rbac.RoleBinding, oldRoleBinding *rbac.RoleBinding) field.ErrorList {
  128. allErrs := ValidateRoleBinding(roleBinding)
  129. allErrs = append(allErrs, validation.ValidateObjectMetaUpdate(&roleBinding.ObjectMeta, &oldRoleBinding.ObjectMeta, field.NewPath("metadata"))...)
  130. if oldRoleBinding.RoleRef != roleBinding.RoleRef {
  131. allErrs = append(allErrs, field.Invalid(field.NewPath("roleRef"), roleBinding.RoleRef, "cannot change roleRef"))
  132. }
  133. return allErrs
  134. }
  135. func ValidateClusterRoleBinding(roleBinding *rbac.ClusterRoleBinding) field.ErrorList {
  136. allErrs := field.ErrorList{}
  137. allErrs = append(allErrs, validation.ValidateObjectMeta(&roleBinding.ObjectMeta, false, ValidateRBACName, field.NewPath("metadata"))...)
  138. // TODO allow multiple API groups. For now, restrict to one, but I can envision other experimental roles in other groups taking
  139. // advantage of the binding infrastructure
  140. if roleBinding.RoleRef.APIGroup != rbac.GroupName {
  141. allErrs = append(allErrs, field.NotSupported(field.NewPath("roleRef", "apiGroup"), roleBinding.RoleRef.APIGroup, []string{rbac.GroupName}))
  142. }
  143. switch roleBinding.RoleRef.Kind {
  144. case "ClusterRole":
  145. default:
  146. allErrs = append(allErrs, field.NotSupported(field.NewPath("roleRef", "kind"), roleBinding.RoleRef.Kind, []string{"ClusterRole"}))
  147. }
  148. if len(roleBinding.RoleRef.Name) == 0 {
  149. allErrs = append(allErrs, field.Required(field.NewPath("roleRef", "name"), ""))
  150. } else {
  151. for _, msg := range ValidateRBACName(roleBinding.RoleRef.Name, false) {
  152. allErrs = append(allErrs, field.Invalid(field.NewPath("roleRef", "name"), roleBinding.RoleRef.Name, msg))
  153. }
  154. }
  155. subjectsPath := field.NewPath("subjects")
  156. for i, subject := range roleBinding.Subjects {
  157. allErrs = append(allErrs, ValidateRoleBindingSubject(subject, false, subjectsPath.Index(i))...)
  158. }
  159. return allErrs
  160. }
  161. func ValidateClusterRoleBindingUpdate(roleBinding *rbac.ClusterRoleBinding, oldRoleBinding *rbac.ClusterRoleBinding) field.ErrorList {
  162. allErrs := ValidateClusterRoleBinding(roleBinding)
  163. allErrs = append(allErrs, validation.ValidateObjectMetaUpdate(&roleBinding.ObjectMeta, &oldRoleBinding.ObjectMeta, field.NewPath("metadata"))...)
  164. if oldRoleBinding.RoleRef != roleBinding.RoleRef {
  165. allErrs = append(allErrs, field.Invalid(field.NewPath("roleRef"), roleBinding.RoleRef, "cannot change roleRef"))
  166. }
  167. return allErrs
  168. }
  169. // ValidateRoleBindingSubject is exported to allow types outside of the RBAC API group to embed a rbac.Subject and reuse this validation logic
  170. func ValidateRoleBindingSubject(subject rbac.Subject, isNamespaced bool, fldPath *field.Path) field.ErrorList {
  171. allErrs := field.ErrorList{}
  172. if len(subject.Name) == 0 {
  173. allErrs = append(allErrs, field.Required(fldPath.Child("name"), ""))
  174. }
  175. switch subject.Kind {
  176. case rbac.ServiceAccountKind:
  177. if len(subject.Name) > 0 {
  178. for _, msg := range validation.ValidateServiceAccountName(subject.Name, false) {
  179. allErrs = append(allErrs, field.Invalid(fldPath.Child("name"), subject.Name, msg))
  180. }
  181. }
  182. if len(subject.APIGroup) > 0 {
  183. allErrs = append(allErrs, field.NotSupported(fldPath.Child("apiGroup"), subject.APIGroup, []string{""}))
  184. }
  185. if !isNamespaced && len(subject.Namespace) == 0 {
  186. allErrs = append(allErrs, field.Required(fldPath.Child("namespace"), ""))
  187. }
  188. case rbac.UserKind:
  189. // TODO(ericchiang): What other restrictions on user name are there?
  190. if subject.APIGroup != rbac.GroupName {
  191. allErrs = append(allErrs, field.NotSupported(fldPath.Child("apiGroup"), subject.APIGroup, []string{rbac.GroupName}))
  192. }
  193. case rbac.GroupKind:
  194. // TODO(ericchiang): What other restrictions on group name are there?
  195. if subject.APIGroup != rbac.GroupName {
  196. allErrs = append(allErrs, field.NotSupported(fldPath.Child("apiGroup"), subject.APIGroup, []string{rbac.GroupName}))
  197. }
  198. default:
  199. allErrs = append(allErrs, field.NotSupported(fldPath.Child("kind"), subject.Kind, []string{rbac.ServiceAccountKind, rbac.UserKind, rbac.GroupKind}))
  200. }
  201. return allErrs
  202. }