Files
home-network-k3s-impl/bootstrap/sops/age-keygen.sh

86 lines
3.4 KiB
Bash
Executable File

#!/usr/bin/env bash
#
# age-keygen.sh — generate the SOPS age keypair for the platform.
#
# Phase 1, T-1.4 (ADR-0007 SOPS+age, R-SEC-01). AUTHORED, NOT RUN.
# =============================================================================
# *** THE OPERATOR RUNS THIS, ONCE, AT BOOTSTRAP. AGENTS MUST NOT RUN IT. ***
# =============================================================================
#
# What it does:
# - Generates an age keypair to a path OUTSIDE this git repo (default:
# ~/.config/sops/age/keys.txt — SOPS's conventional location, honoured by the
# SOPS_AGE_KEY_FILE default lookup).
# - Prints ONLY the PUBLIC recipient (age1...) to stdout. The public recipient
# is safe to paste into the repo-root .sops.yaml. The PRIVATE key stays in
# the file and is NEVER printed, NEVER committed (see .gitignore: age.key,
# *.key). Spec docs/07 + AGENTS.md §5: no secret material in logs/PR/output.
#
# Hard guarantees:
# - REFUSES to write anywhere inside this repository (private key must never
# land where git could track it).
# - REFUSES to overwrite an existing key file (rotation is deliberate — see
# age-key-secret.md "Rotation").
#
# Next step after running this: install the private key into the cluster as the
# `sops-age` Secret — see age-key-secret.md. Then paste the printed recipient
# into .sops.yaml.
#
set -euo pipefail
# --- resolve target path (override with AGE_KEY_FILE) -------------------------
KEY_FILE="${AGE_KEY_FILE:-${HOME}/.config/sops/age/keys.txt}"
# --- refuse to write inside the repo -----------------------------------------
# Repo root = git toplevel if available, else two levels up from this script
# (bootstrap/sops/ -> repo root).
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
if repo_root="$(git -C "${script_dir}" rev-parse --show-toplevel 2>/dev/null)"; then
:
else
repo_root="$(cd "${script_dir}/../.." && pwd)"
fi
abspath() { # portable realpath for a possibly-not-yet-existing path
local p="$1"
local d b
d="$(dirname "$p")"
b="$(basename "$p")"
mkdir -p "$d"
printf '%s/%s\n' "$(cd "$d" && pwd)" "$b"
}
key_abs="$(abspath "${KEY_FILE}")"
case "${key_abs}/" in
"${repo_root}/"*)
echo "ERROR: refusing to write the age private key inside the repo:" >&2
echo " ${key_abs}" >&2
echo " repo root: ${repo_root}" >&2
echo " Set AGE_KEY_FILE to a path OUTSIDE the repo (default is fine)." >&2
exit 1
;;
esac
# --- refuse to clobber an existing key ---------------------------------------
if [[ -e "${key_abs}" ]]; then
echo "ERROR: ${key_abs} already exists — refusing to overwrite." >&2
echo " Rotating? Move the old key aside deliberately (see age-key-secret.md)." >&2
exit 1
fi
command -v age-keygen >/dev/null 2>&1 || {
echo "ERROR: age-keygen not found. Use the pinned dev container" >&2
echo " (infra/devcontainer, AGE_VERSION in its versions.env)." >&2
exit 1
}
# --- generate ----------------------------------------------------------------
umask 077 # 0600 key file
age-keygen -o "${key_abs}" >/dev/null
echo "age private key written (0600) to: ${key_abs}"
echo "This file is OUTSIDE the repo and must stay that way — do NOT commit it."
echo
echo "Public recipient (safe to share — paste into the repo-root .sops.yaml, both age: lines):"
age-keygen -y "${key_abs}" # prints ONLY the public recipient (age1...)