#!/usr/bin/env bash
# scripts/creds-audit.sh -- per-site credential-consolidation audit (SEC-009 enforcement).
#
# Verifies a site's consolidated creds folder (~/<site>-creds/) holds EXACTLY the
# secrets its manifest requires -- each present, non-empty, and with the right mode --
# and that no sensitive file is sprawled loose in the home dir OUTSIDE a *-creds/
# folder. It reads NO secret values: only existence, mode, size, and declared
# provenance (from creds-manifests/<site>.manifest).
#
# WHY THIS EXISTS. SEC-009 established the "ALL site secrets live in ~/<site>-creds/"
# convention, but nothing ENFORCED it, and a secret MINTED ON A SITE VM
# (office1-netbox:/root/netbox-secrets/api.token) was never pulled into the folder --
# it went unnoticed until the fidelity re-check needed it. This audit is the forcing
# check: run it at the END of every site standup and periodically. For the DCs, run
# `creds-audit.sh vr1-dc0` / `vr1-dc1` at standup close so the same gap cannot recur.
#
# Usage:
#   bash scripts/creds-audit.sh <site>          # e.g. vr1-office1
# Env overrides (used by the harness; not for normal operation):
#   CREDS_ROOT    parent dir of <site>-creds   (default: $HOME)
#   MANIFEST_DIR  dir holding <site>.manifest   (default: <repo>/creds-manifests)
set -uo pipefail

SITE="${1:-}"
[ -n "$SITE" ] || { echo "usage: creds-audit.sh <site>   (e.g. vr1-office1)"; exit 2; }

HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO="$(cd "$HERE/.." && pwd)"
CREDS_ROOT="${CREDS_ROOT:-$HOME}"
MANIFEST_DIR="${MANIFEST_DIR:-$REPO/creds-manifests}"
CREDS="$CREDS_ROOT/${SITE}-creds"
MANIFEST="$MANIFEST_DIR/${SITE}.manifest"

F=0
fail() { echo "  [FAIL] $1"; F=$((F+1)); }
okln() { echo "  [ok]   $1"; }

[ -f "$MANIFEST" ] || { echo "FAIL: no manifest at $MANIFEST"; exit 1; }
[ -d "$CREDS" ]    || { echo "FAIL: no creds folder at $CREDS"; exit 1; }

echo "=== creds-audit: $SITE ($CREDS) ==="

# 1. folder must be 0700 (owner-only)
fmode=$(stat -c '%a' "$CREDS")
[ "$fmode" = 700 ] && okln "folder mode 0700" || fail "folder mode $fmode (want 0700)"

# 2. every manifest entry present, right mode, non-empty
declare -A expected=()
while read -r fname fmode2 fsrc; do
  case "$fname" in ''|\#*) continue;; esac
  expected["$fname"]=1
  path="$CREDS/$fname"
  if [ ! -e "$path" ]; then
    fail "MISSING: $fname  (provenance: ${fsrc:-?})"
    continue
  fi
  amode=$(stat -c '%a' "$path")
  [ "$amode" = "$fmode2" ] || fail "$fname mode $amode (want $fmode2)"
  [ -s "$path" ]           || fail "$fname is EMPTY"
done < "$MANIFEST"
[ "$F" = 0 ] && okln "all ${#expected[@]} manifest entries present, correct mode, non-empty"

# 3. no UNDECLARED file in the folder (an undeclared secret sitting here is spread)
for path in "$CREDS"/* "$CREDS"/.[!.]*; do
  [ -e "$path" ] || continue
  b=$(basename "$path")
  [ -n "${expected[$b]:-}" ] || fail "UNDECLARED file in folder: $b (add it to the manifest, or remove it)"
done

# 4. sprawl: sensitive-looking files loose in CREDS_ROOT, OUTSIDE any *-creds/ folder
#
# GLOBS WIDENED 2026-07-26 (D-137 / SEC-023). The original six patterns had measured blind
# spots: they would not have matched `admin.pass` (the SEC-020 secret that sat undetected
# for 12 days), any bare SSH private key, any PEM or `.key`, a `.apikey`, or the
# `~/admin-openrc` that phase-03 is PREDICTED to write at Stage 5. A sprawl check that
# cannot see the exposures already on the register is not a control.
#
# DOTFILES ADDED 2026-07-26 (committee finding): the widening was still a depth-1 glob over
# NON-dot names, so `.admin.pass` -- the dotfile sibling of the very secret the widening
# cites as its motivation -- stayed invisible. SEC-009's founding case was `.vr1-netbox.env`,
# a dotfile, so this class is not hypothetical. STILL NOT COVERED and logged, not fixed:
# secrets nested in a SUBDIRECTORY outside a `*-creds/` folder -- that needs a walk, not a
# glob, and a walk needs a declared bound (D-137 ruling 3) rather than an ad-hoc recursion.
shopt -s nullglob
for path in "$CREDS_ROOT"/*.env "$CREDS_ROOT"/.*.env "$CREDS_ROOT"/*appcred* \
            "$CREDS_ROOT"/*-cred*.txt "$CREDS_ROOT"/*authkey* "$CREDS_ROOT"/*.token \
            "$CREDS_ROOT"/*.pass "$CREDS_ROOT"/*.apikey "$CREDS_ROOT"/*.key \
            "$CREDS_ROOT"/*.pem "$CREDS_ROOT"/*_ed25519 "$CREDS_ROOT"/*_rsa \
            "$CREDS_ROOT"/*openrc* \
            "$CREDS_ROOT"/.*.pass "$CREDS_ROOT"/.*.apikey "$CREDS_ROOT"/.*.key \
            "$CREDS_ROOT"/.*.pem "$CREDS_ROOT"/.*_ed25519 "$CREDS_ROOT"/.*_rsa \
            "$CREDS_ROOT"/.*.token "$CREDS_ROOT"/.*openrc*; do
  [ -f "$path" ] || continue
  fail "SPRAWL: sensitive file loose in $CREDS_ROOT: $(basename "$path") (belongs in a *-creds/ folder)"
done
shopt -u nullglob

echo
if [ "$F" = 0 ]; then echo "creds-audit $SITE: CLEAN"; exit 0; fi
echo "creds-audit $SITE: $F problem(s) -- consolidate/fix before proceeding"; exit 1
