Add helper for per-user age keys
This commit is contained in:
parent
728cb73faf
commit
c3ac96ec02
2 changed files with 123 additions and 0 deletions
|
|
@ -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 user’s private key securely (password manager). Multiple recipients in `.sops.yaml` allow any listed user to decrypt.
|
||||||
|
|
|
||||||
114
scripts/manage-age-key.sh
Executable file
114
scripts/manage-age-key.sh
Executable 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
|
||||||
Loading…
Add table
Reference in a new issue