2021-12-072021-12-07 | tkaefer
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/bashipsetcommand="$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 = truefilter = mailserverlogpath = /var/log/syslogmaxretry = 2findtime = 86400bantime = 86400banaction = 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 Syslogfailregex = .* 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/bashfail2banjail="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}"doneIPS="$(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 fidone## 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.