Sign the automated version bump commit
main requires signed commits, so the pull request the bump job opens after a release cannot be merged while the commit in it is unsigned. The key the server signs merge commits with is not reachable from a runner, so the job signs with a dedicated release-bot SSH key that the instance trusts through TRUSTED_SSH_KEYS — no bot account, because an account key is only consulted after the web Verify flow and that flow has no API. Inert until the key is trusted and RELEASE_BOT_SIGNING_KEY is set, and loudly so: the step checks the secret and ssh-keygen before it starts, runs the key through ssh-keygen -y so a truncated or re-wrapped one is caught as itself rather than as "gpg failed to sign the data", and the commit is re-read for a gpgsig header before it is pushed. Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
@@ -279,6 +279,42 @@ image just published. The job is the only one that runs in a container (`node:22
|
||||
npm), and a job in a container is handed `sh`, not bash — hence the explicit
|
||||
`shell: bash`, without which `set -o pipefail` fails the first line of the first step.
|
||||
|
||||
### Signing the bump commit
|
||||
|
||||
main requires signed commits, and a pull request carrying an unsigned one cannot be
|
||||
merged — so the bump job signs the commit it makes. Not with the key the server signs
|
||||
merge commits with: that one lives on the server and no runner can reach it. It uses a
|
||||
dedicated release-bot SSH key instead, which also means it can be rotated on its own if
|
||||
the secret ever leaks.
|
||||
|
||||
There is deliberately no release-bot account. A key attached to an account is only
|
||||
consulted for signature checking once it has been through the web *Verify* flow, and
|
||||
that flow has no API, so a bot account would need an interactive login to be worth
|
||||
anything. Listing the key under `[repository.signing] TRUSTED_SSH_KEYS` instead makes
|
||||
the signature verify with no account lookup at all, which is all the protected branch
|
||||
asks for. `[email protected]` is therefore a label and not an identity, and
|
||||
the signature is attributed to the instance's `SIGNING_NAME`/`SIGNING_EMAIL` rather
|
||||
than to it. The other side of trusting a key instance-wide: a commit signed with it
|
||||
verifies in *every* repository on that instance, because the trust is in the key and not
|
||||
in a user whose permissions you could scope.
|
||||
|
||||
Set up once per instance, and again only on rotation:
|
||||
|
||||
1. Generate a passphrase-less key — it has to be usable unattended:
|
||||
`ssh-keygen -t ed25519 -C release-bot -f release-bot -N ''`.
|
||||
2. Add the public half to `TRUSTED_SSH_KEYS` in the server config and restart.
|
||||
3. Store the private half as the `RELEASE_BOT_SIGNING_KEY` Actions secret — the whole
|
||||
file verbatim, `-----BEGIN OPENSSH PRIVATE KEY-----` and footer included, not the
|
||||
`.pub` and not a GPG export. An organisation secret covers every repository at once.
|
||||
Delete both local files afterwards.
|
||||
|
||||
Until both are in place the bump job fails, loudly and on purpose: it checks the secret
|
||||
is set and that `ssh-keygen` exists before it starts, feeds the key through
|
||||
`ssh-keygen -y` so a truncated or re-wrapped one is caught as itself rather than as
|
||||
"gpg failed to sign the data", and re-reads the commit for a `gpgsig` header before
|
||||
pushing. Nothing else in the pipeline signs anything — release tags are made by hand,
|
||||
and the merge commit is signed by the server.
|
||||
|
||||
Two things any deployment has to get right, both learned the hard way:
|
||||
|
||||
- **Chromium needs more than the default 64Mi `/dev/shm`** or it crashes. Mount a
|
||||
|
||||
Reference in New Issue
Block a user