Since my post about Docker and fail2ban quite a lot of time has passed (since August 2019), but the page gets still most of the attention on my blog.
I did quite some more work on that due to several reasons.
First of all, I had severe performance issues as soon as ther were too many ips blocked. The iptables are not very good when they need to handle quite some rules.
Adding a new rule for every ip being blocked, is a pretty bad idea.
Specially when all traffic is passing the rules sometime twice.
I do just hook into three different chains:
- INPUT
- FORWARD
- DOCKER-USER
Normally FORWARD would be suffiecient, but docker also faciliates the FORWARD chain. For me it is not deterministic how this actually behaves.
Therefore I’m also hooking into the DOCKER-USER chain (see https://docs.docker.com/network/iptables/ for details).
I also use ipset and iptables to reduce the number individual iptables rules. And there is a single ipset for all ips to be blocked.
[0] # cat /etc/fail2ban/action.d/iptables-mangle-allports-ipset.conf
# Fail2Ban configuration file
#
# Author: Cyril Jaquier
# Modified: Yaroslav O. Halchenko <debian@onerussian.com>
# made active on all ports from original iptables.conf
# Tobias Kaefer <tobias@tkaefer.de>
#
#
[INCLUDES]
before = iptables-common.conf
[Definition]
# Option: actionstart
# Notes.: command executed once at the start of Fail2Ban.
# Values: CMD
#
actionstart = ipset create f2b-<name> hash:net forceadd
<iptables> -t filter -I INPUT -p <protocol> -m set --match-set f2b-<name> src -j REJECT --reject-with icmp-host-unreachable
<iptables> -t filter -I FORWARD -p <protocol> -m set --match-set f2b-<name> src -j REJECT --reject-with icmp-host-unreachable
<iptables> -t filter -I DOCKER-USER -p <protocol> -m set --match-set f2b-<name> src -j REJECT --reject-with icmp-host-unreachable
# Option: actionflush
# Notes.: command executed once to flush IPS, by shutdown (resp. by stop of the jail or this action)
# Values: CMD
#
actionflush = ipset flush f2b-<name>
# Option: actionstop
# Notes.: command executed at the stop of jail (or at the end of Fail2Ban)
# Values: CMD
#
actionstop = <iptables> -t filter -D INPUT -p <protocol> -m set --match-set f2b-<name> src -j REJECT --reject-with icmp-host-unreachable
<iptables> -t filter -D FORWARD -p <protocol> -m set --match-set f2b-<name> src -j REJECT --reject-with icmp-host-unreachable
<iptables> -t filter -D DOCKER-USER -p <protocol> -m set --match-set f2b-<name> src -j REJECT --reject-with icmp-host-unreachable
<actionflush>
ipset destroy f2b-<name>
# Option: actioncheck
# Notes.: command executed once before each actionban command
# Values: CMD
#
# actioncheck = <iptables> -t filter -n -L <chain> | grep -q 'f2b-<name>[ \t]'
# Option: actionban
# Notes.: command executed when banning an IP. Take care that the
# command is executed with Fail2Ban user rights.
# Tags: See jail.conf(5) man page
# Values: CMD
#
actionban = /usr/local/bin/ipset-fail2ban.sh add f2b-<name> <ip>
# Option: actionunban
# Notes.: command executed when unbanning an IP. Take care that the
# command is executed with Fail2Ban user rights.
# Tags: See jail.conf(5) man page
# Values: CMD
#
actionunban = /usr/local/bin/ipset-fail2ban.sh del f2b-<name> <ip>
[Init]
There were comments about „-j REJECT –reject-with icmp-host-unreachable“ not being available on certain systems and therefore „-j DROP“ was used. Which should be fine. They both pervent any more data being routed to the services – the meaning is different though.
I also use a generic shell script to ban or unban an IP for a given fail2ban jail (/usr/local/bin/ipset-fail2ban.sh):
[0] # cat /usr/local/bin/ipset-fail2ban.sh
#!/bin/bash
ipsetcommand="$1"
ipsetname="$2"
IP="$3"
if [[ "del" == ""${ipsetcommand}"" ]]; then
/usr/sbin/ipset test "${ipsetname}" "${IP}" && /usr/sbin/ipset "${ipsetcommand}" "${ipsetname}" "${IP}"
else
/usr/sbin/ipset test "${ipsetname}" "${IP}" || /usr/sbin/ipset "${ipsetcommand}" "${ipsetname}" "${IP}"
fi
It does several things:
- For delete
- Check whether the IP is in the ipset
- Delete if it is in the ipset
- For add
- Check whether the IP is in the ipset
- Add if it is not in the ipset
The jail mail.conf looks something like this:
[0] # cat /etc/fail2ban/jail.d/mailserver.conf
# 3 ban in 1 hour > Ban for 1 hour
[mailserver]
enabled = true
filter = mailserver
logpath = /var/log/syslog
maxretry = 2
findtime = 86400
bantime = 86400
banaction = iptables-mangle-allports-ipset[name="mailserver"]
And the filter looks like this:
[0] # cat /etc/fail2ban/filter.d/mailserver.conf
# Fail2Ban configuration file
[Definition]
# Option: failregex
# Filter "client login failed" in the Syslog
failregex = .* client login failed: .+ client:\ <HOST>
# Option: ignoreregex
# Notes.: regex to ignore. If this regex matches, the line is ignored.
# Values: TEXT
#
ignoreregex =
The docker compose logging hasn’t been changed since my last blog post about that topic.
I am also using blocklist ipsets to eliminate already known malicious IPs with a cron job running this script here:
[0] # cat /usr/local/bin/blockSubnets.sh
#!/bin/bash
fail2banjail="mailserver"
IPS=""
WHITELIST="0.0.0.0/8 10.0.0.0/8 100.64.0.0/10 127.0.0.0/8 169.254.0.0/16 172.16.0.0/12 192.168.0.0/16 255.255.255.255/32"
SOURCE_URLS="http://lists.blocklist.de/lists/strongips.txt https://raw.githubusercontent.com/firehol/blocklist-ipsets/master/firehol_level1.netset"
# There a several other lists to be considered, like:
# https://raw.githubusercontent.com/firehol/blocklist-ipsets/master/dshield_7d.netset \
# https://raw.githubusercontent.com/firehol/blocklist-ipsets/master/greensnow.ipset \
# https://raw.githubusercontent.com/firehol/blocklist-ipsets/master/firehol_level1.netset"
# \
# https://raw.githubusercontent.com/firehol/blocklist-ipsets/master/darklist_de.netset \
# https://raw.githubusercontent.com/firehol/blocklist-ipsets/master/firehol_abusers_1d.netset"
for SOURCE_URL in ${SOURCE_URLS}; do
CURRENT_IPS=$(curl -s ${SOURCE_URL} | grep -v '^#')
IPS="${IPS} ${CURRENT_IPS}"
done
IPS="$(echo ${IPS} | sort -u)"
for IP in ${IPS}; do
# echo "${IP}";
if [[ "${WHITELIST}" == *"${IP}"* ]]; then
echo "not blocking ${IP}"
else
/usr/sbin/ipset --test "f2b-${fail2banjail}" "${IP}" || /usr/bin/fail2ban-client set "${fail2banjail}" banip "${IP}" &> /dev/null
fi
done
## You might also want to add the IP from your cable/DSL/fiber connection at home to not block yourself out, like:
/usr/bin/fail2ban-client set mailu addignoreip $(/usr/bin/dig +short A <<<mydyndnsipv4name.dyndnsprovider.tld>>>)
/usr/bin/fail2ban-client set mailu addignoreip $(/usr/bin/dig +short AAAA <<<mydyndnsipv6name.dyndnsprovider.tld>>>)
Please replace „mydyndnsipv4name.dyndnsprovider.tld“ and „mydyndnsipv6name.dyndnsprovider.tld“ with an appropriate dns record for your cable/DSL/fiber connection.