Sunday, February 18, 2024

Running a Cloudflare Zero-Trust Tunnel as a Systemd Container Service

I'm running a Cloudflare Zero Trust tunnel for inbound access to a remote office.  For a site served by Zero-Trust networking, there is no exposed inbound listener. Instead, a tunnel is initiated outbound from the site to the Cloudflare network service. 

I recently learned of Quadlets, a way to run this service as a software container on Red Hat derived Linux systems such as CentOS, Fedora and Fedora CoreOS that systemd as their init process and podman for container management. 

The tunnel is created by running a process on a host inside the destination network. This process connects out to the Cloudflare infrastructure which can then route traffic down the tunnel from connected clients.

Cloudflare distributes a single binary for each platform that creates the tunnel uplink and then carries and routes the inbound traffic. That binary, cloudflared, must be executed as a service on one or more hosts on the destination network. When running a tunnel on a host using the binary, one must define a system service. On most Linux systems today, system services are managed by systemd. However, while Cloudflare provides the binary, they do not provide a systemd service definition. If you want to run the cloudflared as a service you must create the service file.

Cloudflare also offers the cloudflared as a software container at docker.io/cloudflare/cloudflared.

To create a tunnel daemon on a suitable host you only need to create two files. The first is the container definition for Podman. The other is a sysconfig file that defines the TUNNEL_TOKEN environment variable used to identify your tunnel configuration.

Quadlet container definitions look very similar to systemd service files (because, of course, they are derived from them).  The file below must be placed at
/etc/containers/systemd/cloudflare-tunnel.container on the server host.

--- cloudflare-tunnel.container ---
[Unit]
Description=Cloudflare Tunnel Daemon
After=network-onlone.target

[Container]
EnvironmentFile=/etc/sysconfig/cloudflare-tunnel
Image=docker.io/cloudflare/cloudflared
Exec=tunnel --no-autoupdate run

[Install]
WantedBy=multi-user.target default.target
---

The other file is placed at /etc/sysconfig/cloudflare-tunnel as indicated by the EnvironmentFile value in the container file.


--- cloudflare-tunnel ---

TUNNEL_TOKEN=<your tunnel token>

---

The tunnel token is defined when you create the tunnel on the Cloudflare Zero-Trust Network dashboard. Replace the marker with your token string.

With those two files defined all that remains is to load the container spec into systemd and start the service.  All container specs are, by definition, enabled, so you don't need to enable/disable the service.

$ sudo systemctl daemon-reload

$ sudo systemctl start cloudflare-tunnel

$ sudo systemctl status cloudflare-tunnel

Then check your Cloudflare tunnel dashboard to confirm that the tunnel is indeed up.

NOTE: It's a really good idea, when creating network (vs single host) tunnels to run at least two copies on different servers within the destination network.  This creates redundancy and allows you to work on either tunnel box without (ok with LESS) risk of losing connectivity while you work.