The optional XBPS publisher signs repository metadata after package signing. Remove the runner key only after publication and release asset upload.
232 lines
8.0 KiB
Markdown
232 lines
8.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 | 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 |
|
|
|
|
## 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
|
|
```
|
|
|
|
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
|
|
```
|
|
|
|
After provisioning the secret, delete the private key from the key-generation
|
|
keyring:
|
|
|
|
```bash
|
|
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
|
|
the placeholder comments).
|
|
|
|
## XBPS signing key
|
|
|
|
XBPS uses a separate RSA 3072 key. Its private half is stored as the Gitea
|
|
repository Actions secret `XBPS_SIGNING_KEY`. The corresponding public key is
|
|
published at
|
|
`https://git.bongbetic.com/xavierk/Fenris-xbps/raw/branch/stable/keys/fenris-xbps-signing.pub`,
|
|
with fingerprint `SHA256:AvPMRlKMikPg75u0iKr8AUkxlfU/Ad4k/S4o2M9W4/w`.
|
|
The secret must match that public key.
|
|
|
|
The release workflow writes the key to `~/.ssh/id_xbps` to sign the XBPS
|
|
package. A requested XBPS publication also uses the key to sign repository
|
|
metadata. A final `always()` cleanup removes the runner copy after publication
|
|
and release asset upload, including when an earlier step fails.
|
|
|
|
## Per-release signing flow
|
|
|
|
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: Push the release tag
|
|
|
|
After updating the version and dated changelog section, push the matching tag:
|
|
|
|
```bash
|
|
git push origin v<version>
|
|
```
|
|
|
|
### Step 2: Build, verify, and sign packages
|
|
|
|
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. The
|
|
workflow imports `XBPS_SIGNING_KEY` separately and signs the XBPS package.
|
|
XBPS publication is optional and also signs repository metadata; it requires
|
|
host acceptance and explicit selection during workflow dispatch.
|
|
|
|
### Step 3: Verify runner cleanup
|
|
|
|
The workflow's `always()` cleanup removes the GPG key from the runner's keyring
|
|
and deletes `~/.ssh/id_xbps`, including after a failed job. Confirm no signing
|
|
key remains on the runner after the release job.
|
|
|
|
The Gitea Actions secret remains the approved signing source. Do not copy it to
|
|
the runner or repository outside the workflow.
|
|
|
|
## 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
|
|
```
|