#!/usr/bin/env bash # npm-scanner – automated testbed checks (run as root next to docker-compose.yaml) # # ./check.sh prepare create the simulated NPM log directories (before `docker compose up`) # ./check.sh run all checks against the running testbed # ./check.sh cleanup unban the test addresses # # Test addresses are from the documentation ranges (RFC 5737) and never route anywhere. set -uo pipefail cd "$(dirname "$0")" C=npm-scanner-testbed F=./fake-npm-logs.sh pass=0 fail=0 ok() { echo " ok $*"; pass=$((pass + 1)); } bad() { echo " FAIL $*"; fail=$((fail + 1)); } f2b() { docker exec "$C" fail2ban-client "$@"; } banned() { f2b get "$1" banip 2>/dev/null | tr ' ' '\n' | grep -qx "$2"; } wait_ban() { # jail ip [seconds] for _ in $(seq 1 "${3:-15}"); do banned "$1" "$2" && return 0; sleep 1; done return 1 } if [[ "${1:-}" == "prepare" ]]; then mkdir -p npm-logs/log npm-logs/logs data touch npm-logs/log/proxy-host-1_access.log npm-logs/log/default-host_access.log echo "prepared: npm-logs/log, npm-logs/logs, data" exit 0 fi if [[ "${1:-}" == "cleanup" ]]; then for ip in 198.51.100.77 198.51.100.78 198.51.100.79 198.51.100.80 192.0.2.10 198.51.100.81 198.51.100.82; do f2b unban "$ip" >/dev/null 2>&1 || true done echo "test addresses unbanned" exit 0 fi echo "== 1. container" state=$(docker inspect -f '{{.State.Health.Status}}' "$C" 2>/dev/null) [[ "$state" == healthy ]] && ok "health: $state" || bad "health: ${state:-not running}" jails=$(f2b status | sed -n 's/.*Jail list:\s*//p') [[ "$jails" == *npm-scanner* ]] && ok "jails: $jails" || bad "npm-scanner jail missing ($jails)" [[ "$jails" == *npm-wplogin* ]] && ok "npm-wplogin active" || bad "npm-wplogin missing (ENABLE_WPLOGIN=true?)" docker exec "$C" test -f /usr/share/npm-scanner/filter-tests.passed && ok "filter tests passed in build" || bad "filter test marker missing" echo "== 2. scanner ban (log/, host 1)" $F scan 198.51.100.77 3 1 log >/dev/null wait_ban npm-scanner 198.51.100.77 && ok "198.51.100.77 banned" || bad "198.51.100.77 not banned" iptables -S DOCKER-USER | grep -q -- '-j f2b-npm-scan' && ok "DOCKER-USER jumps to f2b-npm-scan" || bad "no jump in DOCKER-USER" iptables -S f2b-npm-scan 2>/dev/null | grep -q '198.51.100.77' && ok "rule for 198.51.100.77 on the host" || bad "no host rule for 198.51.100.77" echo "== 3. new host in second directory (logs/, host 40) -> watcher" $F newhost 40 logs >/dev/null sleep 6 f2b get npm-scanner logpath | grep -q '/logs/npm2/proxy-host-40_access.log' && ok "host 40 watched after reload" || bad "host 40 not watched" docker logs "$C" 2>&1 | grep -q 'proxy-host-40_access.log – npm-scanner reloaded' && ok "watcher logged the reload" || bad "no watcher message" banned npm-scanner 198.51.100.77 && ok "ban survived the reload" || bad "ban lost after reload" $F scan 198.51.100.78 3 40 logs >/dev/null wait_ban npm-scanner 198.51.100.78 && ok "198.51.100.78 banned from host 40" || bad "198.51.100.78 not banned" echo "== 4. default host 444" $F fallback 198.51.100.79 3 >/dev/null wait_ban npm-scanner 198.51.100.79 && ok "198.51.100.79 banned (444)" || bad "198.51.100.79 not banned" echo "== 5. wp-login brute force" $F wplogin 198.51.100.80 5 1 log >/dev/null wait_ban npm-wplogin 198.51.100.80 && ok "198.51.100.80 banned by npm-wplogin" || bad "198.51.100.80 not banned" echo "== 6. must NOT ban" $F scan 192.0.2.10 5 1 log >/dev/null $F harmless 198.51.100.81 10 1 log >/dev/null $F scan 198.51.100.82 2 1 log >/dev/null sleep 5 banned npm-scanner 192.0.2.10 && bad "whitelisted 192.0.2.10 banned" || ok "whitelisted 192.0.2.10 not banned" banned npm-scanner 198.51.100.81 && bad "harmless 198.51.100.81 banned" || ok "harmless 404/200 not banned" banned npm-scanner 198.51.100.82 && bad "198.51.100.82 banned after 2 hits" || ok "2 hits below maxretry" echo "== 7. restart keeps bans" docker restart "$C" >/dev/null for _ in $(seq 1 30); do f2b ping >/dev/null 2>&1 && break; sleep 1; done sleep 3 banned npm-scanner 198.51.100.77 && ok "198.51.100.77 restored after restart" || bad "bans lost after restart" iptables -S f2b-npm-scan 2>/dev/null | grep -q '198.51.100.77' && ok "host rule restored" || bad "host rule missing after restart" echo "== 8. stop removes the rules" docker stop "$C" >/dev/null if iptables -S 2>/dev/null | grep -q 'f2b-npm-'; then bad "f2b-npm-* rules left after stop"; else ok "no f2b-npm-* rules after stop"; fi docker start "$C" >/dev/null for _ in $(seq 1 30); do f2b ping >/dev/null 2>&1 && break; sleep 1; done sleep 3 banned npm-scanner 198.51.100.77 && ok "bans back after start" || bad "bans missing after start" echo echo "result: $pass ok, $fail failed" [[ $fail -eq 0 ]]