ValidatingPolicy Autogen#
The autogen system automatically generates ValidatingPolicy variants that cover Pod controllers (Deployments, DaemonSets, StatefulSets, ReplicaSets, Jobs, CronJobs) from a policy written against Pods. It works by rewriting the CEL expression paths in the policy spec from Pod-level (e.g., object.spec.containers) to controller-specific template paths (e.g., object.spec.template.spec.containers).
Key source files:
| File | Role |
|---|---|
pkg/cel/policies/vpol/autogen/autogen.go | Entry point β Autogen() and generateRuleForControllers() |
pkg/cel/autogen/data.go | Defines controller targets and path replacement maps |
pkg/cel/autogen/replacement.go | Implements byte-level path rewriting |
pkg/cel/policies/vpol/engine/reconciler.go | Wires autogen into the policy reconcile loop |
pkg/cel/policies/vpol/compiler/compiler.go | Compiles policy + exceptions; called for each autogen variant |
Path Replacement Mechanism#
Autogen uses two replacement groups, defined in data.go:
AutogenDefaults(DaemonSets, Deployments, ReplicaSets, StatefulSets, Jobs):object.specβobject.spec.template.specobject.metadataβobject.spec.template.metadata
AutogenCronjobs(CronJobs only):object.specβobject.spec.jobTemplate.spec.template.specobject.metadataβobject.spec.jobTemplate.spec.template.metadata
The Replacement.Apply() function rewrites every occurrence of object.<from> and oldObject.<from> in the raw JSON bytes. One protection is in place: paths immediately followed by .namespace (e.g., object.metadata.namespace) are excluded from rewriting to avoid breaking match conditions that reference the workload's own namespace .
The full replacement pipeline in generateRuleForControllers:
- Deep-copy the
ValidatingPolicySpec - Update
MatchConstraintsto target the controller resource group - Marshal the spec to JSON
- Apply replacements with
autogen.Apply(bytes, autogen.ReplacementsMap[config]...) - Unmarshal back into the spec
This rewrites all paths found inside spec.matchConditions, spec.validations, spec.variables, and spec.auditAnnotations β everything encoded in the policy spec.
Autogen Flow in the Reconciler#
In reconciler.go, for each policy reconcile cycle:
- Fetch all matching
PolicyExceptionobjects - Compile the original (Pod-level) policy with those exceptions
- Generate autogen variants via
autogen.Autogen(policy) - Compile each autogen variant by calling
r.compiler.Compile(autogenPolicy, exceptions)with the same unmodified exception list
Known Gap: PolicyException matchConditions Are Not Transformed#
The problem: PolicyException.Spec.MatchConditions are compiled verbatim from the original exception object β they are never passed through the JSON replacement step.
Because exceptions are a separate CRD fetched at reconcile time and passed directly to Compile(), their CEL expressions bypass autogen.Apply(). The compiler compiles exception match conditions as-is . When an autogen variant for, say, a Deployment is evaluated, its exception match conditions still reference Pod-level paths like object.spec.containers[0].image, which do not exist on a Deployment object. The result is evaluation failure or incorrect exception matching for controller resources.
What is and isn't transformed:
| Expression source | Transformed by autogen? |
|---|---|
spec.matchConditions | β Yes (via JSON replacement) |
spec.validations | β Yes |
spec.variables | β Yes |
spec.auditAnnotations | β Yes |
PolicyException.Spec.MatchConditions | β No |
Workaround: Write PolicyException matchConditions to handle both Pod-level and controller-level paths, using conditional expressions like object.kind == 'Pod' ? object.spec.containers : object.spec.template.spec.containers.
Comparison: mpol vs vpol Autogen#
PR #14208 (merged 2025-10-29) fixed an analogous gap in MutatingPolicy autogen β specifically, it added explicit iteration over spec.MatchConditions and applied convertPodToTemplateExpression to each expression . The same fix was also applied to metadata path translation in mpol.
The vpol autogen does not have an equivalent fix. It relies entirely on the JSON-level byte replacement, which covers spec-level fields but cannot reach PolicyException objects that are injected externally.
π‘ If implementing a fix, the pattern from
mpol/autogen/autogen.go(post-PR #14208) is the reference: iterate exception match conditions and apply the appropriateReplacementfor the target controller type.