add cloudflare infrastructure setup guide for tunnels, domains, and token rotation
This commit is contained in:
214
methodology/CLOUDFLARE-SETUP.md
Normal file
214
methodology/CLOUDFLARE-SETUP.md
Normal file
@@ -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=<new-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="<new-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 "<new-token>"
|
||||||
|
```
|
||||||
|
|
||||||
|
Also update the `.env` file:
|
||||||
|
```
|
||||||
|
TUNNEL_TOKEN=<new-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
|
||||||
Reference in New Issue
Block a user