#!/usr/bin/env bash
# tests/opnsense-bootstrap-apikey/run-tests.sh -- offline harness for
# scripts/opnsense-bootstrap-apikey.sh. Stubs ssh/scp on PATH, so no edge is ever
# contacted and no real key is ever minted.
#
# The load-bearing case is T5: the script MUST refuse to overwrite an existing creds
# file. An existing file means a LIVE key on the edge; overwriting the local copy
# would strand it there permanently, because OPNsense stores the secret as
# crypt(secret,'$6$') and it can never be read back. That is an unrecoverable
# credential loss, and a guard clause is the only thing standing in front of it.
#
# Exit: 0 all pass | 1 any case failed. ASCII + LF.
set -uo pipefail
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
SCRIPT="$HERE/../../scripts/opnsense-bootstrap-apikey.sh"
MINTER="$HERE/../../scripts/opnsense-mint-apikey.php"
TMP="$(mktemp -d)"; trap 'rm -rf "$TMP"' EXIT
PASS=0; FAIL=0
FAKE_KEY="FAKEKEYaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
FAKE_SECRET="FAKESECRETbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
ok() { PASS=$((PASS+1)); echo " PASS $1"; }
bad() { FAIL=$((FAIL+1)); echo " FAIL $1"; }
# --- stub ssh/scp: scp "retrieving" the remote file writes a fake creds payload ---
mk_stubs() {
cat > "$TMP/bin/ssh" <<'STUB'
#!/usr/bin/env bash
exit 0
STUB
cat > "$TMP/bin/scp" <<STUB
#!/usr/bin/env bash
# last arg is the destination; if it is a LOCAL path (no colon), we are retrieving
dest="\${!#}"
case "\$dest" in
*:*) exit 0 ;; # shipping TO the edge -- nothing to do
*) printf 'key=%s\nsecret=%s\n' "$FAKE_KEY" "$FAKE_SECRET" > "\$dest"; exit 0 ;;
esac
STUB
chmod +x "$TMP/bin/ssh" "$TMP/bin/scp"
}
mkdir -p "$TMP/bin"; mk_stubs
echo "T1: the minter PHP exists and is what we ship"
if [ -f "$MINTER" ] && grep -q "apikeys->add()" "$MINTER"; then
ok "T1 minter present and calls the VENDOR model (apikeys->add)"
else
bad "T1 minter missing or does not call apikeys->add()"
fi
echo "T2: the minter never prints the secret"
if grep -qE 'printf|echo' "$MINTER" && ! grep -E "printf|echo" "$MINTER" | grep -q "\['secret'\]"; then
ok "T2 minter prints lengths, never the secret itself"
else
bad "T2 minter may print the secret"
fi
echo "T3: no args -> usage, exit 1"
out="$(bash "$SCRIPT" 2>&1)"; rc=$?
{ [ "$rc" = "1" ] && grep -q usage <<<"$out"; } && ok "T3 usage guard" || bad "T3 usage guard (rc=$rc)"
echo "T4: dry-run needs no ssh key and contacts nothing"
out="$(env -u OPNSENSE_SSH_KEY bash "$SCRIPT" --dry-run 10.10.0.1 "$TMP/nope.txt" 2>&1)"; rc=$?
{ [ "$rc" = "0" ] && grep -q "DRY-RUN" <<<"$out"; } && ok "T4 dry-run" || bad "T4 dry-run (rc=$rc)"
echo "T5: REFUSES to overwrite an existing creds file (the unrecoverable-loss guard)"
printf 'key=existing\nsecret=existing\n' > "$TMP/existing.txt"
out="$(OPNSENSE_SSH_KEY=/dev/null PATH="$TMP/bin:$PATH" bash "$SCRIPT" 10.10.0.1 "$TMP/existing.txt" 2>&1)"; rc=$?
if [ "$rc" = "1" ] && grep -q "refusing to overwrite" <<<"$out"; then
# and it must NOT have touched the file
if grep -q "^key=existing$" "$TMP/existing.txt"; then
ok "T5 refuses to clobber a live credential, and leaves it intact"
else
bad "T5 refused but MODIFIED the existing file"
fi
else
bad "T5 did NOT refuse to overwrite an existing creds file (rc=$rc) -- would strand a live key"
fi
echo "T6: missing SSH key -> fail loud (not a silent agent-auth attempt)"
out="$(env -u OPNSENSE_SSH_KEY PATH="$TMP/bin:$PATH" bash "$SCRIPT" 10.10.0.1 "$TMP/out6.txt" 2>&1)"; rc=$?
{ [ "$rc" = "1" ] && grep -q "OPNSENSE_SSH_KEY" <<<"$out"; } && ok "T6 ssh-key guard" || bad "T6 ssh-key guard (rc=$rc)"
echo "T7: happy path writes a 0600 creds file in the shape opnsense-api.sh parses"
out="$(OPNSENSE_SSH_KEY=/dev/null PATH="$TMP/bin:$PATH" bash "$SCRIPT" 10.10.0.1 "$TMP/out7.txt" 2>&1)"; rc=$?
if [ "$rc" = "0" ] && [ -f "$TMP/out7.txt" ]; then
mode=$(stat -c %a "$TMP/out7.txt")
k=$(grep -c '^key=' "$TMP/out7.txt"); s=$(grep -c '^secret=' "$TMP/out7.txt")
if [ "$mode" = "600" ] && [ "$k" = "1" ] && [ "$s" = "1" ]; then
ok "T7 minted file: mode 600, one key= line, one secret= line"
else
bad "T7 bad output file (mode=$mode key=$k secret=$s)"
fi
else
bad "T7 happy path failed (rc=$rc): $out"
fi
echo "T8: the secret never appears on stdout/stderr"
case "$out" in
*"$FAKE_SECRET"*) bad "T8 SECRET PRINTED to output";;
*) ok "T8 secret absent from output";;
esac
echo
echo "RESULT: PASS=$PASS FAIL=$FAIL"
[ "$FAIL" -eq 0 ] && { echo "ALL PASS"; exit 0; } || { echo "FAILURES"; exit 1; }