From c558973fe2d35e94eb9819d58b1825080eeef289 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Cuadrado=20Juan?= Date: Fri, 19 Jul 2024 09:45:47 +0200 Subject: [PATCH 01/10] deps: Add policy_validation_matchconditions.go from kubernetes@v1.30.3 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This file comes from k8s.io/kubernetes (https://github.com/kubernetes/kubernetes), pkg/apis/admissionregistration/validation/validation.go. Signed-off-by: Víctor Cuadrado Juan --- .../v1/policy_validation_matchconditions.go | 1321 +++++++++++++++++ 1 file changed, 1321 insertions(+) create mode 100644 api/policies/v1/policy_validation_matchconditions.go diff --git a/api/policies/v1/policy_validation_matchconditions.go b/api/policies/v1/policy_validation_matchconditions.go new file mode 100644 index 00000000..f254e686 --- /dev/null +++ b/api/policies/v1/policy_validation_matchconditions.go @@ -0,0 +1,1321 @@ +package v1 + +/* +The following code is taken from the Kubernetes source code +at pkg/apis/admissionregistration/validation/validation.go +*/ + +/* +Copyright 2017 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// package validation + +import ( + "fmt" + "reflect" + "regexp" + "strings" + + genericvalidation "k8s.io/apimachinery/pkg/api/validation" + "k8s.io/apimachinery/pkg/api/validation/path" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + metav1validation "k8s.io/apimachinery/pkg/apis/meta/v1/validation" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + "k8s.io/apimachinery/pkg/util/sets" + utilvalidation "k8s.io/apimachinery/pkg/util/validation" + "k8s.io/apimachinery/pkg/util/validation/field" + plugincel "k8s.io/apiserver/pkg/admission/plugin/cel" + validatingadmissionpolicy "k8s.io/apiserver/pkg/admission/plugin/policy/validating" + "k8s.io/apiserver/pkg/admission/plugin/webhook/matchconditions" + "k8s.io/apiserver/pkg/cel" + "k8s.io/apiserver/pkg/cel/environment" + "k8s.io/apiserver/pkg/features" + utilfeature "k8s.io/apiserver/pkg/util/feature" + "k8s.io/apiserver/pkg/util/webhook" + "k8s.io/client-go/util/jsonpath" + + "k8s.io/kubernetes/pkg/apis/admissionregistration" + admissionregistrationv1 "k8s.io/kubernetes/pkg/apis/admissionregistration/v1" + admissionregistrationv1beta1 "k8s.io/kubernetes/pkg/apis/admissionregistration/v1beta1" + apivalidation "k8s.io/kubernetes/pkg/apis/core/validation" +) + +func hasWildcard(slice []string) bool { + for _, s := range slice { + if s == "*" { + return true + } + } + return false +} + +func validateResources(resources []string, fldPath *field.Path) field.ErrorList { + var allErrors field.ErrorList + if len(resources) == 0 { + allErrors = append(allErrors, field.Required(fldPath, "")) + } + + // x/* + resourcesWithWildcardSubresoures := sets.String{} + // */x + subResourcesWithWildcardResource := sets.String{} + // */* + hasDoubleWildcard := false + // * + hasSingleWildcard := false + // x + hasResourceWithoutSubresource := false + + for i, resSub := range resources { + if resSub == "" { + allErrors = append(allErrors, field.Required(fldPath.Index(i), "")) + continue + } + if resSub == "*/*" { + hasDoubleWildcard = true + } + if resSub == "*" { + hasSingleWildcard = true + } + parts := strings.SplitN(resSub, "/", 2) + if len(parts) == 1 { + hasResourceWithoutSubresource = resSub != "*" + continue + } + res, sub := parts[0], parts[1] + if _, ok := resourcesWithWildcardSubresoures[res]; ok { + allErrors = append(allErrors, field.Invalid(fldPath.Index(i), resSub, fmt.Sprintf("if '%s/*' is present, must not specify %s", res, resSub))) + } + if _, ok := subResourcesWithWildcardResource[sub]; ok { + allErrors = append(allErrors, field.Invalid(fldPath.Index(i), resSub, fmt.Sprintf("if '*/%s' is present, must not specify %s", sub, resSub))) + } + if sub == "*" { + resourcesWithWildcardSubresoures[res] = struct{}{} + } + if res == "*" { + subResourcesWithWildcardResource[sub] = struct{}{} + } + } + if len(resources) > 1 && hasDoubleWildcard { + allErrors = append(allErrors, field.Invalid(fldPath, resources, "if '*/*' is present, must not specify other resources")) + } + if hasSingleWildcard && hasResourceWithoutSubresource { + allErrors = append(allErrors, field.Invalid(fldPath, resources, "if '*' is present, must not specify other resources without subresources")) + } + return allErrors +} + +func validateResourcesNoSubResources(resources []string, fldPath *field.Path) field.ErrorList { + var allErrors field.ErrorList + if len(resources) == 0 { + allErrors = append(allErrors, field.Required(fldPath, "")) + } + for i, resource := range resources { + if resource == "" { + allErrors = append(allErrors, field.Required(fldPath.Index(i), "")) + } + if strings.Contains(resource, "/") { + allErrors = append(allErrors, field.Invalid(fldPath.Index(i), resource, "must not specify subresources")) + } + } + if len(resources) > 1 && hasWildcard(resources) { + allErrors = append(allErrors, field.Invalid(fldPath, resources, "if '*' is present, must not specify other resources")) + } + return allErrors +} + +var validScopes = sets.NewString( + string(admissionregistration.ClusterScope), + string(admissionregistration.NamespacedScope), + string(admissionregistration.AllScopes), +) + +func validateRule(rule *admissionregistration.Rule, fldPath *field.Path, allowSubResource bool) field.ErrorList { + var allErrors field.ErrorList + if len(rule.APIGroups) == 0 { + allErrors = append(allErrors, field.Required(fldPath.Child("apiGroups"), "")) + } + if len(rule.APIGroups) > 1 && hasWildcard(rule.APIGroups) { + allErrors = append(allErrors, field.Invalid(fldPath.Child("apiGroups"), rule.APIGroups, "if '*' is present, must not specify other API groups")) + } + // Note: group could be empty, e.g., the legacy "v1" API + if len(rule.APIVersions) == 0 { + allErrors = append(allErrors, field.Required(fldPath.Child("apiVersions"), "")) + } + if len(rule.APIVersions) > 1 && hasWildcard(rule.APIVersions) { + allErrors = append(allErrors, field.Invalid(fldPath.Child("apiVersions"), rule.APIVersions, "if '*' is present, must not specify other API versions")) + } + for i, version := range rule.APIVersions { + if version == "" { + allErrors = append(allErrors, field.Required(fldPath.Child("apiVersions").Index(i), "")) + } + } + if allowSubResource { + allErrors = append(allErrors, validateResources(rule.Resources, fldPath.Child("resources"))...) + } else { + allErrors = append(allErrors, validateResourcesNoSubResources(rule.Resources, fldPath.Child("resources"))...) + } + if rule.Scope != nil && !validScopes.Has(string(*rule.Scope)) { + allErrors = append(allErrors, field.NotSupported(fldPath.Child("scope"), *rule.Scope, validScopes.List())) + } + return allErrors +} + +// AcceptedAdmissionReviewVersions contains the list of AdmissionReview versions the *prior* version of the API server understands. +// 1.15: server understands v1beta1; accepted versions are ["v1beta1"] +// 1.16: server understands v1, v1beta1; accepted versions are ["v1beta1"] +// 1.17+: server understands v1, v1beta1; accepted versions are ["v1","v1beta1"] +var AcceptedAdmissionReviewVersions = []string{admissionregistrationv1.SchemeGroupVersion.Version, admissionregistrationv1beta1.SchemeGroupVersion.Version} + +func isAcceptedAdmissionReviewVersion(v string) bool { + for _, version := range AcceptedAdmissionReviewVersions { + if v == version { + return true + } + } + return false +} + +func validateAdmissionReviewVersions(versions []string, requireRecognizedAdmissionReviewVersion bool, fldPath *field.Path) field.ErrorList { + allErrors := field.ErrorList{} + + // Currently only v1beta1 accepted in AdmissionReviewVersions + if len(versions) < 1 { + allErrors = append(allErrors, field.Required(fldPath, fmt.Sprintf("must specify one of %v", strings.Join(AcceptedAdmissionReviewVersions, ", ")))) + } else { + seen := map[string]bool{} + hasAcceptedVersion := false + for i, v := range versions { + if seen[v] { + allErrors = append(allErrors, field.Invalid(fldPath.Index(i), v, "duplicate version")) + continue + } + seen[v] = true + for _, errString := range utilvalidation.IsDNS1035Label(v) { + allErrors = append(allErrors, field.Invalid(fldPath.Index(i), v, errString)) + } + if isAcceptedAdmissionReviewVersion(v) { + hasAcceptedVersion = true + } + } + if requireRecognizedAdmissionReviewVersion && !hasAcceptedVersion { + allErrors = append(allErrors, field.Invalid( + fldPath, versions, + fmt.Sprintf("must include at least one of %v", + strings.Join(AcceptedAdmissionReviewVersions, ", ")))) + } + } + return allErrors +} + +// ValidateValidatingWebhookConfiguration validates a webhook before creation. +func ValidateValidatingWebhookConfiguration(e *admissionregistration.ValidatingWebhookConfiguration) field.ErrorList { + return validateValidatingWebhookConfiguration(e, validationOptions{ + ignoreMatchConditions: false, + allowParamsInMatchConditions: false, + requireNoSideEffects: true, + requireRecognizedAdmissionReviewVersion: true, + requireUniqueWebhookNames: true, + allowInvalidLabelValueInSelector: false, + strictCostEnforcement: utilfeature.DefaultFeatureGate.Enabled(features.StrictCostEnforcementForWebhooks), + }) +} + +func validateValidatingWebhookConfiguration(e *admissionregistration.ValidatingWebhookConfiguration, opts validationOptions) field.ErrorList { + allErrors := genericvalidation.ValidateObjectMeta(&e.ObjectMeta, false, genericvalidation.NameIsDNSSubdomain, field.NewPath("metadata")) + hookNames := sets.NewString() + for i, hook := range e.Webhooks { + allErrors = append(allErrors, validateValidatingWebhook(&hook, opts, field.NewPath("webhooks").Index(i))...) + allErrors = append(allErrors, validateAdmissionReviewVersions(hook.AdmissionReviewVersions, opts.requireRecognizedAdmissionReviewVersion, field.NewPath("webhooks").Index(i).Child("admissionReviewVersions"))...) + if opts.requireUniqueWebhookNames && len(hook.Name) > 0 { + if hookNames.Has(hook.Name) { + allErrors = append(allErrors, field.Duplicate(field.NewPath("webhooks").Index(i).Child("name"), hook.Name)) + } else { + hookNames.Insert(hook.Name) + } + } + } + return allErrors +} + +// ValidateMutatingWebhookConfiguration validates a webhook before creation. +func ValidateMutatingWebhookConfiguration(e *admissionregistration.MutatingWebhookConfiguration) field.ErrorList { + return validateMutatingWebhookConfiguration(e, validationOptions{ + ignoreMatchConditions: false, + allowParamsInMatchConditions: false, + requireNoSideEffects: true, + requireRecognizedAdmissionReviewVersion: true, + requireUniqueWebhookNames: true, + allowInvalidLabelValueInSelector: false, + strictCostEnforcement: utilfeature.DefaultFeatureGate.Enabled(features.StrictCostEnforcementForWebhooks), + }) +} + +type validationOptions struct { + ignoreMatchConditions bool + allowParamsInMatchConditions bool + requireNoSideEffects bool + requireRecognizedAdmissionReviewVersion bool + requireUniqueWebhookNames bool + allowInvalidLabelValueInSelector bool + preexistingExpressions preexistingExpressions + strictCostEnforcement bool +} + +type preexistingExpressions struct { + matchConditionExpressions sets.Set[string] + validationExpressions sets.Set[string] + validationMessageExpressions sets.Set[string] + auditAnnotationValuesExpressions sets.Set[string] +} + +func newPreexistingExpressions() preexistingExpressions { + return preexistingExpressions{ + matchConditionExpressions: sets.New[string](), + validationExpressions: sets.New[string](), + validationMessageExpressions: sets.New[string](), + auditAnnotationValuesExpressions: sets.New[string](), + } +} + +func findMutatingPreexistingExpressions(mutating *admissionregistration.MutatingWebhookConfiguration) preexistingExpressions { + preexisting := newPreexistingExpressions() + for _, wh := range mutating.Webhooks { + for _, mc := range wh.MatchConditions { + preexisting.matchConditionExpressions.Insert(mc.Expression) + } + } + return preexisting +} + +func findValidatingPreexistingExpressions(validating *admissionregistration.ValidatingWebhookConfiguration) preexistingExpressions { + preexisting := newPreexistingExpressions() + for _, wh := range validating.Webhooks { + for _, mc := range wh.MatchConditions { + preexisting.matchConditionExpressions.Insert(mc.Expression) + } + } + return preexisting +} + +func findValidatingPolicyPreexistingExpressions(validatingPolicy *admissionregistration.ValidatingAdmissionPolicy) preexistingExpressions { + preexisting := newPreexistingExpressions() + for _, mc := range validatingPolicy.Spec.MatchConditions { + preexisting.matchConditionExpressions.Insert(mc.Expression) + } + for _, v := range validatingPolicy.Spec.Validations { + preexisting.validationExpressions.Insert(v.Expression) + if len(v.MessageExpression) > 0 { + preexisting.validationMessageExpressions.Insert(v.MessageExpression) + } + } + for _, a := range validatingPolicy.Spec.AuditAnnotations { + preexisting.auditAnnotationValuesExpressions.Insert(a.ValueExpression) + } + return preexisting +} + +func validateMutatingWebhookConfiguration(e *admissionregistration.MutatingWebhookConfiguration, opts validationOptions) field.ErrorList { + allErrors := genericvalidation.ValidateObjectMeta(&e.ObjectMeta, false, genericvalidation.NameIsDNSSubdomain, field.NewPath("metadata")) + + hookNames := sets.NewString() + for i, hook := range e.Webhooks { + allErrors = append(allErrors, validateMutatingWebhook(&hook, opts, field.NewPath("webhooks").Index(i))...) + allErrors = append(allErrors, validateAdmissionReviewVersions(hook.AdmissionReviewVersions, opts.requireRecognizedAdmissionReviewVersion, field.NewPath("webhooks").Index(i).Child("admissionReviewVersions"))...) + if opts.requireUniqueWebhookNames && len(hook.Name) > 0 { + if hookNames.Has(hook.Name) { + allErrors = append(allErrors, field.Duplicate(field.NewPath("webhooks").Index(i).Child("name"), hook.Name)) + } else { + hookNames.Insert(hook.Name) + } + } + } + return allErrors +} + +func validateValidatingWebhook(hook *admissionregistration.ValidatingWebhook, opts validationOptions, fldPath *field.Path) field.ErrorList { + var allErrors field.ErrorList + // hook.Name must be fully qualified + allErrors = append(allErrors, utilvalidation.IsFullyQualifiedName(fldPath.Child("name"), hook.Name)...) + labelSelectorValidationOpts := metav1validation.LabelSelectorValidationOptions{ + AllowInvalidLabelValueInSelector: opts.allowInvalidLabelValueInSelector, + } + + for i, rule := range hook.Rules { + allErrors = append(allErrors, validateRuleWithOperations(&rule, fldPath.Child("rules").Index(i))...) + } + if hook.FailurePolicy != nil && !supportedFailurePolicies.Has(string(*hook.FailurePolicy)) { + allErrors = append(allErrors, field.NotSupported(fldPath.Child("failurePolicy"), *hook.FailurePolicy, supportedFailurePolicies.List())) + } + if hook.MatchPolicy != nil && !supportedMatchPolicies.Has(string(*hook.MatchPolicy)) { + allErrors = append(allErrors, field.NotSupported(fldPath.Child("matchPolicy"), *hook.MatchPolicy, supportedMatchPolicies.List())) + } + allowedSideEffects := supportedSideEffectClasses + if opts.requireNoSideEffects { + allowedSideEffects = noSideEffectClasses + } + if hook.SideEffects == nil { + allErrors = append(allErrors, field.Required(fldPath.Child("sideEffects"), fmt.Sprintf("must specify one of %v", strings.Join(allowedSideEffects.List(), ", ")))) + } + if hook.SideEffects != nil && !allowedSideEffects.Has(string(*hook.SideEffects)) { + allErrors = append(allErrors, field.NotSupported(fldPath.Child("sideEffects"), *hook.SideEffects, allowedSideEffects.List())) + } + if hook.TimeoutSeconds != nil && (*hook.TimeoutSeconds > 30 || *hook.TimeoutSeconds < 1) { + allErrors = append(allErrors, field.Invalid(fldPath.Child("timeoutSeconds"), *hook.TimeoutSeconds, "the timeout value must be between 1 and 30 seconds")) + } + + if hook.NamespaceSelector != nil { + allErrors = append(allErrors, metav1validation.ValidateLabelSelector(hook.NamespaceSelector, labelSelectorValidationOpts, fldPath.Child("namespaceSelector"))...) + } + + if hook.ObjectSelector != nil { + allErrors = append(allErrors, metav1validation.ValidateLabelSelector(hook.ObjectSelector, labelSelectorValidationOpts, fldPath.Child("objectSelector"))...) + } + + cc := hook.ClientConfig + switch { + case (cc.URL == nil) == (cc.Service == nil): + allErrors = append(allErrors, field.Required(fldPath.Child("clientConfig"), "exactly one of url or service is required")) + case cc.URL != nil: + allErrors = append(allErrors, webhook.ValidateWebhookURL(fldPath.Child("clientConfig").Child("url"), *cc.URL, true)...) + case cc.Service != nil: + allErrors = append(allErrors, webhook.ValidateWebhookService(fldPath.Child("clientConfig").Child("service"), cc.Service.Name, cc.Service.Namespace, cc.Service.Path, cc.Service.Port)...) + } + + if !opts.ignoreMatchConditions { + allErrors = append(allErrors, validateMatchConditions(hook.MatchConditions, opts, fldPath.Child("matchConditions"))...) + } + + return allErrors +} + +func validateMutatingWebhook(hook *admissionregistration.MutatingWebhook, opts validationOptions, fldPath *field.Path) field.ErrorList { + var allErrors field.ErrorList + // hook.Name must be fully qualified + allErrors = append(allErrors, utilvalidation.IsFullyQualifiedName(fldPath.Child("name"), hook.Name)...) + labelSelectorValidationOpts := metav1validation.LabelSelectorValidationOptions{ + AllowInvalidLabelValueInSelector: opts.allowInvalidLabelValueInSelector, + } + + for i, rule := range hook.Rules { + allErrors = append(allErrors, validateRuleWithOperations(&rule, fldPath.Child("rules").Index(i))...) + } + if hook.FailurePolicy != nil && !supportedFailurePolicies.Has(string(*hook.FailurePolicy)) { + allErrors = append(allErrors, field.NotSupported(fldPath.Child("failurePolicy"), *hook.FailurePolicy, supportedFailurePolicies.List())) + } + if hook.MatchPolicy != nil && !supportedMatchPolicies.Has(string(*hook.MatchPolicy)) { + allErrors = append(allErrors, field.NotSupported(fldPath.Child("matchPolicy"), *hook.MatchPolicy, supportedMatchPolicies.List())) + } + allowedSideEffects := supportedSideEffectClasses + if opts.requireNoSideEffects { + allowedSideEffects = noSideEffectClasses + } + if hook.SideEffects == nil { + allErrors = append(allErrors, field.Required(fldPath.Child("sideEffects"), fmt.Sprintf("must specify one of %v", strings.Join(allowedSideEffects.List(), ", ")))) + } + if hook.SideEffects != nil && !allowedSideEffects.Has(string(*hook.SideEffects)) { + allErrors = append(allErrors, field.NotSupported(fldPath.Child("sideEffects"), *hook.SideEffects, allowedSideEffects.List())) + } + if hook.TimeoutSeconds != nil && (*hook.TimeoutSeconds > 30 || *hook.TimeoutSeconds < 1) { + allErrors = append(allErrors, field.Invalid(fldPath.Child("timeoutSeconds"), *hook.TimeoutSeconds, "the timeout value must be between 1 and 30 seconds")) + } + + if hook.NamespaceSelector != nil { + allErrors = append(allErrors, metav1validation.ValidateLabelSelector(hook.NamespaceSelector, labelSelectorValidationOpts, fldPath.Child("namespaceSelector"))...) + } + if hook.ObjectSelector != nil { + allErrors = append(allErrors, metav1validation.ValidateLabelSelector(hook.ObjectSelector, labelSelectorValidationOpts, fldPath.Child("objectSelector"))...) + } + if hook.ReinvocationPolicy != nil && !supportedReinvocationPolicies.Has(string(*hook.ReinvocationPolicy)) { + allErrors = append(allErrors, field.NotSupported(fldPath.Child("reinvocationPolicy"), *hook.ReinvocationPolicy, supportedReinvocationPolicies.List())) + } + + cc := hook.ClientConfig + switch { + case (cc.URL == nil) == (cc.Service == nil): + allErrors = append(allErrors, field.Required(fldPath.Child("clientConfig"), "exactly one of url or service is required")) + case cc.URL != nil: + allErrors = append(allErrors, webhook.ValidateWebhookURL(fldPath.Child("clientConfig").Child("url"), *cc.URL, true)...) + case cc.Service != nil: + allErrors = append(allErrors, webhook.ValidateWebhookService(fldPath.Child("clientConfig").Child("service"), cc.Service.Name, cc.Service.Namespace, cc.Service.Path, cc.Service.Port)...) + } + + if !opts.ignoreMatchConditions { + allErrors = append(allErrors, validateMatchConditions(hook.MatchConditions, opts, fldPath.Child("matchConditions"))...) + } + + return allErrors +} + +var supportedFailurePolicies = sets.NewString( + string(admissionregistration.Ignore), + string(admissionregistration.Fail), +) + +var supportedMatchPolicies = sets.NewString( + string(admissionregistration.Exact), + string(admissionregistration.Equivalent), +) + +var supportedSideEffectClasses = sets.NewString( + string(admissionregistration.SideEffectClassUnknown), + string(admissionregistration.SideEffectClassNone), + string(admissionregistration.SideEffectClassSome), + string(admissionregistration.SideEffectClassNoneOnDryRun), +) + +var noSideEffectClasses = sets.NewString( + string(admissionregistration.SideEffectClassNone), + string(admissionregistration.SideEffectClassNoneOnDryRun), +) + +var supportedOperations = sets.NewString( + string(admissionregistration.OperationAll), + string(admissionregistration.Create), + string(admissionregistration.Update), + string(admissionregistration.Delete), + string(admissionregistration.Connect), +) + +var supportedReinvocationPolicies = sets.NewString( + string(admissionregistration.NeverReinvocationPolicy), + string(admissionregistration.IfNeededReinvocationPolicy), +) + +var supportedValidationPolicyReason = sets.NewString( + string(metav1.StatusReasonForbidden), + string(metav1.StatusReasonInvalid), + string(metav1.StatusReasonRequestEntityTooLarge), +) + +func hasWildcardOperation(operations []admissionregistration.OperationType) bool { + for _, o := range operations { + if o == admissionregistration.OperationAll { + return true + } + } + return false +} + +func validateRuleWithOperations(ruleWithOperations *admissionregistration.RuleWithOperations, fldPath *field.Path) field.ErrorList { + var allErrors field.ErrorList + if len(ruleWithOperations.Operations) == 0 { + allErrors = append(allErrors, field.Required(fldPath.Child("operations"), "")) + } + if len(ruleWithOperations.Operations) > 1 && hasWildcardOperation(ruleWithOperations.Operations) { + allErrors = append(allErrors, field.Invalid(fldPath.Child("operations"), ruleWithOperations.Operations, "if '*' is present, must not specify other operations")) + } + for i, operation := range ruleWithOperations.Operations { + if !supportedOperations.Has(string(operation)) { + allErrors = append(allErrors, field.NotSupported(fldPath.Child("operations").Index(i), operation, supportedOperations.List())) + } + } + allowSubResource := true + allErrors = append(allErrors, validateRule(&ruleWithOperations.Rule, fldPath, allowSubResource)...) + return allErrors +} + +// mutatingHasAcceptedAdmissionReviewVersions returns true if all webhooks have at least one +// admission review version this apiserver accepts. +func mutatingHasAcceptedAdmissionReviewVersions(webhooks []admissionregistration.MutatingWebhook) bool { + for _, hook := range webhooks { + hasRecognizedVersion := false + for _, version := range hook.AdmissionReviewVersions { + if isAcceptedAdmissionReviewVersion(version) { + hasRecognizedVersion = true + break + } + } + if !hasRecognizedVersion && len(hook.AdmissionReviewVersions) > 0 { + return false + } + } + return true +} + +// validatingHasAcceptedAdmissionReviewVersions returns true if all webhooks have at least one +// admission review version this apiserver accepts. +func validatingHasAcceptedAdmissionReviewVersions(webhooks []admissionregistration.ValidatingWebhook) bool { + for _, hook := range webhooks { + hasRecognizedVersion := false + for _, version := range hook.AdmissionReviewVersions { + if isAcceptedAdmissionReviewVersion(version) { + hasRecognizedVersion = true + break + } + } + if !hasRecognizedVersion && len(hook.AdmissionReviewVersions) > 0 { + return false + } + } + return true +} + +// ignoreMatchConditions returns false if any change to match conditions +func ignoreMutatingWebhookMatchConditions(new, old []admissionregistration.MutatingWebhook) bool { + if len(new) != len(old) { + return false + } + for i := range old { + if !reflect.DeepEqual(new[i].MatchConditions, old[i].MatchConditions) { + return false + } + } + + return true +} + +// ignoreMatchConditions returns true if any new expressions are added +func ignoreValidatingWebhookMatchConditions(new, old []admissionregistration.ValidatingWebhook) bool { + if len(new) != len(old) { + return false + } + for i := range old { + if !reflect.DeepEqual(new[i].MatchConditions, old[i].MatchConditions) { + return false + } + } + + return true +} + +// ignoreValidatingAdmissionPolicyMatchConditions returns true if there have been no updates that could invalidate previously-valid match conditions +func ignoreValidatingAdmissionPolicyMatchConditions(new, old *admissionregistration.ValidatingAdmissionPolicy) bool { + if !reflect.DeepEqual(new.Spec.ParamKind, old.Spec.ParamKind) { + return false + } + if !reflect.DeepEqual(new.Spec.MatchConditions, old.Spec.MatchConditions) { + return false + } + return true +} + +// mutatingHasUniqueWebhookNames returns true if all webhooks have unique names +func mutatingHasUniqueWebhookNames(webhooks []admissionregistration.MutatingWebhook) bool { + names := sets.NewString() + for _, hook := range webhooks { + if names.Has(hook.Name) { + return false + } + names.Insert(hook.Name) + } + return true +} + +// validatingHasUniqueWebhookNames returns true if all webhooks have unique names +func validatingHasUniqueWebhookNames(webhooks []admissionregistration.ValidatingWebhook) bool { + names := sets.NewString() + for _, hook := range webhooks { + if names.Has(hook.Name) { + return false + } + names.Insert(hook.Name) + } + return true +} + +// mutatingHasNoSideEffects returns true if all webhooks have no side effects +func mutatingHasNoSideEffects(webhooks []admissionregistration.MutatingWebhook) bool { + for _, hook := range webhooks { + if hook.SideEffects == nil || !noSideEffectClasses.Has(string(*hook.SideEffects)) { + return false + } + } + return true +} + +// validatingHasNoSideEffects returns true if all webhooks have no side effects +func validatingHasNoSideEffects(webhooks []admissionregistration.ValidatingWebhook) bool { + for _, hook := range webhooks { + if hook.SideEffects == nil || !noSideEffectClasses.Has(string(*hook.SideEffects)) { + return false + } + } + return true +} + +// validatingWebhookAllowInvalidLabelValueInSelector returns true if all webhooksallow invalid label value in selector +func validatingWebhookHasInvalidLabelValueInSelector(webhooks []admissionregistration.ValidatingWebhook) bool { + labelSelectorValidationOpts := metav1validation.LabelSelectorValidationOptions{ + AllowInvalidLabelValueInSelector: false, + } + + for _, hook := range webhooks { + if hook.NamespaceSelector != nil { + if len(metav1validation.ValidateLabelSelector(hook.NamespaceSelector, labelSelectorValidationOpts, nil)) > 0 { + return true + } + } + if hook.ObjectSelector != nil { + if len(metav1validation.ValidateLabelSelector(hook.ObjectSelector, labelSelectorValidationOpts, nil)) > 0 { + return true + } + } + } + return false +} + +// mutatingWebhookAllowInvalidLabelValueInSelector returns true if all webhooks allow invalid label value in selector +func mutatingWebhookHasInvalidLabelValueInSelector(webhooks []admissionregistration.MutatingWebhook) bool { + labelSelectorValidationOpts := metav1validation.LabelSelectorValidationOptions{ + AllowInvalidLabelValueInSelector: false, + } + + for _, hook := range webhooks { + if hook.NamespaceSelector != nil { + if len(metav1validation.ValidateLabelSelector(hook.NamespaceSelector, labelSelectorValidationOpts, nil)) > 0 { + return true + } + } + if hook.ObjectSelector != nil { + if len(metav1validation.ValidateLabelSelector(hook.ObjectSelector, labelSelectorValidationOpts, nil)) > 0 { + return true + } + } + } + return false +} + +// ValidateValidatingWebhookConfigurationUpdate validates update of validating webhook configuration +func ValidateValidatingWebhookConfigurationUpdate(newC, oldC *admissionregistration.ValidatingWebhookConfiguration) field.ErrorList { + return validateValidatingWebhookConfiguration(newC, validationOptions{ + ignoreMatchConditions: ignoreValidatingWebhookMatchConditions(newC.Webhooks, oldC.Webhooks), + allowParamsInMatchConditions: false, + requireNoSideEffects: validatingHasNoSideEffects(oldC.Webhooks), + requireRecognizedAdmissionReviewVersion: validatingHasAcceptedAdmissionReviewVersions(oldC.Webhooks), + requireUniqueWebhookNames: validatingHasUniqueWebhookNames(oldC.Webhooks), + allowInvalidLabelValueInSelector: validatingWebhookHasInvalidLabelValueInSelector(oldC.Webhooks), + preexistingExpressions: findValidatingPreexistingExpressions(oldC), + strictCostEnforcement: utilfeature.DefaultFeatureGate.Enabled(features.StrictCostEnforcementForWebhooks), + }) +} + +// ValidateMutatingWebhookConfigurationUpdate validates update of mutating webhook configuration +func ValidateMutatingWebhookConfigurationUpdate(newC, oldC *admissionregistration.MutatingWebhookConfiguration) field.ErrorList { + return validateMutatingWebhookConfiguration(newC, validationOptions{ + ignoreMatchConditions: ignoreMutatingWebhookMatchConditions(newC.Webhooks, oldC.Webhooks), + allowParamsInMatchConditions: false, + requireNoSideEffects: mutatingHasNoSideEffects(oldC.Webhooks), + requireRecognizedAdmissionReviewVersion: mutatingHasAcceptedAdmissionReviewVersions(oldC.Webhooks), + requireUniqueWebhookNames: mutatingHasUniqueWebhookNames(oldC.Webhooks), + allowInvalidLabelValueInSelector: mutatingWebhookHasInvalidLabelValueInSelector(oldC.Webhooks), + preexistingExpressions: findMutatingPreexistingExpressions(oldC), + strictCostEnforcement: utilfeature.DefaultFeatureGate.Enabled(features.StrictCostEnforcementForWebhooks), + }) +} + +const ( + maxAuditAnnotations = 20 + // use a 5kb limit the CEL expression, note that this is less than the length limit + // for the audit annotation value limit (10kb) since an expressions that concatenates + // strings will often produce a longer value than the expression + maxAuditAnnotationValueExpressionLength = 5 * 1024 +) + +// ValidateValidatingAdmissionPolicy validates a ValidatingAdmissionPolicy before creation. +func ValidateValidatingAdmissionPolicy(p *admissionregistration.ValidatingAdmissionPolicy) field.ErrorList { + return validateValidatingAdmissionPolicy(p, validationOptions{ignoreMatchConditions: false, strictCostEnforcement: utilfeature.DefaultFeatureGate.Enabled(features.StrictCostEnforcementForVAP)}) +} + +func validateValidatingAdmissionPolicy(p *admissionregistration.ValidatingAdmissionPolicy, opts validationOptions) field.ErrorList { + allErrors := genericvalidation.ValidateObjectMeta(&p.ObjectMeta, false, genericvalidation.NameIsDNSSubdomain, field.NewPath("metadata")) + allErrors = append(allErrors, validateValidatingAdmissionPolicySpec(p.ObjectMeta, &p.Spec, opts, field.NewPath("spec"))...) + return allErrors +} + +func validateValidatingAdmissionPolicySpec(meta metav1.ObjectMeta, spec *admissionregistration.ValidatingAdmissionPolicySpec, opts validationOptions, fldPath *field.Path) field.ErrorList { + var allErrors field.ErrorList + var compiler plugincel.Compiler // composition compiler is stateful, create one lazily per policy + getCompiler := func() plugincel.Compiler { + if compiler == nil { + needsComposition := len(spec.Variables) > 0 + compiler = createCompiler(needsComposition, opts.strictCostEnforcement) + } + return compiler + } + if spec.FailurePolicy == nil { + allErrors = append(allErrors, field.Required(fldPath.Child("failurePolicy"), "")) + } else if !supportedFailurePolicies.Has(string(*spec.FailurePolicy)) { + allErrors = append(allErrors, field.NotSupported(fldPath.Child("failurePolicy"), *spec.FailurePolicy, supportedFailurePolicies.List())) + } + if spec.ParamKind != nil { + opts.allowParamsInMatchConditions = true + allErrors = append(allErrors, validateParamKind(*spec.ParamKind, fldPath.Child("paramKind"))...) + } + if spec.MatchConstraints == nil { + allErrors = append(allErrors, field.Required(fldPath.Child("matchConstraints"), "")) + } else { + allErrors = append(allErrors, validateMatchResources(spec.MatchConstraints, fldPath.Child("matchConstraints"))...) + // at least one resourceRule must be defined to provide type information + if len(spec.MatchConstraints.ResourceRules) == 0 { + allErrors = append(allErrors, field.Required(fldPath.Child("matchConstraints", "resourceRules"), "")) + } + } + if !opts.ignoreMatchConditions { + allErrors = append(allErrors, validateMatchConditions(spec.MatchConditions, opts, fldPath.Child("matchConditions"))...) + } + if len(spec.Variables) > 0 { + for i, variable := range spec.Variables { + allErrors = append(allErrors, validateVariable(getCompiler(), &variable, spec.ParamKind, opts, fldPath.Child("variables").Index(i))...) + } + } + if len(spec.Validations) == 0 && len(spec.AuditAnnotations) == 0 { + allErrors = append(allErrors, field.Required(fldPath.Child("validations"), "validations or auditAnnotations must contain at least one item")) + allErrors = append(allErrors, field.Required(fldPath.Child("auditAnnotations"), "validations or auditAnnotations must contain at least one item")) + } else { + for i, validation := range spec.Validations { + allErrors = append(allErrors, validateValidation(getCompiler(), &validation, spec.ParamKind, opts, fldPath.Child("validations").Index(i))...) + } + if spec.AuditAnnotations != nil { + keys := sets.NewString() + if len(spec.AuditAnnotations) > maxAuditAnnotations { + allErrors = append(allErrors, field.Invalid(fldPath.Child("auditAnnotations"), spec.AuditAnnotations, fmt.Sprintf("must not have more than %d auditAnnotations", maxAuditAnnotations))) + } + for i, auditAnnotation := range spec.AuditAnnotations { + allErrors = append(allErrors, validateAuditAnnotation(getCompiler(), meta, &auditAnnotation, spec.ParamKind, opts, fldPath.Child("auditAnnotations").Index(i))...) + if keys.Has(auditAnnotation.Key) { + allErrors = append(allErrors, field.Duplicate(fldPath.Child("auditAnnotations").Index(i).Child("key"), auditAnnotation.Key)) + } + keys.Insert(auditAnnotation.Key) + } + } + } + return allErrors +} + +func validateParamKind(gvk admissionregistration.ParamKind, fldPath *field.Path) field.ErrorList { + var allErrors field.ErrorList + if len(gvk.APIVersion) == 0 { + allErrors = append(allErrors, field.Required(fldPath.Child("apiVersion"), "")) + } else if gv, err := parseGroupVersion(gvk.APIVersion); err != nil { + allErrors = append(allErrors, field.Invalid(fldPath.Child("apiVersion"), gvk.APIVersion, err.Error())) + } else { + // this matches the APIService group field validation + if len(gv.Group) > 0 { + if errs := utilvalidation.IsDNS1123Subdomain(gv.Group); len(errs) > 0 { + allErrors = append(allErrors, field.Invalid(fldPath.Child("apiVersion"), gv.Group, strings.Join(errs, ","))) + } + } + // this matches the APIService version field validation + if len(gv.Version) == 0 { + allErrors = append(allErrors, field.Invalid(fldPath.Child("apiVersion"), gvk.APIVersion, "version must be specified")) + } else { + if errs := utilvalidation.IsDNS1035Label(gv.Version); len(errs) > 0 { + allErrors = append(allErrors, field.Invalid(fldPath.Child("apiVersion"), gv.Version, strings.Join(errs, ","))) + } + } + } + if len(gvk.Kind) == 0 { + allErrors = append(allErrors, field.Required(fldPath.Child("kind"), "")) + } else if errs := utilvalidation.IsDNS1035Label(strings.ToLower(gvk.Kind)); len(errs) > 0 { + allErrors = append(allErrors, field.Invalid(fldPath.Child("kind"), gvk.Kind, "may have mixed case, but should otherwise match: "+strings.Join(errs, ","))) + } + + return allErrors +} + +type groupVersion struct { + Group string + Version string +} + +// parseGroupVersion turns "group/version" string into a groupVersion struct. It reports error +// if it cannot parse the string. +func parseGroupVersion(gv string) (groupVersion, error) { + if (len(gv) == 0) || (gv == "/") { + return groupVersion{}, nil + } + + switch strings.Count(gv, "/") { + case 0: + return groupVersion{"", gv}, nil + case 1: + i := strings.Index(gv, "/") + return groupVersion{gv[:i], gv[i+1:]}, nil + default: + return groupVersion{}, fmt.Errorf("unexpected GroupVersion string: %v", gv) + } +} + +func validateMatchResources(mc *admissionregistration.MatchResources, fldPath *field.Path) field.ErrorList { + var allErrors field.ErrorList + if mc == nil { + return allErrors + } + if mc.MatchPolicy == nil { + allErrors = append(allErrors, field.Required(fldPath.Child("matchPolicy"), "")) + } else if !supportedMatchPolicies.Has(string(*mc.MatchPolicy)) { + allErrors = append(allErrors, field.NotSupported(fldPath.Child("matchPolicy"), *mc.MatchPolicy, supportedMatchPolicies.List())) + } + if mc.NamespaceSelector == nil { + allErrors = append(allErrors, field.Required(fldPath.Child("namespaceSelector"), "")) + } else { + // validate selector strictly, this type was released after issue #99139 was resolved + allErrors = append(allErrors, metav1validation.ValidateLabelSelector(mc.NamespaceSelector, metav1validation.LabelSelectorValidationOptions{}, fldPath.Child("namespaceSelector"))...) + } + + if mc.ObjectSelector == nil { + allErrors = append(allErrors, field.Required(fldPath.Child("objectSelector"), "")) + } else { + // validate selector strictly, this type was released after issue #99139 was resolved + allErrors = append(allErrors, metav1validation.ValidateLabelSelector(mc.ObjectSelector, metav1validation.LabelSelectorValidationOptions{}, fldPath.Child("objectSelector"))...) + } + + for i, namedRuleWithOperations := range mc.ResourceRules { + allErrors = append(allErrors, validateNamedRuleWithOperations(&namedRuleWithOperations, fldPath.Child("resourceRules").Index(i))...) + } + + for i, namedRuleWithOperations := range mc.ExcludeResourceRules { + allErrors = append(allErrors, validateNamedRuleWithOperations(&namedRuleWithOperations, fldPath.Child("excludeResourceRules").Index(i))...) + } + return allErrors +} + +var validValidationActions = sets.NewString( + string(admissionregistration.Deny), + string(admissionregistration.Warn), + string(admissionregistration.Audit), +) + +func validateValidationActions(va []admissionregistration.ValidationAction, fldPath *field.Path) field.ErrorList { + var allErrors field.ErrorList + actions := sets.NewString() + for i, action := range va { + if !validValidationActions.Has(string(action)) { + allErrors = append(allErrors, field.NotSupported(fldPath.Index(i), action, validValidationActions.List())) + } + if actions.Has(string(action)) { + allErrors = append(allErrors, field.Duplicate(fldPath.Index(i), action)) + } + actions.Insert(string(action)) + } + if actions.Has(string(admissionregistration.Deny)) && actions.Has(string(admissionregistration.Warn)) { + 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)")) + } + if len(actions) == 0 { + allErrors = append(allErrors, field.Required(fldPath, "at least one validation action is required")) + } + return allErrors +} + +func validateNamedRuleWithOperations(n *admissionregistration.NamedRuleWithOperations, fldPath *field.Path) field.ErrorList { + var allErrors field.ErrorList + resourceNames := sets.NewString() + for i, rName := range n.ResourceNames { + for _, msg := range path.ValidatePathSegmentName(rName, false) { + allErrors = append(allErrors, field.Invalid(fldPath.Child("resourceNames").Index(i), rName, msg)) + } + if resourceNames.Has(rName) { + allErrors = append(allErrors, field.Duplicate(fldPath.Child("resourceNames").Index(i), rName)) + } else { + resourceNames.Insert(rName) + } + } + allErrors = append(allErrors, validateRuleWithOperations(&n.RuleWithOperations, fldPath)...) + return allErrors +} + +func validateMatchConditions(m []admissionregistration.MatchCondition, opts validationOptions, fldPath *field.Path) field.ErrorList { + var allErrors field.ErrorList + conditionNames := sets.NewString() + if len(m) > 64 { + allErrors = append(allErrors, field.TooMany(fldPath, len(m), 64)) + } + for i, matchCondition := range m { + allErrors = append(allErrors, validateMatchCondition(&matchCondition, opts, fldPath.Index(i))...) + if len(matchCondition.Name) > 0 { + if conditionNames.Has(matchCondition.Name) { + allErrors = append(allErrors, field.Duplicate(fldPath.Index(i).Child("name"), matchCondition.Name)) + } else { + conditionNames.Insert(matchCondition.Name) + } + } + } + return allErrors +} + +func validateMatchCondition(v *admissionregistration.MatchCondition, opts validationOptions, fldPath *field.Path) field.ErrorList { + var allErrors field.ErrorList + trimmedExpression := strings.TrimSpace(v.Expression) + if len(trimmedExpression) == 0 { + allErrors = append(allErrors, field.Required(fldPath.Child("expression"), "")) + } else { + allErrors = append(allErrors, validateMatchConditionsExpression(trimmedExpression, opts, fldPath.Child("expression"))...) + } + if len(v.Name) == 0 { + allErrors = append(allErrors, field.Required(fldPath.Child("name"), "")) + } else { + allErrors = append(allErrors, apivalidation.ValidateQualifiedName(v.Name, fldPath.Child("name"))...) + } + return allErrors +} + +func validateVariable(compiler plugincel.Compiler, v *admissionregistration.Variable, paramKind *admissionregistration.ParamKind, opts validationOptions, fldPath *field.Path) field.ErrorList { + var allErrors field.ErrorList + if len(v.Name) == 0 || strings.TrimSpace(v.Name) == "" { + allErrors = append(allErrors, field.Required(fldPath.Child("name"), "name is not specified")) + } else { + if !isCELIdentifier(v.Name) { + allErrors = append(allErrors, field.Invalid(fldPath.Child("name"), v.Name, "name is not a valid CEL identifier")) + } + } + if len(v.Expression) == 0 || strings.TrimSpace(v.Expression) == "" { + allErrors = append(allErrors, field.Required(fldPath.Child("expression"), "expression is not specified")) + } else { + if compiler, ok := compiler.(*plugincel.CompositedCompiler); ok { + envType := environment.NewExpressions + if opts.preexistingExpressions.validationExpressions.Has(v.Expression) { + envType = environment.StoredExpressions + } + variable := &validatingadmissionpolicy.Variable{ + Name: v.Name, + Expression: v.Expression, + } + result := compiler.CompileAndStoreVariable(variable, plugincel.OptionalVariableDeclarations{ + HasParams: paramKind != nil, + HasAuthorizer: true, + StrictCost: opts.strictCostEnforcement, + }, envType) + if result.Error != nil { + allErrors = append(allErrors, convertCELErrorToValidationError(fldPath.Child("expression"), variable, result.Error)) + } + } else { + allErrors = append(allErrors, field.InternalError(fldPath, fmt.Errorf("variable composition is not allowed"))) + } + } + return allErrors +} + +func validateValidation(compiler plugincel.Compiler, v *admissionregistration.Validation, paramKind *admissionregistration.ParamKind, opts validationOptions, fldPath *field.Path) field.ErrorList { + var allErrors field.ErrorList + trimmedExpression := strings.TrimSpace(v.Expression) + trimmedMsg := strings.TrimSpace(v.Message) + trimmedMessageExpression := strings.TrimSpace(v.MessageExpression) + if len(trimmedExpression) == 0 { + allErrors = append(allErrors, field.Required(fldPath.Child("expression"), "expression is not specified")) + } else { + allErrors = append(allErrors, validateValidationExpression(compiler, v.Expression, paramKind != nil, opts, fldPath.Child("expression"))...) + } + if len(v.MessageExpression) > 0 && len(trimmedMessageExpression) == 0 { + allErrors = append(allErrors, field.Invalid(fldPath.Child("messageExpression"), v.MessageExpression, "must be non-empty if specified")) + } else if len(trimmedMessageExpression) != 0 { + // use v.MessageExpression instead of trimmedMessageExpression so that + // the compiler output shows the correct column. + allErrors = append(allErrors, validateMessageExpression(compiler, v.MessageExpression, opts, fldPath.Child("messageExpression"))...) + } + if len(v.Message) > 0 && len(trimmedMsg) == 0 { + allErrors = append(allErrors, field.Invalid(fldPath.Child("message"), v.Message, "message must be non-empty if specified")) + } else if hasNewlines(trimmedMsg) { + allErrors = append(allErrors, field.Invalid(fldPath.Child("message"), v.Message, "message must not contain line breaks")) + } else if hasNewlines(trimmedMsg) && trimmedMsg == "" { + allErrors = append(allErrors, field.Required(fldPath.Child("message"), "message must be specified if expression contains line breaks")) + } + if v.Reason != nil && !supportedValidationPolicyReason.Has(string(*v.Reason)) { + allErrors = append(allErrors, field.NotSupported(fldPath.Child("reason"), *v.Reason, supportedValidationPolicyReason.List())) + } + return allErrors +} + +func validateCELCondition(compiler plugincel.Compiler, expression plugincel.ExpressionAccessor, variables plugincel.OptionalVariableDeclarations, envType environment.Type, fldPath *field.Path) field.ErrorList { + var allErrors field.ErrorList + result := compiler.CompileCELExpression(expression, variables, envType) + if result.Error != nil { + allErrors = append(allErrors, convertCELErrorToValidationError(fldPath, expression, result.Error)) + } + return allErrors +} + +func convertCELErrorToValidationError(fldPath *field.Path, expression plugincel.ExpressionAccessor, err error) *field.Error { + if celErr, ok := err.(*cel.Error); ok { + switch celErr.Type { + case cel.ErrorTypeRequired: + return field.Required(fldPath, celErr.Detail) + case cel.ErrorTypeInvalid: + return field.Invalid(fldPath, expression.GetExpression(), celErr.Detail) + case cel.ErrorTypeInternal: + return field.InternalError(fldPath, celErr) + } + } + return field.InternalError(fldPath, fmt.Errorf("unsupported error type: %w", err)) +} + +func validateValidationExpression(compiler plugincel.Compiler, expression string, hasParams bool, opts validationOptions, fldPath *field.Path) field.ErrorList { + envType := environment.NewExpressions + if opts.preexistingExpressions.validationExpressions.Has(expression) { + envType = environment.StoredExpressions + } + return validateCELCondition(compiler, &validatingadmissionpolicy.ValidationCondition{ + Expression: expression, + }, plugincel.OptionalVariableDeclarations{ + HasParams: hasParams, + HasAuthorizer: true, + StrictCost: opts.strictCostEnforcement, + }, envType, fldPath) +} + +func validateMatchConditionsExpression(expression string, opts validationOptions, fldPath *field.Path) field.ErrorList { + envType := environment.NewExpressions + if opts.preexistingExpressions.matchConditionExpressions.Has(expression) { + envType = environment.StoredExpressions + } + var compiler plugincel.Compiler + if opts.strictCostEnforcement { + compiler = strictStatelessCELCompiler + } else { + compiler = nonStrictStatelessCELCompiler + } + return validateCELCondition(compiler, &matchconditions.MatchCondition{ + Expression: expression, + }, plugincel.OptionalVariableDeclarations{ + HasParams: opts.allowParamsInMatchConditions, + HasAuthorizer: true, + StrictCost: opts.strictCostEnforcement, + }, envType, fldPath) +} + +func validateMessageExpression(compiler plugincel.Compiler, expression string, opts validationOptions, fldPath *field.Path) field.ErrorList { + envType := environment.NewExpressions + if opts.preexistingExpressions.validationMessageExpressions.Has(expression) { + envType = environment.StoredExpressions + } + return validateCELCondition(compiler, &validatingadmissionpolicy.MessageExpressionCondition{ + MessageExpression: expression, + }, plugincel.OptionalVariableDeclarations{ + HasParams: opts.allowParamsInMatchConditions, + HasAuthorizer: false, + StrictCost: opts.strictCostEnforcement, + }, envType, fldPath) +} + +func validateAuditAnnotation(compiler plugincel.Compiler, meta metav1.ObjectMeta, v *admissionregistration.AuditAnnotation, paramKind *admissionregistration.ParamKind, opts validationOptions, fldPath *field.Path) field.ErrorList { + var allErrors field.ErrorList + if len(meta.GetName()) != 0 { + name := meta.GetName() + allErrors = append(allErrors, apivalidation.ValidateQualifiedName(name+"/"+v.Key, fldPath.Child("key"))...) + } else { + allErrors = append(allErrors, field.Invalid(fldPath.Child("key"), v.Key, "requires metadata.name be non-empty")) + } + + trimmedValueExpression := strings.TrimSpace(v.ValueExpression) + if len(trimmedValueExpression) == 0 { + allErrors = append(allErrors, field.Required(fldPath.Child("valueExpression"), "valueExpression is not specified")) + } else if len(trimmedValueExpression) > maxAuditAnnotationValueExpressionLength { + allErrors = append(allErrors, field.Required(fldPath.Child("valueExpression"), fmt.Sprintf("must not exceed %d bytes in length", maxAuditAnnotationValueExpressionLength))) + } else { + envType := environment.NewExpressions + if opts.preexistingExpressions.auditAnnotationValuesExpressions.Has(v.ValueExpression) { + envType = environment.StoredExpressions + } + result := compiler.CompileCELExpression(&validatingadmissionpolicy.AuditAnnotationCondition{ + ValueExpression: trimmedValueExpression, + }, plugincel.OptionalVariableDeclarations{HasParams: paramKind != nil, HasAuthorizer: true, StrictCost: opts.strictCostEnforcement}, envType) + if result.Error != nil { + switch result.Error.Type { + case cel.ErrorTypeRequired: + allErrors = append(allErrors, field.Required(fldPath.Child("valueExpression"), result.Error.Detail)) + case cel.ErrorTypeInvalid: + allErrors = append(allErrors, field.Invalid(fldPath.Child("valueExpression"), v.ValueExpression, result.Error.Detail)) + default: + allErrors = append(allErrors, field.InternalError(fldPath.Child("valueExpression"), result.Error)) + } + } + } + return allErrors +} + +var newlineMatcher = regexp.MustCompile(`[\n\r]+`) // valid newline chars in CEL grammar +func hasNewlines(s string) bool { + return newlineMatcher.MatchString(s) +} + +// ValidateValidatingAdmissionPolicyBinding validates a ValidatingAdmissionPolicyBinding before create. +func ValidateValidatingAdmissionPolicyBinding(pb *admissionregistration.ValidatingAdmissionPolicyBinding) field.ErrorList { + return validateValidatingAdmissionPolicyBinding(pb) +} + +func validateValidatingAdmissionPolicyBinding(pb *admissionregistration.ValidatingAdmissionPolicyBinding) field.ErrorList { + allErrors := genericvalidation.ValidateObjectMeta(&pb.ObjectMeta, false, genericvalidation.NameIsDNSSubdomain, field.NewPath("metadata")) + allErrors = append(allErrors, validateValidatingAdmissionPolicyBindingSpec(&pb.Spec, field.NewPath("spec"))...) + + return allErrors +} + +func validateValidatingAdmissionPolicyBindingSpec(spec *admissionregistration.ValidatingAdmissionPolicyBindingSpec, fldPath *field.Path) field.ErrorList { + var allErrors field.ErrorList + + if len(spec.PolicyName) == 0 { + allErrors = append(allErrors, field.Required(fldPath.Child("policyName"), "")) + } else { + for _, msg := range genericvalidation.NameIsDNSSubdomain(spec.PolicyName, false) { + allErrors = append(allErrors, field.Invalid(fldPath.Child("policyName"), spec.PolicyName, msg)) + } + } + allErrors = append(allErrors, validateParamRef(spec.ParamRef, fldPath.Child("paramRef"))...) + allErrors = append(allErrors, validateMatchResources(spec.MatchResources, fldPath.Child("matchResouces"))...) + allErrors = append(allErrors, validateValidationActions(spec.ValidationActions, fldPath.Child("validationActions"))...) + + return allErrors +} + +func validateParamRef(pr *admissionregistration.ParamRef, fldPath *field.Path) field.ErrorList { + var allErrors field.ErrorList + if pr == nil { + return allErrors + } + + if len(pr.Name) > 0 { + for _, msg := range path.ValidatePathSegmentName(pr.Name, false) { + allErrors = append(allErrors, field.Invalid(fldPath.Child("name"), pr.Name, msg)) + } + + if pr.Selector != nil { + allErrors = append(allErrors, field.Forbidden(fldPath.Child("name"), `name and selector are mutually exclusive`)) + } + } + + if pr.Selector != nil { + labelSelectorValidationOpts := metav1validation.LabelSelectorValidationOptions{} + allErrors = append(allErrors, metav1validation.ValidateLabelSelector(pr.Selector, labelSelectorValidationOpts, fldPath.Child("selector"))...) + + if len(pr.Name) > 0 { + allErrors = append(allErrors, field.Forbidden(fldPath.Child("selector"), `name and selector are mutually exclusive`)) + } + } + + if len(pr.Name) == 0 && pr.Selector == nil { + allErrors = append(allErrors, field.Required(fldPath, `one of name or selector must be specified`)) + } + + if pr.ParameterNotFoundAction == nil || len(*pr.ParameterNotFoundAction) == 0 { + allErrors = append(allErrors, field.Required(fldPath.Child("parameterNotFoundAction"), "")) + } else { + if *pr.ParameterNotFoundAction != admissionregistration.DenyAction && *pr.ParameterNotFoundAction != admissionregistration.AllowAction { + allErrors = append(allErrors, field.NotSupported(fldPath.Child("parameterNotFoundAction"), pr.ParameterNotFoundAction, []string{string(admissionregistration.DenyAction), string(admissionregistration.AllowAction)})) + } + } + + return allErrors +} + +// ValidateValidatingAdmissionPolicyUpdate validates update of validating admission policy +func ValidateValidatingAdmissionPolicyUpdate(newC, oldC *admissionregistration.ValidatingAdmissionPolicy) field.ErrorList { + return validateValidatingAdmissionPolicy(newC, validationOptions{ + ignoreMatchConditions: ignoreValidatingAdmissionPolicyMatchConditions(newC, oldC), + preexistingExpressions: findValidatingPolicyPreexistingExpressions(oldC), + strictCostEnforcement: utilfeature.DefaultFeatureGate.Enabled(features.StrictCostEnforcementForVAP), + }) +} + +// ValidateValidatingAdmissionPolicyStatusUpdate validates update of status of validating admission policy +func ValidateValidatingAdmissionPolicyStatusUpdate(newC, oldC *admissionregistration.ValidatingAdmissionPolicy) field.ErrorList { + return validateValidatingAdmissionPolicyStatus(&newC.Status, field.NewPath("status")) +} + +// ValidateValidatingAdmissionPolicyBindingUpdate validates update of validating admission policy +func ValidateValidatingAdmissionPolicyBindingUpdate(newC, oldC *admissionregistration.ValidatingAdmissionPolicyBinding) field.ErrorList { + return validateValidatingAdmissionPolicyBinding(newC) +} + +func validateValidatingAdmissionPolicyStatus(status *admissionregistration.ValidatingAdmissionPolicyStatus, fldPath *field.Path) field.ErrorList { + var allErrors field.ErrorList + allErrors = append(allErrors, validateTypeChecking(status.TypeChecking, fldPath.Child("typeChecking"))...) + allErrors = append(allErrors, metav1validation.ValidateConditions(status.Conditions, fldPath.Child("conditions"))...) + return allErrors +} + +func validateTypeChecking(typeChecking *admissionregistration.TypeChecking, fldPath *field.Path) field.ErrorList { + if typeChecking == nil { + return nil + } + return validateExpressionWarnings(typeChecking.ExpressionWarnings, fldPath.Child("expressionWarnings")) +} + +func validateExpressionWarnings(expressionWarnings []admissionregistration.ExpressionWarning, fldPath *field.Path) field.ErrorList { + var allErrors field.ErrorList + for i, warning := range expressionWarnings { + allErrors = append(allErrors, validateExpressionWarning(&warning, fldPath.Index(i))...) + } + return allErrors +} + +func validateExpressionWarning(expressionWarning *admissionregistration.ExpressionWarning, fldPath *field.Path) field.ErrorList { + var allErrors field.ErrorList + if expressionWarning.Warning == "" { + allErrors = append(allErrors, field.Required(fldPath.Child("warning"), "")) + } + allErrors = append(allErrors, validateFieldRef(expressionWarning.FieldRef, fldPath.Child("fieldRef"))...) + return allErrors +} + +func validateFieldRef(fieldRef string, fldPath *field.Path) field.ErrorList { + fieldRef = strings.TrimSpace(fieldRef) + if fieldRef == "" { + return field.ErrorList{field.Required(fldPath, "")} + } + jsonPath := jsonpath.New("spec") + if err := jsonPath.Parse(fmt.Sprintf("{%s}", fieldRef)); err != nil { + return field.ErrorList{field.Invalid(fldPath, fieldRef, fmt.Sprintf("invalid JSONPath: %v", err))} + } + // no further checks, for an easier upgrade/rollback + return nil +} + +// statelessCELCompiler does not support variable composition (and thus is stateless). It should be used when +// variable composition is not allowed, for example, when validating MatchConditions. +// strictStatelessCELCompiler is a cel Compiler that enforces strict cost enforcement. +// nonStrictStatelessCELCompiler is a cel Compiler that does not enforce strict cost enforcement. +var ( + strictStatelessCELCompiler = plugincel.NewCompiler(environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion(), true)) + nonStrictStatelessCELCompiler = plugincel.NewCompiler(environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion(), false)) +) + +func createCompiler(allowComposition, strictCost bool) plugincel.Compiler { + if !allowComposition { + if strictCost { + return strictStatelessCELCompiler + } else { + return nonStrictStatelessCELCompiler + } + } + compiler, err := plugincel.NewCompositedCompiler(environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion(), strictCost)) + if err != nil { + // should never happen, but cannot panic either. + utilruntime.HandleError(err) + return plugincel.NewCompiler(environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion(), strictCost)) + } + return compiler +} + +var ( + celIdentRegex = regexp.MustCompile("^[_a-zA-Z][_a-zA-Z0-9]*$") + celReserved = sets.NewString("true", "false", "null", "in", + "as", "break", "const", "continue", "else", + "for", "function", "if", "import", "let", + "loop", "package", "namespace", "return", + "var", "void", "while") +) + +func isCELIdentifier(name string) bool { + // IDENT ::= [_a-zA-Z][_a-zA-Z0-9]* - RESERVED + // BOOL_LIT ::= "true" | "false" + // NULL_LIT ::= "null" + // RESERVED ::= BOOL_LIT | NULL_LIT | "in" + // | "as" | "break" | "const" | "continue" | "else" + // | "for" | "function" | "if" | "import" | "let" + // | "loop" | "package" | "namespace" | "return" + // | "var" | "void" | "while" + return celIdentRegex.MatchString(name) && !celReserved.Has(name) +} From 5ddd27d97bcabd46fbb33148049418f5cab3b19f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Cuadrado=20Juan?= Date: Fri, 19 Jul 2024 11:04:37 +0200 Subject: [PATCH 02/10] chore: Drop all uneeded code from validation_matchconditions.go MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is needed because the majority of it is using internal k8s.io/kubernetes packages that aren't supported for importing (those with version @v0.0.0). Signed-off-by: Víctor Cuadrado Juan --- .../v1/policy_validation_matchconditions.go | 1183 +---------------- 1 file changed, 1 insertion(+), 1182 deletions(-) diff --git a/api/policies/v1/policy_validation_matchconditions.go b/api/policies/v1/policy_validation_matchconditions.go index f254e686..42cdd1e4 100644 --- a/api/policies/v1/policy_validation_matchconditions.go +++ b/api/policies/v1/policy_validation_matchconditions.go @@ -25,245 +25,18 @@ limitations under the License. import ( "fmt" - "reflect" - "regexp" "strings" - genericvalidation "k8s.io/apimachinery/pkg/api/validation" - "k8s.io/apimachinery/pkg/api/validation/path" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - metav1validation "k8s.io/apimachinery/pkg/apis/meta/v1/validation" - utilruntime "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/apimachinery/pkg/util/sets" - utilvalidation "k8s.io/apimachinery/pkg/util/validation" "k8s.io/apimachinery/pkg/util/validation/field" plugincel "k8s.io/apiserver/pkg/admission/plugin/cel" - validatingadmissionpolicy "k8s.io/apiserver/pkg/admission/plugin/policy/validating" "k8s.io/apiserver/pkg/admission/plugin/webhook/matchconditions" "k8s.io/apiserver/pkg/cel" "k8s.io/apiserver/pkg/cel/environment" - "k8s.io/apiserver/pkg/features" - utilfeature "k8s.io/apiserver/pkg/util/feature" - "k8s.io/apiserver/pkg/util/webhook" - "k8s.io/client-go/util/jsonpath" - "k8s.io/kubernetes/pkg/apis/admissionregistration" - admissionregistrationv1 "k8s.io/kubernetes/pkg/apis/admissionregistration/v1" - admissionregistrationv1beta1 "k8s.io/kubernetes/pkg/apis/admissionregistration/v1beta1" apivalidation "k8s.io/kubernetes/pkg/apis/core/validation" ) -func hasWildcard(slice []string) bool { - for _, s := range slice { - if s == "*" { - return true - } - } - return false -} - -func validateResources(resources []string, fldPath *field.Path) field.ErrorList { - var allErrors field.ErrorList - if len(resources) == 0 { - allErrors = append(allErrors, field.Required(fldPath, "")) - } - - // x/* - resourcesWithWildcardSubresoures := sets.String{} - // */x - subResourcesWithWildcardResource := sets.String{} - // */* - hasDoubleWildcard := false - // * - hasSingleWildcard := false - // x - hasResourceWithoutSubresource := false - - for i, resSub := range resources { - if resSub == "" { - allErrors = append(allErrors, field.Required(fldPath.Index(i), "")) - continue - } - if resSub == "*/*" { - hasDoubleWildcard = true - } - if resSub == "*" { - hasSingleWildcard = true - } - parts := strings.SplitN(resSub, "/", 2) - if len(parts) == 1 { - hasResourceWithoutSubresource = resSub != "*" - continue - } - res, sub := parts[0], parts[1] - if _, ok := resourcesWithWildcardSubresoures[res]; ok { - allErrors = append(allErrors, field.Invalid(fldPath.Index(i), resSub, fmt.Sprintf("if '%s/*' is present, must not specify %s", res, resSub))) - } - if _, ok := subResourcesWithWildcardResource[sub]; ok { - allErrors = append(allErrors, field.Invalid(fldPath.Index(i), resSub, fmt.Sprintf("if '*/%s' is present, must not specify %s", sub, resSub))) - } - if sub == "*" { - resourcesWithWildcardSubresoures[res] = struct{}{} - } - if res == "*" { - subResourcesWithWildcardResource[sub] = struct{}{} - } - } - if len(resources) > 1 && hasDoubleWildcard { - allErrors = append(allErrors, field.Invalid(fldPath, resources, "if '*/*' is present, must not specify other resources")) - } - if hasSingleWildcard && hasResourceWithoutSubresource { - allErrors = append(allErrors, field.Invalid(fldPath, resources, "if '*' is present, must not specify other resources without subresources")) - } - return allErrors -} - -func validateResourcesNoSubResources(resources []string, fldPath *field.Path) field.ErrorList { - var allErrors field.ErrorList - if len(resources) == 0 { - allErrors = append(allErrors, field.Required(fldPath, "")) - } - for i, resource := range resources { - if resource == "" { - allErrors = append(allErrors, field.Required(fldPath.Index(i), "")) - } - if strings.Contains(resource, "/") { - allErrors = append(allErrors, field.Invalid(fldPath.Index(i), resource, "must not specify subresources")) - } - } - if len(resources) > 1 && hasWildcard(resources) { - allErrors = append(allErrors, field.Invalid(fldPath, resources, "if '*' is present, must not specify other resources")) - } - return allErrors -} - -var validScopes = sets.NewString( - string(admissionregistration.ClusterScope), - string(admissionregistration.NamespacedScope), - string(admissionregistration.AllScopes), -) - -func validateRule(rule *admissionregistration.Rule, fldPath *field.Path, allowSubResource bool) field.ErrorList { - var allErrors field.ErrorList - if len(rule.APIGroups) == 0 { - allErrors = append(allErrors, field.Required(fldPath.Child("apiGroups"), "")) - } - if len(rule.APIGroups) > 1 && hasWildcard(rule.APIGroups) { - allErrors = append(allErrors, field.Invalid(fldPath.Child("apiGroups"), rule.APIGroups, "if '*' is present, must not specify other API groups")) - } - // Note: group could be empty, e.g., the legacy "v1" API - if len(rule.APIVersions) == 0 { - allErrors = append(allErrors, field.Required(fldPath.Child("apiVersions"), "")) - } - if len(rule.APIVersions) > 1 && hasWildcard(rule.APIVersions) { - allErrors = append(allErrors, field.Invalid(fldPath.Child("apiVersions"), rule.APIVersions, "if '*' is present, must not specify other API versions")) - } - for i, version := range rule.APIVersions { - if version == "" { - allErrors = append(allErrors, field.Required(fldPath.Child("apiVersions").Index(i), "")) - } - } - if allowSubResource { - allErrors = append(allErrors, validateResources(rule.Resources, fldPath.Child("resources"))...) - } else { - allErrors = append(allErrors, validateResourcesNoSubResources(rule.Resources, fldPath.Child("resources"))...) - } - if rule.Scope != nil && !validScopes.Has(string(*rule.Scope)) { - allErrors = append(allErrors, field.NotSupported(fldPath.Child("scope"), *rule.Scope, validScopes.List())) - } - return allErrors -} - -// AcceptedAdmissionReviewVersions contains the list of AdmissionReview versions the *prior* version of the API server understands. -// 1.15: server understands v1beta1; accepted versions are ["v1beta1"] -// 1.16: server understands v1, v1beta1; accepted versions are ["v1beta1"] -// 1.17+: server understands v1, v1beta1; accepted versions are ["v1","v1beta1"] -var AcceptedAdmissionReviewVersions = []string{admissionregistrationv1.SchemeGroupVersion.Version, admissionregistrationv1beta1.SchemeGroupVersion.Version} - -func isAcceptedAdmissionReviewVersion(v string) bool { - for _, version := range AcceptedAdmissionReviewVersions { - if v == version { - return true - } - } - return false -} - -func validateAdmissionReviewVersions(versions []string, requireRecognizedAdmissionReviewVersion bool, fldPath *field.Path) field.ErrorList { - allErrors := field.ErrorList{} - - // Currently only v1beta1 accepted in AdmissionReviewVersions - if len(versions) < 1 { - allErrors = append(allErrors, field.Required(fldPath, fmt.Sprintf("must specify one of %v", strings.Join(AcceptedAdmissionReviewVersions, ", ")))) - } else { - seen := map[string]bool{} - hasAcceptedVersion := false - for i, v := range versions { - if seen[v] { - allErrors = append(allErrors, field.Invalid(fldPath.Index(i), v, "duplicate version")) - continue - } - seen[v] = true - for _, errString := range utilvalidation.IsDNS1035Label(v) { - allErrors = append(allErrors, field.Invalid(fldPath.Index(i), v, errString)) - } - if isAcceptedAdmissionReviewVersion(v) { - hasAcceptedVersion = true - } - } - if requireRecognizedAdmissionReviewVersion && !hasAcceptedVersion { - allErrors = append(allErrors, field.Invalid( - fldPath, versions, - fmt.Sprintf("must include at least one of %v", - strings.Join(AcceptedAdmissionReviewVersions, ", ")))) - } - } - return allErrors -} - -// ValidateValidatingWebhookConfiguration validates a webhook before creation. -func ValidateValidatingWebhookConfiguration(e *admissionregistration.ValidatingWebhookConfiguration) field.ErrorList { - return validateValidatingWebhookConfiguration(e, validationOptions{ - ignoreMatchConditions: false, - allowParamsInMatchConditions: false, - requireNoSideEffects: true, - requireRecognizedAdmissionReviewVersion: true, - requireUniqueWebhookNames: true, - allowInvalidLabelValueInSelector: false, - strictCostEnforcement: utilfeature.DefaultFeatureGate.Enabled(features.StrictCostEnforcementForWebhooks), - }) -} - -func validateValidatingWebhookConfiguration(e *admissionregistration.ValidatingWebhookConfiguration, opts validationOptions) field.ErrorList { - allErrors := genericvalidation.ValidateObjectMeta(&e.ObjectMeta, false, genericvalidation.NameIsDNSSubdomain, field.NewPath("metadata")) - hookNames := sets.NewString() - for i, hook := range e.Webhooks { - allErrors = append(allErrors, validateValidatingWebhook(&hook, opts, field.NewPath("webhooks").Index(i))...) - allErrors = append(allErrors, validateAdmissionReviewVersions(hook.AdmissionReviewVersions, opts.requireRecognizedAdmissionReviewVersion, field.NewPath("webhooks").Index(i).Child("admissionReviewVersions"))...) - if opts.requireUniqueWebhookNames && len(hook.Name) > 0 { - if hookNames.Has(hook.Name) { - allErrors = append(allErrors, field.Duplicate(field.NewPath("webhooks").Index(i).Child("name"), hook.Name)) - } else { - hookNames.Insert(hook.Name) - } - } - } - return allErrors -} - -// ValidateMutatingWebhookConfiguration validates a webhook before creation. -func ValidateMutatingWebhookConfiguration(e *admissionregistration.MutatingWebhookConfiguration) field.ErrorList { - return validateMutatingWebhookConfiguration(e, validationOptions{ - ignoreMatchConditions: false, - allowParamsInMatchConditions: false, - requireNoSideEffects: true, - requireRecognizedAdmissionReviewVersion: true, - requireUniqueWebhookNames: true, - allowInvalidLabelValueInSelector: false, - strictCostEnforcement: utilfeature.DefaultFeatureGate.Enabled(features.StrictCostEnforcementForWebhooks), - }) -} - type validationOptions struct { ignoreMatchConditions bool allowParamsInMatchConditions bool @@ -276,656 +49,7 @@ type validationOptions struct { } type preexistingExpressions struct { - matchConditionExpressions sets.Set[string] - validationExpressions sets.Set[string] - validationMessageExpressions sets.Set[string] - auditAnnotationValuesExpressions sets.Set[string] -} - -func newPreexistingExpressions() preexistingExpressions { - return preexistingExpressions{ - matchConditionExpressions: sets.New[string](), - validationExpressions: sets.New[string](), - validationMessageExpressions: sets.New[string](), - auditAnnotationValuesExpressions: sets.New[string](), - } -} - -func findMutatingPreexistingExpressions(mutating *admissionregistration.MutatingWebhookConfiguration) preexistingExpressions { - preexisting := newPreexistingExpressions() - for _, wh := range mutating.Webhooks { - for _, mc := range wh.MatchConditions { - preexisting.matchConditionExpressions.Insert(mc.Expression) - } - } - return preexisting -} - -func findValidatingPreexistingExpressions(validating *admissionregistration.ValidatingWebhookConfiguration) preexistingExpressions { - preexisting := newPreexistingExpressions() - for _, wh := range validating.Webhooks { - for _, mc := range wh.MatchConditions { - preexisting.matchConditionExpressions.Insert(mc.Expression) - } - } - return preexisting -} - -func findValidatingPolicyPreexistingExpressions(validatingPolicy *admissionregistration.ValidatingAdmissionPolicy) preexistingExpressions { - preexisting := newPreexistingExpressions() - for _, mc := range validatingPolicy.Spec.MatchConditions { - preexisting.matchConditionExpressions.Insert(mc.Expression) - } - for _, v := range validatingPolicy.Spec.Validations { - preexisting.validationExpressions.Insert(v.Expression) - if len(v.MessageExpression) > 0 { - preexisting.validationMessageExpressions.Insert(v.MessageExpression) - } - } - for _, a := range validatingPolicy.Spec.AuditAnnotations { - preexisting.auditAnnotationValuesExpressions.Insert(a.ValueExpression) - } - return preexisting -} - -func validateMutatingWebhookConfiguration(e *admissionregistration.MutatingWebhookConfiguration, opts validationOptions) field.ErrorList { - allErrors := genericvalidation.ValidateObjectMeta(&e.ObjectMeta, false, genericvalidation.NameIsDNSSubdomain, field.NewPath("metadata")) - - hookNames := sets.NewString() - for i, hook := range e.Webhooks { - allErrors = append(allErrors, validateMutatingWebhook(&hook, opts, field.NewPath("webhooks").Index(i))...) - allErrors = append(allErrors, validateAdmissionReviewVersions(hook.AdmissionReviewVersions, opts.requireRecognizedAdmissionReviewVersion, field.NewPath("webhooks").Index(i).Child("admissionReviewVersions"))...) - if opts.requireUniqueWebhookNames && len(hook.Name) > 0 { - if hookNames.Has(hook.Name) { - allErrors = append(allErrors, field.Duplicate(field.NewPath("webhooks").Index(i).Child("name"), hook.Name)) - } else { - hookNames.Insert(hook.Name) - } - } - } - return allErrors -} - -func validateValidatingWebhook(hook *admissionregistration.ValidatingWebhook, opts validationOptions, fldPath *field.Path) field.ErrorList { - var allErrors field.ErrorList - // hook.Name must be fully qualified - allErrors = append(allErrors, utilvalidation.IsFullyQualifiedName(fldPath.Child("name"), hook.Name)...) - labelSelectorValidationOpts := metav1validation.LabelSelectorValidationOptions{ - AllowInvalidLabelValueInSelector: opts.allowInvalidLabelValueInSelector, - } - - for i, rule := range hook.Rules { - allErrors = append(allErrors, validateRuleWithOperations(&rule, fldPath.Child("rules").Index(i))...) - } - if hook.FailurePolicy != nil && !supportedFailurePolicies.Has(string(*hook.FailurePolicy)) { - allErrors = append(allErrors, field.NotSupported(fldPath.Child("failurePolicy"), *hook.FailurePolicy, supportedFailurePolicies.List())) - } - if hook.MatchPolicy != nil && !supportedMatchPolicies.Has(string(*hook.MatchPolicy)) { - allErrors = append(allErrors, field.NotSupported(fldPath.Child("matchPolicy"), *hook.MatchPolicy, supportedMatchPolicies.List())) - } - allowedSideEffects := supportedSideEffectClasses - if opts.requireNoSideEffects { - allowedSideEffects = noSideEffectClasses - } - if hook.SideEffects == nil { - allErrors = append(allErrors, field.Required(fldPath.Child("sideEffects"), fmt.Sprintf("must specify one of %v", strings.Join(allowedSideEffects.List(), ", ")))) - } - if hook.SideEffects != nil && !allowedSideEffects.Has(string(*hook.SideEffects)) { - allErrors = append(allErrors, field.NotSupported(fldPath.Child("sideEffects"), *hook.SideEffects, allowedSideEffects.List())) - } - if hook.TimeoutSeconds != nil && (*hook.TimeoutSeconds > 30 || *hook.TimeoutSeconds < 1) { - allErrors = append(allErrors, field.Invalid(fldPath.Child("timeoutSeconds"), *hook.TimeoutSeconds, "the timeout value must be between 1 and 30 seconds")) - } - - if hook.NamespaceSelector != nil { - allErrors = append(allErrors, metav1validation.ValidateLabelSelector(hook.NamespaceSelector, labelSelectorValidationOpts, fldPath.Child("namespaceSelector"))...) - } - - if hook.ObjectSelector != nil { - allErrors = append(allErrors, metav1validation.ValidateLabelSelector(hook.ObjectSelector, labelSelectorValidationOpts, fldPath.Child("objectSelector"))...) - } - - cc := hook.ClientConfig - switch { - case (cc.URL == nil) == (cc.Service == nil): - allErrors = append(allErrors, field.Required(fldPath.Child("clientConfig"), "exactly one of url or service is required")) - case cc.URL != nil: - allErrors = append(allErrors, webhook.ValidateWebhookURL(fldPath.Child("clientConfig").Child("url"), *cc.URL, true)...) - case cc.Service != nil: - allErrors = append(allErrors, webhook.ValidateWebhookService(fldPath.Child("clientConfig").Child("service"), cc.Service.Name, cc.Service.Namespace, cc.Service.Path, cc.Service.Port)...) - } - - if !opts.ignoreMatchConditions { - allErrors = append(allErrors, validateMatchConditions(hook.MatchConditions, opts, fldPath.Child("matchConditions"))...) - } - - return allErrors -} - -func validateMutatingWebhook(hook *admissionregistration.MutatingWebhook, opts validationOptions, fldPath *field.Path) field.ErrorList { - var allErrors field.ErrorList - // hook.Name must be fully qualified - allErrors = append(allErrors, utilvalidation.IsFullyQualifiedName(fldPath.Child("name"), hook.Name)...) - labelSelectorValidationOpts := metav1validation.LabelSelectorValidationOptions{ - AllowInvalidLabelValueInSelector: opts.allowInvalidLabelValueInSelector, - } - - for i, rule := range hook.Rules { - allErrors = append(allErrors, validateRuleWithOperations(&rule, fldPath.Child("rules").Index(i))...) - } - if hook.FailurePolicy != nil && !supportedFailurePolicies.Has(string(*hook.FailurePolicy)) { - allErrors = append(allErrors, field.NotSupported(fldPath.Child("failurePolicy"), *hook.FailurePolicy, supportedFailurePolicies.List())) - } - if hook.MatchPolicy != nil && !supportedMatchPolicies.Has(string(*hook.MatchPolicy)) { - allErrors = append(allErrors, field.NotSupported(fldPath.Child("matchPolicy"), *hook.MatchPolicy, supportedMatchPolicies.List())) - } - allowedSideEffects := supportedSideEffectClasses - if opts.requireNoSideEffects { - allowedSideEffects = noSideEffectClasses - } - if hook.SideEffects == nil { - allErrors = append(allErrors, field.Required(fldPath.Child("sideEffects"), fmt.Sprintf("must specify one of %v", strings.Join(allowedSideEffects.List(), ", ")))) - } - if hook.SideEffects != nil && !allowedSideEffects.Has(string(*hook.SideEffects)) { - allErrors = append(allErrors, field.NotSupported(fldPath.Child("sideEffects"), *hook.SideEffects, allowedSideEffects.List())) - } - if hook.TimeoutSeconds != nil && (*hook.TimeoutSeconds > 30 || *hook.TimeoutSeconds < 1) { - allErrors = append(allErrors, field.Invalid(fldPath.Child("timeoutSeconds"), *hook.TimeoutSeconds, "the timeout value must be between 1 and 30 seconds")) - } - - if hook.NamespaceSelector != nil { - allErrors = append(allErrors, metav1validation.ValidateLabelSelector(hook.NamespaceSelector, labelSelectorValidationOpts, fldPath.Child("namespaceSelector"))...) - } - if hook.ObjectSelector != nil { - allErrors = append(allErrors, metav1validation.ValidateLabelSelector(hook.ObjectSelector, labelSelectorValidationOpts, fldPath.Child("objectSelector"))...) - } - if hook.ReinvocationPolicy != nil && !supportedReinvocationPolicies.Has(string(*hook.ReinvocationPolicy)) { - allErrors = append(allErrors, field.NotSupported(fldPath.Child("reinvocationPolicy"), *hook.ReinvocationPolicy, supportedReinvocationPolicies.List())) - } - - cc := hook.ClientConfig - switch { - case (cc.URL == nil) == (cc.Service == nil): - allErrors = append(allErrors, field.Required(fldPath.Child("clientConfig"), "exactly one of url or service is required")) - case cc.URL != nil: - allErrors = append(allErrors, webhook.ValidateWebhookURL(fldPath.Child("clientConfig").Child("url"), *cc.URL, true)...) - case cc.Service != nil: - allErrors = append(allErrors, webhook.ValidateWebhookService(fldPath.Child("clientConfig").Child("service"), cc.Service.Name, cc.Service.Namespace, cc.Service.Path, cc.Service.Port)...) - } - - if !opts.ignoreMatchConditions { - allErrors = append(allErrors, validateMatchConditions(hook.MatchConditions, opts, fldPath.Child("matchConditions"))...) - } - - return allErrors -} - -var supportedFailurePolicies = sets.NewString( - string(admissionregistration.Ignore), - string(admissionregistration.Fail), -) - -var supportedMatchPolicies = sets.NewString( - string(admissionregistration.Exact), - string(admissionregistration.Equivalent), -) - -var supportedSideEffectClasses = sets.NewString( - string(admissionregistration.SideEffectClassUnknown), - string(admissionregistration.SideEffectClassNone), - string(admissionregistration.SideEffectClassSome), - string(admissionregistration.SideEffectClassNoneOnDryRun), -) - -var noSideEffectClasses = sets.NewString( - string(admissionregistration.SideEffectClassNone), - string(admissionregistration.SideEffectClassNoneOnDryRun), -) - -var supportedOperations = sets.NewString( - string(admissionregistration.OperationAll), - string(admissionregistration.Create), - string(admissionregistration.Update), - string(admissionregistration.Delete), - string(admissionregistration.Connect), -) - -var supportedReinvocationPolicies = sets.NewString( - string(admissionregistration.NeverReinvocationPolicy), - string(admissionregistration.IfNeededReinvocationPolicy), -) - -var supportedValidationPolicyReason = sets.NewString( - string(metav1.StatusReasonForbidden), - string(metav1.StatusReasonInvalid), - string(metav1.StatusReasonRequestEntityTooLarge), -) - -func hasWildcardOperation(operations []admissionregistration.OperationType) bool { - for _, o := range operations { - if o == admissionregistration.OperationAll { - return true - } - } - return false -} - -func validateRuleWithOperations(ruleWithOperations *admissionregistration.RuleWithOperations, fldPath *field.Path) field.ErrorList { - var allErrors field.ErrorList - if len(ruleWithOperations.Operations) == 0 { - allErrors = append(allErrors, field.Required(fldPath.Child("operations"), "")) - } - if len(ruleWithOperations.Operations) > 1 && hasWildcardOperation(ruleWithOperations.Operations) { - allErrors = append(allErrors, field.Invalid(fldPath.Child("operations"), ruleWithOperations.Operations, "if '*' is present, must not specify other operations")) - } - for i, operation := range ruleWithOperations.Operations { - if !supportedOperations.Has(string(operation)) { - allErrors = append(allErrors, field.NotSupported(fldPath.Child("operations").Index(i), operation, supportedOperations.List())) - } - } - allowSubResource := true - allErrors = append(allErrors, validateRule(&ruleWithOperations.Rule, fldPath, allowSubResource)...) - return allErrors -} - -// mutatingHasAcceptedAdmissionReviewVersions returns true if all webhooks have at least one -// admission review version this apiserver accepts. -func mutatingHasAcceptedAdmissionReviewVersions(webhooks []admissionregistration.MutatingWebhook) bool { - for _, hook := range webhooks { - hasRecognizedVersion := false - for _, version := range hook.AdmissionReviewVersions { - if isAcceptedAdmissionReviewVersion(version) { - hasRecognizedVersion = true - break - } - } - if !hasRecognizedVersion && len(hook.AdmissionReviewVersions) > 0 { - return false - } - } - return true -} - -// validatingHasAcceptedAdmissionReviewVersions returns true if all webhooks have at least one -// admission review version this apiserver accepts. -func validatingHasAcceptedAdmissionReviewVersions(webhooks []admissionregistration.ValidatingWebhook) bool { - for _, hook := range webhooks { - hasRecognizedVersion := false - for _, version := range hook.AdmissionReviewVersions { - if isAcceptedAdmissionReviewVersion(version) { - hasRecognizedVersion = true - break - } - } - if !hasRecognizedVersion && len(hook.AdmissionReviewVersions) > 0 { - return false - } - } - return true -} - -// ignoreMatchConditions returns false if any change to match conditions -func ignoreMutatingWebhookMatchConditions(new, old []admissionregistration.MutatingWebhook) bool { - if len(new) != len(old) { - return false - } - for i := range old { - if !reflect.DeepEqual(new[i].MatchConditions, old[i].MatchConditions) { - return false - } - } - - return true -} - -// ignoreMatchConditions returns true if any new expressions are added -func ignoreValidatingWebhookMatchConditions(new, old []admissionregistration.ValidatingWebhook) bool { - if len(new) != len(old) { - return false - } - for i := range old { - if !reflect.DeepEqual(new[i].MatchConditions, old[i].MatchConditions) { - return false - } - } - - return true -} - -// ignoreValidatingAdmissionPolicyMatchConditions returns true if there have been no updates that could invalidate previously-valid match conditions -func ignoreValidatingAdmissionPolicyMatchConditions(new, old *admissionregistration.ValidatingAdmissionPolicy) bool { - if !reflect.DeepEqual(new.Spec.ParamKind, old.Spec.ParamKind) { - return false - } - if !reflect.DeepEqual(new.Spec.MatchConditions, old.Spec.MatchConditions) { - return false - } - return true -} - -// mutatingHasUniqueWebhookNames returns true if all webhooks have unique names -func mutatingHasUniqueWebhookNames(webhooks []admissionregistration.MutatingWebhook) bool { - names := sets.NewString() - for _, hook := range webhooks { - if names.Has(hook.Name) { - return false - } - names.Insert(hook.Name) - } - return true -} - -// validatingHasUniqueWebhookNames returns true if all webhooks have unique names -func validatingHasUniqueWebhookNames(webhooks []admissionregistration.ValidatingWebhook) bool { - names := sets.NewString() - for _, hook := range webhooks { - if names.Has(hook.Name) { - return false - } - names.Insert(hook.Name) - } - return true -} - -// mutatingHasNoSideEffects returns true if all webhooks have no side effects -func mutatingHasNoSideEffects(webhooks []admissionregistration.MutatingWebhook) bool { - for _, hook := range webhooks { - if hook.SideEffects == nil || !noSideEffectClasses.Has(string(*hook.SideEffects)) { - return false - } - } - return true -} - -// validatingHasNoSideEffects returns true if all webhooks have no side effects -func validatingHasNoSideEffects(webhooks []admissionregistration.ValidatingWebhook) bool { - for _, hook := range webhooks { - if hook.SideEffects == nil || !noSideEffectClasses.Has(string(*hook.SideEffects)) { - return false - } - } - return true -} - -// validatingWebhookAllowInvalidLabelValueInSelector returns true if all webhooksallow invalid label value in selector -func validatingWebhookHasInvalidLabelValueInSelector(webhooks []admissionregistration.ValidatingWebhook) bool { - labelSelectorValidationOpts := metav1validation.LabelSelectorValidationOptions{ - AllowInvalidLabelValueInSelector: false, - } - - for _, hook := range webhooks { - if hook.NamespaceSelector != nil { - if len(metav1validation.ValidateLabelSelector(hook.NamespaceSelector, labelSelectorValidationOpts, nil)) > 0 { - return true - } - } - if hook.ObjectSelector != nil { - if len(metav1validation.ValidateLabelSelector(hook.ObjectSelector, labelSelectorValidationOpts, nil)) > 0 { - return true - } - } - } - return false -} - -// mutatingWebhookAllowInvalidLabelValueInSelector returns true if all webhooks allow invalid label value in selector -func mutatingWebhookHasInvalidLabelValueInSelector(webhooks []admissionregistration.MutatingWebhook) bool { - labelSelectorValidationOpts := metav1validation.LabelSelectorValidationOptions{ - AllowInvalidLabelValueInSelector: false, - } - - for _, hook := range webhooks { - if hook.NamespaceSelector != nil { - if len(metav1validation.ValidateLabelSelector(hook.NamespaceSelector, labelSelectorValidationOpts, nil)) > 0 { - return true - } - } - if hook.ObjectSelector != nil { - if len(metav1validation.ValidateLabelSelector(hook.ObjectSelector, labelSelectorValidationOpts, nil)) > 0 { - return true - } - } - } - return false -} - -// ValidateValidatingWebhookConfigurationUpdate validates update of validating webhook configuration -func ValidateValidatingWebhookConfigurationUpdate(newC, oldC *admissionregistration.ValidatingWebhookConfiguration) field.ErrorList { - return validateValidatingWebhookConfiguration(newC, validationOptions{ - ignoreMatchConditions: ignoreValidatingWebhookMatchConditions(newC.Webhooks, oldC.Webhooks), - allowParamsInMatchConditions: false, - requireNoSideEffects: validatingHasNoSideEffects(oldC.Webhooks), - requireRecognizedAdmissionReviewVersion: validatingHasAcceptedAdmissionReviewVersions(oldC.Webhooks), - requireUniqueWebhookNames: validatingHasUniqueWebhookNames(oldC.Webhooks), - allowInvalidLabelValueInSelector: validatingWebhookHasInvalidLabelValueInSelector(oldC.Webhooks), - preexistingExpressions: findValidatingPreexistingExpressions(oldC), - strictCostEnforcement: utilfeature.DefaultFeatureGate.Enabled(features.StrictCostEnforcementForWebhooks), - }) -} - -// ValidateMutatingWebhookConfigurationUpdate validates update of mutating webhook configuration -func ValidateMutatingWebhookConfigurationUpdate(newC, oldC *admissionregistration.MutatingWebhookConfiguration) field.ErrorList { - return validateMutatingWebhookConfiguration(newC, validationOptions{ - ignoreMatchConditions: ignoreMutatingWebhookMatchConditions(newC.Webhooks, oldC.Webhooks), - allowParamsInMatchConditions: false, - requireNoSideEffects: mutatingHasNoSideEffects(oldC.Webhooks), - requireRecognizedAdmissionReviewVersion: mutatingHasAcceptedAdmissionReviewVersions(oldC.Webhooks), - requireUniqueWebhookNames: mutatingHasUniqueWebhookNames(oldC.Webhooks), - allowInvalidLabelValueInSelector: mutatingWebhookHasInvalidLabelValueInSelector(oldC.Webhooks), - preexistingExpressions: findMutatingPreexistingExpressions(oldC), - strictCostEnforcement: utilfeature.DefaultFeatureGate.Enabled(features.StrictCostEnforcementForWebhooks), - }) -} - -const ( - maxAuditAnnotations = 20 - // use a 5kb limit the CEL expression, note that this is less than the length limit - // for the audit annotation value limit (10kb) since an expressions that concatenates - // strings will often produce a longer value than the expression - maxAuditAnnotationValueExpressionLength = 5 * 1024 -) - -// ValidateValidatingAdmissionPolicy validates a ValidatingAdmissionPolicy before creation. -func ValidateValidatingAdmissionPolicy(p *admissionregistration.ValidatingAdmissionPolicy) field.ErrorList { - return validateValidatingAdmissionPolicy(p, validationOptions{ignoreMatchConditions: false, strictCostEnforcement: utilfeature.DefaultFeatureGate.Enabled(features.StrictCostEnforcementForVAP)}) -} - -func validateValidatingAdmissionPolicy(p *admissionregistration.ValidatingAdmissionPolicy, opts validationOptions) field.ErrorList { - allErrors := genericvalidation.ValidateObjectMeta(&p.ObjectMeta, false, genericvalidation.NameIsDNSSubdomain, field.NewPath("metadata")) - allErrors = append(allErrors, validateValidatingAdmissionPolicySpec(p.ObjectMeta, &p.Spec, opts, field.NewPath("spec"))...) - return allErrors -} - -func validateValidatingAdmissionPolicySpec(meta metav1.ObjectMeta, spec *admissionregistration.ValidatingAdmissionPolicySpec, opts validationOptions, fldPath *field.Path) field.ErrorList { - var allErrors field.ErrorList - var compiler plugincel.Compiler // composition compiler is stateful, create one lazily per policy - getCompiler := func() plugincel.Compiler { - if compiler == nil { - needsComposition := len(spec.Variables) > 0 - compiler = createCompiler(needsComposition, opts.strictCostEnforcement) - } - return compiler - } - if spec.FailurePolicy == nil { - allErrors = append(allErrors, field.Required(fldPath.Child("failurePolicy"), "")) - } else if !supportedFailurePolicies.Has(string(*spec.FailurePolicy)) { - allErrors = append(allErrors, field.NotSupported(fldPath.Child("failurePolicy"), *spec.FailurePolicy, supportedFailurePolicies.List())) - } - if spec.ParamKind != nil { - opts.allowParamsInMatchConditions = true - allErrors = append(allErrors, validateParamKind(*spec.ParamKind, fldPath.Child("paramKind"))...) - } - if spec.MatchConstraints == nil { - allErrors = append(allErrors, field.Required(fldPath.Child("matchConstraints"), "")) - } else { - allErrors = append(allErrors, validateMatchResources(spec.MatchConstraints, fldPath.Child("matchConstraints"))...) - // at least one resourceRule must be defined to provide type information - if len(spec.MatchConstraints.ResourceRules) == 0 { - allErrors = append(allErrors, field.Required(fldPath.Child("matchConstraints", "resourceRules"), "")) - } - } - if !opts.ignoreMatchConditions { - allErrors = append(allErrors, validateMatchConditions(spec.MatchConditions, opts, fldPath.Child("matchConditions"))...) - } - if len(spec.Variables) > 0 { - for i, variable := range spec.Variables { - allErrors = append(allErrors, validateVariable(getCompiler(), &variable, spec.ParamKind, opts, fldPath.Child("variables").Index(i))...) - } - } - if len(spec.Validations) == 0 && len(spec.AuditAnnotations) == 0 { - allErrors = append(allErrors, field.Required(fldPath.Child("validations"), "validations or auditAnnotations must contain at least one item")) - allErrors = append(allErrors, field.Required(fldPath.Child("auditAnnotations"), "validations or auditAnnotations must contain at least one item")) - } else { - for i, validation := range spec.Validations { - allErrors = append(allErrors, validateValidation(getCompiler(), &validation, spec.ParamKind, opts, fldPath.Child("validations").Index(i))...) - } - if spec.AuditAnnotations != nil { - keys := sets.NewString() - if len(spec.AuditAnnotations) > maxAuditAnnotations { - allErrors = append(allErrors, field.Invalid(fldPath.Child("auditAnnotations"), spec.AuditAnnotations, fmt.Sprintf("must not have more than %d auditAnnotations", maxAuditAnnotations))) - } - for i, auditAnnotation := range spec.AuditAnnotations { - allErrors = append(allErrors, validateAuditAnnotation(getCompiler(), meta, &auditAnnotation, spec.ParamKind, opts, fldPath.Child("auditAnnotations").Index(i))...) - if keys.Has(auditAnnotation.Key) { - allErrors = append(allErrors, field.Duplicate(fldPath.Child("auditAnnotations").Index(i).Child("key"), auditAnnotation.Key)) - } - keys.Insert(auditAnnotation.Key) - } - } - } - return allErrors -} - -func validateParamKind(gvk admissionregistration.ParamKind, fldPath *field.Path) field.ErrorList { - var allErrors field.ErrorList - if len(gvk.APIVersion) == 0 { - allErrors = append(allErrors, field.Required(fldPath.Child("apiVersion"), "")) - } else if gv, err := parseGroupVersion(gvk.APIVersion); err != nil { - allErrors = append(allErrors, field.Invalid(fldPath.Child("apiVersion"), gvk.APIVersion, err.Error())) - } else { - // this matches the APIService group field validation - if len(gv.Group) > 0 { - if errs := utilvalidation.IsDNS1123Subdomain(gv.Group); len(errs) > 0 { - allErrors = append(allErrors, field.Invalid(fldPath.Child("apiVersion"), gv.Group, strings.Join(errs, ","))) - } - } - // this matches the APIService version field validation - if len(gv.Version) == 0 { - allErrors = append(allErrors, field.Invalid(fldPath.Child("apiVersion"), gvk.APIVersion, "version must be specified")) - } else { - if errs := utilvalidation.IsDNS1035Label(gv.Version); len(errs) > 0 { - allErrors = append(allErrors, field.Invalid(fldPath.Child("apiVersion"), gv.Version, strings.Join(errs, ","))) - } - } - } - if len(gvk.Kind) == 0 { - allErrors = append(allErrors, field.Required(fldPath.Child("kind"), "")) - } else if errs := utilvalidation.IsDNS1035Label(strings.ToLower(gvk.Kind)); len(errs) > 0 { - allErrors = append(allErrors, field.Invalid(fldPath.Child("kind"), gvk.Kind, "may have mixed case, but should otherwise match: "+strings.Join(errs, ","))) - } - - return allErrors -} - -type groupVersion struct { - Group string - Version string -} - -// parseGroupVersion turns "group/version" string into a groupVersion struct. It reports error -// if it cannot parse the string. -func parseGroupVersion(gv string) (groupVersion, error) { - if (len(gv) == 0) || (gv == "/") { - return groupVersion{}, nil - } - - switch strings.Count(gv, "/") { - case 0: - return groupVersion{"", gv}, nil - case 1: - i := strings.Index(gv, "/") - return groupVersion{gv[:i], gv[i+1:]}, nil - default: - return groupVersion{}, fmt.Errorf("unexpected GroupVersion string: %v", gv) - } -} - -func validateMatchResources(mc *admissionregistration.MatchResources, fldPath *field.Path) field.ErrorList { - var allErrors field.ErrorList - if mc == nil { - return allErrors - } - if mc.MatchPolicy == nil { - allErrors = append(allErrors, field.Required(fldPath.Child("matchPolicy"), "")) - } else if !supportedMatchPolicies.Has(string(*mc.MatchPolicy)) { - allErrors = append(allErrors, field.NotSupported(fldPath.Child("matchPolicy"), *mc.MatchPolicy, supportedMatchPolicies.List())) - } - if mc.NamespaceSelector == nil { - allErrors = append(allErrors, field.Required(fldPath.Child("namespaceSelector"), "")) - } else { - // validate selector strictly, this type was released after issue #99139 was resolved - allErrors = append(allErrors, metav1validation.ValidateLabelSelector(mc.NamespaceSelector, metav1validation.LabelSelectorValidationOptions{}, fldPath.Child("namespaceSelector"))...) - } - - if mc.ObjectSelector == nil { - allErrors = append(allErrors, field.Required(fldPath.Child("objectSelector"), "")) - } else { - // validate selector strictly, this type was released after issue #99139 was resolved - allErrors = append(allErrors, metav1validation.ValidateLabelSelector(mc.ObjectSelector, metav1validation.LabelSelectorValidationOptions{}, fldPath.Child("objectSelector"))...) - } - - for i, namedRuleWithOperations := range mc.ResourceRules { - allErrors = append(allErrors, validateNamedRuleWithOperations(&namedRuleWithOperations, fldPath.Child("resourceRules").Index(i))...) - } - - for i, namedRuleWithOperations := range mc.ExcludeResourceRules { - allErrors = append(allErrors, validateNamedRuleWithOperations(&namedRuleWithOperations, fldPath.Child("excludeResourceRules").Index(i))...) - } - return allErrors -} - -var validValidationActions = sets.NewString( - string(admissionregistration.Deny), - string(admissionregistration.Warn), - string(admissionregistration.Audit), -) - -func validateValidationActions(va []admissionregistration.ValidationAction, fldPath *field.Path) field.ErrorList { - var allErrors field.ErrorList - actions := sets.NewString() - for i, action := range va { - if !validValidationActions.Has(string(action)) { - allErrors = append(allErrors, field.NotSupported(fldPath.Index(i), action, validValidationActions.List())) - } - if actions.Has(string(action)) { - allErrors = append(allErrors, field.Duplicate(fldPath.Index(i), action)) - } - actions.Insert(string(action)) - } - if actions.Has(string(admissionregistration.Deny)) && actions.Has(string(admissionregistration.Warn)) { - 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)")) - } - if len(actions) == 0 { - allErrors = append(allErrors, field.Required(fldPath, "at least one validation action is required")) - } - return allErrors -} - -func validateNamedRuleWithOperations(n *admissionregistration.NamedRuleWithOperations, fldPath *field.Path) field.ErrorList { - var allErrors field.ErrorList - resourceNames := sets.NewString() - for i, rName := range n.ResourceNames { - for _, msg := range path.ValidatePathSegmentName(rName, false) { - allErrors = append(allErrors, field.Invalid(fldPath.Child("resourceNames").Index(i), rName, msg)) - } - if resourceNames.Has(rName) { - allErrors = append(allErrors, field.Duplicate(fldPath.Child("resourceNames").Index(i), rName)) - } else { - resourceNames.Insert(rName) - } - } - allErrors = append(allErrors, validateRuleWithOperations(&n.RuleWithOperations, fldPath)...) - return allErrors + matchConditionExpressions sets.Set[string] } func validateMatchConditions(m []admissionregistration.MatchCondition, opts validationOptions, fldPath *field.Path) field.ErrorList { @@ -963,72 +87,6 @@ func validateMatchCondition(v *admissionregistration.MatchCondition, opts valida return allErrors } -func validateVariable(compiler plugincel.Compiler, v *admissionregistration.Variable, paramKind *admissionregistration.ParamKind, opts validationOptions, fldPath *field.Path) field.ErrorList { - var allErrors field.ErrorList - if len(v.Name) == 0 || strings.TrimSpace(v.Name) == "" { - allErrors = append(allErrors, field.Required(fldPath.Child("name"), "name is not specified")) - } else { - if !isCELIdentifier(v.Name) { - allErrors = append(allErrors, field.Invalid(fldPath.Child("name"), v.Name, "name is not a valid CEL identifier")) - } - } - if len(v.Expression) == 0 || strings.TrimSpace(v.Expression) == "" { - allErrors = append(allErrors, field.Required(fldPath.Child("expression"), "expression is not specified")) - } else { - if compiler, ok := compiler.(*plugincel.CompositedCompiler); ok { - envType := environment.NewExpressions - if opts.preexistingExpressions.validationExpressions.Has(v.Expression) { - envType = environment.StoredExpressions - } - variable := &validatingadmissionpolicy.Variable{ - Name: v.Name, - Expression: v.Expression, - } - result := compiler.CompileAndStoreVariable(variable, plugincel.OptionalVariableDeclarations{ - HasParams: paramKind != nil, - HasAuthorizer: true, - StrictCost: opts.strictCostEnforcement, - }, envType) - if result.Error != nil { - allErrors = append(allErrors, convertCELErrorToValidationError(fldPath.Child("expression"), variable, result.Error)) - } - } else { - allErrors = append(allErrors, field.InternalError(fldPath, fmt.Errorf("variable composition is not allowed"))) - } - } - return allErrors -} - -func validateValidation(compiler plugincel.Compiler, v *admissionregistration.Validation, paramKind *admissionregistration.ParamKind, opts validationOptions, fldPath *field.Path) field.ErrorList { - var allErrors field.ErrorList - trimmedExpression := strings.TrimSpace(v.Expression) - trimmedMsg := strings.TrimSpace(v.Message) - trimmedMessageExpression := strings.TrimSpace(v.MessageExpression) - if len(trimmedExpression) == 0 { - allErrors = append(allErrors, field.Required(fldPath.Child("expression"), "expression is not specified")) - } else { - allErrors = append(allErrors, validateValidationExpression(compiler, v.Expression, paramKind != nil, opts, fldPath.Child("expression"))...) - } - if len(v.MessageExpression) > 0 && len(trimmedMessageExpression) == 0 { - allErrors = append(allErrors, field.Invalid(fldPath.Child("messageExpression"), v.MessageExpression, "must be non-empty if specified")) - } else if len(trimmedMessageExpression) != 0 { - // use v.MessageExpression instead of trimmedMessageExpression so that - // the compiler output shows the correct column. - allErrors = append(allErrors, validateMessageExpression(compiler, v.MessageExpression, opts, fldPath.Child("messageExpression"))...) - } - if len(v.Message) > 0 && len(trimmedMsg) == 0 { - allErrors = append(allErrors, field.Invalid(fldPath.Child("message"), v.Message, "message must be non-empty if specified")) - } else if hasNewlines(trimmedMsg) { - allErrors = append(allErrors, field.Invalid(fldPath.Child("message"), v.Message, "message must not contain line breaks")) - } else if hasNewlines(trimmedMsg) && trimmedMsg == "" { - allErrors = append(allErrors, field.Required(fldPath.Child("message"), "message must be specified if expression contains line breaks")) - } - if v.Reason != nil && !supportedValidationPolicyReason.Has(string(*v.Reason)) { - allErrors = append(allErrors, field.NotSupported(fldPath.Child("reason"), *v.Reason, supportedValidationPolicyReason.List())) - } - return allErrors -} - func validateCELCondition(compiler plugincel.Compiler, expression plugincel.ExpressionAccessor, variables plugincel.OptionalVariableDeclarations, envType environment.Type, fldPath *field.Path) field.ErrorList { var allErrors field.ErrorList result := compiler.CompileCELExpression(expression, variables, envType) @@ -1052,20 +110,6 @@ func convertCELErrorToValidationError(fldPath *field.Path, expression plugincel. return field.InternalError(fldPath, fmt.Errorf("unsupported error type: %w", err)) } -func validateValidationExpression(compiler plugincel.Compiler, expression string, hasParams bool, opts validationOptions, fldPath *field.Path) field.ErrorList { - envType := environment.NewExpressions - if opts.preexistingExpressions.validationExpressions.Has(expression) { - envType = environment.StoredExpressions - } - return validateCELCondition(compiler, &validatingadmissionpolicy.ValidationCondition{ - Expression: expression, - }, plugincel.OptionalVariableDeclarations{ - HasParams: hasParams, - HasAuthorizer: true, - StrictCost: opts.strictCostEnforcement, - }, envType, fldPath) -} - func validateMatchConditionsExpression(expression string, opts validationOptions, fldPath *field.Path) field.ErrorList { envType := environment.NewExpressions if opts.preexistingExpressions.matchConditionExpressions.Has(expression) { @@ -1086,193 +130,6 @@ func validateMatchConditionsExpression(expression string, opts validationOptions }, envType, fldPath) } -func validateMessageExpression(compiler plugincel.Compiler, expression string, opts validationOptions, fldPath *field.Path) field.ErrorList { - envType := environment.NewExpressions - if opts.preexistingExpressions.validationMessageExpressions.Has(expression) { - envType = environment.StoredExpressions - } - return validateCELCondition(compiler, &validatingadmissionpolicy.MessageExpressionCondition{ - MessageExpression: expression, - }, plugincel.OptionalVariableDeclarations{ - HasParams: opts.allowParamsInMatchConditions, - HasAuthorizer: false, - StrictCost: opts.strictCostEnforcement, - }, envType, fldPath) -} - -func validateAuditAnnotation(compiler plugincel.Compiler, meta metav1.ObjectMeta, v *admissionregistration.AuditAnnotation, paramKind *admissionregistration.ParamKind, opts validationOptions, fldPath *field.Path) field.ErrorList { - var allErrors field.ErrorList - if len(meta.GetName()) != 0 { - name := meta.GetName() - allErrors = append(allErrors, apivalidation.ValidateQualifiedName(name+"/"+v.Key, fldPath.Child("key"))...) - } else { - allErrors = append(allErrors, field.Invalid(fldPath.Child("key"), v.Key, "requires metadata.name be non-empty")) - } - - trimmedValueExpression := strings.TrimSpace(v.ValueExpression) - if len(trimmedValueExpression) == 0 { - allErrors = append(allErrors, field.Required(fldPath.Child("valueExpression"), "valueExpression is not specified")) - } else if len(trimmedValueExpression) > maxAuditAnnotationValueExpressionLength { - allErrors = append(allErrors, field.Required(fldPath.Child("valueExpression"), fmt.Sprintf("must not exceed %d bytes in length", maxAuditAnnotationValueExpressionLength))) - } else { - envType := environment.NewExpressions - if opts.preexistingExpressions.auditAnnotationValuesExpressions.Has(v.ValueExpression) { - envType = environment.StoredExpressions - } - result := compiler.CompileCELExpression(&validatingadmissionpolicy.AuditAnnotationCondition{ - ValueExpression: trimmedValueExpression, - }, plugincel.OptionalVariableDeclarations{HasParams: paramKind != nil, HasAuthorizer: true, StrictCost: opts.strictCostEnforcement}, envType) - if result.Error != nil { - switch result.Error.Type { - case cel.ErrorTypeRequired: - allErrors = append(allErrors, field.Required(fldPath.Child("valueExpression"), result.Error.Detail)) - case cel.ErrorTypeInvalid: - allErrors = append(allErrors, field.Invalid(fldPath.Child("valueExpression"), v.ValueExpression, result.Error.Detail)) - default: - allErrors = append(allErrors, field.InternalError(fldPath.Child("valueExpression"), result.Error)) - } - } - } - return allErrors -} - -var newlineMatcher = regexp.MustCompile(`[\n\r]+`) // valid newline chars in CEL grammar -func hasNewlines(s string) bool { - return newlineMatcher.MatchString(s) -} - -// ValidateValidatingAdmissionPolicyBinding validates a ValidatingAdmissionPolicyBinding before create. -func ValidateValidatingAdmissionPolicyBinding(pb *admissionregistration.ValidatingAdmissionPolicyBinding) field.ErrorList { - return validateValidatingAdmissionPolicyBinding(pb) -} - -func validateValidatingAdmissionPolicyBinding(pb *admissionregistration.ValidatingAdmissionPolicyBinding) field.ErrorList { - allErrors := genericvalidation.ValidateObjectMeta(&pb.ObjectMeta, false, genericvalidation.NameIsDNSSubdomain, field.NewPath("metadata")) - allErrors = append(allErrors, validateValidatingAdmissionPolicyBindingSpec(&pb.Spec, field.NewPath("spec"))...) - - return allErrors -} - -func validateValidatingAdmissionPolicyBindingSpec(spec *admissionregistration.ValidatingAdmissionPolicyBindingSpec, fldPath *field.Path) field.ErrorList { - var allErrors field.ErrorList - - if len(spec.PolicyName) == 0 { - allErrors = append(allErrors, field.Required(fldPath.Child("policyName"), "")) - } else { - for _, msg := range genericvalidation.NameIsDNSSubdomain(spec.PolicyName, false) { - allErrors = append(allErrors, field.Invalid(fldPath.Child("policyName"), spec.PolicyName, msg)) - } - } - allErrors = append(allErrors, validateParamRef(spec.ParamRef, fldPath.Child("paramRef"))...) - allErrors = append(allErrors, validateMatchResources(spec.MatchResources, fldPath.Child("matchResouces"))...) - allErrors = append(allErrors, validateValidationActions(spec.ValidationActions, fldPath.Child("validationActions"))...) - - return allErrors -} - -func validateParamRef(pr *admissionregistration.ParamRef, fldPath *field.Path) field.ErrorList { - var allErrors field.ErrorList - if pr == nil { - return allErrors - } - - if len(pr.Name) > 0 { - for _, msg := range path.ValidatePathSegmentName(pr.Name, false) { - allErrors = append(allErrors, field.Invalid(fldPath.Child("name"), pr.Name, msg)) - } - - if pr.Selector != nil { - allErrors = append(allErrors, field.Forbidden(fldPath.Child("name"), `name and selector are mutually exclusive`)) - } - } - - if pr.Selector != nil { - labelSelectorValidationOpts := metav1validation.LabelSelectorValidationOptions{} - allErrors = append(allErrors, metav1validation.ValidateLabelSelector(pr.Selector, labelSelectorValidationOpts, fldPath.Child("selector"))...) - - if len(pr.Name) > 0 { - allErrors = append(allErrors, field.Forbidden(fldPath.Child("selector"), `name and selector are mutually exclusive`)) - } - } - - if len(pr.Name) == 0 && pr.Selector == nil { - allErrors = append(allErrors, field.Required(fldPath, `one of name or selector must be specified`)) - } - - if pr.ParameterNotFoundAction == nil || len(*pr.ParameterNotFoundAction) == 0 { - allErrors = append(allErrors, field.Required(fldPath.Child("parameterNotFoundAction"), "")) - } else { - if *pr.ParameterNotFoundAction != admissionregistration.DenyAction && *pr.ParameterNotFoundAction != admissionregistration.AllowAction { - allErrors = append(allErrors, field.NotSupported(fldPath.Child("parameterNotFoundAction"), pr.ParameterNotFoundAction, []string{string(admissionregistration.DenyAction), string(admissionregistration.AllowAction)})) - } - } - - return allErrors -} - -// ValidateValidatingAdmissionPolicyUpdate validates update of validating admission policy -func ValidateValidatingAdmissionPolicyUpdate(newC, oldC *admissionregistration.ValidatingAdmissionPolicy) field.ErrorList { - return validateValidatingAdmissionPolicy(newC, validationOptions{ - ignoreMatchConditions: ignoreValidatingAdmissionPolicyMatchConditions(newC, oldC), - preexistingExpressions: findValidatingPolicyPreexistingExpressions(oldC), - strictCostEnforcement: utilfeature.DefaultFeatureGate.Enabled(features.StrictCostEnforcementForVAP), - }) -} - -// ValidateValidatingAdmissionPolicyStatusUpdate validates update of status of validating admission policy -func ValidateValidatingAdmissionPolicyStatusUpdate(newC, oldC *admissionregistration.ValidatingAdmissionPolicy) field.ErrorList { - return validateValidatingAdmissionPolicyStatus(&newC.Status, field.NewPath("status")) -} - -// ValidateValidatingAdmissionPolicyBindingUpdate validates update of validating admission policy -func ValidateValidatingAdmissionPolicyBindingUpdate(newC, oldC *admissionregistration.ValidatingAdmissionPolicyBinding) field.ErrorList { - return validateValidatingAdmissionPolicyBinding(newC) -} - -func validateValidatingAdmissionPolicyStatus(status *admissionregistration.ValidatingAdmissionPolicyStatus, fldPath *field.Path) field.ErrorList { - var allErrors field.ErrorList - allErrors = append(allErrors, validateTypeChecking(status.TypeChecking, fldPath.Child("typeChecking"))...) - allErrors = append(allErrors, metav1validation.ValidateConditions(status.Conditions, fldPath.Child("conditions"))...) - return allErrors -} - -func validateTypeChecking(typeChecking *admissionregistration.TypeChecking, fldPath *field.Path) field.ErrorList { - if typeChecking == nil { - return nil - } - return validateExpressionWarnings(typeChecking.ExpressionWarnings, fldPath.Child("expressionWarnings")) -} - -func validateExpressionWarnings(expressionWarnings []admissionregistration.ExpressionWarning, fldPath *field.Path) field.ErrorList { - var allErrors field.ErrorList - for i, warning := range expressionWarnings { - allErrors = append(allErrors, validateExpressionWarning(&warning, fldPath.Index(i))...) - } - return allErrors -} - -func validateExpressionWarning(expressionWarning *admissionregistration.ExpressionWarning, fldPath *field.Path) field.ErrorList { - var allErrors field.ErrorList - if expressionWarning.Warning == "" { - allErrors = append(allErrors, field.Required(fldPath.Child("warning"), "")) - } - allErrors = append(allErrors, validateFieldRef(expressionWarning.FieldRef, fldPath.Child("fieldRef"))...) - return allErrors -} - -func validateFieldRef(fieldRef string, fldPath *field.Path) field.ErrorList { - fieldRef = strings.TrimSpace(fieldRef) - if fieldRef == "" { - return field.ErrorList{field.Required(fldPath, "")} - } - jsonPath := jsonpath.New("spec") - if err := jsonPath.Parse(fmt.Sprintf("{%s}", fieldRef)); err != nil { - return field.ErrorList{field.Invalid(fldPath, fieldRef, fmt.Sprintf("invalid JSONPath: %v", err))} - } - // no further checks, for an easier upgrade/rollback - return nil -} - // statelessCELCompiler does not support variable composition (and thus is stateless). It should be used when // variable composition is not allowed, for example, when validating MatchConditions. // strictStatelessCELCompiler is a cel Compiler that enforces strict cost enforcement. @@ -1281,41 +138,3 @@ var ( strictStatelessCELCompiler = plugincel.NewCompiler(environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion(), true)) nonStrictStatelessCELCompiler = plugincel.NewCompiler(environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion(), false)) ) - -func createCompiler(allowComposition, strictCost bool) plugincel.Compiler { - if !allowComposition { - if strictCost { - return strictStatelessCELCompiler - } else { - return nonStrictStatelessCELCompiler - } - } - compiler, err := plugincel.NewCompositedCompiler(environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion(), strictCost)) - if err != nil { - // should never happen, but cannot panic either. - utilruntime.HandleError(err) - return plugincel.NewCompiler(environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion(), strictCost)) - } - return compiler -} - -var ( - celIdentRegex = regexp.MustCompile("^[_a-zA-Z][_a-zA-Z0-9]*$") - celReserved = sets.NewString("true", "false", "null", "in", - "as", "break", "const", "continue", "else", - "for", "function", "if", "import", "let", - "loop", "package", "namespace", "return", - "var", "void", "while") -) - -func isCELIdentifier(name string) bool { - // IDENT ::= [_a-zA-Z][_a-zA-Z0-9]* - RESERVED - // BOOL_LIT ::= "true" | "false" - // NULL_LIT ::= "null" - // RESERVED ::= BOOL_LIT | NULL_LIT | "in" - // | "as" | "break" | "const" | "continue" | "else" - // | "for" | "function" | "if" | "import" | "let" - // | "loop" | "package" | "namespace" | "return" - // | "var" | "void" | "while" - return celIdentRegex.MatchString(name) && !celReserved.Has(name) -} From 2b03e8b2f539e8f8697742eb77ec36816d03baaa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Cuadrado=20Juan?= Date: Fri, 19 Jul 2024 10:02:06 +0200 Subject: [PATCH 03/10] deps: Add needed deps for validation_matchconditions.go MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ``` $ go get k8s.io/apiserver/pkg/admission/plugin/cel go: upgraded k8s.io/api v0.30.2 => v0.30.3 go: upgraded k8s.io/apimachinery v0.30.2 => v0.30.3 go: upgraded k8s.io/apiserver v0.30.1 => v0.30.3 go: upgraded k8s.io/client-go v0.30.1 => v0.30.3 go: upgraded k8s.io/component-base v0.30.1 => v0.30.3 $ go get k8s.io/apiserver/pkg/admission/plugin/webhook/matchconditions $ go get k8s.io/apiserver/pkg/cel $ go get k8s.io/kubernetes/pkg/apis/admissionregistration ``` Signed-off-by: Víctor Cuadrado Juan --- go.mod | 14 ++++-- go.sum | 156 ++++++++------------------------------------------------- 2 files changed, 29 insertions(+), 141 deletions(-) diff --git a/go.mod b/go.mod index 5e61e4af..d5beff46 100644 --- a/go.mod +++ b/go.mod @@ -6,11 +6,9 @@ toolchain go1.22.5 require ( github.com/go-logr/logr v1.4.2 - github.com/google/go-cmp v0.6.0 github.com/onsi/ginkgo/v2 v2.19.0 github.com/onsi/gomega v1.33.1 github.com/stretchr/testify v1.9.0 - github.com/testcontainers/testcontainers-go v0.32.0 github.com/testcontainers/testcontainers-go/modules/k3s v0.32.0 go.opentelemetry.io/otel v1.28.0 go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.28.0 @@ -18,7 +16,8 @@ require ( go.opentelemetry.io/otel/sdk/metric v1.28.0 k8s.io/api v0.30.3 k8s.io/apimachinery v0.30.3 - k8s.io/client-go v0.30.1 + k8s.io/apiserver v0.30.3 + k8s.io/client-go v0.30.3 k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 sigs.k8s.io/controller-runtime v0.18.4 ) @@ -28,7 +27,9 @@ require ( github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect github.com/Microsoft/hcsshim v0.11.5 // indirect + github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230305170008-8188dc5388df // indirect github.com/beorn7/perks v1.0.1 // indirect + github.com/blang/semver/v4 v4.0.0 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/containerd/containerd v1.7.18 // indirect @@ -55,7 +56,9 @@ require ( github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.4 // indirect + github.com/google/cel-go v0.17.8 // indirect github.com/google/gnostic-models v0.6.8 // indirect + github.com/google/go-cmp v0.6.0 // indirect github.com/google/gofuzz v1.2.0 // indirect github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6 // indirect github.com/google/uuid v1.6.0 // indirect @@ -89,6 +92,8 @@ require ( github.com/shoenig/go-m1cpu v0.1.6 // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/spf13/pflag v1.0.5 // indirect + github.com/stoewer/go-strcase v1.2.0 // indirect + github.com/testcontainers/testcontainers-go v0.32.0 // indirect github.com/tklauser/go-sysconf v0.3.12 // indirect github.com/tklauser/numcpus v0.6.1 // indirect github.com/yusufpapurcu/wmi v1.2.3 // indirect @@ -100,7 +105,6 @@ require ( go.uber.org/zap v1.27.0 // indirect golang.org/x/crypto v0.24.0 // indirect golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8 // indirect - golang.org/x/mod v0.17.0 // indirect golang.org/x/net v0.26.0 // indirect golang.org/x/oauth2 v0.20.0 // indirect golang.org/x/sync v0.7.0 // indirect @@ -110,7 +114,6 @@ require ( golang.org/x/time v0.5.0 // indirect golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect - google.golang.org/appengine v1.6.8 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 // indirect google.golang.org/grpc v1.64.0 // indirect @@ -119,6 +122,7 @@ require ( gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/apiextensions-apiserver v0.30.1 // indirect + k8s.io/component-base v0.30.3 // indirect k8s.io/klog/v2 v2.120.1 // indirect k8s.io/kube-openapi v0.0.0-20240322212309-b815d8309940 // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect diff --git a/go.sum b/go.sum index bad66a5b..e748a3bd 100644 --- a/go.sum +++ b/go.sum @@ -4,22 +4,20 @@ github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9 github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= -github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= -github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= -github.com/Microsoft/hcsshim v0.11.4 h1:68vKo2VN8DE9AdN4tnkWnmdhqdbpUFM8OF3Airm7fz8= -github.com/Microsoft/hcsshim v0.11.4/go.mod h1:smjE4dvqPX9Zldna+t5FG3rnoHhaB7QYxPRqGcpAD9w= github.com/Microsoft/hcsshim v0.11.5 h1:haEcLNpj9Ka1gd3B3tAEs9CpE0c+1IhoL59w/exYU38= github.com/Microsoft/hcsshim v0.11.5/go.mod h1:MV8xMfmECjl5HdO7U/3/hFVnkmSBjAjmA09d4bExKcU= +github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230305170008-8188dc5388df h1:7RFfzj4SSt6nnvCPbCqijJi1nWCd+TqAT3bYCStRC18= +github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230305170008-8188dc5388df/go.mod h1:pSwJ0fSY5KhvocuWSx4fz3BA8OrA1bQn+K1Eli3BRwM= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= +github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/containerd/containerd v1.7.15 h1:afEHXdil9iAm03BmhjzKyXnnEBtjaLJefdU7DV0IFes= -github.com/containerd/containerd v1.7.15/go.mod h1:ISzRRTMF8EXNpJlTzyr2XMhN+j9K302C21/+cr3kUnY= github.com/containerd/containerd v1.7.18 h1:jqjZTQNfXGoEaZdW1WwPU0RqSn1Bm2Ay/KJPUuO8nao= github.com/containerd/containerd v1.7.18/go.mod h1:IYEk9/IO6wAPUz2bCMVUbsfXjzw5UNP5fLz4PsUygQ4= github.com/containerd/errdefs v0.1.0 h1:m0wCRBiu1WJT/Fr+iOoQHMQS/eP5myQ8lCv4Dz5ZURM= @@ -33,12 +31,8 @@ github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0= -github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/docker v26.0.2+incompatible h1:yGVmKUFGgcxA6PXWAokO0sQL22BrQ67cgVjko8tGdXE= -github.com/docker/docker v26.0.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker v27.0.3+incompatible h1:aBGI9TeQ4MPlhquTQKq9XbK79rKFVwXNUAYz9aXyEBE= github.com/docker/docker v27.0.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= @@ -56,8 +50,6 @@ github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSw github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= -github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= @@ -78,13 +70,12 @@ github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/cel-go v0.17.8 h1:j9m730pMZt1Fc4oKhCLUHfjj6527LuhYcYw0Rl8gqto= +github.com/google/cel-go v0.17.8/go.mod h1:HXZKzB0LXqer5lHHgfWAnlYwJaQBDKMjxjulNQzhwhY= github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= @@ -96,8 +87,6 @@ github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6 h1:k7nVchz72niMH6YLQN github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 h1:/c3QmbOGMGTOumP2iT/rCwB7b0QDGLKzqOmktBjT+Is= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1/go.mod h1:5SN9VR2LTsRFsrEC6FHgRbTWrTHu6tqPeKxEQv15giM= github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0= github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k= github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= @@ -108,8 +97,6 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.16.0 h1:iULayQNOReoYUe+1qtKOqw9CwJv3aNQu8ivo7lw1HU4= -github.com/klauspost/compress v1.16.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= @@ -141,8 +128,6 @@ github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/onsi/ginkgo/v2 v2.17.3 h1:oJcvKpIb7/8uLpDDtnQuf18xVnwKp8DTD7DQ6gTd/MU= -github.com/onsi/ginkgo/v2 v2.17.3/go.mod h1:nP2DPOQoNsQmsVyv5rDA8JkXQoCs6goXIvr/PRJ1eCc= github.com/onsi/ginkgo/v2 v2.19.0 h1:9Cnnf7UHo57Hy3k6/m5k3dRfGTMXGvxhHFvkDTCTpvA= github.com/onsi/ginkgo/v2 v2.19.0/go.mod h1:rlwLi9PilAFJ8jCg9UE1QP6VBpd6/xj3SRC0d6TU0To= github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk= @@ -165,8 +150,8 @@ github.com/prometheus/common v0.51.1 h1:eIjN50Bwglz6a/c3hAgSMcofL3nD+nFQkV6Dd4Ds github.com/prometheus/common v0.51.1/go.mod h1:lrWtQx+iDfn2mbH5GUzlH9TSHyfZpHkSiG1W7y3sF2Q= github.com/prometheus/procfs v0.13.0 h1:GqzLlQyfsPbaEHaQkO7tbDlriv/4o5Hudv6OXHGKX7o= github.com/prometheus/procfs v0.13.0/go.mod h1:cd4PFCR54QLnGKPaKGA6l+cfuNXtht43ZKY6tow0Y1g= -github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= -github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/shirou/gopsutil/v3 v3.23.12 h1:z90NtUkp3bMtmICZKpC4+WaknU1eXtp5vtbQ11DgpE4= github.com/shirou/gopsutil/v3 v3.23.12/go.mod h1:1FrWgea594Jp7qmjHUUPlJDTPgcsb9mGnXDxavtikzM= github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= @@ -177,22 +162,21 @@ github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stoewer/go-strcase v1.2.0 h1:Z2iHWqGXH00XYgqDmNgQbIBxf3wrNq0F3feEy0ainaU= +github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/testcontainers/testcontainers-go v0.31.0 h1:W0VwIhcEVhRflwL9as3dhY6jXjVCA27AkmbnZ+UTh3U= -github.com/testcontainers/testcontainers-go v0.31.0/go.mod h1:D2lAoA0zUFiSY+eAflqK5mcUx/A5hrrORaEQrd0SefI= github.com/testcontainers/testcontainers-go v0.32.0 h1:ug1aK08L3gCHdhknlTTwWjPHPS+/alvLJU/DRxTD/ME= github.com/testcontainers/testcontainers-go v0.32.0/go.mod h1:CRHrzHLQhlXUsa5gXjTOfqIEJcrK5+xMDmBr/WMI88E= -github.com/testcontainers/testcontainers-go/modules/k3s v0.31.0 h1:Yw9KASuwVix5a9dv2zR4j4/y1aJxRvQOfWw1E6Z8gFg= -github.com/testcontainers/testcontainers-go/modules/k3s v0.31.0/go.mod h1:v+3E2oXvTQd7z9I+AzfegGOqMW3r7oR8ohjzv2UktWs= github.com/testcontainers/testcontainers-go/modules/k3s v0.32.0 h1:Z3DTMveNUqeGJZ+CXZhpvI7OF1BS71Ywi3SwoXLZ4Lc= github.com/testcontainers/testcontainers-go/modules/k3s v0.32.0/go.mod h1:SYp1WtvNc3n/cg5atO6LvaOd2aqkQYMSDCcWPOUdaZg= github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= @@ -201,53 +185,26 @@ github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+F github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw= github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw= -go.opentelemetry.io/otel v1.26.0 h1:LQwgL5s/1W7YiiRwxf03QGnWLb2HW4pLiAhaA5cZXBs= -go.opentelemetry.io/otel v1.26.0/go.mod h1:UmLkJHUAidDval2EICqBMbnAd0/m2vmpf/dAM+fvFs4= -go.opentelemetry.io/otel v1.27.0 h1:9BZoF3yMK/O1AafMiQTVu0YDj5Ea4hPhxCs7sGva+cg= -go.opentelemetry.io/otel v1.27.0/go.mod h1:DMpAK8fzYRzs+bi3rS5REupisuqTheUlSZJ1WnZaPAQ= go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo= go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.26.0 h1:+hm+I+KigBy3M24/h1p/NHkUx/evbLH0PNcjpMyCHc4= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.26.0/go.mod h1:NjC8142mLvvNT6biDpaMjyz78kyEHIwAJlSX0N9P5KI= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.27.0 h1:bFgvUr3/O4PHj3VQcFEuYKvRZJX1SJDQ+11JXuSB3/w= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.27.0/go.mod h1:xJntEd2KL6Qdg5lwp97HMLQDVeAhrYxmzFseAMDPQ8I= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.28.0 h1:U2guen0GhqH8o/G2un8f/aG/y++OuW6MyCo6hT9prXk= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.28.0/go.mod h1:yeGZANgEcpdx/WK0IvvRFC+2oLiMS2u4L/0Rj2M2Qr0= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0 h1:Mne5On7VWdx7omSrSSZvM4Kw7cS7NQkOOmLcgscI51U= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0/go.mod h1:IPtUMKL4O3tH5y+iXVyAXqpAwMuzC1IrxVS81rummfE= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 h1:IeMeyr1aBvBiPVYihXIaeIZba6b8E1bYp7lbdxK8CQg= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0/go.mod h1:oVdCUtjq9MK9BlS7TtucsQwUcXcymNiEDjgDD2jMtZU= -go.opentelemetry.io/otel/metric v1.26.0 h1:7S39CLuY5Jgg9CrnA9HHiEjGMF/X2VHvoXGgSllRz30= -go.opentelemetry.io/otel/metric v1.26.0/go.mod h1:SY+rHOI4cEawI9a7N1A4nIg/nTQXe1ccCNWYOJUrpX4= -go.opentelemetry.io/otel/metric v1.27.0 h1:hvj3vdEKyeCi4YaYfNjv2NUje8FqKqUY8IlF0FxV/ik= -go.opentelemetry.io/otel/metric v1.27.0/go.mod h1:mVFgmRlhljgBiuk/MP/oKylr4hs85GZAylncepAX/ak= go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q= go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s= -go.opentelemetry.io/otel/sdk v1.26.0 h1:Y7bumHf5tAiDlRYFmGqetNcLaVUZmh4iYfmGxtmz7F8= -go.opentelemetry.io/otel/sdk v1.26.0/go.mod h1:0p8MXpqLeJ0pzcszQQN4F0S5FVjBLgypeGSngLsmirs= -go.opentelemetry.io/otel/sdk v1.27.0 h1:mlk+/Y1gLPLn84U4tI8d3GNJmGT/eXe3ZuOXN9kTWmI= -go.opentelemetry.io/otel/sdk v1.27.0/go.mod h1:Ha9vbLwJE6W86YstIywK2xFfPjbWlCuwPtMkKdz/Y4A= go.opentelemetry.io/otel/sdk v1.28.0 h1:b9d7hIry8yZsgtbmM0DKyPWMMUMlK9NEKuIG4aBqWyE= go.opentelemetry.io/otel/sdk v1.28.0/go.mod h1:oYj7ClPUA7Iw3m+r7GeEjz0qckQRJK2B8zjcZEfu7Pg= -go.opentelemetry.io/otel/sdk/metric v1.26.0 h1:cWSks5tfriHPdWFnl+qpX3P681aAYqlZHcAyHw5aU9Y= -go.opentelemetry.io/otel/sdk/metric v1.26.0/go.mod h1:ClMFFknnThJCksebJwz7KIyEDHO+nTB6gK8obLy8RyE= -go.opentelemetry.io/otel/sdk/metric v1.27.0 h1:5uGNOlpXi+Hbo/DRoI31BSb1v+OGcpv2NemcCrOL8gI= -go.opentelemetry.io/otel/sdk/metric v1.27.0/go.mod h1:we7jJVrYN2kh3mVBlswtPU22K0SA+769l93J6bsyvqw= go.opentelemetry.io/otel/sdk/metric v1.28.0 h1:OkuaKgKrgAbYrrY0t92c+cC+2F6hsFNnCQArXCKlg08= go.opentelemetry.io/otel/sdk/metric v1.28.0/go.mod h1:cWPjykihLAPvXKi4iZc1dpER3Jdq2Z0YLse3moQUCpg= -go.opentelemetry.io/otel/trace v1.26.0 h1:1ieeAUb4y0TE26jUFrCIXKpTuVK7uJGN9/Z/2LP5sQA= -go.opentelemetry.io/otel/trace v1.26.0/go.mod h1:4iDxvGDQuUkHve82hJJ8UqrwswHYsZuWCBllGV2U2y0= -go.opentelemetry.io/otel/trace v1.27.0 h1:IqYb813p7cmbHk0a5y6pD5JPakbVfftRXABGt5/Rscw= -go.opentelemetry.io/otel/trace v1.27.0/go.mod h1:6RiD1hkAprV4/q+yd2ln1HG9GoPx39SuvvstaLBl+l4= go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g= go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= -go.opentelemetry.io/proto/otlp v1.2.0 h1:pVeZGk7nXDC9O2hncA6nHldxEjm6LByfA2aN8IOkz94= -go.opentelemetry.io/proto/otlp v1.2.0/go.mod h1:gGpR8txAl5M03pDhMC79G6SdqNV26naRm/KDsgaHD8A= go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= @@ -259,78 +216,41 @@ go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= -golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= -golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= -golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI= golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8 h1:aAcj0Da7eBAtrTp03QXWvm88pSyOt+UgdZw2BFZ+lEw= golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8/go.mod h1:CQ1k9gNrJ50XIzaKCRR2hssIjF07kZFEiieALBM/ARQ= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= -golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= -golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= -golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= -golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= -golang.org/x/oauth2 v0.18.0 h1:09qnuIAgzdx1XplqJvW6CQqMCtGZykZWcXzPMPUusvI= -golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8= golang.org/x/oauth2 v0.20.0 h1:4mQdhULixXKP1rwYBW0vAijoXnkTG0BLCDRzfe1idMo= golang.org/x/oauth2 v0.20.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= -golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= -golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q= -golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= -golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw= -golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA= golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= -golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= @@ -339,11 +259,6 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.20.0 h1:hz/CVckiOxybQvFw6h7b/q80NTr9IUQb4s1IIzW7KNY= -golang.org/x/tools v0.20.0/go.mod h1:WvitBU7JJf6A4jOdg4S1tviW9bhUxkgeCui/0JHctQg= -golang.org/x/tools v0.21.0 h1:qc0xYgIbsSDt9EyWz05J5wfa7LOVW0YTLOXrqdLAWIw= -golang.org/x/tools v0.21.0/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -352,30 +267,12 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw= gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= -google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= -google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= -google.golang.org/genproto/googleapis/api v0.0.0-20240325203815-454cdb8f5daa h1:Jt1XW5PaLXF1/ePZrznsh/aAUvI7Adfc3LY1dAKlzRs= -google.golang.org/genproto/googleapis/api v0.0.0-20240325203815-454cdb8f5daa/go.mod h1:K4kfzHtI0kqWA79gecJarFtDn/Mls+GxQcg3Zox91Ac= -google.golang.org/genproto/googleapis/api v0.0.0-20240520151616-dc85e6b867a5 h1:P8OJ/WCl/Xo4E4zoe4/bifHpSmmKwARqyqE4nW6J2GQ= -google.golang.org/genproto/googleapis/api v0.0.0-20240520151616-dc85e6b867a5/go.mod h1:RGnPtTG7r4i8sPlNyDeikXF99hMM+hN6QMm4ooG9g2g= google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094 h1:0+ozOGcrp+Y8Aq8TLNN2Aliibms5LEzsq99ZZmAGYm0= google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094/go.mod h1:fJ/e3If/Q67Mj99hin0hMhiNyCRmt6BQ2aWIJshUSJw= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda h1:LI5DOvAxUPMv/50agcLLoo+AdWc1irS9Rzz4vPuD1V4= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240515191416-fc5f0ca64291 h1:AgADTJarZTBqgjiUzRgfaBchgYB3/WFTC80GPwsMcRI= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240515191416-fc5f0ca64291/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0= google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 h1:BwIjyKYGsK9dMCBOorzRri8MQwmi7mT9rGHsCEinZkA= google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY= -google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM= -google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA= google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY= google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= -google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= -google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= -google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -383,46 +280,33 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntN gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gotest.tools/v3 v3.5.0 h1:Ljk6PdHdOhAb5aDMWXjDLMMhph+BpztA4v1QdqEW2eY= -gotest.tools/v3 v3.5.0/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= -k8s.io/api v0.30.1 h1:kCm/6mADMdbAxmIh0LBjS54nQBE+U4KmbCfIkF5CpJY= -k8s.io/api v0.30.1/go.mod h1:ddbN2C0+0DIiPntan/bye3SW3PdwLa11/0yqwvuRrJM= -k8s.io/api v0.30.2 h1:+ZhRj+28QT4UOH+BKznu4CBgPWgkXO7XAvMcMl0qKvI= -k8s.io/api v0.30.2/go.mod h1:ULg5g9JvOev2dG0u2hig4Z7tQ2hHIuS+m8MNZ+X6EmI= +gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= +gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= k8s.io/api v0.30.3 h1:ImHwK9DCsPA9uoU3rVh4QHAHHK5dTSv1nxJUapx8hoQ= k8s.io/api v0.30.3/go.mod h1:GPc8jlzoe5JG3pb0KJCSLX5oAFIW3/qNJITlDj8BH04= -k8s.io/apiextensions-apiserver v0.30.0 h1:jcZFKMqnICJfRxTgnC4E+Hpcq8UEhT8B2lhBcQ+6uAs= -k8s.io/apiextensions-apiserver v0.30.0/go.mod h1:N9ogQFGcrbWqAY9p2mUAL5mGxsLqwgtUce127VtRX5Y= k8s.io/apiextensions-apiserver v0.30.1 h1:4fAJZ9985BmpJG6PkoxVRpXv9vmPUOVzl614xarePws= k8s.io/apiextensions-apiserver v0.30.1/go.mod h1:R4GuSrlhgq43oRY9sF2IToFh7PVlF1JjfWdoG3pixk4= -k8s.io/apimachinery v0.30.1 h1:ZQStsEfo4n65yAdlGTfP/uSHMQSoYzU/oeEbkmF7P2U= -k8s.io/apimachinery v0.30.1/go.mod h1:iexa2somDaxdnj7bha06bhb43Zpa6eWH8N8dbqVjTUc= -k8s.io/apimachinery v0.30.2 h1:fEMcnBj6qkzzPGSVsAZtQThU62SmQ4ZymlXRC5yFSCg= -k8s.io/apimachinery v0.30.2/go.mod h1:iexa2somDaxdnj7bha06bhb43Zpa6eWH8N8dbqVjTUc= k8s.io/apimachinery v0.30.3 h1:q1laaWCmrszyQuSQCfNB8cFgCuDAoPszKY4ucAjDwHc= k8s.io/apimachinery v0.30.3/go.mod h1:iexa2somDaxdnj7bha06bhb43Zpa6eWH8N8dbqVjTUc= -k8s.io/client-go v0.30.0 h1:sB1AGGlhY/o7KCyCEQ0bPWzYDL0pwOZO4vAtTSh/gJQ= -k8s.io/client-go v0.30.0/go.mod h1:g7li5O5256qe6TYdAMyX/otJqMhIiGgTapdLchhmOaY= -k8s.io/client-go v0.30.1 h1:uC/Ir6A3R46wdkgCV3vbLyNOYyCJ8oZnjtJGKfytl/Q= -k8s.io/client-go v0.30.1/go.mod h1:wrAqLNs2trwiCH/wxxmT/x3hKVH9PuV0GGW0oDoHVqc= +k8s.io/apiserver v0.30.3 h1:QZJndA9k2MjFqpnyYv/PH+9PE0SHhx3hBho4X0vE65g= +k8s.io/apiserver v0.30.3/go.mod h1:6Oa88y1CZqnzetd2JdepO0UXzQX4ZnOekx2/PtEjrOg= +k8s.io/client-go v0.30.3 h1:bHrJu3xQZNXIi8/MoxYtZBBWQQXwy16zqJwloXXfD3k= +k8s.io/client-go v0.30.3/go.mod h1:8d4pf8vYu665/kUbsxWAQ/JDBNWqfFeZnvFiVdmx89U= +k8s.io/component-base v0.30.3 h1:Ci0UqKWf4oiwy8hr1+E3dsnliKnkMLZMVbWzeorlk7s= +k8s.io/component-base v0.30.3/go.mod h1:C1SshT3rGPCuNtBs14RmVD2xW0EhRSeLvBh7AGk1quA= k8s.io/klog/v2 v2.120.1 h1:QXU6cPEOIslTGvZaXvFWiP9VKyeet3sawzTOvdXb4Vw= k8s.io/klog/v2 v2.120.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/kube-openapi v0.0.0-20240322212309-b815d8309940 h1:qVoMaQV5t62UUvHe16Q3eb2c5HPzLHYzsi0Tu/xLndo= k8s.io/kube-openapi v0.0.0-20240322212309-b815d8309940/go.mod h1:yD4MZYeKMBwQKVht279WycxKyM84kkAx2DPrTXaeb98= -k8s.io/utils v0.0.0-20240310230437-4693a0247e57 h1:gbqbevonBh57eILzModw6mrkbwM0gQBEuevE/AaBsHY= -k8s.io/utils v0.0.0-20240310230437-4693a0247e57/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 h1:pUdcCO1Lk/tbT5ztQWOBi5HBgbBP1J8+AsQnQCKsi8A= k8s.io/utils v0.0.0-20240711033017-18e509b52bc8/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -sigs.k8s.io/controller-runtime v0.18.2 h1:RqVW6Kpeaji67CY5nPEfRz6ZfFMk0lWQlNrLqlNpx+Q= -sigs.k8s.io/controller-runtime v0.18.2/go.mod h1:tuAt1+wbVsXIT8lPtk5RURxqAnq7xkpv2Mhttslg7Hw= -sigs.k8s.io/controller-runtime v0.18.3 h1:B5Wmmo8WMWK7izei+2LlXLVDGzMwAHBNLX68lwtlSR4= -sigs.k8s.io/controller-runtime v0.18.3/go.mod h1:TVoGrfdpbA9VRFaRnKgk9P5/atA0pMwq+f+msb9M8Sg= sigs.k8s.io/controller-runtime v0.18.4 h1:87+guW1zhvuPLh1PHybKdYFLU0YJp4FhJRmiHvm5BZw= sigs.k8s.io/controller-runtime v0.18.4/go.mod h1:TVoGrfdpbA9VRFaRnKgk9P5/atA0pMwq+f+msb9M8Sg= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= From a401ac9353c2fc67c97a43c37e55541e5c40da14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Cuadrado=20Juan?= Date: Fri, 19 Jul 2024 11:08:12 +0200 Subject: [PATCH 04/10] refactor: Simplify deps for validateMatchCondition() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Substitute: `apivalidation.ValidateQualifiedName()` that uses `"k8s.io/kubernetes/pkg/apis/core/validation"`, which isn't supported for public consumption with: `validation.IsQualifiedName()` that uses `"k8s.io/apimachinery/pkg/util/validation"` The substituted function was just calling the one supported for public consumption anyways. This saves us from needing to deal with packages that are unsupported for public consumption (those with version `v0.0.0`), needing dozens of replaces, and pulling ~10 more of those packages. There were also incompatibilities with versions there and our consumption from other dependencies, e.g: ``` $ go vet ./... ../../../code/go/pkg/mod/k8s.io/component-base@v0.30.3/metrics/testutil/metrics.go:73:59: undefined: expfmt.FmtText ``` Signed-off-by: Víctor Cuadrado Juan --- api/policies/v1/policy_validation_matchconditions.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/api/policies/v1/policy_validation_matchconditions.go b/api/policies/v1/policy_validation_matchconditions.go index 42cdd1e4..8cfaf1e0 100644 --- a/api/policies/v1/policy_validation_matchconditions.go +++ b/api/policies/v1/policy_validation_matchconditions.go @@ -28,13 +28,13 @@ import ( "strings" "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/apimachinery/pkg/util/validation" "k8s.io/apimachinery/pkg/util/validation/field" plugincel "k8s.io/apiserver/pkg/admission/plugin/cel" "k8s.io/apiserver/pkg/admission/plugin/webhook/matchconditions" "k8s.io/apiserver/pkg/cel" "k8s.io/apiserver/pkg/cel/environment" "k8s.io/kubernetes/pkg/apis/admissionregistration" - apivalidation "k8s.io/kubernetes/pkg/apis/core/validation" ) type validationOptions struct { @@ -82,7 +82,9 @@ func validateMatchCondition(v *admissionregistration.MatchCondition, opts valida if len(v.Name) == 0 { allErrors = append(allErrors, field.Required(fldPath.Child("name"), "")) } else { - allErrors = append(allErrors, apivalidation.ValidateQualifiedName(v.Name, fldPath.Child("name"))...) + for _, msg := range validation.IsQualifiedName(v.Name) { + allErrors = append(allErrors, field.Invalid(fldPath, v.Name, msg)) + } } return allErrors } From ee44b65f10b0fab704e7d13c4fd91f1b4e52d677 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Cuadrado=20Juan?= Date: Mon, 22 Jul 2024 16:23:01 +0200 Subject: [PATCH 05/10] refactor: Use admissionregistration/v1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use public package instead of internal one. Signed-off-by: Víctor Cuadrado Juan --- api/policies/v1/policy_validation_matchconditions.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/api/policies/v1/policy_validation_matchconditions.go b/api/policies/v1/policy_validation_matchconditions.go index 8cfaf1e0..8a67dfdd 100644 --- a/api/policies/v1/policy_validation_matchconditions.go +++ b/api/policies/v1/policy_validation_matchconditions.go @@ -27,6 +27,7 @@ import ( "fmt" "strings" + admissionregistrationv1 "k8s.io/api/admissionregistration/v1" "k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/validation" "k8s.io/apimachinery/pkg/util/validation/field" @@ -34,7 +35,6 @@ import ( "k8s.io/apiserver/pkg/admission/plugin/webhook/matchconditions" "k8s.io/apiserver/pkg/cel" "k8s.io/apiserver/pkg/cel/environment" - "k8s.io/kubernetes/pkg/apis/admissionregistration" ) type validationOptions struct { @@ -52,7 +52,7 @@ type preexistingExpressions struct { matchConditionExpressions sets.Set[string] } -func validateMatchConditions(m []admissionregistration.MatchCondition, opts validationOptions, fldPath *field.Path) field.ErrorList { +func validateMatchConditions(m []admissionregistrationv1.MatchCondition, opts validationOptions, fldPath *field.Path) field.ErrorList { var allErrors field.ErrorList conditionNames := sets.NewString() if len(m) > 64 { @@ -71,7 +71,7 @@ func validateMatchConditions(m []admissionregistration.MatchCondition, opts vali return allErrors } -func validateMatchCondition(v *admissionregistration.MatchCondition, opts validationOptions, fldPath *field.Path) field.ErrorList { +func validateMatchCondition(v *admissionregistrationv1.MatchCondition, opts validationOptions, fldPath *field.Path) field.ErrorList { var allErrors field.ErrorList trimmedExpression := strings.TrimSpace(v.Expression) if len(trimmedExpression) == 0 { From 82f822e2d1139ea097eb7de13280abd9c9dd1b69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Cuadrado=20Juan?= Date: Mon, 22 Jul 2024 17:55:06 +0200 Subject: [PATCH 06/10] feat: Return aggregate errors on policy CR validation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On policy CR validation for validatePolicyCreate() and validatePolicyUpdate(), aggregate all error messages in the returned apierrors.StatusError, not only the first. This simplifies validatePolicyCreate() expansion. Signed-off-by: Víctor Cuadrado Juan --- api/policies/v1/admissionpolicy_webhook.go | 11 +++++++- .../v1/clusteradmissionpolicy_webhook.go | 10 ++++++- api/policies/v1/policy_validation.go | 27 +++++++++---------- api/policies/v1/policy_validation_test.go | 5 ++-- 4 files changed, 35 insertions(+), 18 deletions(-) diff --git a/api/policies/v1/admissionpolicy_webhook.go b/api/policies/v1/admissionpolicy_webhook.go index 3f851de9..58cef7c8 100644 --- a/api/policies/v1/admissionpolicy_webhook.go +++ b/api/policies/v1/admissionpolicy_webhook.go @@ -19,6 +19,7 @@ import ( apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/validation/field" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" logf "sigs.k8s.io/controller-runtime/pkg/log" @@ -65,7 +66,15 @@ var _ webhook.Validator = &AdmissionPolicy{} // ValidateCreate implements webhook.Validator so a webhook will be registered for the type. func (r *AdmissionPolicy) ValidateCreate() (admission.Warnings, error) { admissionpolicylog.Info("validate create", "name", r.Name) - return nil, validateRulesField(r) + errList := field.ErrorList{} + + if errs := validateRulesField(r); len(errs) != 0 { + errList = append(errList, errs...) + } + if len(errList) != 0 { + return nil, prepareInvalidAPIError(r, errList) + } + return nil, nil } // ValidateUpdate implements webhook.Validator so a webhook will be registered for the type. diff --git a/api/policies/v1/clusteradmissionpolicy_webhook.go b/api/policies/v1/clusteradmissionpolicy_webhook.go index 755af7e1..dc5eb385 100644 --- a/api/policies/v1/clusteradmissionpolicy_webhook.go +++ b/api/policies/v1/clusteradmissionpolicy_webhook.go @@ -18,6 +18,7 @@ import ( "fmt" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/validation/field" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" "sigs.k8s.io/controller-runtime/pkg/webhook" @@ -67,8 +68,15 @@ var _ webhook.Validator = &ClusterAdmissionPolicy{} // ValidateCreate implements webhook.Validator so a webhook will be registered for the type. func (r *ClusterAdmissionPolicy) ValidateCreate() (admission.Warnings, error) { clusteradmissionpolicylog.Info("validate create", "name", r.Name) + errList := field.ErrorList{} - return nil, validateRulesField(r) + if errs := validateRulesField(r); len(errs) != 0 { + errList = append(errList, errs...) + } + if len(errList) != 0 { + return nil, prepareInvalidAPIError(r, errList) + } + return nil, nil } // ValidateUpdate implements webhook.Validator so a webhook will be registered for the type. diff --git a/api/policies/v1/policy_validation.go b/api/policies/v1/policy_validation.go index a6e8d0ba..7ae9eaaf 100644 --- a/api/policies/v1/policy_validation.go +++ b/api/policies/v1/policy_validation.go @@ -19,21 +19,20 @@ package v1 import ( admissionregistrationv1 "k8s.io/api/admissionregistration/v1" - "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/validation/field" apierrors "k8s.io/apimachinery/pkg/api/errors" ) // Validates the spec.Rules field for non-empty, webhook-valid rules. -func validateRulesField(policy Policy) error { +func validateRulesField(policy Policy) field.ErrorList { errs := field.ErrorList{} rulesField := field.NewPath("spec", "rules") if len(policy.GetRules()) == 0 { errs = append(errs, field.Required(rulesField, "a value must be specified")) - return prepareInvalidAPIError(policy, errs) + return errs } for _, rule := range policy.GetRules() { @@ -59,7 +58,7 @@ func validateRulesField(policy Policy) error { } if len(errs) != 0 { - return prepareInvalidAPIError(policy, errs) + return errs } return nil @@ -101,8 +100,11 @@ func prepareInvalidAPIError(policy Policy, errorList field.ErrorList) *apierrors } func validatePolicyUpdate(oldPolicy, newPolicy Policy) error { - if err := validateRulesField(newPolicy); err != nil { - return err + errList := field.ErrorList{} + + if errs := validateRulesField(newPolicy); len(errs) != 0 { + errList = append(errList, errs...) + } } if newPolicy.GetPolicyServer() != oldPolicy.GetPolicyServer() { @@ -110,10 +112,7 @@ func validatePolicyUpdate(oldPolicy, newPolicy Policy) error { p := field.NewPath("spec") pp := p.Child("policyServer") errs = append(errs, field.Forbidden(pp, "the field is immutable")) - - return apierrors.NewInvalid( - schema.GroupKind{Group: GroupVersion.Group, Kind: "ClusterAdmissionPolicy"}, - newPolicy.GetName(), errs) + errList = append(errList, errs...) } if newPolicy.GetPolicyMode() == "monitor" && oldPolicy.GetPolicyMode() == "protect" { @@ -121,11 +120,11 @@ func validatePolicyUpdate(oldPolicy, newPolicy Policy) error { p := field.NewPath("spec") pp := p.Child("mode") errs = append(errs, field.Forbidden(pp, "field cannot transition from protect to monitor. Recreate instead.")) - - return apierrors.NewInvalid( - schema.GroupKind{Group: GroupVersion.Group, Kind: "ClusterAdmissionPolicy"}, - newPolicy.GetName(), errs) + errList = append(errList, errs...) } + if len(errList) != 0 { + return prepareInvalidAPIError(newPolicy, errList) + } return nil } diff --git a/api/policies/v1/policy_validation_test.go b/api/policies/v1/policy_validation_test.go index 5b45afe6..f5f9796d 100644 --- a/api/policies/v1/policy_validation_test.go +++ b/api/policies/v1/policy_validation_test.go @@ -113,12 +113,13 @@ func TestValidateRulesField(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { - err := validateRulesField(test.policy) + errList := validateRulesField(test.policy) if test.expectedErrorMessage != "" { + err := prepareInvalidAPIError(test.policy, errList) require.ErrorContains(t, err, test.expectedErrorMessage) return } - require.NoError(t, err) + require.Len(t, errList, 0) }) } } From 11297fb1e6e444c72d6833c93333ead9e663e29e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Cuadrado=20Juan?= Date: Mon, 22 Jul 2024 17:58:19 +0200 Subject: [PATCH 07/10] feat: validate Policy spec.MatchCondions on CREATE and UPDATE MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Víctor Cuadrado Juan --- api/policies/v1/admissionpolicy_webhook.go | 3 +++ .../v1/clusteradmissionpolicy_webhook.go | 3 +++ api/policies/v1/policy_validation.go | 27 +++++++++++++++++++ 3 files changed, 33 insertions(+) diff --git a/api/policies/v1/admissionpolicy_webhook.go b/api/policies/v1/admissionpolicy_webhook.go index 58cef7c8..a0833608 100644 --- a/api/policies/v1/admissionpolicy_webhook.go +++ b/api/policies/v1/admissionpolicy_webhook.go @@ -71,6 +71,9 @@ func (r *AdmissionPolicy) ValidateCreate() (admission.Warnings, error) { if errs := validateRulesField(r); len(errs) != 0 { errList = append(errList, errs...) } + if errs := validateMatchConditionsField(r); len(errs) != 0 { + errList = append(errList, errs...) + } if len(errList) != 0 { return nil, prepareInvalidAPIError(r, errList) } diff --git a/api/policies/v1/clusteradmissionpolicy_webhook.go b/api/policies/v1/clusteradmissionpolicy_webhook.go index dc5eb385..434a8102 100644 --- a/api/policies/v1/clusteradmissionpolicy_webhook.go +++ b/api/policies/v1/clusteradmissionpolicy_webhook.go @@ -73,6 +73,9 @@ func (r *ClusterAdmissionPolicy) ValidateCreate() (admission.Warnings, error) { if errs := validateRulesField(r); len(errs) != 0 { errList = append(errList, errs...) } + if errs := validateMatchConditionsField(r); len(errs) != 0 { + errList = append(errList, errs...) + } if len(errList) != 0 { return nil, prepareInvalidAPIError(r, errList) } diff --git a/api/policies/v1/policy_validation.go b/api/policies/v1/policy_validation.go index 7ae9eaaf..d197bf67 100644 --- a/api/policies/v1/policy_validation.go +++ b/api/policies/v1/policy_validation.go @@ -90,6 +90,30 @@ func checkRulesArrayForEmptyString(rulesArray []string, fieldName string, parent return nil } +func validateMatchConditionsField(policy Policy) field.ErrorList { + // taken from the configuration for validating MutatingWebhookConfiguration: + // https://github.com/kubernetes/kubernetes/blob/c052f64689ee26aace4689f2433c5c7dcf1931ad/pkg/apis/admissionregistration/validation/validation.go#L257 + opts := validationOptions{ + ignoreMatchConditions: false, + allowParamsInMatchConditions: false, + requireNoSideEffects: true, + requireRecognizedAdmissionReviewVersion: true, + requireUniqueWebhookNames: true, + allowInvalidLabelValueInSelector: false, + // strictCostEnforcement enables cost enforcement for CEL. + // This is enabled with the StrictCostEnforcementForWebhooks feature gate + // (alpha on v1.30). Don't check it for now. Nevertheless, will get + // checked by the k8s API on WebhookConfiguration creation if the feature + // gate is enabled. + strictCostEnforcement: false, + } + + if errs := validateMatchConditions(policy.GetMatchConditions(), opts, field.NewPath("spec").Child("matchConditions")); len(errs) != 0 { + return errs + } + return nil +} + // prepareInvalidAPIError is a shorthand for generating an invalid apierrors.StatusError with data from a policy. func prepareInvalidAPIError(policy Policy, errorList field.ErrorList) *apierrors.StatusError { return apierrors.NewInvalid( @@ -105,6 +129,9 @@ func validatePolicyUpdate(oldPolicy, newPolicy Policy) error { if errs := validateRulesField(newPolicy); len(errs) != 0 { errList = append(errList, errs...) } + + if errs := validateMatchConditionsField(newPolicy); len(errs) != 0 { + errList = append(errList, errs...) } if newPolicy.GetPolicyServer() != oldPolicy.GetPolicyServer() { From d6c2aba7002e34430b1f99a46cf9544800a765d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Cuadrado=20Juan?= Date: Tue, 23 Jul 2024 14:41:06 +0200 Subject: [PATCH 08/10] test: Add matchConditions testscases for CREATE, UPDATE MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Refactor clusterAdmissionPolicyFactory() to accept a matchConditions variable. Add new TestValidateMatchConditionsField() field with 3 testcases. Add new testcases for matchConditions for the existing TestValidatePolicyUpdate(). Signed-off-by: Víctor Cuadrado Juan --- .../v1/admissionpolicy_webhook_test.go | 2 +- .../v1/clusteradmissionpolicy_webhook_test.go | 10 +- api/policies/v1/policy_utils_test.go | 7 +- api/policies/v1/policy_validation_test.go | 152 +++++++++++++++--- 4 files changed, 138 insertions(+), 33 deletions(-) diff --git a/api/policies/v1/admissionpolicy_webhook_test.go b/api/policies/v1/admissionpolicy_webhook_test.go index 3a269bd9..2ecc992f 100644 --- a/api/policies/v1/admissionpolicy_webhook_test.go +++ b/api/policies/v1/admissionpolicy_webhook_test.go @@ -46,7 +46,7 @@ func TestAdmissionPolicyValidateUpdate(t *testing.T) { } func TestAdmissionPolicyValidateUpdateWithInvalidOldPolicy(t *testing.T) { - oldPolicy := clusterAdmissionPolicyFactory(nil, "", "protect") + oldPolicy := clusterAdmissionPolicyFactory(nil, nil, "", "protect") newPolicy := admissionPolicyFactory() warnings, err := newPolicy.ValidateUpdate(oldPolicy) require.Empty(t, warnings) diff --git a/api/policies/v1/clusteradmissionpolicy_webhook_test.go b/api/policies/v1/clusteradmissionpolicy_webhook_test.go index d6a37e15..72117b91 100644 --- a/api/policies/v1/clusteradmissionpolicy_webhook_test.go +++ b/api/policies/v1/clusteradmissionpolicy_webhook_test.go @@ -23,7 +23,7 @@ import ( ) func TestClusterAdmissionPolicyDefault(t *testing.T) { - policy := clusterAdmissionPolicyFactory(nil, "", "protect") + policy := clusterAdmissionPolicyFactory(nil, nil, "", "protect") policy.Default() require.Equal(t, constants.DefaultPolicyServer, policy.GetPolicyServer()) @@ -31,15 +31,15 @@ func TestClusterAdmissionPolicyDefault(t *testing.T) { } func TestClusterAdmissionPolicyValidateCreate(t *testing.T) { - policy := clusterAdmissionPolicyFactory(nil, "", "protect") + policy := clusterAdmissionPolicyFactory(nil, nil, "", "protect") warnings, err := policy.ValidateCreate() require.NoError(t, err) require.Empty(t, warnings) } func TestClusterAdmissionPolicyValidateUpdate(t *testing.T) { - oldPolicy := clusterAdmissionPolicyFactory(nil, "", "protect") - newPolicy := clusterAdmissionPolicyFactory(nil, "", "protect") + oldPolicy := clusterAdmissionPolicyFactory(nil, nil, "", "protect") + newPolicy := clusterAdmissionPolicyFactory(nil, nil, "", "protect") warnings, err := newPolicy.ValidateUpdate(oldPolicy) require.NoError(t, err) require.Empty(t, warnings) @@ -47,7 +47,7 @@ func TestClusterAdmissionPolicyValidateUpdate(t *testing.T) { func TestClusterAdmissionPolicyValidateUpdateWithInvalidOldPolicy(t *testing.T) { oldPolicy := admissionPolicyFactory() - newPolicy := clusterAdmissionPolicyFactory(nil, "", "protect") + newPolicy := clusterAdmissionPolicyFactory(nil, nil, "", "protect") warnings, err := newPolicy.ValidateUpdate(oldPolicy) require.Empty(t, warnings) require.ErrorContains(t, err, "object is not of type ClusterAdmissionPolicy") diff --git a/api/policies/v1/policy_utils_test.go b/api/policies/v1/policy_utils_test.go index 37d5409b..ef30c417 100644 --- a/api/policies/v1/policy_utils_test.go +++ b/api/policies/v1/policy_utils_test.go @@ -39,7 +39,7 @@ func admissionPolicyFactory() *AdmissionPolicy { } } -func clusterAdmissionPolicyFactory(customRules []admissionregistrationv1.RuleWithOperations, policyServer string, policyMode PolicyMode) *ClusterAdmissionPolicy { +func clusterAdmissionPolicyFactory(customRules []admissionregistrationv1.RuleWithOperations, matchConds []admissionregistrationv1.MatchCondition, policyServer string, policyMode PolicyMode) *ClusterAdmissionPolicy { return &ClusterAdmissionPolicy{ ObjectMeta: metav1.ObjectMeta{ Name: "testing-policy", @@ -51,8 +51,9 @@ func clusterAdmissionPolicyFactory(customRules []admissionregistrationv1.RuleWit Settings: runtime.RawExtension{ Raw: []byte("{}"), }, - Rules: getRules(customRules), - Mode: policyMode, + Rules: getRules(customRules), + Mode: policyMode, + MatchConditions: matchConds, }, }, } diff --git a/api/policies/v1/policy_validation_test.go b/api/policies/v1/policy_validation_test.go index f5f9796d..d7f62e29 100644 --- a/api/policies/v1/policy_validation_test.go +++ b/api/policies/v1/policy_validation_test.go @@ -28,29 +28,32 @@ func TestValidateRulesField(t *testing.T) { policy Policy expectedErrorMessage string // use empty string when no error is expected }{ - {"with no operations and API groups and resources", clusterAdmissionPolicyFactory([]admissionregistrationv1.RuleWithOperations{}, "default", "protect"), "spec.rules: Required value: a value must be specified"}, - {"with empty objects", clusterAdmissionPolicyFactory([]admissionregistrationv1.RuleWithOperations{{}}, "default", "protect"), "spec.rules.operations: Required value: a value must be specified"}, + {"with no operations and API groups and resources", clusterAdmissionPolicyFactory([]admissionregistrationv1.RuleWithOperations{}, nil, "default", "protect"), "spec.rules: Required value: a value must be specified"}, + {"with empty objects", clusterAdmissionPolicyFactory([]admissionregistrationv1.RuleWithOperations{{}}, nil, "default", "protect"), "spec.rules.operations: Required value: a value must be specified"}, {"with no operations", clusterAdmissionPolicyFactory([]admissionregistrationv1.RuleWithOperations{{ Operations: []admissionregistrationv1.OperationType{}, Rule: admissionregistrationv1.Rule{ APIGroups: []string{"*"}, APIVersions: []string{"*"}, Resources: []string{"*/*"}, - }}}, "default", "protect"), "spec.rules.operations: Required value: a value must be specified"}, + }, + }}, nil, "default", "protect"), "spec.rules.operations: Required value: a value must be specified"}, {"with null operations", clusterAdmissionPolicyFactory([]admissionregistrationv1.RuleWithOperations{{ Operations: nil, Rule: admissionregistrationv1.Rule{ APIGroups: []string{"*"}, APIVersions: []string{"*"}, Resources: []string{"*/*"}, - }}}, "default", "protect"), "spec.rules.operations: Required value: a value must be specified"}, + }, + }}, nil, "default", "protect"), "spec.rules.operations: Required value: a value must be specified"}, {"with empty operations string", clusterAdmissionPolicyFactory([]admissionregistrationv1.RuleWithOperations{{ Operations: []admissionregistrationv1.OperationType{""}, Rule: admissionregistrationv1.Rule{ APIGroups: []string{"*"}, APIVersions: []string{"*"}, Resources: []string{"*/*"}, - }}}, "default", "protect"), "spec.rules.operations: Invalid value: \"\": field value cannot contain the empty string"}, + }, + }}, nil, "default", "protect"), "spec.rules.operations: Invalid value: \"\": field value cannot contain the empty string"}, {"with no apiVersion", clusterAdmissionPolicyFactory([]admissionregistrationv1.RuleWithOperations{{ Operations: []admissionregistrationv1.OperationType{admissionregistrationv1.OperationAll}, @@ -58,57 +61,65 @@ func TestValidateRulesField(t *testing.T) { APIGroups: []string{"*"}, APIVersions: []string{}, Resources: []string{"*/*"}, - }}}, "default", "protect"), "spec.rules: Required value: apiVersions and resources must have specified values"}, + }, + }}, nil, "default", "protect"), "spec.rules: Required value: apiVersions and resources must have specified values"}, {"with no resources", clusterAdmissionPolicyFactory([]admissionregistrationv1.RuleWithOperations{{ Operations: []admissionregistrationv1.OperationType{admissionregistrationv1.OperationAll}, Rule: admissionregistrationv1.Rule{ APIGroups: []string{"*"}, APIVersions: []string{"*"}, Resources: []string{}, - }}}, "default", "protect"), "spec.rules: Required value: apiVersions and resources must have specified values"}, + }, + }}, nil, "default", "protect"), "spec.rules: Required value: apiVersions and resources must have specified values"}, {"with empty apiVersion string", clusterAdmissionPolicyFactory([]admissionregistrationv1.RuleWithOperations{{ Operations: []admissionregistrationv1.OperationType{admissionregistrationv1.OperationAll}, Rule: admissionregistrationv1.Rule{ APIGroups: []string{"*"}, APIVersions: []string{""}, Resources: []string{"*/*"}, - }}}, "default", "protect"), "spec.rules.rule.apiVersions: Invalid value: \"\": rule.apiVersions value cannot contain the empty string"}, + }, + }}, nil, "default", "protect"), "spec.rules.rule.apiVersions: Invalid value: \"\": rule.apiVersions value cannot contain the empty string"}, {"with some of the apiVersion are empty string", clusterAdmissionPolicyFactory([]admissionregistrationv1.RuleWithOperations{{ Operations: []admissionregistrationv1.OperationType{admissionregistrationv1.OperationAll}, Rule: admissionregistrationv1.Rule{ APIGroups: []string{"*"}, APIVersions: []string{""}, Resources: []string{"*/*"}, - }}}, "default", "protect"), "spec.rules.rule.apiVersions: Invalid value: \"\": rule.apiVersions value cannot contain the empty string"}, + }, + }}, nil, "default", "protect"), "spec.rules.rule.apiVersions: Invalid value: \"\": rule.apiVersions value cannot contain the empty string"}, {"with empty resources string", clusterAdmissionPolicyFactory([]admissionregistrationv1.RuleWithOperations{{ Operations: []admissionregistrationv1.OperationType{admissionregistrationv1.OperationAll}, Rule: admissionregistrationv1.Rule{ APIGroups: []string{"*"}, APIVersions: []string{"*"}, Resources: []string{""}, - }}}, "default", "protect"), "spec.rules.rule.resources: Invalid value: \"\": rule.resources value cannot contain the empty string"}, + }, + }}, nil, "default", "protect"), "spec.rules.rule.resources: Invalid value: \"\": rule.resources value cannot contain the empty string"}, {"with some of the resources are string", clusterAdmissionPolicyFactory([]admissionregistrationv1.RuleWithOperations{{ Operations: []admissionregistrationv1.OperationType{admissionregistrationv1.OperationAll}, Rule: admissionregistrationv1.Rule{ APIGroups: []string{""}, APIVersions: []string{"v1"}, Resources: []string{"", "pods"}, - }}}, "default", "protect"), "spec.rules.rule.resources: Invalid value: \"\": rule.resources value cannot contain the empty string"}, - {"with all operations and API groups and resources", clusterAdmissionPolicyFactory(nil, "default", "protect"), ""}, + }, + }}, nil, "default", "protect"), "spec.rules.rule.resources: Invalid value: \"\": rule.resources value cannot contain the empty string"}, + {"with all operations and API groups and resources", clusterAdmissionPolicyFactory(nil, nil, "default", "protect"), ""}, {"with valid APIVersion and resources. But with empty APIGroup", clusterAdmissionPolicyFactory([]admissionregistrationv1.RuleWithOperations{{ Operations: []admissionregistrationv1.OperationType{admissionregistrationv1.OperationAll}, Rule: admissionregistrationv1.Rule{ APIGroups: []string{""}, APIVersions: []string{"v1"}, Resources: []string{"pods"}, - }}}, "default", "protect"), ""}, + }, + }}, nil, "default", "protect"), ""}, {"with valid APIVersion, Resources and APIGroup", clusterAdmissionPolicyFactory([]admissionregistrationv1.RuleWithOperations{{ Operations: []admissionregistrationv1.OperationType{admissionregistrationv1.OperationAll}, Rule: admissionregistrationv1.Rule{ APIGroups: []string{"apps"}, APIVersions: []string{"v1"}, Resources: []string{"deployments"}, - }}}, "default", "protect"), ""}, + }, + }}, nil, "default", "protect"), ""}, } for _, test := range tests { @@ -119,7 +130,59 @@ func TestValidateRulesField(t *testing.T) { require.ErrorContains(t, err, test.expectedErrorMessage) return } - require.Len(t, errList, 0) + require.Empty(t, errList) + }) + } +} + +func TestValidateMatchConditionsField(t *testing.T) { + tests := []struct { + name string + policy Policy + expectedErrorMessage string // use empty string when no error is expected + }{ + {"with empty MatchConditions", clusterAdmissionPolicyFactory([]admissionregistrationv1.RuleWithOperations{{ + Operations: []admissionregistrationv1.OperationType{admissionregistrationv1.OperationAll}, + Rule: admissionregistrationv1.Rule{APIGroups: []string{"apps"}, APIVersions: []string{"v1"}, Resources: []string{"deployments"}}, + }}, nil, "default", "protect"), ""}, + {"with valid MatchConditions", clusterAdmissionPolicyFactory([]admissionregistrationv1.RuleWithOperations{{ + Operations: []admissionregistrationv1.OperationType{admissionregistrationv1.OperationAll}, + Rule: admissionregistrationv1.Rule{APIGroups: []string{"apps"}, APIVersions: []string{"v1"}, Resources: []string{"deployments"}}, + }}, []admissionregistrationv1.MatchCondition{ + { + Name: "foo", + Expression: "true", + }, + }, "default", "protect"), ""}, + {"with non-boolean MatchConditions", clusterAdmissionPolicyFactory([]admissionregistrationv1.RuleWithOperations{{ + Operations: []admissionregistrationv1.OperationType{admissionregistrationv1.OperationAll}, + Rule: admissionregistrationv1.Rule{APIGroups: []string{"apps"}, APIVersions: []string{"v1"}, Resources: []string{"deployments"}}, + }}, []admissionregistrationv1.MatchCondition{ + { + Name: "foo", + Expression: "1 + 1", + }, + }, "default", "protect"), "Invalid value: \"1 + 1\": must evaluate to bool"}, + {"with invalid expression in MatchConditions", clusterAdmissionPolicyFactory([]admissionregistrationv1.RuleWithOperations{{ + Operations: []admissionregistrationv1.OperationType{admissionregistrationv1.OperationAll}, + Rule: admissionregistrationv1.Rule{APIGroups: []string{"apps"}, APIVersions: []string{"v1"}, Resources: []string{"deployments"}}, + }}, []admissionregistrationv1.MatchCondition{ + { + Name: "foo", + Expression: "invalid expression", + }, + }, "default", "protect"), "Syntax error: extraneous input 'expression'"}, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + errList := validateMatchConditionsField(test.policy) + if test.expectedErrorMessage != "" { + err := prepareInvalidAPIError(test.policy, errList) + require.ErrorContains(t, err, test.expectedErrorMessage) + return + } + require.Empty(t, errList) }) } } @@ -137,34 +200,69 @@ func TestValidatePolicyUpdate(t *testing.T) { APIGroups: []string{"apps"}, APIVersions: []string{"v1"}, Resources: []string{"deployments"}, - }}}, "old-policy-server", "monitor"), clusterAdmissionPolicyFactory([]admissionregistrationv1.RuleWithOperations{{ + }, + }}, nil, "old-policy-server", "monitor"), clusterAdmissionPolicyFactory([]admissionregistrationv1.RuleWithOperations{{ Operations: []admissionregistrationv1.OperationType{admissionregistrationv1.OperationAll}, Rule: admissionregistrationv1.Rule{ APIGroups: []string{"apps"}, APIVersions: []string{"v1"}, Resources: []string{"deployments"}, - }}}, "new-policy-server", "monitor"), "spec.policyServer: Forbidden: the field is immutable"}, + }, + }}, nil, "new-policy-server", "monitor"), "spec.policyServer: Forbidden: the field is immutable"}, {"change from protect to monitor", clusterAdmissionPolicyFactory([]admissionregistrationv1.RuleWithOperations{{ Operations: []admissionregistrationv1.OperationType{admissionregistrationv1.OperationAll}, Rule: admissionregistrationv1.Rule{ APIGroups: []string{"apps"}, APIVersions: []string{"v1"}, Resources: []string{"deployments"}, - }}}, "default", "protect"), clusterAdmissionPolicyFactory([]admissionregistrationv1.RuleWithOperations{{ + }, + }}, nil, "default", "protect"), clusterAdmissionPolicyFactory([]admissionregistrationv1.RuleWithOperations{{ Operations: []admissionregistrationv1.OperationType{admissionregistrationv1.OperationAll}, Rule: admissionregistrationv1.Rule{ APIGroups: []string{"apps"}, APIVersions: []string{"v1"}, Resources: []string{"deployments"}, - }}}, "default", "monitor"), "spec.mode: Forbidden: field cannot transition from protect to monitor. Recreate instead."}, - {"adding more rules", + }, + }}, nil, "default", "monitor"), "spec.mode: Forbidden: field cannot transition from protect to monitor. Recreate instead."}, + { + "adding more rules", + clusterAdmissionPolicyFactory([]admissionregistrationv1.RuleWithOperations{{ + Operations: []admissionregistrationv1.OperationType{admissionregistrationv1.OperationAll}, + Rule: admissionregistrationv1.Rule{ + APIGroups: []string{"apps"}, + APIVersions: []string{"v1"}, + Resources: []string{"deployments"}, + }, + }}, nil, "default", "protect"), + clusterAdmissionPolicyFactory([]admissionregistrationv1.RuleWithOperations{ + { + Operations: []admissionregistrationv1.OperationType{admissionregistrationv1.OperationAll}, + Rule: admissionregistrationv1.Rule{ + APIGroups: []string{"apps"}, + APIVersions: []string{"v1"}, + Resources: []string{"deployments"}, + }, + }, + { + Operations: []admissionregistrationv1.OperationType{admissionregistrationv1.OperationAll}, + Rule: admissionregistrationv1.Rule{ + APIGroups: []string{""}, + APIVersions: []string{"v1"}, + Resources: []string{"pods"}, + }, + }, + }, nil, "default", "protect"), "", + }, + { + "adding matchCondtions", clusterAdmissionPolicyFactory([]admissionregistrationv1.RuleWithOperations{{ Operations: []admissionregistrationv1.OperationType{admissionregistrationv1.OperationAll}, Rule: admissionregistrationv1.Rule{ APIGroups: []string{"apps"}, APIVersions: []string{"v1"}, Resources: []string{"deployments"}, - }}}, "default", "protect"), + }, + }}, nil, "default", "protect"), clusterAdmissionPolicyFactory([]admissionregistrationv1.RuleWithOperations{ { Operations: []admissionregistrationv1.OperationType{admissionregistrationv1.OperationAll}, @@ -172,15 +270,21 @@ func TestValidatePolicyUpdate(t *testing.T) { APIGroups: []string{"apps"}, APIVersions: []string{"v1"}, Resources: []string{"deployments"}, - }}, + }, + }, { Operations: []admissionregistrationv1.OperationType{admissionregistrationv1.OperationAll}, Rule: admissionregistrationv1.Rule{ APIGroups: []string{""}, APIVersions: []string{"v1"}, Resources: []string{"pods"}, - }}, - }, "default", "protect"), ""}, + }, + }, + }, []admissionregistrationv1.MatchCondition{{ + Name: "foo", + Expression: "true", + }}, "default", "protect"), "", + }, } for _, test := range tests { From 47d5352dacadcecb55f8da605cd00e8b750dd780 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Cuadrado=20Juan?= Date: Tue, 23 Jul 2024 14:58:27 +0200 Subject: [PATCH 09/10] chore: Ignore golangci-lint in imported policy_validation_matchconditions.go MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Víctor Cuadrado Juan --- .golangci.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.golangci.yml b/.golangci.yml index f4f35aa7..c291b40f 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -373,3 +373,9 @@ issues: - path: 'api/policies/v1/(.+)_webhook.go' linters: - dupl + - path: "api/policies/v1/policy_validation_matchconditions.go" + # this file is imported from k8s.io/kubernetes, ignore lint errors + linters: + - errorlint + - gochecknoglobals + - mnd From 63c84cadc949f4faa721bc8ec130934de17222a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Cuadrado=20Juan?= Date: Tue, 23 Jul 2024 16:51:39 +0200 Subject: [PATCH 10/10] review: Remove unneeded comment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Víctor Cuadrado Juan --- api/policies/v1/policy_validation_matchconditions.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/api/policies/v1/policy_validation_matchconditions.go b/api/policies/v1/policy_validation_matchconditions.go index 8a67dfdd..0314563c 100644 --- a/api/policies/v1/policy_validation_matchconditions.go +++ b/api/policies/v1/policy_validation_matchconditions.go @@ -21,8 +21,6 @@ See the License for the specific language governing permissions and limitations under the License. */ -// package validation - import ( "fmt" "strings"