Newer
Older
openstack-caracal-dc-dc / tests / site-ssh-config / run-tests.sh
#!/usr/bin/env bash
# tests/site-ssh-config/run-tests.sh
#
# Harness for scripts/site-ssh-config.sh (D-126 shell layer). OFFLINE -- touches
# no SSH, no network; `install` is exercised only against a throwaway --config-dir
# (never the real ~/.ssh). Pins the as-built host/user/proxyjump mapping, the
# secrets boundary (key by path, never baked), Include idempotency + 0600 perms,
# and that no un-measured DC host leaks into an active row. ASCII + LF.
set -uo pipefail
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
S="$(cd "$HERE/../../scripts" && pwd)/site-ssh-config.sh"
pass=0; fail=0
ok()  { pass=$((pass+1)); }
bad() { fail=$((fail+1)); echo "  FAIL: $1"; }

command -v bash >/dev/null 2>&1 || { echo "FAIL: bash required"; exit 1; }

# --- parse / help / list ---
bash -n "$S" 2>/dev/null && ok || bad "does not parse (bash -n)"
bash "$S" --help >/dev/null 2>&1 && ok || bad "--help -> nonzero"
bash "$S" >/dev/null 2>&1; [ $? -ne 0 ] && ok || bad "no-arg must be nonzero (usage)"
bash "$S" list 2>&1 | grep -q 'office1-netbox' && ok || bad "list lost office1-netbox"

R="$(bash "$S" render 2>&1)"
RK="$(bash "$S" render --key /tmp/fake.key 2>&1)"

# --- as-built host/user/proxyjump mapping (MEASURED values) ---
printf '%s' "$R" | grep -q 'Host voffice1'          && ok || bad "lost voffice1 alias"
printf '%s' "$R" | grep -q 'Host office1-netbox'    && ok || bad "lost office1-netbox alias"
printf '%s' "$R" | grep -qE 'HostName 10\.10\.1\.10' && ok || bad "netbox HostName not 10.10.1.10"
# office1-netbox must be User ubuntu + ProxyJump voffice1 (the -J chain of record)
awk '/^Host office1-netbox/{f=1} f&&/User ubuntu/{u=1} f&&/ProxyJump voffice1/{p=1} /^Host office1-tailscale/{exit} END{exit !(u&&p)}' <<<"$R" \
  && ok || bad "office1-netbox is not (User ubuntu + ProxyJump voffice1)"
# voffice1 is reached DIRECT (no ProxyJump) -- it is the jump host itself
awk '/^Host voffice1/{f=1} f&&/ProxyJump/{bad=1} /^Host office1-netbox/{exit} END{exit bad?1:0}' <<<"$R" \
  && ok || bad "voffice1 wrongly has a ProxyJump (must be direct)"
printf '%s' "$R" | grep -q 'tcsh' && ok || bad "lost the OPNsense tcsh caveat"

# --- SECRETS BOUNDARY: no baked key path/material; key is an operator INPUT ---
printf '%s' "$R"  | grep -q 'IdentityFile <KEY-PATH>' && ok || bad "no-key render must show <KEY-PATH> slot"
printf '%s' "$RK" | grep -q 'IdentityFile /tmp/fake.key' && ok || bad "--key not substituted into IdentityFile"
# scope to EXECUTABLE lines (strip full-line comments) -- the hazard is a baked
# creds path in code/heredoc, not a path named in a documentation comment.
grep -vE '^\s*#' "$S" | grep -qE '\-creds/|BEGIN [A-Z]* ?PRIVATE KEY|/root/netbox-secrets|api\.token' && bad "script bakes a key/secret path or material in code" || ok
grep -q 'NEVER reads key' "$S" && ok || bad "lost the secrets-boundary contract comment"
printf '%s' "$RK" | grep -q 'IdentitiesOnly yes' && ok || bad "lost IdentitiesOnly yes"

# --- NO INFERRED VALUE: no un-measured DC host in an ACTIVE row ---
active="$(sed -n '/declare -A HOSTS=(/,/^)/p' "$S" | grep -vE '^\s*#')"
printf '%s' "$active" | grep -qE '10\.12\.|172\.31\.|vr1-dc[01]' && bad "an inferred DC host is in an ACTIVE row -- measure post-apply" || ok

# --- install (scratch dir only): Include idempotency + 0600 perms + refuse no key ---
SB="$(mktemp -d)"; K="$SB/k"; : > "$K"; chmod 600 "$K"
bash "$S" install --key "$K" --config-dir "$SB" >/dev/null 2>&1 && ok || bad "install failed"
[ -f "$SB/config.d/vr1-sites" ] && ok || bad "fragment not written"
bash "$S" install --key "$K" --config-dir "$SB" >/dev/null 2>&1
[ "$(grep -c 'Include config.d' "$SB/config")" -eq 1 ] && ok || bad "Include line duplicated on re-install"
[ "$(stat -c '%a' "$SB/config")" = "600" ] && ok || bad "config not 0600"
[ "$(stat -c '%a' "$SB/config.d/vr1-sites")" = "600" ] && ok || bad "fragment not 0600"
bash "$S" install --config-dir "$SB" >/dev/null 2>&1; [ $? -ne 0 ] && ok || bad "install without --key must fail"
bash "$S" install --key /nonexistent/key --config-dir "$SB" >/dev/null 2>&1; [ $? -ne 0 ] && ok || bad "install unreadable key must fail"
bash "$S" remove --config-dir "$SB" >/dev/null 2>&1
[ ! -f "$SB/config.d/vr1-sites" ] && ok || bad "remove left the fragment"
[ "$(grep -c 'Include' "$SB/config")" -eq 1 ] && ok || bad "remove wrongly stripped the Include line"
rm -rf "$SB"

# --- rootless + self-locate + no hardcoded repo path (L9) ---
grep -qE '^\s*sudo |systemctl' "$S" && bad "invokes sudo/systemctl -- must be rootless config-only" || ok
grep -q 'BASH_SOURCE' "$S" && ok || bad "does not self-locate via BASH_SOURCE"
grep -q '/home/jessea123/openstack' "$S" && bad "hardcodes an absolute repo path (L9)" || ok

# --- ASCII + LF ---
LC_ALL=C grep -qP '[^\x00-\x7F]' "$S" && bad "non-ASCII byte in script" || ok
grep -qU $'\r' "$S" && bad "CR byte (must be LF)" || ok

echo
total=$((pass+fail))
if [ "$fail" -eq 0 ]; then echo "site-ssh-config: $pass/$total PASS"; exit 0; fi
echo "site-ssh-config: $fail/$total FAIL"; exit 1