From a5b84f7566f33985eb9d93b8d8854ac15c344ec7 Mon Sep 17 00:00:00 2001 From: xavierk Date: Tue, 29 Sep 2026 03:57:24 +0530 Subject: [PATCH] docs: align signing ceremony with Gitea workflow --- docs/install/signing-key-ceremony.md | 69 +++++++++++----------------- docs/spec/release-packaging.md | 4 +- 2 files changed, 29 insertions(+), 44 deletions(-) diff --git a/docs/install/signing-key-ceremony.md b/docs/install/signing-key-ceremony.md index 59c5dd0..42ea669 100644 --- a/docs/install/signing-key-ceremony.md +++ b/docs/install/signing-key-ceremony.md @@ -12,7 +12,7 @@ and destruction. | UID | `Fenris Packaging ` | | Expiry | 2 years from creation | | Hierarchy | Single key — no master/subkey split (single maintainer, manual builds) | -| Private key storage | Password manager only | +| Private key storage | Gitea repository Actions secret `GPG_PRIVATE_KEY` | | Public key storage | `packaging/keys/fenris-packaging.asc` in-repo, release notes, docs | | Keyservers | Never — TOFU-over-TLS via raw URL | @@ -37,18 +37,22 @@ gpg --armor --export packaging@bongbetic.com > packaging/keys/fenris-packaging.a gpg --fingerprint packaging@bongbetic.com ``` -Save the **private key** to the password manager immediately: +Provision the **private key** as the repository Actions secret `GPG_PRIVATE_KEY`. +Run the export on the trusted key-generation machine, then enter its output in +the Gitea repository's Actions secret settings. Do not save it in the checkout, +logs, or a runner directory. The release workflow checks its fingerprint +against the committed public key before signing. ```bash gpg --armor --export-secret-keys packaging@bongbetic.com ``` -Then **delete the private key from the local keyring** — it must never persist -on any build host: +After provisioning the secret, delete the private key from the key-generation +keyring: ```bash -gpg --delete-secret-keys packaging@bongbetic.com -gpg --delete-keys packaging@bongbetic.com +gpg --batch --yes --delete-secret-keys packaging@bongbetic.com +gpg --batch --yes --delete-keys packaging@bongbetic.com ``` The committed `fenris-packaging.asc` must contain the real public key (replace @@ -56,52 +60,33 @@ the placeholder comments). ## Per-release signing flow -Each release performs: **import → sign → delete**. The private key is never -stored on disk longer than the release takes. +Each tagged release performs: **import → verify → sign → delete** on the +repository-scoped Gitea Actions runner. The Gitea secret remains configured; +the runner's keyring copy is removed after the job. -### Step 1: Import the private key +### Step 1: Push the release tag -Retrieve the private key from the password manager and import it: +After updating the version and dated changelog section, push the matching tag: ```bash -gpg --import /tmp/packaging-key-private.asc -rm /f /tmp/packaging-key-private.asc # Shred if possible +git push origin v ``` -### Step 2: Build and sign packages +### Step 2: Build, verify, and sign packages -The Makefile target `make release` handles signing automatically when the -key is in the keyring: +The release workflow imports `GPG_PRIVATE_KEY`, checks it against +`packaging/keys/fenris-packaging.asc`, builds packages, signs the RPM and +clearsigned checksum manifest, validates both, and publishes the release. +XBPS publication is separate and requires its signing key and host acceptance. -```bash -make release # builds, signs RPM, clearsigns SHA256SUMS, prints upload steps -``` +### Step 3: Verify runner cleanup -Under the hood: +The workflow's `always()` cleanup removes the imported key from the runner's +keyring, including after a failed job. Confirm no packaging secret key remains +on the runner after the release job. -1. `rpmsign --addsign` signs the RPM payload with the packaging key - (invoked by `make sign-rpm`). -2. `sha256sum` generates the checksum manifest. -3. `gpg --clearsign` produces `SHA256SUMS.asc` with the packaging key. - -### Step 3: Delete the private key - -Immediately after signing: - -```bash -gpg --delete-secret-keys packaging@bongbetic.com -gpg --delete-keys packaging@bongbetic.com -``` - -Verify the key is gone: - -```bash -gpg --list-keys packaging@bongbetic.com -# Should produce: gpg: keyblock resource ...: No such file or directory -``` - -The entire import → sign → delete cycle should take minutes. The private key -must never be left in any keyring between releases. +The Gitea Actions secret remains the approved signing source. Do not copy it to +the runner or repository outside the workflow. ## Key rotation (outline) diff --git a/docs/spec/release-packaging.md b/docs/spec/release-packaging.md index 6289287..89bc8f4 100644 --- a/docs/spec/release-packaging.md +++ b/docs/spec/release-packaging.md @@ -44,7 +44,7 @@ - **RPM payload: signed.** rpmsign with the dedicated packaging key, invoked by `make sign-rpm` after the package is built. This is required, not optional: it is the only working dnf-native verification path. - **deb: unsigned.** apt never verifies payload signatures; trust = instance-signed `InRelease` (signed-by keyring) + TLS + Acquire-By-Hash. Manual-download integrity is covered by SHA256SUMS. - **SHA256SUMS: clearsigned** with the packaging key — the trust anchor for manually downloaded release assets, independent of TLS. -- **Packaging key:** single dedicated key, RSA 3072, UID `Fenris Packaging `, 2-year expiry, no master/subkey hierarchy (single maintainer, manual builds). Private key lives in the password manager only; each release does import → sign → delete — nothing permanent on any build host. The full ceremony is documented in `docs/install/signing-key-ceremony.md`. +- **Packaging key:** single dedicated key, RSA 3072, UID `Fenris Packaging `, 2-year expiry, no master/subkey hierarchy. The private key is stored as the repository Actions secret `GPG_PRIVATE_KEY`. The release workflow imports it on the self-hosted runner, verifies it against the in-repo public key, signs the RPM and SHA256SUMS, then deletes the runner's keyring copy in an `always()` cleanup step. The full ceremony is documented in `docs/install/signing-key-ceremony.md`. - **Public key publication:** in-repo `packaging/keys/fenris-packaging.asc` (raw URL doubles as the `.repo` gpgkey target), release notes, docs page. No keyservers — TOFU-over-TLS. - **Rotation (outline):** new key published alongside old; rpm signed with the new key; `fenris.repo` gpgkey lists both URLs (dnf accepts multiple); old key dropped after one release cycle. Procedure details stay in map fog. @@ -53,7 +53,7 @@ - **A Release is:** a version tag, its packages in the channel, a Gitea release entry with notes, and a clearsigned SHA256SUMS — all together. **Bare tags are forbidden** (tag without packages + release entry is not a Release). - **Cadence: on-demand.** Tag when user-visible changes or fixes accumulate; no calendar, no empty releases, no frequency SLA, no RC ceremony — fixes ship as a revision bump of the current version. - **Versioning: plain semver.** Major = breaking CLI/config/unit change; store schema changes ride the natural bump (the forward-only refusal handles old-reader/new-store). -- **Promotion flow:** bump `pyproject.toml` and the matching dated `CHANGELOG.md` section, then push tag `v`. The repository-scoped Gitea Actions workflow builds and validates the deb, rpm, checksums, and release entry. XBPS publication remains a manual dispatch option after host acceptance. `make release` remains the local package/sign/checksum fallback. The ceremony is documented in `docs/install/signing-key-ceremony.md`. +- **Promotion flow:** bump `pyproject.toml` and the matching dated `CHANGELOG.md` section, then push tag `v`. The repository-scoped Gitea Actions workflow builds and validates the deb, rpm, checksums, and release entry. XBPS publication remains a manual dispatch option after host acceptance. `make release` is for local artifact preparation and does not replace the tag workflow as the supported publication path. The ceremony is documented in `docs/install/signing-key-ceremony.md`. - **Rollback:** installing an older package over a newer store is **unsupported** — the store's forward-only version refusal fails it by design. Documented rollback = restore the observation-store snapshot, then install the old Release. No automatic downgrade machinery exists or will be built. - **CI:** a repository-scoped self-hosted runner is registered and online (checked 2026-09-29). `.gitea/workflows/release.yml` is the tag-triggered release path; maintainers must confirm runner availability and required Gitea secrets before tagging. The workflow publishes deb/rpm packages and release assets; Void publication is withheld unless the signed XBPS host-release step is explicitly requested.