r/CloudFlare: Can I secure public tunnel endpoint so only certain IP can connect?

r/CloudFlare: Can I secure public tunnel endpoint so only certain IP can connect?
💡
This article archives a conversation, which took place in a subreddit post (original source linked below) and to which I contributed a solution or answer (with the u/MasterofSynapse handle), in a Q&A format.

Original Reddit post: https://www.reddit.com/r/CloudFlare/comments/x3bvd2/can_i_secure_public_tunnel_endpoint_so_only/

Question

Hi, I have a proprietary client device that needs to speak directly to a server I host (via URL or IP). I made a CF tunnel in the server LAN so it would point to `mypublicaddress.tld` and I can indeed access it flawlessly on that device from another location which is what I want. The problem is that I don't want _everyone_ in the world to be able to access it, just me. The device in question is a black box sort of situation so I can't run WARP on it and I can't make it log in via CF Access to the server (or any form of additional OAuth2 proxy form of authentication either, I can only put server IP/URL, login and password on it - that's it). Is there any way I could force CF tunnel to only serve it to my second location IP and deny access to the rest of the world?

Answer

The easiest way is a Cloudflare Access policy targeting the FQDN you created for the Tunnel application. And in that policy you can specify a bypass action that is filtered to the locations WAN IP. So everyone else would get a login prompt but the location can just access it.

However, public hostnames only support web traffic, so HTTP-type packages. And it only reacts to requests on port 80 and 443. If your client needs other ports then a public hostname is not the way to go. WARP and Tunnel with private endpoints can transport any type of traffic, but you need an authenticated entry point to make that work.

Comment 1 on Answer

I love you, it worked! I didn't know about bypass option!

My response to comment 1

Please, not for that ;) Good to hear my instructions helped.

Out of curiosity, which IdP did you connect to ZT? Did you leave it at the default "Request PIN" option? That could potentially be a security vulnerability because email is easily broken.

Comment 1.1 on Answer

Haha, sorry for the emotions, I just started with CF and were little overwhelmed with options and the security model in general. I spend literally hours tinkering with various rules and other ideas (I even made a RPi box with WARP that proxied the service locally via ssh tunnel but that's not ideal) yet somehow the "bypass" option was in my blind spot all of the time so your reply was such a relief 😅. I have Access connected to my AzureAD but I just locked this service out from other locations anyway so it just gives the standard "cloudflare forbidden access" site for everyone else instead.

BTW - my public IP in the client device location rotates every week or so and I'm about to make a script that updates the CF rule on the dynamic dns change trigger, which is not a problem, but are you aware of a leaner way to this with just CF stack? The only other way that seem viable at first glance is the external verification with a CF worker that checks if resolved ddns FQDN matches the client query IP but that seems non trivial to do for me right now and I'm not even sure if it's possible to do it like this.

My response to comment 1.1

All good. And Azure AD is fine, since you can apply Conditional Access policies on top.

The only good way to tackle the IP change is to use the Cloudflare API:

https://developers.cloudflare.com/cloudflare-one/api-terraform/

https://api.cloudflare.com/#access-policies-update-an-access-policy

Then just write a script that gets the current public IP and updates the bypass policy with the (new) value. Run that once every six hours and it should be fine. To optimize the API calls, save the current IP in a local file and only if a new IP gets issued, update the policy.

Comment 1.2 on Answer

That's exactly what I was going for. Thank you for the direct links 🙏