Verifying a Real Signature at Admission

Two Pods, one signed and one not, evaluated against a Kyverno verifyImages policy holding a real public key. The signed Pod passes; the unsigned one fails with 'no signatures found' — and the whole run takes about two seconds.

Verifying a Real Signature at Admission

Policy structure validating offline is not the same as a signature being verified. The structural test proves the YAML is well-formed; it says nothing about whether Kyverno can reach a registry, find a .sig artifact and check it against a key. With a locally signed image in hand, that last step finally becomes testable.

The setup

Two Pods, one policy, three rules:

  • signed-image-should-pass — an image whose digest was signed with the POC key pair.
  • unsigned-image-should-fail — poc/tc07b:v1, pushed to the same local registry and deliberately never signed.

Same registry, same policy, same key. The only variable is whether a signature exists.

The result

Applying 3 policy rule(s) to 2 resource(s)...

policy poc-verify-signature-local-e2e -> resource
  poc-imgsec/Pod/unsigned-image-should-fail failed:
1 - verify-poc-local-signature failed to verify image
    127.0.0.1:5000/poc/tc07b:v1: .attestors[0].entries[0].keys:
    no signatures found

policy poc-verify-signature-local-e2e -> resource
  poc-imgsec/Pod/unsigned-image-should-fail failed:
1 - verify-poc-local-signature unverified image 127.0.0.1:5000/poc/tc07b:v1

pass: 2, fail: 2, warn: 0, error: 0, skip: 0
EXIT_CODE=1
elapsed_ms=2003

Read the counters carefully, because they are the whole finding. Two passes, two failures, and zero errors. Zero errors is the number that matters: every earlier signature test in this POC ended in errors, because the registries were unresolvable placeholders and Kyverno could not even attempt a verification. Here it attempted one, reached the registry, and returned a verdict on both Pods.

Signed Pod resolves to a .sig and passes; unsigned Pod finds nothing
Signed Pod resolves to a .sig and passes; unsigned Pod finds nothing

The same policy, the same key, the same registry — the only difference is whether a signature artifact exists for the digest.

The error is specific, and that is the point

.attestors[0].entries[0].keys: no signatures found names the exact attestor entry that came up empty. That distinguishes the three failure modes an operator will otherwise confuse:

  • No signatures found — the artifact is genuinely absent. The image was never signed.
  • Signature verification failed — an artifact exists but does not match the configured key. Wrong key, or a tampered image.
  • An error rather than a failure — Kyverno could not reach the registry at all. This is the class that Audit mode papers over and failurePolicy: Ignore silently admits.

The unsigned Pod produces two failure lines from one rule: the specific attestor miss, then the general unverified image verdict. Anything parsing this output for alerting should expect more failure lines than failing resources.

What two seconds actually measures

elapsed_ms=2003 is the wall time of the whole CLI evaluation — three rules across two Pods, including the registry round-trips to look for signature artifacts. It is not a per-Pod admission latency, and it should not be quoted as one.

What it does establish is an order of magnitude. Signature verification means a network call to the registry inside the admission path, so this work happens while the API server waits. Compare it with the read-only policy run against the real cluster's Pods, which came back in elapsed_ms=401 doing no registry work at all. Roughly five times the elapsed time, for the same tooling, because one of them goes out to a registry and the other does not.

That is the argument for failurePolicy: Ignore on a verification policy, and for key-based verification over keyless: key-based needs registry access only, while keyless makes every Pod create depend on the public Sigstore being reachable.

Next: the admission gate that lists 320 KB per Pod — what a context.apiCall really costs on the hot path.