plc

Today I learned about Cloudflare Tunnels, which I am now using to manage the network for this blog and my nextcloud instance.

I had actually considered using it earlier, but the documentation and product description is so abhorrent that it was easier for me to write a terraform script and systemd service that would provision an EC2 instance and use it as an ssh jumphost.

I hate it with a passion when companies insist only describing what their products are supposedly good for, rather than explaining what they actually are or do. (Prime offender: OS-integrated network file sharing. “Share this folder on the network” means absolutely nothing unless you also tell me what protocol its using and which clients are available on different platforms)

But for some reason (I think https://lemmy.world/post/21778874 was what sparked it, even though the post itself is only tangentially related, or maybe it was that my monthly AWS bill had ballooned to a whopping €5.50. Always was a miser.) I started reconsidering my tunneling infrastructure.

I shall try not to get myself started on ranting about why this is necessary in the first place.. something something CG-NAT... something something IPv6... something something running for office..

So I started looking, and rediscovered ngrok. Only to find that the base offering is at $18/month just to not have the tunnel endpoint change with every restart, which in comparison made my homerolled EC2 setup look like a bargain.

Then I revisited cloudflare and took the time to learn what it is that they actually do.

So it turns out that, for my intents and purposes, what Cloudflare does (and the tunnels specifically) is:

  • A nameserver (with a much better configuration than what my duck-pond registrar one.com offers)
  • A reverse http proxy which does both caching and TLS infrastructure.
  • A tunneling daemon that runs on my origin server and accesses my sites locally, setting proxy headers making this observable to the server.
  • ... And the free offering incudes both dns and tunnels with unlimited bandwidth, making it (financially) the best offering so far.

There is a bit of kit needed to get this working, and I won't detail it all. Just mention that I had to do quite a bit of debugging and fiddling, even to figure out when I had in fact arrived at a working configuration.

The Nixos wiki page shows reasonably nicely how to enable cloudflared.

What took a bit more digging was:

  • How to actually get my tunnel endpoint to point my DNS records at. It turns out you have to make a CNAME record pointing to <tunnel-id>.cfargotunnel.com and (apparently?) make sure the record is “proxied through cloudflare”. In hindsight this makes some sense, since Cloudflare is also managing TLS certificates, which it can only do in tandem with DNS control, as it is essentially an authorized man-in-the-middle.
  • How to get the reverse proxy to play nice with my existing Nginx setup. (see the final nix expression. There is some silliness going because I was already using Let's Encrypt to have Nginx manage TLS).

All-together:

  • At your registrar, delegate to the Cloudflare nameservers (Reasonably obvious with a bit of poking around in the cloudflare admin UI). Nothing will work even the slightest until this is complete.

  • Setup the tunnel on the origin server: (From nixos wiki:)

    $ cloudflared tunnel login <the-token-you-see-in-dashboard>
    $ cloudflared tunnel create ConvenientTunnelName # This creates the credentials json file!
    
  • Augment configuration.nix in the following manner:

    services.cloudflared = {
    enable = true;
    tunnels = {
      "<tunnel-id>" = {
        # Put the credentials file somewhere convenient to you. (should be readable by user:group 'cloudflared:cloudflared'.)
        credentialsFile = "/var/secrets/tunnel.json";
        default = "http_status:404";
        ingress = {
          "blog.philsas.one" = {
            service = "https://localhost:443";
            # See https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/configure-tunnels/origin-configuration/
            originRequest = {
              # Nginx already has TLS setup. This makes cloudflared accept that the server identifies itself as 'blog.philsas.one'.
              originServerName =  "blog.philsas.one";
              # The tunnel cannot know what the service would like to be called.
              # To the tunnel daemon they are at 'http://localhost', but to the internet they are 'http://something.philsas.one'.
              # Because this setup is on top of Nginx we have to retain the original Host header to let it do virtual host routing.
              httpHostHeader = "blog.philsas.one";
            };
          };
          # Rinse and repeat for all services.
          "other-service.philsas.one" = {
            service = "https://localhost:443";
            originRequest = {
              originServerName = "other-service.philsas.one";
              httpHostHeader = "other-service.philsas.one";
            };
          };
        };
      };
    };
    };