lb: trust the host Caddy's X-Forwarded-Proto
All checks were successful
PR build (required check) / changes (pull_request) Successful in 7s
secrets-guard / encrypted (pull_request) Successful in 5s
PR build (required check) / build-backend (pull_request) Has been skipped
PR build (required check) / build-frontend (pull_request) Has been skipped
shell-lint / shellcheck (pull_request) Successful in 7s
PR build (required check) / validate-observability (pull_request) Has been skipped
PR build (required check) / gate (pull_request) Successful in 2s
All checks were successful
PR build (required check) / changes (pull_request) Successful in 7s
secrets-guard / encrypted (pull_request) Successful in 5s
PR build (required check) / build-backend (pull_request) Has been skipped
PR build (required check) / build-frontend (pull_request) Has been skipped
shell-lint / shellcheck (pull_request) Successful in 7s
PR build (required check) / validate-observability (pull_request) Has been skipped
PR build (required check) / gate (pull_request) Successful in 2s
Every OAuth redirect URI the backend builds is currently http://, whatever scheme the user actually arrived with. 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; the stack LB is the second hop. Caddy preserves an incoming X-Forwarded-* header only when the immediate peer is a trusted proxy, and trusted_proxies was never set here -- so the LB overwrote the header with the scheme of the connection it had just received, which is http. 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. Measured on vps2, same request to each hop: direct to web, XFP=https -> https://thermograph.org/api/v2/... through the LB, XFP=https -> http://thermograph.org/api/v2/... through the PATCHED LB -> https://thermograph.org/api/v2/... through the PATCHED LB, no XFP-> http://thermograph.org/api/v2/... The last line is the point of using trusted_proxies rather than hardcoding `header_up X-Forwarded-Proto https`: the LB still reports the truth when nothing in front of it claims otherwise. private_ranges covers 127.0.0.1/8 and 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, 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.
This commit is contained in:
parent
7b14b9062c
commit
a4bcae5bf9
2 changed files with 39 additions and 0 deletions
|
|
@ -13,6 +13,34 @@
|
|||
{
|
||||
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 {
|
||||
|
|
|
|||
|
|
@ -17,6 +17,17 @@
|
|||
{
|
||||
auto_https off
|
||||
admin off
|
||||
|
||||
# Same reason as ./Caddyfile — see the long note there. Without it this second
|
||||
# Caddy hop overwrites the host Caddy's X-Forwarded-Proto: https with http,
|
||||
# and every OAuth redirect URI the backend builds comes out non-HTTPS, which
|
||||
# Google rejects outright for a web client.
|
||||
#
|
||||
# Beta needs it as much as prod does, and arguably first: beta is where an
|
||||
# OAuth provider change gets tested before it reaches thermograph.org.
|
||||
servers {
|
||||
trusted_proxies static private_ranges
|
||||
}
|
||||
}
|
||||
|
||||
:8137 {
|
||||
|
|
|
|||
Loading…
Reference in a new issue