Skip to main content
ImageTrustPolicy (namespaced) and ClusterImageTrustPolicy (cluster-wide, with a namespaceSelector) say who must have signed the images your workloads run. Like AuditNetworkPolicy they only report: the kguardian evaluator compares every running container with the signatures kguardian verified and writes what would be denied to the policy’s status. There is no webhook. Nothing is admitted, blocked or changed.

What it needs

  • supplychain.signatureDiscovery.enabled: true: the supplychain component checks who signed every running digest (keyless identity, a configured key, unsigned, invalid, or unknown). See Signature endpoints.
  • The evaluator (on by default). evaluator.imageTrust.enabled follows signature discovery unless set.
  • The CRDs in charts/kguardian/crds/. Helm installs that directory on first install only; on an upgrade apply them yourself: kubectl apply -f charts/kguardian/crds/. Without them the evaluator logs once and evaluates nothing.

Example

  • An image is trusted when a signature from any authority verified, and every listed attestation is attached, verified and signed by one of them.
  • keyless needs an issuer and a subject, each exact or a regular expression. Regular expressions must match the whole value.
  • key takes the PEM or its fingerprint (sha256 of the DER public key, as kguardian reports it). kguardian’s supplychain component must hold the same key (supplychain.signatureDiscovery.publicKeys) to verify those signatures; otherwise they read as key-not-verified.
  • images is matched against the image repository (docker.io/library/nginx). Empty matches every image.

Status

STATE says whether there is a current evaluation: evaluated; never-read (the broker has never been read); broker-unavailable or broker-unauthorized (every container is Unknown). Without a current evaluation the counts are left out of status, so their columns are blank, never a reassuring 0; the containers last known are still listed in findings with the reason. The BrokerRead condition carries the same with a reason (Read, NeverRead, BrokerUnavailable, BrokerUnauthorized) and the error. status.evaluation has the counts and up to 20 findings (would-deny first): Unknown is never counted as trusted. An enforcing policy would most likely deny these images, so treat them as findings to resolve. status.error is set when the spec cannot be evaluated (a bad regular expression or key). evaluation.lastEvaluated is when the running containers were last read from the broker; lastChanged is when the verdicts last changed, so status is written once per interval while the broker is readable. It is written with server-side apply, so a finding or error that no longer holds is removed, not left behind. If the broker cannot be read, the last verdicts stay for up to 3x the interval; after that every container is reported Unknown (broker-unavailable), never as its last verdict. A rejected token makes them Unknown at once. The evaluator also serves the per-container results at GET /image-trust on its own port (?namespace=, ?verdict=). It requires the same broker READ-scope token as the broker’s read API (Authorization: Bearer ...); with broker auth off it is open, like the broker. The broker serves them too, bounded and with workload filters, at GET /image-trust; that is what kubectl kguardian images trust and the assistant’s explain_image_trust tool read.

Generating policies

kguardian can write the policy for you from the signers it verified on running images, for three engines: Get them with kubectl kguardian images admission-policy, from GET /attestations/policy, or as the admission artifact of a workload’s export bundle (ImageTrustPolicy in audit, Kyverno Deny in enforce).
  • A repository is covered only when every running digest of it verified. Unsigned, invalid, unchecked, unknown, and key-signed images whose key kguardian does not hold are listed in the header as not covered, never given a guessed signer.
  • The authorities are the signers seen. A GitHub Actions subject pinned to a tag (.../release.yaml@refs/tags/v1.2.3) is widened to any tag of the same workflow, or the next release would fail; other subjects stay exact. Regular expressions are anchored.
  • SLSA provenance is required when every digest of the repository has it, verified and signed by one of the authorities. policy-controller checks only attestations for an authority that lists them, so the requirement is a second ClusterImagePolicy next to the signature one.
  • Image globs follow each engine. Kyverno matches the image as the pod spec writes it, so every spelling is listed, bare, with a tag and with a digest: for Docker Hub nginx, library/nginx, docker.io/nginx, docker.io/library/nginx and index.docker.io/.... policy-controller matches the normalised name (index.docker.io/library/nginx:**).
  • The header lists every identity the policy trusts, with the images and digests it was seen verifying, under “review every identity before applying”: they were observed on running images, not vetted. If a rogue signer signed an image you run, it is in that list.
  • A pod whose image reference was malformed when reported (a control character, line separator or over-long value) is kept in the inventory as malformed_reference and listed as not covered, never dropped. The broker counts these in kguardian_image_inventory_malformed_total{field}.
  • policy-controller only checks namespaces labelled policy.sigstore.dev/include=true.
The generated policies were checked with Kyverno CLI 1.19.1 against registry.k8s.io/pause:3.10 (keyless: passes; with another identity, fails) and with policy-controller’s policy-tester 0.15.1 against the same image and a cosign key-signed image (both verify; another identity or key fails).

How it works

Every evaluator.imageTrust.interval (5 minutes) the evaluator lists the policies and reads GET /attestations/running from the broker with the read token: every running workload container, the digest it runs, and the signers kguardian verified for it. It never verifies a signature itself; that is the supplychain component’s job.