diff --git a/CLAUDE.md b/CLAUDE.md index 0692e30..5869174 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -173,6 +173,37 @@ service builds its own site block instead of using this helper (e.g. `services/asterisk-digital-ocean.sh` does, deliberately, see its own comment for why), put its auth block first there too. +**`forward_auth` to a remote Authelia over a scheme-qualified URL needs +explicit `header_up` pins.** A bare `forward_auth authelia:9091` (Authelia on +the same Docker network, one hop) is fine relying on Caddy's default +`X-Forwarded-*` headers. But `forward_auth https://auth.example.com { ... }` +(Authelia on a *different* machine, reached over its own public domain+TLS — +see `services/asterisk-digital-ocean.sh`'s remote-Authelia prompt) is a +second Caddy hop: Caddy rewrites the outgoing request's `Host` header to +`auth.example.com` so the remote Caddy can route/SNI-match it, and without an +override `X-Forwarded-Host` picks up that rewritten value instead of the +original site's host. Confirmed live: Authelia evaluated *every* protected +domain as if the request were for `auth.example.com` itself (which typically +has `policy: bypass` in `access_control.rules` so its own login portal isn't +gated behind itself) — so every domain behind the remote instance silently +passed through with no 2FA prompt, regardless of that domain's own policy. +Fix: pin the forwarded headers to the original request explicitly instead of +trusting Caddy's default derivation: + +``` +forward_auth https://auth.example.com { + uri /api/authz/forward-auth + copy_headers Remote-User Remote-Groups Remote-Name Remote-Email + header_up X-Forwarded-Method {method} + header_up X-Forwarded-Proto {scheme} + header_up X-Forwarded-Host {host} + header_up X-Forwarded-Uri {uri} +} +``` + +This only affects the remote-Authelia path — same-machine `authelia:9091` +snippets (`services/authelia.sh`) are a single hop and don't need it. + Sets two out-params (not `local` — read them after the call returns) so the caller can tell whether Caddy actually ended up fronting the service: diff --git a/services/asterisk-digital-ocean.sh b/services/asterisk-digital-ocean.sh index 480fb23..235c85d 100755 --- a/services/asterisk-digital-ocean.sh +++ b/services/asterisk-digital-ocean.sh @@ -584,9 +584,27 @@ ENV local _remote_authelia="" prompt_text " Remote Authelia address — a bare host:port over a private network (e.g. a NetBird mesh IP:9091), or a full https:// URL if it's on its own public domain+TLS:" "" _remote_authelia if [[ -n "$_remote_authelia" ]]; then + # header_up lines are required here (unlike the local + # "authelia:9091" snippet in services/authelia.sh) because + # this upstream is reached over a second Caddy hop when + # given as a scheme-qualified URL (https://auth.example.com). + # Caddy rewrites the outgoing request's Host header to that + # upstream host so the remote Caddy can route/SNI-match it — + # and without an explicit override, X-Forwarded-Host picks up + # that rewritten value instead of the original site's host. + # Confirmed live: Authelia was evaluating every request as + # if it were for auth.example.com itself (which has + # policy: bypass in access_control.rules), so every domain + # silently passed through with no 2FA prompt regardless of + # its own policy. Pinning these to the original request's + # values fixes it regardless of hop count. EXTRA_BLOCK=" forward_auth ${_remote_authelia} { uri /api/authz/forward-auth copy_headers Remote-User Remote-Groups Remote-Name Remote-Email + header_up X-Forwarded-Method {method} + header_up X-Forwarded-Proto {scheme} + header_up X-Forwarded-Host {host} + header_up X-Forwarded-Uri {uri} }" sed -i "s/^WEB_ADMIN_AUTH_DISABLED=.*/WEB_ADMIN_AUTH_DISABLED=true/" .env log_info "Using remote Authelia at ${_remote_authelia}."