Skip to content

image: expose in-toto attestation statements via the API - #52636

Merged
vvoland merged 2 commits into
moby:masterfrom
smerkviladze:add-attestation-statements
Jun 12, 2026
Merged

vvoland merged 2 commits into
moby:masterfrom
smerkviladze:add-attestation-statements

Conversation

@smerkviladze

@smerkviladze smerkviladze commented May 15, 2026

Copy link
Copy Markdown
Contributor

- What I did
Added a new Engine API endpoint, GET /images/{name}/attestations, which returns the in-toto attestation statements attached to an image for a given platform.

Query parameters:

  • platform: JSON-encoded OCI platform; defaults to the daemon’s host platform if omitted.
  • type: comma-separated list of in-toto predicate type URIs; if omitted, all statements are returned.
  • statement: boolean, defaults to false. When true, the daemon reads each matching statement blob and includes the verbatim in-toto JSON in the response. When omitted or false, only the descriptor and predicate type are returned and statement blobs are not read.

The response is a JSON array of statement objects. Each object contains the layer's OCI descriptor (including media type, digest, size, annotations), the in-toto predicate type, and when statement=true is set, the verbatim statement JSON. With statement=true the caller gets the full content inline and avoids additional registry round-trips; without it, the response is metadata only.

- How I did it
The manifest-chain walk (locating the image manifest for the platform and the associated attestation manifest) is delegated to policyimage.ResolveSignatureChain from policy-helpers, ensuring that both Moby and BuildKit agree on how to interpret the attestation storage format.

The statement-layer iteration and blob reading are implemented in daemon/containerd/image_attestations.go. WhenIncludeStatement is set, the daemon fails fast on the first unreadable blob and reads matching blobs eagerly into memory; otherwise the content store is not touched for statement bodies.

The endpoint is implemented for the containerd image store. The legacy graphdriver store returns errdefs.NotImplemented (HTTP 501), allowing clients to distinguish backend non-support from images that have no attestations.

HTTP status codes:

  • 200 - success (empty array when the image has no attestations)
  • 400 - malformed platform value, or API version below 1.55
  • 404 - unknown image, or no manifest for the requested platform
  • 500 - server error (e.g., blob read failure)
  • 501 - legacy graphdriver backend; attestations not supported

The Go client SDK exposes (*Client).ImageAttestations(...), returning an ImageAttestationsResult wrapper that contains an Items slice.

- How to verify it

  • Build an image with attestations.

  • Query the endpoint:

    curl --unix-socket /var/run/docker.sock \
      'http://localhost/v1.55/images/attest-test:latest/attestations' | jq '.'
    
  • Include statement bodies:

    curl --unix-socket /var/run/docker.sock \
      'http://localhost/v1.55/images/moby/buildkit:latest/attestations?statement=true' | jq '.'
    
  • Or filter by predicate type:

    curl --unix-socket /var/run/docker.sock \
      'http://localhost/v1.55/images/attest-test:latest/attestations?type=https://slsa.dev/provenance/v1' | jq '.'
    

Automated coverage:

  • Unit tests in daemon/containerd/image_provenance_test.go cover layer iteration, filtering behavior, and error propagation.
  • Integration tests in integration/image/attestation_test.go exercise the HTTP endpoint end-to-end via the client SDK.

- Human readable description for the release notes

Add `GET /images/{name}/attestations` endpoint returns in-toto attestation statements (such as SLSA provenance and SPDX SBOM) attached to an image, with optional platform selection, predicate type filtering, and an opt-in `statement` query parameter for retrieving the verbatim statement bodies. Clients can now retrieve attestation metadata and content directly from the daemon instead of performing additional registry round-trips.

@github-actions github-actions Bot added impact/changelog area/images Image Service area/daemon Core Engine containerd-integration Issues and PRs related to containerd integration module/api labels May 15, 2026
@smerkviladze
smerkviladze force-pushed the add-attestation-statements branch from dce3aca to e394bdd Compare May 15, 2026 18:02
@corhere corhere added this to the 29.6.0 milestone May 15, 2026
@corhere corhere added the kind/enhancement Enhancements are not bugs or new features but can improve usability or performance. label May 15, 2026
Comment thread api/types/image/manifest.go Outdated
@corhere

corhere commented May 21, 2026

Copy link
Copy Markdown
Contributor

Our ultimate goal is to evaluate policies during the container lifecycle with the same information as is available to Buildkit build policies. Buildkit does the heavy lifting of finding all the relevant attestations for the image and sending them to the builder: ResolveSourceImageRequest.ResolveAttestations. We need some functional equivalent of that. Clients could use gRPC to fetch the content blobs directly, sure, but they need to know the digest of the blobs to do so. From discussion with @vvoland in the maintainers call it is looking like we'd need to extend the Engine API to expose the layer descriptors for attestation manifests. That would also push a lot of the complexity of resolving the attestation chain to the Engine API client, in start contrast to Buildkit builders.

Since it's looking like an Engine API change would be needed regardless of which approach we take, I propose we add a new endpoint (strawman: GET /images/{name}/attestations?platform=<platform>&type=<csv>) to afford Engine API clients the same functionality as ResolveAttestations for Buildkit builders.

