feat(oci): index annotations and cosign accessory mirroring #53

Merged
ryangr0 merged 1 commit from feat/oci-annotations-and-accessory-mirroring into main 2026-08-26 09:03:56 +00:00
Owner

Two gaps found publishing webgrip/ploeg to GHCR, both invisible until someone looked at the package page. Both new inputs default to off, so every existing caller is unaffected.

1. annotations on the build composite

GHCR links a package to a repository from org.opencontainers.image.source, and reads it from the manifest — not from the image config. A Dockerfile LABEL lands in the per-platform config, so ploeg's multi-arch image published with the label correctly set and linked nothing, while its Helm chart (a single manifest, annotated by Helm) linked fine. The label was right and in the wrong place.

New annotations input takes buildx's own LEVEL:key=value form, parsed exactly like docker-build-args — one argv element per line, blanks and comment lines dropped. The comment rule is not cosmetic here either: --provenance=mode=max would otherwise write a caller's prose into the artifact's own metadata.

Annotating at build rather than at copy was measured, not assumed. Against two local registries:

plain imagetools create preserves index annotations ✅
index digest across the copy sha256:4677b0ad… → sha256:4677b0ad… identical

So one annotation at build reaches Harbor, Forgejo and GHCR, and the identical-digest property the mirroring design rests on is untouched. Annotating at copy would have diverged GHCR's index digest from Harbor's.

2. copy-accessories on github-distribute

imagetools create copies the manifest list. BuildKit's SLSA provenance and SBOM ride along inside the index for free — GHCR already has both, confirmed by reading the in-toto predicates back (spdx.dev/Document, slsa.dev/provenance/v1). But cosign's signature and attestations do not: they live as separate sha256-<digest>.sig / .att tags and were being left behind, publishing an unsigned mirror of a signed image.

Adds cosign copy --only=sig,att,sbom after the image copy, so the image is not pushed twice.

Verified end to end: signed an image at one registry, cosign copy to a different host:port, verified at the destination — passes, because cosign checks the digest.

One property worth knowing, recorded on the input: the signature payload keeps the origin reference —

"docker-reference":"host.docker.internal:5555/exp/src"

so a policy pinning docker-reference must verify against the source registry, not the mirror. Kyverno currently verifies against Harbor, so nothing changes today.

Fail-soft on a missing signature: an unsigned source is legitimate (the signing job is independent and may not have run for a given tag), and failing the mirror over it would make the image unavailable rather than merely unverifiable.

