bootstrap/sops (age-keygen refusing in-repo, .sops.yaml, install-age-key.sh glue installing the sops-age Secret) + bootstrap/flux (bootstrap.sh: flux install -> apply -k clusters/dev; decryption/url patches). PM wiring: place .sops.yaml at repo root (SOPS walks to root); inline the 3 flux patches into clusters/dev (avoids kustomize ../ load-restriction). clusters/dev builds with decryption enabled; full CI gate PASS. No key material; nothing executed. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
86 lines
3.4 KiB
Bash
Executable File
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 bootstrap/sops/.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 bootstrap/sops/.sops.yaml):"
|
|
age-keygen -y "${key_abs}" # prints ONLY the public recipient (age1...)
|