In a previous blog post (Fail2ban + Tarpit), I explained how to setup a Tarpit for Fail2ban to use it against the attacker that got banned multiple times. It works great especially in conjunction with WP Fail2ban, a fail2ban plugin for WordPress.
Later on, I moved this website to CloudFlare, by doing so I rendered one my security mechanism inefficient.
WP Fail2ban
This plugin simply log the failed login attempts to one of the system log like /var/log/user.log or even the normal syslog. You can also set a group of user that should be logged directly when there is an attempt on it like “admin”. It’s useful to find the bot attempting to login into your admin panel with a default username “admin”.
I set up 2 different jail, one for failed login attempts and one for login attempts from blocked source.
You can find all the information on how to setup this in the documentation of the plugin:Â Installation steps
Problems
By using CloudFlare, it means all the traffic to the website is first sent to CloudFlare then to my server. Which means, my server only get connections from CloudFlare. This poses 2 problems for fail2ban.
No more visitor IP
The first one being the lack of real IP of the visitor, since only CloudFlare is connecting to my server, this can be easily resolved and I’ll explain how. The second problem I faced is directly linked to the resolution of the first one. Even with the real IP of the visitor, fail2ban won’t be able to ban the “visitor” (understand
Real IP useless
Even with the real IP of the visitor, fail2ban won’t be able to ban the “visitor” (understand bot) trying to connect as admin to my WordPress since it’s coming from CloudFlare IP’s. And obviously banning it as I was doing before with TARPIT won’t have any effect.
Solutions
To resolve those problems, I first setup Nginx to gather the real IP of the visitor, CloudFlare does provide a header with this information.
Secondly, I created a script to interact with CloudFlare API to use their firewall to ban an IP.
Getting the real IP
Configuring Nginx for doing is is pretty easy. Add this file to your conf.d
directory. It contains all the CloudFlare CIDR, IPv4 and IPv6.
Now Nginx will send, to your different application, the right IP
CloudFlare Firewall
The script I created directly use the API v4 of CloudFlare to ban the IP directly for your website but adding a captcha. I prefer to put the captcha because a bot can’t pass it (thanks reCAPTACHA) and because if it’s a legitimate user, he’s not completely banned from accessing the website.
Then I created a new action for fail2ban to automatically add and remove IP ban in CloudFlare. The last part was to assign it to the wordpress-hard rule in jail.d. (Or any other jail you setup in your configuration).
Script
To use my script you need to have JQ installed. I’m using it to parse the response from CloudFlare API when wanting to remove a blocked IP.
sudo apt-get install jq
Don’t forget to make the file executable (chmod +x
). I placed it at  /usr/local/sbin/cloudflare-firewall
Fail2ban Action
Put this file in your action.d
 directory. You also need to edit it to add your CloudFlare username, API Key and ZONE ID. You can find all of them in your CloudFlare profile. The ZoneID is the ID assigned to your domain by CloudFlare. You can override this default in each one of the jail you create by given parameter to the action.
Fail2ban Jail for WP Fail2Ban
And the last part is to set your jail to use the new action like this:
[wordpress-hard] enabled = true filter = wordpress-hard logpath = /var/log/auth.log port = http,https maxretry = 1 action =cloudflare-firewall[name=wordpress-dangerous] bantime=86400
Here you go, you’re again protected against those bots.
13th January 2021 at 12:13
Thanks for your article! I know it’s a few years old, but it served as ‘inspiration’ for my own configuration. Indeed, I was having trouble dealing with a single
bash
command-line onaction.d
, and pushed everything out into separate external scripts, just as you did.Eventually, I saw the latest version of how the
fail2ban
core developers implemented this functionality directly inaction.d/cloudflare.conf
. They cheated! 🙂 They used several lines instead of just one! Apparently,fail2ban
has no trouble dealing with multiple lines, so, ultimately, I moved all the code back into that and removed my external scripts…The advantage of having those scripts, of course, is that you can manually ban/unban IPs on Cloudflare without going through
fail2ban-client
, which is sometimes useful, especially when debugging 🙂BTW, Cloudflare’s IP addresses may change over time (not much, but it happens). The safest way to be up-to-date is to have a
cron
job updating the file included bynginx
. Cloudflare eagerly publishes their ‘current’ list of IP addresses in an easy-to-retrieve format, and I’m sure there are many scripts to do exactly that; I use https://github.com/pothi/wordpress-nginx/blob/master/scripts/update-cloudflare-ip-list.sh (that repository has a lot of great tips to run WordPress on top ofnginx
).