Fail2Ban action for CloudFlare REST API V4

Update: Kazimer Corp’s Fail2Ban action code as described in this article was selected as the new CloudFlare action for Fail2Ban.  It has since been modified by other developers. This article is here only for historical reference.

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.

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.  This 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. The POST data will set 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.  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 -I@@ 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 from CloudFlare is then piped into a single line Python script.  This extracts the firewall rule ID from the first result 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. Create a new action file and copy this new action shown below to 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, look at mod_cloudflare to make sure your log files are reporting the correct client IP address. Otherwise the following ignoreip setting results 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 for any jail, the ban may be happening because abuse other than your web server. The offending IP address needs to be banned on all ports and protocols of the server’s firewall and on CloudFlare to block 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 your web server.  To prevent this, 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.

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.  Then select a web site.  Then 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.  Use 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