fix(packaging/rpm): a release must not publish unsigned RPMs into a gpgcheck=1 repo
Signing was already live and the docs were half right about it. `RPM_GPG_PRIVATE_KEY` is an ORG-level secret on unom, so it is invisible in this repo's Actions secrets — which reads exactly like "never set up", and both the rpm.yml step name and sign-rpms.sh's header still said "dormant". Checked it on the wire instead: a published punktfunk-web RPM carries an OpenPGP V4 EdDSA header signature from af245c506f4e4763, the same key committed at packaging/rpm/RPM-GPG-KEY-punktfunk. The real gap was the failure mode. README.md hands users a repo file with gpgcheck=1, but sign-rpms.sh exits 0 when the key is missing — so an org secret that got rotated, renamed, or not inherited would publish an unsigned release into a repo that rejects unsigned packages, and every user's `dnf upgrade` would break with us none the wiser. On refs/tags/v* that is now a build failure. Other builds still fall through unsigned so forks and local builds keep working. Docs corrected to match: the org-level location (with a wire-level check that doesn't depend on where the secret lives), the fail-closed rule, and a note that `rpmkeys --checksig` reporting NOKEY still means signed. Guard tested locally: exit 1 on refs/tags/v0.21.0, exit 0 on refs/heads/main and on an unset ref. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -110,7 +110,9 @@ jobs:
|
||||
# Recommends both). Both need bun (ensured in Prep).
|
||||
run: PF_VERSION="$PF_VERSION" PF_RELEASE="$PF_RELEASE" PF_WITH_WEB=1 PF_WITH_SCRIPTING=1 bash packaging/rpm/build-rpm.sh
|
||||
|
||||
- name: Sign RPMs (dormant until RPM_GPG_PRIVATE_KEY is set — see packaging/rpm/README.md)
|
||||
# Signs with packages@unom.io (org secret) and self-verifies before publish. On a v* tag a
|
||||
# missing key FAILS the build rather than publishing unsigned RPMs into a gpgcheck=1 repo.
|
||||
- name: Sign RPMs
|
||||
env:
|
||||
RPM_GPG_PRIVATE_KEY: ${{ secrets.RPM_GPG_PRIVATE_KEY }}
|
||||
RPM_GPG_PASSPHRASE: ${{ secrets.RPM_GPG_PASSPHRASE }}
|
||||
|
||||
+13
-2
@@ -52,6 +52,16 @@ The public key is served from the registry (the `gpgkey=` URL above) and committ
|
||||
`packaging/rpm/RPM-GPG-KEY-punktfunk`. (This is a GPG/OpenPGP key — a `step-ca`/X.509 cert can't
|
||||
sign RPMs; step-ca is only for registry/console TLS.)
|
||||
|
||||
> `RPM_GPG_PRIVATE_KEY` is an **org-level** secret on `unom`, not a repo secret — it will not show
|
||||
> up under this repository's Actions secrets. Verify it end to end instead of by its absence there:
|
||||
> `curl -O <repo-url>/package/punktfunk-web/<ver>/x86_64/…rpm && rpm -qp --qf '%{RSAHEADER:pgpsig}\n'`
|
||||
> (or `rpmkeys --checksig`, which reports `NOKEY` until you import the public key — `NOKEY` still
|
||||
> means *signed*, just by a key that box doesn't have yet).
|
||||
|
||||
On a `v*` tag build, a missing key **fails** the build: `sign-rpms.sh` will not publish unsigned
|
||||
RPMs into a repo whose own instructions say `gpgcheck=1`, because every user's `dnf upgrade` would
|
||||
then break on them. Non-release builds still fall through unsigned so forks and local builds work.
|
||||
|
||||
How it was set up (and how to rotate the key):
|
||||
|
||||
```sh
|
||||
@@ -68,8 +78,9 @@ EOF
|
||||
gpg --armor --export-secret-keys packages@unom.io # -> the RPM_GPG_PRIVATE_KEY CI secret
|
||||
gpg --armor --export packages@unom.io > packaging/rpm/RPM-GPG-KEY-punktfunk # public half
|
||||
|
||||
# 2. Add the armored PRIVATE key as the RPM_GPG_PRIVATE_KEY Gitea Actions secret. Commit the public
|
||||
# half and publish it to the registry so the gpgkey= URL resolves:
|
||||
# 2. Add the armored PRIVATE key as the RPM_GPG_PRIVATE_KEY Gitea Actions secret, at the ORG level
|
||||
# (git.unom.io/org/unom/settings/actions/secrets) so every repo's workflows inherit it. Commit
|
||||
# the public half and publish it to the registry so the gpgkey= URL resolves:
|
||||
curl --user "<user>:<write:package-PAT>" --upload-file packaging/rpm/RPM-GPG-KEY-punktfunk \
|
||||
https://git.unom.io/api/packages/unom/generic/punktfunk-keys/1/RPM-GPG-KEY-punktfunk
|
||||
```
|
||||
|
||||
@@ -1,9 +1,16 @@
|
||||
#!/usr/bin/env bash
|
||||
# Detached-GPG-sign the built dist/*.rpm so the Gitea RPM registry can be served with gpgcheck=1.
|
||||
#
|
||||
# DORMANT by default: if RPM_GPG_PRIVATE_KEY is unset this exits 0 and leaves the RPMs unsigned —
|
||||
# exactly today's behaviour — so it is SAFE to ship before a key exists. The signing only activates
|
||||
# once you add the key as a CI secret (see packaging/rpm/README.md "Enabling per-package signing").
|
||||
# ACTIVE: RPM_GPG_PRIVATE_KEY is set as an ORG-level secret on `unom` (packages@unom.io,
|
||||
# AF245C506F4E4763 — the public half is committed at packaging/rpm/RPM-GPG-KEY-punktfunk and served
|
||||
# from the registry). Every published RPM carries its EdDSA header signature; the repo file in
|
||||
# README.md tells users gpgcheck=1, which only works because of that.
|
||||
#
|
||||
# Without the key this exits 0 and leaves the RPMs unsigned, so a fork or a local build still works
|
||||
# — but NOT on a release. On refs/tags/v* a missing key is a hard failure: an org secret that got
|
||||
# rotated, renamed, or not inherited would otherwise publish an unsigned release into a repo whose
|
||||
# own instructions say gpgcheck=1, and every user's `dnf upgrade` would break on it. Better to fail
|
||||
# the build than to find out from users. (Same fail-closed rule as the Windows pack scripts.)
|
||||
#
|
||||
# Requires a DEDICATED, PASSPHRASE-LESS signing key (the one the runbook generates with
|
||||
# %no-protection), distinct from the Gitea instance's repo-metadata key — rpm's default signer
|
||||
@@ -13,7 +20,13 @@
|
||||
set -euo pipefail
|
||||
|
||||
if [ -z "${RPM_GPG_PRIVATE_KEY:-}" ]; then
|
||||
echo "RPM_GPG_PRIVATE_KEY unset — leaving dist/*.rpm UNSIGNED (registry stays gpgcheck=0)."
|
||||
case "${GITHUB_REF:-}" in
|
||||
refs/tags/v*)
|
||||
echo "RPM_GPG_PRIVATE_KEY unset on a release build (${GITHUB_REF}) — refusing to publish" >&2
|
||||
echo "unsigned RPMs into a gpgcheck=1 repo. Restore the org secret (packaging/rpm/README.md)." >&2
|
||||
exit 1 ;;
|
||||
esac
|
||||
echo "RPM_GPG_PRIVATE_KEY unset — leaving dist/*.rpm UNSIGNED (non-release build)."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
|
||||
Reference in New Issue
Block a user