Newer
Older
openstack-caracal-dc-dc / tests / opnsense-api / run-tests.sh
#!/usr/bin/env bash
# tests/opnsense-api/run-tests.sh -- offline harness for scripts/opnsense-api.sh.
#
# Tests the guard clauses, URL construction, and credential parsing WITHOUT
# touching the network and WITHOUT a real credential. The fixture key/secret
# below are throwaway fakes, never real key material.
#
# The load-bearing case is T9: the secret must never reach argv. We assert that
# by stubbing `curl` with a recorder that dumps its own argv -- if a future edit
# "simplifies" the script to `curl -u key:secret`, T9 goes red. That is the
# point: the secret is visible in `ps` for anyone on the box otherwise.
#
# Exit: 0 all pass | 1 any case failed.  ASCII + LF.
set -uo pipefail
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
SCRIPT="$HERE/../../scripts/opnsense-api.sh"
TMP="$(mktemp -d)"; trap 'rm -rf "$TMP"' EXIT
PASS=0; FAIL=0

FAKE_KEY="FAKEKEYaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
FAKE_SECRET="FAKESECRETbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"

ok()   { PASS=$((PASS+1)); echo "  PASS  $1"; }
bad()  { FAIL=$((FAIL+1)); echo "  FAIL  $1"; }
check(){ if [ "$2" = "$3" ]; then ok "$1"; else bad "$1 (want '$3', got '$2')"; fi; }

mkcreds() { printf 'key=%s\nsecret=%s\n' "$FAKE_KEY" "$FAKE_SECRET" > "$1"; }

echo "T1: no args -> usage, exit 1"
out="$(bash "$SCRIPT" 2>&1)"; rc=$?
check "T1 exit" "$rc" "1"
case "$out" in *usage*) ok "T1 prints usage";; *) bad "T1 usage text";; esac

echo "T2: bad method rejected"
OPNSENSE_API_HOST=10.10.0.1 out="$(bash "$SCRIPT" DELETE core/firmware/status 2>&1)"; rc=$?
check "T2 exit" "$rc" "1"
case "$out" in *"must be GET or POST"*) ok "T2 method guard";; *) bad "T2 method guard";; esac

echo "T3: host NEVER inferred -- unset OPNSENSE_API_HOST must fail loud"
out="$(env -u OPNSENSE_API_HOST bash "$SCRIPT" GET core/firmware/status 2>&1)"; rc=$?
check "T3 exit" "$rc" "1"
case "$out" in *OPNSENSE_API_HOST*) ok "T3 refuses to guess the edge address";; *) bad "T3 host guard";; esac

echo "T4: dry-run builds the right URL"
out="$(OPNSENSE_API_HOST=10.10.0.1 bash "$SCRIPT" --dry-run GET core/firmware/status 2>&1)"; rc=$?
check "T4 exit" "$rc" "0"
check "T4 url" "$out" "DRY-RUN GET https://10.10.0.1/api/core/firmware/status"

echo "T5: leading '/' and 'api/' in the path are normalized, not 404'd"
out="$(OPNSENSE_API_HOST=10.10.0.1 bash "$SCRIPT" --dry-run GET /api/kea/dhcpv4/get 2>&1)"
check "T5 url" "$out" "DRY-RUN GET https://10.10.0.1/api/kea/dhcpv4/get"

echo "T6: missing creds file -> fail loud, exit 1"
out="$(OPNSENSE_API_HOST=10.10.0.1 OPNSENSE_API_CREDS="$TMP/nope.txt" bash "$SCRIPT" GET core/firmware/status 2>&1)"; rc=$?
check "T6 exit" "$rc" "1"
case "$out" in *"not found"*) ok "T6 says which file is missing";; *) bad "T6 message";; esac

echo "T7: creds file with no key= -> fail loud"
printf 'secret=%s\n' "$FAKE_SECRET" > "$TMP/nokey.txt"
out="$(OPNSENSE_API_HOST=10.10.0.1 OPNSENSE_API_CREDS="$TMP/nokey.txt" bash "$SCRIPT" GET core/firmware/status 2>&1)"; rc=$?
check "T7 exit" "$rc" "1"
case "$out" in *"no 'key='"*) ok "T7 detects missing key";; *) bad "T7 message";; esac

