Overview
Withsupplychain.signatureDiscovery.enabled, the supplychain component
checks every running image digest for cosign signatures and Sigstore
attestations, verifies them with sigstore-go and posts one result per
digest. The broker stores the latest result for each digest and serves it.
It never verifies anything itself, and nothing here blocks a workload.
How discovery works and what each verdict means is in
supplychain/README.md (“Signature discovery”). In short:
verified means valid, not trusted: any Fulcio identity or configured key
can produce a valid signature, and anyone who can push to the repository
can attach one. Never show it without the signer.
Reads (read token)
GET /images/{digest}/attestation
The stored result for one digest, or 404.
signedViaisindexwhen the signature was found on the multi-arch index that lists the digest rather than on the digest itself;signedDigestis then the index.- A key signature has
signerKind: "key",keyNameandkeyFingerprint(sha256 of the DER public key) instead ofissuerandsan. A key-signed bundle also carrieskeyHint, verified or not. - An unverified signature or attestation has
error, usuallydetail, and no identity: a claimed signer is never shown as a fact.detailis the verifier’s error text with endpoints and secrets removed, at ingest and again when served for rows stored earlier: URLs become<url>; host names, IP addresses andhost:port, with anything after them, become<host>or<host>/…, also when glued to a key such asaddr=orhost:, which is kept (an image reference with a registry keeps only its@sha256:digest); userinfo becomes<credentials>@; and token, password or signature values, JWTs, and the word afterBearer, become<redacted>. Digests, reason codes and version numbers are kept. The rule errs toward removing too much. Only the first 4 KiB of the text is redacted, and the result is cut to 256 bytes; a word crossing the 4 KiB point is dropped with everything after it. Sodetailcan be shorter than the verifier’s text, and is omitted when it can’t be safely shortened (for example, one unbroken word longer than 4 KiB).erroris always present and is the reason code. Unverified attestations carry noprovenanceeither. - Each attestation has
predicateType,payloadSha256(sha256 of the in-toto statement) and, for SLSA provenance,provenance.builderId,sourceRepo,sourceCommit,sourceRef.
GET /attestations
A keyset page of summaries, ordered by digest.
Each item has
digest, repository, verdict, reason, signedVia,
checkedAt, up to 8 distinct verified signers ({kind: "keyless", issuer, san} or {kind: "key", keyName, keyFingerprint}) and up to 16
verifiedPredicates. In the summary, the repository, issuers, SANs and
predicate types are cut to 256 bytes (never inside a character); the full
values are in the single-digest read.
GET /attestations/running
The evaluator’s ImageTrustPolicy feed:
every running workload container (namespace, workloadKind,
workloadName, container, digest, imageRef, repository) with its
image’s verdict, reason, verified signers and verified
attestations. verdict is null when the digest has no result yet (not
checked, which is not the same as unsigned). ?namespace=, limit 1-200
(default 100), and an opaque after cursor from nextAfter.
GET /attestations/policy
An admission policy generated from the verified signers of running images,
as YAML. See ImageTrustPolicy.
At most 5000 running containers are read; beyond that the answer is
422
and asks for namespace. The read budget is charged per running container
in the scope (16 KiB each, so a cluster-wide generation at the cap takes
about 78 MiB of the default 256 MiB), and the count is taken before
anything is read, so the 422 reserves nothing from the read budget and a
namespaced generation is priced by its own containers.
GET /image-trust
The evaluator’s ImageTrustPolicy results,
read from the evaluator (EVALUATOR_URL) with the broker’s own read token
and bounded. One entry per (policy, running container):
- Results are ordered
WouldDeny,Unknown,Trusted. The counts cover every matching result;truncatedis true whenlimitcut the list. available: falsewith areasonwhen there is nothing to report: no evaluator configured, image trust evaluation off in it (it answers404), it refused the broker’s read token, it could not be reached, or its answer was larger than 16 MiB (filter by namespace). This is unknown, never “nothing would be denied”.evaluatedAtisnulluntil the evaluator has finished a pass.reasonis a fixed phrase with a coarse cause and never contains the evaluator’s address or the HTTP client’s error text:the evaluator could not be reached: connection failed(orrequest failed),the evaluator did not answer within N ms,the evaluator answered with status N,the evaluator's answer did not arrive within N ms,the evaluator's answer could not be read,the evaluator sent an invalid response, plus the size, image-trust-off and token cases above. The broker logs the full error at warn.
503
with Retry-After rather than a partial result.
Ingest (supplychain token)
- Scoped auth only, like the vulnerability ingest: a token with the
supplychainscope,403for a token without it, and503withRetry-Afterwhile the broker has nosupplychaintoken at all (auth off, or only the legacy shared token). - Plain JSON (no
Content-Encoding), at most 256 KiB, received within 30 s. At most 32 signatures and 64 attestations (413); long strings are truncated. 422for an unknownschema_version,verdictorsigner_kind, a body digest that differs from the path, a control character, U+2028/U+2029 or a bidi/format control (U+200E/F, U+202A-E, U+2066-9, U+FEFF) in any string, or a malformed reason code (the message names the field),checked_atmore than 60 s ahead, a verdict that does not match the signatures (verifiedwith none verified,unsignedwith signatures, …), or a verified signer’s identity or provenance longer than the limits: those are stored whole or refused, never truncated, because trust policies match them.- A signature or attestation sent as verified must name its signer: a
keyless issuer AND SAN, or a key fingerprint with
signer_kind: key(blank counts as missing). One that names no one is stored as unverified with errorno_signer_identity, and if that leaves averifiedverdict with no verified signature, the verdict is derived again (tamper →invalid, an unchecked key signature →key_signed, elseunknownwith reasonno_signer_identity). The result is kept, so the digest does not read as never checked. - A well-formed reason code this broker does not know (a newer
supplychain) is stored as
unrecognised_reason, logged once per code and counted inkguardian_attestation_unrecognised_reason_total; it never changes the verdict. Unknown verdicts and signer kinds are still422. - The digest must be in the image inventory (
404otherwise). - A result replaces the stored one only with a strictly newer
checked_at. The same result again answers200; a different result with an equal or olderchecked_atanswers409. New results answer201.
Retention
An hourly pass deletes results whose digest has left the image inventory (no workload has run it for thebroker.imageInventory retention window),
and results not re-checked for
broker.supplychain.retention.attestationDays (default 7; 0 keeps them):
the component re-checks every running digest at least daily, so an older
result means it stopped.