From 3b07a232cfc7e248f24a5072585acbe8f32471bb Mon Sep 17 00:00:00 2001 From: Hermes Date: Mon, 11 May 2026 18:36:36 +0000 Subject: [PATCH] add cloudflare infrastructure setup guide for tunnels, domains, and token rotation --- methodology/CLOUDFLARE-SETUP.md | 214 ++++++++++++++++++++++++++++++++ 1 file changed, 214 insertions(+) create mode 100644 methodology/CLOUDFLARE-SETUP.md diff --git a/methodology/CLOUDFLARE-SETUP.md b/methodology/CLOUDFLARE-SETUP.md new file mode 100644 index 0000000..000d08a --- /dev/null +++ b/methodology/CLOUDFLARE-SETUP.md @@ -0,0 +1,214 @@ +# Cloudflare Infrastructure Setup + +## Architecture Overview + +``` +Browser ──► Cloudflare (orange cloud) ──► Tunnel ──► cloudflared ──► Traefik:8081 + │ + (tunnel entrypoint) + │ + ┌──────────┴──────────┐ + secureweb tunnel + (direct internet) (via tunnel) + :443 :8081 + LetsEncrypt plain HTTP +``` + +All external traffic goes through Cloudflare Tunnel. Traefik serves as the internal reverse proxy, routing by Host header. Traefik's LetsEncrypt certs exist for LAN/direct access but aren't used externally (Cloudflare edge terminates TLS for visitors). + +## Active Stack + +| Component | Location | Notes | +|---|---|---| +| Tunnel name | `home` | Created at Cloudflare Zero Trust → Networks → Tunnels | +| Tunnel ID | `c29295c5-946a-4ddf-bdfe-7eafcd74faa3` | Visible in dashboard | +| Token | `.env` `TUNNEL_TOKEN=...` | Used by cloudflared to authenticate | +| cloudflared | production-1 Docker container | Runs with `--restart unless-stopped` | +| Traefik | production-1 Docker container | Ports 80, 443, 8080, 8082 | +| Traefik entrypoints | `web` (:80→redirect), `secureweb` (:443, TLS), `tunnel` (:8081), `metrics` (:8082) | | +| Traefik config | `/docker/appdata/traefik/traefik.yaml` | | +| Compose file | `/docker/compose/docker-compose.yaml` | cloudflared service defined but runs standalone due to interpolation issues | +| Traefik labels | Per-service in docker-compose.yaml | Pattern: `entrypoints=tunnel`, `Host(`subdomain.gharbeia.net`)` | + +## 1. Adding a Second Domain + +Example: adding `example.com` alongside `gharbeia.net`. + +### Step 1: Cloudflare DNS + +1. Go to Cloudflare Dashboard → Add site → enter `example.com` +2. Cloudflare scans existing DNS records — verify and continue +3. Change nameservers at your registrar to Cloudflare's +4. Wait for DNS to propagate + +### Step 2: Cloudflare Tunnel — add hostname + +1. **Zero Trust** → **Networks** → **Connectors** → **Cloudflare Tunnels** +2. Select tunnel **home** → **Edit** +3. Click **Add a public hostname** +4. Set: + - **Subdomain:** `*` + - **Domain:** `example.com` + - **Service Type:** HTTP + - **URL:** `traefik:8081` +5. Save + +Cloudflare DNS will automatically create a wildcard CNAME for `*.example.com` pointing to `c29295c5-946a-4ddf-bdfe-7eafcd74faa3.cfargotunnel.com`. + +### Step 3: Traefik — update cert resolver + +Cloudflare wildcard certs only cover `*.gharbeia.net`. For `*.example.com`, either: + +**Option A: Add to Traefik's ACME config** + +In `/docker/appdata/traefik/traefik.yaml`, in `certificatesResolvers.letsencrypt.acme`: +```yaml +dnsChallenge: + provider: cloudflare + resolvers: + - 1.1.1.1:53 + - 1.0.0.1:53 + propagation: + delayBeforeChecks: 120s # increase for wildcards +``` + +Then in `entryPoints.secureweb.http.tls.domains`: +```yaml +domains: + - main: gharbeia.net + sans: + - "*.gharbeia.net" + - main: example.com + sans: + - "*.example.com" +``` + +**Important:** Before ACME DNS challenge can succeed for wildcard domains proxied via Cloudflare, you MUST create a DNS-only placeholder record for `_acme-challenge` to break the wildcard: + +Go to Cloudflare DNS → Add record: +- **Type:** A +- **Name:** `_acme-challenge` +- **Content:** `127.0.0.1` +- **Proxy:** DNS-only (gray cloud) + +Without this, the proxied wildcard CNAME intercepts `_acme-challenge.*` queries and LetsEncrypt can't verify the TXT challenge records. + +**Option B: Skip HTTPS cert for now** + +If the second domain doesn't need Traefik's own cert (i.e., Cloudflare edge certs are sufficient), no Traefik changes needed. The tunnel handles everything. + +### Step 4: Add Traefik labels + +For each service on the new domain, add Traefik labels in docker-compose.yaml: +```yaml +labels: + - traefik.enable=true + - traefik.http.routers.servicename.rule=Host(`subdomain.example.com`) + - traefik.http.routers.servicename.entrypoints=tunnel + - traefik.http.services.servicename.loadbalancer.server.port=3000 +``` + +Then restart the container to pick up labels (compose or docker run depending on env state). + +--- + +## 2. Security Panic — Changing All Tokens and Settings + +### Step 1: Cloudflare API Token + +1. Go to Cloudflare Dashboard → **My Profile** → **API Tokens** +2. **Delete** the old token +3. **Create Token** → **Edit zone DNS** template: + - Permissions: Zone → DNS → Edit + - Zone Resources: Include → Specific zone → `gharbeia.net` + - TTL: No expiration (or set a reasonable one) +4. Copy the new token + +### Step 2: Update .env on production-1 + +```bash +ssh production-1 +# Edit the token +sed -i "s|^CLOUDFLARE_DNS_API_TOKEN=.*|CLOUDFLARE_DNS_API_TOKEN=|" /docker/compose/.env +``` + +### Step 3: Restart Traefik + +Traefik runs standalone (not under compose). Restart it with the new token: + +```bash +docker rm -f traefik +docker run -d --name traefik --restart unless-stopped --network networking \ + -p 80:80 -p 443:443 -p 8080:8080 -p 8082:8082 \ + -e CF_DNS_API_TOKEN="" \ + -e TZ="America/New_York" \ + -v /var/run/docker.sock:/var/run/docker.sock:ro \ + -v /docker/appdata/logs/traefik:/var/log \ + -v /docker/appdata/traefik:/etc/traefik \ + -v /docker/appdata/traefik/letsencrypt:/letsencrypt \ + traefik:latest +``` + +Traefik will automatically renew its LetsEncrypt cert with the new token. + +### Step 4: Cloudflare Tunnel Token + +If you also rotate the tunnel token: + +1. Go to **Zero Trust** → **Networks** → **Tunnels** → **home** +2. Click the three dots → **Recreate token** +3. Copy the new token + +On production-1: +```bash +docker rm -f cloudflared +docker run -d --name cloudflared --restart unless-stopped --network networking \ + -v /docker/appdata/cloudflared:/home/nonroot/.cloudflared \ + cloudflare/cloudflared:latest tunnel --no-autoupdate run --token "" +``` + +Also update the `.env` file: +``` +TUNNEL_TOKEN= +``` + +### Step 5: Verify everything works + +```bash +# Check cloudflared is connected +docker logs cloudflared --tail 5 | grep "Registered tunnel connection" + +# Check Traefik is running and has routes +docker logs traefik --tail 10 | grep "Register" + +# Test end-to-end +curl -sI https://git.gharbeia.net/ | head -5 +# Should return HTTP/2 200 +``` + +### Step 6: Rotate other secrets (if needed) + +Other credentials in `.env` that may need rotation: +- `AUTHENTIK_SECRET_KEY` — generate new: `openssl rand -base64 60 | tr -d '\n'` +- `POSTGRESQL_PASSWORD` — generate new +- `EMAIL_PASSWORD` — Fastmail app password +- `GITEA_TOKEN` — Gitea settings → Applications +- `OPENROUTER_API_KEY` — OpenRouter dashboard + +## Troubleshooting + +### Cert renewal fails with "Invalid format for Authorization header" +The Cloudflare API token in `CF_DNS_API_TOKEN` is wrong or expired. Generate a new one and restart Traefik. + +### Cert renewal stuck on "Waiting for DNS record propagation" +Likely the proxied wildcard CNAME is intercepting `_acme-challenge` subdomain. Add a DNS-only A record for `_acme-challenge` → `127.0.0.1` in Cloudflare DNS to break the wildcard. + +### Tunnel shows 502 Bad Gateway +- Check cloudflared logs: `docker logs cloudflared --tail 10` +- If `connection refused` → Traefik isn't listening on the expected port +- If `tls: unrecognized name` → SNI mismatch (revert to HTTP mode in tunnel config) + +### Adding a new service +1. Add Traefik labels to the service in docker-compose.yaml +2. If the container was started without labels, recreate it with `docker run` including `-l` flags (compose may fail due to .env interpolation issues) +3. The tunnel wildcard already catches `*.gharbeia.net` — no tunnel changes needed