Files
stack/hw/TUNNEL-SETUP.md
kert 59eb56f659
Some checks failed
CI / skinny-install (aco) (push) Successful in 1m18s
CI / skinny-install (api) (push) Successful in 40s
CI / skinny-install (bcda) (push) Successful in 35s
CI / skinny-install (bib) (push) Successful in 38s
CI / skinny-install (cli) (push) Successful in 46s
CI / skinny-install (conf) (push) Successful in 36s
CI / skinny-install (opps) (push) Successful in 38s
CI / skinny-install (pfs) (push) Successful in 47s
CI / skinny-install (rex) (push) Successful in 35s
Infra CI / notebooks (push) Successful in 3m17s
CI / lint-test (push) Failing after 3m30s
CI / skinny-install (bls) (push) Successful in 34s
CI / skinny-install (ccw) (push) Successful in 45s
CI / skinny-install (cms) (push) Successful in 32s
CI / skinny-install (perf) (push) Successful in 43s
Deploy / build-scan-report (push) Has been cancelled
Infra CI / docs (push) Failing after 20s
Infra CI / api (push) Successful in 16s
Infra CI / mc (push) Successful in 12s
Package Supply Chain / pkg-supply-chain (push) Successful in 1m27s
Infra CI / zotero (push) Successful in 6m10s
chore: hw provisioning, test coverage, deps
2026-04-09 22:26:31 -04:00

7.2 KiB

Cloudflare Tunnel + Certs Setup for rig.fhirworx.io

Architecture

Internet                         Your Server (rig)
                                 ┌──────────────────────────────┐
Users ──→ Cloudflare Edge        │                              │
          (TLS termination)      │  cloudflared ←──outbound──→ CF Edge
          *.rig.fhirworx.io     │    │                         │
                                 │    ├─→ rustfs:9000  (S3 API)│
                                 │    └─→ rustfs:9001  (Console)│
                                 │                              │
          Cloudflare WARP        │  warp-svc (DNS/Zero Trust)  │
                                 └──────────────────────────────┘

All connections are OUTBOUND from your server. No inbound ports needed except SSH (22) for YubiKey management.

Step 1: Cloudflare Dashboard — DNS

  1. Log into https://dash.cloudflare.com
  2. Select fhirworx.io zone
  3. Go to DNS → Records
  4. You do NOT need to add A/AAAA records manually — the tunnel creates CNAME records automatically. But verify the zone exists and is active.

Step 2: Create the Tunnel

  1. Go to https://one.dash.cloudflare.com (Zero Trust dashboard)
  2. Networks → Tunnels → Create a tunnel
  3. Tunnel name: rig
  4. Choose Cloudflared connector
  5. You'll get a tunnel token — it looks like:
    eyJhIjoiNGY4...very-long-base64-string
    
  6. Copy this token — you'll need it in Step 4

Step 3: Configure Tunnel Routes (Public Hostnames)

Still in the tunnel config, add these public hostnames:

Public Hostname Service Notes
s3.rig.fhirworx.io http://rustfs:9000 S3 API endpoint
console.rig.fhirworx.io http://rustfs:9001 Web console
rig.fhirworx.io http://rustfs:9000 Default/root domain → S3

For each route:

  • Type: HTTP (not HTTPS — cloudflared handles the tunnel encryption, the local connection to the container is plaintext over Docker network)
  • TLS → Origin Server Name: leave blank
  • No TLS Verify: Yes (local traffic, no cert needed)

Optional: Add SSH access through tunnel

Public Hostname Service Notes
ssh.rig.fhirworx.io ssh://localhost:22 Browser SSH or cloudflared access

For SSH through tunnel, on the Access tab:

  • Create an Access Application for ssh.rig.fhirworx.io
  • Add an Access Policy (e.g., email allowlist, one-time PIN)
  • This gives you browser-based SSH as a backup to direct SSH

Step 4: Install the Token on Your Server

After yubikey-unlock, edit the env file:

rw
nano /opt/rustfs/.env

Replace PASTE_YOUR_TOKEN_HERE with the actual token from Step 2:

TUNNEL_TOKEN=eyJhIjoiNGY4...

Save and:

ro
cd /opt/rustfs && docker compose up -d

Verify the tunnel connects:

docker logs cloudflared
# Should show: "Connection registered" and "Tunnel is connected"

