diff --git a/README.md b/README.md index c3d4706..f0a4a19 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ subdomain behind a single sign-on portal at `auth.example.com`. - Filesystem notifier for password reset (swap to SMTP later, one block change). - fail2ban bans via the `DOCKER-USER` iptables chain: drops happen at the host edge before traffic reaches any docker-published port. -- Caddy is not in this stack. Copy `caddy/Caddyfile` into your Caddy setup. +- Caddy is not in this stack. Copy blocks from `caddy/snippets.caddyfile` into your Caddy setup. ## Repo layout @@ -76,20 +76,49 @@ There are four ways a site can relate to Authelia. Pick one per site. | Case | App has built-in auth? | Supports proxy auth? | What to do | |------|------------------------|----------------------|------------| | **1** | No | n/a | `import authelia` in Caddy + rule in Authelia. Authelia is the only login. | -| **2** | Yes | Yes | `import authelia` in Caddy + rule in Authelia + disable app's own login form. Single login. | -| **3** | Yes | No | `import authelia` in Caddy + rule in Authelia. App auth is unchanged. User logs into Authelia then the app. Two logins. | +| **2a** | Yes | Yes (Remote-User header) | `import authelia` + disable app's own login form. Single login via headers. | +| **2b** | Yes | Yes (OIDC) | `import authelia` + configure Authelia as OIDC provider in the app. Single login via token exchange. | +| **3** | Yes | No | `import authelia` in Caddy. App auth is unchanged. User logs into Authelia then the app. Two logins. | | **4** | Yes | — | Plain `reverse_proxy`. No `import authelia`, no rule. App handles auth. | Concretely: - **`doorbell.example.com`** (Pi PTT page) -- **case 1**. No app auth at all. Authelia is the only gate. Use `two_factor` -- this URL controls a speaker. -- **`cam.example.com`** (Frigate UI) -- **case 2**. Frigate 0.14+ supports - proxy auth. Disable Frigate's login form and let Authelia drive both the - access gate and the role mapping (admin vs. viewer) via headers. +- **`cam.example.com`** (Frigate UI) -- **case 2a**. Frigate 0.14+ supports + proxy auth via `Remote-User` header. Disable Frigate's login form and let + Authelia drive both the access gate and the role mapping (admin vs. viewer). +- **`books.example.com`** (Audiobookshelf) -- **case 2b**. App redirects to + Authelia, Authelia issues a JWT token, app accepts it. No password set in + the app itself. - **Router admin / NAS UI** -- **case 3** if you want a 2FA gate in front, **case 4** if you just leave it to the app. +### What is OIDC? + +OpenID Connect (OIDC) is an identity protocol layered on top of OAuth 2.0. +The short version: instead of an app checking your password itself, it +redirects you to Authelia, Authelia authenticates you and issues a signed +token (JWT), and the app trusts that token. The app never handles your +password — it only ever sees the token. + +Authelia becomes the **identity provider** (IdP). Apps like Audiobookshelf, +Immich, Jellyfin, and Mealie become **relying parties** — they trust Authelia's +tokens and use them to identify users. + +The practical difference from proxy-header auth (case 2a): +- **Headers**: Caddy adds `Remote-User` to every request and the app reads it. + Works silently. Requires the app to support header-based auth. +- **OIDC**: The browser does a full redirect dance (app → Authelia → app). + Users see the Authelia login page. Requires the app to support OIDC/OAuth. + +Both result in the same thing: one Authelia credential covers the app. + +OIDC requires additional setup in `authelia/configuration.yml` — +an `identity_providers.oidc` block with a client entry per app, each with +its own `client_id` and `client_secret`. See Authelia's OIDC docs for the +full config. The Caddy side is identical to case 2a: `import authelia`. + Default policy in `configuration.yml` is `deny`, so a domain with no rule AND no `import authelia` in Caddy never reaches Authelia at all. @@ -116,7 +145,8 @@ solid security posture. Authelia improves on it in specific situations: than on any individual service. **The real sweet spot:** -- You have services with zero auth (case 1) -- something has to gate them. +- You have services with zero auth (case 1: Homer, Prometheus, NUT web UI, + Gatus) -- something has to gate them. - You have services with auth but no TOTP -- Authelia gives them 2FA without touching the app at all. - You manage access for more than one person. @@ -128,24 +158,49 @@ upstream", "trusted proxies", "header-based auth", "SSO via reverse proxy". | App | Case | Notes | |-----|------|-------| -| Frigate 0.14+ | 2 | `auth.enabled: False` + `proxy:` block in config.yml | -| Grafana | 2 | `[auth.proxy]` in grafana.ini; `GF_AUTH_PROXY_ENABLED=true` | -| Gitea / Forgejo | 2 | `ENABLE_REVERSE_PROXY_AUTHENTICATION=true` in app.ini | -| Nextcloud | 2 | `trusted_proxies` + `user_external` app + HTTP header auth | -| Paperless-ngx | 2 | `PAPERLESS_ENABLE_HTTP_REMOTE_USER=true` | -| Miniflux | 2 | `AUTH_PROXY_HEADER=Remote-User` env var | -| Home Assistant | 2 | `trusted_networks` auth provider | -| BookStack | 2 (partial) | HTTP header auth via SAML2 or custom auth method | -| Immich | 2 (OIDC) | No header auth; configure Authelia as OIDC provider | -| Mealie | 2 (OIDC) | OIDC support; configure Authelia as OIDC provider | +| **Proxy-header auth (case 2a)** | | | +| Frigate 0.14+ | 2a | `auth.enabled: False` + `proxy:` block in config.yml | +| Grafana | 2a | `[auth.proxy]` in grafana.ini; `GF_AUTH_PROXY_ENABLED=true` | +| Gitea / Forgejo | 2a | `ENABLE_REVERSE_PROXY_AUTHENTICATION=true` in app.ini | +| Nextcloud | 2a | `trusted_proxies` + `user_external` app + HTTP header auth | +| Paperless-ngx | 2a | `PAPERLESS_ENABLE_HTTP_REMOTE_USER=true` | +| Miniflux | 2a | `AUTH_PROXY_HEADER=Remote-User` env var | +| Home Assistant | 2a | `trusted_networks` auth provider + header forwarding | +| BookStack | 2a | `AUTH_METHOD=http` + `HTTP_AUTH_HEADER=Remote-User` in .env | +| **OIDC auth (case 2b)** | | | +| Audiobookshelf | 2b | Native OIDC; configure in Settings > Authentication | +| Jellyfin | 2b | Requires `Jellyfin.Plugin.SSO` from Plugin Catalogue | +| Immich | 2b | No header auth; OIDC only. Admin > OAuth settings | +| Mealie | 2b | OIDC supported; or use case 3 as a simpler gate | +| Portainer | 2b | OIDC in Settings > Authentication; or case 3 as simple gate | +| **No built-in auth (case 1)** | | | | Homer / Heimdall | 1 | No auth at all -- Authelia is the only gate | | Dozzle | 1 | No auth by default -- Authelia is the only gate | -| Uptime Kuma | 3 | No proxy auth, no native TOTP -- case 3 is the only way to add 2FA | -| Portainer | 3 | No proxy auth headers; has OIDC for full SSO | -| Jellyfin | 3 | Community plugin for header auth; otherwise case 3 | -| Syncthing | 4 | Decent native auth; proxy auth not supported | +| Prometheus | 1 | No auth built in; always gate, metrics expose internals | +| Alertmanager | 1 | No auth built in | +| Gatus | 1 | Status page; optional built-in OIDC but simpler to gate here | +| WatchYourLAN | 1 | Network ARP scanner, no built-in auth | +| NUT web UI | 1 | NUT daemon has no web UI; frontend web apps vary -- most have no auth | +| **App keeps own auth (case 3)** | | | +| Uptime Kuma | 3 | No proxy auth, no native TOTP -- Authelia is the only way to add 2FA | +| qBittorrent | 3 | Web UI has own auth; no proxy headers | +| Plex | 3 ⚠ | Web UI gate only -- Plex clients bypass Caddy entirely; VPN is better | +| Emby | 3 ⚠ | Same caveat as Plex; clients bypass Caddy | +| Gotify | 3 | Notification server; own auth | +| ntfy | 3 | Notification server; token-based auth | +| wg-easy | 3 | WireGuard web UI; WireGuard clients bypass Caddy (UDP 51820) | +| Umami | 3 | Analytics; own auth | +| phpIPAM | 3 | IP address management; own auth | +| Checkmk | 3 | Monitoring; own auth (LDAP in enterprise edition) | +| Snipe-IT | 3 | Asset management; own auth | +| Zammad | 3 | Help desk / ticketing; own auth | +| Lubelog | 3 | Vehicle maintenance; own auth | +| UniFi | 3 | Network controller; own auth | +| MeshCentral | 3 | Remote management; own auth; OIDC in enterprise builds | | Vaultwarden | 3 or 4 | Strong native auth + TOTP; many skip Authelia here entirely | -| Router / NAS admin | 3 or 4 | Depends on firmware; usually case 4 is fine | +| Router / NAS admin | 3 or 4 | Depends on firmware; case 4 is usually fine | +| **App handles own auth (case 4)** | | | +| Syncthing | 4 | Decent native auth; proxy auth not supported | ## Getting git and authenticating to GitHub diff --git a/caddy/snippets.caddyfile b/caddy/snippets.caddyfile index 78e0d5a..5d95405 100644 --- a/caddy/snippets.caddyfile +++ b/caddy/snippets.caddyfile @@ -51,8 +51,9 @@ auth.{env.DOMAIN} { # ============================================================================= # CASE 1 -- App has NO built-in auth. Authelia is the only gate. # -# Examples: Homer, Heimdall, Dozzle, phpIPAM, any internal tool with no -# login page. Use two_factor in authelia/configuration.yml for these. +# Examples: Homer, Heimdall, Dozzle, Prometheus, Alertmanager, Gatus, +# WatchYourLAN, NUT web UI. Use two_factor in +# authelia/configuration.yml for any of these. # ============================================================================= # Homer / Heimdall dashboard (no auth whatsoever) @@ -69,7 +70,49 @@ dozzle.{env.DOMAIN} { reverse_proxy 192.168.x.x:8888 # CHANGE IP:PORT } -# Pi doorbell PTT page (Flask server, no auth) +# --- Prometheus --- +# No authentication built in. Always gate it -- metrics expose internal details. +# prom.{env.DOMAIN} { +# import accesslog +# import authelia +# reverse_proxy 192.168.x.x:9090 # CHANGE IP +# } + +# --- Alertmanager --- +# No authentication built in. +# alerts.{env.DOMAIN} { +# import accesslog +# import authelia +# reverse_proxy 192.168.x.x:9093 # CHANGE IP +# } + +# --- Gatus --- +# Status / uptime page. Has optional built-in OIDC but simpler to gate here. +# status.{env.DOMAIN} { +# import accesslog +# import authelia +# reverse_proxy 192.168.x.x:8080 # CHANGE IP +# } + +# --- WatchYourLAN --- +# Network ARP scanner. No built-in auth. +# lan.{env.DOMAIN} { +# import accesslog +# import authelia +# reverse_proxy 192.168.x.x:8840 # CHANGE IP +# } + +# --- NUT (Network UPS Tools) web UI --- +# The NUT daemon (upsd) has no web UI itself. Common frontends -- NUT-Monitor, +# upsd-web, various Docker images -- have minimal or no auth. Gate whichever +# you run here. Port varies by image. +# ups.{env.DOMAIN} { +# import accesslog +# import authelia +# reverse_proxy 192.168.x.x:PORT # CHANGE IP:PORT +# } + +# --- Pi doorbell PTT page (Flask server, no auth) --- # Uncomment when the Pi is deployed. # doorbell.{env.DOMAIN} { # import accesslog @@ -85,8 +128,8 @@ dozzle.{env.DOMAIN} { # ============================================================================= -# CASE 2 -- App supports trusted-header proxy auth. Authelia replaces its -# own login form. Single login, app reads Remote-User for roles. +# CASE 2a -- App supports trusted-header proxy auth. Authelia replaces its +# own login form. Single login, app reads Remote-User for roles. # # Requires per-app config changes -- see notes in each block. # ============================================================================= @@ -167,20 +210,87 @@ miniflux.{env.DOMAIN} { } # --- BookStack --- -# See BookStack docs for HTTP header auth (requires the SAML2 or HTTP auth method). +# In .env: +# AUTH_METHOD=http +# HTTP_AUTH_HEADER=Remote-User +# HTTP_AUTH_AUTO_INITIATE=true # bookstack.{env.DOMAIN} { # import accesslog # import authelia # reverse_proxy 192.168.x.x:80 # CHANGE IP # } + +# ============================================================================= +# CASE 2b -- App supports OIDC. Authelia acts as the OIDC provider. +# +# What is OIDC? OpenID Connect is an identity protocol on top of OAuth 2.0. +# Authelia becomes the "identity provider" (IdP). Apps redirect users to +# auth.DOMAIN, Authelia authenticates them and issues a signed token (JWT), +# then redirects back. The app trusts the token instead of checking a password. +# +# The Caddy config is identical to case 2a: `import authelia` gates the request. +# The difference is all on the app side -- it does a token exchange with +# Authelia's OIDC endpoint rather than reading a Remote-User header. +# +# SETUP REQUIRED in authelia/configuration.yml: +# Add an identity_providers.oidc block with a client entry for each app. +# Each app gets its own client_id and client_secret. +# See: https://www.authelia.com/configuration/identity-providers/openid-connect/ +# +# Result: users never set a password in the app itself. After OIDC is working, +# disable all local accounts in the app -- Authelia is the only credential. +# ============================================================================= + +# --- Audiobookshelf --- +# Native OIDC support. In Audiobookshelf Settings > Authentication: +# Enable OpenID Connect SSO +# Issuer URL: https://auth.DOMAIN +# Client ID / Secret: from identity_providers.oidc in configuration.yml +# Auto Register: on (creates user on first OIDC login) +# books.{env.DOMAIN} { +# import accesslog +# import authelia +# reverse_proxy 192.168.x.x:13378 # CHANGE IP +# } + +# --- Jellyfin --- +# Requires the community SSO plugin (Jellyfin.Plugin.SSO). +# Install from the Plugin Catalogue, then configure OIDC pointing at Authelia. +# jellyfin.{env.DOMAIN} { +# import accesslog +# import authelia +# reverse_proxy 192.168.x.x:8096 # CHANGE IP +# } + # --- Immich --- -# Proxy auth headers not supported; use Authelia as OIDC provider instead. -# Configure Authelia's identity_providers.oidc block and point Immich at it. +# No proxy-header auth; OIDC is the only Authelia path. +# In Immich Admin > Authentication Settings > OAuth: +# Issuer URL: https://auth.DOMAIN +# Client ID / Secret: from configuration.yml +# Auto register: on # photos.{env.DOMAIN} { # import accesslog # import authelia -# reverse_proxy 192.168.x.x:2283 # CHANGE IP +# reverse_proxy 192.168.x.x:2283 # CHANGE IP +# } + +# --- Mealie --- +# OIDC supported. Set in Mealie's admin OIDC settings. +# Alternatively, skip OIDC and use case 3 as a simpler gate. +# mealie.{env.DOMAIN} { +# import accesslog +# import authelia +# reverse_proxy 192.168.x.x:9000 # CHANGE IP +# } + +# --- Portainer --- +# Has OIDC for full SSO -- configure under Settings > Authentication. +# Or use case 3 (below) as a simpler gate without OIDC config. +# portainer.{env.DOMAIN} { +# import accesslog +# import authelia +# reverse_proxy 192.168.x.x:9000 # CHANGE IP (OIDC version) # } @@ -188,9 +298,8 @@ miniflux.{env.DOMAIN} { # CASE 3 -- App keeps its own login. Authelia adds a 2FA gate in front. # # The user passes Authelia 2FA, then the app's own login appears. -# Use for apps that don't support proxy auth headers but you still want -# 2FA before they're even reachable. This is the only way to get TOTP -# in front of apps that don't natively support it. +# Use for apps that don't support proxy auth headers or OIDC, but you still +# want 2FA before they're even reachable. # ============================================================================= # --- Uptime Kuma --- @@ -201,25 +310,15 @@ uptime.{env.DOMAIN} { reverse_proxy 192.168.x.x:3001 # CHANGE IP } -# --- Portainer --- -# No proxy auth header support (use OIDC for full SSO instead). +# --- Portainer (simple gate, no OIDC) --- portainer.{env.DOMAIN} { import accesslog import authelia reverse_proxy 192.168.x.x:9000 # CHANGE IP } -# --- Mealie --- -# Has OIDC support -- can do full case 2 with Authelia as OIDC provider. -# For simpler case-3 gate: -# mealie.{env.DOMAIN} { -# import accesslog -# import authelia -# reverse_proxy 192.168.x.x:9000 # CHANGE IP -# } - # --- Home Assistant --- -# Can also do case 2 via the trusted_networks auth provider + header forwarding. +# Can also do case 2a via the trusted_networks auth provider + header forwarding. # homeassistant.{env.DOMAIN} { # import accesslog # import authelia @@ -236,6 +335,129 @@ portainer.{env.DOMAIN} { # reverse_proxy 192.168.x.x:80 # CHANGE IP # } +# --- qBittorrent --- +# Web UI has its own auth. No proxy headers. +# torrent.{env.DOMAIN} { +# import accesslog +# import authelia +# reverse_proxy 192.168.x.x:8080 # CHANGE IP:PORT +# } + +# --- Plex --- +# IMPORTANT: Plex is tightly coupled to plex.tv cloud auth -- Authelia cannot +# replace it. This gate applies to the web UI only. Plex clients (mobile, TV, +# desktop apps) communicate directly with the server port and bypass Caddy +# entirely, so the gate does not protect those paths. Tailscale or VPN is +# the better approach for Plex remote access. +# plex.{env.DOMAIN} { +# import accesslog +# import authelia +# reverse_proxy 192.168.x.x:32400 # CHANGE IP +# } + +# --- Emby --- +# Same caveat as Plex: Emby clients bypass Caddy. Web UI gate only. +# emby.{env.DOMAIN} { +# import accesslog +# import authelia +# reverse_proxy 192.168.x.x:8096 # CHANGE IP +# } + +# --- Gotify --- +# Notification server. Own auth, no proxy headers. +# gotify.{env.DOMAIN} { +# import accesslog +# import authelia +# reverse_proxy 192.168.x.x:8080 # CHANGE IP +# } + +# --- ntfy --- +# Notification server. Token-based auth, no proxy headers. +# ntfy.{env.DOMAIN} { +# import accesslog +# import authelia +# reverse_proxy 192.168.x.x:80 # CHANGE IP +# } + +# --- wg-easy --- +# WireGuard web UI. Own password, no proxy headers. +# NOTE: WireGuard clients connect directly to UDP 51820, not through Caddy. +# This gate only protects the web management UI. +# wg.{env.DOMAIN} { +# import accesslog +# import authelia +# reverse_proxy 192.168.x.x:51821 # CHANGE IP +# } + +# --- Umami --- +# Web analytics. Own auth, no proxy headers. +# analytics.{env.DOMAIN} { +# import accesslog +# import authelia +# reverse_proxy 192.168.x.x:3000 # CHANGE IP +# } + +# --- phpIPAM --- +# IP address management. Own auth, no proxy headers. +# ipam.{env.DOMAIN} { +# import accesslog +# import authelia +# reverse_proxy 192.168.x.x:80 # CHANGE IP +# } + +# --- Checkmk --- +# Monitoring. Own auth. No proxy headers in the free (Raw) edition. +# checkmk.{env.DOMAIN} { +# import accesslog +# import authelia +# reverse_proxy 192.168.x.x:5000 # CHANGE IP +# } + +# --- Snipe-IT --- +# Asset management. Own auth, no proxy headers. +# assets.{env.DOMAIN} { +# import accesslog +# import authelia +# reverse_proxy 192.168.x.x:80 # CHANGE IP +# } + +# --- Zammad --- +# Help desk / ticketing. Own auth, no proxy headers. +# tickets.{env.DOMAIN} { +# import accesslog +# import authelia +# reverse_proxy 192.168.x.x:3000 # CHANGE IP +# } + +# --- Lubelog --- +# Vehicle maintenance log. Own auth, no proxy headers. +# cars.{env.DOMAIN} { +# import accesslog +# import authelia +# reverse_proxy 192.168.x.x:8080 # CHANGE IP +# } + +# --- UniFi Network Application --- +# Network controller. Own auth, no proxy headers (LDAP/RADIUS in enterprise). +# Serves HTTPS on 8443; skip TLS verify for the upstream. +# unifi.{env.DOMAIN} { +# import accesslog +# import authelia +# reverse_proxy 192.168.x.x:8443 { # CHANGE IP +# transport http { tls_insecure_skip_verify } +# } +# } + +# --- MeshCentral --- +# Remote management server. Own auth; has OIDC in paid/enterprise builds. +# meshcentral.{env.DOMAIN} { +# import accesslog +# import authelia +# reverse_proxy 192.168.x.x:443 { # CHANGE IP +# transport http { tls_insecure_skip_verify } +# } +# } + # ============================================================================= # CASE 4 -- App handles its own auth. Authelia not involved.