Rotating CI signing identities without breaking verification Jump to heading
CI signing keys should rotate often — quarterly is a reasonable default — because unlike a person’s key, nobody notices if one leaks. In practice they rotate rarely, because the last rotation broke something: a release job that was mid-run when the secret changed, a deploy gate whose pinned trust file still had only the old key, an audit that went red for every commit signed before the switch. Each of those is a sequencing problem, and each is avoided by the same pattern: add the new key everywhere before using it, switch, and only then close the old key’s window. This page turns that pattern into a repeatable, mostly automated procedure, within signed commits in CI pipelines.
When to use this approach Jump to heading
- A pipeline signs commits or tags with a stored key, as in signing bot commits made by CI workflows or signing release tags from a pipeline.
- The key has been in use for months, or since someone with access to it left.
- Several verifiers consume the trust file: server hooks, CI gates, deploy tooling, users.
- You want rotation to be routine rather than an incident response. For a suspected compromise, follow rotating a compromised commit-signing key instead, which closes the old window immediately.
Step 1 — Generate the new key and add it to trust first Jump to heading
The new key must be trusted by every verifier before anything signs with it. Add it with a valid-after date of today and leave the old key’s line untouched.
new=release-bot-$(date +%Y%m)
ssh-keygen -t ed25519 -N '' -C "signing:[email protected]:$(date +%Y-%m)" -f "$new"
printf '[email protected] namespaces="git",valid-after="%s" %s\n' \
"$(date +%Y%m%d)" "$(cat "$new.pub")" >> trust/allowed_signers
git commit -am "Trust new release-bot signing key $(date +%Y-%m)" Step 2 — Wait until every verifier has the new trust file Jump to heading
Distribution is the step rotations skip. Server hooks may copy the trust file on a schedule, deploy tooling may pin a revision, and users may have an old copy. Check each consumer before switching.
# Each verifier should report the new key's fingerprint
new_fp=$(ssh-keygen -lf "$new.pub" | awk '{print $2}')
for host in git-primary git-replica; do
ssh "$host" "grep -c '$(cut -d' ' -f2 "$new.pub")' /etc/git/allowed_signers" | sed "s/^/$host: /"
done
grep -rn "SIGNERS_REV" .github/workflows/ # pinned revisions that need bumping If a verifier pins the trust file by revision, bump the pin now and let that change merge before the switch.
Step 3 — Switch the secret, not the workflow Jump to heading
Replace the secret’s value in place. The workflow keeps referring to the same secret name, so no pipeline change is needed and in-flight runs are unaffected — a job that already loaded the old key finishes with it, which is fine because the old key is still trusted.
gh secret set RELEASE_BOT_KEY --env release-signing < "$new"
shred -u "$new"
gh secret list --env release-signing --json name,updatedAt # Verification: the next signed object uses the new key's fingerprint
git fetch --tags && git log -1 --format='%GF' "$(git describe --tags --abbrev=0)" Step 4 — Close the old key’s window after the overlap Jump to heading
Once the new key has signed successfully and every verifier has had a week to catch up, add a valid-before date to the old key’s line. Do not delete the line: historical commits and tags signed with it must keep verifying.
# Close the old key as of today (edit the line for the previous key)
sed -i "/[email protected]/ { /$(date -d '-3 months' +%Y%m)/ s/namespaces=\"git\"/namespaces=\"git\",valid-before=\"$(date +%Y%m%d)\"/ }" trust/allowed_signers
git diff trust/allowed_signers
git commit -am "Close previous release-bot signing key" The sed expression above depends on how you label lines; reviewing the diff before committing is the real safeguard.
# Verification: an old tag still verifies, and a test signature with the old key is rejected
git verify-tag v2.3.0 && echo "history ok" Step 5 — Automate the schedule Jump to heading
A rotation that needs a person to remember it will not happen quarterly. Script steps one and four as two scheduled workflows that open pull requests, with step three run by an approved environment job in between.
Validation checklist Jump to heading
Frequently Asked Questions Jump to heading
How long should the overlap be? Jump to heading
Long enough for the slowest verifier to pick up the new trust file — usually a few days — plus the longest job that might start with the old key. A week covers almost every setup. During a compromise there is no overlap: close the old key at once and accept a few failed runs.
Can I rotate keyless signing identities? Jump to heading
There is nothing to rotate: each signature uses a certificate that lives for minutes. What you manage instead is the identity policy verifiers check, such as the workflow path and repository, which changes only when the workflow moves.
What if a verifier was missed and starts rejecting new tags? Jump to heading
Add the new key to that verifier’s trust file; nothing else needs to change, because the old key is still valid during the overlap. That is the reason to close the old window last.
Related Jump to heading
- Signed Commits in CI Pipelines — the parent topic.
- Setting Expiry and Renewal Reminders for Signing Keys — the reminder layer for keys that are rotated by hand.
- Protecting CI Signing Keys with Environment Secrets — where the rotated secret lives.
- Building an Allowed Signers File from Team Membership — the file whose lines this procedure edits.