Serving CloudTk through a CloudFlare Tunnel

"I've looked at clouds from both sides now" - Joni Mitchell.

I've set up a spare home PC to serve CloudTk applications. To put this on the net with some degree of security I'm using a free-tier CloudFlare "Zero Trust" tunnel. The only thing I had to pay for was to register a domain, which was very cheap. However there were a few complications which I will try to record here.

Getting CloudTk up and running locally with the TkPool demo that comes with it was easy - the procedure is at https://cloudtk.tcl-lang.org/ .

Setting up a CloudFlare tunnel seems a convenient way to put a home-hosted site online. You don't need to open any incoming ports on your router or firewall. You run the cloudflared process on your machine, which opens an outgoing connection to their "cloud" which then relays incoming connections to your site - https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/ .

Configuring the tunnel can be done two ways, either through the Cloudflare web site, or by editing a local config file. I started doing this through the website, but later found that one of the options I wanted could only be set from the config file. Switching from the one approach to the other was awkward, so I would recommend starting with the local config file.

After bringing up the tunnel I could make a non-local http connection and get to the cloudtk home page on my local server. But when I tried to launch the TkPool demo this failed. After some debugging I found that this was because the code to launch that does a Redirect_Self and this failed because the address being redirected to included the internal port number (the default 8015) but the external connection was on port 80. To get around this I switched to using the standard port numbers, 80 for http & 443 for https, internally as well.

But this raised another problem. I was using Debian Linux, and on Unix-like system ports below 1024 are considered privileged. Traditionally only root could open them. So the usual procedure for programs which needed to use such ports was to start them as root, they would open the privileged port, then switch to running as a less-privileged user to minimise the damage that could be done if the service was hacked. However the tclhttpd which came with CloudTk did not appear to support this method. Jeff Smith then helpfully pointed me to a piece of magic incorporated in recent Linux systems:

setcap 'cap_net_bind_service=+ep' /path/to/tclkit

Running this once as root gives the tclkit executable privilege to open privileged ports like 80 and 443 without needing to run as root - problem solved.

I then switched to https, using a Cloudflare Origin CA certificate installed on tclhttpd. This also had a complication - the certificate was generated for my domain cmacleod.me.uk, but when it's used for communication between tclhttpd and cloudflared it gets rejected because it doesn't match "localhost" which is how cloudflared sees tclhttpd. To get around this I had to set the option originServerName: cmacleod.me.uk in the cloudflared config file.

I had been expecting that CloudTk's use of websockets might be problematic, but once I got everything else properly set up they just worked "out of the box".

Here is the config.yml I ended up with:

tunnel: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
credentials-file: /home/colin/.cloudflared/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx.json
originRequest:
  originServerName: cmacleod.me.uk

ingress:
  - hostname: cmacleod.me.uk
    service: https://localhost
  - service: http_status:404

stevel - 2024-04-09 07:07:04

If you want to run multiple CloudTk instances behind the same tunnel moving to the standard ports isn't an option. One way around it is to alias Redirect_Self to Redirect_To by adding a file containing the following to the TclHttpd custom folder.

interp alias {} Redirect_Self {} Redirect_To

This may have implications if your code relies on the existing Redirect_Self behaviour though.