echo "T8: creds file with no secret= -> fail loud"
printf 'key=%s\n' "$FAKE_KEY" > "$TMP/nosecret.txt"
out="$(OPNSENSE_API_HOST=10.10.0.1 OPNSENSE_API_CREDS="$TMP/nosecret.txt" bash "$SCRIPT" GET core/firmware/status 2>&1)"; rc=$?
check "T8 exit" "$rc" "1"
case "$out" in *"no 'secret='"*) ok "T8 detects missing secret";; *) bad "T8 message";; esac

echo "T9: THE SECRET MUST NEVER REACH ARGV (it would show up in \`ps\`)"
mkcreds "$TMP/creds.txt"
# stub curl: record argv, then emit a plausible response + status line
cat > "$TMP/curl" <<'STUB'
#!/usr/bin/env bash
printf '%s\n' "$@" > "$TMP_ARGV"
cat > "$TMP_STDIN"
echo '{"status":"ok"}'
echo '__HTTP_STATUS__:200'
STUB
chmod +x "$TMP/curl"
TMP_ARGV="$TMP/argv.txt" TMP_STDIN="$TMP/stdin.txt" \
  PATH="$TMP:$PATH" OPNSENSE_API_HOST=10.10.0.1 OPNSENSE_API_CREDS="$TMP/creds.txt" \
  bash "$SCRIPT" GET core/firmware/status >/dev/null 2>&1
if grep -q "$FAKE_SECRET" "$TMP/argv.txt" 2>/dev/null; then
  bad "T9 SECRET LEAKED INTO ARGV -- visible in ps"
else
  ok "T9 secret absent from argv"
fi
if grep -q "$FAKE_SECRET" "$TMP/stdin.txt" 2>/dev/null; then
  ok "T9 secret passed via stdin --config (correct)"
else
  bad "T9 secret did not reach curl via stdin -- auth would fail"
fi

echo "T10: the secret is never printed to stdout/stderr"
out="$(TMP_ARGV="$TMP/argv2.txt" TMP_STDIN="$TMP/stdin2.txt" \
  PATH="$TMP:$PATH" OPNSENSE_API_HOST=10.10.0.1 OPNSENSE_API_CREDS="$TMP/creds.txt" \
  bash "$SCRIPT" GET core/firmware/status 2>&1)"
case "$out" in
  *"$FAKE_SECRET"*) bad "T10 SECRET PRINTED to output";;
  *"$FAKE_KEY"*)    bad "T10 KEY PRINTED to output";;
  *)                ok "T10 neither key nor secret appears in output";;
esac

echo "T11: dry-run must NOT read the creds file at all"
out="$(OPNSENSE_API_HOST=10.10.0.1 OPNSENSE_API_CREDS="$TMP/nope.txt" \
  bash "$SCRIPT" --dry-run GET core/firmware/status 2>&1)"; rc=$?
check "T11 exit (no creds needed)" "$rc" "0"

echo "T12: a 401 from the API is surfaced as exit 2, not silently swallowed"
cat > "$TMP/curl" <<'STUB'
#!/usr/bin/env bash
cat > /dev/null
echo '{"status":"denied"}'
echo '__HTTP_STATUS__:401'
STUB
chmod +x "$TMP/curl"
out="$(PATH="$TMP:$PATH" OPNSENSE_API_HOST=10.10.0.1 OPNSENSE_API_CREDS="$TMP/creds.txt" \
  bash "$SCRIPT" GET core/firmware/status 2>&1)"; rc=$?
check "T12 exit" "$rc" "2"
case "$out" in *401*) ok "T12 reports the 401";; *) bad "T12 message";; esac

echo
echo "RESULT: PASS=$PASS FAIL=$FAIL"
[ "$FAIL" -eq 0 ] && { echo "ALL PASS"; exit 0; } || { echo "FAILURES"; exit 1; }