Image Verification CEL Path (IVP CEL)#
Kyverno has two distinct image-verification execution paths:
| Path | Policy Kind | Engine Entry Point |
|---|---|---|
| Legacy | ClusterPolicy / Policy with verifyImages rules | verifyAndPatchImages β MutateImageHandler |
| IVP CEL | ImageValidatingPolicy / NamespacedImageValidatingPolicy | HandleMutating / HandleValidating |
The IVP CEL path is implemented in pkg/cel/policies/ivpol/engine/engine.go and uses a two-phase admission webhook design β a mutating webhook followed by a validating webhook β rather than a single-phase handler.
Key Structural Difference from the Legacy Path#
The legacy path runs signature/attestation verification inside the mutating webhook via imageVerifier.Verify and emits JSON Patch operations to pin tags to digests and record ImageVerificationMetadata. The IVP CEL path separates these concerns: mutation is for digest-pinning only; actual image verification is performed exclusively in the validating phase .
Two-Phase Webhook Architecture#
Four HTTP routes are registered in pkg/webhooks/server.go for the IVP CEL path :
| Phase | Cluster Path | Namespaced Path |
|---|---|---|
| Mutate | /ivpol/mutate/*policies | /nivpol/mutate/*policies |
| Validate | /ivpol/validate/*policies | /nivpol/validate/*policies |
The policy name is embedded in the URL, so each webhook call targets only the matching policy subset.
Phase 1 β HandleMutating#
HandleMutating is the entry point for the mutating webhook. Its current implementation extracts the resource from the admission request and returns it unchanged with no patches and no policy results . The code comment documents this explicitly: performing signature/attestation verification here would duplicate registry calls and β critically β previously relied on the kyverno.io/image-verification-outcomes annotation to hand results to the validating webhook, an annotation that a caller could forge (see issue #16336).
The mutating phase is reserved for true mutation (e.g., tag-to-digest pinning), none of which is implemented for ImageValidatingPolicy yet.
Phase 2 β HandleValidating#
HandleValidating performs actual image verification: it fetches compiled policies, matches resources, compiles CEL expressions, and calls cosign/notary signature/attestation verification via pkg/cel/libs/imageverify/impl.go. Results are mapped to pass, fail, skip, or error .
Enforcement depends on ValidationAction: Deny rejects on Fail/Error; Warn emits warnings instead.
mutateDigest and verifyDigest Fields#
In the Legacy Path (ClusterPolicy)#
For ClusterPolicy/Policy, MutateDigest and VerifyDigest are boolean fields on ImageVerification, defaulting to true. When MutateDigest is true, handleMutateDigest fetches the image descriptor and emits a JSON Patch to replace the image tag with a digest. This mutation happens inside the same engine call that performs verification.
In the IVP CEL Path (ImageValidatingPolicy)#
MutateDigest and VerifyDigest are pointer-typed fields on spec.validationConfigurations. Both default to true when unset. The webhook controller's ivpolsNeedingMutation function uses these fields to determine which ImageValidatingPolicy/NamespacedImageValidatingPolicy resources need a mutating webhook registered:
mutateDigest := spec.ValidationConfigurations.MutateDigest == nil || *spec.ValidationConfigurations.MutateDigest
verifyDigest := spec.ValidationConfigurations.VerifyDigest == nil || *spec.ValidationConfigurations.VerifyDigest
// Policy gets a mutating webhook only if mutateDigest || verifyDigest
Tag-to-digest pinning gap: Even though mutateDigest defaults to true, the current HandleMutating implementation returns no patches . This means tag-to-digest pinning is not yet implemented for ImageValidatingPolicy. The mutating webhook is registered when mutateDigest or verifyDigest is enabled, but it presently does nothing. Pinning is a planned mutation responsibility of the mutating phase once implemented .
Setting both to false: Policies with both mutateDigest: false and verifyDigest: false are excluded from mutating webhook registration entirely .
Security Context: The Annotation Bypass (Issue #16336)#
The original IVP CEL design ran image verification in the mutating webhook and serialized outcomes into the kyverno.io/image-verification-outcomes annotation, which the validating webhook then read to enforce policy. This created a bypass:
- If the mutating webhook was skipped (e.g.,
failurePolicy: Ignore+ registry timeout), a user-supplied forged annotation could be trusted by the validating webhook, admitting an unverified image . - Mixed
Ignore(mutate) +Fail(validate) configurations were particularly vulnerable.
The fix β decided by the Kyverno maintainers β was to move verification into the validating webhook so there is no annotation to forge . The current HandleMutating reflects this: it is intentionally a no-op for verification and the comment in the source explicitly references issue #16336 as the rationale .
The fix also calls for adding a verification cache for ImageValidatingPolicy, since verification now runs on every admission request. The existing imageVerifyCacheEnabled cache is wired only into the legacy ClusterPolicy engine .
Background scan is unaffected: it re-verifies independently rather than trusting the annotation .
Key Source Files#
| File | Role |
|---|---|
pkg/cel/policies/ivpol/engine/engine.go | HandleMutating / HandleValidating β core IVP CEL engine |
pkg/engine/image_verify.go | Legacy verifyAndPatchImages β entry point for ClusterPolicy verify-images rules |
pkg/engine/internal/imageverifier.go | Legacy imageVerifier.Verify β handles MutateDigest, attestor/attestation checks |
pkg/controllers/webhook/controller.go | ivpolsNeedingMutation β conditional mutating webhook registration |
api/kyverno/v1/image_verification_types.go | MutateDigest / VerifyDigest field definitions (legacy API) |
pkg/webhooks/server.go | HTTP route registration for /ivpol and /nivpol paths |
pkg/cel/libs/imageverify/impl.go | CEL built-in functions for cosign/notary verification |
pkg/image/verification/evaluator/validate.go | ImageVerificationOutcome struct; outcome annotation serialization |