Limiting access to your site to Cloudflare IP addresses only

Wednesday, October 2, 2019
For those of you that now me, you know that I am very paranoid about security.  I feel that while I know how to make a secure system, it very easy to get it wrong and very hard to get it right.  One of the things that always concerns me is having a lot of people randomly "poking" at my servers and possibly finding errors in my system.

CloudflareI have turned to Cloudflare to help reduce my exposed attack surface.  They have some great services, many of which are available for free.  This particular post will simply be talking about using their Web Application Firewall (WAF).

Cloudflare acts as a Content Distribution Network (CDN) that actually helps to speed up your web-site.  The short version of this is that a visitor connects to an IP address on the Cloudflare network which acts as a caching proxy to connect back to your actual server.  This is how Cloudflare can provide both security and caching.

What happens if someone has your servers actual IP address instead of the one hosted by Cloudflare?  This allows them to circumvent all of the Cloudflare provided security and attack your server directly.

If you are running a Linux server, it is actually easy to restrict incoming connections to your server to only be from trusted Cloudflare addresses.  By closing this bypass you are guaranteeing that you always have the protection of Cloudflare in front of your server.  This also means that you can use additional Cloudflare capabilities to minimize the number of unauthorized requests that come to your server, which also reduces server load.

NOTE:  This script should work with any CDN that provides similar capabilities as Cloudflare does - but I have not tested anybody else.

Here is the script that I use to close this Cloudflare bypass.  Please look for colored keywords to identify areas that you need to customize
#!/bin/sh
# Script taken and modified from https://github.com/Paul-Reed/cloudflare-ufw/blob/master/cloudflare-ufw.sh

# Safety - make sure you are authorized before we do anything
if [ "$(whoami)" != "root" ]; then
   echo ABORT: You must be root to run this script
   exit 1
fi

# Clear out all firewall rues
echo y | ufw reset

# Flush and remove all UFW rules in both the filter and nat tables
/sbin/iptables -F
/sbin/iptables -X
/sbin/iptables -F -t nat
/sbin/iptables -X -t nat

# Safety to make sure that everything is really removed
echo y | /usr/sbin/ufw reset

# Remove backup copies that the reset command generates
rm /etc/ufw/*201?????_*

# Disable the firewall until rules are set and assign default policies
/usr/sbin/ufw disable
/usr/sbin/ufw default deny incoming
/usr/sbin/ufw default allow outgoing

# Check to see if OpenVPN rules have been added to UFW already
#   If the rules are not already there, add rules above to the before.rules file
# NOTE: If you are not using OpenVPN, this block is not needed
if [ $(cat /etc/ufw/before.rules | grep "OpenVPN routing" | wc -l) -eq 0 ];
then
cat < /etc/ufw/before.rules.nat
*nat
:POSTROUTING ACCEPT [0:0]
-A POSTROUTING -s ww.xx.yy.zz/8 -o  -m comment --comment "OpenVPN routing" -j MASQUERADE
COMMIT
EOF
fi
# Merge the new before.rules into the existing before.rules
cat /etc/ufw/before.rules > /etc/ufw/before.rules.orig
cat /etc/ufw/before.rules.nat /etc/ufw/before.rules.orig > /etc/ufw/before.rules
# NOTE: If you are not using OpenVPN, this block is not needed

# Disable the firewall again to make sure all rules are really purged
/usr/sbin/ufw disable
# Enable the new firewall
echo y | /usr/sbin/ufw enable

# Put in some safety rules so you do not get locked out accidentally
/usr/sbin/ufw allow from www.xxx.yyy.zzz/32  to any port 22,80,443 proto tcp comment "Safety - home computers"
/usr/sbin/ufw allow from 127.0.0.1/32      comment "Safety - localhost"
/usr/sbin/ufw allow from www.xxx.yyy.zzz/32  to any port 22,80,443 proto tcp comment "Safety - home router"
/usr/sbin/ufw allow from www.xxx.yyy.zzz/32  to any port 22,80,443 proto tcp comment "Allow VPN users"
/usr/sbin/ufw allow from any   to any port 1194 proto udp  comment "OpenVPN via UDP"
/usr/sbin/ufw deny  from www.xxx.yyy.zzz to 224.0.0.1   comment "Block multi-cast"
echo Deny applied

# Determine working directory
DIR="$(dirname $(readlink -f $0))"
cd $DIR

# Get the authoritative lists of Cloudflare IP addresses
wget https://www.cloudflare.com/ips-v4 -O ips-v4.tmp
wget https://www.cloudflare.com/ips-v6 -O ips-v6.tmp
mv ips-v4.tmp ips-v4
mv ips-v6.tmp ips-v6

# Loop through all of the Cloudflare IP addresses and authorize them
for cfip in `cat ips-v4`; do /usr/sbin/ufw allow from $cfip to any port 443 proto tcp comment "Allow Cloudflare via TCP"; done
for cfip in `cat ips-v6`; do /usr/sbin/ufw allow from $cfip to any port 443 proto tcp comment "Allow Cloudflare via TCP"; done

#NOTE: You can repeat the above lines to add other rules or change the allowed ports

# Enable the firewall rules
echo y | ufw enable

# Display the nat table to ensure rules are properly added
/sbin/iptables -t nat -L -n
After that, I simply added this script to my system's crontab for the root user - since this script requires permission to run. I do not know how often Cloudflare IP addresses update so I set this to run once per day and on a system reboot.
@reboot               /path/to/script.sh | mail -s "resetUFW results - reboot" me@some_domain.com
   0  4   *   *   *   /path/to/script.sh | mail -s "resetUFW results" me@some_domain.com
Everything has been working great ever since.