#!/usr/bin/env bash # emergency-bundle.sh — assemble and display the net-kingdom emergency credential bundle # # Usage: # bash sso-mfa/bootstrap/emergency-bundle.sh \ # --age-key (default: ~/.config/sops/age/keys.txt) # --secrets-dir (default: ./secrets) # --ops-bundle (path to the .tar.age ops bundle) # [--reprint] (reprint a previously-delivered bundle without # requiring all secrets to be present) # # Displays the emergency bundle on stdout, optionally writes a temp file # for 60 seconds, then prompts for human confirmation. # Sets emergency_bundle_delivered: true in creds-state.yaml after confirmation. set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" STATE_FILE="$SCRIPT_DIR/creds-state.yaml" AGE_KEY="$HOME/.config/sops/age/keys.txt" SECRETS_DIR="$SCRIPT_DIR/secrets" OPS_BUNDLE="" REPRINT=false # ── Argument parsing ────────────────────────────────────────────────────────── while [[ $# -gt 0 ]]; do case "$1" in --age-key) AGE_KEY="$2"; shift 2 ;; --secrets-dir) SECRETS_DIR="$2"; shift 2 ;; --ops-bundle) OPS_BUNDLE="$2"; shift 2 ;; --reprint) REPRINT=true; shift ;; *) echo "Unknown argument: $1" >&2; exit 1 ;; esac done # ── Read helpers ────────────────────────────────────────────────────────────── read_env_val() { local file="$1" key="$2" grep -E "^${key}=" "$file" 2>/dev/null | head -1 | sed "s/^${key}=//" || echo "" } read_env() { local component="$1" key="$2" local f="$SECRETS_DIR/$component/secrets.env" if [[ -f "$f" ]]; then read_env_val "$f" "$key" else echo "" fi } # ── Collect values ──────────────────────────────────────────────────────────── # Age private key if [[ -f "$AGE_KEY" ]]; then AGE_PRIVATE_KEY=$(cat "$AGE_KEY") AGE_PUBKEY=$(grep 'public key:' "$AGE_KEY" | awk '{print $NF}' || echo "") else AGE_PRIVATE_KEY="" AGE_PUBKEY="" fi # Break-glass passwords PI_ADMIN_PASS="$(read_env privacyidea PI_ADMIN_PASSWORD)" LLDAP_ADMIN_PASS="$(read_env lldap LLDAP_LDAP_USER_PASS)" PG_ROOT_PASS="$(read_env postgres PG_ROOT_PASSWORD)" BG_PASS="$(read_env breakglass BREAKGLASS_PASSWORD)" # Ops bundle info OPS_BUNDLE_LOCATION="${OPS_BUNDLE:-}" if [[ -n "$AGE_PUBKEY" ]]; then DECRYPT_CMD="age -d -i ops-bundle-.tar.age" else DECRYPT_CMD="age -d -i ops-bundle-.tar.age" fi GENERATED_DATE="$(date -Iseconds)" # ── Temp file (shredded automatically) ─────────────────────────────────────── TMPFILE="$(mktemp --suffix=-emergency-bundle.txt)" cleanup_tmp() { shred -u "$TMPFILE" 2>/dev/null || rm -f "$TMPFILE"; } trap cleanup_tmp EXIT # ── Assemble bundle text ────────────────────────────────────────────────────── BUNDLE_TEXT="$(cat < "$TMPFILE" chmod 600 "$TMPFILE" # ── Display ─────────────────────────────────────────────────────────────────── echo "" echo "$BUNDLE_TEXT" echo "" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo " A copy has been written to: $TMPFILE" echo " It will be shredded automatically when you confirm below." echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "" echo " Store the above in your personal password manager now." echo " (1Password, Bitwarden, KeePassXC, paper — your choice.)" echo "" read -rp " Press Enter when you have stored the bundle (this clears the screen and shreds the temp file): " # Shred temp file (trap handles it but do it explicitly here first) shred -u "$TMPFILE" 2>/dev/null || rm -f "$TMPFILE" trap - EXIT # Clear screen to remove secrets from terminal scrollback clear 2>/dev/null || printf '\033c' echo "" echo " ✔ Emergency bundle confirmed. Temp file shredded." echo ""