Skip to content

feat(sbom): generate SBOM per platform for multi-platform images - #232

Open
reyreavman wants to merge 18 commits into
mainfrom
feat/sbom/per-platform-sbom
Open

feat(sbom): generate SBOM per platform for multi-platform images#232
reyreavman wants to merge 18 commits into
mainfrom
feat/sbom/per-platform-sbom

Conversation

@reyreavman

Copy link
Copy Markdown
Collaborator

Summary

For a multi-platform image the SBOM was generated once per image name: syft scanned the index tag (yielding whatever platform the build host pulled), the io.werf.target-platform annotation was taken from the first platform image, the base SBOM was merged from the first platform only, and the in-toto subject pointed at the index digest. This PR generates one SBOM per platform, attached to the fallback tag of that platform's manifest digest, with a truthful subject, annotation and per-platform base/import merge — the cosign v3 discovery model (cosign sign --recursive / verify-attestation --platform).

Key changes

  • Per-platform generation: convergeImageSbom runs the single-platform pipeline once per platform image (own stage descriptor, own base/import SBOM merge, own scan options, own platform annotation). The branch that built a throwaway MultiplatformImage with an always-nil stage descriptor is deleted, removing a latent nil dereference along the way.
  • Cache: target platform is included in the SBOM checksum (only when set — single-platform checksums stay byte-identical, proven by unchanged golden hashes) and passed to the backend pull.
  • Legacy bases: building on top of a multi-platform base whose SBOM was attached to the index digest by an older version now fails with an actionable "rebuild the base image with a newer werf version" error instead of silently merging a platform-ambiguous SBOM.
  • CLI:
    • werf sbom get and werf attest get/verify accept --platform; an index reference without it fails listing the available platforms and their digests (no host-platform default). sbom get IMAGE_NAME selects the exported image by platform instead of taking the first name match.
    • werf attest ls expands an image index and lists the attestations of every platform in one table with a PLATFORM column.
    • --platform against a single-platform manifest is validated against the config platform (mismatch is an error, not a silent pass-through); platform input is normalized (linux/arm64/v8linux/arm64) consistently across sbom and attest commands; variant matching is restricted to os/arch requests (bare linux is rejected). New helpers live in pkg/oci/artifact/platform.go.
  • Cleanup: no new deletion logic — the existing digest-generic orphan pass collects per-platform fallback tags once the platform manifests are deleted; covered by new discovery-layer and delete-loop tests.
  • e2e: new multiplatform-labeled suite asserts per-platform artifacts, subjects, annotations, absence of an index-digest artifact and cache reuse on rebuild.
  • Spec-kit artifacts for the feature added under specs/016-sbom-multiplatform-per-platform/ (status: migrated).

Why

Signing an SBOM asserts "this SBOM correctly describes this image". The old model produced an SBOM that claimed one platform while describing another, which made multi-platform SBOM signing (the parent feature) impossible to do honestly. Per-platform SBOMs attached to platform manifest digests are what the OCI distribution-spec referrers model prescribes (association is strictly per manifest digest) and what cosign discovery expects, so cosign verify-attestation --platform <index-ref> works out of the box.

Verification

  • Deletion-chain audit for cleanup: platform stages of a deleted index are deleted in the same run (no protection left), making their fallback tags orphans; a platform stage shared with another protected index correctly keeps its SBOM.
  • Storage model checked against OCI image-spec 1.1 (subject/artifactType/empty-config artifact guidelines) and distribution-spec 1.1 (referrers tag schema is per subject digest — one fallback tag per platform manifest is the prescribed layout).
  • The e2e suite could NOT be run locally (macOS): it needs Linux CI with docker buildx + QEMU binfmt (task test:setup:environment). It is the merge gate for the end-to-end claims (subject/annotation/index-absence/cache).
  • Manual QA against a live registry was not performed.

Review focus / risks

  • Breaking change docs(readme): focus on Deckhouse Delivery Kit #1 (accepted, no feature flag): builds on top of multi-platform base images whose SBOM is in the legacy index-attached format fail until the base is rebuilt. Single-platform bases are unaffected. Needs release-notes visibility. The legacy hint in the error is unconditional — the format cannot be detected (registries have no child→parent resolution).
  • Breaking change docs: remove irrelevant documentation #2 (intended fix): sbom get --tag/--digest on a multi-platform reference now errors with a platform list instead of silently returning the build host's platform SBOM.
  • Stage digests and content-based tags are NOT affected — the platform enters only the SBOM artifact checksum annotation, so no user stage cache invalidates and no tags move. Multi-platform SBOM artifacts regenerate on first build after upgrade (old index-attached ones become orphans and are collected by cleanup).
  • pkg/sbom/image.PullSBOMByTag (exported) is removed — its single caller moved to command-level resolution.
  • pkg/build/build_phase.go: verify the per-platform loop uses the loop variable everywhere (base/import/scan/gost/platform) — the old code took everything from images[0], and this property is guarded only by the e2e suite.

@reyreavman
reyreavman marked this pull request as ready for review August 7, 2026 07:17
@reyreavman
reyreavman force-pushed the feat/sbom/per-platform-sbom branch from 41839b7 to 3c51145 Compare August 7, 2026 08:07
Previously the SBOM cache checksum ignored the target platform and the
image pull used no platform selection, so multi-platform images could
reuse an SBOM generated for a different platform. Include the platform
in the checksum (only when set, keeping single-platform checksums
unchanged) and pass it to the container backend pull.

Signed-off-by: Radmir Khurum <radmir.khurum@flant.com>
Previously a single SBOM was generated per image name: the image index
tag was scanned (yielding host-platform content), the platform
annotation was taken from the first platform image, and the base image
SBOM was merged from the first platform only. Generate an SBOM for each
platform image instead, scanning its own stage, merging its own base
and import SBOMs and annotating its real platform. This also removes
the branch constructing a throwaway MultiplatformImage whose stage
descriptor was always nil.

Signed-off-by: Radmir Khurum <radmir.khurum@flant.com>
Per-platform SBOM lookup cannot find SBOMs that an older werf attached
to the image index digest. Extend the missing-base-SBOM error to tell
the user that such legacy platform-ambiguous SBOMs are unusable and the
base image must be rebuilt with a newer werf version.

Signed-off-by: Radmir Khurum <radmir.khurum@flant.com>
SBOM artifacts for multi-platform images are attached to platform
manifest digests, so CLI commands need to resolve an index reference to
a concrete platform manifest. Add ResolvePlatformDigest (index-aware
digest resolution with an explicit available-platforms error when the
platform is not specified) and ListIndexPlatforms (platform inventory
of an index) to pkg/oci/artifact.

Signed-off-by: Radmir Khurum <radmir.khurum@flant.com>
Previously sbom get silently returned whatever SBOM was attached to the
resolved digest, which for a multi-platform image meant nothing or the
wrong platform. Resolve index references through the platform helpers:
--tag and --digest now accept --platform and fail with the list of
available platforms when it is missing, and the positional mode selects
the exported image by platform instead of taking the first name match.
Remove PullSBOMByTag which became dead after moving resolution into the
command.

Signed-off-by: Radmir Khurum <radmir.khurum@flant.com>
Attestations of multi-platform images are attached to platform manifest
digests, so an index reference used to yield nothing. Add --platform to
attest get and verify with an explicit available-platforms error when
it is missing, and make attest ls expand an image index listing the
attestations of every platform in one table with a PLATFORM column.

Signed-off-by: Radmir Khurum <radmir.khurum@flant.com>
Per-platform SBOMs attach fallback tags to platform manifest digests.
Prove the existing digest-generic orphan pass collects them: discovery
tests for GetOrphanedArtifactNames with per-platform tags (orphaned
when the platform manifest is gone, kept while it exists) and a delete
loop entry for per-platform tag names.

Signed-off-by: Radmir Khurum <radmir.khurum@flant.com>
Cover the per-platform SBOM model end to end: a two-platform build must
attach one SBOM artifact to each platform manifest digest with a
truthful in-toto subject and target-platform annotation, attach nothing
to the index digest, and serve both platform SBOMs from cache on
rebuild.

Signed-off-by: Radmir Khurum <radmir.khurum@flant.com>
Previously a --platform passed against a single-platform manifest was
silently ignored, a bare OS like "linux" could prefix-match an index
entry, and attest commands compared the raw platform string while sbom
get normalized it. Validate the requested platform against the manifest
config platform, restrict variant matching to os/arch requests, and
normalize the platform inside ResolvePlatformDigest and attest ls.

Signed-off-by: Radmir Khurum <radmir.khurum@flant.com>
The manual read loop swallowed non-EOF errors, surfacing truncated
payloads as confusing JSON parse failures.

Signed-off-by: Radmir Khurum <radmir.khurum@flant.com>
Reverse-engineer spec.md, plan.md and tasks.md from the implemented
feat/sbom/per-platform-sbom branch to bring the feature into the SDD
workflow. All tasks are marked complete; identified gaps (unexecuted
e2e, unconditional legacy-base hint, accepted breaking changes) are
recorded for follow-up.

Signed-off-by: Radmir Khurum <radmir.khurum@flant.com>
CI runners use the default buildx builder with the docker driver, which
rejects multi-platform builds. Build each platform with a plain docker
build (the builder-base Dockerfile has no RUN instructions, so no
emulation is involved), push the per-platform tags and assemble the
image index in-test via go-containerregistry instead.

Signed-off-by: Radmir Khurum <radmir.khurum@flant.com>
SBOM generation is only supported for Stapel images; the Dockerfile
fixture caused werf to reject the config at load time.

Signed-off-by: Radmir Khurum <radmir.khurum@flant.com>
SBOM generation only supports stapel images, and stapel images for
non-amd64 platforms can only be built with the Buildah backend — the
Docker-Server backend rejects them. Switch the test entry from BuildKit
Docker to native Buildah with chroot isolation (the combination the
multiarch build suite already exercises on CI) and add the simple label
so the test runs in the e2e_simple lane instead of extra.

Signed-off-by: Radmir Khurum <radmir.khurum@flant.com>
Both platforms build natively on amd64 runners, removing the arm64
emulation dependency from the multiplatform SBOM suite.

Signed-off-by: Radmir Khurum <radmir.khurum@flant.com>
The per-platform SBOM loop constructs the PM BOM patcher per platform
image, and no test exercised that combination. Build a two-platform
stapel image with an os-pm packages directive on a synthesized
multi-arch trusted builder base (alpine + pm binary copied from the
container-factory image) and assert that the pm.lock components appear
in the SBOM of every platform with a correct in-toto subject.

Signed-off-by: Radmir Khurum <radmir.khurum@flant.com>
SBOM generation only supports stapel images and the stapel toolchain is
exercised on CI for linux/amd64 and linux/arm64, so use exactly those
platforms. Replace the docker-built trusted builder bases with
scratch-based stapel images: scratch needs no trusted-base chain and is
platform-neutral. The packages case receives the pm binary and CA
certificates via the git stage — the test extracts them from the
container factory image with go-containerregistry and commits them into
the test repository, removing docker and Dockerfiles from the suite
entirely.

Signed-off-by: Radmir Khurum <radmir.khurum@flant.com>
@reyreavman
reyreavman force-pushed the feat/sbom/per-platform-sbom branch from acb1efd to 2ce9f5e Compare August 10, 2026 06:42
Multi-platform stapel builds require the Buildah backend, and SBOM
generation does not work with Buildah yet — follow the suite convention
and keep the Buildah entries as XEntry until it does. The suite stays
compiled and ready to enable.

Signed-off-by: Radmir Khurum <radmir.khurum@flant.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant