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