Merge pull request #192 from outis1one/claude/asterisk-digital-ocean-w22kk8

Claude/asterisk digital ocean w22kk8
This commit is contained in:
Outis
2026-07-20 18:40:18 -04:00
committed by GitHub
4 changed files with 106 additions and 3 deletions
+45
View File
@@ -159,6 +159,51 @@ reloads Caddy. No-ops silently if Caddy isn't installed. The fourth argument
is an optional string inserted verbatim inside the Caddy site block (use it is an optional string inserted verbatim inside the Caddy site block (use it
for `import authelia` or custom matchers). for `import authelia` or custom matchers).
The function places that block **before** `reverse_proxy` in the generated
site block — don't reorder this. `forward_auth` (what `import authelia`
expands to) is the same directive family as `reverse_proxy` internally, and
Caddy doesn't reorder repeats of the same directive within a block; it runs
them in the order they're written. `reverse_proxy` written first would
handle and terminate every request immediately, making an auth check
written after it dead code that never runs — full bypass regardless of what
the auth server's own access-control rules say. Confirmed live: this was
the actual cause of a "Caddy proxies fine but Authelia never prompts for
login" bug, on a site block that otherwise looked completely correct. If a
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 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: caller can tell whether Caddy actually ended up fronting the service:
+8 -1
View File
@@ -573,6 +573,14 @@ configure_caddy_for_service() {
# $SERVICE_NAME # $SERVICE_NAME
${SERVICE_DOMAIN} { ${SERVICE_DOMAIN} {
# Auth (if any) must come before reverse_proxy — forward_auth is the
# same directive family as reverse_proxy internally, and Caddy doesn't
# reorder repeats of the same directive within a block; it runs them in
# the order they're written. With reverse_proxy first, it would handle
# and terminate every request immediately, so an auth check written
# after it would be dead code that never runs — full bypass regardless
# of what the auth server's own rules say.
${EXTRA_CONFIG}
reverse_proxy ${_BLOCK_UPSTREAM} reverse_proxy ${_BLOCK_UPSTREAM}
# Security headers # Security headers
@@ -588,7 +596,6 @@ ${SERVICE_DOMAIN} {
output file /var/log/caddy/${SERVICE_DOMAIN}.log output file /var/log/caddy/${SERVICE_DOMAIN}.log
format json format json
} }
${EXTRA_CONFIG}
} }
CADDY_BLOCK CADDY_BLOCK
)" )"
+39 -1
View File
@@ -584,9 +584,40 @@ ENV
local _remote_authelia="" 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 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 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.
#
# X-Forwarded-Host uses a literal domain, NOT the {host}
# placeholder. Confirmed live: {host} still evaluated to
# the upstream's own hostname (auth.example.com) rather
# than the original site's — Caddy appears to rewrite the
# outgoing request's Host to the upstream target before
# header_up placeholders are resolved for a scheme-
# qualified upstream, so {host} echoes back the already-
# rewritten value instead of the original client-facing
# host. Since this site block only ever serves one domain
# (DOMAIN_NAME), hardcoding it sidesteps the ambiguity
# entirely instead of depending on Caddy's internal
# header-mutation ordering.
EXTRA_BLOCK=" forward_auth ${_remote_authelia} { EXTRA_BLOCK=" forward_auth ${_remote_authelia} {
uri /api/authz/forward-auth uri /api/authz/forward-auth
copy_headers Remote-User Remote-Groups Remote-Name Remote-Email 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 ${DOMAIN_NAME}
header_up X-Forwarded-Uri {uri}
}" }"
sed -i "s/^WEB_ADMIN_AUTH_DISABLED=.*/WEB_ADMIN_AUTH_DISABLED=true/" .env sed -i "s/^WEB_ADMIN_AUTH_DISABLED=.*/WEB_ADMIN_AUTH_DISABLED=true/" .env
log_info "Using remote Authelia at ${_remote_authelia}." log_info "Using remote Authelia at ${_remote_authelia}."
@@ -628,6 +659,14 @@ ENV
# Asterisk Web Admin # Asterisk Web Admin
${DOMAIN_NAME} { ${DOMAIN_NAME} {
# Auth (if any) must come before reverse_proxy — forward_auth is the
# same directive family as reverse_proxy internally, and Caddy doesn't
# reorder repeats of the same directive within a block; it runs them in
# the order they're written. With reverse_proxy first, it would handle
# and terminate every request immediately, so an auth check written
# after it would be dead code that never runs — full bypass regardless
# of what the auth server's own rules say.
${EXTRA_BLOCK}
reverse_proxy ${_PROXY_TARGET} reverse_proxy ${_PROXY_TARGET}
header { header {
@@ -641,7 +680,6 @@ ${DOMAIN_NAME} {
output file /var/log/caddy/${DOMAIN_NAME}.log output file /var/log/caddy/${DOMAIN_NAME}.log
format json format json
} }
${EXTRA_BLOCK}
} }
CADDY_BLOCK CADDY_BLOCK
)" )"
+14 -1
View File
@@ -414,7 +414,20 @@ SNIPPET_EOF
# ── Authelia login portal ────────────────────────────────────────────────────── # ── Authelia login portal ──────────────────────────────────────────────────────
auth.${AUTHELIA_DOMAIN} { auth.${AUTHELIA_DOMAIN} {
reverse_proxy authelia:9091 # header_up pins X-Forwarded-Host to whatever the client actually sent.
# Without it, Caddy's reverse_proxy recomputes X-Forwarded-Host from its
# own incoming request (always auth.${AUTHELIA_DOMAIN} itself) and
# overwrites the value a forward_auth caller (e.g. a remote site's
# "forward_auth https://auth.${AUTHELIA_DOMAIN}" block, see
# services/asterisk-digital-ocean.sh) set for its own domain. Confirmed
# live: every forward-auth check evaluated as if it were for
# auth.${AUTHELIA_DOMAIN} itself (which has policy: bypass in
# access_control.rules so its own login portal isn't gated behind
# itself), so every domain behind it silently passed through with no
# 2FA prompt regardless of that domain's own policy.
reverse_proxy authelia:9091 {
header_up X-Forwarded-Host {http.request.header.X-Forwarded-Host}
}
log { log {
output file /var/log/caddy/auth.log output file /var/log/caddy/auth.log
} }