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.enabledfollows 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.
keylessneeds an issuer and a subject, each exact or a regular expression. Regular expressions must match the whole value.keytakes the PEM or itsfingerprint(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 askey-not-verified.imagesis 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
ClusterImagePolicynext 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/nginxandindex.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_referenceand listed as not covered, never dropped. The broker counts these inkguardian_image_inventory_malformed_total{field}. - policy-controller only checks namespaces labelled
policy.sigstore.dev/include=true.
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
Everyevaluator.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.