From ebb70eb9a3f5190680838bf53d8ddcfd060c0df1 Mon Sep 17 00:00:00 2001 From: Noah White Date: Mon, 16 Feb 2026 15:54:04 +0000 Subject: [PATCH 1/2] fix: move GPG key to systemd-sysupdate expected location systemd-sysupdate only checks global keyrings for signature verification: - /usr/lib/systemd/import-pubring.gpg (system) - /etc/systemd/import-pubring.gpg (admin) The previous location (/etc/sysupdate.alloy.d/alloy.gpg) was not used by systemd-sysupdate. This change moves the key to the correct location so SHA256SUMS.gpg signature verification works. Implements: GHO-59 --- CLAUDE.md | 4 ++-- opentofu/modules/vultr/instance/userdata/ghost.bu | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 0a1e66e..1edc7ba 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -379,7 +379,7 @@ All Alloy sysext images are cryptographically signed. The instance verifies sign before installing updates via systemd-sysupdate. - **Signing Key:** `Alloy Sysext Signing Key ` -- **Public Key Location:** `/etc/sysupdate.alloy.d/alloy.gpg` +- **Public Key Location:** `/etc/systemd/import-pubring.gpg` - **Sysupdate Config:** `/etc/sysupdate.alloy.d/alloy.conf` with `Verify=true` - **Signature Files:** `.asc` files stored alongside images in R2 @@ -438,7 +438,7 @@ cat /etc/sysupdate.alloy.d/alloy.conf systemd-sysupdate -C alloy list # Check the public key -cat /etc/sysupdate.alloy.d/alloy.gpg +cat /etc/systemd/import-pubring.gpg ``` **Note:** Changing the Butane configuration (including the Alloy version) will cause diff --git a/opentofu/modules/vultr/instance/userdata/ghost.bu b/opentofu/modules/vultr/instance/userdata/ghost.bu index 018b097..e518216 100644 --- a/opentofu/modules/vultr/instance/userdata/ghost.bu +++ b/opentofu/modules/vultr/instance/userdata/ghost.bu @@ -60,9 +60,10 @@ storage: Path=/opt/extensions/alloy/ CurrentSymlink=/etc/extensions/alloy.raw - # GPG public key for verifying Alloy sysext signatures + # GPG public key for verifying Alloy sysext signatures (SHA256SUMS.gpg) + # systemd-sysupdate requires keys at /etc/systemd/import-pubring.gpg # Key: Alloy Sysext Signing Key - - path: /etc/sysupdate.alloy.d/alloy.gpg + - path: /etc/systemd/import-pubring.gpg mode: 0644 contents: inline: | From d230f1f98989ed27d53f1b747896494c0155fa3b Mon Sep 17 00:00:00 2001 From: Noah White Date: Mon, 16 Feb 2026 19:53:14 +0000 Subject: [PATCH 2/2] feat: implement dynamic GPG keyring merge for systemd-sysupdate Instead of statically placing the GPG key at /etc/systemd/import-pubring.gpg (which would override the system keyring), implement a dynamic merge approach: - Store Alloy signing key at /etc/systemd/alloy-sysext.gpg.pub - Add merge script at /usr/local/bin/sysupdate-merge-keyring.sh that: - Checks for .pgp (newer systemd) before .gpg (legacy) - Copies system keyring from /usr/lib/systemd/ to /etc/systemd/ - Imports Alloy key using gpg to merge keyrings - Cleans up temporary GPG home directory - Add sysupdate-import-pubring.service that runs before systemd-sysupdate This ensures the Alloy signing key is always appended to whatever keys come with the base image (e.g., Fedora/Ubuntu legacy keys), even if the base image is updated with different keys. --- .../modules/vultr/instance/userdata/ghost.bu | 88 ++++++++++++++++++- 1 file changed, 86 insertions(+), 2 deletions(-) diff --git a/opentofu/modules/vultr/instance/userdata/ghost.bu b/opentofu/modules/vultr/instance/userdata/ghost.bu index e518216..13fa93f 100644 --- a/opentofu/modules/vultr/instance/userdata/ghost.bu +++ b/opentofu/modules/vultr/instance/userdata/ghost.bu @@ -61,9 +61,11 @@ storage: CurrentSymlink=/etc/extensions/alloy.raw # GPG public key for verifying Alloy sysext signatures (SHA256SUMS.gpg) - # systemd-sysupdate requires keys at /etc/systemd/import-pubring.gpg + # This key is merged into /etc/systemd/import-pubring.gpg by the + # sysupdate-import-pubring.service at boot time. The merge preserves + # any existing keys from the base image (e.g., Fedora/Ubuntu legacy keys). # Key: Alloy Sysext Signing Key - - path: /etc/systemd/import-pubring.gpg + - path: /etc/systemd/alloy-sysext.gpg.pub mode: 0644 contents: inline: | @@ -97,6 +99,69 @@ storage: =8FOV -----END PGP PUBLIC KEY BLOCK----- + # Script to merge system GPG keyring with Alloy signing key + # Checks for .pgp (newer systemd) first, then falls back to .gpg (legacy) + - path: /usr/local/bin/sysupdate-merge-keyring.sh + mode: 0755 + contents: + inline: | + #!/bin/bash + # Merge system import-pubring with Alloy sysext signing key + # systemd-sysupdate checks /etc/systemd/ first, then /usr/lib/systemd/ + # We copy the system keyring to /etc and append our key to preserve both + set -euo pipefail + + SYS_KEYRING="" + ETC_KEYRING="/etc/systemd/import-pubring.gpg" + ALLOY_KEY="/etc/systemd/alloy-sysext.gpg.pub" + + # Check for system keyring (.pgp preferred, .gpg fallback) + if [[ -f /usr/lib/systemd/import-pubring.pgp ]]; then + SYS_KEYRING="/usr/lib/systemd/import-pubring.pgp" + ETC_KEYRING="/etc/systemd/import-pubring.pgp" + elif [[ -f /usr/lib/systemd/import-pubring.gpg ]]; then + SYS_KEYRING="/usr/lib/systemd/import-pubring.gpg" + ETC_KEYRING="/etc/systemd/import-pubring.gpg" + fi + + # Create /etc/systemd directory if needed + mkdir -p /etc/systemd + + if [[ -n "$SYS_KEYRING" ]]; then + echo "Copying system keyring from $SYS_KEYRING to $ETC_KEYRING" + cp "$SYS_KEYRING" "$ETC_KEYRING" + else + echo "No system keyring found, creating new keyring at $ETC_KEYRING" + touch "$ETC_KEYRING" + fi + + # Import Alloy signing key into the keyring + if [[ -f "$ALLOY_KEY" ]]; then + echo "Importing Alloy signing key into $ETC_KEYRING" + # Use gpg to import key into keyring file + GNUPGHOME=$(mktemp -d) + export GNUPGHOME + trap 'rm -rf "$GNUPGHOME"' EXIT + + # Import existing keyring if it has content + if [[ -s "$ETC_KEYRING" ]]; then + gpg --batch --quiet --import "$ETC_KEYRING" 2>/dev/null || true + fi + + # Import Alloy key + gpg --batch --quiet --import "$ALLOY_KEY" + + # Export merged keyring + gpg --batch --quiet --export > "$ETC_KEYRING" + + echo "Keyring merge complete" + else + echo "Warning: Alloy signing key not found at $ALLOY_KEY" + fi + + chmod 0644 "$ETC_KEYRING" + echo "Keyring ready at $ETC_KEYRING" + - path: /etc/systemd/system/ghost-compose.service mode: 0644 contents: @@ -401,6 +466,25 @@ systemd: - name: locksmithd.service enabled: true + # Merge system GPG keyring with Alloy signing key before sysupdate runs + # This ensures systemd-sysupdate can verify both system packages and Alloy sysext + # The merge preserves existing keys from the base image while adding our key + - name: sysupdate-import-pubring.service + enabled: true + contents: | + [Unit] + Description=Merge GPG keyring for systemd-sysupdate + Before=systemd-sysupdate.service + ConditionPathExists=/etc/systemd/alloy-sysext.gpg.pub + + [Service] + Type=oneshot + ExecStart=/usr/local/bin/sysupdate-merge-keyring.sh + RemainAfterExit=yes + + [Install] + WantedBy=multi-user.target + - name: systemd-sysupdate.timer enabled: true