ValidatingPolicy Engine (vpolengine)#
Kyverno maintains two separate validation pipelines: the legacy engine for Policy/ClusterPolicy resources, and a distinct CEL-based engine (vpolengine) for ValidatingPolicy and NamespacedValidatingPolicy resources. They diverge in their type systems, response structures, and how enforcement intent is expressed. Understanding this split is essential when adding features, wiring admission handlers, or processing policy evaluation results.
Location#
The vpolengine lives under pkg/cel/policies/vpol/engine/:
| File | Purpose |
|---|---|
engine.go | engineImpl, the concrete Handle() implementation |
policy.go | Internal Policy struct (actions + compiled policy) |
provider.go | Static (NewProvider) and Kubernetes-dynamic (NewKubeProvider) providers |
reconciler.go | Controller-runtime reconciler that watches ValidatingPolicy / NamespacedValidatingPolicy |
The shared generic building blocks (Engine[T], EngineRequest, EngineResponse) are in pkg/cel/engine/.
Generic Engine Interface#
The engine is built on a Go generic interface defined in pkg/cel/engine/engine.go. For ValidatingPolicies the concrete alias instantiates the generic over policiesv1beta1.ValidatingPolicyLike . The predicate parameter (func(T) bool) lets callers filter which policies are evaluated at call time.
EngineResponse — vpolengine vs. legacy#
These two response types are not interchangeable:
vpolengine (pkg/cel/engine/response.go) | Legacy engine (pkg/engine/api/engineresponse.go) | |
|---|---|---|
| Type | engine.EngineResponse | api.EngineResponse |
| Top-level resource | *unstructured.Unstructured (pointer) | unstructured.Unstructured (value) + PatchedResource |
| Policy results | []ValidatingPolicyResponse | PolicyResponse (single policy, flat rule list) |
| Enforcement field | Actions sets.Set[admissionregistrationv1.ValidationAction] per policy | Resolved via GetValidationFailureAction() → kyvernov1.ValidationFailureAction |
| Mutation support | No | Yes (GetPatches(), PatchedResource) |
The legacy api.EngineResponse exposes GetValidationFailureAction() which walks kyvernov1.Spec.ValidationFailureAction and override lists . For VAP-based resources (including ValidatingAdmissionPolicy), this method explicitly returns an empty string .
ValidationAction vs. ValidationFailureAction#
| vpolengine | Legacy engine | |
|---|---|---|
| Type | admissionregistrationv1.ValidationAction (Kubernetes stdlib) | kyvernov1.ValidationFailureAction (Kyverno-defined string) |
| Values | Deny, Warn, Audit (set-based, multiple can coexist) | Enforce / Audit (single value, case-insensitive variants) |
| Storage | sets.Set[ValidationAction] on the internal Policy struct | Field on kyvernov1.Spec + per-rule overrides |
ValidationAction values are sourced from spec.ValidationActions() on the ValidatingPolicySpec . The legacy ValidationFailureAction is declared in api/kyverno/v1/spec_types.go, with Enforce and Audit as the canonical casing and deprecated lowercase aliases.
Internal Policy Struct#
Each compiled policy carried by the vpolengine is represented as a Policy struct with three fields: Actions (sets.Set[admissionregistrationv1.ValidationAction]), Policy (the API object), and CompiledPolicy (CEL-compiled rules). Actions is pre-computed at reconcile time so the Handle() hot-path does not re-read the spec on every request. The same action set is echoed into ValidatingPolicyResponse.Actions so consumers can apply enforcement decisions without re-fetching the policy.
Provider and Reconciliation#
Two provider modes are available :
NewProvider— static list, compiles all policies upfront (used in CLI / background scan pipelines).NewKubeProvider— spins up two controller-runtime controllers, one forValidatingPolicyand one forNamespacedValidatingPolicy, with optionalPolicyExceptionwatch .
The reconciler skips policies whose status.generated == true (auto-generated policies are managed separately) and compiles autogen variants using autogen.Autogen() .
Evaluation Flow#
When Handle() is called :
- Fetches all compiled policies from the
Provider. - For JSON payload requests, evaluates directly without admission attributes.
- For admission requests, builds
admission.Attributesfrom theAdmissionRequest, resolves the namespace object, then callshandlePolicyper policy. handlePolicyruns match constraints, callsCompiledPolicy.Evaluate(), and maps theEvaluationResulttoengineapi.RulePass,RuleSkip,RuleFail, orRuleError.- On a fail with a known validation index, the zero-based index of the failing CEL expression is stored in the
cel.validationIndexannotation .
Exceptions are resolved during compilation (reconcile time), not at evaluation time. When exceptions are present the highest-priority exception (by label LabelPolicyExceptionPriority) determines whether the rule is skipped or passed .