#!/bin/bash # Integration tests for martillo-maldito CLI in isolated network namespaces. # These exercise the full iptables+ipset stack end-to-end. # # Requires: ipset, iptables, sudo, cargo (already built binary at $BIN) # Usage: BIN=./target/release/martillo_maldito ./tests/integration_netns.sh set -euo pipefail BIN="${BIN:-./target/release/martillo_maldito}" SET="${IPSET_NAME:-banned_it_test}" NS="martillo-it-$RANDOM" if [[ ! -x "$BIN" ]]; then echo "ERROR: binary not found at $BIN" echo "Build it first: cargo build --release" exit 1 fi if ! command -v ipset >/dev/null; then echo "ERROR: ipset not installed" exit 1 fi if ! command -v sudo >/dev/null; then echo "ERROR: sudo not installed (required for netns + iptables)" exit 1 fi cleanup() { sudo ip netns del "$NS" 2>/dev/null || true sudo ipset destroy "$SET" 2>/dev/null || true } trap cleanup EXIT echo "==> Creating namespace $NS" sudo ip netns add "$NS" echo "==> Cleaning any pre-existing set" sudo ipset destroy "$SET" 2>/dev/null || true PASS=0 FAIL=0 assert_eq() { local desc="$1" expected="$2" actual="$3" if [[ "$expected" == "$actual" ]]; then echo " PASS: $desc" PASS=$((PASS+1)) else echo " FAIL: $desc (expected: '$expected', got: '$actual')" FAIL=$((FAIL+1)) fi } run_cli() { sudo ip netns exec "$NS" "$BIN" "$@" } echo "" echo "==> Test 1: ban then unban cycle" run_cli ban-ip -i 192.0.2.10 assert_eq "ipset contains banned IP" "0" "$(sudo ip netns exec "$NS" ipset test "$SET" 192.0.2.10 >/dev/null 2>&1; echo $?)" run_cli unban-ip -i 192.0.2.10 assert_eq "ipset no longer contains IP" "1" "$(sudo ip netns exec "$NS" ipset test "$SET" 192.0.2.10 >/dev/null 2>&1; echo $?)" echo "" echo "==> Test 2: get-banned-ips returns JSON array" sudo ip netns exec "$NS" "$BIN" ban-ip -i 192.0.2.20 sudo ip netns exec "$NS" "$BIN" ban-ip -i 192.0.2.21 output=$(sudo ip netns exec "$NS" "$BIN" get-banned-ips) assert_eq "output is valid JSON" "true" "$(echo "$output" | jq -e '. | type == "array"' >/dev/null 2>&1 && echo true || echo false)" count=$(echo "$output" | jq 'length') assert_eq "contains 2 IPs" "2" "$count" echo "" echo "==> Test 3: unban is idempotent" run_cli unban-ip -i 192.0.2.99 output=$(run_cli unban-ip -i 192.0.2.99) assert_eq "second unban returns ok" "true" "$output" echo "" echo "==> Test 4: iptables rule installed for ipset match" rule_check=$(sudo ip netns exec "$NS" iptables -C INPUT -m set --match-set "$SET" src -j DROP 2>&1; echo $?) assert_eq "iptables rule exists" "0" "$rule_check" echo "" echo "==> Test 5: secured ports with allowed IPs" run_cli secure-port -p 9999 assert_eq "port 9999 is secured" "true" "$(run_cli is-port-secured -p 9999)" assert_eq "port 8888 is NOT secured" "false" "$(run_cli is-port-secured -p 8888)" run_cli allow-ip-for-port -i 10.0.0.5 -p 9999 allowed=$(run_cli get-secured-ports-with-allowed-ips) assert_eq "10.0.0.5 allowed for 9999" "10.0.0.5" "$(echo "$allowed" | jq -r '."9999"[]')" run_cli unsecure-port -p 9999 run_cli remove-allow-ip-port -i 10.0.0.5 -p 9999 echo "" echo "==> Test 6: bulk ban performance (1000 IPs)" start=$(date +%s%N) for i in $(seq 1 1000); do a=$((i / 256)) b=$((i % 256)) sudo ip netns exec "$NS" ipset add "$SET" "10.50.$a.$b" -exist >/dev/null done end=$(date +%s%N) elapsed_ms=$(( (end - start) / 1000000 )) assert_eq "1000 ipset adds under 5s" "true" "$([[ $elapsed_ms -lt 5000 ]] && echo true || echo false)" echo " (1000 adds took ${elapsed_ms}ms)" echo "" echo "==> Test 7: cli list matches ipset list directly" cli_list=$(sudo ip netns exec "$NS" "$BIN" get-banned-ips | jq -r '.[]' | sort) ipset_list=$(sudo ip netns exec "$NS" ipset list "$SET" -json | jq -r '.ipset[0].members[].ip' | sort) # ipset -json uses different format, compare counts instead cli_count=$(echo "$cli_list" | wc -l) ipset_count=$(echo "$ipset_list" | wc -l) assert_eq "cli and ipset report same count" "$ipset_count" "$cli_count" echo "" echo "================================================" echo "Results: $PASS passed, $FAIL failed" echo "================================================" if [[ $FAIL -gt 0 ]]; then exit 1 fi echo "All integration tests passed."