skip to content

In Kyverno, how do you block an image whose vulnerability-scan attestation is older than 14 days?

level: seniorimportance: must knowfreq 45%

answer

  1. existence proves almost nothing
  2. name the predicate type first
  3. the predicate's fields become the variables
  4. express the window as a duration, not a date
  5. no verifiable attestation means denied

basics

~10 s

Add an attestations entry naming the scan predicate type, give it the attestors whose signature you accept, and condition on the predicate's timestamp field. No verifiable attestation of that type blocks the image.

solid answer

~50 s

Inside `verifyImages`, the `attestations` list is where you assert over the *content* of a signed statement rather than just its existence. Each entry names a predicate type, carries `attestors` — the identities whose signature over that attestation you accept — and a `conditions` block of `all`/`any` clauses. Kyverno fetches attestations attached to the image, keeps the ones of that type whose signature verifies, decodes the predicate body, and evaluates the conditions against its fields as variables. For freshness you compare a timestamp field in the predicate against now, using Kyverno's `time_since` JMESPath filter with a duration like `336h` for 14 days. Two behaviours matter: absence is a denial — no attestation of that type, or none that verifies, fails the rule — and the condition is checked at admission only, so nothing re-evaluates staleness for a pod already running.

code

yaml · 17 lines
yaml
verifyImages:
- imageReferences:
  - "registry.example.com/apps/*"
  attestations:
  - type: https://example.com/attestations/vuln-scan/v1
    attestors:
    - count: 1
      entries:
      - keys:
          publicKeys: |-
            -----BEGIN PUBLIC KEY-----
            ...
    conditions:
    - all:
      - key: "{{ time_since('', '{{ scanFinishedOn }}', '') }}"
        operator: LessThan
        value: "336h"

go deeper

for a junior

Know that an attestation is a signed statement attached to an image, and that a policy can require one of a specific kind rather than just a signature on the image.

for a middle

Explain the three parts of an attestations entry — predicate type, attestors, conditions — and that the conditions address fields inside the decoded predicate body.

for a senior

Demonstrate the operational side: express freshness as a rolling duration, know that absence fails closed, and plan the rollout around the producing side already emitting the attestation.

for a principal

Own the contract between the team producing the predicate and the team asserting over it, including how its shape changes without breaking deploys and who is accountable when it does.

## Existence is not the interesting assertion A rule that says "this image must carry a scan attestation" is easy to satisfy and almost worthless: a scan from eight months ago, run by anything, satisfies it. The valuable rule asserts over the *inside* of the attestation — the predicate — and that is what the `attestations` block in a `verifyImages` rule is for. An attestation here is a signed statement attached to an image in the registry. It has a **predicate type**, a URI naming what kind of statement it is, and a **predicate body**, the actual document. The policy names the type it cares about, says whose signature over that statement it accepts, and then asserts over fields in the body. ## The three parts of an attestations entry 1. **The predicate type.** The `type` field (older policies spell it `predicateType`) selects which attestations are relevant. Attestations of other types on the same image are ignored by this entry. 2. **The attestors.** An attestation is only as good as the identity that signed it. The entry carries its own `attestors`, which is what lets the scanner that produces the scan attestation be a different identity from whoever signed the image itself. 3. **The conditions.** `conditions` holds `all` and `any` blocks of `key` / `operator` / `value` clauses. The `key` is a JMESPath expression evaluated against the decoded predicate body, so the predicate's own fields are what you address. ## Freshness as a condition Freshness is the canonical example because it is the one thing an existence check cannot give you. If the predicate records when the scan finished, you compare that against now: ```yaml conditions: - all: - key: "{{ time_since('', '{{ scanFinishedOn }}', '') }}" operator: LessThan value: "336h" ``` `time_since` is one of Kyverno's JMESPath filters; with an empty end argument it measures from the given timestamp to now, and the result is compared against a duration string — 336 hours being 14 days. Expressing the window as a duration rather than a date is what keeps the policy from needing an edit every fortnight. Note what the policy is *not* doing: it is not reading the scanner's findings and deciding whether the vulnerabilities are acceptable. It is asserting that a recent, signed statement of a known type exists. Keeping the assertion narrow is deliberate; a condition on one timestamp field is something you can debug at 5pm, whereas a condition that walks a findings array is something you will be arguing about at 5pm. ## What happens when the attestation is missing The important behaviour is that **absence fails**. If the image carries no attestation of that predicate type, or carries one whose signature does not verify against the listed attestors, or carries one whose conditions evaluate false, the `verifyImages` rule does not pass — the request is refused rather than quietly skipped. This is the opposite of the `imageReferences` behaviour, where an unmatched image is not evaluated at all. Selection is permissive; assertion is strict. Being able to state that difference cleanly is most of the value of this question in an interview. The practical consequence is that turning such a rule on is an availability event unless the attestations already exist. The images being deployed today have to already carry a signed, recent, correctly-typed scan attestation, which means the producing side — whatever runs the scan and attaches the statement — has to be in place and covering every image the rule selects, before the rule starts refusing anything. ## Where the rule stops Three limits are worth volunteering: - **It is a point-in-time check.** The condition is evaluated when the pod is admitted. A pod admitted this morning with a two-day-old attestation is still running next month with a forty-day-old one, and nothing re-evaluates it until something re-creates the pod. - **It asserts over a document, not over reality.** The predicate says a scan finished at a time. Whether that scan was thorough, whether it covered the layers that matter, whether the producing system could be persuaded to emit a favourable statement — none of that is visible to the condition. The attestor list is what carries that weight, which is why *whose* signature you accept deserves as much thought as the condition itself. - **A predicate field can be missing.** If the predicate does not carry the field your condition addresses, the condition does not evaluate to a helpful "true"; the rule fails to be satisfied and the deploy is refused. That is fail-closed and correct, but it means a change to the predicate's shape on the producing side is a change that can break deploys, and the two need to move together. ## In an interview Lay out the three parts — type, attestors, conditions — say that the predicate's fields become the variables the conditions address, express freshness as a duration rather than a date, and then volunteer the absence-fails behaviour and the point-in-time limit. Candidates who only describe the happy path have usually never had to turn one of these on in a cluster that was already running.

  • What happens if the image carries no attestation of that predicate type at all?
    The rule is not satisfied and the request is refused. Assertion fails closed, unlike image selection, where an image matching no imageReferences pattern is simply never evaluated. That asymmetry is why enabling such a rule is an availability event unless the producing side is already attaching the attestation to every selected image.
  • Why give the attestations entry its own attestors instead of reusing the image's?
    Because a different party usually makes the statement. The build system signs the image; the scanner signs the scan result. Letting the attestation entry name its own accepted identities means you can insist the scan statement came from your scanning platform specifically, rather than from anyone who could sign the image.
  • A pod admitted three weeks ago passed this rule. Is its attestation still fresh?
    Almost certainly not, and the rule cannot tell you. The condition ran once, at admission. Freshness decays afterwards and nothing re-evaluates it until the pod is recreated, so the guarantee is "was fresh when admitted", not "is fresh now".
  • The scanner team renames a field in the predicate. What breaks?
    Deploys. The condition addresses a field that no longer exists, the rule cannot be satisfied, and selected images stop being admitted. The predicate is an interface between two teams, so a shape change needs the same coordination as any other contract change — publish the new field alongside the old, move the policy, then retire the old one.

saying these in an interview costs you the question

  • Checks only that an attestation exists, never its contents
  • Assumes a missing attestation is skipped rather than denied
  • Hardcodes a date instead of a rolling duration
  • Thinks the rule keeps re-checking freshness while the pod runs
  • Ignores which identity signed the attestation

context