diff --git a/.env.example b/.env.example index 046f09e..44c2e2c 100644 --- a/.env.example +++ b/.env.example @@ -7,8 +7,8 @@ # Pin your image versions. Bump to current stable when you upgrade -- # check https://github.com/authelia/authelia/releases and # https://github.com/crazy-max/docker-fail2ban/releases. -AUTHELIA_VERSION=4.39 -FAIL2BAN_VERSION=latest +AUTHELIA_VERSION=4.39.19 +FAIL2BAN_VERSION=1.1.0-r0 # Used by both containers for log timestamps. Set to your IANA zone. TZ=America/New_York diff --git a/README.md b/README.md index cf6c8c6..d19e6e8 100644 --- a/README.md +++ b/README.md @@ -120,16 +120,42 @@ docker compose up -d docker compose logs -f authelia # expect "Authelia is listening on ..." ``` +## Which sites go behind Authelia? + +Authelia is opt-in per site. The goal is one login (Authelia, with 2FA) +across everything that *can* use it -- and zero double-prompts for things +that already authenticate themselves and can't be retrofitted. + +| Case | App has built-in auth? | Switchable to proxy auth? | What to do | +|------|-----------------------|---------------------------|------------| +| 1 | No | n/a | Gate with Authelia. Use `two_factor` for anything that controls hardware. | +| 2 | Yes | Yes (Authelia, Authentik, oauth2_proxy headers) | Disable the app's login form, point it at Authelia headers, gate with Authelia. Single login. | +| 3 | Yes | No | Don't involve Authelia. Plain `reverse_proxy` in Caddy. The app handles its own login. | + +Concretely, in this household: + +- **`doorbell.example.com`** (Pi PTT page) -- case 1. No app auth. Authelia + is the only gate. `two_factor`. +- **`cam.example.com`** (Frigate UI) -- case 2. Frigate 0.14+ supports + proxy auth, so disable Frigate's login form and let Authelia drive both + the auth and the role mapping. Single login covers Frigate too. +- **router admin / NAS UI / odd one-offs** -- case 3 territory. Plain + `reverse_proxy`, no `import authelia`, no Authelia rule. + +Default policy in `configuration.yml` is `deny`, so a domain with no rule +*and* no `import authelia` in Caddy never reaches Authelia at all -- the +deny doesn't apply. + ## Wire Caddy into Authelia Open `caddy/snippet.example.caddyfile`. It defines: -- `(authelia)` -- a reusable snippet: `import authelia` in any site block to - gate it. +- `(authelia)` -- reusable snippet: `import authelia` in any site block + you want gated. - `(accesslog)` -- writes Caddy's JSON access log to `/var/log/caddy/access.log` so fail2ban can watch it. - `auth.example.com` -- the Authelia portal subdomain. -- Example protected blocks for `cam.example.com` and `doorbell.example.com`. +- Example blocks for the three cases above. Copy the relevant blocks into your real Caddyfile, replace `example.com` with your domain and `192.168.x.x` with real upstream IPs, then reload Caddy: @@ -139,14 +165,45 @@ docker compose -f ~/docker/caddy/docker-compose.yml exec caddy \ caddy reload --config /etc/caddy/Caddyfile ``` -For each protected domain, also add a rule under `access_control.rules` in -`authelia/configuration.yml` (Authelia's default policy is `deny` -- a -domain with no rule will not authenticate). Restart Authelia after editing: +For each case-1 or case-2 domain, also add a rule under `access_control.rules` +in `authelia/configuration.yml`. Restart Authelia after editing: ```bash docker compose restart authelia ``` +### Switching Frigate to Authelia (case 2) + +Frigate 0.14+ has a `proxy:` config block that consumes a username header +from the upstream and skips its own login form. Edit +`frigate_config/config.yml` in your Frigate repo: + +```yaml +auth: + enabled: False + trusted_proxies: + - 172.18.0.0/16 # the caddy_net subnet -- find it with: + # docker network inspect caddy_net | jq '.[0].IPAM.Config' + +proxy: + header_map: + user: remote-user # what Authelia sends; matches `copy_headers` in Caddy + role: remote-groups + default_role: viewer + separator: '|' + # Optional but recommended when Caddy crosses VLANs to reach Frigate. + # Generate with `openssl rand -hex 32`. Caddy must send the same value + # as `X-Proxy-Secret` -- see the cam.* block in caddy/snippet.example.caddyfile. + # auth_secret: 'paste-32-byte-hex-here' +``` + +Restart Frigate (`docker compose restart frigate` in the Frigate repo). +Confirm the Frigate UI now jumps straight to Authelia and back without +a Frigate login screen. + +If you want Authelia groups to drive Frigate roles (admin vs. viewer), +add a `role_map:` under `proxy:` (see Frigate docs). + ### Caddy access log path fail2ban mounts `/var/log/caddy` from the host as read-only. Your Caddy diff --git a/authelia/configuration.yml b/authelia/configuration.yml index 1a4174d..3f20b9a 100644 --- a/authelia/configuration.yml +++ b/authelia/configuration.yml @@ -63,28 +63,61 @@ authentication_backend: # --------------------------------------------------------------------------- # Access control # -# default_policy: deny means every domain Authelia sees must have an -# explicit allow rule. Caddy only sends a domain to Authelia when its site -# block has `import authelia`, so domains you don't proxy through Authelia -# aren't affected. +# default_policy: deny means every domain that Caddy forwards here must +# have an explicit allow rule. Caddy only forwards when a site block has +# `import authelia`, so domains where you keep the app's own auth (no +# `import authelia` in Caddy) bypass Authelia entirely and don't need +# rules here. # # Policies: -# bypass no auth (Authelia portal itself) +# bypass Authelia waves the request through (used for the portal) # one_factor password only # two_factor password + TOTP +# +# Decision tree for whether a site needs an Authelia rule at all: +# +# - App has NO built-in auth (doorbell PTT page) -> rule here + +# `import authelia` in Caddy. Use two_factor for anything that +# controls hardware in the house. +# +# - App has built-in auth but supports trusted-header proxy auth +# (Frigate 0.14+, Grafana, Gitea, Jellyfin, ...) -> rule here + +# `import authelia` in Caddy AND switch the app to proxy auth in +# its own config. Single login (Authelia), 2FA, app keeps its own +# user/role mapping driven from Authelia headers. +# +# - App has built-in auth and CAN'T switch (router admin, legacy +# things) -> NO rule here, NO `import authelia` in Caddy. The +# traffic skips Authelia entirely; the app handles its own login. # --------------------------------------------------------------------------- access_control: default_policy: 'deny' rules: - - domain: 'auth.example.com' # CHANGE + # The Authelia portal itself is always bypass. + - domain: 'auth.example.com' # CHANGE policy: 'bypass' - # Examples -- uncomment / change to your subdomains: - # - domain: 'cam.example.com' - # policy: 'one_factor' + # ----- Apps with NO built-in auth: Authelia is the only gate ----- + # The Pi doorbell PTT page has no app-level auth, so Authelia is it. + # 2FA is appropriate -- this URL controls a speaker in your house. # - domain: 'doorbell.example.com' # policy: 'two_factor' + # ----- Apps that switched FROM their own auth TO Authelia ----- + # Frigate 0.14+ supports trusted-header proxy auth. To use this: + # 1. In frigate_config/config.yml, set `auth.enabled: False` + # and add a `proxy:` block with `header_map.user: remote-user` + # and `header_map.role: remote-groups` (see README). + # 2. `import authelia` in the cam.* Caddy block. + # 3. Add the rule below. + # - domain: 'cam.example.com' + # policy: 'two_factor' + + # ----- Apps that KEEP their own auth ----- + # Don't add a rule and don't `import authelia` in their Caddy block. + # Example: a router admin page on `router.example.com` -- no rule + # appears here, the request never reaches Authelia. + session: # secret loaded via AUTHELIA_SESSION_SECRET_FILE cookies: diff --git a/caddy/snippet.example.caddyfile b/caddy/snippet.example.caddyfile index 8710d2e..f3fe3cf 100644 --- a/caddy/snippet.example.caddyfile +++ b/caddy/snippet.example.caddyfile @@ -11,13 +11,32 @@ # - Caddy v2.5.1 or newer # - Caddy joined to the external `caddy_net` docker network so it can # resolve `authelia` by container name -# - access_control.rules in authelia/configuration.yml have an entry -# for each protected domain (otherwise Authelia's default_policy of -# `deny` will refuse access) +# +# ----------------------------------------------------------------------------- +# DECISION TREE: which sites go behind Authelia? +# +# 1. App has NO built-in auth (e.g. the Pi doorbell PTT page). +# -> `import authelia` here AND add a rule in +# authelia/configuration.yml access_control.rules. Use +# two_factor for anything that controls hardware. +# +# 2. App has built-in auth AND supports trusted-header proxy auth +# (Frigate 0.14+, Grafana, Gitea, Jellyfin, Portainer, ...). +# -> `import authelia` here, add a rule in Authelia, AND switch +# the app's own config to consume Remote-User from upstream +# (disable its built-in login form). One login, 2FA, app +# still owns its user/role mapping. See the cam.* example +# and the README for Frigate specifics. +# +# 3. App has built-in auth and CAN'T switch (router admin pages, +# odd legacy things). +# -> Plain `reverse_proxy` block. NO `import authelia`, NO +# Authelia rule. The traffic skips Authelia entirely. # ============================================================================= # ----------------------------------------------------------------------------- -# Reusable forward_auth snippet -- import into any site you want gated. +# Reusable forward_auth snippet for cases (1) and (2). Import into any site +# block you want gated by Authelia. # ----------------------------------------------------------------------------- (authelia) { forward_auth authelia:9091 { @@ -42,8 +61,12 @@ } } +# ============================================================================= +# Site blocks +# ============================================================================= + # ----------------------------------------------------------------------------- -# Authelia login portal -- bypass policy in access_control.rules +# Authelia login portal -- always bypass in access_control.rules. # ----------------------------------------------------------------------------- auth.example.com { import accesslog @@ -51,22 +74,8 @@ auth.example.com { } # ----------------------------------------------------------------------------- -# Example: gate cam.example.com (Frigate UI on a different VLAN/host) -# Per-domain policy lives in authelia/configuration.yml, NOT here. -# ----------------------------------------------------------------------------- -cam.example.com { - import accesslog - import authelia - reverse_proxy 192.168.x.x:8971 { - transport http { - read_timeout 60s - write_timeout 60s - } - } -} - -# ----------------------------------------------------------------------------- -# Example: gate doorbell.example.com (Pi PTT page + same-origin Frigate proxy) +# CASE 1: app has NO built-in auth. +# Pi doorbell PTT page -- Authelia is the only gate. two_factor in Authelia. # ----------------------------------------------------------------------------- doorbell.example.com { import accesslog @@ -85,3 +94,51 @@ doorbell.example.com { reverse_proxy 192.168.x.x:5555 } } + +# ----------------------------------------------------------------------------- +# CASE 2: app supports trusted-header proxy auth. +# Frigate UI -- Authelia gates access AND Frigate consumes Remote-User from +# upstream so its own role mapping (admin/viewer) still works. To use this, +# also edit frigate_config/config.yml: +# +# auth: +# enabled: False +# trusted_proxies: +# - 172.18.0.0/16 # your caddy_net subnet, see README +# proxy: +# header_map: +# user: remote-user +# role: remote-groups +# default_role: viewer +# separator: '|' +# # Optional but recommended when Caddy and Frigate are on different +# # hosts/VLANs. Generate with `openssl rand -hex 32` and add the +# # matching `header_up X-Proxy-Secret ` below. +# # auth_secret: 'paste-32-byte-hex-here' +# ----------------------------------------------------------------------------- +cam.example.com { + import accesslog + import authelia + reverse_proxy 192.168.x.x:8971 { + transport http { + read_timeout 60s + write_timeout 60s + } + # Uncomment and match Frigate's auth_secret if you set one above. + # header_up X-Proxy-Secret "paste-same-32-byte-hex-here" + } +} + +# ----------------------------------------------------------------------------- +# CASE 3: app keeps its own auth (no Authelia involvement). +# Example: a router admin page or a service that can't do proxy auth. +# Do NOT add `import authelia` and do NOT add an access_control rule for it. +# ----------------------------------------------------------------------------- +# router.example.com { +# import accesslog +# reverse_proxy 192.168.1.1:443 { +# transport http { +# tls_insecure_skip_verify +# } +# } +# } diff --git a/docker-compose.yml b/docker-compose.yml index 51bb0e8..0855c58 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -21,7 +21,7 @@ services: authelia: container_name: authelia - image: authelia/authelia:${AUTHELIA_VERSION:-4.39} + image: authelia/authelia:${AUTHELIA_VERSION:-4.39.19} restart: unless-stopped networks: - caddy_net @@ -46,7 +46,7 @@ services: fail2ban: container_name: fail2ban - image: crazymax/fail2ban:${FAIL2BAN_VERSION:-latest} + image: crazymax/fail2ban:${FAIL2BAN_VERSION:-1.1.0-r0} restart: unless-stopped # Host networking so iptables bans take effect on the host's edge, # including DOCKER-USER chain rules that gate traffic to containers.