validation.go 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524
  1. /*
  2. Copyright 2017 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. "fmt"
  16. "strings"
  17. genericvalidation "k8s.io/apimachinery/pkg/api/validation"
  18. metav1validation "k8s.io/apimachinery/pkg/apis/meta/v1/validation"
  19. "k8s.io/apimachinery/pkg/runtime/schema"
  20. "k8s.io/apimachinery/pkg/util/sets"
  21. utilvalidation "k8s.io/apimachinery/pkg/util/validation"
  22. "k8s.io/apimachinery/pkg/util/validation/field"
  23. "k8s.io/apiserver/pkg/util/webhook"
  24. "k8s.io/kubernetes/pkg/apis/admissionregistration"
  25. admissionregistrationv1 "k8s.io/kubernetes/pkg/apis/admissionregistration/v1"
  26. admissionregistrationv1beta1 "k8s.io/kubernetes/pkg/apis/admissionregistration/v1beta1"
  27. )
  28. func hasWildcard(slice []string) bool {
  29. for _, s := range slice {
  30. if s == "*" {
  31. return true
  32. }
  33. }
  34. return false
  35. }
  36. func validateResources(resources []string, fldPath *field.Path) field.ErrorList {
  37. var allErrors field.ErrorList
  38. if len(resources) == 0 {
  39. allErrors = append(allErrors, field.Required(fldPath, ""))
  40. }
  41. // */x
  42. resourcesWithWildcardSubresoures := sets.String{}
  43. // x/*
  44. subResourcesWithWildcardResource := sets.String{}
  45. // */*
  46. hasDoubleWildcard := false
  47. // *
  48. hasSingleWildcard := false
  49. // x
  50. hasResourceWithoutSubresource := false
  51. for i, resSub := range resources {
  52. if resSub == "" {
  53. allErrors = append(allErrors, field.Required(fldPath.Index(i), ""))
  54. continue
  55. }
  56. if resSub == "*/*" {
  57. hasDoubleWildcard = true
  58. }
  59. if resSub == "*" {
  60. hasSingleWildcard = true
  61. }
  62. parts := strings.SplitN(resSub, "/", 2)
  63. if len(parts) == 1 {
  64. hasResourceWithoutSubresource = resSub != "*"
  65. continue
  66. }
  67. res, sub := parts[0], parts[1]
  68. if _, ok := resourcesWithWildcardSubresoures[res]; ok {
  69. allErrors = append(allErrors, field.Invalid(fldPath.Index(i), resSub, fmt.Sprintf("if '%s/*' is present, must not specify %s", res, resSub)))
  70. }
  71. if _, ok := subResourcesWithWildcardResource[sub]; ok {
  72. allErrors = append(allErrors, field.Invalid(fldPath.Index(i), resSub, fmt.Sprintf("if '*/%s' is present, must not specify %s", sub, resSub)))
  73. }
  74. if sub == "*" {
  75. resourcesWithWildcardSubresoures[res] = struct{}{}
  76. }
  77. if res == "*" {
  78. subResourcesWithWildcardResource[sub] = struct{}{}
  79. }
  80. }
  81. if len(resources) > 1 && hasDoubleWildcard {
  82. allErrors = append(allErrors, field.Invalid(fldPath, resources, "if '*/*' is present, must not specify other resources"))
  83. }
  84. if hasSingleWildcard && hasResourceWithoutSubresource {
  85. allErrors = append(allErrors, field.Invalid(fldPath, resources, "if '*' is present, must not specify other resources without subresources"))
  86. }
  87. return allErrors
  88. }
  89. func validateResourcesNoSubResources(resources []string, fldPath *field.Path) field.ErrorList {
  90. var allErrors field.ErrorList
  91. if len(resources) == 0 {
  92. allErrors = append(allErrors, field.Required(fldPath, ""))
  93. }
  94. for i, resource := range resources {
  95. if resource == "" {
  96. allErrors = append(allErrors, field.Required(fldPath.Index(i), ""))
  97. }
  98. if strings.Contains(resource, "/") {
  99. allErrors = append(allErrors, field.Invalid(fldPath.Index(i), resource, "must not specify subresources"))
  100. }
  101. }
  102. if len(resources) > 1 && hasWildcard(resources) {
  103. allErrors = append(allErrors, field.Invalid(fldPath, resources, "if '*' is present, must not specify other resources"))
  104. }
  105. return allErrors
  106. }
  107. var validScopes = sets.NewString(
  108. string(admissionregistration.ClusterScope),
  109. string(admissionregistration.NamespacedScope),
  110. string(admissionregistration.AllScopes),
  111. )
  112. func validateRule(rule *admissionregistration.Rule, fldPath *field.Path, allowSubResource bool) field.ErrorList {
  113. var allErrors field.ErrorList
  114. if len(rule.APIGroups) == 0 {
  115. allErrors = append(allErrors, field.Required(fldPath.Child("apiGroups"), ""))
  116. }
  117. if len(rule.APIGroups) > 1 && hasWildcard(rule.APIGroups) {
  118. allErrors = append(allErrors, field.Invalid(fldPath.Child("apiGroups"), rule.APIGroups, "if '*' is present, must not specify other API groups"))
  119. }
  120. // Note: group could be empty, e.g., the legacy "v1" API
  121. if len(rule.APIVersions) == 0 {
  122. allErrors = append(allErrors, field.Required(fldPath.Child("apiVersions"), ""))
  123. }
  124. if len(rule.APIVersions) > 1 && hasWildcard(rule.APIVersions) {
  125. allErrors = append(allErrors, field.Invalid(fldPath.Child("apiVersions"), rule.APIVersions, "if '*' is present, must not specify other API versions"))
  126. }
  127. for i, version := range rule.APIVersions {
  128. if version == "" {
  129. allErrors = append(allErrors, field.Required(fldPath.Child("apiVersions").Index(i), ""))
  130. }
  131. }
  132. if allowSubResource {
  133. allErrors = append(allErrors, validateResources(rule.Resources, fldPath.Child("resources"))...)
  134. } else {
  135. allErrors = append(allErrors, validateResourcesNoSubResources(rule.Resources, fldPath.Child("resources"))...)
  136. }
  137. if rule.Scope != nil && !validScopes.Has(string(*rule.Scope)) {
  138. allErrors = append(allErrors, field.NotSupported(fldPath.Child("scope"), *rule.Scope, validScopes.List()))
  139. }
  140. return allErrors
  141. }
  142. // AcceptedAdmissionReviewVersions contains the list of AdmissionReview versions the *prior* version of the API server understands.
  143. // 1.15: server understands v1beta1; accepted versions are ["v1beta1"]
  144. // 1.16: server understands v1, v1beta1; accepted versions are ["v1beta1"]
  145. // 1.17+: server understands v1, v1beta1; accepted versions are ["v1","v1beta1"]
  146. var AcceptedAdmissionReviewVersions = []string{admissionregistrationv1.SchemeGroupVersion.Version, admissionregistrationv1beta1.SchemeGroupVersion.Version}
  147. func isAcceptedAdmissionReviewVersion(v string) bool {
  148. for _, version := range AcceptedAdmissionReviewVersions {
  149. if v == version {
  150. return true
  151. }
  152. }
  153. return false
  154. }
  155. func validateAdmissionReviewVersions(versions []string, requireRecognizedAdmissionReviewVersion bool, fldPath *field.Path) field.ErrorList {
  156. allErrors := field.ErrorList{}
  157. // Currently only v1beta1 accepted in AdmissionReviewVersions
  158. if len(versions) < 1 {
  159. allErrors = append(allErrors, field.Required(fldPath, fmt.Sprintf("must specify one of %v", strings.Join(AcceptedAdmissionReviewVersions, ", "))))
  160. } else {
  161. seen := map[string]bool{}
  162. hasAcceptedVersion := false
  163. for i, v := range versions {
  164. if seen[v] {
  165. allErrors = append(allErrors, field.Invalid(fldPath.Index(i), v, "duplicate version"))
  166. continue
  167. }
  168. seen[v] = true
  169. for _, errString := range utilvalidation.IsDNS1035Label(v) {
  170. allErrors = append(allErrors, field.Invalid(fldPath.Index(i), v, errString))
  171. }
  172. if isAcceptedAdmissionReviewVersion(v) {
  173. hasAcceptedVersion = true
  174. }
  175. }
  176. if requireRecognizedAdmissionReviewVersion && !hasAcceptedVersion {
  177. allErrors = append(allErrors, field.Invalid(
  178. fldPath, versions,
  179. fmt.Sprintf("must include at least one of %v",
  180. strings.Join(AcceptedAdmissionReviewVersions, ", "))))
  181. }
  182. }
  183. return allErrors
  184. }
  185. // ValidateValidatingWebhookConfiguration validates a webhook before creation.
  186. func ValidateValidatingWebhookConfiguration(e *admissionregistration.ValidatingWebhookConfiguration, requestGV schema.GroupVersion) field.ErrorList {
  187. return validateValidatingWebhookConfiguration(e, validationOptions{
  188. requireNoSideEffects: requireNoSideEffects(requestGV),
  189. requireRecognizedAdmissionReviewVersion: true,
  190. requireUniqueWebhookNames: requireUniqueWebhookNames(requestGV),
  191. })
  192. }
  193. func validateValidatingWebhookConfiguration(e *admissionregistration.ValidatingWebhookConfiguration, opts validationOptions) field.ErrorList {
  194. allErrors := genericvalidation.ValidateObjectMeta(&e.ObjectMeta, false, genericvalidation.NameIsDNSSubdomain, field.NewPath("metadata"))
  195. hookNames := sets.NewString()
  196. for i, hook := range e.Webhooks {
  197. allErrors = append(allErrors, validateValidatingWebhook(&hook, opts, field.NewPath("webhooks").Index(i))...)
  198. allErrors = append(allErrors, validateAdmissionReviewVersions(hook.AdmissionReviewVersions, opts.requireRecognizedAdmissionReviewVersion, field.NewPath("webhooks").Index(i).Child("admissionReviewVersions"))...)
  199. if opts.requireUniqueWebhookNames && len(hook.Name) > 0 {
  200. if hookNames.Has(hook.Name) {
  201. allErrors = append(allErrors, field.Duplicate(field.NewPath("webhooks").Index(i).Child("name"), hook.Name))
  202. }
  203. hookNames.Insert(hook.Name)
  204. }
  205. }
  206. return allErrors
  207. }
  208. // ValidateMutatingWebhookConfiguration validates a webhook before creation.
  209. func ValidateMutatingWebhookConfiguration(e *admissionregistration.MutatingWebhookConfiguration, requestGV schema.GroupVersion) field.ErrorList {
  210. return validateMutatingWebhookConfiguration(e, validationOptions{
  211. requireNoSideEffects: requireNoSideEffects(requestGV),
  212. requireRecognizedAdmissionReviewVersion: true,
  213. requireUniqueWebhookNames: requireUniqueWebhookNames(requestGV),
  214. })
  215. }
  216. type validationOptions struct {
  217. requireNoSideEffects bool
  218. requireRecognizedAdmissionReviewVersion bool
  219. requireUniqueWebhookNames bool
  220. }
  221. func validateMutatingWebhookConfiguration(e *admissionregistration.MutatingWebhookConfiguration, opts validationOptions) field.ErrorList {
  222. allErrors := genericvalidation.ValidateObjectMeta(&e.ObjectMeta, false, genericvalidation.NameIsDNSSubdomain, field.NewPath("metadata"))
  223. hookNames := sets.NewString()
  224. for i, hook := range e.Webhooks {
  225. allErrors = append(allErrors, validateMutatingWebhook(&hook, opts, field.NewPath("webhooks").Index(i))...)
  226. allErrors = append(allErrors, validateAdmissionReviewVersions(hook.AdmissionReviewVersions, opts.requireRecognizedAdmissionReviewVersion, field.NewPath("webhooks").Index(i).Child("admissionReviewVersions"))...)
  227. if opts.requireUniqueWebhookNames && len(hook.Name) > 0 {
  228. if hookNames.Has(hook.Name) {
  229. allErrors = append(allErrors, field.Duplicate(field.NewPath("webhooks").Index(i).Child("name"), hook.Name))
  230. }
  231. hookNames.Insert(hook.Name)
  232. }
  233. }
  234. return allErrors
  235. }
  236. func validateValidatingWebhook(hook *admissionregistration.ValidatingWebhook, opts validationOptions, fldPath *field.Path) field.ErrorList {
  237. var allErrors field.ErrorList
  238. // hook.Name must be fully qualified
  239. allErrors = append(allErrors, utilvalidation.IsFullyQualifiedName(fldPath.Child("name"), hook.Name)...)
  240. for i, rule := range hook.Rules {
  241. allErrors = append(allErrors, validateRuleWithOperations(&rule, fldPath.Child("rules").Index(i))...)
  242. }
  243. if hook.FailurePolicy != nil && !supportedFailurePolicies.Has(string(*hook.FailurePolicy)) {
  244. allErrors = append(allErrors, field.NotSupported(fldPath.Child("failurePolicy"), *hook.FailurePolicy, supportedFailurePolicies.List()))
  245. }
  246. if hook.MatchPolicy != nil && !supportedMatchPolicies.Has(string(*hook.MatchPolicy)) {
  247. allErrors = append(allErrors, field.NotSupported(fldPath.Child("matchPolicy"), *hook.MatchPolicy, supportedMatchPolicies.List()))
  248. }
  249. allowedSideEffects := supportedSideEffectClasses
  250. if opts.requireNoSideEffects {
  251. allowedSideEffects = noSideEffectClasses
  252. }
  253. if hook.SideEffects == nil {
  254. allErrors = append(allErrors, field.Required(fldPath.Child("sideEffects"), fmt.Sprintf("must specify one of %v", strings.Join(allowedSideEffects.List(), ", "))))
  255. }
  256. if hook.SideEffects != nil && !allowedSideEffects.Has(string(*hook.SideEffects)) {
  257. allErrors = append(allErrors, field.NotSupported(fldPath.Child("sideEffects"), *hook.SideEffects, allowedSideEffects.List()))
  258. }
  259. if hook.TimeoutSeconds != nil && (*hook.TimeoutSeconds > 30 || *hook.TimeoutSeconds < 1) {
  260. allErrors = append(allErrors, field.Invalid(fldPath.Child("timeoutSeconds"), *hook.TimeoutSeconds, "the timeout value must be between 1 and 30 seconds"))
  261. }
  262. if hook.NamespaceSelector != nil {
  263. allErrors = append(allErrors, metav1validation.ValidateLabelSelector(hook.NamespaceSelector, fldPath.Child("namespaceSelector"))...)
  264. }
  265. if hook.ObjectSelector != nil {
  266. allErrors = append(allErrors, metav1validation.ValidateLabelSelector(hook.ObjectSelector, fldPath.Child("objectSelector"))...)
  267. }
  268. cc := hook.ClientConfig
  269. switch {
  270. case (cc.URL == nil) == (cc.Service == nil):
  271. allErrors = append(allErrors, field.Required(fldPath.Child("clientConfig"), "exactly one of url or service is required"))
  272. case cc.URL != nil:
  273. allErrors = append(allErrors, webhook.ValidateWebhookURL(fldPath.Child("clientConfig").Child("url"), *cc.URL, true)...)
  274. case cc.Service != nil:
  275. allErrors = append(allErrors, webhook.ValidateWebhookService(fldPath.Child("clientConfig").Child("service"), cc.Service.Name, cc.Service.Namespace, cc.Service.Path, cc.Service.Port)...)
  276. }
  277. return allErrors
  278. }
  279. func validateMutatingWebhook(hook *admissionregistration.MutatingWebhook, opts validationOptions, fldPath *field.Path) field.ErrorList {
  280. var allErrors field.ErrorList
  281. // hook.Name must be fully qualified
  282. allErrors = append(allErrors, utilvalidation.IsFullyQualifiedName(fldPath.Child("name"), hook.Name)...)
  283. for i, rule := range hook.Rules {
  284. allErrors = append(allErrors, validateRuleWithOperations(&rule, fldPath.Child("rules").Index(i))...)
  285. }
  286. if hook.FailurePolicy != nil && !supportedFailurePolicies.Has(string(*hook.FailurePolicy)) {
  287. allErrors = append(allErrors, field.NotSupported(fldPath.Child("failurePolicy"), *hook.FailurePolicy, supportedFailurePolicies.List()))
  288. }
  289. if hook.MatchPolicy != nil && !supportedMatchPolicies.Has(string(*hook.MatchPolicy)) {
  290. allErrors = append(allErrors, field.NotSupported(fldPath.Child("matchPolicy"), *hook.MatchPolicy, supportedMatchPolicies.List()))
  291. }
  292. allowedSideEffects := supportedSideEffectClasses
  293. if opts.requireNoSideEffects {
  294. allowedSideEffects = noSideEffectClasses
  295. }
  296. if hook.SideEffects == nil {
  297. allErrors = append(allErrors, field.Required(fldPath.Child("sideEffects"), fmt.Sprintf("must specify one of %v", strings.Join(allowedSideEffects.List(), ", "))))
  298. }
  299. if hook.SideEffects != nil && !allowedSideEffects.Has(string(*hook.SideEffects)) {
  300. allErrors = append(allErrors, field.NotSupported(fldPath.Child("sideEffects"), *hook.SideEffects, allowedSideEffects.List()))
  301. }
  302. if hook.TimeoutSeconds != nil && (*hook.TimeoutSeconds > 30 || *hook.TimeoutSeconds < 1) {
  303. allErrors = append(allErrors, field.Invalid(fldPath.Child("timeoutSeconds"), *hook.TimeoutSeconds, "the timeout value must be between 1 and 30 seconds"))
  304. }
  305. if hook.NamespaceSelector != nil {
  306. allErrors = append(allErrors, metav1validation.ValidateLabelSelector(hook.NamespaceSelector, fldPath.Child("namespaceSelector"))...)
  307. }
  308. if hook.ObjectSelector != nil {
  309. allErrors = append(allErrors, metav1validation.ValidateLabelSelector(hook.ObjectSelector, fldPath.Child("objectSelector"))...)
  310. }
  311. if hook.ReinvocationPolicy != nil && !supportedReinvocationPolicies.Has(string(*hook.ReinvocationPolicy)) {
  312. allErrors = append(allErrors, field.NotSupported(fldPath.Child("reinvocationPolicy"), *hook.ReinvocationPolicy, supportedReinvocationPolicies.List()))
  313. }
  314. cc := hook.ClientConfig
  315. switch {
  316. case (cc.URL == nil) == (cc.Service == nil):
  317. allErrors = append(allErrors, field.Required(fldPath.Child("clientConfig"), "exactly one of url or service is required"))
  318. case cc.URL != nil:
  319. allErrors = append(allErrors, webhook.ValidateWebhookURL(fldPath.Child("clientConfig").Child("url"), *cc.URL, true)...)
  320. case cc.Service != nil:
  321. allErrors = append(allErrors, webhook.ValidateWebhookService(fldPath.Child("clientConfig").Child("service"), cc.Service.Name, cc.Service.Namespace, cc.Service.Path, cc.Service.Port)...)
  322. }
  323. return allErrors
  324. }
  325. var supportedFailurePolicies = sets.NewString(
  326. string(admissionregistration.Ignore),
  327. string(admissionregistration.Fail),
  328. )
  329. var supportedMatchPolicies = sets.NewString(
  330. string(admissionregistration.Exact),
  331. string(admissionregistration.Equivalent),
  332. )
  333. var supportedSideEffectClasses = sets.NewString(
  334. string(admissionregistration.SideEffectClassUnknown),
  335. string(admissionregistration.SideEffectClassNone),
  336. string(admissionregistration.SideEffectClassSome),
  337. string(admissionregistration.SideEffectClassNoneOnDryRun),
  338. )
  339. var noSideEffectClasses = sets.NewString(
  340. string(admissionregistration.SideEffectClassNone),
  341. string(admissionregistration.SideEffectClassNoneOnDryRun),
  342. )
  343. var supportedOperations = sets.NewString(
  344. string(admissionregistration.OperationAll),
  345. string(admissionregistration.Create),
  346. string(admissionregistration.Update),
  347. string(admissionregistration.Delete),
  348. string(admissionregistration.Connect),
  349. )
  350. var supportedReinvocationPolicies = sets.NewString(
  351. string(admissionregistration.NeverReinvocationPolicy),
  352. string(admissionregistration.IfNeededReinvocationPolicy),
  353. )
  354. func hasWildcardOperation(operations []admissionregistration.OperationType) bool {
  355. for _, o := range operations {
  356. if o == admissionregistration.OperationAll {
  357. return true
  358. }
  359. }
  360. return false
  361. }
  362. func validateRuleWithOperations(ruleWithOperations *admissionregistration.RuleWithOperations, fldPath *field.Path) field.ErrorList {
  363. var allErrors field.ErrorList
  364. if len(ruleWithOperations.Operations) == 0 {
  365. allErrors = append(allErrors, field.Required(fldPath.Child("operations"), ""))
  366. }
  367. if len(ruleWithOperations.Operations) > 1 && hasWildcardOperation(ruleWithOperations.Operations) {
  368. allErrors = append(allErrors, field.Invalid(fldPath.Child("operations"), ruleWithOperations.Operations, "if '*' is present, must not specify other operations"))
  369. }
  370. for i, operation := range ruleWithOperations.Operations {
  371. if !supportedOperations.Has(string(operation)) {
  372. allErrors = append(allErrors, field.NotSupported(fldPath.Child("operations").Index(i), operation, supportedOperations.List()))
  373. }
  374. }
  375. allowSubResource := true
  376. allErrors = append(allErrors, validateRule(&ruleWithOperations.Rule, fldPath, allowSubResource)...)
  377. return allErrors
  378. }
  379. // mutatingHasAcceptedAdmissionReviewVersions returns true if all webhooks have at least one
  380. // admission review version this apiserver accepts.
  381. func mutatingHasAcceptedAdmissionReviewVersions(webhooks []admissionregistration.MutatingWebhook) bool {
  382. for _, hook := range webhooks {
  383. hasRecognizedVersion := false
  384. for _, version := range hook.AdmissionReviewVersions {
  385. if isAcceptedAdmissionReviewVersion(version) {
  386. hasRecognizedVersion = true
  387. break
  388. }
  389. }
  390. if !hasRecognizedVersion && len(hook.AdmissionReviewVersions) > 0 {
  391. return false
  392. }
  393. }
  394. return true
  395. }
  396. // validatingHasAcceptedAdmissionReviewVersions returns true if all webhooks have at least one
  397. // admission review version this apiserver accepts.
  398. func validatingHasAcceptedAdmissionReviewVersions(webhooks []admissionregistration.ValidatingWebhook) bool {
  399. for _, hook := range webhooks {
  400. hasRecognizedVersion := false
  401. for _, version := range hook.AdmissionReviewVersions {
  402. if isAcceptedAdmissionReviewVersion(version) {
  403. hasRecognizedVersion = true
  404. break
  405. }
  406. }
  407. if !hasRecognizedVersion && len(hook.AdmissionReviewVersions) > 0 {
  408. return false
  409. }
  410. }
  411. return true
  412. }
  413. // mutatingHasUniqueWebhookNames returns true if all webhooks have unique names
  414. func mutatingHasUniqueWebhookNames(webhooks []admissionregistration.MutatingWebhook) bool {
  415. names := sets.NewString()
  416. for _, hook := range webhooks {
  417. if names.Has(hook.Name) {
  418. return false
  419. }
  420. names.Insert(hook.Name)
  421. }
  422. return true
  423. }
  424. // validatingHasUniqueWebhookNames returns true if all webhooks have unique names
  425. func validatingHasUniqueWebhookNames(webhooks []admissionregistration.ValidatingWebhook) bool {
  426. names := sets.NewString()
  427. for _, hook := range webhooks {
  428. if names.Has(hook.Name) {
  429. return false
  430. }
  431. names.Insert(hook.Name)
  432. }
  433. return true
  434. }
  435. // mutatingHasNoSideEffects returns true if all webhooks have no side effects
  436. func mutatingHasNoSideEffects(webhooks []admissionregistration.MutatingWebhook) bool {
  437. for _, hook := range webhooks {
  438. if hook.SideEffects == nil || !noSideEffectClasses.Has(string(*hook.SideEffects)) {
  439. return false
  440. }
  441. }
  442. return true
  443. }
  444. // validatingHasNoSideEffects returns true if all webhooks have no side effects
  445. func validatingHasNoSideEffects(webhooks []admissionregistration.ValidatingWebhook) bool {
  446. for _, hook := range webhooks {
  447. if hook.SideEffects == nil || !noSideEffectClasses.Has(string(*hook.SideEffects)) {
  448. return false
  449. }
  450. }
  451. return true
  452. }
  453. func ValidateValidatingWebhookConfigurationUpdate(newC, oldC *admissionregistration.ValidatingWebhookConfiguration, requestGV schema.GroupVersion) field.ErrorList {
  454. return validateValidatingWebhookConfiguration(newC, validationOptions{
  455. requireNoSideEffects: requireNoSideEffects(requestGV) && validatingHasNoSideEffects(oldC.Webhooks),
  456. requireRecognizedAdmissionReviewVersion: validatingHasAcceptedAdmissionReviewVersions(oldC.Webhooks),
  457. requireUniqueWebhookNames: requireUniqueWebhookNames(requestGV) && validatingHasUniqueWebhookNames(oldC.Webhooks),
  458. })
  459. }
  460. func ValidateMutatingWebhookConfigurationUpdate(newC, oldC *admissionregistration.MutatingWebhookConfiguration, requestGV schema.GroupVersion) field.ErrorList {
  461. return validateMutatingWebhookConfiguration(newC, validationOptions{
  462. requireNoSideEffects: requireNoSideEffects(requestGV) && mutatingHasNoSideEffects(oldC.Webhooks),
  463. requireRecognizedAdmissionReviewVersion: mutatingHasAcceptedAdmissionReviewVersions(oldC.Webhooks),
  464. requireUniqueWebhookNames: requireUniqueWebhookNames(requestGV) && mutatingHasUniqueWebhookNames(oldC.Webhooks),
  465. })
  466. }
  467. // requireUniqueWebhookNames returns true for all requests except v1beta1 (for backwards compatibility)
  468. func requireUniqueWebhookNames(requestGV schema.GroupVersion) bool {
  469. return requestGV != (schema.GroupVersion{Group: admissionregistration.GroupName, Version: "v1beta1"})
  470. }
  471. // requireNoSideEffects returns true for all requests except v1beta1 (for backwards compatibility)
  472. func requireNoSideEffects(requestGV schema.GroupVersion) bool {
  473. return requestGV != (schema.GroupVersion{Group: admissionregistration.GroupName, Version: "v1beta1"})
  474. }