Ban lists: where Guard and fail2ban ban addresses

A program bans an address (every port, or a protocol's ports) for a
time; the ban list says what it is kept from: the firewall, some hosts
behind it, or everything. Bans drop ahead of every rule, close the
address's connections, group IPv6 by /64, are kept in the database
across restarts and audited. Never banned: the inside, the firewall's
own addresses, the caller, the list's never. bans grant (bans:write:LIST),
hc banlist/bans/ban/unban, Traffic -> Bans, a fail2ban action in contrib/,
and failed logins and refused tokens logged for Guard and fail2ban.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
abrown38andClaude Opus 5.5 committed 2026-10-22 16:29:37 +01:00
1 parent 6f56eefae2
commit 500bfc7bdd
44 files changed
+2541 -39

No files matched your search

+90
View File
@@ -480,6 +480,96 @@ run "the VLAN rule goes" 0 H rule delete -y "$VR"
run "the VLAN zone goes" 0 H zone delete -y iot
R ip link del enp2s0.30
# ---- ban lists: bans kept from the firewall, a host, or everything
# DNS is open to the internet on the router (tcp/53): a listener answers.
docker exec -d ${P}-router sh -c 'while [ ! -f /tmp/nodns ]; do echo dns | nc -N -l 0.0.0.0 53; done'
sleep 1
run "the router's tcp/53 from the internet" 0 connect net $WAN4 53
contains "^dns$"
run "a ban list for the firewall" 0 H banlist add router --scope firewall
# The lab's internet host shares the router's WAN /64: ban IPv6 addresses
# alone (a /64 would hold the router's own, and is refused).
run "a ban list for jellyfin" 0 H banlist add jf --scope hosts --host $JF4 --host $JF6 --group-v6 128
run "a ban list for everything" 0 H banlist add edge --scope everything --max-time 1h
run "the lists" 0 H banlist list
contains "jf *hosts *10.88.10.57, 2001:db8:7f3d:10::57 *0"
# The firewall's own DNS, for the internet host.
run "ban from the firewall's tcp/53" 0 H ban router $NET4 --tcp dns --reason "e2e"
contains "banned $NET4, tcp/53, in router"
run "the router's DNS is cut for it" 1 connect net $WAN4 53
run "the port forward isn't (scope firewall)" 0 connect net $WAN4 8096
contains "^jellyfin$"
run "the ban in the ruleset" 0 R nft list ruleset
contains "ip saddr . meta l4proto . th dport @ban_[0-9]*_v4_svc counter packets [1-9]"
run "lift it" 0 H unban router $NET4
contains "1 bans lifted"
run "DNS again" 0 connect net $WAN4 53
contains "^dns$"
# fail2ban on jellyfin, with Housecarl's action and a token for its list.
run "a user for jellyfin's fail2ban" 0 H user add f2b-jf --no-password --grant bans:write:jf
R sh -c "/w/hc token create jf --user f2b-jf > /w/f2b.token 2>/dev/null"
run "its token" 0 test -s "$WORK/f2b.token"
# f2b ACTION ip=… port=…: the action's command from contrib/, with the
# jail's values, as fail2ban runs it (on jellyfin).
f2b() {
local conf="$ROOT/contrib/fail2ban/action.d/housecarl.conf" a=$1; shift
local cmd curl
cmd=$(awk -v a="$a" '$0 ~ "^"a" *=" {sub(/^[^=]*= */,""); print; on=1; next} on && /^[ \t]/ {sub(/^[ \t]+/,""); print; next} {on=0}' "$conf" | tr '\n' ' ')
curl=$(sed -n 's/^_curl *= *//p' "$conf")
cmd=${cmd//<_curl>/$curl}
local kv
for kv in "$@" "list=jf" "url=http://$LAN4:8070" "tokenfile=/w/f2b.token" "curlopts=" "protocol=tcp" "bantime=600" "name=jellyfin"; do
cmd=${cmd//<${kv%%=*}>/${kv#*=}}
done
docker exec ${P}-jf sh -c "$cmd"
}
run "fail2ban bans the internet host's https" 0 f2b actionban ip=$NET4 port=https
run "the port forward to jellyfin is cut for it" 1 connect net $WAN4 8096
run "the router's DNS isn't (scope hosts)" 0 connect net $WAN4 53
run "fail2ban bans its IPv6 address" 0 f2b actionban ip=$NET6 port=https
run "IPv6 to jellyfin's 443 is cut" 1 connect net $JF6 443
run "the bans" 0 H bans jf
contains "$NET4 *tcp/443 .*f2b-jf *fail2ban/jellyfin"
contains "$NET6 *tcp/443"
run "never the inside" 22 f2b actionban ip=$CLIENT4 port=https
run "refused, audited" 0 H audit --action ban.add
contains "failure.*inside"
run "fail2ban unbans" 0 f2b actionunban ip=$NET4
run "the port forward works again" 0 connect net $WAN4 8096
contains "^jellyfin$"
run "unbanning what Housecarl already let go is no error" 0 f2b actionunban ip=$NET4
run "and the IPv6 ban" 0 f2b actionunban ip=$NET6
run "jellyfin by IPv6 again" 0 connect net $JF6 443
contains "jellyfin6"
run "the token can't ban elsewhere" 22 f2b actionban ip=$NET4 port=https list=router
# Everything, cutting a connection that is open, and kept over a restart.
R touch /tmp/nodns
connect net $WAN4 53 >/dev/null 2>&1
docker exec -d ${P}-router sh -c 'nc -l 0.0.0.0 53 > /w/held.out'
sleep 1
docker exec -d ${P}-net sh -c "sleep 30 | nc $WAN4 53"
sleep 1
run "an open connection from the internet host" 0 H connections --search "$NET4 :53"
contains "$NET4:[0-9]* *$WAN4:53"
run "ban it from everything" 0 H ban edge $NET4 --time 1d
contains "banned $NET4, every port, in edge (ends in 1h)"
contains "[1-9][0-9]* connections closed"
run "its connection is gone" 0 H connections --search "$NET4 :53"
not_contains ":53"
run "nothing gets through for it" 1 connect net $WAN4 8096
stop
run "restart housecarld" 0 start
run "the ban is back after the restart" 0 R nft list ruleset
contains "$NET4 timeout [0-9hms]* expires"
run "still nothing for it" 1 connect net $WAN4 8096
run "lift it" 0 H unban edge $NET4
run "the ban is out of the ruleset" 0 R nft list ruleset
not_contains "$NET4 timeout"
run "the port forward again" 0 connect net $WAN4 8096
contains "^jellyfin$"
run "the lists go" 0 sh -c "docker exec ${P}-router /w/hc banlist delete -y router && docker exec ${P}-router /w/hc banlist delete -y jf && docker exec ${P}-router /w/hc banlist delete -y edge"
# ---- renaming a zone others refer to
run "rename the imported dhcp zone" 0 H zone update dhcp --name lan