feat(packaging/bazzite): the sysext feed is signed, and the client refuses one that isn't
`punktfunk-sysext` checked the SHA256 of every image it downloaded, which sounds like verification but isn't: SHA256SUMS lives on the same registry as the images it describes, so anything able to replace an image could replace its checksum in the same request. The checksum only ever proved the download wasn't corrupted. Each feed now carries SHA256SUMS.asc, a detached OpenPGP signature over the manifest, and the client verifies it before believing a line of it. The key is packages@unom.io (AF245C506F4E4763) — the same one that already signs our RPMs, so boxes have one key to trust and we have one key to rotate. Its public half is baked into the script rather than fetched from the feed, because a key you fetch from the thing you're authenticating authenticates nothing; gpg (present on Bazzite) does the verifying against a throwaway keyring holding only that key, so "good signature" and "signed by us" are the same statement. Rollout: stable feeds only publish on a tag, so a `--seal` mode re-signs an existing manifest without rebuilding an image, and every rpm.yml run seals the OTHER channel of its Fedora major too. Canary pushes are frequent, so all live feeds seal within a day of this landing and a key rotation propagates without republishing anything. Until a feed is sealed the client refuses it and says so, naming PUNKTFUNK_SYSEXT_ALLOW_UNSIGNED=1 as the informed way through. Two things testing changed. The baked-key fingerprint check compared against an empty string — the armor block is a single-quoted shell literal, so the extracted range carried `FEED_KEY='` on its first line and gpg saw no armor at all; every publish would have "mismatched" and, on a tag, failed the release. And `status` captured fetch_manifest's stderr but only printed it on failure, swallowing the ALLOW_UNSIGNED warning precisely when someone was running unverified. Verified end to end on Bazzite 44 against a file:// feed with a throwaway key: unsigned feed refused; ALLOW_UNSIGNED=1 proceeds and says so; wrong signer rejected; tampered manifest rejected; good signature accepted; `update` exits 1 before touching anything on a bad feed. Publisher side: signs when the baked key matches, refuses on mismatch, refuses with no key, and its signature round-trips through the real client. shellcheck clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -88,6 +88,15 @@ curl --user "<user>:<write:package-PAT>" --upload-file packaging/rpm/RPM-GPG-KEY
|
||||
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.
|
||||
|
||||
**This key also signs the Bazzite sysext feed**, and a third copy of its public half is baked into
|
||||
`packaging/bazzite/punktfunk-sysext.sh` (`FEED_KEY=`) — that script is bootstrapped by `curl` on
|
||||
machines that have nothing installed yet, so it can't fetch the key from the thing it's
|
||||
authenticating. A rotation must update **all three**: the CI secret, this directory's
|
||||
`RPM-GPG-KEY-punktfunk` (+ its registry upload), and `FEED_KEY`. `publish-sysext-feed.sh` compares
|
||||
its signing key's fingerprint against `FEED_KEY` and refuses to sign on a mismatch, so forgetting
|
||||
the third one fails the publish instead of stranding every Bazzite box in front of a feed it
|
||||
can't verify.
|
||||
|
||||
After reboot, as the desktop user:
|
||||
|
||||
```sh
|
||||
|
||||
Reference in New Issue
Block a user