Add helper for per-user age keys

This commit is contained in:
Tero Halla-aho 2025-12-11 13:51:35 +02:00
parent 728cb73faf
commit c3ac96ec02
2 changed files with 123 additions and 0 deletions

View file

@ -30,3 +30,12 @@ This decrypts `creds/secrets.enc.env` to `creds/secrets.env` if needed (requires
- Generate a new age key: `age-keygen -o creds/age-key.txt` (keep old backup if you need to reencrypt). - Generate a new age key: `age-keygen -o creds/age-key.txt` (keep old backup if you need to reencrypt).
- Update `.sops.yaml` recipient to the new public key. - Update `.sops.yaml` recipient to the new public key.
- Re-encrypt: `SOPS_AGE_KEY_FILE=creds/age-key.txt sops --encrypt --in-place creds/secrets.enc.env`. - Re-encrypt: `SOPS_AGE_KEY_FILE=creds/age-key.txt sops --encrypt --in-place creds/secrets.enc.env`.
## Per-user age keys
- Keys live under `creds/age/<user>.key` (git-ignored) and carry a public key in the header.
- Helper: `./scripts/manage-age-key.sh add alice` generates a key and appends the recipient to `.sops.yaml`.
- Remove: `./scripts/manage-age-key.sh remove alice` deletes the key file and strips the recipient (re-encrypt afterwards).
- List: `./scripts/manage-age-key.sh list`.
- After adding/removing recipients, re-encrypt secrets: `sops --encrypt --in-place creds/secrets.enc.env`.
Share each users private key securely (password manager). Multiple recipients in `.sops.yaml` allow any listed user to decrypt.

114
scripts/manage-age-key.sh Executable file
View file

@ -0,0 +1,114 @@
#!/usr/bin/env bash
# Manage per-user age keys and sops recipients.
# - Creates a new age keypair under creds/age/<user>.key
# - Updates .sops.yaml recipients list for creds/secrets.enc.env
# - Can delete a user (removes key file and recipient entry)
#
# Usage:
# ./scripts/manage-age-key.sh add <username>
# ./scripts/manage-age-key.sh remove <username>
# ./scripts/manage-age-key.sh list
#
# Notes:
# - Keep private keys out of git; creds/age/ is git-ignored.
# - After add/remove, re-encrypt secrets: sops --encrypt --in-place creds/secrets.enc.env
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
AGE_DIR="$ROOT_DIR/creds/age"
SOPS_CONFIG="$ROOT_DIR/.sops.yaml"
usage() {
sed -n '1,12p' "${BASH_SOURCE[0]}"
exit 1
}
list_keys() {
if [[ -d "$AGE_DIR" ]]; then
find "$AGE_DIR" -maxdepth 1 -type f -name '*.key' -printf '%f\n'
fi
}
ensure_age_dir() {
mkdir -p "$AGE_DIR"
}
add_user() {
local user="$1"
ensure_age_dir
local key_file="$AGE_DIR/${user}.key"
if [[ -f "$key_file" ]]; then
echo "Key already exists: $key_file"
exit 1
fi
echo "Generating age key for ${user} -> ${key_file}"
age-keygen -o "$key_file" >/dev/null
local pub
pub=$(grep '^# public key:' "$key_file" | awk '{print $4}')
echo "Public key: $pub"
if [[ ! -f "$SOPS_CONFIG" ]]; then
echo "sops config not found at $SOPS_CONFIG; skipping recipient update."
echo "Add this recipient manually: $pub"
exit 0
fi
if grep -q "$pub" "$SOPS_CONFIG"; then
echo "Recipient already present in $SOPS_CONFIG"
else
echo "Adding recipient to $SOPS_CONFIG"
# Append under the first age key list.
tmp=$(mktemp)
awk -v pub="$pub" '
/^ *- age:/ { in_age=1; print; next }
in_age && /^ *- age1/ { print " - " pub; in_age=0 }
{ print }
' "$SOPS_CONFIG" >"$tmp"
mv "$tmp" "$SOPS_CONFIG"
fi
echo "Done. Re-encrypt secrets after updating recipients:"
echo " sops --encrypt --in-place creds/secrets.enc.env"
}
remove_user() {
local user="$1"
local key_file="$AGE_DIR/${user}.key"
if [[ -f "$key_file" ]]; then
rm -f "$key_file"
echo "Removed key $key_file"
else
echo "No key file for $user at $key_file"
fi
if [[ -f "$SOPS_CONFIG" ]]; then
local pub
pub=$(grep '^# public key:' "$key_file" 2>/dev/null | awk '{print $4}')
if [[ -n "${pub:-}" ]]; then
tmp=$(mktemp)
awk -v pub="$pub" '!($0 ~ pub)' "$SOPS_CONFIG" >"$tmp"
mv "$tmp" "$SOPS_CONFIG"
echo "Removed recipient from $SOPS_CONFIG (if present)."
fi
fi
echo "Re-encrypt secrets to drop removed recipients if needed:"
echo " sops --encrypt --in-place creds/secrets.enc.env"
}
cmd="${1:-}"
case "$cmd" in
add)
[[ $# -eq 2 ]] || usage
add_user "$2"
;;
remove|rm|delete)
[[ $# -eq 2 ]] || usage
remove_user "$2"
;;
list|ls)
list_keys
;;
*)
usage
;;
esac