1
16
17 package validation
18
19 import (
20 "fmt"
21 "reflect"
22 "regexp"
23 "strings"
24
25 genericvalidation "k8s.io/apimachinery/pkg/api/validation"
26 "k8s.io/apimachinery/pkg/api/validation/path"
27 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
28 metav1validation "k8s.io/apimachinery/pkg/apis/meta/v1/validation"
29 utilruntime "k8s.io/apimachinery/pkg/util/runtime"
30 "k8s.io/apimachinery/pkg/util/sets"
31 utilvalidation "k8s.io/apimachinery/pkg/util/validation"
32 "k8s.io/apimachinery/pkg/util/validation/field"
33 plugincel "k8s.io/apiserver/pkg/admission/plugin/cel"
34 validatingadmissionpolicy "k8s.io/apiserver/pkg/admission/plugin/policy/validating"
35 "k8s.io/apiserver/pkg/admission/plugin/webhook/matchconditions"
36 "k8s.io/apiserver/pkg/cel"
37 "k8s.io/apiserver/pkg/cel/environment"
38 "k8s.io/apiserver/pkg/util/webhook"
39 "k8s.io/client-go/util/jsonpath"
40
41 "k8s.io/kubernetes/pkg/apis/admissionregistration"
42 admissionregistrationv1 "k8s.io/kubernetes/pkg/apis/admissionregistration/v1"
43 admissionregistrationv1beta1 "k8s.io/kubernetes/pkg/apis/admissionregistration/v1beta1"
44 apivalidation "k8s.io/kubernetes/pkg/apis/core/validation"
45 )
46
47 func hasWildcard(slice []string) bool {
48 for _, s := range slice {
49 if s == "*" {
50 return true
51 }
52 }
53 return false
54 }
55
56 func validateResources(resources []string, fldPath *field.Path) field.ErrorList {
57 var allErrors field.ErrorList
58 if len(resources) == 0 {
59 allErrors = append(allErrors, field.Required(fldPath, ""))
60 }
61
62
63 resourcesWithWildcardSubresoures := sets.String{}
64
65 subResourcesWithWildcardResource := sets.String{}
66
67 hasDoubleWildcard := false
68
69 hasSingleWildcard := false
70
71 hasResourceWithoutSubresource := false
72
73 for i, resSub := range resources {
74 if resSub == "" {
75 allErrors = append(allErrors, field.Required(fldPath.Index(i), ""))
76 continue
77 }
78 if resSub == "*/*" {
79 hasDoubleWildcard = true
80 }
81 if resSub == "*" {
82 hasSingleWildcard = true
83 }
84 parts := strings.SplitN(resSub, "/", 2)
85 if len(parts) == 1 {
86 hasResourceWithoutSubresource = resSub != "*"
87 continue
88 }
89 res, sub := parts[0], parts[1]
90 if _, ok := resourcesWithWildcardSubresoures[res]; ok {
91 allErrors = append(allErrors, field.Invalid(fldPath.Index(i), resSub, fmt.Sprintf("if '%s/*' is present, must not specify %s", res, resSub)))
92 }
93 if _, ok := subResourcesWithWildcardResource[sub]; ok {
94 allErrors = append(allErrors, field.Invalid(fldPath.Index(i), resSub, fmt.Sprintf("if '*/%s' is present, must not specify %s", sub, resSub)))
95 }
96 if sub == "*" {
97 resourcesWithWildcardSubresoures[res] = struct{}{}
98 }
99 if res == "*" {
100 subResourcesWithWildcardResource[sub] = struct{}{}
101 }
102 }
103 if len(resources) > 1 && hasDoubleWildcard {
104 allErrors = append(allErrors, field.Invalid(fldPath, resources, "if '*/*' is present, must not specify other resources"))
105 }
106 if hasSingleWildcard && hasResourceWithoutSubresource {
107 allErrors = append(allErrors, field.Invalid(fldPath, resources, "if '*' is present, must not specify other resources without subresources"))
108 }
109 return allErrors
110 }
111
112 func validateResourcesNoSubResources(resources []string, fldPath *field.Path) field.ErrorList {
113 var allErrors field.ErrorList
114 if len(resources) == 0 {
115 allErrors = append(allErrors, field.Required(fldPath, ""))
116 }
117 for i, resource := range resources {
118 if resource == "" {
119 allErrors = append(allErrors, field.Required(fldPath.Index(i), ""))
120 }
121 if strings.Contains(resource, "/") {
122 allErrors = append(allErrors, field.Invalid(fldPath.Index(i), resource, "must not specify subresources"))
123 }
124 }
125 if len(resources) > 1 && hasWildcard(resources) {
126 allErrors = append(allErrors, field.Invalid(fldPath, resources, "if '*' is present, must not specify other resources"))
127 }
128 return allErrors
129 }
130
131 var validScopes = sets.NewString(
132 string(admissionregistration.ClusterScope),
133 string(admissionregistration.NamespacedScope),
134 string(admissionregistration.AllScopes),
135 )
136
137 func validateRule(rule *admissionregistration.Rule, fldPath *field.Path, allowSubResource bool) field.ErrorList {
138 var allErrors field.ErrorList
139 if len(rule.APIGroups) == 0 {
140 allErrors = append(allErrors, field.Required(fldPath.Child("apiGroups"), ""))
141 }
142 if len(rule.APIGroups) > 1 && hasWildcard(rule.APIGroups) {
143 allErrors = append(allErrors, field.Invalid(fldPath.Child("apiGroups"), rule.APIGroups, "if '*' is present, must not specify other API groups"))
144 }
145
146 if len(rule.APIVersions) == 0 {
147 allErrors = append(allErrors, field.Required(fldPath.Child("apiVersions"), ""))
148 }
149 if len(rule.APIVersions) > 1 && hasWildcard(rule.APIVersions) {
150 allErrors = append(allErrors, field.Invalid(fldPath.Child("apiVersions"), rule.APIVersions, "if '*' is present, must not specify other API versions"))
151 }
152 for i, version := range rule.APIVersions {
153 if version == "" {
154 allErrors = append(allErrors, field.Required(fldPath.Child("apiVersions").Index(i), ""))
155 }
156 }
157 if allowSubResource {
158 allErrors = append(allErrors, validateResources(rule.Resources, fldPath.Child("resources"))...)
159 } else {
160 allErrors = append(allErrors, validateResourcesNoSubResources(rule.Resources, fldPath.Child("resources"))...)
161 }
162 if rule.Scope != nil && !validScopes.Has(string(*rule.Scope)) {
163 allErrors = append(allErrors, field.NotSupported(fldPath.Child("scope"), *rule.Scope, validScopes.List()))
164 }
165 return allErrors
166 }
167
168
169
170
171
172 var AcceptedAdmissionReviewVersions = []string{admissionregistrationv1.SchemeGroupVersion.Version, admissionregistrationv1beta1.SchemeGroupVersion.Version}
173
174 func isAcceptedAdmissionReviewVersion(v string) bool {
175 for _, version := range AcceptedAdmissionReviewVersions {
176 if v == version {
177 return true
178 }
179 }
180 return false
181 }
182
183 func validateAdmissionReviewVersions(versions []string, requireRecognizedAdmissionReviewVersion bool, fldPath *field.Path) field.ErrorList {
184 allErrors := field.ErrorList{}
185
186
187 if len(versions) < 1 {
188 allErrors = append(allErrors, field.Required(fldPath, fmt.Sprintf("must specify one of %v", strings.Join(AcceptedAdmissionReviewVersions, ", "))))
189 } else {
190 seen := map[string]bool{}
191 hasAcceptedVersion := false
192 for i, v := range versions {
193 if seen[v] {
194 allErrors = append(allErrors, field.Invalid(fldPath.Index(i), v, "duplicate version"))
195 continue
196 }
197 seen[v] = true
198 for _, errString := range utilvalidation.IsDNS1035Label(v) {
199 allErrors = append(allErrors, field.Invalid(fldPath.Index(i), v, errString))
200 }
201 if isAcceptedAdmissionReviewVersion(v) {
202 hasAcceptedVersion = true
203 }
204 }
205 if requireRecognizedAdmissionReviewVersion && !hasAcceptedVersion {
206 allErrors = append(allErrors, field.Invalid(
207 fldPath, versions,
208 fmt.Sprintf("must include at least one of %v",
209 strings.Join(AcceptedAdmissionReviewVersions, ", "))))
210 }
211 }
212 return allErrors
213 }
214
215
216 func ValidateValidatingWebhookConfiguration(e *admissionregistration.ValidatingWebhookConfiguration) field.ErrorList {
217 return validateValidatingWebhookConfiguration(e, validationOptions{
218 ignoreMatchConditions: false,
219 allowParamsInMatchConditions: false,
220 requireNoSideEffects: true,
221 requireRecognizedAdmissionReviewVersion: true,
222 requireUniqueWebhookNames: true,
223 allowInvalidLabelValueInSelector: false,
224 })
225 }
226
227 func validateValidatingWebhookConfiguration(e *admissionregistration.ValidatingWebhookConfiguration, opts validationOptions) field.ErrorList {
228 allErrors := genericvalidation.ValidateObjectMeta(&e.ObjectMeta, false, genericvalidation.NameIsDNSSubdomain, field.NewPath("metadata"))
229 hookNames := sets.NewString()
230 for i, hook := range e.Webhooks {
231 allErrors = append(allErrors, validateValidatingWebhook(&hook, opts, field.NewPath("webhooks").Index(i))...)
232 allErrors = append(allErrors, validateAdmissionReviewVersions(hook.AdmissionReviewVersions, opts.requireRecognizedAdmissionReviewVersion, field.NewPath("webhooks").Index(i).Child("admissionReviewVersions"))...)
233 if opts.requireUniqueWebhookNames && len(hook.Name) > 0 {
234 if hookNames.Has(hook.Name) {
235 allErrors = append(allErrors, field.Duplicate(field.NewPath("webhooks").Index(i).Child("name"), hook.Name))
236 } else {
237 hookNames.Insert(hook.Name)
238 }
239 }
240 }
241 return allErrors
242 }
243
244
245 func ValidateMutatingWebhookConfiguration(e *admissionregistration.MutatingWebhookConfiguration) field.ErrorList {
246 return validateMutatingWebhookConfiguration(e, validationOptions{
247 ignoreMatchConditions: false,
248 allowParamsInMatchConditions: false,
249 requireNoSideEffects: true,
250 requireRecognizedAdmissionReviewVersion: true,
251 requireUniqueWebhookNames: true,
252 allowInvalidLabelValueInSelector: false,
253 })
254 }
255
256 type validationOptions struct {
257 ignoreMatchConditions bool
258 allowParamsInMatchConditions bool
259 requireNoSideEffects bool
260 requireRecognizedAdmissionReviewVersion bool
261 requireUniqueWebhookNames bool
262 allowInvalidLabelValueInSelector bool
263 preexistingExpressions preexistingExpressions
264 }
265
266 type preexistingExpressions struct {
267 matchConditionExpressions sets.Set[string]
268 validationExpressions sets.Set[string]
269 validationMessageExpressions sets.Set[string]
270 auditAnnotationValuesExpressions sets.Set[string]
271 }
272
273 func newPreexistingExpressions() preexistingExpressions {
274 return preexistingExpressions{
275 matchConditionExpressions: sets.New[string](),
276 validationExpressions: sets.New[string](),
277 validationMessageExpressions: sets.New[string](),
278 auditAnnotationValuesExpressions: sets.New[string](),
279 }
280 }
281
282 func findMutatingPreexistingExpressions(mutating *admissionregistration.MutatingWebhookConfiguration) preexistingExpressions {
283 preexisting := newPreexistingExpressions()
284 for _, wh := range mutating.Webhooks {
285 for _, mc := range wh.MatchConditions {
286 preexisting.matchConditionExpressions.Insert(mc.Expression)
287 }
288 }
289 return preexisting
290 }
291
292 func findValidatingPreexistingExpressions(validating *admissionregistration.ValidatingWebhookConfiguration) preexistingExpressions {
293 preexisting := newPreexistingExpressions()
294 for _, wh := range validating.Webhooks {
295 for _, mc := range wh.MatchConditions {
296 preexisting.matchConditionExpressions.Insert(mc.Expression)
297 }
298 }
299 return preexisting
300 }
301
302 func findValidatingPolicyPreexistingExpressions(validatingPolicy *admissionregistration.ValidatingAdmissionPolicy) preexistingExpressions {
303 preexisting := newPreexistingExpressions()
304 for _, mc := range validatingPolicy.Spec.MatchConditions {
305 preexisting.matchConditionExpressions.Insert(mc.Expression)
306 }
307 for _, v := range validatingPolicy.Spec.Validations {
308 preexisting.validationExpressions.Insert(v.Expression)
309 if len(v.MessageExpression) > 0 {
310 preexisting.validationMessageExpressions.Insert(v.MessageExpression)
311 }
312 }
313 for _, a := range validatingPolicy.Spec.AuditAnnotations {
314 preexisting.auditAnnotationValuesExpressions.Insert(a.ValueExpression)
315 }
316 return preexisting
317 }
318
319 func validateMutatingWebhookConfiguration(e *admissionregistration.MutatingWebhookConfiguration, opts validationOptions) field.ErrorList {
320 allErrors := genericvalidation.ValidateObjectMeta(&e.ObjectMeta, false, genericvalidation.NameIsDNSSubdomain, field.NewPath("metadata"))
321
322 hookNames := sets.NewString()
323 for i, hook := range e.Webhooks {
324 allErrors = append(allErrors, validateMutatingWebhook(&hook, opts, field.NewPath("webhooks").Index(i))...)
325 allErrors = append(allErrors, validateAdmissionReviewVersions(hook.AdmissionReviewVersions, opts.requireRecognizedAdmissionReviewVersion, field.NewPath("webhooks").Index(i).Child("admissionReviewVersions"))...)
326 if opts.requireUniqueWebhookNames && len(hook.Name) > 0 {
327 if hookNames.Has(hook.Name) {
328 allErrors = append(allErrors, field.Duplicate(field.NewPath("webhooks").Index(i).Child("name"), hook.Name))
329 } else {
330 hookNames.Insert(hook.Name)
331 }
332 }
333 }
334 return allErrors
335 }
336
337 func validateValidatingWebhook(hook *admissionregistration.ValidatingWebhook, opts validationOptions, fldPath *field.Path) field.ErrorList {
338 var allErrors field.ErrorList
339
340 allErrors = append(allErrors, utilvalidation.IsFullyQualifiedName(fldPath.Child("name"), hook.Name)...)
341 labelSelectorValidationOpts := metav1validation.LabelSelectorValidationOptions{
342 AllowInvalidLabelValueInSelector: opts.allowInvalidLabelValueInSelector,
343 }
344
345 for i, rule := range hook.Rules {
346 allErrors = append(allErrors, validateRuleWithOperations(&rule, fldPath.Child("rules").Index(i))...)
347 }
348 if hook.FailurePolicy != nil && !supportedFailurePolicies.Has(string(*hook.FailurePolicy)) {
349 allErrors = append(allErrors, field.NotSupported(fldPath.Child("failurePolicy"), *hook.FailurePolicy, supportedFailurePolicies.List()))
350 }
351 if hook.MatchPolicy != nil && !supportedMatchPolicies.Has(string(*hook.MatchPolicy)) {
352 allErrors = append(allErrors, field.NotSupported(fldPath.Child("matchPolicy"), *hook.MatchPolicy, supportedMatchPolicies.List()))
353 }
354 allowedSideEffects := supportedSideEffectClasses
355 if opts.requireNoSideEffects {
356 allowedSideEffects = noSideEffectClasses
357 }
358 if hook.SideEffects == nil {
359 allErrors = append(allErrors, field.Required(fldPath.Child("sideEffects"), fmt.Sprintf("must specify one of %v", strings.Join(allowedSideEffects.List(), ", "))))
360 }
361 if hook.SideEffects != nil && !allowedSideEffects.Has(string(*hook.SideEffects)) {
362 allErrors = append(allErrors, field.NotSupported(fldPath.Child("sideEffects"), *hook.SideEffects, allowedSideEffects.List()))
363 }
364 if hook.TimeoutSeconds != nil && (*hook.TimeoutSeconds > 30 || *hook.TimeoutSeconds < 1) {
365 allErrors = append(allErrors, field.Invalid(fldPath.Child("timeoutSeconds"), *hook.TimeoutSeconds, "the timeout value must be between 1 and 30 seconds"))
366 }
367
368 if hook.NamespaceSelector != nil {
369 allErrors = append(allErrors, metav1validation.ValidateLabelSelector(hook.NamespaceSelector, labelSelectorValidationOpts, fldPath.Child("namespaceSelector"))...)
370 }
371
372 if hook.ObjectSelector != nil {
373 allErrors = append(allErrors, metav1validation.ValidateLabelSelector(hook.ObjectSelector, labelSelectorValidationOpts, fldPath.Child("objectSelector"))...)
374 }
375
376 cc := hook.ClientConfig
377 switch {
378 case (cc.URL == nil) == (cc.Service == nil):
379 allErrors = append(allErrors, field.Required(fldPath.Child("clientConfig"), "exactly one of url or service is required"))
380 case cc.URL != nil:
381 allErrors = append(allErrors, webhook.ValidateWebhookURL(fldPath.Child("clientConfig").Child("url"), *cc.URL, true)...)
382 case cc.Service != nil:
383 allErrors = append(allErrors, webhook.ValidateWebhookService(fldPath.Child("clientConfig").Child("service"), cc.Service.Name, cc.Service.Namespace, cc.Service.Path, cc.Service.Port)...)
384 }
385
386 if !opts.ignoreMatchConditions {
387 allErrors = append(allErrors, validateMatchConditions(hook.MatchConditions, opts, fldPath.Child("matchConditions"))...)
388 }
389
390 return allErrors
391 }
392
393 func validateMutatingWebhook(hook *admissionregistration.MutatingWebhook, opts validationOptions, fldPath *field.Path) field.ErrorList {
394 var allErrors field.ErrorList
395
396 allErrors = append(allErrors, utilvalidation.IsFullyQualifiedName(fldPath.Child("name"), hook.Name)...)
397 labelSelectorValidationOpts := metav1validation.LabelSelectorValidationOptions{
398 AllowInvalidLabelValueInSelector: opts.allowInvalidLabelValueInSelector,
399 }
400
401 for i, rule := range hook.Rules {
402 allErrors = append(allErrors, validateRuleWithOperations(&rule, fldPath.Child("rules").Index(i))...)
403 }
404 if hook.FailurePolicy != nil && !supportedFailurePolicies.Has(string(*hook.FailurePolicy)) {
405 allErrors = append(allErrors, field.NotSupported(fldPath.Child("failurePolicy"), *hook.FailurePolicy, supportedFailurePolicies.List()))
406 }
407 if hook.MatchPolicy != nil && !supportedMatchPolicies.Has(string(*hook.MatchPolicy)) {
408 allErrors = append(allErrors, field.NotSupported(fldPath.Child("matchPolicy"), *hook.MatchPolicy, supportedMatchPolicies.List()))
409 }
410 allowedSideEffects := supportedSideEffectClasses
411 if opts.requireNoSideEffects {
412 allowedSideEffects = noSideEffectClasses
413 }
414 if hook.SideEffects == nil {
415 allErrors = append(allErrors, field.Required(fldPath.Child("sideEffects"), fmt.Sprintf("must specify one of %v", strings.Join(allowedSideEffects.List(), ", "))))
416 }
417 if hook.SideEffects != nil && !allowedSideEffects.Has(string(*hook.SideEffects)) {
418 allErrors = append(allErrors, field.NotSupported(fldPath.Child("sideEffects"), *hook.SideEffects, allowedSideEffects.List()))
419 }
420 if hook.TimeoutSeconds != nil && (*hook.TimeoutSeconds > 30 || *hook.TimeoutSeconds < 1) {
421 allErrors = append(allErrors, field.Invalid(fldPath.Child("timeoutSeconds"), *hook.TimeoutSeconds, "the timeout value must be between 1 and 30 seconds"))
422 }
423
424 if hook.NamespaceSelector != nil {
425 allErrors = append(allErrors, metav1validation.ValidateLabelSelector(hook.NamespaceSelector, labelSelectorValidationOpts, fldPath.Child("namespaceSelector"))...)
426 }
427 if hook.ObjectSelector != nil {
428 allErrors = append(allErrors, metav1validation.ValidateLabelSelector(hook.ObjectSelector, labelSelectorValidationOpts, fldPath.Child("objectSelector"))...)
429 }
430 if hook.ReinvocationPolicy != nil && !supportedReinvocationPolicies.Has(string(*hook.ReinvocationPolicy)) {
431 allErrors = append(allErrors, field.NotSupported(fldPath.Child("reinvocationPolicy"), *hook.ReinvocationPolicy, supportedReinvocationPolicies.List()))
432 }
433
434 cc := hook.ClientConfig
435 switch {
436 case (cc.URL == nil) == (cc.Service == nil):
437 allErrors = append(allErrors, field.Required(fldPath.Child("clientConfig"), "exactly one of url or service is required"))
438 case cc.URL != nil:
439 allErrors = append(allErrors, webhook.ValidateWebhookURL(fldPath.Child("clientConfig").Child("url"), *cc.URL, true)...)
440 case cc.Service != nil:
441 allErrors = append(allErrors, webhook.ValidateWebhookService(fldPath.Child("clientConfig").Child("service"), cc.Service.Name, cc.Service.Namespace, cc.Service.Path, cc.Service.Port)...)
442 }
443
444 if !opts.ignoreMatchConditions {
445 allErrors = append(allErrors, validateMatchConditions(hook.MatchConditions, opts, fldPath.Child("matchConditions"))...)
446 }
447
448 return allErrors
449 }
450
451 var supportedFailurePolicies = sets.NewString(
452 string(admissionregistration.Ignore),
453 string(admissionregistration.Fail),
454 )
455
456 var supportedMatchPolicies = sets.NewString(
457 string(admissionregistration.Exact),
458 string(admissionregistration.Equivalent),
459 )
460
461 var supportedSideEffectClasses = sets.NewString(
462 string(admissionregistration.SideEffectClassUnknown),
463 string(admissionregistration.SideEffectClassNone),
464 string(admissionregistration.SideEffectClassSome),
465 string(admissionregistration.SideEffectClassNoneOnDryRun),
466 )
467
468 var noSideEffectClasses = sets.NewString(
469 string(admissionregistration.SideEffectClassNone),
470 string(admissionregistration.SideEffectClassNoneOnDryRun),
471 )
472
473 var supportedOperations = sets.NewString(
474 string(admissionregistration.OperationAll),
475 string(admissionregistration.Create),
476 string(admissionregistration.Update),
477 string(admissionregistration.Delete),
478 string(admissionregistration.Connect),
479 )
480
481 var supportedReinvocationPolicies = sets.NewString(
482 string(admissionregistration.NeverReinvocationPolicy),
483 string(admissionregistration.IfNeededReinvocationPolicy),
484 )
485
486 var supportedValidationPolicyReason = sets.NewString(
487 string(metav1.StatusReasonForbidden),
488 string(metav1.StatusReasonInvalid),
489 string(metav1.StatusReasonRequestEntityTooLarge),
490 )
491
492 func hasWildcardOperation(operations []admissionregistration.OperationType) bool {
493 for _, o := range operations {
494 if o == admissionregistration.OperationAll {
495 return true
496 }
497 }
498 return false
499 }
500
501 func validateRuleWithOperations(ruleWithOperations *admissionregistration.RuleWithOperations, fldPath *field.Path) field.ErrorList {
502 var allErrors field.ErrorList
503 if len(ruleWithOperations.Operations) == 0 {
504 allErrors = append(allErrors, field.Required(fldPath.Child("operations"), ""))
505 }
506 if len(ruleWithOperations.Operations) > 1 && hasWildcardOperation(ruleWithOperations.Operations) {
507 allErrors = append(allErrors, field.Invalid(fldPath.Child("operations"), ruleWithOperations.Operations, "if '*' is present, must not specify other operations"))
508 }
509 for i, operation := range ruleWithOperations.Operations {
510 if !supportedOperations.Has(string(operation)) {
511 allErrors = append(allErrors, field.NotSupported(fldPath.Child("operations").Index(i), operation, supportedOperations.List()))
512 }
513 }
514 allowSubResource := true
515 allErrors = append(allErrors, validateRule(&ruleWithOperations.Rule, fldPath, allowSubResource)...)
516 return allErrors
517 }
518
519
520
521 func mutatingHasAcceptedAdmissionReviewVersions(webhooks []admissionregistration.MutatingWebhook) bool {
522 for _, hook := range webhooks {
523 hasRecognizedVersion := false
524 for _, version := range hook.AdmissionReviewVersions {
525 if isAcceptedAdmissionReviewVersion(version) {
526 hasRecognizedVersion = true
527 break
528 }
529 }
530 if !hasRecognizedVersion && len(hook.AdmissionReviewVersions) > 0 {
531 return false
532 }
533 }
534 return true
535 }
536
537
538
539 func validatingHasAcceptedAdmissionReviewVersions(webhooks []admissionregistration.ValidatingWebhook) bool {
540 for _, hook := range webhooks {
541 hasRecognizedVersion := false
542 for _, version := range hook.AdmissionReviewVersions {
543 if isAcceptedAdmissionReviewVersion(version) {
544 hasRecognizedVersion = true
545 break
546 }
547 }
548 if !hasRecognizedVersion && len(hook.AdmissionReviewVersions) > 0 {
549 return false
550 }
551 }
552 return true
553 }
554
555
556 func ignoreMutatingWebhookMatchConditions(new, old []admissionregistration.MutatingWebhook) bool {
557 if len(new) != len(old) {
558 return false
559 }
560 for i := range old {
561 if !reflect.DeepEqual(new[i].MatchConditions, old[i].MatchConditions) {
562 return false
563 }
564 }
565
566 return true
567 }
568
569
570 func ignoreValidatingWebhookMatchConditions(new, old []admissionregistration.ValidatingWebhook) bool {
571 if len(new) != len(old) {
572 return false
573 }
574 for i := range old {
575 if !reflect.DeepEqual(new[i].MatchConditions, old[i].MatchConditions) {
576 return false
577 }
578 }
579
580 return true
581 }
582
583
584 func ignoreValidatingAdmissionPolicyMatchConditions(new, old *admissionregistration.ValidatingAdmissionPolicy) bool {
585 if !reflect.DeepEqual(new.Spec.ParamKind, old.Spec.ParamKind) {
586 return false
587 }
588 if !reflect.DeepEqual(new.Spec.MatchConditions, old.Spec.MatchConditions) {
589 return false
590 }
591 return true
592 }
593
594
595 func mutatingHasUniqueWebhookNames(webhooks []admissionregistration.MutatingWebhook) bool {
596 names := sets.NewString()
597 for _, hook := range webhooks {
598 if names.Has(hook.Name) {
599 return false
600 }
601 names.Insert(hook.Name)
602 }
603 return true
604 }
605
606
607 func validatingHasUniqueWebhookNames(webhooks []admissionregistration.ValidatingWebhook) bool {
608 names := sets.NewString()
609 for _, hook := range webhooks {
610 if names.Has(hook.Name) {
611 return false
612 }
613 names.Insert(hook.Name)
614 }
615 return true
616 }
617
618
619 func mutatingHasNoSideEffects(webhooks []admissionregistration.MutatingWebhook) bool {
620 for _, hook := range webhooks {
621 if hook.SideEffects == nil || !noSideEffectClasses.Has(string(*hook.SideEffects)) {
622 return false
623 }
624 }
625 return true
626 }
627
628
629 func validatingHasNoSideEffects(webhooks []admissionregistration.ValidatingWebhook) bool {
630 for _, hook := range webhooks {
631 if hook.SideEffects == nil || !noSideEffectClasses.Has(string(*hook.SideEffects)) {
632 return false
633 }
634 }
635 return true
636 }
637
638
639 func validatingWebhookHasInvalidLabelValueInSelector(webhooks []admissionregistration.ValidatingWebhook) bool {
640 labelSelectorValidationOpts := metav1validation.LabelSelectorValidationOptions{
641 AllowInvalidLabelValueInSelector: false,
642 }
643
644 for _, hook := range webhooks {
645 if hook.NamespaceSelector != nil {
646 if len(metav1validation.ValidateLabelSelector(hook.NamespaceSelector, labelSelectorValidationOpts, nil)) > 0 {
647 return true
648 }
649 }
650 if hook.ObjectSelector != nil {
651 if len(metav1validation.ValidateLabelSelector(hook.ObjectSelector, labelSelectorValidationOpts, nil)) > 0 {
652 return true
653 }
654 }
655 }
656 return false
657 }
658
659
660 func mutatingWebhookHasInvalidLabelValueInSelector(webhooks []admissionregistration.MutatingWebhook) bool {
661 labelSelectorValidationOpts := metav1validation.LabelSelectorValidationOptions{
662 AllowInvalidLabelValueInSelector: false,
663 }
664
665 for _, hook := range webhooks {
666 if hook.NamespaceSelector != nil {
667 if len(metav1validation.ValidateLabelSelector(hook.NamespaceSelector, labelSelectorValidationOpts, nil)) > 0 {
668 return true
669 }
670 }
671 if hook.ObjectSelector != nil {
672 if len(metav1validation.ValidateLabelSelector(hook.ObjectSelector, labelSelectorValidationOpts, nil)) > 0 {
673 return true
674 }
675 }
676 }
677 return false
678 }
679
680
681 func ValidateValidatingWebhookConfigurationUpdate(newC, oldC *admissionregistration.ValidatingWebhookConfiguration) field.ErrorList {
682 return validateValidatingWebhookConfiguration(newC, validationOptions{
683 ignoreMatchConditions: ignoreValidatingWebhookMatchConditions(newC.Webhooks, oldC.Webhooks),
684 allowParamsInMatchConditions: false,
685 requireNoSideEffects: validatingHasNoSideEffects(oldC.Webhooks),
686 requireRecognizedAdmissionReviewVersion: validatingHasAcceptedAdmissionReviewVersions(oldC.Webhooks),
687 requireUniqueWebhookNames: validatingHasUniqueWebhookNames(oldC.Webhooks),
688 allowInvalidLabelValueInSelector: validatingWebhookHasInvalidLabelValueInSelector(oldC.Webhooks),
689 preexistingExpressions: findValidatingPreexistingExpressions(oldC),
690 })
691 }
692
693
694 func ValidateMutatingWebhookConfigurationUpdate(newC, oldC *admissionregistration.MutatingWebhookConfiguration) field.ErrorList {
695 return validateMutatingWebhookConfiguration(newC, validationOptions{
696 ignoreMatchConditions: ignoreMutatingWebhookMatchConditions(newC.Webhooks, oldC.Webhooks),
697 allowParamsInMatchConditions: false,
698 requireNoSideEffects: mutatingHasNoSideEffects(oldC.Webhooks),
699 requireRecognizedAdmissionReviewVersion: mutatingHasAcceptedAdmissionReviewVersions(oldC.Webhooks),
700 requireUniqueWebhookNames: mutatingHasUniqueWebhookNames(oldC.Webhooks),
701 allowInvalidLabelValueInSelector: mutatingWebhookHasInvalidLabelValueInSelector(oldC.Webhooks),
702 preexistingExpressions: findMutatingPreexistingExpressions(oldC),
703 })
704 }
705
706 const (
707 maxAuditAnnotations = 20
708
709
710
711 maxAuditAnnotationValueExpressionLength = 5 * 1024
712 )
713
714
715 func ValidateValidatingAdmissionPolicy(p *admissionregistration.ValidatingAdmissionPolicy) field.ErrorList {
716 return validateValidatingAdmissionPolicy(p, validationOptions{ignoreMatchConditions: false})
717 }
718
719 func validateValidatingAdmissionPolicy(p *admissionregistration.ValidatingAdmissionPolicy, opts validationOptions) field.ErrorList {
720 allErrors := genericvalidation.ValidateObjectMeta(&p.ObjectMeta, false, genericvalidation.NameIsDNSSubdomain, field.NewPath("metadata"))
721 allErrors = append(allErrors, validateValidatingAdmissionPolicySpec(p.ObjectMeta, &p.Spec, opts, field.NewPath("spec"))...)
722 return allErrors
723 }
724
725 func validateValidatingAdmissionPolicySpec(meta metav1.ObjectMeta, spec *admissionregistration.ValidatingAdmissionPolicySpec, opts validationOptions, fldPath *field.Path) field.ErrorList {
726 var allErrors field.ErrorList
727 var compiler plugincel.Compiler
728 getCompiler := func() plugincel.Compiler {
729 if compiler == nil {
730 needsComposition := len(spec.Variables) > 0
731 compiler = createCompiler(needsComposition)
732 }
733 return compiler
734 }
735 if spec.FailurePolicy == nil {
736 allErrors = append(allErrors, field.Required(fldPath.Child("failurePolicy"), ""))
737 } else if !supportedFailurePolicies.Has(string(*spec.FailurePolicy)) {
738 allErrors = append(allErrors, field.NotSupported(fldPath.Child("failurePolicy"), *spec.FailurePolicy, supportedFailurePolicies.List()))
739 }
740 if spec.ParamKind != nil {
741 opts.allowParamsInMatchConditions = true
742 allErrors = append(allErrors, validateParamKind(*spec.ParamKind, fldPath.Child("paramKind"))...)
743 }
744 if spec.MatchConstraints == nil {
745 allErrors = append(allErrors, field.Required(fldPath.Child("matchConstraints"), ""))
746 } else {
747 allErrors = append(allErrors, validateMatchResources(spec.MatchConstraints, fldPath.Child("matchConstraints"))...)
748
749 if len(spec.MatchConstraints.ResourceRules) == 0 {
750 allErrors = append(allErrors, field.Required(fldPath.Child("matchConstraints", "resourceRules"), ""))
751 }
752 }
753 if !opts.ignoreMatchConditions {
754 allErrors = append(allErrors, validateMatchConditions(spec.MatchConditions, opts, fldPath.Child("matchConditions"))...)
755 }
756 if len(spec.Variables) > 0 {
757 for i, variable := range spec.Variables {
758 allErrors = append(allErrors, validateVariable(getCompiler(), &variable, spec.ParamKind, opts, fldPath.Child("variables").Index(i))...)
759 }
760 }
761 if len(spec.Validations) == 0 && len(spec.AuditAnnotations) == 0 {
762 allErrors = append(allErrors, field.Required(fldPath.Child("validations"), "validations or auditAnnotations must contain at least one item"))
763 allErrors = append(allErrors, field.Required(fldPath.Child("auditAnnotations"), "validations or auditAnnotations must contain at least one item"))
764 } else {
765 for i, validation := range spec.Validations {
766 allErrors = append(allErrors, validateValidation(getCompiler(), &validation, spec.ParamKind, opts, fldPath.Child("validations").Index(i))...)
767 }
768 if spec.AuditAnnotations != nil {
769 keys := sets.NewString()
770 if len(spec.AuditAnnotations) > maxAuditAnnotations {
771 allErrors = append(allErrors, field.Invalid(fldPath.Child("auditAnnotations"), spec.AuditAnnotations, fmt.Sprintf("must not have more than %d auditAnnotations", maxAuditAnnotations)))
772 }
773 for i, auditAnnotation := range spec.AuditAnnotations {
774 allErrors = append(allErrors, validateAuditAnnotation(getCompiler(), meta, &auditAnnotation, spec.ParamKind, opts, fldPath.Child("auditAnnotations").Index(i))...)
775 if keys.Has(auditAnnotation.Key) {
776 allErrors = append(allErrors, field.Duplicate(fldPath.Child("auditAnnotations").Index(i).Child("key"), auditAnnotation.Key))
777 }
778 keys.Insert(auditAnnotation.Key)
779 }
780 }
781 }
782 return allErrors
783 }
784
785 func validateParamKind(gvk admissionregistration.ParamKind, fldPath *field.Path) field.ErrorList {
786 var allErrors field.ErrorList
787 if len(gvk.APIVersion) == 0 {
788 allErrors = append(allErrors, field.Required(fldPath.Child("apiVersion"), ""))
789 } else if gv, err := parseGroupVersion(gvk.APIVersion); err != nil {
790 allErrors = append(allErrors, field.Invalid(fldPath.Child("apiVersion"), gvk.APIVersion, err.Error()))
791 } else {
792
793 if len(gv.Group) > 0 {
794 if errs := utilvalidation.IsDNS1123Subdomain(gv.Group); len(errs) > 0 {
795 allErrors = append(allErrors, field.Invalid(fldPath.Child("apiVersion"), gv.Group, strings.Join(errs, ",")))
796 }
797 }
798
799 if len(gv.Version) == 0 {
800 allErrors = append(allErrors, field.Invalid(fldPath.Child("apiVersion"), gvk.APIVersion, "version must be specified"))
801 } else {
802 if errs := utilvalidation.IsDNS1035Label(gv.Version); len(errs) > 0 {
803 allErrors = append(allErrors, field.Invalid(fldPath.Child("apiVersion"), gv.Version, strings.Join(errs, ",")))
804 }
805 }
806 }
807 if len(gvk.Kind) == 0 {
808 allErrors = append(allErrors, field.Required(fldPath.Child("kind"), ""))
809 } else if errs := utilvalidation.IsDNS1035Label(strings.ToLower(gvk.Kind)); len(errs) > 0 {
810 allErrors = append(allErrors, field.Invalid(fldPath.Child("kind"), gvk.Kind, "may have mixed case, but should otherwise match: "+strings.Join(errs, ",")))
811 }
812
813 return allErrors
814 }
815
816 type groupVersion struct {
817 Group string
818 Version string
819 }
820
821
822
823 func parseGroupVersion(gv string) (groupVersion, error) {
824 if (len(gv) == 0) || (gv == "/") {
825 return groupVersion{}, nil
826 }
827
828 switch strings.Count(gv, "/") {
829 case 0:
830 return groupVersion{"", gv}, nil
831 case 1:
832 i := strings.Index(gv, "/")
833 return groupVersion{gv[:i], gv[i+1:]}, nil
834 default:
835 return groupVersion{}, fmt.Errorf("unexpected GroupVersion string: %v", gv)
836 }
837 }
838
839 func validateMatchResources(mc *admissionregistration.MatchResources, fldPath *field.Path) field.ErrorList {
840 var allErrors field.ErrorList
841 if mc == nil {
842 return allErrors
843 }
844 if mc.MatchPolicy == nil {
845 allErrors = append(allErrors, field.Required(fldPath.Child("matchPolicy"), ""))
846 } else if !supportedMatchPolicies.Has(string(*mc.MatchPolicy)) {
847 allErrors = append(allErrors, field.NotSupported(fldPath.Child("matchPolicy"), *mc.MatchPolicy, supportedMatchPolicies.List()))
848 }
849 if mc.NamespaceSelector == nil {
850 allErrors = append(allErrors, field.Required(fldPath.Child("namespaceSelector"), ""))
851 } else {
852
853 allErrors = append(allErrors, metav1validation.ValidateLabelSelector(mc.NamespaceSelector, metav1validation.LabelSelectorValidationOptions{}, fldPath.Child("namespaceSelector"))...)
854 }
855
856 if mc.ObjectSelector == nil {
857 allErrors = append(allErrors, field.Required(fldPath.Child("objectSelector"), ""))
858 } else {
859
860 allErrors = append(allErrors, metav1validation.ValidateLabelSelector(mc.ObjectSelector, metav1validation.LabelSelectorValidationOptions{}, fldPath.Child("objectSelector"))...)
861 }
862
863 for i, namedRuleWithOperations := range mc.ResourceRules {
864 allErrors = append(allErrors, validateNamedRuleWithOperations(&namedRuleWithOperations, fldPath.Child("resourceRules").Index(i))...)
865 }
866
867 for i, namedRuleWithOperations := range mc.ExcludeResourceRules {
868 allErrors = append(allErrors, validateNamedRuleWithOperations(&namedRuleWithOperations, fldPath.Child("excludeResourceRules").Index(i))...)
869 }
870 return allErrors
871 }
872
873 var validValidationActions = sets.NewString(
874 string(admissionregistration.Deny),
875 string(admissionregistration.Warn),
876 string(admissionregistration.Audit),
877 )
878
879 func validateValidationActions(va []admissionregistration.ValidationAction, fldPath *field.Path) field.ErrorList {
880 var allErrors field.ErrorList
881 actions := sets.NewString()
882 for i, action := range va {
883 if !validValidationActions.Has(string(action)) {
884 allErrors = append(allErrors, field.NotSupported(fldPath.Index(i), action, validValidationActions.List()))
885 }
886 if actions.Has(string(action)) {
887 allErrors = append(allErrors, field.Duplicate(fldPath.Index(i), action))
888 }
889 actions.Insert(string(action))
890 }
891 if actions.Has(string(admissionregistration.Deny)) && actions.Has(string(admissionregistration.Warn)) {
892 allErrors = append(allErrors, field.Invalid(fldPath, va, "must not contain both Deny and Warn (repeating the same validation failure information in the API response and headers serves no purpose)"))
893 }
894 if len(actions) == 0 {
895 allErrors = append(allErrors, field.Required(fldPath, "at least one validation action is required"))
896 }
897 return allErrors
898 }
899
900 func validateNamedRuleWithOperations(n *admissionregistration.NamedRuleWithOperations, fldPath *field.Path) field.ErrorList {
901 var allErrors field.ErrorList
902 resourceNames := sets.NewString()
903 for i, rName := range n.ResourceNames {
904 for _, msg := range path.ValidatePathSegmentName(rName, false) {
905 allErrors = append(allErrors, field.Invalid(fldPath.Child("resourceNames").Index(i), rName, msg))
906 }
907 if resourceNames.Has(rName) {
908 allErrors = append(allErrors, field.Duplicate(fldPath.Child("resourceNames").Index(i), rName))
909 } else {
910 resourceNames.Insert(rName)
911 }
912 }
913 allErrors = append(allErrors, validateRuleWithOperations(&n.RuleWithOperations, fldPath)...)
914 return allErrors
915 }
916
917 func validateMatchConditions(m []admissionregistration.MatchCondition, opts validationOptions, fldPath *field.Path) field.ErrorList {
918 var allErrors field.ErrorList
919 conditionNames := sets.NewString()
920 if len(m) > 64 {
921 allErrors = append(allErrors, field.TooMany(fldPath, len(m), 64))
922 }
923 for i, matchCondition := range m {
924 allErrors = append(allErrors, validateMatchCondition(&matchCondition, opts, fldPath.Index(i))...)
925 if len(matchCondition.Name) > 0 {
926 if conditionNames.Has(matchCondition.Name) {
927 allErrors = append(allErrors, field.Duplicate(fldPath.Index(i).Child("name"), matchCondition.Name))
928 } else {
929 conditionNames.Insert(matchCondition.Name)
930 }
931 }
932 }
933 return allErrors
934 }
935
936 func validateMatchCondition(v *admissionregistration.MatchCondition, opts validationOptions, fldPath *field.Path) field.ErrorList {
937 var allErrors field.ErrorList
938 trimmedExpression := strings.TrimSpace(v.Expression)
939 if len(trimmedExpression) == 0 {
940 allErrors = append(allErrors, field.Required(fldPath.Child("expression"), ""))
941 } else {
942 allErrors = append(allErrors, validateMatchConditionsExpression(trimmedExpression, opts, fldPath.Child("expression"))...)
943 }
944 if len(v.Name) == 0 {
945 allErrors = append(allErrors, field.Required(fldPath.Child("name"), ""))
946 } else {
947 allErrors = append(allErrors, apivalidation.ValidateQualifiedName(v.Name, fldPath.Child("name"))...)
948 }
949 return allErrors
950 }
951
952 func validateVariable(compiler plugincel.Compiler, v *admissionregistration.Variable, paramKind *admissionregistration.ParamKind, opts validationOptions, fldPath *field.Path) field.ErrorList {
953 var allErrors field.ErrorList
954 if len(v.Name) == 0 || strings.TrimSpace(v.Name) == "" {
955 allErrors = append(allErrors, field.Required(fldPath.Child("name"), "name is not specified"))
956 } else {
957 if !isCELIdentifier(v.Name) {
958 allErrors = append(allErrors, field.Invalid(fldPath.Child("name"), v.Name, "name is not a valid CEL identifier"))
959 }
960 }
961 if len(v.Expression) == 0 || strings.TrimSpace(v.Expression) == "" {
962 allErrors = append(allErrors, field.Required(fldPath.Child("expression"), "expression is not specified"))
963 } else {
964 if compiler, ok := compiler.(*plugincel.CompositedCompiler); ok {
965 envType := environment.NewExpressions
966 if opts.preexistingExpressions.validationExpressions.Has(v.Expression) {
967 envType = environment.StoredExpressions
968 }
969 variable := &validatingadmissionpolicy.Variable{
970 Name: v.Name,
971 Expression: v.Expression,
972 }
973 result := compiler.CompileAndStoreVariable(variable, plugincel.OptionalVariableDeclarations{
974 HasParams: paramKind != nil,
975 HasAuthorizer: true,
976 }, envType)
977 if result.Error != nil {
978 allErrors = append(allErrors, convertCELErrorToValidationError(fldPath.Child("expression"), variable, result.Error))
979 }
980 } else {
981 allErrors = append(allErrors, field.InternalError(fldPath, fmt.Errorf("variable composition is not allowed")))
982 }
983 }
984 return allErrors
985 }
986
987 func validateValidation(compiler plugincel.Compiler, v *admissionregistration.Validation, paramKind *admissionregistration.ParamKind, opts validationOptions, fldPath *field.Path) field.ErrorList {
988 var allErrors field.ErrorList
989 trimmedExpression := strings.TrimSpace(v.Expression)
990 trimmedMsg := strings.TrimSpace(v.Message)
991 trimmedMessageExpression := strings.TrimSpace(v.MessageExpression)
992 if len(trimmedExpression) == 0 {
993 allErrors = append(allErrors, field.Required(fldPath.Child("expression"), "expression is not specified"))
994 } else {
995 allErrors = append(allErrors, validateValidationExpression(compiler, v.Expression, paramKind != nil, opts, fldPath.Child("expression"))...)
996 }
997 if len(v.MessageExpression) > 0 && len(trimmedMessageExpression) == 0 {
998 allErrors = append(allErrors, field.Invalid(fldPath.Child("messageExpression"), v.MessageExpression, "must be non-empty if specified"))
999 } else if len(trimmedMessageExpression) != 0 {
1000
1001
1002 allErrors = append(allErrors, validateMessageExpression(compiler, v.MessageExpression, opts, fldPath.Child("messageExpression"))...)
1003 }
1004 if len(v.Message) > 0 && len(trimmedMsg) == 0 {
1005 allErrors = append(allErrors, field.Invalid(fldPath.Child("message"), v.Message, "message must be non-empty if specified"))
1006 } else if hasNewlines(trimmedMsg) {
1007 allErrors = append(allErrors, field.Invalid(fldPath.Child("message"), v.Message, "message must not contain line breaks"))
1008 } else if hasNewlines(trimmedMsg) && trimmedMsg == "" {
1009 allErrors = append(allErrors, field.Required(fldPath.Child("message"), "message must be specified if expression contains line breaks"))
1010 }
1011 if v.Reason != nil && !supportedValidationPolicyReason.Has(string(*v.Reason)) {
1012 allErrors = append(allErrors, field.NotSupported(fldPath.Child("reason"), *v.Reason, supportedValidationPolicyReason.List()))
1013 }
1014 return allErrors
1015 }
1016
1017 func validateCELCondition(compiler plugincel.Compiler, expression plugincel.ExpressionAccessor, variables plugincel.OptionalVariableDeclarations, envType environment.Type, fldPath *field.Path) field.ErrorList {
1018 var allErrors field.ErrorList
1019 result := compiler.CompileCELExpression(expression, variables, envType)
1020 if result.Error != nil {
1021 allErrors = append(allErrors, convertCELErrorToValidationError(fldPath, expression, result.Error))
1022 }
1023 return allErrors
1024 }
1025
1026 func convertCELErrorToValidationError(fldPath *field.Path, expression plugincel.ExpressionAccessor, err error) *field.Error {
1027 if celErr, ok := err.(*cel.Error); ok {
1028 switch celErr.Type {
1029 case cel.ErrorTypeRequired:
1030 return field.Required(fldPath, celErr.Detail)
1031 case cel.ErrorTypeInvalid:
1032 return field.Invalid(fldPath, expression.GetExpression(), celErr.Detail)
1033 case cel.ErrorTypeInternal:
1034 return field.InternalError(fldPath, celErr)
1035 }
1036 }
1037 return field.InternalError(fldPath, fmt.Errorf("unsupported error type: %w", err))
1038 }
1039
1040 func validateValidationExpression(compiler plugincel.Compiler, expression string, hasParams bool, opts validationOptions, fldPath *field.Path) field.ErrorList {
1041 envType := environment.NewExpressions
1042 if opts.preexistingExpressions.validationExpressions.Has(expression) {
1043 envType = environment.StoredExpressions
1044 }
1045 return validateCELCondition(compiler, &validatingadmissionpolicy.ValidationCondition{
1046 Expression: expression,
1047 }, plugincel.OptionalVariableDeclarations{
1048 HasParams: hasParams,
1049 HasAuthorizer: true,
1050 }, envType, fldPath)
1051 }
1052
1053 func validateMatchConditionsExpression(expression string, opts validationOptions, fldPath *field.Path) field.ErrorList {
1054 envType := environment.NewExpressions
1055 if opts.preexistingExpressions.matchConditionExpressions.Has(expression) {
1056 envType = environment.StoredExpressions
1057 }
1058 return validateCELCondition(statelessCELCompiler, &matchconditions.MatchCondition{
1059 Expression: expression,
1060 }, plugincel.OptionalVariableDeclarations{
1061 HasParams: opts.allowParamsInMatchConditions,
1062 HasAuthorizer: true,
1063 }, envType, fldPath)
1064 }
1065
1066 func validateMessageExpression(compiler plugincel.Compiler, expression string, opts validationOptions, fldPath *field.Path) field.ErrorList {
1067 envType := environment.NewExpressions
1068 if opts.preexistingExpressions.validationMessageExpressions.Has(expression) {
1069 envType = environment.StoredExpressions
1070 }
1071 return validateCELCondition(compiler, &validatingadmissionpolicy.MessageExpressionCondition{
1072 MessageExpression: expression,
1073 }, plugincel.OptionalVariableDeclarations{
1074 HasParams: opts.allowParamsInMatchConditions,
1075 HasAuthorizer: false,
1076 }, envType, fldPath)
1077 }
1078
1079 func validateAuditAnnotation(compiler plugincel.Compiler, meta metav1.ObjectMeta, v *admissionregistration.AuditAnnotation, paramKind *admissionregistration.ParamKind, opts validationOptions, fldPath *field.Path) field.ErrorList {
1080 var allErrors field.ErrorList
1081 if len(meta.GetName()) != 0 {
1082 name := meta.GetName()
1083 allErrors = append(allErrors, apivalidation.ValidateQualifiedName(name+"/"+v.Key, fldPath.Child("key"))...)
1084 } else {
1085 allErrors = append(allErrors, field.Invalid(fldPath.Child("key"), v.Key, "requires metadata.name be non-empty"))
1086 }
1087
1088 trimmedValueExpression := strings.TrimSpace(v.ValueExpression)
1089 if len(trimmedValueExpression) == 0 {
1090 allErrors = append(allErrors, field.Required(fldPath.Child("valueExpression"), "valueExpression is not specified"))
1091 } else if len(trimmedValueExpression) > maxAuditAnnotationValueExpressionLength {
1092 allErrors = append(allErrors, field.Required(fldPath.Child("valueExpression"), fmt.Sprintf("must not exceed %d bytes in length", maxAuditAnnotationValueExpressionLength)))
1093 } else {
1094 envType := environment.NewExpressions
1095 if opts.preexistingExpressions.auditAnnotationValuesExpressions.Has(v.ValueExpression) {
1096 envType = environment.StoredExpressions
1097 }
1098 result := compiler.CompileCELExpression(&validatingadmissionpolicy.AuditAnnotationCondition{
1099 ValueExpression: trimmedValueExpression,
1100 }, plugincel.OptionalVariableDeclarations{HasParams: paramKind != nil, HasAuthorizer: true}, envType)
1101 if result.Error != nil {
1102 switch result.Error.Type {
1103 case cel.ErrorTypeRequired:
1104 allErrors = append(allErrors, field.Required(fldPath.Child("valueExpression"), result.Error.Detail))
1105 case cel.ErrorTypeInvalid:
1106 allErrors = append(allErrors, field.Invalid(fldPath.Child("valueExpression"), v.ValueExpression, result.Error.Detail))
1107 default:
1108 allErrors = append(allErrors, field.InternalError(fldPath.Child("valueExpression"), result.Error))
1109 }
1110 }
1111 }
1112 return allErrors
1113 }
1114
1115 var newlineMatcher = regexp.MustCompile(`[\n\r]+`)
1116 func hasNewlines(s string) bool {
1117 return newlineMatcher.MatchString(s)
1118 }
1119
1120
1121 func ValidateValidatingAdmissionPolicyBinding(pb *admissionregistration.ValidatingAdmissionPolicyBinding) field.ErrorList {
1122 return validateValidatingAdmissionPolicyBinding(pb)
1123 }
1124
1125 func validateValidatingAdmissionPolicyBinding(pb *admissionregistration.ValidatingAdmissionPolicyBinding) field.ErrorList {
1126 allErrors := genericvalidation.ValidateObjectMeta(&pb.ObjectMeta, false, genericvalidation.NameIsDNSSubdomain, field.NewPath("metadata"))
1127 allErrors = append(allErrors, validateValidatingAdmissionPolicyBindingSpec(&pb.Spec, field.NewPath("spec"))...)
1128
1129 return allErrors
1130 }
1131
1132 func validateValidatingAdmissionPolicyBindingSpec(spec *admissionregistration.ValidatingAdmissionPolicyBindingSpec, fldPath *field.Path) field.ErrorList {
1133 var allErrors field.ErrorList
1134
1135 if len(spec.PolicyName) == 0 {
1136 allErrors = append(allErrors, field.Required(fldPath.Child("policyName"), ""))
1137 } else {
1138 for _, msg := range genericvalidation.NameIsDNSSubdomain(spec.PolicyName, false) {
1139 allErrors = append(allErrors, field.Invalid(fldPath.Child("policyName"), spec.PolicyName, msg))
1140 }
1141 }
1142 allErrors = append(allErrors, validateParamRef(spec.ParamRef, fldPath.Child("paramRef"))...)
1143 allErrors = append(allErrors, validateMatchResources(spec.MatchResources, fldPath.Child("matchResouces"))...)
1144 allErrors = append(allErrors, validateValidationActions(spec.ValidationActions, fldPath.Child("validationActions"))...)
1145
1146 return allErrors
1147 }
1148
1149 func validateParamRef(pr *admissionregistration.ParamRef, fldPath *field.Path) field.ErrorList {
1150 var allErrors field.ErrorList
1151 if pr == nil {
1152 return allErrors
1153 }
1154
1155 if len(pr.Name) > 0 {
1156 for _, msg := range path.ValidatePathSegmentName(pr.Name, false) {
1157 allErrors = append(allErrors, field.Invalid(fldPath.Child("name"), pr.Name, msg))
1158 }
1159
1160 if pr.Selector != nil {
1161 allErrors = append(allErrors, field.Forbidden(fldPath.Child("name"), `name and selector are mutually exclusive`))
1162 }
1163 }
1164
1165 if pr.Selector != nil {
1166 labelSelectorValidationOpts := metav1validation.LabelSelectorValidationOptions{}
1167 allErrors = append(allErrors, metav1validation.ValidateLabelSelector(pr.Selector, labelSelectorValidationOpts, fldPath.Child("selector"))...)
1168
1169 if len(pr.Name) > 0 {
1170 allErrors = append(allErrors, field.Forbidden(fldPath.Child("selector"), `name and selector are mutually exclusive`))
1171 }
1172 }
1173
1174 if len(pr.Name) == 0 && pr.Selector == nil {
1175 allErrors = append(allErrors, field.Required(fldPath, `one of name or selector must be specified`))
1176 }
1177
1178 if pr.ParameterNotFoundAction == nil || len(*pr.ParameterNotFoundAction) == 0 {
1179 allErrors = append(allErrors, field.Required(fldPath.Child("parameterNotFoundAction"), ""))
1180 } else {
1181 if *pr.ParameterNotFoundAction != admissionregistration.DenyAction && *pr.ParameterNotFoundAction != admissionregistration.AllowAction {
1182 allErrors = append(allErrors, field.NotSupported(fldPath.Child("parameterNotFoundAction"), pr.ParameterNotFoundAction, []string{string(admissionregistration.DenyAction), string(admissionregistration.AllowAction)}))
1183 }
1184 }
1185
1186 return allErrors
1187 }
1188
1189
1190 func ValidateValidatingAdmissionPolicyUpdate(newC, oldC *admissionregistration.ValidatingAdmissionPolicy) field.ErrorList {
1191 return validateValidatingAdmissionPolicy(newC, validationOptions{
1192 ignoreMatchConditions: ignoreValidatingAdmissionPolicyMatchConditions(newC, oldC),
1193 preexistingExpressions: findValidatingPolicyPreexistingExpressions(oldC),
1194 })
1195 }
1196
1197
1198 func ValidateValidatingAdmissionPolicyStatusUpdate(newC, oldC *admissionregistration.ValidatingAdmissionPolicy) field.ErrorList {
1199 return validateValidatingAdmissionPolicyStatus(&newC.Status, field.NewPath("status"))
1200 }
1201
1202
1203 func ValidateValidatingAdmissionPolicyBindingUpdate(newC, oldC *admissionregistration.ValidatingAdmissionPolicyBinding) field.ErrorList {
1204 return validateValidatingAdmissionPolicyBinding(newC)
1205 }
1206
1207 func validateValidatingAdmissionPolicyStatus(status *admissionregistration.ValidatingAdmissionPolicyStatus, fldPath *field.Path) field.ErrorList {
1208 var allErrors field.ErrorList
1209 allErrors = append(allErrors, validateTypeChecking(status.TypeChecking, fldPath.Child("typeChecking"))...)
1210 allErrors = append(allErrors, metav1validation.ValidateConditions(status.Conditions, fldPath.Child("conditions"))...)
1211 return allErrors
1212 }
1213
1214 func validateTypeChecking(typeChecking *admissionregistration.TypeChecking, fldPath *field.Path) field.ErrorList {
1215 if typeChecking == nil {
1216 return nil
1217 }
1218 return validateExpressionWarnings(typeChecking.ExpressionWarnings, fldPath.Child("expressionWarnings"))
1219 }
1220
1221 func validateExpressionWarnings(expressionWarnings []admissionregistration.ExpressionWarning, fldPath *field.Path) field.ErrorList {
1222 var allErrors field.ErrorList
1223 for i, warning := range expressionWarnings {
1224 allErrors = append(allErrors, validateExpressionWarning(&warning, fldPath.Index(i))...)
1225 }
1226 return allErrors
1227 }
1228
1229 func validateExpressionWarning(expressionWarning *admissionregistration.ExpressionWarning, fldPath *field.Path) field.ErrorList {
1230 var allErrors field.ErrorList
1231 if expressionWarning.Warning == "" {
1232 allErrors = append(allErrors, field.Required(fldPath.Child("warning"), ""))
1233 }
1234 allErrors = append(allErrors, validateFieldRef(expressionWarning.FieldRef, fldPath.Child("fieldRef"))...)
1235 return allErrors
1236 }
1237
1238 func validateFieldRef(fieldRef string, fldPath *field.Path) field.ErrorList {
1239 fieldRef = strings.TrimSpace(fieldRef)
1240 if fieldRef == "" {
1241 return field.ErrorList{field.Required(fldPath, "")}
1242 }
1243 jsonPath := jsonpath.New("spec")
1244 if err := jsonPath.Parse(fmt.Sprintf("{%s}", fieldRef)); err != nil {
1245 return field.ErrorList{field.Invalid(fldPath, fieldRef, fmt.Sprintf("invalid JSONPath: %v", err))}
1246 }
1247
1248 return nil
1249 }
1250
1251
1252
1253 var statelessCELCompiler = plugincel.NewCompiler(environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion()))
1254
1255 func createCompiler(allowComposition bool) plugincel.Compiler {
1256 if !allowComposition {
1257 return statelessCELCompiler
1258 }
1259 compiler, err := plugincel.NewCompositedCompiler(environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion()))
1260 if err != nil {
1261
1262 utilruntime.HandleError(err)
1263 return plugincel.NewCompiler(environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion()))
1264 }
1265 return compiler
1266 }
1267
1268 var celIdentRegex = regexp.MustCompile("^[_a-zA-Z][_a-zA-Z0-9]*$")
1269 var celReserved = sets.NewString("true", "false", "null", "in",
1270 "as", "break", "const", "continue", "else",
1271 "for", "function", "if", "import", "let",
1272 "loop", "package", "namespace", "return",
1273 "var", "void", "while")
1274
1275 func isCELIdentifier(name string) bool {
1276
1277
1278
1279
1280
1281
1282
1283
1284 return celIdentRegex.MatchString(name) && !celReserved.Has(name)
1285 }
1286
View as plain text