Skip to main content

Overview

With supplychain.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.
  • signedVia is index when the signature was found on the multi-arch index that lists the digest rather than on the digest itself; signedDigest is then the index.
  • A key signature has signerKind: "key", keyName and keyFingerprint (sha256 of the DER public key) instead of issuer and san. A key-signed bundle also carries keyHint, verified or not.
  • An unverified signature or attestation has error and detail and no identity: a claimed signer is never shown as a fact. Unverified attestations carry no provenance either.
  • 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. A generation is charged at its worst case, which takes the whole read budget: other reads wait (up to broker.readAcquireWaitMs) or get 503 while it runs.

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; truncated is true when limit cut the list.
  • available: false with a reason when there is nothing to report: no evaluator configured, image trust evaluation off in it (it answers 404), 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”.
  • evaluatedAt is null until the evaluator has finished a pass.
All reads are charged to the broker’s read-memory budget and answer 503 with Retry-After rather than a partial result.

Ingest (supplychain token)

  • Scoped auth only, like the vulnerability ingest: a token with the supplychain scope, 403 otherwise (also with broker auth off).
  • 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.
  • 422 for an unknown schema_version, verdict or signer_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_at more than 60 s ahead, a verdict that does not match the signatures (verified with none verified, unsigned with 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 well-formed reason code this broker does not know (a newer supplychain) is stored as unrecognised_reason, logged once per code and counted in kguardian_attestation_unrecognised_reason_total; it never changes the verdict. Unknown verdicts and signer kinds are still 422.
  • The digest must be in the image inventory (404 otherwise).
  • A result replaces the stored one only with a strictly newer checked_at. The same result again answers 200; a different result with an equal or older checked_at answers 409. New results answer 201.

Retention

An hourly pass deletes results whose digest has left the image inventory (no workload has run it for the broker.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.