How to Integrate fail2ban with CloudFlare API v4 Guide

CloudFlare is a pretty sweet free security, firewall and acceleration service that I use on all my WordPress sites. The old CloudFlare API is being retired shortly (November 2016, source). Since the CloudFlare v4 API is already live, I wanted to be prepared for the new API switch.

I use fail2ban to protect my wp-login with basic HTTP authentication (nginx, Apache) and D(D)oS protection from hacker bots that slam wp-login and xmlrpc.php. When you use CloudFlare, it will route all safe traffic to your web server unless you block certain IPs. Blocking IPs on my web servers alone is therefore not sufficient. Luckily fail2ban support custom actions so I wrote this integration with CloudFlare's API v4 so CloudFlare won't send unwanted traffic to my servers.

If you are using CloudFlare with WordPress or WooCommerce I highly recommend this sort of integration for additional security.

This fail2ban CloudFlare tutorial was tested on Ubuntu and Debian but should have no problems on CentOS or other Linux distributions.

How to Integrate fail2ban with CloudFlare API v4 Guide

Installation overview

  • Test CloudFlare API v4 with cURL
  • Create custom CloudFlare action for integration with fail2ban

I have only tested this on fail2ban 0.8.3 and 0.9.4, check your version with this command before proceeding.

sudo fail2ban-client -V

Test CloudFlare API v4

First install cURL

sudo apt-get update
sudo apt-get install curl -y

Go get your CloudFlare API key from here and replace the values below, we will ban IP 1.2.3.4 as a test.

This will be the fail2ban ban action.

curl -s -X POST "https://api.cloudflare.com/client/v4/user/firewall/access_rules/rules" \
  -H "X-Auth-Email: CloudFlare-username" \
  -H "X-Auth-Key: CloudFlare-API-Key" \
  -H "Content-Type: application/json" \
  --data '{"mode":"block","configuration":{"target":"ip","value":"1.2.3.4"},"notes":"Fail2ban"}'

You should get this feedback showing the IP rule was added to CloudFlare, note the success: true.

{"result":{"id":"8286c03a200db3b5d09bab0f414dc111","mode":"block","allowed_modes":["block","challenge","whitelist","js_challenge"],"status":"active","notes":"Fail2ban","scope":{"id":"b39e813030ef3ce72a82896683932a7d","email":"CloudFlare-username","type":"user"},"configuration":{"value":"1.2.3.4","target":"ip"},"created_on":"2016-09-08T14:36:36.770021Z","modified_on":"2016-09-08T14:36:36.770021Z"},"success":true,"errors":[],"messages":[]}

Now test deleting the CloudFlare firewall ban rule we just created. This will be used for the fail2ban unban action.

Note: This uses a nested cURL command, we need to get the rule ID in order to delete it.

curl -X DELETE "https://api.cloudflare.com/client/v4/user/firewall/access_rules/rules/$( \
curl -s -X GET "https://api.cloudflare.com/client/v4/user/firewall/access_rules/rules?mode=block&configuration_target=ip&configuration_value=1.2.3.4&page=1&per_page=1&match=all" \
-H "X-Auth-Email: CloudFlare-username" \
-H "X-Auth-Key: CloudFlare-API-Key" \
-H "Content-Type: application/json" | awk -F"[,:}]" '{for(i=1;i<=NF;i++){if($i~/'id'\042/){print $(i+1)}}}' | tr -d '"' | head -n 1)" \
-H "X-Auth-Email: CloudFlare-username" \
-H "X-Auth-Key: CloudFlare-API-Key" \
-H "Content-Type: application/json"

You should get this feedback showing the IP rule was deleted from CloudFlare.

{"result":{"id":"8286c03a200db3b5d09bab0f414dc111"},"success":true,"errors":[],"messages":[]}

Create Custom CloudFlare Action for Integration with fail2ban

Create the CloudFlare fail2ban action, if it exists already you can back it up first

sudo mv /etc/fail2ban/action.d/cloudflare.conf /etc/fail2ban/action.d/cloudflare.conf.bak
sudo nano /etc/fail2ban/action.d/cloudflare.conf

Paste this configuration which we tested above using CloudFlare's API v4 to ban an IP.

The unban action is the nested cURL command from above that deletes the ban when fail2ban's bantime expires This will keep your CloudFlare IP blacklist clean.

Remember to put your CloudFlare username (your email) in cfuser and your API key which can be found here in cftoken.

#
# Author: Mike Andreasen from https://guides.wp-bullet.com
# Adapted Source: https://github.com/fail2ban/fail2ban/blob/master/config/action.d/cloudflare.conf
# Referenced from: https://www.normyee.net/blog/2012/02/02/adding-cloudflare-support-to-fail2ban by NORM YEE
#
# To get your Cloudflare API key: https://www.cloudflare.com/my-account
#

[Definition]

# Option:  actionstart
# Notes.:  command executed once at the start of Fail2Ban.
# Values:  CMD
#
actionstart =

# Option:  actionstop
# Notes.:  command executed once at the end of Fail2Ban
# Values:  CMD
#
actionstop =

# Option:  actioncheck
# Notes.:  command executed once before each actionban command
# Values:  CMD
#
actioncheck =

# Option:  actionban
# Notes.:  command executed when banning an IP. Take care that the
#          command is executed with Fail2Ban user rights.
# Tags:      IP address
#            number of failures
#          

Ctrl+X, Y and Enter to Save and Exit.

EDIT: Thanks to Vini's research there are now updated ban and unban actions that solve the 400 bad request error

actionban = curl -s -o /dev/null -X POST -H 'X-Auth-Email: ' -H 'X-Auth-Key: ' \
        -H 'Content-Type: application/json' -d '{ "mode": "block", "configuration": { "target": "ip", "value": "" } }' \
        https://api.cloudflare.com/client/v4/user/firewall/access_rules/rules

actionunban = curl -s -o /dev/null -X DELETE -H 'X-Auth-Email: ' -H 'X-Auth-Key: ' \
          https://api.cloudflare.com/client/v4/user/firewall/access_rules/rules/$(curl -s -X GET -H 'X-Auth-Email: ' -H 'X-Auth-Key: ' \
          'https://api.cloudflare.com/client/v4/user/firewall/access_rules/rules?mode=block&configuration_target=ip&configuration_value=&page=1&per_page=1' | tr -d '\n' | cut -d'"' -f6)

Now open up any fail2ban jail you want to integrate with CloudFlare's API v4. I am using a pre-configured nginx-limit-req jail for fail2ban to ban users who make too many requests to nginx.

sudo nano /etc/fail2ban/jail.d/nginx-limit-req.conf

Add an action line that includes our CloudFlare action, if you had no action line before the default is iptables-multiport.

In some versions of fail2ban you need to call it actions instead of action otherwise the jail will not be activated and neither will the action.

[nginx-limit-req]
enabled = true
filter = nginx-limit-req
action = iptables-multiport
         cloudflare
