# The loopback LB bridge's own config — NOT the host Caddy (that one still # terminates TLS for thermograph.org and proxies to 127.0.0.1:8137 exactly as # before; it needs no change for the stack cutover). # # This Caddy runs as a PLAIN container (deploy-stack.sh manages it) because # only plain containers can bind a specific host IP: Swarm port configs # publish on 0.0.0.0 (routing mesh or host mode alike), which would expose # the plaintext app un-fronted — hazard #6. It joins the stack's attachable # overlay and proxies to the service VIPs; Swarm's VIP round-robins across # however many web replicas the autoscaler is running, so this bridge never # needs to know the replica count. { auto_https off admin off # WITHOUT THIS, EVERY OAUTH REDIRECT URI THE BACKEND BUILDS IS http://. # # There are two Caddy hops in front of the app: the HOST Caddy terminates TLS # and sets X-Forwarded-Proto: https, then proxies to 127.0.0.1:8137 over plain # HTTP; this LB is the second hop. Caddy only preserves an incoming # X-Forwarded-* header when the immediate peer is a TRUSTED proxy — otherwise # it overwrites the header with the scheme of the connection it just received, # which here is http. So the app saw http:// no matter how the user arrived. # # accounts/oauth.py:_redirect_uri builds the callback from x-forwarded-proto, # and that URI must match what is registered in the provider's console. # Discord tolerates the http:// form; GOOGLE REJECTS a non-HTTPS redirect URI # for a web client outright, so Google sign-in could not work at all until # this was fixed — independently of whether the credentials were configured. # # Measured on vps2 before the fix, same request to each hop: # direct to web, XFP=https -> https://thermograph.org/api/v2/... # through this LB, XFP=https -> http://thermograph.org/api/v2/... # # private_ranges covers 127.0.0.1/8 and the Docker bridge ranges, which is # where the host Caddy reaches this container from. Nothing outside the box # can connect here — the LB is bound to loopback precisely so it cannot be # reached un-fronted (hazard #6, above) — so trusting the local peer does not # widen anything: the only party that can set these headers is the host Caddy. servers { trusted_proxies static private_ranges } } :8137 { reverse_proxy web:8137 { # Fail fast to the client if the VIP has no healthy task; Swarm's own # task healthchecks handle ejecting dead replicas from the VIP. lb_try_duration 5s } } :8080 { reverse_proxy frontend:8080 { lb_try_duration 5s } }