posit-dev / posit-dev/images-shared
Spike: determine signing/attestation method for container images
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 2
- Forks
- 0
- Avg merge
- 4d 13h
- Merged PRs (30d)
- 22
Description
Part of #453.
Determine which mechanism Posit should use to produce and sign build-provenance/SBOM attestations for published container images, before the implementation sub-issues below are scoped precisely.
Candidates to evaluate: GitHub Artifact Attestations (Sigstore-backed), Notation/Notary v2, self-managed cosign + KMS, others surfaced during investigation.
Deliverable: a documented recommendation that the implementation sub-issues can be scoped against, covering:
- Method + rationale — which mechanism, and why.
- Where in the pipeline attestation happens — build, post-merge, or post-SOCI.
- How attestations reach every destination registry — Docker Hub, GHCR, ECR.
Points 2 and 3 are not free choices. See the pipeline constraints below.
Starting point / research so far:
| Option | Trust model | Key mgmt | Notes |
|---|---|---|---|
| GitHub Artifact Attestations | Sigstore (Fulcio/Rekor), keyless OIDC | None | Verifiable via gh attestation verify or cosign verify-attestation (>=2.4.0). The SLSA project itself is deprecating its standalone slsa-github-generator in favor of this. |
| Notation / Notary v2 | Hierarchical PKI, TUF | Certs/HSM | Supports multiple independent signatures per image (e.g. build + separate compliance sign-off). Recommended more for enterprises with existing PKI than OSS projects. |
| Self-managed cosign + KMS | Sigstore tooling, static keys | KMS-backed keypair | Avoids depending on public Fulcio/Rekor; loses keyless convenience and public transparency log unless Rekor is self-hosted too. |
| Docker Content Trust (Notary v1) | Hierarchical, offline keys | Notary server + MySQL + mTLS | Legacy; one signature per image max, no multi-registry portability. |
| GPG/PGP | Web of trust | Manual | No OCI-native signature distribution; industry is actively moving away from it. |
Given our images are public, multi-registry (Docker Hub + GHCR + ECR), and built exclusively on GitHub Actions, GitHub Artifact Attestations is the leading candidate — but this issue should produce a documented decision rather than assume it.
Pipeline constraints
Our publish path constrains where and how attestation can happen, possibly
more than trust model constrains which method. These should be settled as part
of this spike, because they may discriminate between candidates.
Constraints 1 and 3 are derived from reading the code and need empirical
confirmation. Constraint 2 is structural.
1. The merge job rebuilds the index, so build-time attestations may not survive
Per-platform builds push by digest, untagged (push-by-digest: True,
name-canonical: True in image_target.py). The merge job then constructs a
new index with oras manifest index create, from get_merge_sources()
(image_target.py:794) — which returns exactly one ref per platform, the most
recent artifact for each distinct metadata.platform.
Buildx attaches provenance and SBOM as extra manifests in the index, which
summary.py:125 already accounts for. But a reconstructed index contains only the
sources it was given, and attestation manifests are not platform images.
To confirm: build one target with --provenance=mode=max, then inspect the
merged index at the temp registry for attestation manifests. If they are absent,
attestation must happen after merge — which decides whether the flags in #705
belong on bakery build or on the publish stage.
2. SOCI conversion changes the published digest
For SOCI-enabled images (connect has soci: enabled: true):
index create -> temp_ref
SOCI convert -> temp_ref becomes f"{source_ref}-soci" (soci.py:152)
-> content is the converted layout; digest read from its
index.json (soci.py:177)
oras cp -> Docker Hub, GHCR
The digest that ships is not the digest buildx produced. An attestation bound
at build time would reference a digest that is never published, so verification
against the published image would fail.
Attestation must therefore be produced against the final, post-SOCI digest. Worth
enumerating which images actually have SOCI enabled when sizing this.
3. oras cp does not copy referrers
Sigstore-style attestations — including actions/attest-build-provenance and
cosign attest — are stored as referring artifacts carrying a subject
field, discovered through the Referrers API. They are separate manifests, not part
of the image index.
OrasCopy builds its command as
oras cp [--plain-http] [--from-oci-layout] [--to-oci-layout] <src> <dst>
(oras.py:155-163). There is no -r / --recursive, so referrers attached to
the temp-registry image would not be copied to Docker Hub or GHCR — the
attestation would exist only in the temp registry, which clean.yml
garbage-collects.
Likely a one-flag fix, but it should be a stated requirement rather than a
discovery during rollout. It also interacts with #759 and with any change to
publish from a local OCI layout.
4. Conditional — the invoking repository may become internal
Relevant only if #680 lands. That design has an internal orchestrator repo
dispatch the build, so provenance would name posit-dev/images-build — a
repository public verifiers cannot read.
The attestation content stays public (it lives in the registry), but the
verification identity names something unreadable, and users would naturally
try --repo posit-dev/images-connect and fail. This affects candidates
differently:
- GitHub Artifact Attestations bind to the invoking repo and workflow ref. The
internal repo name appears in the Fulcio certificate identity, and
gh attestation verifyexpects--repo. Workable, but the verification recipe
is non-obvious and needs documenting. - Self-managed cosign + KMS binds to a key we control, decoupling the
assertion from repository visibility entirely.
Not an argument against the leading candidate — the disclosure is arguably
correct — but it belongs in the comparison, and it is cheaper to weigh now than
after #705/#706 are scoped.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with image_target.py:794 and summary.py:125, then inspect soci.py:152,177 and oras.py:155-163. Build a target with --provenance=mode=max and inspect the merged index at the temporary registry, including which images use SOCI. Done means a documented recommendation covering the method, pipeline stage, propagation to Docker Hub, GHCR, and ECR, and the internal-repository verification case.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- docker, github-actions, python
- Domain
- build-system, devops, release, security
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100