k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/pkg/apis/admissionregistration/validation/validation.go (about)

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