ci(release): tag-driven release pipeline with cosign + SLSA (#16) #74
No reviewers
Labels
No labels
area:auth
area:ci
area:db
area:infra
area:native
area:pwa
area:service
epic
feature
foundation
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
james/carol!74
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "16-release-pipeline"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Closes #16.
Summary
Pushing a
vX.Y.Ztag now fires.forgejo/workflows/release.yml, which:vX.Y.Z) vs pre-release (vX.Y.Z-rc.1,-beta.2).forge.wynning.tech/james/caroltaggedvX.Y.Z, plus:latestonly for stable releases. Stamps source commit / version / repo URL as OCI image labels.cosign signby digest with a key-pair (private key inCOSIGN_PRIVATE_KEYForgejo secret; public keycosign.publives onmain).slsa-github-generatorequivalent for Forgejo) and **cosign attest**s it.git-cliffparsing Conventional Commits, appends the verification command pinned to this build's digest, creates the Forgejo release page via the API.Notable supply-chain touches
Dockerfilenow pinsnode:22-slimto the multi-arch manifest list digest (sha256:e21f...b752) via a top-levelARG NODE_IMAGE=consumed by everyFROM. Renovate's existingdockerfile base imagesgroup will bump it on a human-reviewed PR.docker/login-action@5e57cd1… # v3.7.0,sigstore/cosign-installer@d58896d… # v3.9.2.Conventions adopted
feat:/fix:/ci:/ etc.). Pre-#16 commits land in a catch-all "Other" group in release notes — followed by a transition period. Enforcement (commit-msg hook + PR check) tracked as #70.Dockerfile'sRUN npm cidoes not yet inherit the install-script allowlist from #46. Tracked as #69.Files
DockerfileARG NODE_IMAGE; reuse in all threeFROMs.forgejo/workflows/release.ymlcliff.tomldocs/adr/0014-release-pipeline.mddocs/ci.mdCLAUDE.mddocs/adr/README.mdAcceptance criteria
git tag v0.1.0 && git push --tagsproduces a published image and a Forgejo release page with notes. (CI to confirm on first tag push after the cosign keypair is registered.)Dockerfilepins the base image by digest, not by tag.npm ci(lockfile-strict) inside the Dockerfile and fails ifpackage-lock.jsonis out of sync withpackage.json— samenpm ciinvocation as today's PR pipeline.One-time setup before the first tag push
cosign generate-key-pairlocally (sets a password — remember it).git add cosign.pub && git commit -m "build(release): add cosign public key" && git push.COSIGN_PRIVATE_KEY(contents ofcosign.key) andCOSIGN_PASSWORDas Forgejo secrets on this repo (or at user level).shred -u cosign.keylocally.REGISTRY_USERNAME/REGISTRY_TOKENalready exist at user level — no setup needed.Full setup walkthrough lives in
docs/ci.md"Release pipeline → One-time cosign keypair setup".Test plan
pull_request, so it won't run here).python3 -c 'import yaml; yaml.safe_load(open(".forgejo/workflows/release.yml"))'— passes.python3 -c 'import tomllib; tomllib.load(open("cliff.toml","rb"))'— passes.docker build --check .—Check complete, no warnings found.Digest resolves on Docker Hub.git-cliff --config cliff.toml --unreleased— generates expected output; myci(release):commit lands in "CI", legacy merge commits land in "Other".v0.0.0orv0.0.1-rc.0tag, watch the workflow, confirm an image lands on the registry, signature verifies, attestation verifies, Forgejo release page appears.Follow-ups already filed
Dockerfile npm ci(close the asymmetry with #46).A note on the ADR number
The original draft used ADR-0012 but #43 and #67 landed
0012and0013while this PR was in progress. Renamed to ADR-0014 during rebase.Pushing a vX.Y.Z tag triggers .forgejo/workflows/release.yml, which: 1. Builds the service image (Dockerfile, now digest-pinned to the multi-arch node:22-slim manifest list). 2. Pushes to forge.wynning.tech/<owner>/<repo> as vX.Y.Z, and as :latest only for stable releases (no -rc/-beta). 3. Captures the immutable registry digest of what was pushed. 4. cosign-signs the digest using a key-pair (private key in the COSIGN_PRIVATE_KEY Forgejo secret; cosign.pub on main). 5. Hand-rolls a SLSA v1.0 provenance predicate (no slsa-github- generator equivalent for Forgejo) and cosign-attests it. 6. Generates release notes with git-cliff parsing Conventional Commits, appends the verification command pinned to this build's digest, and creates the Forgejo release page via the API. Carol adopts Conventional Commits as a convention going forward; the cliff.toml parser falls back to a catch-all "Other" group for pre-#16 commits and any unconverted future ones. Enforcement (commit-msg hook, PR-time check) is tracked as #70. The Dockerfile RUN npm ci does NOT yet inherit the install-script allowlist from #46 — tracked as #69. Renovate's existing dockerfile- base-images and forgejo-actions groups already cover the new SHA pins and image digest pin. See docs/ci.md "Release pipeline" for the one-time cosign keypair setup, how to cut a release, and how a self-hoster verifies an image. ADR-0014 captures the design rationale and alternatives rejected (keyless cosign via Fulcio, slsa-github-generator, plain git log). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>Semgrep flagged two `${{ }}` interpolations inside `run:` blocks in the new release workflow. The pattern is real — a tag containing `"; rm -rf / #` would land in the shell verbatim before the script ran, because the runner substitutes `${{ }}` as text before invoking bash. Fix: every `${{ github.* }}`, `${{ steps.*.outputs.* }}`, and `${{ secrets.* }}` reference that ends up in a `run:` script is now exported via the step's `env:` block and read as `"$NAME"` inside the script. Workflow-level env values (`REGISTRY`, `IMAGE_NAME`, `GIT_CLIFF_VERSION`, etc.) were already shell-safe and stay as-is. Also adds a header comment explaining the convention so future steps follow the same pattern. Verified locally: `semgrep scan --config .semgrep --config p/javascript --config p/typescript --config p/nodejsscan --config p/owasp-top-ten --severity ERROR .forgejo/workflows/release.yml` reports 0 findings. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>