216 lines
7.1 KiB
Markdown
216 lines
7.1 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).
|
|
|
|
## 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.
|
|
XBPS publication is separate and requires its signing key and host acceptance.
|
|
|
|
### Step 3: Verify runner cleanup
|
|
|
|
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.
|
|
|
|
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
|
|
```
|