Web interface to view, ban and unban fail2ban IPs on several hosts via SSH, generalised from the private f2b-web tool. - Web UI (Flask/Waitress): hosts.yaml, local/OIDC/proxy authentication, English/German UI, origin map, optional Loki/Grafana/GeoIP, CLI helpers - Agent f2b-webctl with install.sh/uninstall.sh (dedicated user + sudo, from= restriction, protected networks, manual jail) - Hardened Docker image, examples, documentation, smoke test, testbed Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
9.7 KiB
Agent – f2b-webctl
Every fail2ban host runs the agent f2b-webctl: a small Python
script (standard library only) started as SSH forced command of the web UI's key.
The web UI cannot run anything else on the host.
| Command (sent by the web UI) | Effect |
|---|---|
list |
all jails with their banned IPs (JSON) |
ban <ip> |
ban permanently in the manual jail (manual-block) |
unban <jail> <ip> |
unban in one jail |
unban-all <ip> |
unban in all jails |
version |
agent version |
Never banned, whatever the web UI sends:
- private, loopback, link-local, CGNAT, multicast and reserved ranges
(
10/8,172.16/12,192.168/16,100.64/10,127/8,169.254/16,fc00::/7,fe80::/10, …) - all addresses of the host itself and the address the web UI connects from
- everything listed in
PROTECTEDin/etc/f2b-webctl.conf(e.g. your home IP)
Every action is logged to syslog (facility auth, tag f2b-webctl), including rejected ones.
Requirements
| fail2ban | ≥ 0.11, running (fail2ban-client ping) – older versions without --with-time work, but without start/end times |
| python3 | ≥ 3.8 |
| sshd | reachable from the web UI host |
| sudo | for the default user f2bweb (apt install sudo); not needed with --user root |
| bash, ssh-keygen | for the installer |
Works on Debian, Ubuntu, Proxmox VE and other systemd or non-systemd Linux systems – bare metal, VMs, LXC containers (privileged or unprivileged) and Docker hosts.
Installation
You need the public key printed by fail2ban-webui keygen and the address the web UI
connects from (the public IP of the Docker host, or its LAN address).
# as root on the fail2ban host
git clone https://git.retronerd.at/schiwagoa/fail2ban-webui.git /tmp/fail2ban-webui
cd /tmp/fail2ban-webui/agent
echo 'ssh-ed25519 AAAA… fail2ban-webui' > /tmp/webui.pub
# see what would happen
bash install.sh --key-file /tmp/webui.pub --from 198.51.100.10 --dry-run
# install
bash install.sh --key-file /tmp/webui.pub --from 198.51.100.10
Without git: download the three files f2b-webctl, install.sh and uninstall.sh from
the agent/ directory into one directory and run install.sh from there.
The installer shows a summary and asks before it changes anything:
fail2ban-webui agent 1.0.0 – fail2ban 1.1.0 – web01
script /usr/local/sbin/f2b-webctl
config /etc/f2b-webctl.conf
SSH user f2bweb (will be created)
sudo rule /etc/sudoers.d/f2b-webctl
key file /var/lib/f2bweb/.ssh/authorized_keys
key 256 SHA256:zfwh… (ED25519)
allowed from 198.51.100.10
manual jail manual-block (will be created)
backups /var/backups/fail2ban-webui-agent
What it does:
- backs up every file it is going to change to
--backup-dir - installs
/usr/local/sbin/f2b-webctl(root:root 0750) - writes
/etc/f2b-webctl.conf(kept if it already exists) - creates the system user
f2bweb(home/var/lib/f2bweb, no password) and the sudo ruleDefaults!/usr/local/sbin/f2b-webctl env_keep += "SSH_ORIGINAL_COMMAND SSH_CLIENT" f2bweb ALL=(root) NOPASSWD: /usr/local/sbin/f2b-webctl - adds the key with all restrictions:
restrict,from="198.51.100.10",command="sudo -n /usr/local/sbin/f2b-webctl" ssh-ed25519 AAAA… fail2ban-webui - creates the jail
manual-blockif it does not exist (see below) - runs a self test through the same sudo path the web UI uses
Running the installer again is safe: it updates the script, replaces the key entry and keeps your configuration.
Options
| Option | Default | Meaning |
|---|---|---|
--key-file FILE / --key "KEY" |
– | public key of the web UI (required) |
--from ADDRESSES |
any | only accept the key from these IPs/networks (comma separated). Strongly recommended. |
--user NAME |
f2bweb |
SSH user; root works without sudo |
--authorized-keys-file PATH |
~USER/.ssh/authorized_keys |
where the key entry goes – see managed keys |
--manual-jail NAME |
manual-block |
jail for manual bans |
--banaction ACTION |
from jail.local |
banaction of a newly created manual jail |
--protect NETWORKS |
– | additional protected addresses (only when the config file is written) |
--no-jail |
– | do not create the manual jail |
--force-config |
– | overwrite an existing /etc/f2b-webctl.conf |
--backup-dir DIR |
/var/backups/fail2ban-webui-agent |
backups of changed files |
--dry-run |
– | only show what would be done |
-y, --yes |
– | do not ask |
The installer warns when sshd would not read the chosen key file (AuthorizedKeysFile),
when AllowUsers/AllowGroups are set, or when --user root meets PermitRootLogin no.
The manual jail
Bans from the web UI go into one jail, manual-block by default, with bantime = -1
(permanent). If the jail does not exist, the installer creates:
/etc/fail2ban/filter.d/manual-block.conf
# fail2ban-webui: never matches – the jail is only filled via
# "fail2ban-client set manual-block banip <ip>"
[Definition]
failregex = ^FAIL2BAN-WEBUI-NEVER-MATCHES <HOST>$
ignoreregex =
/etc/fail2ban/jail.d/manual-block.local
# fail2ban-webui: permanent manual bans from the web UI
[manual-block]
enabled = true
filter = manual-block
backend = polling
logpath = /var/log/fail2ban-manual-block.log
maxretry = 1
bantime = -1
The jail uses your default banaction from jail.local. Bans survive restarts through
the fail2ban database.
An existing jail with that name is never touched – if you already have a jail for manual
bans, pass its name with --manual-jail.
Docker hosts
fail2ban's default actions block in the INPUT chain. Traffic to published Docker
ports does not pass INPUT but FORWARD → DOCKER-USER, so those bans would not
affect your containers. For a Docker host, create the manual jail with an action that
uses the DOCKER-USER chain, e.g. with iptables:
bash install.sh --key-file /tmp/webui.pub --from 198.51.100.10 \
--banaction "iptables-allports[chain=DOCKER-USER]"
Check the result after the first ban:
iptables -S DOCKER-USER | head
If the web UI runs on the same Docker host, use address: host.docker.internal in
hosts.yaml and allow SSH from the Docker network if a firewall (e.g. UFW) blocks it:
ufw allow from 172.23.51.0/24 to any port 22 proto tcp comment 'fail2ban-webui'
and install the agent with --from 172.23.51.10.
LXC containers
The agent runs inside the container where fail2ban runs – nothing special is needed. In unprivileged containers fail2ban itself must be able to change the container's firewall (nftables/iptables inside the CT); test with a manual ban:
fail2ban-client set manual-block banip 203.0.113.1 && nft list ruleset | grep 203.0.113.1
fail2ban-client set manual-block unbanip 203.0.113.1
Hosts with managed authorized_keys
Some tools (Ansible, Keywarden, cloud-init, …) overwrite ~/.ssh/authorized_keys.
Put the key into a file they do not manage and make sure sshd reads it:
grep -i '^AuthorizedKeysFile' /etc/ssh/sshd_config /etc/ssh/sshd_config.d/*.conf
# e.g. AuthorizedKeysFile .ssh/authorized_keys .ssh/authorized_keys2
bash install.sh --key-file /tmp/webui.pub --from 198.51.100.10 \
--user root --authorized-keys-file /root/.ssh/authorized_keys2
Configuration – /etc/f2b-webctl.conf
# fail2ban-webui agent (f2b-webctl) – settings
# Jail used for manual bans from the web UI
MANUAL_JAIL=manual-block
# Addresses/networks that must never be banned (comma separated), e.g. your home IP.
# Private, loopback, link-local and CGNAT ranges are always protected.
PROTECTED=203.0.113.25,2001:db8:1234::/48
# Also protect all addresses of this host and the address of the web UI (yes/no)
PROTECT_OWN_ADDRESSES=yes
PROTECT_CLIENT=yes
Changes take effect immediately – the agent reads the file on every call.
Testing on the host
# as root, directly
f2b-webctl list | python3 -m json.tool | head -30
f2b-webctl ban 10.0.0.1 # → rejected, protected
# the same path as the web UI (user f2bweb through sudo)
runuser -u f2bweb -- env SSH_ORIGINAL_COMMAND=version sudo -n /usr/local/sbin/f2b-webctl
# what the agent did
journalctl -t f2b-webctl --since today # or: grep f2b-webctl /var/log/auth.log
Update
Run the installer of the new version again with the same options. Your configuration and the jail are kept; the script and the key entry are replaced.
Uninstall
bash uninstall.sh --dry-run
bash uninstall.sh
| Option | Meaning |
|---|---|
--user NAME |
agent user (default f2bweb) |
--authorized-keys-file PATH |
file with the key entry |
--remove-user |
also delete the user and its home |
--remove-jail |
also delete the manual jail – lifts all manual bans (they are listed first) |
--backup-dir DIR, --dry-run, --yes |
as for the installer |
Manual installation
If you prefer to do it by hand, these are all the steps the installer performs:
install -o root -g root -m 0750 f2b-webctl /usr/local/sbin/f2b-webctl
nano /etc/f2b-webctl.conf # content see above
useradd --system --create-home --home-dir /var/lib/f2bweb --shell /bin/sh f2bweb
usermod -p '*' f2bweb
visudo -f /etc/sudoers.d/f2b-webctl # content see "What it does", step 4
install -d -o f2bweb -g f2bweb -m 700 /var/lib/f2bweb/.ssh
nano /var/lib/f2bweb/.ssh/authorized_keys # line see step 5
chown f2bweb: /var/lib/f2bweb/.ssh/authorized_keys
chmod 600 /var/lib/f2bweb/.ssh/authorized_keys
# manual jail: the two files from "The manual jail", then
touch /var/log/fail2ban-manual-block.log
fail2ban-client reload