v0.312.0: every public name answers only over https; HSTS one day (R-1033, P1)
gates / gates (push) Successful in 48s
gates / gates (push) Successful in 48s
forwarded.yml: felhom-forwarded is now a chain [felhom-https-only (redirectScheme https, 301 when the trusted tunnel says X-Forwarded-Proto: http), felhom-forwarded-headers (the clean-up + HSTS 86400, no subdomains, no preload)] plus a priority-1 catch-all router for unknown names over http. traefik.yml unchanged; hot-loaded. Tests red-proofed. Evidence: felhom.eu documentation/audits/https-only-2026-10-14/.
This commit is contained in:
@@ -1005,7 +1005,7 @@ The controller stands up its own base stack — **traefik** (reverse proxy), **c
|
||||
|
||||
cloudflared is only deployed when a tunnel token is configured. **Triggers**: a first-boot goroutine (after stack init) and an unconditional call on every `system-health` tick (self-heal — cheap when healthy thanks to the idempotency). `monitor.EffectiveProtected` mirrors the cloudflared condition so a LAN-only node (no tunnel token) doesn't report a perpetual "protected container not running" FAIL.
|
||||
|
||||
**The box tells visitors apart (v0.286.0, R-753, `09` §3 decision 63).** cloudflared sits ALONE on the `felhom-tunnel` network (`172.16.253.0/29`, docker's own allocation confined to `.4/30`) at the fixed address `172.16.253.2`; traefik joins it at `.3`. traefik's `websecure` entrypoint trusts forwarded headers from `172.16.253.2/32` only, so an app behind the tunnel receives `X-Forwarded-For: <client-written…>, <real visitor>, 172.16.253.2` and a LAN visitor's own address; every other peer's chain is dropped as before. Every websecure request passes the entrypoint middleware `felhom-forwarded@file` (`dynamic/forwarded.yml`, `RenderForwardedHeaders`), which removes the headers a client could write a host, path or address into (`X-Forwarded-Host/-Uri/-Method/-Prefix`, `Forwarded`, `True-Client-Ip`, `X-Client-Ip`, …) and fixes `X-Forwarded-Port: 443` — measured: Cloudflare passes a client's `X-Forwarded-Host`/`-Port` and appends to a client's `X-Forwarded-For`, and strips a client's `X-Real-IP`. **Readers take the visitor from the RIGHT, never the leftmost.** The controller's own rule is `internal/web/clientaddr.go` (`clientIP`): believed only when the TCP peer is traefik (docker DNS); the rightmost entry is the hop traefik saw; that hop being the tunnel address → `CF-Connecting-IP` (Cloudflare's edge refuses a client-sent one). Catalog apps that read the LEFTMOST entry carry a router middleware that removes the chain (`<router>-xff`). If the network cannot be made, traefik keeps trusting nobody and cloudflared stays on `traefik-public`. Design + measurements: `felhom.eu/documentation/audits/visitors-2026-10-01/A/`.
|
||||
**The box tells visitors apart (v0.286.0, R-753, `09` §3 decision 63).** cloudflared sits ALONE on the `felhom-tunnel` network (`172.16.253.0/29`, docker's own allocation confined to `.4/30`) at the fixed address `172.16.253.2`; traefik joins it at `.3`. traefik's `websecure` entrypoint trusts forwarded headers from `172.16.253.2/32` only, so an app behind the tunnel receives `X-Forwarded-For: <client-written…>, <real visitor>, 172.16.253.2` and a LAN visitor's own address; every other peer's chain is dropped as before. Every websecure request passes the entrypoint middleware `felhom-forwarded@file` (`dynamic/forwarded.yml`, `RenderForwardedHeaders`), which removes the headers a client could write a host, path or address into (`X-Forwarded-Host/-Uri/-Method/-Prefix`, `Forwarded`, `True-Client-Ip`, `X-Client-Ip`, …) and fixes `X-Forwarded-Port: 443` — measured: Cloudflare passes a client's `X-Forwarded-Host`/`-Port` and appends to a client's `X-Forwarded-For`, and strips a client's `X-Real-IP`. **Readers take the visitor from the RIGHT, never the leftmost.** The controller's own rule is `internal/web/clientaddr.go` (`clientIP`): believed only when the TCP peer is traefik (docker DNS); the rightmost entry is the hop traefik saw; that hop being the tunnel address → `CF-Connecting-IP` (Cloudflare's edge refuses a client-sent one). Catalog apps that read the LEFTMOST entry carry a router middleware that removes the chain (`<router>-xff`). **https only (v0.312.0, R-1033, P1):** `felhom-forwarded` is a chain — `felhom-https-only` (301 to the same https URL when the trusted tunnel says `X-Forwarded-Proto: http`) then `felhom-forwarded-headers` (the clean-up + HSTS one day, no subdomains, no preload); the router `felhom-https-only-catchall` (priority 1, only `X-Forwarded-Proto: http`) redirects names no app route matches. An https or LAN visitor is unchanged. If the network cannot be made, traefik keeps trusting nobody and cloudflared stays on `traefik-public`. Design + measurements: `felhom.eu/documentation/audits/visitors-2026-10-01/A/`.
|
||||
|
||||
**Backend transports — self-signed HTTPS backends (`ensureServersTransports` → `RenderServersTransports`, v0.83.0).** Traefik talks **HTTP** to app backends by default, which is correct for every catalog app that serves plain HTTP. The exception is an app that serves its **own self-signed TLS** on the internal docker bridge (the first is Crafty, HTTPS-only on `:8443`): Traefik must speak `https` to it *and* skip verifying a per-container self-signed cert (no CA to verify against; the hop never leaves the host). `insecureSkipVerify` is **not settable via Docker labels** in traefik v3 — it must live in static/file config — so `EnsureBaseStack` writes a file-provider dynamic file `dynamic/serverstransports.yml` defining a **named** transport `insecure-skip-verify` (write-if-changed; hot-loaded by the file watcher). An app **opts in per-service** via two catalog labels — `loadbalancer.server.scheme=https` + `loadbalancer.serverstransport=insecure-skip-verify@file` (the `@file` suffix is the cross-provider reference). Backend verification stays the default (ON) for every other service — there is deliberately **no global `insecureSkipVerify`** in `traefik.yml`. This write runs **outside** `ensureTraefik` (which early-returns when traefik is already up) so an established node still materializes the file on a self-heal tick.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user