Only allow Cloudflare IPs for Discourse server

All of our servers, site and Discourse community (discussions.ftw.in) sit behind Cloudflare.

Can we set at the Discourse server level to only allow Cloudflare IPs and deny all other IP traffic?

We’ve been under a DDoS layer 7 attack and learned our main site IP was exposed.
To fix this, we updated our .htaccess on our main site server to only allow traffic from Cloudflare IPs and deny all other traffic. This does not let our origin server IP get exposed.

We are not exactly sure how to do the same above for our Discourse server at the server level. In my searches, I’ve seen cloudflare.template.yml mentioned and that it’s set to make sure we are seeing our user IPs, but I’m not sure.

Thanks in advance!

This does not matter; once your IP is exposed it can be attacked and you must either

  1. Move to a new IP

  2. Get your upstream network provider to block the unwanted traffic (it is usually not https)

After that you must be very careful to not expose your IP at any time.

3 Likes

Layer 7 attacks can be blocked in nginx by blocking everything but CF.

@Brock_Busby
Here are some pointers, don’t know how to do it in Docker config though.

Make sure you are using an email provider, otherwise the emails will reveal your IP.

1 Like

This is perfect @michaeld. Thank you! And yes, we are on SparkPost. Hopefully after this change it will only expose the Cloudflare IP.

And correct. We will move to a new IP after denying only Cloudflare IPs and not allowing any other IPs.

Thanks all

Hey everyone,

I am reading this thread and we need to know this information as well. We are running Discourse using the docker install on an Ubuntu server hosted on AWS.

How would we whitelist Cloudflare IPs with this build? I just ssh’d in and did not see any obvious NGINX paths within the container.

Any help would be greatly appreciated.

Best,

Sunny

1 Like

Look at how ./templates/cloudflare.template.yml downloads the IP list and constructs set_real_ip_from from that list.

So you’d copy or modify that file to also generate allow directives, and add a deny all;.

5 Likes

Also not being familiar with nginx and yaml, I followed your instructions @riking and created a copy named ./templates/cloudflare.allowdeny.yml with the following code.

Can you please verify this will do the job? Much appreciated.

run:
  - file:
      path: /tmp/add-cloudflare-ips
      chmod: +x
      contents: |
        #!/bin/bash -e
        # Download list of CloudFlare ips
        wget https://www.cloudflare.com/ips-v4/ -O - > /tmp/cloudflare-ips
        wget https://www.cloudflare.com/ips-v6/ -O - >> /tmp/cloudflare-ips
        # Make into nginx commands and escape for inclusion into sed append command
        CONTENTS=$(</tmp/cloudflare-ips sed 's/^/allow /' | sed 's/$/;/' | tr '\n' '\\' | sed 's/\\/\\n/g')

        echo CloudFlare IPs:
        echo $(echo | sed "/^/a $CONTENTS")
        echo "deny all;"
        # Insert into discourse.conf
        sed -i "/sendfile on;/a $CONTENTS\nreal_ip_header CF-Connecting-IP;" /etc/nginx/conf.d/discourse.conf
        # Clean up
        rm /tmp/cloudflare-ips

  - exec: "/tmp/add-cloudflare-ips"
  - exec: "rm /tmp/add-cloudflare-ips"

Well, you’re going to need the other rules too, so we might as well only download the list once. You missed replacing real_ip_header with deny all. Here’s a combined version:

run:
  - file:
      path: /tmp/add-cloudflare-ips
      chmod: +x
      contents: |
        #!/bin/bash -e
        # Download list of CloudFlare ips
        wget https://www.cloudflare.com/ips-v4/ -O - > /tmp/cloudflare-ips
        wget https://www.cloudflare.com/ips-v6/ -O - >> /tmp/cloudflare-ips
        # Make into nginx commands and escape for inclusion into sed append command
        CONTENTS1=$(</tmp/cloudflare-ips sed 's/^/allow /' | sed 's/$/;/' | tr '\n' '\\' | sed 's/\\/\\n/g')
        CONTENTS2=$(</tmp/cloudflare-ips sed 's/^/set_real_ip_from /' | sed 's/$/;/' | tr '\n' '\\' | sed 's/\\/\\n/g')

        echo CloudFlare IPs:
        echo $(echo | sed "/^/a $CONTENTS1")

        # Insert into discourse.conf
        sed -i "/sendfile on;/a deny all;$CONTENTS1\n $CONTENTS2\nreal_ip_header CF-Connecting-IP;" /etc/nginx/conf.d/discourse.conf
        # Clean up
        rm /tmp/cloudflare-ips

  - exec: "/tmp/add-cloudflare-ips"
  - exec: "rm /tmp/add-cloudflare-ips"

Save that to your cloudflare.allowdeny and remove the stock version from your app.yml. And then test it, of course :slight_smile:

5 Likes

This is great thanks @riking, but since you maintain cloudflare.template.yml I would not like to remove or mess with it, and let it download twice. …or does it download for every user or just when the container is built?

Nah, the file hasn’t changed in a while and I don’t think it will anytime soon. The primary priority here is to not break git pull, hence having a copy.

Yes, it’s only when the container is built; but the fastest code is the code that doesn’t run. Why download twice when you can do it once.

1 Like

Got it. You rock. We will test and report back!

We are getting a 403 Forbidden on our sandbox which means the “deny all” is working. :wink:
I can confirm we are proxied through Cloudflare.

Two things @riking

  1. Should “deny all;” come after $CONTENTS1, since that is the list of allow?
  2. I noticed you output $CONTENTS1 twice, but $CONTENTS2 only once

The echo is printed to the console during rebuild and isn’t saved anywhere.

I don’t know about the order; I remember seeing someone have deny all first followed by allows so that’s what I did.

We couldn’t get this to work. Kept getting a 403. We tried moving “deny all;” around and we even tried breaking up the two files and still wouldn’t work.

We are on AWS and realized we can manage using the security group to allow and deny. This is working for us right now.

Appreciate the help though. I think a nice-to-have for those on Cloudflare is an option to turn on to whitelist only Cloudflare IPs through the method above. Perhaps an admin checkbox. It makes a lot of sense and great addition for security. And honestly a nice selling point!

Next thing we need to solve though… our IP is still showing up in emails. We are using SparkPost as our email vendor so we are scratching our heads why it’s still showing.

1 Like

they probably got your ip by posting an ip logger
my post mentioned here where i had same problem Disable discourse from crawling links

I don’t use AWS, but block access to our instance using firewall rules to filter out IPs, not on an approved list. Not sure what AWS offers in terms of firewall support.

I am using PFSense, and it can pull a list of IPs from a URL at an interval. I have scripts that create the approved IP list for PFsense to pull. IPs on the list make it to the forum, IPs not on the list are redirected to a page explaining the block.

I use this same technique to block access to Atlassian Confluence instance. Since it’s a firewall rule you can block any type of traffic you want, to any destination you want. Very flexible.

4 Likes