Fail2Ban action for CloudFlare REST API V4

Although the current Fail2Ban package contains a CloudFlare action configuration file (/etc/fail2ban/action.d/cloudflare.conf), it was written to use a CloudFlare API that is now depreciated. To get the action working again, the actionban and actionunban variables need to be updated.

tl;dr version: You can get the updated action file on the fail2ban-action-cloudflare-restv4 GitHub repository.

The CloudFlare REST API V4 documentation show three different approaches to blocking an IP address; User Level, Account Level, and Organizational Level (which is now marked as depreciated). For simplicity, this article will use User Level firewall rules, which will block the IP address on all websites associated with the CloudFlare user account, and does not require the extra step of looking up accounts IDs.

THE NEW ACTIONBAN COMMAND

The new actionban command for CloudFlare IP address blocking is as follows…

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":"Banned by Fail2Ban"}'

This is a straight forward use of CloudFlare’s User Level Create Access Rule command. In the POST data we set the mode to block, send the IP address to be blocked, and set a note that Fail2Ban created this rule.  There is no error checking of the response data returned by CloudFlare.  The actioncheck command could be used to test if the IP address is already banned.  However, there is no need to consume more resources running an actioncheck command before every actionban command since CloudFlare will simply ignore any Create Access Rule command that targets an IP address that is already blocked.

THE NEW ACTIONUNBAN COMMAND

The actionunban command is a bit more difficult to update to the new API because you can’t simply specify an IP address to unblock, you need the firewall rule ID for the blocked IP address to delete the block. The solution is to use the CloudFlare List Access Rules command to look up the firewall rule ID for an IP address, pipe the response to a Python script to extract the IP address, then pipe the script output into a CloudFlare Delete Access Rule command. The new actionban command needed is as follows…

curl -s -X GET -H "X-Auth-Email: <cfuser>" -H "X-Auth-Key: <cftoken>" -H "Content-Type: application/json" \
"https://api.cloudflare.com/client/v4/user/firewall/access_rules/rules?page=1&per_page=5&mode=block&configuration.target=ip&configuration.value=<ip>&notes=Banned by Fail2Ban&match=all&order=configuration.value&direction=desc" | \
python -c "import sys, json; print json.load(sys.stdin)['result'][0]['id'];" | \
xargs [email protected]@ curl -s -X DELETE -H "X-Auth-Email: <cfuser>" -H "X-Auth-Key: <cftoken>" -H "Content-Type: application/json" https://api.cloudflare.com/client/v4/user/firewall/access_rules/rules/@@

The first curl command asks CloudFlare for a list of firewall rules that match both the given IP address and has a note that contains “Banned by Fail2Ban”. Filtering on both the IP address and the note contents ensures that we are only working with firewall rules created by Fail2Ban and not deleting any firewall rules created manually or by some other tool.

The JSON response data from CloudFlare is then piped into a single line Python script which extracts the firewall rule ID from the first result in the response and prints it. This output is piped into the xargs command, which then builds and executes the CloudFlare Delete Access Rule command.


IMPLEMENTATION

We could modify the /etc/fail2ban/action.d/cloudflare.conf file with these new commands. But to avoid package updates possibly overwriting our changes, it would be better to create a new action configuration file. You can download the latest version of the new action file /etc/fail2ban/action.d/cloudflare-restv4.conf from the fail2ban-action-cloudflare-restv4 GitHub repository.  Copy this new action file the /etc/fail2ban/action.d/ directory on your server.

Changes to jail.local

Next, add CloudFlare’s IPv4 Addresses Ranges to your ignoreip variable in your jail.local file. This will prevent Fail2Ban from accidentally banning any of CloudFlare’s servers. Also, if you are running Apache2, please take a look at mod_cloudflare to make sure your log files are reporting the correct client IP address. Otherwise the following ignoreip setting will result in all filter hits being ignored…

# "ignoreip" can be an IP address, a CIDR mask or a DNS host. Fail2ban will not
# ban a host which matches an address in this list. Several addresses can be
# defined using space (and/or comma) separator.
ignoreip = 127.0.0.1/8 103.21.244.0/22 103.22.200.0/22 103.31.4.0/22 104.16.0.0/12 108.162.192.0/18 131.0.72.0/22 141.101.64.0/18 162.158.0.0/15 172.64.0.0/13 173.245.48.0/20 188.114.96.0/20 190.93.240.0/20 197.234.240.0/22 198.41.128.0/17

