Zero Trust Access with Cloudflare

Since its inception, remote work has relied on fundamentally having a VPN client installed on a device. A user authenticates at the perimeter and once they’re through, they’re on the network - trusted and free to move (!). This model made sense when applications and users all lived inside the corporate walls but it doesn’t anymore. Applications have moved to the Cloud, users work from anywhere (including the beach) and the trusted internal network has become the single biggest liability in enterprise security - a stolen credential or compromised device and an attacker is inside, able to move laterally toward whatever they’re after to deploy ransomware (network-wide access spreads ransomeware to everything the attacker can reach) and data exfiltration.

This project was created to demonstrate the alternative Zero Trust access model, built with Cloudflare. Instead of placing a user on a network, it connects an authenticated user to one specific application and nothing else. The application is never exposed to the Internet - it sits behind an outbound-only tunnel with no inbound ports to scan or attack. Every request is checked against identity and policy before access is granted (and everyone else is denied by default). The result is secure access to a private resource with no attack surface, no lateral movement and without a VPN in sight.

How it works

〰️

How it works 〰️

Using Cloudflare One for SASE

Cloudflare has a suite of products with their original pitch being to make any internet property faster and more secure without installing hardware or changing code. They started out as a CDN (content delivery network). Instead of every visitor loading a website from one main computer (origin server), the CDN serves copies from a nearby location. This makes sites load faster, reduces load on the origin and blocks cyber attacks. Cloudflare can do this thanks to their global network that sits inline between users and everything they access.

The same inline network is used to power the Cloudflare One platform (their answer to Zero Trust) which we’ll be utilising for this project. It replaces VPNs with a cloud-delivered model.

This website (perrycyber.com) is registered with GoDaddy and hosted on Squarespace (two-provider setup). Adding Cloudflare's tunnel to that domain would have introduced a third provider into the DNS chain with each handoff (GoDaddy → Cloudflare → Squarespace) a point where a misconfiguration could take the site offline or break its SSL. Rather than risk a working site for a test project, I registered a separate domain directly through Cloudflare. Registering at Cloudflare means DNS is managed there natively. Thus, zt-test-brian.com was conceived.

The Cloudflared Connector

Using the cloudflared-windows-amd64.exe binary obtained from here, we rename it and authenticate it to the Cloudflare account, and establish an outbound-only tunnel that publishes the test service through Cloudflare's edge.

1) PowerShell’s Rename-Item function to call it something more manageable

2) Authenticate account and domain. Downloads certificate cert.pm that authorises tunnel to the account

3) Create named tunnel. The JSON file added under \.cloudflared is the tunnel’s key

4) This creates a DNS record so app.zt-test-brian.com points at zt-demo tunnel.

5) Checked for a local web application runtime using node - - version. Node.js not installed but not required because Cloudfalre Tunnel provides a built-in hello_world origin service. No locahost web server, Node.js application or Python HTTP server was needed.

6) Created a config.yml file in the .cloudflare directory. The tunnel ID, credentials file and ingress rule were defined with the service set to the hello_world origin:

7) Attempted to start tunnel. Terminal reported that config.yml was empty because the file contents had not been saved. Going back to save the YAML config in Notepad had the tunnel successfully start.

8) Populated and saved config.yml and the tunnel was started again using cloudflared tunnel run zt-demo. Connectivity pre-checks passed for DNS resolution, UDP, TCP and Cloudflare API reachability, tunnel was successfully established and connected to Cloudflare's edge. The output also confirmed that QUIC would be used as the primary transport protocol.

Now the tunnel’s live. “Congrats! You created a tunnel!” on the hello-world page means the whole path is operational (my machine → outbound tunnel → Cloudflare's edge → app.zt-test-brian.com) with no inbound ports opened on the network.

Putting it all together

If you’re able to access app.zt-test-brian.com, it means I haven’t done a good job. I’ve only defined access to my personal account so I can navigate to the hostname, login and see the “You created a tunnel!” message. If the access policies don’t define the email address or domain that the visitor uses to sign in, they’re met with “Cloudflare sign-in is restricted to members of the account”.

Configuring ZTNA Controls

But right now, anyone on the internet can reach that page. There’s no access policies implemented yet.

This is where the important stuff happens. Users can be defined by

  • Email address or email domain (e.g. only @perrycyber.com users)

  • Identity provider groups (Microsoft Entra ID, Okta, Google, etc.)

  • Multi-factor authentication

  • Device posture checks (managed device, WARP installed, OS compliance)

  • Geographic restrictions (country-based access)

  • IP allowlists and denylists

  • Service tokens for non-human or application access

  • Time-based access controls

  • Default-deny policies with allow, block, bypass, and service-auth action

Displayed after user enters their login details and the access policies don’t allow their identity

Mobile devices aren’t exempt from access policies either

Other domains currently being managed under my Cloudflare account. After purchasing through their website, the test domain is Active

Zero Trust enforcement: the tunnel exposes the app, the Access policy decides who gets in

Private or public?

Private (private destination, routed via Cloudflare Tunnel):

Users access the test app through public hostname app.zt-test-brian.com but the origin itself is never exposed to the Internet. Cloudflare Tunnel brokers an outbound-only connection between Cloudflare’s edge and the private origin using the cloudflared connector, eliminated the need for a public IP address, inbound firewall rules or NAT rules. Access policies are configured through Cloudflare One.

Public (public DNS destination protected by Cloudflare Access):

The app is already internet-accessible and Cloudflare Access would be used as an added authentication layer. It improves seucrity but the origin remains publicly reachable and we all know, if you can reach it, you can breach it. This doesn’t fully demonstrate the Zero Trust principle of hiding infrastructure and verifying identity before granting access.

Navigate to hostname app.zt-test-brian.com

Enter login details, I chose to login via Google

To add access policies, a Zero Trust plan will need to be selected. I went for the free option

Access policy allows me through and I’m met with the generic landing page. I’m in

Previous
Previous

Arturo: An NLU-Powered Ordering Assistant