lb: trust the host Caddy's X-Forwarded-Proto #145
No reviewers
Labels
No labels
Compat/Breaking
Kind/Bug
Kind/Documentation
Kind/Enhancement
Kind/Feature
Kind/Security
Kind/Testing
Priority
Critical
Priority
High
Priority
Low
Priority
Medium
Reviewed
Confirmed
Reviewed
Duplicate
Reviewed
Invalid
Reviewed
Won't Fix
Status
Abandoned
Status
Blocked
Status
Need More Info
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: Jinemi/thermograph#145
Loading…
Reference in a new issue
No description provided.
Delete branch "fix/lb-forwarded-proto"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
⚠️ Do not merge-and-deploy before adding the https:// redirect URI to the Discord app
This change alters what the backend sends providers. Today it sends
http://thermograph.org/api/v2/discord/link/callback; after this it sends thehttps://form. If the Discord developer console only has thehttp://URI registered, Discord sign-in breaks on the next deploy. Both can coexist — add thehttps://one first, deploy, then remove thehttp://one later. Same for beta.The bug
Every OAuth redirect URI the backend builds is
http://, whatever scheme the user actually arrived with.Prod is two Caddy hops: the host Caddy terminates TLS and sets
X-Forwarded-Proto: https, then proxies to127.0.0.1:8137over plain HTTP; the stack LB is the second hop. Caddy preserves an incomingX-Forwarded-*header only when the immediate peer is a trusted proxy, andtrusted_proxieswas never set here — so the LB overwrote the header with the scheme of the connection it had just received.accounts/oauth.py:_redirect_uribuilds the callback fromx-forwarded-proto, and that URI must match what is registered in the provider's console.Google rejects a non-HTTPS redirect URI for a web client outright. So
THERMOGRAPH_GOOGLE_CLIENT_ID/_SECRETcould not have worked even once configured — Google sign-in shipped to prod in #122 and/api/v2/oauth/configreportsgoogle: enabled=falsetoday, but this would have been the next wall. Discord tolerates thehttp://form, which is why nobody noticed.Measured on vps2
Same request to each hop:
web,XFP: httpshttps://thermograph.org/api/v2/...XFP: httpshttp://thermograph.org/api/v2/...XFP: httpshttps://thermograph.org/api/v2/...http://thermograph.org/api/v2/...Verified by running a throwaway
caddy:2-alpineonthermograph_internalwith this config, on a spare loopback port, against the livewebservice;caddy validatepasses; container removed afterwards.That last row is why this uses
trusted_proxiesrather than hardcodingheader_up X-Forwarded-Proto https— the LB still reports the truth when nothing in front of it claims otherwise, which keepsSTACK_TEST=1rehearsals honest.private_rangescovers127.0.0.1/8and the Docker bridge ranges, which is where the host Caddy reaches this container from. The LB binds loopback only, precisely so it cannot be reached un-fronted (hazard #6), so the only party that can set these headers is the host Caddy — trusting the local peer widens nothing.Applied to beta's LB too: beta is where a provider change gets tested before it reaches thermograph.org, so it needs the same behaviour or the test is not a test.
Takes effect when
deploy-stack.shrecreates the LB container, not oninfra-sync.a4bcae5bf9to3993b7552f