Next, fill in your cfemail with your CloudFlare account email address. Fill in cfapikey with your CloudFlare Global API Key. Define a new action value called action_cf_v4 to replace the old action_cf_mwl value as shown below. You will probably not want to use this CloudFlare action as the default action since CloudFlare only deals with web site traffic (for example, a CloudFlare IP block will not stop a direct connection to your server’s SSH port). Instead, use this CloudFlare action as an override for web service related jails. However, the recidive jail is a good candidate for using the CloudFlare action to entirely block repeat offenders.

#
# ACTIONS
#
# ban IP on CloudFlare & send an e-mail with whois report and relevant log lines
# to the destemail. Replace cfemail and cfapikey example values with your own.
#
cfemail = [email protected]
cfapikey = c2547eb745079dac9320b638f5e225cf483cc5cfdda41
action_cf_mwl = cloudflare[cfuser="%(cfemail)s", cftoken="%(cfapikey)s"]
                %(mta)s-whois-lines[name=%(__name__)s, sender="%(sender)s", dest="%(destemail)s", logpath=%(logpath)s, chain="%(chain)s"]

action_cf_v4 = cloudflare-restv4[cfuser="%(cfemail)s", cftoken="%(cfapikey)s"]
               %(mta)s-whois-lines[name=%(__name__)s, sender="%(sender)s", dest="%(destemail)s", logpath=%(logpath)s, chain="%(chain)s"]

RECIDIVE EXAMPLE

Here is an example of how to modify the recidive jail to use the new CloudFlare action. Since the recidive jail bans repeat offenders of any other jails, the ban may or may not be happening because of abuse of web services. The offending IP address needs to be banned on all ports and protocols of the server’s firewall and on CloudFlare to bock access to web services through their servers. Here is how to implement that…

# JAILS
#

# Jail for more extended banning of persistent abusers
# !!! WARNINGS !!!
# 1. Make sure that your loglevel specified in fail2ban.conf/.local
#    is not at DEBUG level -- which might then cause fail2ban to fall into
#    an infinite loop constantly feeding itself with non-informative lines
# 2. Increase dbpurgeage defined in fail2ban.conf to e.g. 648000 (7.5 days)
#    to maintain entries for failed logins for sufficient amount of time
[recidive]
logpath  = /var/log/fail2ban.log
banaction = %(banaction_allports)s
action   = %(banaction)s[name=%(__name__)s, bantime="%(bantime)s", port="%(port)s", protocol="%(protocol)s", chain="%(chain)s"]
           cloudflare-restv4[cfuser="%(cfemail)s", cftoken="%(cfapikey)s"]
           %(mta)s-whois-lines[name=%(__name__)s, sender="%(sender)s", dest="%(destemail)s", logpath=%(logpath)s, chain="%(chain)s"]
bantime  = 604800  ; 1 week
findtime = 172800  ; 2 days
enabled = true

This jail will now perform three separate actions when banning an IP address. The first action bans the IP addresses on all ports and protocols with the server’s firewall. But since access to your web services are proxied through CloudFlare the attacker can still get through to the server’s web sites, so the second action uses the new cloudflare-restv4 action to block web access by the offending IP address. The third action, %(mta)s-whois-lines, sends an e-mail about what triggered the ban through your configured e-mail service and to your configured notification e-mail address.

Don’t forget to restart the Fail2Ban service after these edits…

sudo service fail2ban restart

TESTING

fail2ban-cloudflareVerify the action is working by logging in to your CloudFlare account, select a web site, navigate to the Firewall page, and scroll down to the Access Rules table. If you received a notice from Fail2Ban that a jail configured to use the cloudflare-restv4 action has banned an IP address, you should find it listed as “Banned by Fail2Ban” in the the Access List.

If fail2ban won’t restart after you have edited configuration files, you can try manually starting it with verbose messaging enabled to find out where it is failing as it is starting up.

fail2ban-client -vvv -x start

Once you have fixed all errors in your configuration files, restart it as a service.

fail2ban-client -x stop
service fail2ban start