port = http,https
logpath = /var/log/nginx/*error*.log
findtime = 10
bantime = 6000
maxretry = 3

Ctrl+X, Y and Enter to Save

Test the CloudFlare fail2ban integration configuration

sudo fail2ban-client -d

You will see multiple actions are added for your fail2ban jail

['set', 'nginx-limit-req', 'addaction', 'iptables-multiport']
['set', 'nginx-limit-req', 'action', 'iptables-multiport', 'actionban', ' -I f2b- 1 -s  -j ']
['set', 'nginx-limit-req', 'action', 'iptables-multiport', 'actionstop', ' -D  -p  -m multiport --dports  -j f2b-\n -F f2b-\n -X f2b-']
['set', 'nginx-limit-req', 'action', 'iptables-multiport', 'actionstart', ' -N f2b-\n -A f2b- -j \n -I  -p  -m multiport --dports  -j f2b-']
['set', 'nginx-limit-req', 'action', 'iptables-multiport', 'actionunban', ' -D f2b- -s  -j ']
['set', 'nginx-limit-req', 'action', 'iptables-multiport', 'actioncheck', " -n -L  | grep -q 'f2b-[ \\t]'"]
['set', 'nginx-limit-req', 'action', 'iptables-multiport', 'protocol', 'tcp']
['set', 'nginx-limit-req', 'action', 'iptables-multiport', 'chain', 'INPUT']
['set', 'nginx-limit-req', 'action', 'iptables-multiport', 'lockingopt', '-w']
['set', 'nginx-limit-req', 'action', 'iptables-multiport', 'known/known/name', 'default']
['set', 'nginx-limit-req', 'action', 'iptables-multiport', 'blocktype', 'REJECT --reject-with icmp-port-unreachable']
['set', 'nginx-limit-req', 'action', 'iptables-multiport', 'known/lockingopt', '-w']
['set', 'nginx-limit-req', 'action', 'iptables-multiport', 'known/known/port', 'ssh']
['set', 'nginx-limit-req', 'action', 'iptables-multiport', 'known/protocol', 'tcp']
['set', 'nginx-limit-req', 'action', 'iptables-multiport', 'known/known/lockingopt', '-w']
['set', 'nginx-limit-req', 'action', 'iptables-multiport', 'port', 'ssh']
['set', 'nginx-limit-req', 'action', 'iptables-multiport', 'known/known/chain', 'INPUT']
['set', 'nginx-limit-req', 'action', 'iptables-multiport', 'known/name', 'default']
['set', 'nginx-limit-req', 'action', 'iptables-multiport', 'known/known/protocol', 'tcp']
['set', 'nginx-limit-req', 'action', 'iptables-multiport', 'iptables', 'iptables ']
['set', 'nginx-limit-req', 'action', 'iptables-multiport', 'known/__name__', 'Init']
['set', 'nginx-limit-req', 'action', 'iptables-multiport', 'returntype', 'RETURN']
['set', 'nginx-limit-req', 'action', 'iptables-multiport', 'known/returntype', 'RETURN']
['set', 'nginx-limit-req', 'action', 'iptables-multiport', 'known/known/__name__', 'Init']
['set', 'nginx-limit-req', 'action', 'iptables-multiport', 'known/known/returntype', 'RETURN']
['set', 'nginx-limit-req', 'action', 'iptables-multiport', 'name', 'default']
['set', 'nginx-limit-req', 'action', 'iptables-multiport', 'known/known/blocktype', 'REJECT --reject-with icmp-port-unreachable']
['set', 'nginx-limit-req', 'action', 'iptables-multiport', 'known/port', 'ssh']
['set', 'nginx-limit-req', 'action', 'iptables-multiport', 'known/iptables', 'iptables ']
['set', 'nginx-limit-req', 'action', 'iptables-multiport', 'known/chain', 'INPUT']
['set', 'nginx-limit-req', 'action', 'iptables-multiport', 'known/blocktype', 'REJECT --reject-with icmp-port-unreachable']
['set', 'nginx-limit-req', 'action', 'iptables-multiport', 'known/known/iptables', 'iptables ']
['set', 'nginx-limit-req', 'addaction', 'cloudflare']
['set', 'nginx-limit-req', 'action', 'cloudflare', 'actionban', 'curl -X POST "https://api.cloudflare.com/client/v4/user/firewall/access_rules/rules" \\\n-H "X-Auth-Email: " \\\n-H "X-Auth-Key: " \\\n-H "Content-Type: application/json" \\\n--data \'{"mode":"block","configuration":{"target":"ip","value":""},"notes":"Fail2ban"}\'']
['set', 'nginx-limit-req', 'action', 'cloudflare', 'actionstop', '']
['set', 'nginx-limit-req', 'action', 'cloudflare', 'actionstart', '']
['set', 'nginx-limit-req', 'action', 'cloudflare', 'actionunban', 'curl -s -X DELETE "https://api.cloudflare.com/client/v4/user/firewall/access_rules/rules/$(']
['set', 'nginx-limit-req', 'action', 'cloudflare', 'actioncheck', '']
['set', 'nginx-limit-req', 'action', 'cloudflare', 'cfuser', 'CloudFlare-username']
['set', 'nginx-limit-req', 'action', 'cloudflare', 'cftoken', 'CloudFlare-API-Key']
['set', 'nginx-limit-req', 'action', 'cloudflare', 'known/cfuser', 'CloudFlare-username']
['set', 'nginx-limit-req', 'action', 'cloudflare', 'known/cftoken', 'CloudFlare-API-Key']
['start', 'nginx-limit-req']

You can restart fail2ban to activate the CloudFlare integration on your jail.

sudo service fail2ban restart

You can check your logs

sudo tail -f /var/log/fail2ban.log

Now when users violate a rule on your server they will be banned on both your server and CloudFlare, all automatically for convenience.

Sources

Create CloudFlare Firewall Rule
List CloudFlare Rules
Delete CloudFlare Firewall Rule
Setting Multiple Actions fail2ban
Integrate fail2ban with CloudFlare (old API)
DDoS Protection with CloudFlare and fail2ban (old API)
fail2ban Custom Action
Extracting JSON values with Bash