Files
abrown38andClaude Opus 5.5 500bfc7bdd 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>
2026-10-22 16:29:37 +01:00

6.0 KiB

Ban lists

A ban list is where a program bans addresses: Guard (Housecarl's companion, which reads logs), fail2ban with Housecarl's action, or you with hc ban. The program says only which address, and perhaps a protocol and ports. The ban list says what that address is kept from. So a web server that sees password guessing in its own logs can ban the guesser at the router without knowing anything about the router.

Ban lists

hc banlist add router --scope firewall                   # the firewall itself
hc banlist add web --scope hosts --host 10.0.10.57 --host 2001:db8:10::57
hc banlist add edge --scope everything --never trusted
Scope Banned addresses are kept from Where the drops are
firewall the firewall itself: its SSH, DNS, web UI input
hosts the list's hosts (addresses or sets), behind the firewall forward, to those addresses
everything the firewall and everything behind it input and forward

A hosts list covers port forwards to its hosts as well: the forward chain sees a forwarded connection's translated address and port, the host's own. A web server's Guard bans tcp/22, and that is right even when the router forwards its port 2222 to it.

Other settings, with their defaults:

Setting Default
never none addresses, prefixes or sets this list never bans
default_time (--time) 1h a ban's length when the program asks for none
max_time (--max-time) 7d the longest ban; longer ones are cut to it. -1 (none): no limit, so bans may be permanent
max_entries 10000 the most bans the list holds; more are refused (list_full)
group_v6 64 the IPv6 prefix length banned for an address. One customer usually has a whole /64 and can move around in it; 128 bans the one address. IPv4 addresses are banned alone
disabled no the list's bans aren't in force (they are kept)

A ban list can't be renamed: the tokens and programs that ban into it know it by its name. Make a new one instead.

Bans

hc ban edge 203.0.113.9 --time 1d --reason "port scan"
hc ban web 203.0.113.9 --tcp ssh --tcp 8443
hc bans                       # every list's
hc bans web
hc unban web 203.0.113.9      # all of the address's bans in web
hc unban web 17               # one, by ID

A ban is of every port, or of a protocol's ports (numbers or service names, built-in or yours). A port without a protocol is banned for TCP and UDP; a service name takes the service's protocols. Banning an address again renews the ban: its new time counts from now.

Bans are dropped ahead of every rule in their chains, and ahead of the built-in that lets established connections through, so a ban cuts a connection that is already open. Housecarl also closes the banned address's tracked connections the ban covers when it bans it, because connections offloaded to the flowtable no longer pass the rules at all. Replies to connections the inside opened to a banned address are dropped too.

Bans are kept in the database, apart from the configuration (like a dynamic set's entries): they aren't in the history, can't be undone, aren't on probation, and don't go through changesets. They end by themselves; every change, restart and reboot puts the active ones back with the time they have left. Each ban and unban is in the audit log (ban.add, ban.remove), with the program's source and reason.

What is never banned

Whatever a list's never says, Housecarl refuses (422 never_ban) to ban:

  • the inside: addresses in networks of inside zones' interfaces, and private addresses outside the external zones' networks;
  • the firewall's own addresses, and its own prefixes (settings);
  • the address of whoever asks (a program can't lock itself out);
  • loopback, link-local, multicast and unspecified addresses.

A ban grouped to a prefix (IPv6) is refused when the prefix overlaps any of these.

Programs and tokens

Give each program a token for its list alone. A bans grant can name ban lists, as a sets grant names sets:

hc user add guard-web --no-password --grant bans:write:web
hc token create web --user guard-web          # as root on the firewall

With it, the program may ban into web and lift its bans, list them, and read the list itself (GET /banlists/web: its grouping and times), and nothing else. viewer has bans:read, operator bans:write.

Guard

Guard reads log files and the systemd journal on any machine, and bans the addresses that misbehave into a ban list (its own documentation). Run it on the web server with a token for web, and on the router with one for router (or the local socket).

fail2ban

Housecarl ships an action for fail2ban, /usr/share/doc/housecarl/contrib/fail2ban/action.d/housecarl.conf (contrib/ in the source). Copy it to /etc/fail2ban/action.d/, put the token in /etc/fail2ban/housecarl.token (mode 0600), and use it in a jail:

[sshd]
enabled = true
action  = housecarl[list=web, url="https://router.lan:8070"]

The jail's port and protocol go with each ban: port = ssh bans only tcp/22, fail2ban's default 0:65535 bans every port. A bantime of -1 is the list's longest ban. When fail2ban unbans an address Housecarl already let go (its own time ran out first), nothing happens. Add curlopts = --cacert /etc/fail2ban/housecarl-ca.pem for a private CA.

Shadow mode

In shadow mode the ban rules count what they would drop ([would drop]) and let it through, like every other rule, and banning closes no connections.

The ruleset

Each list has four sets, ban_<id>_v4, ban_<id>_v6 (every port) and ban_<id>_v4_svc, ban_<id>_v6_svc (address, protocol and port), all with timeouts:

ip saddr @ban_3_v4 counter drop comment "hc:ban3"
ip saddr . meta l4proto . th dport @ban_3_v4_svc counter drop comment "hc:ban3"
ip6 saddr and ffff:ffff:ffff:ffff:: @ban_3_v6 counter drop comment "hc:ban3"

Set names starting with ban_ are therefore Housecarl's own (set_name_reserved).