@smerkviladze
smerkviladze force-pushed the add-attestation-statements branch from 820ce5a to d92302f Compare June 5, 2026 12:46
Comment thread client/client_interfaces.go Outdated
Comment thread daemon/images/image_attestations.go
@smerkviladze
smerkviladze requested a review from vvoland June 10, 2026 12:53
Comment thread daemon/containerd/image_attestations.go Outdated
}

func (p *localReferrersProvider) FetchReferrers(ctx context.Context, dgst digest.Digest, opts ...remotes.FetchReferrersOpt) ([]ocispec.Descriptor, error) {
return nil, nil

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would silently return empty results for DHI-style attestations? Should we detect that case and return a proper error/warning?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vvoland, FetchReferrers does return empty here, but ResolveSignatureChain then errors out for DHI indexes with "no attestation referrers found for DHI manifest < digest >", and we propagate that as a 500. So users do see something, just not a great error.

To address this, we could detect DHI explicitly and return errdefs.NotImplemented (HTTP 501) with a clear message. policy-helpers already has the detection logic (currently as a private isDHIIndex), so we could export it as IsDHIIndex in a policy-helpers PR and then bump the dependency in moby/moby to use it.

Would you be okay with that approach? And if so, would you prefer it as part of this PR or as a follow-up?

Comment thread api/types/image/attestation.go Outdated
@vvoland vvoland mentioned this pull request Jun 11, 2026

@vvoland vvoland left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

Before merging, could you please rebase and squash the commits?

@smerkviladze
smerkviladze force-pushed the add-attestation-statements branch from f0704dd to 5e76dbb Compare June 12, 2026 09:17
Comment thread api/swagger.yaml
@smerkviladze
smerkviladze force-pushed the add-attestation-statements branch 2 times, most recently from 26afddc to 4c330b8 Compare June 12, 2026 09:38
Add a new Engine API endpoint that returns the in-toto attestation
statements attached to an image for a given platform. The endpoint
locates the attestation manifest(s) referencing the requested platform's
image manifest, enumerates the statement layers, and returns each
layer's OCI descriptor (including media type, digest, size, and
annotations) together with its in-toto predicate type.

Query parameters:
  - platform: JSON-encoded OCI platform; defaults to the daemon's host
    platform if omitted.
  - type: comma-separated list of in-toto predicate type URIs; if
    omitted, all statements are returned.
  - statement: boolean, defaults to false. When true, the daemon reads
    each matching statement blob and includes the verbatim in-toto JSON
    in the response. When false (or omitted), statement blobs are not
    read and the Statement field is absent from each entry.

The manifest-chain walk (locating the platform image manifest and its
associated attestation manifest) is delegated to policy-helpers'
image.ResolveSignatureChain so that moby and BuildKit agree on how to
interpret the attestation storage format. The statement-layer iteration
and blob reading is inlined: when statement bodies are requested it
fails fast on the first unreadable blob and reads matching blobs
eagerly into memory; otherwise statement-layer blobs are never read
from the content store.

The endpoint is implemented for the containerd image store. The legacy
graphdriver store returns errdefs.NotImplemented (HTTP 501).

Signed-off-by: Sopho Merkviladze <[email protected]>
@smerkviladze
smerkviladze force-pushed the add-attestation-statements branch from 4c330b8 to a0b6dbc Compare June 12, 2026 09:41
@vvoland
vvoland requested a review from thaJeztah June 12, 2026 11:25
Comment thread client/image_attestations.go
Both query parameters are now collectionFormat: multi arrays in the
swagger so they can accept multiple values later without an API
version bump. The server still operates on a single platform and
rejects requests passing more than one; type is read directly as a
list of repeated values instead of a comma-separated string.

Signed-off-by: Sopho Merkviladze <[email protected]>
Comment thread daemon/containerd/image_attestations.go

@thaJeztah thaJeztah left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, thanks!

@thaJeztah

Copy link
Copy Markdown
Member

Windows failure is unrelated (no need to re-run CI for that);

=== Failed
=== FAIL: integration-cli TestDockerAPISuite/TestContainersAPICreateMountsCreate/1_config:_{volume__c:\foo\_false__<nil>_<nil>_<nil>_<nil>_<nil>} (12.93s)
    docker_api_containers_test.go:1667: timeout hit after 10s: container 5e4ca1bf670d99ac62b6b941474602db2561004a226c687231eea40e6aa2b875 is running, waiting for exit

=== FAIL: integration-cli TestDockerAPISuite/TestContainersAPICreateMountsCreate (37.79s)

=== FAIL: integration-cli TestDockerAPISuite (579.85s)

@vvoland
vvoland merged commit 5eda928 into moby:master Jun 12, 2026
311 of 313 checks passed
@vvoland vvoland added kind/feature Functionality or other elements that the project doesn't currently have. Features are new and shiny and removed kind/enhancement Enhancements are not bugs or new features but can improve usability or performance. labels Jun 12, 2026
@smerkviladze
smerkviladze deleted the add-attestation-statements branch June 16, 2026 10:40
@docker-agent docker-agent mentioned this pull request Jun 18, 2026
3 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/daemon Core Engine area/images Image Service containerd-integration Issues and PRs related to containerd integration impact/changelog kind/feature Functionality or other elements that the project doesn't currently have. Features are new and shiny module/api module/client

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants