feat: Add a shared builder workflow for container images - #122
Closed
dervoeti wants to merge 1 commit into
Closed
Conversation
dervoeti
force-pushed
the
feat/reusable-build-container-image
branch
from
August 6, 2026 14:57
e647534 to
8e95876
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
As we found out that https://github.com/slsa-framework/slsa-github-generator will likely be deprecated soon, we decided to use https://github.com/actions/attest instead.
Repositories that build their images in a top-level workflow satisfy SLSA Build Level 2. GitHub's documented mechanism for Build Level 3 is that the build runs in a reusable workflow, separate from the workflow calling it (see https://docs.github.com/en/actions/concepts/security/artifact-attestations#slsa-levels-for-artifact-attestations).
Currently, the operators each build in their own top-level
build.yaml, so would be at L2 today. This PR adds the builder they will call.docker-imageskeeps its own in-repository reusable workflow, because it would need a second one here, so the signer identity count is the same either way (one for operators, one for product images) and the "one identity for the platform" argument does not apply to it. The fact that it has a reusable workflow satisfies GitHub's criterion for SLSA L3, no need to move it here.reusable_build_container_image.yamlperforms the whole chain itself: checks out the caller's source, builds the per-architecture images, pushes and attests each one, assembles the image index from exactly those digests, then signs and attests the index.The reason for this design is: The digest must never reach the attesting step as an input from the caller, because anyone able to trigger the calling workflow could point it at an image they had pushed themselves and obtain provenance for it under the trusted generator identity. Attestation therefore happens either in the job that pushed the image, using the digest docker push returned, or in a job of this same workflow that receives that digest as a job output. Neither path is reachable by a caller.
So the inputs are source-level build parameters only (Dockerfile path, build context, build arguments, tags, registries), to limit what a potential attacker can provide as input.
Verification would work like this:
--signer-workflowon its own is not enough: it takes no ref component and matches any ref, including a branch, so someone could push a branch to this repo with a modified builder and satisfy it.--signer-digestis what closes that. It pins the exact build definition by its commit hash.Supporting changes
publish-imageexposes the digest it already resolved after pushing, asimage-repository-digestandimage-digest. The latter is whatactions/attesttakes assubject-digest, which is how each per-arch image gets its own provenance.docker-imagesneeds this too, for the per-architecture attestation in its own reusable workflow, so this action change unblocks that future PR as well.publish-image-index-manifestgainsimage-manifest-digests, so the index is built from the exact digests produced rather than by re-resolving<tag>-<arch>tags..github/actionlint.yamlgains ignores forjob.workflow_repositoryandjob.workflow_sha. These are documented context properties (https://docs.github.com/en/actions/reference/workflows-and-actions/contexts) and the docs show exactly theactions/checkoutusage we rely on, but actionlint does not know them, see rhysd/actionlint#705.