fail2ban & Cloudflare

I have been running a fairly busy site on a hosted package which included a Cloudflare subscription. I moved the site to my Ubuntu server which had fail2ban running.

All seemed to be working. People were being blocked and a few were ending up in recidive. But when I started getting Emails from Cloudflare saying they could not contact my site, I looked a bit deeper.

It dawned on me that the IPs being blocked belonged to Cloudflare. All my traffic was now being routed via Cloudflare’s relatively small range of addresses. Each individual IP would in reality be handling numerous visitors. Blocking one would be blocking many visitors, not just my “hacker”.

My first attempt at a solution was to whitelist Cloudflare from my recidive jail. My thinking was that a 10 minute block on a IP would stop the hacker, and I would avoid doing a longer recidive ban that would interfere with my normal traffic. Upon testing I discovered that Cloudflare varies the IP address on each call to my site.

The result is that fail2ban may block an IP, but the hacker carries on unhindered on another IP, leaving a trail of blocked IPs in his wake.

The solution is to block the unwanted visitor by adding his actual IP to the Cloudflare firewall. I found out too late that there is already a cloudflare action included within fail2ban. My searches also indicated that Cloudflare had updated their API beyond the fail2ban So I do not know if using the vanilla version would have helped.

I am now running with fail2ban blocking the correct IPs at cloudflare, as well as blocking the correct IP locally on behalf of my other sites.

Instructions

The first step is to get the visitors original IP address passed through to your site. The option I chose was to enable the remoteip mod included within the latest Apache package. This supersedes the mod_cloudflare mod which is no longer supported.

Restoring original visitor IPs – Option 2: Installing mod_remoteip with Apache

The installation is a simple enable mod.

sudo a2enmod remoteip

They suggest updating the logformat within the general apache.conf but this assumes all your sites are on Cloudflare. I went for the updated on an individual basis adding the logformat to my virtual host files.

<VirtualHost *:443>

 RemoteIPHeader CF-Connecting-IP	

LogFormat "%a %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" combined
ErrorLog ${APACHE_LOG_DIR}/mywebsite.error.log
CustomLog ${APACHE_LOG_DIR}/mywebsite.access.log combined

# GET LATEST LIST FROM CLOUDFLARE
# https://www.cloudflare.com/ips/
RemoteIPTrustedProxy 173.245.48.0/20
RemoteIPTrustedProxy 103.21.244.0/22
RemoteIPTrustedProxy 103.22.200.0/22
RemoteIPTrustedProxy 103.31.4.0/22
RemoteIPTrustedProxy 141.101.64.0/18
RemoteIPTrustedProxy 108.162.192.0/18
RemoteIPTrustedProxy 190.93.240.0/20
RemoteIPTrustedProxy 188.114.96.0/20
RemoteIPTrustedProxy 197.234.240.0/22
RemoteIPTrustedProxy 198.41.128.0/17
RemoteIPTrustedProxy 162.158.0.0/15
RemoteIPTrustedProxy 104.16.0.0/12
RemoteIPTrustedProxy 172.64.0.0/13
RemoteIPTrustedProxy 131.0.72.0/22
RemoteIPTrustedProxy 2400:cb00::/32
RemoteIPTrustedProxy 2606:4700::/32
RemoteIPTrustedProxy 2803:f800::/32
RemoteIPTrustedProxy 2405:b500::/32
RemoteIPTrustedProxy 2405:8100::/32
RemoteIPTrustedProxy 2a06:98c0::/29
RemoteIPTrustedProxy 2c0f:f248::/32

</VirtualHost>

After restarting Apache, we now have the users original IP appearing in the logs. We are now able to add the cloudflare actions to fail2ban. The following guide was my inspiration. I found I had to make a tweak to the curl call (see the comments about tr -d ‘ ‘) but it pretty much worked out of the box.

About letting it run, I decided I wanted a bit more information in the Cloudflare block so I added the time and jail name.

The display limits the number of characters, so the date-time is in a strange format to save room.

My version of the actionban is reproduced below. <time> is a parameter that is available to the action scripts and is the time the ban was created. <name> is a parameter passed to the action and is configured elsewhere.

actionban = TIMESTAMP=`date [email protected]<time> +%%d%%m-%%H%%M`
	curl -s -X POST "https://api.cloudflare.com/client/v4/user/firewall/access_rules/rules" \
            -H "X-Auth-Email: <cfuser>" \
            -H "X-Auth-Key: <cftoken>" \
            -H "Content-Type: application/json" \
            --data '{"mode":"block","configuration":{"target":"ip","value":"<ip>"},"notes":"'$TIMESTAMP' <name>"}'

Surprisingly, the jail name is not available by default in the action script. It has to be passed in. This is configured in your jail.local

action_yeogle = 	cloudflare[name=%(__name__)s]
		%(banaction)s[name=%(__name__)s, bantime="%(bantime)s", port="%(port)s", protocol="%(protocol)s", chain="%(chain)s"]
             	%(mta)s-whois-lines[name=%(__name__)s, dest="%(destemail)s", logpath=%(logpath)s, chain="%(chain)s"]

action = %(action_yeogle)s

As you can see, I created my own action combo which includes cloudflare, an iptables ban and an Email notification.

One oddity – restarting fail2ban will remove all current bans before reinstating any that are still valid. This is a general feature meaning that any IPs manually unbanned will reappear. The implications for cloudflare is that the bans update to show the reload time and not the original ban time.

previous entry
next entry

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.