Two gaps found publishing `webgrip/ploeg` to GHCR, both invisible until someone looked at the package page. Both new inputs default to **off**, so every existing caller is unaffected. ## 1. `annotations` on the build composite GHCR links a package to a repository from `org.opencontainers.image.source`, and reads it from the **manifest** — not from the image config. A Dockerfile `LABEL` lands in the per-platform config, so ploeg's multi-arch image published with the label correctly set and linked **nothing**, while its Helm chart (a single manifest, annotated by Helm) linked fine. The label was right and in the wrong place. New `annotations` input takes buildx's own `LEVEL:key=value` form, parsed exactly like `docker-build-args` — one argv element per line, blanks and comment lines dropped. The comment rule is not cosmetic here either: `--provenance=mode=max` would otherwise write a caller's prose into the artifact's own metadata. **Annotating at build rather than at copy was measured, not assumed.** Against two local registries: | | | |---|---| | plain `imagetools create` preserves index annotations | ✅ | | index digest across the copy | `sha256:4677b0ad…` → `sha256:4677b0ad…` identical | So one annotation at build reaches Harbor, Forgejo *and* GHCR, and the identical-digest property the mirroring design rests on is untouched. Annotating at copy would have diverged GHCR's index digest from Harbor's. ## 2. `copy-accessories` on github-distribute `imagetools create` copies the manifest list. BuildKit's SLSA provenance and SBOM ride along **inside** the index for free — GHCR already has both, confirmed by reading the in-toto predicates back (`spdx.dev/Document`, `slsa.dev/provenance/v1`). But cosign's signature and attestations do not: they live as separate `sha256-<digest>.sig` / `.att` tags and were being left behind, publishing an **unsigned mirror of a signed image**. Adds `cosign copy --only=sig,att,sbom` after the image copy, so the image is not pushed twice. Verified end to end: signed an image at one registry, `cosign copy` to a different host:port, verified at the destination — **passes**, because cosign checks the digest. One property worth knowing, recorded on the input: the signature payload keeps the **origin** reference — ```json "docker-reference":"host.docker.internal:5555/exp/src" ``` so a policy pinning `docker-reference` must verify against the source registry, not the mirror. Kyverno currently verifies against Harbor, so nothing changes today. Fail-soft on a missing signature: an unsigned source is legitimate (the signing job is independent and may not have run for a given tag), and failing the mirror over it would make the image *unavailable* rather than merely unverifiable.
feat(oci): index annotations and cosign accessory mirroring
Some checks failed
docker-build-and-push-registry.yml / Merge pull request 'feat(oci): index annotations and cosign accessory mirroring' (#53) from feat/oci-annotations-and-accessory-mirroring into main (pull_request) Failing after 0s
docker-build-and-push.yml / Merge pull request 'feat(oci): index annotations and cosign accessory mirroring' (#53) from feat/oci-annotations-and-accessory-mirroring into main (pull_request) Failing after 0s
forgejo-distribute.yml / Merge pull request 'feat(oci): index annotations and cosign accessory mirroring' (#53) from feat/oci-annotations-and-accessory-mirroring into main (pull_request) Failing after 0s
github-distribute.yml / Merge pull request 'feat(oci): index annotations and cosign accessory mirroring' (#53) from feat/oci-annotations-and-accessory-mirroring into main (pull_request) Failing after 0s
github-issue-create-by-prompt.yml / Merge pull request 'feat(oci): index annotations and cosign accessory mirroring' (#53) from feat/oci-annotations-and-accessory-mirroring into main (pull_request) Failing after 0s
github-issues-create-by-prompt.yml / Merge pull request 'feat(oci): index annotations and cosign accessory mirroring' (#53) from feat/oci-annotations-and-accessory-mirroring into main (pull_request) Failing after 0s
helm-chart-deploy.yml / Merge pull request 'feat(oci): index annotations and cosign accessory mirroring' (#53) from feat/oci-annotations-and-accessory-mirroring into main (pull_request) Failing after 0s
helm-chart-push.yml / Merge pull request 'feat(oci): index annotations and cosign accessory mirroring' (#53) from feat/oci-annotations-and-accessory-mirroring into main (pull_request) Failing after 0s
helm-charts-deploy.yml / Merge pull request 'feat(oci): index annotations and cosign accessory mirroring' (#53) from feat/oci-annotations-and-accessory-mirroring into main (pull_request) Failing after 0s
helm-charts-push.yml / Merge pull request 'feat(oci): index annotations and cosign accessory mirroring' (#53) from feat/oci-annotations-and-accessory-mirroring into main (pull_request) Failing after 0s
laravel-quality.yml / Merge pull request 'feat(oci): index annotations and cosign accessory mirroring' (#53) from feat/oci-annotations-and-accessory-mirroring into main (pull_request) Failing after 0s
php-application-static-analysis.yml / Merge pull request 'feat(oci): index annotations and cosign accessory mirroring' (#53) from feat/oci-annotations-and-accessory-mirroring into main (pull_request) Failing after 0s
rust-semantic-release.yml / Merge pull request 'feat(oci): index annotations and cosign accessory mirroring' (#53) from feat/oci-annotations-and-accessory-mirroring into main (pull_request) Failing after 0s
semantic-release-monorepo.yml / Merge pull request 'feat(oci): index annotations and cosign accessory mirroring' (#53) from feat/oci-annotations-and-accessory-mirroring into main (pull_request) Failing after 0s
semantic-release.yml / Merge pull request 'feat(oci): index annotations and cosign accessory mirroring' (#53) from feat/oci-annotations-and-accessory-mirroring into main (pull_request) Failing after 0s
setup-repository-bootstrap.yml / Merge pull request 'feat(oci): index annotations and cosign accessory mirroring' (#53) from feat/oci-annotations-and-accessory-mirroring into main (pull_request) Failing after 0s
setup-repository-copilot-files.yml / Merge pull request 'feat(oci): index annotations and cosign accessory mirroring' (#53) from feat/oci-annotations-and-accessory-mirroring into main (pull_request) Failing after 0s
setup-repository-create-from-template.yml / Merge pull request 'feat(oci): index annotations and cosign accessory mirroring' (#53) from feat/oci-annotations-and-accessory-mirroring into main (pull_request) Failing after 0s
spa-preview.yml / Merge pull request 'feat(oci): index annotations and cosign accessory mirroring' (#53) from feat/oci-annotations-and-accessory-mirroring into main (pull_request) Failing after 0s
static-analysis.yml / Merge pull request 'feat(oci): index annotations and cosign accessory mirroring' (#53) from feat/oci-annotations-and-accessory-mirroring into main (pull_request) Failing after 0s
sync-template-files.yml / Merge pull request 'feat(oci): index annotations and cosign accessory mirroring' (#53) from feat/oci-annotations-and-accessory-mirroring into main (pull_request) Failing after 0s
techdocs-deploy-backstage-s3.yml / Merge pull request 'feat(oci): index annotations and cosign accessory mirroring' (#53) from feat/oci-annotations-and-accessory-mirroring into main (pull_request) Failing after 0s
techdocs-deploy-codeberg.yml / Merge pull request 'feat(oci): index annotations and cosign accessory mirroring' (#53) from feat/oci-annotations-and-accessory-mirroring into main (pull_request) Failing after 0s
techdocs-deploy-docs-site.yml / Merge pull request 'feat(oci): index annotations and cosign accessory mirroring' (#53) from feat/oci-annotations-and-accessory-mirroring into main (pull_request) Failing after 0s
techdocs-deploy-gh-pages.yml / Merge pull request 'feat(oci): index annotations and cosign accessory mirroring' (#53) from feat/oci-annotations-and-accessory-mirroring into main (pull_request) Failing after 0s
tests.yml / Merge pull request 'feat(oci): index annotations and cosign accessory mirroring' (#53) from feat/oci-annotations-and-accessory-mirroring into main (pull_request) Failing after 0s
update_mkdocs.yml / Merge pull request 'feat(oci): index annotations and cosign accessory mirroring' (#53) from feat/oci-annotations-and-accessory-mirroring into main (pull_request) Failing after 0s
update_techdocs.yml / Merge pull request 'feat(oci): index annotations and cosign accessory mirroring' (#53) from feat/oci-annotations-and-accessory-mirroring into main (pull_request) Failing after 0s
wordpress-plugin-release-distribute.yml / Merge pull request 'feat(oci): index annotations and cosign accessory mirroring' (#53) from feat/oci-annotations-and-accessory-mirroring into main (pull_request) Failing after 0s
wordpress-plugin-release.yml / Merge pull request 'feat(oci): index annotations and cosign accessory mirroring' (#53) from feat/oci-annotations-and-accessory-mirroring into main (pull_request) Failing after 0s
c1dc50d306
Two gaps found publishing webgrip/ploeg to GHCR, both invisible until someone
looked at the package page.

## Index annotations

GHCR links a package to a repository from the OCI annotation
org.opencontainers.image.source, and reads it from the MANIFEST — not from the
image config. A Dockerfile LABEL lands in the per-platform config, so ploeg's
multi-arch image published with the label correctly set and still linked
nothing, while its Helm chart (a single manifest, annotated by Helm) linked
fine. The label was right and in the wrong place.

The composite gains an `annotations` input taking buildx's own `LEVEL:key=value`
form, parsed exactly like docker-build-args — one argv element per line, blanks
and comment lines dropped, because `--provenance=mode=max` would otherwise write
a caller's prose into the artifact's own metadata.

Annotating at BUILD time rather than at copy time was deliberate, and measured:
a plain `buildx imagetools create` PRESERVES index annotations and leaves the
index digest byte-identical (verified against two local registries, 2026-08-26).
So one annotation at build reaches Harbor, Forgejo and GHCR, and the
identical-digest property the mirroring design rests on is untouched.

## Accessory mirroring

`imagetools create` copies the manifest list. BuildKit's SLSA provenance and
SBOM ride along inside the index for free — GHCR already has both — but cosign's
signature and attestations do not: they live as separate sha256-<digest>.sig
and .att tags, and were being left behind, publishing an unsigned mirror of a
signed image.

`copy-accessories` adds a `cosign copy --only=sig,att,sbom` after the image
copy, so the image is not pushed twice. Verified that a copied signature still
verifies at the destination — cosign checks the digest, and the copy survives a
change of registry host and repository path. Recorded on the input: the payload
keeps the ORIGIN reference, so a policy pinning docker-reference must verify
against the source registry, not the mirror.

Fail-soft on a missing signature: an unsigned source is legitimate (the signing
job is independent and may not have run for the tag), and failing the mirror
over it would make the image unavailable rather than merely unverifiable.

Both inputs default to off, so every existing caller is unaffected.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
webgrip-ci referenced this pull request from a commit 2026-08-26 09:04:55 +00:00
Sign in to join this conversation.
No reviewers
No labels
pull-request
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
webgrip/workflows!53
No description provided.