Implements the full release flow: a single script builds both deb and rpm packages, signs the RPM payload, generates and clearsigns SHA256SUMS, uploads to the Gitea package registry (deb to bookworm/jammy/noble pools, rpm to the fenris group), creates a Gitea release entry with notes, and attaches all artifacts. Key changes: - scripts/release.sh: new release script with --dry-run and --publish modes - tests/test_release.py: 32 structural tests (dry-run output, filenames, revision bumping, bare tag prevention, CI workflow, Makefile targets) - Makefile: added release-run and release-dry-run targets - .gitea/workflows/release.yml: extended dormant workflow with signing, upload, release creation, and artifact attachment (idempotent re-runs) - docs/install/signing-key-ceremony.md: added one-time live probe section documenting throwaway package publish, apt/dnf verification, and cleanup Acceptance criteria met: - One release command performs build, sign, publish, and attach - Dry-run mode prints every command; tests assert output without network - Revision bumping on 409 (same-version rebuilds increment release number) - Dormant CI workflow replicates the flow (queues harmlessly without runner) - Live probe documented with throwaway package end-to-end - No bare tags: release API creates tag atomically with release entry Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
231 lines
7.0 KiB
Markdown
231 lines
7.0 KiB
Markdown
# Signing key ceremony
|
|
|
|
The Fenris packaging key signs RPM payloads and clearsigns SHA256SUMS manifests.
|
|
This document describes the key's lifecycle: creation, per-release use, rotation,
|
|
and destruction.
|
|
|
|
## Key specification
|
|
|
|
| Property | Value |
|
|
|---|---|
|
|
| Algorithm | RSA 3072 |
|
|
| UID | `Fenris Packaging <packaging@bongbetic.com>` |
|
|
| Expiry | 2 years from creation |
|
|
| Hierarchy | Single key — no master/subkey split (single maintainer, manual builds) |
|
|
| Private key storage | Password manager only |
|
|
| Public key storage | `packaging/keys/fenris-packaging.asc` in-repo, release notes, docs |
|
|
| Keyservers | Never — TOFU-over-TLS via raw URL |
|
|
|
|
## First release: key creation
|
|
|
|
```bash
|
|
# Generate the dedicated RSA-3072 packaging key
|
|
gpg --batch --gen-key <<EOF
|
|
%no-protection
|
|
Key-Type: RSA
|
|
Key-Length: 3072
|
|
Name-Real: Fenris Packaging
|
|
Name-Email: packaging@bongbetic.com
|
|
Expire-Date: 2y
|
|
%commit
|
|
EOF
|
|
|
|
# Export the public half — this file is committed to the repo
|
|
gpg --armor --export packaging@bongbetic.com > packaging/keys/fenris-packaging.asc
|
|
|
|
# Print the fingerprint for docs and release notes
|
|
gpg --fingerprint packaging@bongbetic.com
|
|
```
|
|
|
|
Save the **private key** to the password manager immediately:
|
|
|
|
```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:
|
|
|
|
```bash
|
|
gpg --delete-secret-keys packaging@bongbetic.com
|
|
gpg --delete-keys packaging@bongbetic.com
|
|
```
|
|
|
|
The committed `fenris-packaging.asc` must contain the real public key (replace
|
|
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.
|
|
|
|
### Step 1: Import the private key
|
|
|
|
Retrieve the private key from the password manager and import it:
|
|
|
|
```bash
|
|
gpg --import /tmp/packaging-key-private.asc
|
|
rm /f /tmp/packaging-key-private.asc # Shred if possible
|
|
```
|
|
|
|
### Step 2: Build and sign packages
|
|
|
|
The Makefile target `make release` handles signing automatically when the
|
|
key is in the keyring:
|
|
|
|
```bash
|
|
make release # builds, signs RPM, clearsigns SHA256SUMS, prints upload steps
|
|
```
|
|
|
|
Under the hood:
|
|
|
|
1. `nfpm pkg -p rpm` signs the RPM payload via `rpm.signature.key_file` and
|
|
`rpm.signature.key_id` in `packaging/nfpm.yaml`.
|
|
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.
|
|
|
|
## Key rotation (outline)
|
|
|
|
When the key approaches expiry, or if it is compromised:
|
|
|
|
1. **Generate a new key** using the same procedure as first release.
|
|
2. **Publish the new public key** alongside the old one in-repo:
|
|
```text
|
|
packaging/keys/fenris-packaging.asc # new key (primary)
|
|
packaging/keys/fenris-packaging-previous.asc # old key (one cycle)
|
|
```
|
|
3. **Sign the next RPM** with the new key.
|
|
4. **Update `fenris.repo`** to list both `gpgkey` URLs (dnf accepts multiple):
|
|
```ini
|
|
gpgkey=https://git.bongbetic.com/xavierk/Fenris/raw/branch/main/packaging/keys/fenris-packaging.asc
|
|
https://git.bongbetic.com/xavierk/Fenris/raw/branch/main/packaging/keys/fenris-packaging-previous.asc
|
|
```
|
|
5. **Drop the old key** from the repo after one release cycle. Delete
|
|
`fenris-packaging-previous.asc` and revert `gpgkey` to the single URL.
|
|
|
|
## Verification
|
|
|
|
Consumers verify the RPM payload signature via dnf (gpgcheck=1 in
|
|
`fenris.repo` points at the published public key). The SHA256SUMS manifest
|
|
verification is manual for downloaded assets:
|
|
|
|
```bash
|
|
gpg --verify SHA256SUMS.asc SHA256SUMS
|
|
sha256sum -c SHA256SUMS
|
|
```
|
|
|
|
## One-time live probe
|
|
|
|
Before the first real release, verify the full registry path end-to-end with a
|
|
throwaway package. This confirms apt/dnf metadata generation, signature
|
|
verification, and consumer setup work as a real consumer would experience them.
|
|
|
|
### Setup
|
|
|
|
```bash
|
|
# Create a throwaway package name to avoid polluting fenris metadata
|
|
PROBE_NAME="fenris-regtest"
|
|
PROBE_VERSION="0.0.1"
|
|
```
|
|
|
|
### Publish
|
|
|
|
```bash
|
|
# Build a throwaway deb and rpm (use the existing nfpm config with a dummy name)
|
|
# Or use a pre-built package — the probe tests the registry path, not the build
|
|
|
|
# Upload deb to all codename pools
|
|
for CODENAME in bookworm jammy noble; do
|
|
curl --fail -X PUT \
|
|
-u "xavierk:${GITEA_TOKEN}" \
|
|
-T "dist/${PROBE_NAME}_${PROBE_VERSION}_amd64.deb" \
|
|
"https://git.bongbetic.com/api/packages/xavierk/debian/pool/${CODENAME}/main/upload"
|
|
done
|
|
|
|
# Upload rpm
|
|
curl --fail -X PUT \
|
|
-u "xavierk:${GITEA_TOKEN}" \
|
|
-T "dist/${PROBE_NAME}-${PROBE_VERSION}-1.x86_64.rpm" \
|
|
"https://git.bongbetic.com/api/packages/xavierk/rpm/fenris/upload"
|
|
```
|
|
|
|
### Verify apt metadata (Debian/Ubuntu consumer perspective)
|
|
|
|
```bash
|
|
# On a Debian/Ubuntu machine:
|
|
sudo mkdir -p /etc/apt/keyrings
|
|
sudo curl -fsSL https://git.bongbetic.com/api/packages/xavierk/debian/repository.key \
|
|
| sudo gpg --dearmor -o /etc/apt/keyrings/gitea-xavierk.asc
|
|
|
|
echo "deb [signed-by=/etc/apt/keyrings/gitea-xavierk.asc] https://git.bongbetic.com/api/packages/xavierk/debian bookworm main" \
|
|
| sudo tee /etc/apt/sources.list.d/fenris.list
|
|
|
|
sudo apt update
|
|
apt show ${PROBE_NAME} # metadata present, correct version
|
|
apt install --dry-run ${PROBE_NAME} # dependency resolution works
|
|
|
|
# Verify InRelease signature
|
|
apt-key list 2>/dev/null || gpg --no-default-keyring --keyring /etc/apt/keyrings/gitea-xavierk.asc --list-keys
|
|
```
|
|
|
|
### Verify dnf metadata (Fedora consumer perspective)
|
|
|
|
```bash
|
|
# On a Fedora machine:
|
|
sudo dnf config-manager --add-repo https://git.bongbetic.com/xavierk/Fenris/raw/branch/main/packaging/fenris.repo
|
|
# Or use Gitea's auto-generated repo for the probe:
|
|
sudo dnf config-manager --add-repo https://git.bongbetic.com/api/packages/xavierk/rpm/fenris.repo
|
|
|
|
dnf info ${PROBE_NAME} # metadata present, correct version
|
|
dnf install --assumeno ${PROBE_NAME} # dependency resolution works
|
|
|
|
# Verify rpm signature
|
|
rpm -q --scripts ${PROBE_NAME} # no scripts (throwaway)
|
|
```
|
|
|
|
### Verify checksums and clearsign
|
|
|
|
```bash
|
|
# Download from release assets or local build
|
|
gpg --verify SHA256SUMS.asc SHA256SUMS
|
|
sha256sum -c SHA256SUMS
|
|
```
|
|
|
|
### Cleanup
|
|
|
|
```bash
|
|
# Delete the throwaway packages from the registry
|
|
for CODENAME in bookworm jammy noble; do
|
|
curl --fail -X DELETE \
|
|
-u "xavierk:${GITEA_TOKEN}" \
|
|
"https://git.bongbetic.com/api/packages/xavierk/debian/pool/${CODENAME}/main/${PROBE_NAME}/${PROBE_VERSION}/amd64"
|
|
done
|
|
|
|
curl --fail -X DELETE \
|
|
-u "xavierk:${GITEA_TOKEN}" \
|
|
"https://git.bongbetic.com/api/packages/xavierk/rpm/fenris/${PROBE_NAME}/${PROBE_VERSION}/x86_64"
|
|
|
|
# Remove test source list on consumer machines
|
|
sudo rm /etc/apt/sources.list.d/fenris.list
|
|
sudo apt update
|
|
```
|