diff --git a/pkg/engine/handlers/validation/validate_cel.go b/pkg/engine/handlers/validation/validate_cel.go index 73011f0f68..d15a9edaa6 100644 --- a/pkg/engine/handlers/validation/validate_cel.go +++ b/pkg/engine/handlers/validation/validate_cel.go @@ -9,6 +9,8 @@ import ( engineapi "github.com/kyverno/kyverno/pkg/engine/api" "github.com/kyverno/kyverno/pkg/engine/handlers" engineutils "github.com/kyverno/kyverno/pkg/engine/utils" + admissionregistrationv1 "k8s.io/api/admissionregistration/v1" + admissionregistrationv1alpha1 "k8s.io/api/admissionregistration/v1alpha1" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -45,27 +47,36 @@ func (h validateCELHandler) Process( return resource, nil } + // get resource's name, namespace, GroupVersionResource, and GroupVersionKind gvr := schema.GroupVersionResource(policyContext.RequestResource()) gvk := resource.GroupVersionKind() namespaceName := resource.GetNamespace() resourceName := resource.GetName() - var object, oldObject, versionedParams runtime.Object + object := resource.DeepCopyObject() + // in case of update request, set the oldObject to the current resource before it gets updated + var oldObject, versionedParams runtime.Object oldResource := policyContext.OldResource() - object = resource.DeepCopyObject() if oldResource.Object == nil { oldObject = nil } else { oldObject = oldResource.DeepCopyObject() } - var expressions, messageExpressions, matchExpressions, auditExpressions []cel.ExpressionAccessor - + // check if the rule uses parameter resources + hasParam := rule.Validation.CEL.HasParam() + // extract preconditions written as CEL expressions + matchConditions := rule.CELPreconditions + // extract CEL expressions used in validations and audit annotations validations := rule.Validation.CEL.Expressions auditAnnotations := rule.Validation.CEL.AuditAnnotations + matchExpressions := convertMatchExpressions(matchConditions) + validateExpressions := convertValidations(validations) + messageExpressions := convertMessageExpressions(validations) + auditExpressions := convertAuditAnnotations(auditAnnotations) + // get the parameter resource if exists - hasParam := rule.Validation.CEL.HasParam() if hasParam { paramKind := rule.Validation.CEL.GetParamKind() paramRef := rule.Validation.CEL.GetParamRef() @@ -88,56 +99,22 @@ func (h validateCELHandler) Process( versionedParams = paramResource.DeepCopyObject() } - // extract CEL expressions from validate.cel.expressions - for _, cel := range validations { - condition := &validatingadmissionpolicy.ValidationCondition{ - Expression: cel.Expression, - Message: cel.Message, - } - - messageCondition := &validatingadmissionpolicy.MessageExpressionCondition{ - MessageExpression: cel.MessageExpression, - } - - expressions = append(expressions, condition) - messageExpressions = append(messageExpressions, messageCondition) - } - - // extract CEL expressions from rule.celPreconditions - for _, condition := range rule.CELPreconditions { - matchCondition := &matchconditions.MatchCondition{ - Name: condition.Name, - Expression: condition.Expression, - } - - matchExpressions = append(matchExpressions, matchCondition) - } - - // extract CEL expressions from validate.cel.auditAnnotations - for _, auditAnnotation := range auditAnnotations { - auditCondition := &validatingadmissionpolicy.AuditAnnotationCondition{ - Key: auditAnnotation.Key, - ValueExpression: auditAnnotation.ValueExpression, - } - - auditExpressions = append(auditExpressions, auditCondition) - } + optionalVars := cel.OptionalVariableDeclarations{HasParams: hasParam, HasAuthorizer: false} // compile CEL expressions compositedCompiler, err := cel.NewCompositedCompiler(environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion())) if err != nil { return resource, handlers.WithError(rule, engineapi.Validation, "Error while creating composited compiler", err) } - filter := compositedCompiler.Compile(expressions, cel.OptionalVariableDeclarations{HasParams: hasParam, HasAuthorizer: false}, environment.StoredExpressions) - messageExpressionfilter := compositedCompiler.Compile(messageExpressions, cel.OptionalVariableDeclarations{HasParams: hasParam, HasAuthorizer: false}, environment.StoredExpressions) - auditAnnotationFilter := compositedCompiler.Compile(auditExpressions, cel.OptionalVariableDeclarations{HasParams: hasParam, HasAuthorizer: false}, environment.StoredExpressions) - matchConditionFilter := compositedCompiler.Compile(matchExpressions, cel.OptionalVariableDeclarations{HasParams: hasParam, HasAuthorizer: false}, environment.StoredExpressions) + filter := compositedCompiler.Compile(validateExpressions, optionalVars, environment.StoredExpressions) + messageExpressionfilter := compositedCompiler.Compile(messageExpressions, optionalVars, environment.StoredExpressions) + auditAnnotationFilter := compositedCompiler.Compile(auditExpressions, optionalVars, environment.StoredExpressions) + matchConditionFilter := compositedCompiler.Compile(matchExpressions, optionalVars, environment.StoredExpressions) // newMatcher will be used to check if the incoming resource matches the CEL preconditions newMatcher := matchconditions.NewMatcher(matchConditionFilter, nil, "", "", "") + // newValidator will be used to validate CEL expressions against the incoming object validator := validatingadmissionpolicy.NewValidator(filter, newMatcher, auditAnnotationFilter, messageExpressionfilter, nil) - admissionAttributes := admission.NewAttributesRecord(object, oldObject, gvk, namespaceName, resourceName, gvr, "", admission.Operation(policyContext.Operation()), nil, false, nil) - versionedAttr, _ := admission.NewVersionedAttributes(admissionAttributes, admissionAttributes.GetKind(), nil) var namespace *corev1.Namespace // Special case, the namespace object has the namespace of itself. @@ -154,6 +131,8 @@ func (h validateCELHandler) Process( } } + admissionAttributes := admission.NewAttributesRecord(object, oldObject, gvk, namespaceName, resourceName, gvr, "", admission.Operation(policyContext.Operation()), nil, false, nil) + versionedAttr, _ := admission.NewVersionedAttributes(admissionAttributes, admissionAttributes.GetKind(), nil) // validate the incoming object against the rule validateResult := validator.Validate(ctx, gvr, versionedAttr, versionedParams, namespace, celconfig.RuntimeCELCostBudget, nil) @@ -177,3 +156,53 @@ func (h validateCELHandler) Process( engineapi.RulePass(rule.Name, engineapi.Validation, msg), ) } + +func convertValidations(inputValidations []admissionregistrationv1alpha1.Validation) []cel.ExpressionAccessor { + celExpressionAccessor := make([]cel.ExpressionAccessor, len(inputValidations)) + for i, validation := range inputValidations { + validation := validatingadmissionpolicy.ValidationCondition{ + Expression: validation.Expression, + Message: validation.Message, + Reason: validation.Reason, + } + celExpressionAccessor[i] = &validation + } + return celExpressionAccessor +} + +func convertMessageExpressions(inputValidations []admissionregistrationv1alpha1.Validation) []cel.ExpressionAccessor { + celExpressionAccessor := make([]cel.ExpressionAccessor, len(inputValidations)) + for i, validation := range inputValidations { + if validation.MessageExpression != "" { + condition := validatingadmissionpolicy.MessageExpressionCondition{ + MessageExpression: validation.MessageExpression, + } + celExpressionAccessor[i] = &condition + } + } + return celExpressionAccessor +} + +func convertAuditAnnotations(inputValidations []admissionregistrationv1alpha1.AuditAnnotation) []cel.ExpressionAccessor { + celExpressionAccessor := make([]cel.ExpressionAccessor, len(inputValidations)) + for i, validation := range inputValidations { + validation := validatingadmissionpolicy.AuditAnnotationCondition{ + Key: validation.Key, + ValueExpression: validation.ValueExpression, + } + celExpressionAccessor[i] = &validation + } + return celExpressionAccessor +} + +func convertMatchExpressions(matchExpressions []admissionregistrationv1.MatchCondition) []cel.ExpressionAccessor { + celExpressionAccessor := make([]cel.ExpressionAccessor, len(matchExpressions)) + for i, condition := range matchExpressions { + condition := matchconditions.MatchCondition{ + Name: condition.Name, + Expression: condition.Expression, + } + celExpressionAccessor[i] = &condition + } + return celExpressionAccessor +}