Fail2Ban iptables entries to reject HTTPS not stopping requests to Docker container on Amazon Linux 2

As noted by @tater in the comments above, fail2ban inserts itself into the INPUT chain by default, but traffic to Docker containers is routed using the FORWARD chain, which is routed without touching the INPUT chain. You can see that here:

$ sudo iptables -L
Chain INPUT (policy ACCEPT)
target     prot opt source               destination
f2b-HTTPS  tcp  --  anywhere             anywhere             tcp dpt:https
...

We can test getting fail2ban to work with Docker by inserting a similar fail2ban rule into the FORWARD chain, like this (NOTE: This is not the long term solution):

$ sudo iptables -I FORWARD 1 -p tcp -j f2b-HTTPS

In english, this command says "Insert into the FORWARD chain, at position 1 (i.e. the first rule), for all traffic on the TCP protocol, a reference to the f2b-HTTPS chain", which has the effect of including all that chain's rules at that position.

After doing that, the FORWARD chain should contain the new rule at the top:

$ sudo iptables -L
...
Chain FORWARD (policy DROP)
target     prot opt source               destination
f2b-HTTPS  tcp  --  anywhere             anywhere
DOCKER-USER  all  --  anywhere             anywhere
...

The rules that fail2ban automatically manages under the f2b-HTTPS chain are then used to reject traffic destined for Docker.

However, we want fail2ban to do this for us automatically, rather than having to create our own iptables rules. The solution, then, is to add a second action to the jail config in the file under jail.d/:

action = iptables[actname=iptables-input,   name=HTTPS,                       port=https, protocol=tcp]
         iptables[actname=iptables-forward, name=HTTPS-DOCKER, chain=FORWARD, port=8080, protocol=tcp]

It would probably suffice to just add chain=FORWARD to my original rule, but I decided to keep the INPUT rule as well.

NOTE: The port in the iptables-forward rule is 8080 because that is where my Docker container is listening, and it's the destination forward port that's matched by iptables on a FORWARD rule (it seems), not the inbound port.

Two other things I discovered while solving this:

  1. fail2ban isn't enabled by default when it's installed. To enable it, run:
sudo systemctl enable fail2ban
  1. If your system restarts while a ban is active, fail2ban will insert its rules at the top of the FORWARD chain before Docker inserts its rules at the top of the FORWARD chain. This means that the Docker rules will override the fail2ban rules and bans won't work. (You can simulate this by just doing sudo service docker restart.) To overcome this, you need to make fail2ban start after Docker has established its networking. I did this by changing '/etc/lib/systemd/system/fail2ban.service' by:
  • Adding 'docker.service' to the list of 'After:'
  • Removing the PartOf=firewalld.service line
  • Adding this in the [Service] section to wait for port 443 to open: ExecStartPre=/bin/bash -c '(while ! nc -z -v -w1 localhost 443 > /dev/null; do echo "Waiting for port 443 to open..."; sleep 2; done); sleep 2'
  • (NOTE: You also need nc installed)