Step 5: SSL/TLS Mode

  1. In Cloudflare dashboard → fhirworx.io → SSL/TLS → Overview
  2. Set mode to: Full
    • NOT "Full (Strict)" — unless you set up an origin cert (see below)
    • NOT "Flexible" — that's insecure
    • With tunnels, "Full" is fine because the tunnel itself is encrypted

If you want "Full (Strict)" (belt AND suspenders), do Step 6.

Step 6: Origin Certificate (Optional)

Only needed if:

  • You want Full (Strict) SSL mode, OR
  • You want direct HTTPS access on your LAN to rig.fhirworx.io

Option A: Cloudflare Origin CA (simplest, 15-year validity)

  1. Dashboard → fhirworx.io → SSL/TLS → Origin Server
  2. Create Certificate
  3. Settings:
    • Key type: ECDSA
    • Hostnames: rig.fhirworx.io, *.rig.fhirworx.io
    • Validity: 15 years
  4. Copy the PEM certificate and private key

On the server:

rw

# Paste the certificate
nano /opt/certs/origin.pem

# Paste the private key
nano /opt/certs/origin-key.pem

chmod 644 /opt/certs/origin.pem
chmod 600 /opt/certs/origin-key.pem

ro

These certs are ONLY trusted by Cloudflare's edge — browsers connecting directly will see a cert warning. That's expected and correct for origin certs.

Option B: Let's Encrypt (trusted everywhere, auto-renews)

Use this if you also want trusted HTTPS directly on your LAN.

  1. Create a Cloudflare API token:

  2. On the server:

rw

# Save the API token
cat > /opt/certs/cf-credentials.ini << EOF
dns_cloudflare_api_token = YOUR_TOKEN_HERE
EOF
chmod 600 /opt/certs/cf-credentials.ini

# Request the cert
apk add certbot-dns-cloudflare

certbot certonly \
  --dns-cloudflare \
  --dns-cloudflare-credentials /opt/certs/cf-credentials.ini \
  -d "rig.fhirworx.io" \
  -d "*.rig.fhirworx.io" \
  --preferred-challenges dns-01 \
  --non-interactive \
  --agree-tos \
  -m you@fhirworx.io

ro

Certs land at:

  • /etc/letsencrypt/live/rig.fhirworx.io/fullchain.pem
  • /etc/letsencrypt/live/rig.fhirworx.io/privkey.pem

Auto-renewal cron (add after setup):

rw
echo "0 3 * * * root mount -o remount,rw / && certbot renew --quiet && mount -o remount,ro /" >> /etc/crontabs/root
ro

Step 7: Cloudflare WARP (Zero Trust DNS/VPN)

The WARP container gives you:

  • DNS-over-HTTPS for all container traffic
  • Option to route through Cloudflare's network
  • Zero Trust device posture (if configured)

First run enrollment:

docker exec -it warp-svc warp-cli registration new
docker exec -it warp-svc warp-cli connect

To use WARP as the default DNS for the host:

rw
echo "nameserver 127.0.0.1" > /etc/resolv.conf
ro

Step 8: Verify Everything

# Tunnel status
docker logs cloudflared

# Test S3 endpoint externally
curl -I https://s3.rig.fhirworx.io
# Should return 403 (no auth) or 200 with health check

# Test console
curl -I https://console.rig.fhirworx.io
# Should return 200 or 302 redirect to login

# Test from server locally
curl http://127.0.0.1:9000/minio/health/live
curl http://127.0.0.1:9001

# WARP status
docker exec warp-svc warp-cli status

DNS Records (auto-created by tunnel)

After the tunnel connects, Cloudflare automatically creates:

s3.rig.fhirworx.io       CNAME  <tunnel-id>.cfargotunnel.com
console.rig.fhirworx.io  CNAME  <tunnel-id>.cfargotunnel.com
rig.fhirworx.io          CNAME  <tunnel-id>.cfargotunnel.com

You don't need to create these manually.

Summary: What Needs a Cert and What Doesn't

Connection Encrypted By Cert Needed?
User → Cloudflare Edge Cloudflare Universal SSL No (automatic)
Cloudflare Edge → cloudflared Tunnel encryption (built-in) No
cloudflared → RustFS container Docker internal network (localhost) No
Direct LAN → RustFS Nothing (HTTP) or your own cert (HTTPS) Only if you want LAN HTTPS

For your setup: you need ZERO certificates. The tunnel handles everything. Origin certs are a nice-to-have for defense in depth or direct LAN access.