jahed.dev

Using iptables and ipset to whitelist Cloudflare

One of the major advantages of using Cloudflare is its DNS-level proxy which acts as a shield between clients and your server. However, that doesn't mean no one can go directly to your server. The internet is a public network and everyone has access to everything. All they need is your IP address which is easily guessed.

Once some malicious traffic finds its way into your server, it will flood it with requests, trying to find a hole. First it'll try some common ports like 80, 443 and 22 (HTTP, HTTPS, SSH). Then, if it's bothered, it'll move on to port scanning, trying out every possible port. When it gets a response from a service, like your HTTP server or SSH daemon, it'll try known vulnerabilities or exploit common misconfigurations to get in. Point is, even if it's never getting in, this traffic is a nuisance and ideally you want to reduce it as much as possible.

How do you do that? Well, on Linux, we have iptables. It lets us create filters to accept or drop traffic based on where it came from (source IP address), what it's trying to access (port), how it got there (network interface) and so on.

Here's a simple rule that tells iptables to allow local HTTP requests (i.e. http://127.0.0.1:80) and drop everything else.

-A INPUT -p tcp --dport 80 -i lo -j ACCEPT
-A INPUT -j DROP

Warning: For this specific rule set, if you don't have physical access to the server you'll also be blocked. If you have a static IP, you can add a rule to whitelist your IP address for a specific port. I'll write a separate guide in the future for dealing with dynamic IPs.

Run man ipset for a full list of options.

Now when an outsider makes any request, they won't get a response. As far as they're concerned, your server is either turned off or it doesn't even exist.

Of course, when we run an HTTP server, we do want traffic to come through. And since we're using Cloudflare to proxy our traffic, we only want to open our doors to Cloudflare's servers and no one else.

Cloudflare lists its IP address ranges exactly for this purpose. However, iptables on its own doesn't let you whitelist multiple IP addresses without duplicating a rule for each one.

-A INPUT -p tcp --dport 80 -i lo -j ACCEPT
-A INPUT -s x.x.x.x/r -p tcp --dport 443 -j ACCEPT
-A INPUT -s y.y.y.y/r -p tcp --dport 443 -j ACCEPT
... and so on.
-A INPUT -j DROP

Note: Obviously, replace x.x.x.x/r etc. with actual IP addresses.

There's a more efficient approach using ipset to create a set of IP addresses and --match set to match against it.

First, let's create a new set in ipset.

create cloudflare4 hash:net family inet hashsize 1024 maxelem 65536
add cloudflare4 x.x.x.x/r
add cloudflare4 y.y.y.y/r
... and so on.

Run man ipset for a full list of options.

And then it's a matter of matching that set in iptables with the source IP address of incoming traffic.

-A INPUT -p tcp --dport 80 -i lo -j ACCEPT
-A INPUT -p tcp --dport 443 --match set --match-set cloudflare4 src -j ACCEPT
-A INPUT -j DROP

And that's it! You now have a server which will only accept local HTTP requests and Cloudflare-proxied HTTPS requests.

There are some other things to think about like persisting your iptables and ipset configuration so it still works after reboots and using ip6tables (note the "6") to do the same for IPv6 traffic. But that's all beyond the scope of this guide.

Thanks for reading.