feat: add GPG key setup script and generalize nginx GPG key serving
All checks were successful
poll-upstream / check (push) Successful in 2s
All checks were successful
poll-upstream / check (push) Successful in 2s
Add script/setup/gpg.sh to generate a dedicated lair keyring with a certify-only master key and a 1-year signing subkey, cross-signed by both personal keys. The public key is synced to oolon as <short-id>.gpg. Update nginx config to serve any .gpg file instead of a hardcoded RPM-GPG-KEY-mistralrs path, supporting multiple keys as the repo grows. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -30,7 +30,7 @@ server {
|
|||||||
add_header Cache-Control "no-cache, must-revalidate";
|
add_header Cache-Control "no-cache, must-revalidate";
|
||||||
}
|
}
|
||||||
|
|
||||||
location = /RPM-GPG-KEY-mistralrs {
|
location ~ \.gpg$ {
|
||||||
default_type text/plain;
|
default_type text/plain;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
99
script/setup/gpg.sh
Executable file
99
script/setup/gpg.sh
Executable file
@@ -0,0 +1,99 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
keyring_dir="${HOME}/.gnupg/lair"
|
||||||
|
key_uid="rpm@lair.cafe"
|
||||||
|
remote_host=oolon
|
||||||
|
remote_key_dir="/var/www/rpm"
|
||||||
|
signing_keys=(
|
||||||
|
"1C09AC24C113C7F080DD4AA5B3C5A958508A43F2"
|
||||||
|
"CF3E5AA5DAFD4A7FB69053E393977688ACF3510F"
|
||||||
|
)
|
||||||
|
|
||||||
|
# ensure the lair keyring directory exists
|
||||||
|
install --directory --mode 700 "${keyring_dir}"
|
||||||
|
|
||||||
|
# check for an existing valid key in the lair keyring
|
||||||
|
existing_fpr=$(gpg --homedir "${keyring_dir}" --batch --with-colons --list-keys "${key_uid}" 2>/dev/null \
|
||||||
|
| awk -F: '/^fpr:/ { print $10; exit }') || true
|
||||||
|
|
||||||
|
if [ -n "${existing_fpr}" ]; then
|
||||||
|
echo "found existing key: ${existing_fpr}"
|
||||||
|
else
|
||||||
|
echo "no key found for ${key_uid} in ${keyring_dir}, generating..."
|
||||||
|
|
||||||
|
# create a certify-only master key
|
||||||
|
gpg --homedir "${keyring_dir}" --batch --gen-key <<KEYEOF
|
||||||
|
%no-protection
|
||||||
|
Key-Type: eddsa
|
||||||
|
Key-Curve: ed25519
|
||||||
|
Key-Usage: cert
|
||||||
|
Name-Real: lair.cafe RPM signing
|
||||||
|
Name-Email: ${key_uid}
|
||||||
|
Expire-Date: 0
|
||||||
|
%commit
|
||||||
|
KEYEOF
|
||||||
|
existing_fpr=$(gpg --homedir "${keyring_dir}" --batch --with-colons --list-keys "${key_uid}" \
|
||||||
|
| awk -F: '/^fpr:/ { print $10; exit }')
|
||||||
|
echo "generated master key: ${existing_fpr}"
|
||||||
|
|
||||||
|
# add a dedicated signing subkey with 1-year expiry
|
||||||
|
gpg --homedir "${keyring_dir}" --batch --passphrase '' --quick-add-key \
|
||||||
|
"${existing_fpr}" ed25519 sign 1y
|
||||||
|
echo "added signing subkey to ${existing_fpr}"
|
||||||
|
|
||||||
|
# sign the lair key with each personal key from the default keyring
|
||||||
|
gpg --homedir "${keyring_dir}" --batch --armor --export "${existing_fpr}" | gpg --import
|
||||||
|
for signer in "${signing_keys[@]}"; do
|
||||||
|
gpg --batch --yes --local-user "${signer}" --sign-key "${existing_fpr}"
|
||||||
|
echo "signed lair key with ${signer}"
|
||||||
|
done
|
||||||
|
gpg --armor --export "${existing_fpr}" | gpg --homedir "${keyring_dir}" --import
|
||||||
|
echo "imported signatures back into lair keyring"
|
||||||
|
fi
|
||||||
|
|
||||||
|
short_id="${existing_fpr: -8}"
|
||||||
|
short_id_lower=$(echo "${short_id}" | tr '[:upper:]' '[:lower:]')
|
||||||
|
public_key_file="${short_id_lower}.gpg"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "key fingerprint: ${existing_fpr}"
|
||||||
|
echo "short id: ${short_id}"
|
||||||
|
echo "public key file: ${public_key_file}"
|
||||||
|
|
||||||
|
# export the public key in ascii-armored format
|
||||||
|
gpg --homedir "${keyring_dir}" --batch --armor --export "${existing_fpr}" > "/tmp/${public_key_file}"
|
||||||
|
echo "exported public key to /tmp/${public_key_file}"
|
||||||
|
|
||||||
|
# sync public key to the remote rpm repo root (will not overwrite due to unique filename)
|
||||||
|
if rsync \
|
||||||
|
--archive \
|
||||||
|
--verbose \
|
||||||
|
--ignore-existing \
|
||||||
|
--rsync-path 'sudo rsync' \
|
||||||
|
--chown root:root \
|
||||||
|
--chmod F644 \
|
||||||
|
"/tmp/${public_key_file}" \
|
||||||
|
"${remote_host}:${remote_key_dir}/${public_key_file}"; then
|
||||||
|
echo "sync'd public key to ${remote_host}:${remote_key_dir}/${public_key_file}"
|
||||||
|
else
|
||||||
|
echo "failed to sync public key to ${remote_host}:${remote_key_dir}/${public_key_file}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
rm "/tmp/${public_key_file}"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
signing_subkey_fpr=$(gpg --homedir "${keyring_dir}" --batch --with-colons --list-keys "${key_uid}" \
|
||||||
|
| awk -F: '/^fpr:/ { fpr=$10 } /^sub:/ { getfpr=1; next } getfpr && /^fpr:/ { print $10; exit }')
|
||||||
|
|
||||||
|
echo "next steps:"
|
||||||
|
echo " 1. add the following secrets to the gitea repo:"
|
||||||
|
echo " RPM_SIGNING_KEY = output of: gpg --homedir ${keyring_dir} --armor --export-secret-subkeys ${signing_subkey_fpr}!"
|
||||||
|
echo " RPM_SIGNING_KEY_ID = ${key_uid}"
|
||||||
|
echo " 2. users can import the key with:"
|
||||||
|
echo " sudo rpm --import https://rpm.lair.cafe/${public_key_file}"
|
||||||
|
echo ""
|
||||||
|
echo " the master key (certify-only, no expiry) stays on this workstation in ${keyring_dir}."
|
||||||
|
echo " the signing subkey (1-year expiry) is what CI uses. rotate it with:"
|
||||||
|
echo " gpg --homedir ${keyring_dir} --quick-add-key ${existing_fpr} ed25519 sign 1y"
|
||||||
Reference in New Issue
Block a user