feat: migrate ban_ip/unban_ip to ipset
- New ipset.rs module wrapping ipset binary (hash:ip backend) - ban_ip() and unban_ip() use ipset.add/del instead of per-IP iptables rules - get_banned_ips() parses ipset list -json - Optional timeout via BAN_DURATION_SECS (default 3600s = 1h) - Auto-installs single iptables rule: -m set --match-set banned src -j DROP - New CLI subcommands: ban-ip, unban-ip (with --timeout) - 13 unit tests for ipset module + integration test script using netns - Update Woodpecker CI to install ipset
This commit is contained in:
Executable
+129
@@ -0,0 +1,129 @@
|
||||
#!/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."
|
||||
Reference in New Issue
Block a user