From 1f0dc87658cc0a278e55d830c90294be8a0c2b87 Mon Sep 17 00:00:00 2001 From: enricobuehler Date: Mon, 15 Jun 2026 15:23:57 +0000 Subject: [PATCH] =?UTF-8?q?feat(rpm):=20enable=20gpgcheck=3D1=20=E2=80=94?= =?UTF-8?q?=20packages=20are=20signed=20+=20verified?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The signing rollout is confirmed end to end: the latest published RPM (0.2.0-0.ci1089) carries a header GPG signature (added by `rpm --addsign`) and passed the in-CI `rpmkeys --checksig` self-verify before publishing (a bad/unsigned build fails that gate and never reaches the registry). So flip every .repo snippet from gpgcheck=0 to gpgcheck=1 and add the package-signing public key (served from the generic registry, committed at packaging/rpm/RPM-GPG-KEY-punktfunk) to gpgkey= alongside the Gitea metadata key — dnf/rpm-ostree imports both. Covers rpm/README, packaging/README, the bootc Containerfile, and the docs-site bazzite/fedora-kde install pages; rpm/README's signing section reframed from "dormant/enabling" to active (+ key-rotation notes). Co-Authored-By: Claude Opus 4.8 (1M context) --- docs-site/content/docs/bazzite.md | 9 +++--- docs-site/content/docs/fedora-kde.md | 5 ++-- packaging/README.md | 5 ++-- packaging/bootc/Containerfile | 6 ++-- packaging/rpm/README.md | 44 +++++++++++++--------------- 5 files changed, 34 insertions(+), 35 deletions(-) diff --git a/docs-site/content/docs/bazzite.md b/docs-site/content/docs/bazzite.md index 2e7dbfa..7d6480f 100644 --- a/docs-site/content/docs/bazzite.md +++ b/docs-site/content/docs/bazzite.md @@ -18,18 +18,17 @@ Atomic box layers and updates it with `rpm-ostree`. Add the repo, then layer the console and reboot: ```sh -# Add the repo. Our RPMs are unsigned, but Gitea GPG-signs the repo METADATA — verify that -# (repo_gpgcheck=1) and skip the per-package signature check (gpgcheck=0). The signed metadata -# carries each package's SHA256, so authenticity still holds. (Don't curl Gitea's served -# bazzite.repo verbatim — it sets gpgcheck=1, which fails on unsigned packages.) +# Add the repo. Packages are GPG-signed (gpgcheck=1, the packages@unom.io key) AND the repo +# metadata is Gitea-signed (repo_gpgcheck=1); gpgkey lists both keys so dnf imports each. sudo tee /etc/yum.repos.d/punktfunk.repo >/dev/null <<'REPO' [gitea-unom-bazzite] name=punktfunk (unom, Bazzite) baseurl=https://git.unom.io/api/packages/unom/rpm/bazzite enabled=1 -gpgcheck=0 +gpgcheck=1 repo_gpgcheck=1 gpgkey=https://git.unom.io/api/packages/unom/rpm/repository.key + https://git.unom.io/api/packages/unom/generic/punktfunk-keys/1/RPM-GPG-KEY-punktfunk REPO # Layer the host + the web console, then reboot into the new deployment. diff --git a/docs-site/content/docs/fedora-kde.md b/docs-site/content/docs/fedora-kde.md index 13b5892..c8af769 100644 --- a/docs-site/content/docs/fedora-kde.md +++ b/docs-site/content/docs/fedora-kde.md @@ -64,10 +64,11 @@ sudo tee /etc/yum.repos.d/punktfunk.repo >/dev/null <<'REPO' name=punktfunk baseurl=https://git.unom.io/api/packages/unom/rpm/fedora-44 enabled=1 -# Packages are unsigned; the repo METADATA is Gitea-signed — verify that, skip per-package sig. -gpgcheck=0 +# Packages are GPG-signed (gpgcheck=1) AND the repo metadata is Gitea-signed (repo_gpgcheck=1). +gpgcheck=1 repo_gpgcheck=1 gpgkey=https://git.unom.io/api/packages/unom/rpm/repository.key + https://git.unom.io/api/packages/unom/generic/punktfunk-keys/1/RPM-GPG-KEY-punktfunk REPO sudo dnf install punktfunk diff --git a/packaging/README.md b/packaging/README.md index a1e6a0a..da340c4 100644 --- a/packaging/README.md +++ b/packaging/README.md @@ -40,15 +40,16 @@ push), mirroring the [Debian/apt](debian/README.md) setup. Add one repo file, in updates with `rpm-ostree upgrade` — no COPR account needed. Full guide: [`rpm/README.md`](rpm/README.md). ```sh -# unsigned pkgs + Gitea-signed metadata → repo_gpgcheck=1, gpgcheck=0 (see rpm/README.md) +# GPG-signed pkgs + Gitea-signed metadata → gpgcheck=1, repo_gpgcheck=1 (see rpm/README.md) sudo tee /etc/yum.repos.d/punktfunk.repo >/dev/null <<'REPO' [gitea-unom-bazzite] name=punktfunk (unom, Bazzite) baseurl=https://git.unom.io/api/packages/unom/rpm/bazzite enabled=1 -gpgcheck=0 +gpgcheck=1 repo_gpgcheck=1 gpgkey=https://git.unom.io/api/packages/unom/rpm/repository.key + https://git.unom.io/api/packages/unom/generic/punktfunk-keys/1/RPM-GPG-KEY-punktfunk REPO rpm-ostree install punktfunk && systemctl reboot # updates: rpm-ostree upgrade && systemctl reboot diff --git a/packaging/bootc/Containerfile b/packaging/bootc/Containerfile index 086d4ef..84fe4cf 100644 --- a/packaging/bootc/Containerfile +++ b/packaging/bootc/Containerfile @@ -20,7 +20,7 @@ FROM ${BASE_IMAGE} # packaging/rpm/README). Use it rather than COPR specifically because it carries the # punktfunk-web management console subpackage, which COPR's mock chroot can't build (no `bun`). # Group "bazzite" == the Fedora 43 base; override for a different base. Gitea signs the repo -# metadata (repo_gpgcheck=1); the packages themselves are unsigned (gpgcheck=0). +# metadata (repo_gpgcheck=1) and the packages are GPG-signed (gpgcheck=1, the packages@unom.io key). ARG PUNKTFUNK_RPM_GROUP=bazzite # RPM Fusion nonfree provides the NVENC-capable ffmpeg-libs punktfunk records/encodes with. @@ -35,8 +35,8 @@ RUN printf '%s\n' \ '[gitea-unom-punktfunk]' \ 'name=punktfunk (unom)' \ "baseurl=https://git.unom.io/api/packages/unom/rpm/${PUNKTFUNK_RPM_GROUP}" \ - 'enabled=1' 'gpgcheck=0' 'repo_gpgcheck=1' \ - 'gpgkey=https://git.unom.io/api/packages/unom/rpm/repository.key' \ + 'enabled=1' 'gpgcheck=1' 'repo_gpgcheck=1' \ + 'gpgkey=https://git.unom.io/api/packages/unom/rpm/repository.key https://git.unom.io/api/packages/unom/generic/punktfunk-keys/1/RPM-GPG-KEY-punktfunk' \ > /etc/yum.repos.d/punktfunk.repo \ && dnf5 -y install punktfunk punktfunk-web \ && dnf5 clean all diff --git a/packaging/rpm/README.md b/packaging/rpm/README.md index e157003..4653397 100644 --- a/packaging/rpm/README.md +++ b/packaging/rpm/README.md @@ -17,18 +17,17 @@ paths — same spec (`punktfunk.spec`) — just self-hosted in Gitea instead of ## Install on a Bazzite host (one-time) ```sh -# Add the repo. Our RPMs are unsigned, but Gitea GPG-signs the repo METADATA — so verify that -# (repo_gpgcheck=1) and skip the per-package signature check (gpgcheck=0). The signed metadata -# carries each package's SHA256, so authenticity still holds. (Don't just curl Gitea's served -# bazzite.repo — it sets gpgcheck=1, which fails on unsigned packages.) +# Add the repo. Packages are GPG-signed (gpgcheck=1, the packages@unom.io key) AND the repo +# metadata is Gitea-signed (repo_gpgcheck=1); gpgkey lists both so dnf/rpm-ostree imports each. sudo tee /etc/yum.repos.d/punktfunk.repo >/dev/null <<'REPO' [gitea-unom-bazzite] name=punktfunk (unom, Bazzite) baseurl=https://git.unom.io/api/packages/unom/rpm/bazzite enabled=1 -gpgcheck=0 +gpgcheck=1 repo_gpgcheck=1 gpgkey=https://git.unom.io/api/packages/unom/rpm/repository.key + https://git.unom.io/api/packages/unom/generic/punktfunk-keys/1/RPM-GPG-KEY-punktfunk REPO # Layer the host + the web console (pairing/status), then reboot into the new deployment. @@ -41,18 +40,19 @@ systemctl reboot > If `rpm-ostree` can't complete the metadata GPG check non-interactively, set `repo_gpgcheck=0` > (TLS-only trust to the self-hosted registry). -## Enabling per-package signing (`gpgcheck=1`) +## Per-package signing (`gpgcheck=1`, active) -CI is wired to GPG-sign each RPM (`packaging/rpm/sign-rpms.sh`, run from `rpm.yml`), but it's -**dormant** until you provide a signing key — until then packages publish unsigned and the repo -above uses `gpgcheck=0`. This is a self-hosted registry served over HTTPS with GPG-signed metadata -(`repo_gpgcheck=1`), so per-package signing is hardening, not a correctness fix. (Note: this is a -GPG/OpenPGP key — a `step-ca`/X.509 cert can't sign RPMs; step-ca is for the registry/console TLS.) +CI GPG-signs every RPM: `packaging/rpm/sign-rpms.sh` (run from `rpm.yml` between build and publish) +signs with the dedicated EdDSA key **`packages@unom.io`** (`AF245C506F4E4763`) and self-verifies +with `rpmkeys --checksig` before publishing, so an unsigned/bad build never reaches the registry. +The public key is served from the registry (the `gpgkey=` URL above) and committed at +`packaging/rpm/RPM-GPG-KEY-punktfunk`. (This is a GPG/OpenPGP key — a `step-ca`/X.509 cert can't +sign RPMs; step-ca is only for registry/console TLS.) -One-time setup: +How it was set up (and how to rotate the key): ```sh -# 1. Generate a DEDICATED, passphrase-less signing key (separate from the Gitea registry key). +# 1. Generate a DEDICATED, passphrase-less signing key (separate from the Gitea metadata key). gpg --batch --gen-key < paste into the CI secret below -gpg --armor --export packages@unom.io > RPM-GPG-KEY-punktfunk # the PUBLIC key +gpg --armor --export-secret-keys packages@unom.io # -> the RPM_GPG_PRIVATE_KEY CI secret +gpg --armor --export packages@unom.io > packaging/rpm/RPM-GPG-KEY-punktfunk # public half -# 2. In the repo's Gitea Actions secrets, add RPM_GPG_PRIVATE_KEY = the armored PRIVATE key -# (and RPM_GPG_PASSPHRASE only if the key has one). The next CI run signs + self-verifies. - -# 3. Publish RPM-GPG-KEY-punktfunk where clients can fetch it, then on each host import it and -# flip the repo to gpgcheck=1: -sudo rpm --import https://git.unom.io/.../RPM-GPG-KEY-punktfunk -sudo sed -i 's/^gpgcheck=0/gpgcheck=1/' /etc/yum.repos.d/punktfunk.repo +# 2. Add the armored PRIVATE key as the RPM_GPG_PRIVATE_KEY Gitea Actions secret. Commit the public +# half and publish it to the registry so the gpgkey= URL resolves: +curl --user ":" --upload-file packaging/rpm/RPM-GPG-KEY-punktfunk \ + https://git.unom.io/api/packages/unom/generic/punktfunk-keys/1/RPM-GPG-KEY-punktfunk ``` -Do **not** flip `gpgcheck=1` before a signed build has published, or installs will fail. +Rotating the key means a new generic-registry version (bump `punktfunk-keys/1` → `/2` and the +`gpgkey=` URL), since the registry rejects re-uploading an existing file. After reboot, as the desktop user: