Skip to content

feat(release): cosign-sign connector + ATOR sidecar images via keyless OIDC#66

Merged
ALLiDoizCode merged 2 commits into
mainfrom
feat/cosign-keyless-signing
May 8, 2026
Merged

feat(release): cosign-sign connector + ATOR sidecar images via keyless OIDC#66
ALLiDoizCode merged 2 commits into
mainfrom
feat/cosign-keyless-signing

Conversation

@ALLiDoizCode
Copy link
Copy Markdown
Collaborator

Summary

Story 44.3 / Epic 44 — supply-chain signing for all published images.

Three signing targets:

  • build-and-publish.yml docker job → ghcr.io/toon-protocol/connector (line 179, id: push-connector)
  • build-and-publish.yml sidecar job → ghcr.io/toon-protocol/ator-sidecar (line 261, id: push-sidecar)
  • release.yml docker-release job → ghcr.io/toon-protocol/connector (line 131, id: push-release)

What was added to each signing job:

  • id-token: write permission (required for GHA OIDC token endpoint — without it, Fulcio returns 403)
  • sigstore/cosign-installer@v3 step (installs cosign v2.x; @v3 matches the project's existing action-major convention)
  • id: <step-id> on each docker/build-push-action push step (enables steps.<id>.outputs.digest)
  • cosign sign --yes "${IMAGE}@${DIGEST}" step immediately after each push, signing the index digest not a tag

Why id-token: write: the GHA OIDC token is only available when id-token: write is declared at the job level. Added alongside the existing contents: read + packages: write — not replacing them, and NOT at workflow level (that would over-grant to jobs that don't need it).

Why digest, not tag: steps.<id>.outputs.digest returns the multi-arch manifest index digest. Signing by digest is content-addressed and immune to future tag-pointer mutation. See CONNECTOR_RELEASE_CONTRACT.md § Historical tag corruption for the prior incident that motivates this choice.

The if: steps.tag.outputs.tag != '' guard on the release.yml sign step mirrors all surrounding steps in that job — without it, the step runs on commits where semantic-release decides not to bump (no tag → no digest → cosign signs an empty-string ref).

feat: commit type: semantic-release maps this to a MINOR bump, so merge will cut a new vX.Y.Z tag. Both release.yml (fires on merge-commit-to-main) and build-and-publish.yml (fires on the tag push) will run with signing enabled. Verification (AC #6) will happen against that release's tag automatically.

Verification (AC #6 — to be run post-merge)

TAG="v<X.Y.Z>"  # the new semver tag after merge

# Connector — positive verify (must succeed)
DIGEST=$(docker buildx imagetools inspect ghcr.io/toon-protocol/connector:${TAG} \
  --format '{{ json .Manifest }}' | jq -r '.digest')
cosign verify "ghcr.io/toon-protocol/connector@${DIGEST}" \
  --certificate-identity-regexp 'https://github.com/toon-protocol/connector/\.github/workflows/(build-and-publish|release)\.yml@.*' \
  --certificate-oidc-issuer 'https://token.actions.githubusercontent.com'

# Connector — negative verify (must FAIL — proves cert-identity gating works)
cosign verify "ghcr.io/toon-protocol/connector@${DIGEST}" \
  --certificate-identity-regexp 'https://github.com/MALICIOUS-ACTOR/.*' \
  --certificate-oidc-issuer 'https://token.actions.githubusercontent.com' || echo "✓ negative verify correctly failed"

# ATOR sidecar (narrower regex — only build-and-publish.yml signs sidecar)
SIDECAR_DIGEST=$(docker buildx imagetools inspect ghcr.io/toon-protocol/ator-sidecar:${TAG} \
  --format '{{ json .Manifest }}' | jq -r '.digest')
cosign verify "ghcr.io/toon-protocol/ator-sidecar@${SIDECAR_DIGEST}" \
  --certificate-identity-regexp 'https://github.com/toon-protocol/connector/\.github/workflows/build-and-publish\.yml@.*' \
  --certificate-oidc-issuer 'https://token.actions.githubusercontent.com'

Story 44.3 is NOT closed until all four checks pass.

Files changed

  • .github/workflows/build-and-publish.ymldocker + sidecar jobs: id-token: write, cosign-installer, id: on push steps, sign steps
  • .github/workflows/release.ymldocker-release job: id-token: write, cosign-installer, id: on push step, sign step
  • CONNECTOR_RELEASE_CONTRACT.md — new ## Supply-chain signing section; updated lines 47–49 and line 120 reference
  • CHANGELOG.md[Unreleased] ### Build entry

Developer and others added 2 commits May 8, 2026 10:38
…s OIDC

Adds sigstore/cosign-installer@v3 + cosign sign --yes to all three
docker/build-push-action publish steps (build-and-publish.yml:docker,
build-and-publish.yml:sidecar, release.yml:docker-release). Signs by
digest (steps.<id>.outputs.digest) using GitHub's OIDC token — no static
keys, no secrets beyond GITHUB_TOKEN. Adds id-token: write permission at
the job level for each of the three signing jobs.

Documents the supply-chain guarantee in CONNECTOR_RELEASE_CONTRACT.md
(new ## Supply-chain signing section with cosign verify command + notes
on index-digest coverage and Rekor transparency log integration).

Closes Story 44.3 / Epic 44.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@ALLiDoizCode ALLiDoizCode merged commit 291a2c7 into main May 8, 2026
17 checks passed
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 8, 2026

🎉 This PR is included in version 3.6.0 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant