...

Source file src/k8s.io/kubernetes/pkg/apis/admissionregistration/validation/validation.go

Documentation: k8s.io/kubernetes/pkg/apis/admissionregistration/validation

     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/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  	// x/*
    63  	resourcesWithWildcardSubresoures := sets.String{}
    64  	// */x
    65  	subResourcesWithWildcardResource := sets.String{}
    66  	// */*
    67  	hasDoubleWildcard := false
    68  	// *
    69  	hasSingleWildcard := false
    70  	// x
    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  	// Note: group could be empty, e.g., the legacy "v1" API
   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  // AcceptedAdmissionReviewVersions contains the list of AdmissionReview versions the *prior* version of the API server understands.
   169  // 1.15: server understands v1beta1; accepted versions are ["v1beta1"]
   170  // 1.16: server understands v1, v1beta1; accepted versions are ["v1beta1"]
   171  // 1.17+: server understands v1, v1beta1; accepted versions are ["v1","v1beta1"]
   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  	// Currently only v1beta1 accepted in AdmissionReviewVersions
   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  // ValidateValidatingWebhookConfiguration validates a webhook before creation.
   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  // ValidateMutatingWebhookConfiguration validates a webhook before creation.
   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  	// hook.Name must be fully qualified
   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  	// hook.Name must be fully qualified
   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  // mutatingHasAcceptedAdmissionReviewVersions returns true if all webhooks have at least one
   520  // admission review version this apiserver accepts.
   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  // validatingHasAcceptedAdmissionReviewVersions returns true if all webhooks have at least one
   538  // admission review version this apiserver accepts.
   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  // ignoreMatchConditions returns false if any change to match conditions
   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  // ignoreMatchConditions returns true if any new expressions are added
   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  // ignoreValidatingAdmissionPolicyMatchConditions returns true if there have been no updates that could invalidate previously-valid match conditions
   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  // mutatingHasUniqueWebhookNames returns true if all webhooks have unique names
   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  // validatingHasUniqueWebhookNames returns true if all webhooks have unique names
   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  // mutatingHasNoSideEffects returns true if all webhooks have no side effects
   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  // validatingHasNoSideEffects returns true if all webhooks have no side effects
   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  // validatingWebhookAllowInvalidLabelValueInSelector returns true if all webhooksallow invalid label value in selector
   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  // mutatingWebhookAllowInvalidLabelValueInSelector returns true if all webhooks allow invalid label value in selector
   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  // ValidateValidatingWebhookConfigurationUpdate validates update of validating webhook configuration
   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  // ValidateMutatingWebhookConfigurationUpdate validates update of mutating webhook configuration
   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  	// use a 5kb limit the CEL expression, note that this is less than the length limit
   709  	// for the audit annotation value limit (10kb) since an expressions that concatenates
   710  	// strings will often produce a longer value than the expression
   711  	maxAuditAnnotationValueExpressionLength = 5 * 1024
   712  )
   713  
   714  // ValidateValidatingAdmissionPolicy validates a ValidatingAdmissionPolicy before creation.
   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 // composition compiler is stateful, create one lazily per policy
   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  		// at least one resourceRule must be defined to provide type information
   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  		// this matches the APIService group field validation
   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  		// this matches the APIService version field validation
   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  // parseGroupVersion turns "group/version" string into a groupVersion struct. It reports error
   822  // if it cannot parse the string.
   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  		// validate selector strictly, this type was released after issue #99139 was resolved
   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  		// validate selector strictly, this type was released after issue #99139 was resolved
   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  		// use v.MessageExpression instead of trimmedMessageExpression so that
  1001  		// the compiler output shows the correct column.
  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]+`) // valid newline chars in CEL grammar
  1116  func hasNewlines(s string) bool {
  1117  	return newlineMatcher.MatchString(s)
  1118  }
  1119  
  1120  // ValidateValidatingAdmissionPolicyBinding validates a ValidatingAdmissionPolicyBinding before create.
  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  // ValidateValidatingAdmissionPolicyUpdate validates update of validating admission policy
  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  // ValidateValidatingAdmissionPolicyStatusUpdate validates update of status of validating admission policy
  1198  func ValidateValidatingAdmissionPolicyStatusUpdate(newC, oldC *admissionregistration.ValidatingAdmissionPolicy) field.ErrorList {
  1199  	return validateValidatingAdmissionPolicyStatus(&newC.Status, field.NewPath("status"))
  1200  }
  1201  
  1202  // ValidateValidatingAdmissionPolicyBindingUpdate validates update of validating admission policy
  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  	// no further checks, for an easier upgrade/rollback
  1248  	return nil
  1249  }
  1250  
  1251  // statelessCELCompiler does not support variable composition (and thus is stateless). It should be used when
  1252  // variable composition is not allowed, for example, when validating MatchConditions.
  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  		// should never happen, but cannot panic either.
  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  	// IDENT          ::= [_a-zA-Z][_a-zA-Z0-9]* - RESERVED
  1277  	// BOOL_LIT       ::= "true" | "false"
  1278  	// NULL_LIT       ::= "null"
  1279  	// RESERVED       ::= BOOL_LIT | NULL_LIT | "in"
  1280  	// 	 | "as" | "break" | "const" | "continue" | "else"
  1281  	// 	 | "for" | "function" | "if" | "import" | "let"
  1282  	// 	 | "loop" | "package" | "namespace" | "return"
  1283  	// 	 | "var" | "void" | "while"
  1284  	return celIdentRegex.MatchString(name) && !celReserved.Has(name)
  1285  }
  1286  

View as plain text