Firstly, apologies for the hiatus. The last few years has been insanely busy for me on a personal and professional level. Rest assured, the tinkering has continued – its just that the blogging has been deprioritised instead.

Changes to my infrastructure include Unifi wifi throughout, a 24U rack, and multiple other additions including UPS, etc. I’ve also moved my website to be protected via the Cloudflare CDN network, given i’ve been using it for DNS management for years now and it provides a good number of free-to-use features such as DDoS protection, layer 5-and-below firewall functionality blocking naughty countries (etc), and other cool features I wont go into for now.

If you truly want to set your website to be served solely via Cloudflare (proxied) rather than clients using Cloudflare for a DNS lookup to get your IP and then access your website via your public IP (thus increasing your attack surface), you’ll want to ensure only Cloudflare CDN IP addresses can access your website via HTTP/S.

To do this, feel free to plagiarise my setup below.

First, you’ll need to ensure your web server(s) have ipset and wget installed:

apt-get -y install ipset wget

Once dependencies are installed, create a file and store it wherever you like (in my demo lab i simply placed it in /root for testing purposes).

root@cflb:/# nano /root/ips-v4

Within this file, you’ll need to copy the IPv4 addresses of Cloudflare which can be found here. As of today (06/03/2020, the list looked as below):

root@cflb:/# cat /root/ips-v4
173.245.48.0/20
103.21.244.0/22
103.22.200.0/22
103.31.4.0/22
141.101.64.0/18
108.162.192.0/18
190.93.240.0/20
188.114.96.0/20
197.234.240.0/22
198.41.128.0/17
162.158.0.0/15
104.16.0.0/12
172.64.0.0/13
131.0.72.0/22

Once you’ve created the list, you’ll need to create a script that takes that list and creates an ipset using it:

root@cflb:/# touch /etc/zones/ips-v4
root@cflb:/# nano /etc/allow-cf.sh

In this file, add the following:

# Create the ipset list
ipset -N cloudflare-ips hash:net

# remove any old list that might exist from previous runs of this script
rm /etc/zones/ips-v4

# Pull the latest IP set for Cloudflare
cd /etc/zones/
wget -P . https://www.cloudflare.com/ips-v4

# Add each IP address from the downloaded list into the ipset 'cloudflare-ips'
for i in $(cat /etc/zones/ips-v4 ); do ipset -A cloudflare-ips $i; done

This creates an ipset called ‘cloudflare-ips’, pulls the IPv4 addresses down into a file at /etc/zones/ips-v4, and iterates through the file adding each subnet into the ipset. We will then ACCEPT/REJECT using this ipset.

Next, lets create our iptables rules that leverage this new set. In this example, I’ve created a file containing our iptables rules called /root/iptables-rules:

root@cflb:/# nano /root/iptables-rules

Within this file, you’ll need to add rules similar to the below:

# Get Cloudflare ipset built
/etc/allow-cf.sh
# First rules
 iptables -F
 iptables -N LOGGING
 iptables -I INPUT 1 -s 127.0.0.1 -j ACCEPT -m comment --comment "allow anything that comes from lo"
 iptables -I INPUT 2 -m state --state ESTABLISHED,RELATED -j ACCEPT -m comment --comment "allow anything established/related"
 iptables -I INPUT 3 -p tcp -m set --match-set cloudflare-ips src -m multiport --dports 80,443 -j ACCEPT -m comment --comment "Allow all Cloudflare IP's"
 iptables -I INPUT 4 -s YOUR_IP_HERE -d SERVER_IP_HERE -p tcp --dport 22 -j ACCEPT -m comment --comment "Home to servers via SSH"
 iptables -I INPUT 5 -j LOGGING -m comment --comment "logging for everything else"
# Drop everything if it hasnt met an ACCEPT
 iptables -A LOGGING -m limit --limit 2/min -j LOG --log-prefix "IPTables-Dropped: " --log-level 4
 iptables -A LOGGING -j DROP -m comment --comment "drop everything else after logging it"

Next, lets get chmod’ing:

root@cflb:/# chmod +x /root/iptables-rules
root@cflb:/# chmod +x /etc/allow-cf.sh

Finally, run the script and voila, we’re protected:

root@cflb:/# bash /root/iptables-rules

If we look at the rules, you can now see the following:

root@cflb:/# iptables -nL --lin
Chain INPUT (policy ACCEPT)
num  target     prot opt source               destination
1    ACCEPT     all  --  127.0.0.1            0.0.0.0/0            /* allow anything that comes from lo */
2    ACCEPT     all  --  0.0.0.0/0            0.0.0.0/0            state RELATED,ESTABLISHED /* allow anything established/related */
3    ACCEPT     tcp  --  0.0.0.0/0            0.0.0.0/0            match-set cloudflare-ips src multiport dports 80,443 /* Allow all Cloudflare IP's */
4    ACCEPT     tcp  --  MY_IP        SERVER_IP       tcp dpt:22 /* Home to servers via SSH */
5    LOGGING    all  --  0.0.0.0/0            0.0.0.0/0            /* logging for everything else */

Chain FORWARD (policy ACCEPT)
num  target     prot opt source               destination

Chain OUTPUT (policy ACCEPT)
num  target     prot opt source               destination

Chain LOGGING (1 references)
num  target     prot opt source               destination
1    LOG        all  --  0.0.0.0/0            0.0.0.0/0            limit: avg 2/min burst 5 LOG flags 0 level 4 prefix "IPTables-Dropped: "
2    DROP       all  --  0.0.0.0/0            0.0.0.0/0            /* drop everything else after logging it */

Any issues, hit me up via the contact form.