- nfpm.yaml: type:config → config_noreplace (RPM noreplace semantics) - nfpm.yaml: type:ghost → type:dir for /var/lib/fenris (deb compatibility) - postinst.sh/rpm/post.sh: fix timer restart — capture running unit before daemon-reload so the diff actually detects changes - README: fix Python floor to ≥3.10 (was ≥3.9, inconsistent with Makefile) - signing-key-ceremony.md: fix stale claim about nfpm signing RPMs (actual path is post-build rpmsign) - tests/conftest.py: extract shared _get_version() and _read() helpers - tests: wire up to shared conftest helpers - release.yml: extract VERSION once via GITHUB_OUTPUT step Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
7.0 KiB
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
# 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:
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:
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:
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:
make release # builds, signs RPM, clearsigns SHA256SUMS, prints upload steps
Under the hood:
rpmsign --addsignsigns the RPM payload with the packaging key (invoked bymake sign-rpm).sha256sumgenerates the checksum manifest.gpg --clearsignproducesSHA256SUMS.ascwith the packaging key.
Step 3: Delete the private key
Immediately after signing:
gpg --delete-secret-keys packaging@bongbetic.com
gpg --delete-keys packaging@bongbetic.com
Verify the key is gone:
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:
- Generate a new key using the same procedure as first release.
- Publish the new public key alongside the old one in-repo:
packaging/keys/fenris-packaging.asc # new key (primary) packaging/keys/fenris-packaging-previous.asc # old key (one cycle) - Sign the next RPM with the new key.
- Update
fenris.repoto list bothgpgkeyURLs (dnf accepts multiple):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 - Drop the old key from the repo after one release cycle. Delete
fenris-packaging-previous.ascand revertgpgkeyto 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:
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
# Create a throwaway package name to avoid polluting fenris metadata
PROBE_NAME="fenris-regtest"
PROBE_VERSION="0.0.1"
Publish
# 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)
# 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)
# 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
# Download from release assets or local build
gpg --verify SHA256SUMS.asc SHA256SUMS
sha256sum -c SHA256SUMS
Cleanup
# 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