Refuse anyone except CDN caching server to access my server?

(Nota Xiao) #1

To hide the IP of my server, I am using Cloudflare CDN. However, one can scan IPs to find out the real IP of my site (for example, he can use curl -H "Host: mydomain.com" [MY_REAL_IP] to detect if an IP is related to my site). To prevent such scanning, I plan to refuse anyone except Cloudflare to access the 80/443 port of my server. So this is my Caddyfile:

mydomain.com {
    # ...
    ipfilter / {
        rule allow
        ip 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 2400:cb00::/32 2606:4700::/32 2803:f800::/32 2405:b500::/32 2405:8100::/32 2a06:98c0::/29 2c0f:f248::/32
        # from https://www.cloudflare.com/ips/
    }
}

However, this didn’t work. Visiting the website in normal way (with browser) will be refused, only to get a 403 Forbidden. Then I tried this:

mydomain.com {
    # ...
    realip {
        from cloudflare
    }
    ipfilter / {
        rule allow
        ip 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 2400:cb00::/32 2606:4700::/32 2803:f800::/32 2405:b500::/32 2405:8100::/32 2a06:98c0::/29 2c0f:f248::/32
        # from https://www.cloudflare.com/ips/
    }
}

It still didn’t work. What should I do now?

(Matthew Fay) #2

Hi @Nota,

We could troubleshoot this, but first, I’d be remiss not to ask whether you’d considered using Authenticated Origin Pulls instead and requiring Cloudflare to present a certificate to validate itself on connection?

You can probably also consider swapping to a Cloudflare origin certificate to use for your HTTPS and just stop serving HTTP from your origin, because Cloudflare shouldn’t use it.

1 Like
(Nota Xiao) #3

Thanks for the advice. I have read some about Authenticated Origin Pulls and feel that it may solve my problem. I enabled it in Cloudflare and added this to my Caddyfile:

tls {
    clients /path/origin-pull-ca.pem
}

But curl -H "Host: mydomain.com" [MY_REAL_IP] -L could still get the content of my website with no error. Did I make any mistake?

(Matthew Fay) #4

That’s where the second part of my comment comes in. You can’t do client verification for non-SSL connections - no certificates are changing hands.

You’ll definitely want to go HTTPS-only. Start by prepending all your site labels with https:// - Caddy will know not to serve them over HTTP.

You can leave it there - Caddy will still be able to validate LetsEncrypt certificates over HTTP without necessarily serving the site on it. But again, my recommendation is to download the origin certificate from Cloudflare and use it for your HTTPS. It will break validation for regular clients - the certificate provided by Cloudflare is only trusted by Cloudflare - but you shouldn’t have any regular clients, so this is actually a plus. It’s also usable for up to 15 years, which is fine because you can tell Cloudflare to revoke it at any time.

With a HTTPS-only origin, requiring a client certificate only Cloudflare can provide, and presenting a server certificate only valid to Cloudflare, your origin server will be about as obscure and secure as it can get in the orange cloud.

(Nota Xiao) #5

You’ll definitely want to go HTTPS-only. Start by prepending all your site labels with https:// - Caddy will know not to serve them over HTTP.

I prepended https:// but Caddy still serves the HTTP connections: Caddy’s output shows that it serves both http://mydomain.com and https://mydomain.com. The document seems to indicate that setting the scheme to https will also enable HTTP connection:

https://example.com      # Host: example.com; Ports: 80->443

So I don’t think this is an effective way to disable HTTP connections :thinking:
By the way, I think using the origin certificate from Cloudflare makes little sense. Although it’s not trusted by anyone except Cloudflare, if an attacker really wants to scan IPs to find out my real IP, he can simply ignore the certificate errors. Therefore the Cloudflare origin certificate doesn’t seem to prevent clients from accessing my server directly. Is my understanding correct?

(Matthew Fay) #6

Huh, looks like I was wrong on that front. Automatic HTTPS puts up a HTTP listener even if you specify HTTPS.

Cloudflare origin certificate lets you break Automatic HTTPS, stopping this behaviour (so https://example.com will only serve HTTPS and not HTTP). Alternately, you can just firewall port 80 entirely, ignore Caddy and allow no connections.

By itself, it doesn’t stop attackers - that’s what requiring Authenticated Origin Pulls will do.

Doing both is just an exercise in “breaking” the protocol in as many ways as possible for anyone other than Cloudflare - defense in depth.

(Nota Xiao) #7

Thanks a lot, that makes sense! Now I believe my website is safe enough :laughing:

At last, I’d like to explain something – not very important, just don’t want to make others confused:
Before using the origin certificate and disabling HTTP, the client certificate verification was already working well. In my previous reply, I said curl -H "Host: mydomain.com" [MY_REAL_IP] -L will get the content successfully, that’s because curl followed the 301 redirect (HTTP to HTTPS) and finally reached Cloudflare server. The HTTP connection is served by my server and the HTTPS connection is served by Cloudflare, so there’s no client certificate verification happened indeed.
So I added my server’s real IP to hosts to make the HTTPS connection handled by my server, too. Then visit my site in browser will show a ERR_BAD_SSL_CLIENT_AUTH_CERT error, which means client certificate verification works.

Now, with HTTPS-only, client certificate verification and server certificate, I did some test:
Add the real IP to hosts. Run curl http://mydomain.com -L -i -v, it reports a 404 Not Found (because the HTTP site is not served). Run curl https://mydomain.com -L -i -v, it says the certificate cannot be trusted. Run curl https://mydomain.com -L -i -v -k to ignore the untrusted certificate, error still occured due to client certificate verification. Ahh, quite ideal :relaxed:

1 Like
(Matthew Fay) #8

Short of completely firewalling your entire server and using Argo Tunnel, this is indeed about as far as you can go to stop people from using your origin server instead of going through Cloudflare :smiley:

(Nota Xiao) #9

I am still curious about why IP whitelist doesn’t work :thinking:
It looks like the ipfilter plugin will always use the real client IP, while in other directives/plugins I can choose from proxy IP or client IP with the realip plugin. Is it a bug of ipfilter?

(Matthew Fay) #10

Where does this assertion come from? From your initial post, it seems like it didn’t work with or without realip?

My first step would be to make a new Caddyfile to test this with just ipfilter and test against local IPs to see if the plugin actually functions as expected.

(Nota Xiao) #11

ipfilter works properly in the past. Actually, I have used it for a long time (before I started to use Cloudflare) to stop anyone from accessing critical pages (like WordPress login page), and it meets my needs.
I set up a new site with CDN enabled to test plugins just a moment ago. When I enabled realip and ipfilter with the config in my first post, visit the site will get a 403 Forbidden, and my real client IP was been written in the access log. Then I disabled realip, but still get a 403 Forbidden. This time, the IPs of CloudFlare servers were in the log. What puzzles me is these IPs are in the allow field of ipfilter.:thinking:

(Matthew Fay) #12

This makes it seem like the directive is pretty broken.

Try on something you know shouldn’t fail, like allowing private IPs, then test locally. If it’s still failing, we know things are busted and it’s not something to do with Cloudflare or realip.

(Nota Xiao) #13

I can’t reproduce it locally. It seems the problem will only happen when the website is behind a proxy (like CDN). I will ask ipfilter author for further help.

Update:
I found a strict option in ipfilter document. It’s false by default, which will make the plugin always use the address in the X-Forwarded-For request header. Under this circumstance, realip will be ignored. When setting it to true, ipfilter will use the address realip provides, and that can solve the problem.