2021-12-07 Updated version
Please additional see Follow up – Docker and fail2ban – How I solved it (for me)
Docker is great when running your own services in an isolated ephemeral setup.
I’ve been using this pattern for quite a while now (approx since Linux VServer had been introduced, afterwards with plain LXC and then with Docker).
But SMTP, IMAP and other services are very attractive to not so nice people. And one might want to add some security to the services, eg. using layer 7 level information to block network traffic on layer 3/4.
So as soon as there are like bruteforce login attacks, drop the eg. TCP/IP paket for the IP of the attacker.
Normally you’d use fail2ban out of the box. It provides pretty good detections for the most well known service implementations and integrates the counter measures (eg. iptable based actions) very good into your OS.
With docker this is a little bit different. Docker also uses the iptables network utils for passing the incomming traffic on public IP towards an private IP by DNATting (Destination Network Address Translation) into a docker container private network.
Let’s have a look at the iptables chain flows:
Let’s check the acual config:
$ iptables -t nat -L Chain PREROUTING (policy ACCEPT) target prot opt source destination DOCKER all -- anywhere anywhere ADDRTYPE match dst-type LOCAL Chain INPUT (policy ACCEPT) target prot opt source destination Chain POSTROUTING (policy ACCEPT) target prot opt source destination ... Chain DOCKER (2 references) target prot opt source destination ... DNAT tcp -- anywhere myhost.tdl tcp dpt:https to:10.10.10.10:443
So at a very early stage the traffic is passed on via DNAT. Solutions you can find for Docker and fail2ban mostly deal with the FORWARD chain.
This did not really work oput for me. Therefore I did setup my own fail2ban action (eg. /etc/fail2ban/action.d/iptables-mangle-allports.conf):
[INCLUDES] before = iptables-common.conf [Definition] # Option: actionstart # Notes.: command executed once at the start of Fail2Ban. # Values: CMD # actionstart = -t filter -N f2b-<name> -t filter -A f2b-<name> -j -t filter -I INPUT -p -j f2b-<name> -t filter -I FORWARD -p -j f2b-<name> -t filter -I OUTPUT -p -j f2b-<name> -t filter -I DOCKER -p -j f2b-<name> # Option: actionstop # Notes.: command executed once at the end of Fail2Ban # Values: CMD # actionstop = -t filter -D INPUT -p -j f2b- -t filter -D FORWARD -p -j f2b-<name> -t filter -D OUTPUT -p -j f2b-<name> -t filter -D DOCKER -p -j f2b-<name> <actionflush> -t filter -X f2b-<name> # Option: actioncheck # Notes.: command executed once before each actionban command # Values: CMD # actioncheck = -t filter -n -L | grep -q 'f2b-[ \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 = -t filter -I f2b-<name> 1 -s -j REJECT --reject-with icmp-host-unreachable # 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 = -t filter -D f2b-<name> -s -j REJECT --reject-with icmp-host-unreachable [Init]
I am now able to DROP the packages at a very eraly stage. Which helped me a lot.
Some jail example for my docker based mail setup, /etc/fail2ban/jail.d/mymail.conf:
[emailserver] enabled = true filter = mymail logpath = /var/log/syslog maxretry = 2 findtime = 72000 bantime = 7200 chain = PREROUTING banaction = iptables-mangle-allports[name="emailserver", chain="PREROUTING"]
This uses the mymail filter, defined in /etc/fail2ban/filter.d/mymail.conf:
3 ban in 1 hour > Ban for 1 hour [mymail] enabled = true filter = mymail logpath = /var/log/syslog maxretry = 2 findtime = 86400 bantime = 86400 banaction = iptables-mangle-allports[name="mymail"]
In my docker-compose.yaml, I’ve added a logging towards journald for the auth service used for the mail server:
version: '3' services: auth: image: {{auth-service-image you use}} logging: driver: "journald" options: env: "mail_auth=true" tag: "{{.Name}}/{{.ID}}"