Centralized fail2ban

For some time now we have been using fail2ban and sshguard on our servers.  Both do basically the same thing:  they monitor logfiles for patterns that indicate unwanted behaviour, like brute force attacking ssh or or other services.  If the find a match they block the offending host’s IP address, mostly by means of iptables (on linux) or similar firewalling/packet filerting solutions.

All these tools work quite well out of the box but then again they are missing certain features that would make sense.  One of them is a centralized version that  does not block a host’s IP only on the machine where a pattern was found but instead issues a blocking command to a central firewall.  The reasoning is quite obvious.  From experience we know that e.g. a host that attacks ssh on one of our servers will most likely also attack other servers as well.  With a centralized fail2ban we are now blocking an attacker right on our firewall and hence protect all other machines on our network.

To acchieve this kind of behaviour with fail2ban we only had to make simple modifications to config files and write four small shell scripts.  Furthermore we created ssh-keys so our servers can get access to the firewall.

When fail2ban now detects an unwanted pattern it uses a new „banaction“ that is configured in /etc/fail2ban/action.d/iptables-remote.conf.  The banaction is a call to a shell script (instead of inserting an iptables rule in the default version).  This script only calls another script on the firewall:

#!/bin/sh

firewall=hostname-of-firewall
chain=${1}
ip=${2}
ports=${3}
protocol=${4}

# call banip.sh on $firewall
ssh ${firewall} ./banip.sh ${chain} ${ip} ${ports} ${protocol}

The script on the firewall will then check if the IP is already blocked and will insert a blocking rule if not:

#!/bin/sh
# banip.sh on firewall

chain=${1}
ip=${2}
ports=${3}
protocol=${4}

iptables -v -n -L FORWARD | egrep -wq ${chain}
if [ ${?} -ne 0 ]
then
   sudo iptables -N ${chain}
   sudo iptables -A ${chain} -j RETURN
   sudo iptables -I FORWARD -p ${protocol} -m multiport --dports ${ports} -j ${chain}
   sudo iptables -I INPUT -p ${protocol} -m multiport --dports ${ports} -j ${chain}
fi

# test if ip is already in chain
sudo iptables -v -n -L ${chain}|egrep -wq ${ip}

if [ ${?} -ne 0 ]
then
   sudo iptables -I ${chain} 1 -s ${ip} -j DROP
   exit ${?}
fi

To change fail2ban’s default behaviour all we need is the following file which should be placed in /etc/fail2ban/action.d/.  The parameters actionstart, actionstop and actioncheck have been unset because we simply don’t want them here:

# Fail2Ban configuration file
# iptables-remote.conf
[Definition]
actionstart =
actionstop =
actioncheck =
actionban = /usr/local/sbin/banip.sh fail2ban-<name> <ip> <port> <protocol>
actionunban = /usr/local/sbin/unbanip.sh fail2ban-<name> <ip>

Now we can change fail2ban’s banaction by overriding the default from jail.conf in jail.local like this:

# fail2ban /etc/fail2ban/jail.local
[DEFAULT]
banaction = iptables-remote

The sensible way to implement this configuration is to first stop fail2ban, then make the necessary changes and then start fail2ban again.  Otherwise you would be left with iptables rules on you server that would survive until the next reboot.

Before using the remote scripts we suggest that you first issue something like this

ssh hostname-of-firewall ls

to test the ssh connection.  If you do not establish an ssh connection before fail2ban tries to do so you get an ughly error because fail2ban does not know how to accept the firewall’s host key …

Update 2015-04-29

Having multiple servers accessing the firewall as root is not that elegant.  So we updated our centralized solution to have the servers use a different username (fail2ban in our case) and modified the scripts accordingly.  We also make us of ssh_config now to specify the key, hostname, port and user that should be used to connect to the firewall.  These changes have been added to the code samples above.  Here is a suggestion for an ssh_config:

Host hostname
 Hostname fqdn-of-host
 User fail2ban
 Port portnumber
 IdentityFile path-to-ssh-key
 IdentitiesOnly yes

On the firewall we created an unprivileged user fail2ban that is allowed to exectue iptables via sudo.

Two scripts were missing in our initial post.  Here is the script that servers call to unban an IP:

#!/bin/sh

firewall=hostname-of-firewall
chain=${1}
ip=${2}

ssh ${firewall} ./unbanip.sh ${chain} ${ip}

And the script that is called on the firewall:

#!/bin/sh

chain=${1}
ip=${2}

sudo iptables -v -n -L ${chain}|egrep -wq ${ip}
if [ ${?} -eq 0 ]
then
   sudo iptables -D ${chain} -s ${ip} -j DROP
   exit ${?}
fi