System-Wide Deployment with Cloudflare Tunnel + oauth2-proxy

Reference deployment for exposing kaged through a Cloudflare Tunnel with oauth2-proxy authentication.

System-Wide Deployment: Cloudflare Tunnel + oauth2-proxy

This guide covers the system-wide deployment mode where kaged runs as a system service behind a Cloudflare Tunnel, with oauth2-proxy handling authentication via the header contract defined in ADR-0007.

Architecture

operator device
    │
    ▼
Cloudflare Tunnel (TLS termination)
    │
    ▼
oauth2-proxy (OIDC auth, header injection)
    │  injects: X-Kaged-User-Id, X-Kaged-Auth-Nonce, X-Kaged-User-Email
    ▼
kaged daemon (127.0.0.1:7777)

The daemon validates the nonce and trusts the identity headers. It implements zero OIDC/OAuth logic itself.

Prerequisites

  • A Linux host (ARM64 or x86_64) with systemd
  • A domain managed by Cloudflare
  • cloudflared installed and authenticated (cloudflared tunnel login)
  • An OIDC provider (Google, GitHub, Authentik, Keycloak, etc.) with an OAuth2 client registered
  • kaged binary at /usr/local/bin/kaged

Step 1: Create the kaged system user

sudo useradd --system --home-dir /var/lib/kaged --shell /usr/sbin/nologin kaged
sudo mkdir -p /var/lib/kaged
sudo chown kaged:kaged /var/lib/kaged

Step 2: Install the daemon config

Create /etc/kaged/config.toml:

[daemon]
bind = "127.0.0.1:7777"
home = "/var/lib/kaged"

[auth]
mode = "secure"

[storage]
url = "sqlite:///var/lib/kaged/kaged.db"

[sandbox]
mode = "enabled"
default_seccomp = "default"

[logging]
operational = "stderr"
audit = "file:/var/lib/kaged/audit.log"
level = "info"

[plugins]
dir = "/var/lib/kaged/plugins"
enabled = []

[ui]
serve = true

Create /etc/kaged/env with the shared nonce:

# Generate a fresh nonce at each deploy
KAGED_AUTH_SIDECAR_NONCE=$(openssl rand -hex 32)
echo "KAGED_AUTH_SIDECAR_NONCE=${KAGED_AUTH_SIDECAR_NONCE}" | sudo tee /etc/kaged/env
sudo chmod 600 /etc/kaged/env
sudo chown kaged:kaged /etc/kaged/env

Step 3: Install the systemd unit

Copy the system-wide unit from the examples:

sudo cp examples/deployment/systemd/kaged.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now kaged

Verify the daemon is running:

sudo systemctl status kaged
curl http://127.0.0.1:7777/healthz

Step 4: Configure oauth2-proxy

Copy the reference config from examples/deployment/cloudflare-oauth2-proxy/oauth2-proxy.cfg and edit:

  1. Set client_id and client_secret from your OIDC provider
  2. Generate a cookie_secret (python3 -c "import secrets; print(secrets.token_urlsafe(32))")
  3. Set redirect_url to https://your-kaged-domain/oauth2/callback
  4. Set email_domains to restrict access

Install oauth2-proxy as a systemd service pointing at the config file. See the compose.yaml in the same directory for a Docker-based alternative.

Critical: The oauth2-proxy must inject X-Kaged-Auth-Nonce with the same value as KAGED_AUTH_SIDECAR_NONCE in /etc/kaged/env. Both processes must agree on the nonce.

Step 5: Configure the Cloudflare Tunnel

Create a tunnel and route it to oauth2-proxy:

cloudflared tunnel create kaged
cloudflared tunnel route dns kaged kaged.yourdomain.com

Create ~/.cloudflared/config.yml:

tunnel: kaged
credentials-file: ~/.cloudflared/<tunnel-id>.json

ingress:
  - hostname: kaged.yourdomain.com
    service: http://127.0.0.1:4180
  - service: http_status:404

Run as a system service:

sudo cloudflared service install

Step 6: Open the UI

Navigate to https://kaged.yourdomain.com. You'll be redirected through the OIDC login flow. After authentication, oauth2-proxy injects the identity headers and forwards the request to kaged.

WebSocket support

oauth2-proxy supports WebSocket forwarding by default. kaged uses WebSockets for:

  • Agent output streaming (live token relay)
  • PTY terminal sessions (project terminals)

No additional oauth2-proxy configuration is needed.

Rotating the nonce

The nonce should be rotated on a schedule or after any suspected compromise:

# Generate new nonce
NEW_NONCE=$(openssl rand -hex 32)
echo "KAGED_AUTH_SIDECAR_NONCE=${NEW_NONCE}" | sudo tee /etc/kaged/env

# Update oauth2-proxy config with the same nonce
# (mechanism depends on your deployment)

# Restart both services
sudo systemctl restart kaged
sudo systemctl restart oauth2-proxy

Alternative sidecars

The daemon doesn't care which sidecar you use, as long as the header contract is met:

  • Pomerium: Use Pomerium's policy engine to set custom headers per route
  • Authelia: Use Authelia's forward-auth mode with header injection
  • Tailscale Funnel + tsidp: Map Tailscale identity headers to X-Kaged-*
  • traefik-forward-auth: Same pattern as oauth2-proxy

The contract is always the same four headers. See ADR-0007 for the full specification.