ImageValidatingPolicy Webhook Architecture#
ImageValidatingPolicy (ivpol) uses a two-phase webhook design: a mutating webhook first evaluates policies and writes outcomes into resource annotations; a validating webhook then reads those annotations back to make enforcement decisions. This split exists because Kubernetes API server runs mutating webhooks before validating webhooks, guaranteeing the annotations are present by the time enforcement runs. Both cluster-scoped (ImageValidatingPolicy) and namespace-scoped (NamespacedImageValidatingPolicy) policies share the same handler implementations, just served under different URL prefixes.
HTTP Route Registration#
Four routes handle ivpol admission in pkg/webhooks/server.go:
| Phase | Path | Handler |
|---|---|---|
| Mutate (cluster) | /ivpol/mutate/*policies | ImageVerificationPoliciesMutation |
| Validate (cluster) | /ivpol/validate/*policies | ImageVerificationPolicies |
| Mutate (namespaced) | /nivpol/mutate/*policies | ImageVerificationPoliciesMutation |
| Validate (namespaced) | /nivpol/validate/*policies | ImageVerificationPolicies |
The policy name is embedded in the URL path, allowing the webhook controller to route requests to only the matching subset of policies.
Phase 1 β Mutating Webhook: Evaluate and Record#
The mutating handler (handler.Mutate) calls engine.HandleMutating, which:
- Matches each policy against the incoming resource using CEL
MatchResourcesconstraints. - Compiles and evaluates matched policies using the CEL compiler, calling actual image verification functions (cosign/notary signature and attestation verification via
pkg/cel/libs/imageverify/impl.go). - Handles PolicyExceptions: if a matching exception is found, the result is set to
RuleSkipwith the exception name in the message , recording the skip in the outcome rather than requiring a separate path. - Serializes outcomes:
MakeImageVerifyOutcomePatchconverts allImageVerifyPolicyResponseresults into a JSON map keyed by policy name and produces a JSON Patch that writes the value to thekyverno.io/image-verification-outcomesannotation . If the object has no annotations yet, a patch to create themetadata/annotationsobject is prepended first .
The mutating response is always allowed β the mutating phase never blocks admission. Warn-action policies emit warnings on Fail or Error results at this stage .
Outcome Annotation Schema#
Each policy maps to an ImageVerificationOutcome object :
kyverno.io/image-verification-outcomes: |
{"my-policy": {"name": "...", "status": "pass|fail|skip|error", "message": "...", "properties": {...}}}
status maps to engineapi.RuleStatus and carries the full pass/fail/skip/error distinction, including exception skips.
Phase 2 β Validating Webhook: Read and Enforce#
The validating handler (handler.Validate) calls engine.HandleValidating, which:
- Requires the annotation to be present β missing annotations produce an immediate error:
"annotations not present on object, image verification failed". - Unmarshals the
kyverno.io/image-verification-outcomesannotation into amap[string]ImageVerificationOutcome. - Reconstructs rule responses for each matched policy from the stored outcome, via
engineapi.NewRuleResponse(o.Name, o.RuleType, o.Message, o.Status, o.Properties). If a policy is matched but has no entry in the annotation, it is treated as a hard failure ("policy not evaluated"). - Enforces based on
ValidationAction: policies withDenyaction cause the admission to be rejected onFailorErrorstatus; policies withWarnaction emit warnings instead .
The validating phase also triggers asynchronous side effects: admission report creation and Kubernetes event emission .
Hard dependency: The validating webhook unconditionally expects the outcome annotation. If the mutating webhook is disabled or removed for a policy (e.g., because
MutateDigestandVerifyDigestare bothfalse), the validating webhook will error on every matching request. Ensure both webhooks are registered or both are absent for a given policy.
Conditional Mutating Webhook Registration#
Not all ivpol policies require a mutating webhook. The webhook controller filters using ivpolsNeedingMutation: a policy is included only if MutateDigest or VerifyDigest is enabled in spec.validationConfigurations (both default to true when unset). Policies with both set to false skip the mutation phase entirely β they rely on a different mechanism and do not need annotation-based outcome recording.
Key Source Files#
| File | Role |
|---|---|
pkg/webhooks/server.go | HTTP route registration for /ivpol and /nivpol paths |
pkg/webhooks/resource/ivpol/handler.go | Mutate / Validate admission handlers |
pkg/cel/policies/ivpol/engine/engine.go | Core HandleMutating / HandleValidating engine logic |
pkg/image/verification/evaluator/validate.go | ImageVerificationOutcome struct; MakeImageVerifyOutcomePatch |
api/kyverno/constants.go | AnnotationImageVerifyOutcomes = "kyverno.io/image-verification-outcomes" |
pkg/cel/libs/imageverify/impl.go | CEL built-in functions for cosign/notary verification |
pkg/controllers/webhook/controller.go | ivpolsNeedingMutation β conditional mutating webhook registration |