Compare commits
10
Commits
711f892ada
...
authelia
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c8d9c2921d | ||
|
|
e2b29d3af9 | ||
|
|
d0dab09e21 | ||
|
|
18d5a40d32 | ||
|
|
628d77563c | ||
|
|
62fb5c75f4 | ||
|
|
b797602cbc | ||
|
|
6252271816 | ||
|
|
6cf6a2aeb8 | ||
|
|
cd78a3560d |
@@ -4,6 +4,14 @@
|
||||
# so they can be mounted into the container without env-var leakage. This
|
||||
# .env only holds non-secret tunables.
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Your root domain. This single value flows into authelia/configuration.yml
|
||||
# (via Authelia's {{ env "DOMAIN" }} template substitution) and into
|
||||
# caddy/Caddyfile (via Caddy's {env.DOMAIN} substitution).
|
||||
# No manual find-and-replace needed -- just set this.
|
||||
# ---------------------------------------------------------------------------
|
||||
DOMAIN=example.com
|
||||
|
||||
# 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.
|
||||
|
||||
@@ -0,0 +1,456 @@
|
||||
# HANDOFF -- script to add a site behind Authelia
|
||||
|
||||
This doc is for a fresh session where you'll write a script that
|
||||
automates the "add a new site behind Authelia" workflow currently done
|
||||
by hand. Everything below is the context you need to write it
|
||||
correctly.
|
||||
|
||||
## Goal
|
||||
|
||||
One command, e.g.
|
||||
|
||||
```
|
||||
./add-site --subdomain doorbell --upstream 192.168.1.60:5555 --case 1 --policy two_factor
|
||||
```
|
||||
|
||||
does all of:
|
||||
|
||||
1. Adds the matching rule to `authelia/configuration.yml` under
|
||||
`access_control.rules:`.
|
||||
2. Adds (or modifies) the matching site block in the user's Caddyfile.
|
||||
If a site block already exists with `basic_auth { ... }`, removes
|
||||
that and inserts `import authelia` instead.
|
||||
3. Validates Authelia config (`authelia validate-config`).
|
||||
4. Validates Caddy config (`caddy validate`).
|
||||
5. Restarts Authelia, then reloads Caddy. Order matters.
|
||||
6. Optionally `curl`-tests the new URL and reports.
|
||||
|
||||
Idempotent: re-running with the same args is a no-op.
|
||||
|
||||
## Stack context (what's already running)
|
||||
|
||||
- **Authelia 4.39.19**, file backend, SQLite local storage, in-memory
|
||||
sessions, filesystem notifier. Compose project lives at
|
||||
`~/docker/authelia/`. Container name `authelia`.
|
||||
- **fail2ban** as a sidecar in the same compose project. Watches
|
||||
`./authelia/authelia.log` and `/var/log/caddy/access.log`.
|
||||
- **Caddy** is in its own compose project at `~/docker/caddy/`
|
||||
(assumption -- script should accept the path as a parameter).
|
||||
Container name `caddy`. On the external docker network `caddy_net`.
|
||||
- Both Caddy and Authelia are on `caddy_net`. Caddy reaches Authelia
|
||||
as `authelia:9091`.
|
||||
- The portal is `auth.{DOMAIN}` with `policy: bypass`.
|
||||
- `default_policy: deny` -- every gated domain MUST have a rule.
|
||||
- DOMAIN substitution:
|
||||
- Authelia uses Go templates: `'{{ env "DOMAIN" }}'`. Requires
|
||||
`X_AUTHELIA_CONFIG_FILTERS=template` env var (already set in the
|
||||
docker-compose.yml).
|
||||
- Caddy uses `{env.DOMAIN}`. The Caddy compose passes `DOMAIN`
|
||||
through to the Caddy container.
|
||||
|
||||
## Files the script touches
|
||||
|
||||
| File | What lives there | Who edits |
|
||||
|------|------------------|-----------|
|
||||
| `~/docker/authelia/authelia/configuration.yml` | `access_control.rules:` list | the script |
|
||||
| `<user's Caddyfile>` (path: parameter) | site blocks | the script |
|
||||
| `~/docker/authelia/.env` | `DOMAIN=...`, `TZ=...`, version pins | read-only (script reads DOMAIN from here OR the environment) |
|
||||
|
||||
The script does NOT touch:
|
||||
- `authelia/users_database.yml` (user management is separate)
|
||||
- `authelia/secrets/*` (manual one-time bootstrap)
|
||||
- `frigate_config/config.yml` or any other app's own config
|
||||
(case 2a/2b require app-side edits the script can't safely automate
|
||||
-- it should print instructions instead)
|
||||
- DNS, TLS, anything outside the local Caddy + Authelia configs
|
||||
|
||||
## Manual workflow the script automates
|
||||
|
||||
For reference, here's what a human does today to add `foo.example.com`
|
||||
as a case-1 site:
|
||||
|
||||
```bash
|
||||
# 1. Edit authelia/configuration.yml -- add under access_control.rules:
|
||||
# - domain: 'foo.{{ env "DOMAIN" }}'
|
||||
# policy: 'two_factor'
|
||||
|
||||
# 2. Edit your real Caddyfile -- add a new block (or modify existing):
|
||||
# foo.{env.DOMAIN} {
|
||||
# import accesslog
|
||||
# import authelia
|
||||
# reverse_proxy 192.168.1.60:5555
|
||||
# }
|
||||
|
||||
# 3. Validate before reloading
|
||||
docker compose -f ~/docker/authelia/docker-compose.yml run --rm authelia \
|
||||
authelia validate-config --config /config/configuration.yml
|
||||
|
||||
docker compose -f ~/docker/caddy/docker-compose.yml exec caddy \
|
||||
caddy validate --config /etc/caddy/Caddyfile
|
||||
|
||||
# 4. Restart Authelia FIRST (so the rule is live before Caddy starts
|
||||
# forwarding to it -- otherwise default_policy: deny returns 403)
|
||||
docker compose -f ~/docker/authelia/docker-compose.yml restart authelia
|
||||
|
||||
# 5. Reload Caddy (zero-downtime)
|
||||
docker compose -f ~/docker/caddy/docker-compose.yml exec caddy \
|
||||
caddy reload --config /etc/caddy/Caddyfile
|
||||
|
||||
# 6. Test
|
||||
curl -sI -o /dev/null -w "%{http_code}\n" https://foo.example.com
|
||||
# expect 302 redirect to auth.example.com
|
||||
```
|
||||
|
||||
## Cases the script must handle
|
||||
|
||||
The full case taxonomy is in `authelia/configuration.yml` and
|
||||
`caddy/snippets.caddyfile`. Summary:
|
||||
|
||||
### Case 1 -- no app auth, Authelia is the only gate
|
||||
|
||||
**Authelia rule:**
|
||||
```yaml
|
||||
- domain: 'SUBDOMAIN.{{ env "DOMAIN" }}'
|
||||
policy: 'two_factor' # or one_factor
|
||||
```
|
||||
|
||||
**Caddy block:**
|
||||
```caddyfile
|
||||
SUBDOMAIN.{env.DOMAIN} {
|
||||
import accesslog
|
||||
import authelia
|
||||
reverse_proxy UPSTREAM_IP:UPSTREAM_PORT
|
||||
}
|
||||
```
|
||||
|
||||
Most common case. The migration path from `basic_auth` -- the script
|
||||
should handle "block exists with basic_auth, swap it for import authelia".
|
||||
|
||||
### Case 2a -- app supports trusted-header proxy auth
|
||||
|
||||
Same Caddy block as case 1. Same Authelia rule. **Plus** an app-side
|
||||
config change the script CANNOT safely automate (each app is different:
|
||||
Frigate's `auth.enabled: False`, Grafana's `[auth.proxy]` section,
|
||||
Gitea's `ENABLE_REVERSE_PROXY_AUTHENTICATION`, etc.). Script should
|
||||
print app-specific instructions from a lookup table and require
|
||||
`--ack-app-config-done` to proceed.
|
||||
|
||||
### Case 2b -- app supports OIDC
|
||||
|
||||
Same Caddy block as case 1. Same Authelia rule. **Plus** an
|
||||
`identity_providers.oidc.clients[]` entry to add to
|
||||
`configuration.yml`. Each app needs its own `client_id`,
|
||||
`client_secret`, `redirect_uris`, etc.
|
||||
|
||||
This is more involved. **Suggested**: out of scope for v1 of the script;
|
||||
print a pointer to Authelia's OIDC docs and skip.
|
||||
|
||||
### Case 3 -- app keeps its own auth, Authelia adds 2FA in front
|
||||
|
||||
Caddy block and Authelia rule are identical to case 1. The user just
|
||||
keeps logging into the app after Authelia. The script doesn't need to
|
||||
distinguish case 1 from case 3 mechanically -- the only difference is
|
||||
the user's mental model.
|
||||
|
||||
### Case 4 -- no Authelia involvement
|
||||
|
||||
```caddyfile
|
||||
SUBDOMAIN.{env.DOMAIN} {
|
||||
import accesslog # NO import authelia
|
||||
reverse_proxy UPSTREAM_IP:UPSTREAM_PORT
|
||||
}
|
||||
```
|
||||
|
||||
**No Authelia rule.** The script's job is just the Caddy edit + reload.
|
||||
Useful for things like Plex/Emby where Authelia's redirect breaks
|
||||
native clients.
|
||||
|
||||
## Validation hooks the script must run
|
||||
|
||||
In order, before any reload:
|
||||
|
||||
1. **Authelia config:**
|
||||
```bash
|
||||
docker compose -f ~/docker/authelia/docker-compose.yml run --rm authelia \
|
||||
authelia validate-config --config /config/configuration.yml
|
||||
```
|
||||
Exit 0 = good. Anything else = abort, restore the file from backup.
|
||||
|
||||
2. **Caddy config:**
|
||||
```bash
|
||||
docker compose -f ~/docker/caddy/docker-compose.yml exec caddy \
|
||||
caddy validate --config /etc/caddy/Caddyfile
|
||||
```
|
||||
Exit 0 = good. Anything else = abort, restore Caddyfile from backup.
|
||||
|
||||
After reload:
|
||||
|
||||
3. **HTTP probe:**
|
||||
```bash
|
||||
curl -sI -o /dev/null -w "%{http_code}\n" https://SUBDOMAIN.DOMAIN
|
||||
```
|
||||
- Case 1 / 2a / 2b / 3: expect `302` (redirect to Authelia).
|
||||
- Case 4: expect `200` or whatever the upstream returns.
|
||||
- `403` means the Caddy block has `import authelia` but the Authelia
|
||||
rule isn't in place (or wasn't picked up). Most common script bug.
|
||||
|
||||
## Reload semantics
|
||||
|
||||
**Order**: Authelia restart, THEN Caddy reload. Reverse order is briefly
|
||||
broken: Caddy starts forwarding to Authelia for a domain Authelia
|
||||
doesn't yet have a rule for, and `default_policy: deny` returns 403 to
|
||||
the user.
|
||||
|
||||
**Authelia restart**: full container restart. ~3-5 second outage on
|
||||
auth.{DOMAIN}. Acceptable for household use; if you want zero-downtime
|
||||
later, Authelia supports config reload via SIGHUP -- not used here.
|
||||
|
||||
**Caddy reload**: `caddy reload` is genuinely zero-downtime. It loads
|
||||
the new config, validates, and atomically swaps. If validation fails
|
||||
the old config keeps running.
|
||||
|
||||
**Rollback**: if anything fails, the script should revert the file
|
||||
edits from a `.bak` it took before mutating. Easiest pattern:
|
||||
|
||||
```bash
|
||||
cp authelia/configuration.yml authelia/configuration.yml.bak
|
||||
cp $CADDYFILE_PATH $CADDYFILE_PATH.bak
|
||||
# ... edits ...
|
||||
# if any validate fails:
|
||||
mv authelia/configuration.yml.bak authelia/configuration.yml
|
||||
mv $CADDYFILE_PATH.bak $CADDYFILE_PATH
|
||||
```
|
||||
|
||||
## Idempotency
|
||||
|
||||
The script must detect and short-circuit when the desired end state
|
||||
already exists:
|
||||
|
||||
- **Authelia rule already present**: parse `access_control.rules:`, look
|
||||
for an entry with matching `domain:`. If found and policy matches, skip
|
||||
the YAML edit. If found and policy differs, prompt or fail (don't
|
||||
silently overwrite).
|
||||
- **Caddy block already present with `import authelia`**: parse the
|
||||
Caddyfile, look for `SUBDOMAIN.{env.DOMAIN} {` block. If found and
|
||||
it has `import authelia`, skip the Caddy edit.
|
||||
- **Caddy block exists with `basic_auth`**: this is the migration case.
|
||||
Remove the `basic_auth { ... }` lines, add `import authelia` if
|
||||
missing. Preserve everything else (transport, header_up, etc).
|
||||
- **Caddy block exists without auth at all**: just add `import authelia`.
|
||||
|
||||
After all checks: if neither file actually changed, skip both reloads
|
||||
(important -- restarting Authelia for a no-op kicks every active
|
||||
session).
|
||||
|
||||
## Failure modes the script must handle
|
||||
|
||||
- `DOMAIN` env var not set / `.env` not present -> abort early with
|
||||
clear error.
|
||||
- `caddy_net` docker network doesn't exist -> abort.
|
||||
- Authelia container not running -> can still edit config and validate;
|
||||
reload step needs to be skipped or attempted with helpful error.
|
||||
- Caddy container not running -> same.
|
||||
- Caddyfile path doesn't exist or isn't writable -> abort.
|
||||
- The Caddyfile doesn't have `(authelia)` and `(accesslog)` snippets
|
||||
defined -> the script could either inject them at the top of the file
|
||||
(fragile) or refuse and tell the user to run a one-time setup step
|
||||
first. **Recommended**: refuse, with a clear "run `./bootstrap-caddy`
|
||||
first" message.
|
||||
- Subdomain conflicts with an existing block that's NOT just a
|
||||
`basic_auth` migration target (e.g. an entirely different upstream)
|
||||
-> prompt, don't auto-overwrite.
|
||||
- A site block exists with `basic_auth` AND something else complicated
|
||||
(custom matchers, multiple `handle` blocks) -> migration is hard.
|
||||
Recommended: detect the simple case (single `basic_auth { ... }`
|
||||
inside the block) and refuse the complex case.
|
||||
- `validate-config` or `caddy validate` fails -> rollback both files,
|
||||
report the validator's stderr, exit non-zero.
|
||||
- HTTP probe fails post-reload -> log the symptom but don't auto-revert;
|
||||
user may have DNS not pointing yet, etc.
|
||||
|
||||
## YAML editing -- preserve comments
|
||||
|
||||
`authelia/configuration.yml` has substantial comments (the
|
||||
"ALSO PASTE INTO CADDYFILE" blocks, case explanations). A naive YAML
|
||||
round-trip will eat them. Use a comment-preserving library:
|
||||
|
||||
- **Python**: `ruamel.yaml` with `YAML(typ='rt')` (round-trip mode).
|
||||
- **Go**: `gopkg.in/yaml.v3` is comment-aware.
|
||||
- **`yq`** (the Go-based one from mikefarah): preserves comments
|
||||
reasonably well for simple ops. Adding a list item:
|
||||
```bash
|
||||
yq -i '.access_control.rules += [{"domain": "foo.{{ env \"DOMAIN\" }}", "policy": "two_factor"}]' \
|
||||
authelia/configuration.yml
|
||||
```
|
||||
Note the escaping pain with `{{ env "DOMAIN" }}`. Test before
|
||||
committing.
|
||||
|
||||
The script should anchor inserts at a stable location. The least-bad
|
||||
anchor is the END of `access_control.rules:` -- always append, never
|
||||
splice in the middle.
|
||||
|
||||
## Caddyfile editing -- there is no good parser
|
||||
|
||||
Caddyfile has its own grammar; standard YAML/JSON tools won't touch it.
|
||||
Options, in order of pragmatism:
|
||||
|
||||
1. **Text-based pattern matching** (recommended for v1). Anchors:
|
||||
- Find `^SUBDOMAIN\.\{env\.DOMAIN\} \{$` to detect existing block.
|
||||
- For the basic_auth migration: use a small state machine to find
|
||||
`basic_auth {` ... `}` inside the matched block and delete those
|
||||
lines, then ensure `import authelia` and `import accesslog` lines
|
||||
exist.
|
||||
- For new-block insertion: append at end of file, separated by a
|
||||
blank line.
|
||||
|
||||
2. **`caddy adapt`**: converts Caddyfile to JSON. You could edit the
|
||||
JSON, then... there's no Caddyfile emitter. Adapt is one-way. Skip.
|
||||
|
||||
3. **`caddy fmt`**: normalizes whitespace in a Caddyfile, doesn't
|
||||
semantically edit. Useful AFTER your edits to clean up.
|
||||
|
||||
The text-based approach is fragile for arbitrary Caddyfiles but
|
||||
predictable for the conventions this stack uses (one site block per
|
||||
subdomain, snippets imported at top, no exotic matchers in gated
|
||||
sites).
|
||||
|
||||
## Suggested architecture
|
||||
|
||||
```
|
||||
add-site
|
||||
├── lib/
|
||||
│ ├── env.sh # find DOMAIN, container names, Caddyfile path
|
||||
│ ├── yaml_edit.sh # ruamel.yaml or yq wrapper for access_control
|
||||
│ ├── caddyfile_edit.sh # awk/sed-based site-block patcher
|
||||
│ ├── validate.sh # authelia + caddy validators
|
||||
│ └── reload.sh # restart authelia + reload caddy in order
|
||||
├── cases/
|
||||
│ ├── case-1.sh # no-app-auth recipe
|
||||
│ ├── case-2a.md # printable app-side instructions table
|
||||
│ ├── case-2b.md # OIDC out-of-scope notice + pointer
|
||||
│ └── case-4.sh # no-Authelia recipe
|
||||
├── add-site # main entrypoint
|
||||
└── README.md
|
||||
```
|
||||
|
||||
Or in Python with `ruamel.yaml` and a small Caddyfile patcher class.
|
||||
Either is fine; the bash version has fewer install steps for an
|
||||
end-user.
|
||||
|
||||
## Concrete test cases the script must pass
|
||||
|
||||
Use these as fixtures.
|
||||
|
||||
### Test 1: fresh case-1 add
|
||||
|
||||
Pre-state:
|
||||
- `authelia/configuration.yml` has only the `auth.{DOMAIN}` bypass rule.
|
||||
- Caddyfile has `(authelia)` and `(accesslog)` snippets defined, no
|
||||
`foo.{env.DOMAIN}` block.
|
||||
|
||||
Invocation:
|
||||
```
|
||||
./add-site --subdomain foo --upstream 192.168.1.60:5555 --case 1
|
||||
```
|
||||
|
||||
Post-state:
|
||||
- `access_control.rules:` has new entry for `foo.{{ env "DOMAIN" }}`.
|
||||
- Caddyfile has new `foo.{env.DOMAIN} { ... }` block with `import
|
||||
authelia` and `import accesslog`.
|
||||
- `validate-config` and `caddy validate` both pass.
|
||||
- `curl -sI https://foo.example.com` returns 302.
|
||||
|
||||
### Test 2: idempotent re-run
|
||||
|
||||
Run Test 1's invocation twice. Second run: no file edits, no reloads,
|
||||
exit 0 with "already configured" message.
|
||||
|
||||
### Test 3: basic_auth migration
|
||||
|
||||
Pre-state:
|
||||
- Caddyfile has `foo.{env.DOMAIN} { basic_auth { user $2a$... }
|
||||
reverse_proxy ... }`.
|
||||
|
||||
Invocation:
|
||||
```
|
||||
./add-site --subdomain foo --case 1 --migrate-basic-auth
|
||||
```
|
||||
|
||||
Post-state:
|
||||
- The `basic_auth { ... }` lines are gone.
|
||||
- `import authelia` and `import accesslog` are present.
|
||||
- `reverse_proxy` line is unchanged.
|
||||
- Authelia rule added.
|
||||
|
||||
### Test 4: validation failure rollback
|
||||
|
||||
Pre-state: introduce a typo by hand into the YAML insert template
|
||||
(e.g. `polciy:` instead of `policy:`). Simulate by mocking the
|
||||
template.
|
||||
|
||||
Expected: `validate-config` fails, both files restored from `.bak`,
|
||||
non-zero exit, no reload attempted.
|
||||
|
||||
### Test 5: case-4 (no Authelia)
|
||||
|
||||
Invocation:
|
||||
```
|
||||
./add-site --subdomain plex --upstream 192.168.1.5:32400 --case 4
|
||||
```
|
||||
|
||||
Post-state:
|
||||
- Caddyfile has new `plex.{env.DOMAIN}` block with `import accesslog`,
|
||||
NO `import authelia`.
|
||||
- `access_control.rules:` is UNCHANGED.
|
||||
- Caddy reloads, Authelia is NOT restarted.
|
||||
|
||||
## Out of scope (tell the user, don't try to automate)
|
||||
|
||||
- DNS A-record creation. Caddy will fail to issue a cert for a domain
|
||||
that doesn't resolve. Print a "make sure DNS is pointing at this
|
||||
host" reminder when the script starts.
|
||||
- TLS / Let's Encrypt failures. Caddy auto-provisions; if it fails,
|
||||
it's usually DNS or rate-limit. The script should not try to debug.
|
||||
- App-side proxy-auth config (case 2a). Each app is different. Print
|
||||
the lookup-table snippet from `caddy/snippets.caddyfile` for that
|
||||
app and require `--ack-app-config-done` before running.
|
||||
- OIDC client setup (case 2b). Big enough that it deserves its own
|
||||
tool. Out of scope.
|
||||
- User management (`users_database.yml`).
|
||||
- Secret rotation (`authelia/secrets/*`).
|
||||
- TOTP enrollment.
|
||||
|
||||
## Quick reference: existing files
|
||||
|
||||
If you want to read what's there to understand the conventions:
|
||||
|
||||
- `docker-compose.yml` -- the auth stack compose, including the
|
||||
`X_AUTHELIA_CONFIG_FILTERS=template` and `DOMAIN=${DOMAIN}` env vars.
|
||||
- `authelia/configuration.yml` -- reference for the rule format,
|
||||
comment style, and the "ALSO PASTE INTO CADDYFILE" blocks under
|
||||
each case in `access_control.rules:`.
|
||||
- `caddy/snippets.caddyfile` -- canonical examples of Caddy site
|
||||
blocks for every case, including app-specific notes the case-2a
|
||||
table can be extracted from.
|
||||
- `.env.example` -- the shape of `.env` (DOMAIN, TZ, version pins).
|
||||
- `README.md` -- the comprehensive bootstrap walkthrough; the
|
||||
"Adding a new protected site" section is what the script
|
||||
automates.
|
||||
|
||||
## When in doubt
|
||||
|
||||
- For YAML edits, dry-run with
|
||||
`docker compose run --rm authelia authelia config template --config
|
||||
/config/configuration.yml` -- prints the rendered config so you can
|
||||
see exactly what Authelia will see.
|
||||
- For Caddyfile edits, `caddy fmt --overwrite Caddyfile` normalizes
|
||||
whitespace and `caddy validate` catches syntax errors. Run BOTH
|
||||
before any reload.
|
||||
- The single most useful debug command for "why is my site getting
|
||||
403":
|
||||
```bash
|
||||
docker compose -f ~/docker/authelia/docker-compose.yml exec authelia \
|
||||
tail -f /config/authelia.log
|
||||
```
|
||||
Then hit the URL. The log line tells you exactly which rule (or
|
||||
default_policy) made the call.
|
||||
@@ -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
|
||||
|
||||
@@ -56,7 +56,7 @@ authelia-stack/
|
||||
│ └── caddy.local # 30 fails/2 min -> 30 min IP ban
|
||||
│
|
||||
└── caddy/
|
||||
└── Caddyfile # copy/merge into your Caddy setup
|
||||
└── snippets.caddyfile # per-service snippets to add to your existing Caddyfile
|
||||
```
|
||||
|
||||
## Prerequisites
|
||||
@@ -76,42 +76,131 @@ 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.
|
||||
|
||||
### Should you use Authelia at all?
|
||||
|
||||
A password manager with per-service credentials and per-service TOTP is a
|
||||
solid security posture. Authelia improves on it in specific situations:
|
||||
|
||||
| Situation | Password manager alone | Authelia |
|
||||
|-----------|----------------------|----------|
|
||||
| App has **no auth at all** (Homer, Dozzle, doorbell page) | Can't help | Gates it with 2FA, zero app changes |
|
||||
| App has auth but **no native TOTP** (Uptime Kuma, Syncthing, phpIPAM) | Password-only | Adds 2FA in front for free |
|
||||
| Multiple people need access | Change credentials in N places | Disable one account in Authelia |
|
||||
| True SSO -- log in once, reach 10 services | Still authenticates 10× (autofilled) | One session covers all gated services |
|
||||
| Consistent rate-limiting / brute-force protection | Wildly varies per app | fail2ban + regulation applied uniformly |
|
||||
|
||||
**Where Authelia is marginal for a single user:**
|
||||
- All your services already have native TOTP support → Authelia adds mostly
|
||||
friction. You're right that 2FA doesn't require Authelia: Vaultwarden,
|
||||
Nextcloud, Grafana, and Gitea all support TOTP natively. If you've already
|
||||
set that up in a manager like Bitwarden, Authelia's 2FA argument is weaker.
|
||||
- The Authelia password itself becomes keys-to-the-kingdom for everything
|
||||
gated behind it, which is why the TOTP requirement on Authelia matters more
|
||||
than on any individual service.
|
||||
|
||||
**The real sweet spot:**
|
||||
- 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.
|
||||
|
||||
### How to tell if an app supports proxy auth (case 2)
|
||||
|
||||
Look for any of these in the app's docs:
|
||||
Look for any of these in the app's docs: "Remote-User header", "trusted
|
||||
upstream", "trusted proxies", "header-based auth", "SSO via reverse proxy".
|
||||
|
||||
- "Remote-User header", "trusted upstream", "trusted proxies"
|
||||
- "Header-based authentication", "SSO via reverse proxy"
|
||||
- Support for `X-Forwarded-User`, `X-Remote-User`, or `Remote-User`
|
||||
|
||||
| App | Proxy auth? | Notes |
|
||||
|-----|-------------|-------|
|
||||
| Frigate 0.14+ | Yes | `auth.enabled: False` + `proxy:` block in config.yml |
|
||||
| Grafana | Yes | `[auth.proxy]` section in grafana.ini |
|
||||
| Gitea / Forgejo | Yes | `REVERSE_PROXY_AUTHENTICATION_USER` in app.ini |
|
||||
| Nextcloud | Yes | `TRUSTED_PROXIES` env + `overwriteprotocol = https` |
|
||||
| Home Assistant | Yes | `trusted_networks` auth provider + `use_x_forwarded_for` |
|
||||
| Jellyfin | Partial | Community plugin required |
|
||||
| Portainer | No | Use Authelia OIDC integration instead |
|
||||
| Vaultwarden | No | Use Authelia OIDC integration instead |
|
||||
| Router/NAS admin | Rarely | Use case 3 (2FA gate) or case 4 |
|
||||
| App | Case | Notes |
|
||||
|-----|------|-------|
|
||||
| **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 |
|
||||
| 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 | 4 ⚠ | Do NOT use `import authelia` -- native clients go through Caddy but cannot complete browser-redirect auth; use Case 4, Plex handles its own auth |
|
||||
| Emby | 4 ⚠ | Same as Plex |
|
||||
| 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; 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
|
||||
|
||||
@@ -192,73 +281,186 @@ gh auth login
|
||||
|
||||
## First-run setup
|
||||
|
||||
### 0. Clone the repo
|
||||
|
||||
The Frigate stack lives on `main`. This auth stack is on the `authelia` branch -- clone it separately into its own directory.
|
||||
|
||||
```bash
|
||||
git clone -b authelia \
|
||||
https://github.com/outis1one/frigate_w_audio.git \
|
||||
~/docker/authelia
|
||||
```
|
||||
|
||||
```bash
|
||||
# 0) Clone the auth stack onto the server.
|
||||
# (The Frigate stack lives on the `main` branch and is cloned separately.)
|
||||
gh repo clone outis1one/frigate_w_audio -- \
|
||||
--branch authelia ~/docker/authelia
|
||||
cd ~/docker/authelia
|
||||
```
|
||||
|
||||
# 1) Create the external docker network (Caddy must also be on this).
|
||||
### 1. Create the external Docker network
|
||||
|
||||
Caddy must join this same network so it can reach Authelia by container name. Skip if `caddy_net` already exists.
|
||||
|
||||
```bash
|
||||
docker network create caddy_net 2>/dev/null || true
|
||||
```
|
||||
|
||||
# 2) Bootstrap the secrets directory.
|
||||
### 2. Generate secrets
|
||||
|
||||
Authelia loads these from files so they never appear in `docker inspect` or process listings.
|
||||
|
||||
```bash
|
||||
mkdir -p authelia/secrets
|
||||
openssl rand -hex 32 > authelia/secrets/JWT_SECRET
|
||||
openssl rand -hex 32 > authelia/secrets/SESSION_SECRET
|
||||
openssl rand -hex 32 > authelia/secrets/STORAGE_ENCRYPTION_KEY
|
||||
chmod 600 authelia/secrets/*
|
||||
```
|
||||
|
||||
# 3) Copy and edit .env.
|
||||
### 3. Set your domain
|
||||
|
||||
`DOMAIN` is the only value you set here. It flows into `authelia/configuration.yml` via Go template substitution and into your Caddyfile via `{env.DOMAIN}` -- no find-and-replace needed anywhere else.
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
$EDITOR .env # set TZ; pin AUTHELIA_VERSION if you want
|
||||
```
|
||||
|
||||
# 4) Edit authelia/configuration.yml.
|
||||
# Replace every `example.com` with your real root domain.
|
||||
# Look for the four CHANGE comments: totp.issuer, session.cookies[].domain,
|
||||
# session.cookies[].authelia_url, session.cookies[].default_redirection_url.
|
||||
# Also uncomment access_control.rules entries for the sites you want to gate.
|
||||
```bash
|
||||
$EDITOR .env
|
||||
```
|
||||
|
||||
Set `DOMAIN=yourdomain.com` and `TZ=Your/Timezone`. Save and close.
|
||||
|
||||
### 4. Add access control rules
|
||||
|
||||
**This step and step 8 (Caddy wiring) must be done together for every site you want to gate. Both are required -- neither alone is enough.**
|
||||
|
||||
```bash
|
||||
$EDITOR authelia/configuration.yml
|
||||
```
|
||||
|
||||
# 5) Create your first user.
|
||||
Scroll to `access_control.rules`. Uncomment the rule for each site you want to protect and choose a policy:
|
||||
|
||||
```yaml
|
||||
- domain: 'cam.{{ env "DOMAIN" }}'
|
||||
policy: 'two_factor'
|
||||
```
|
||||
|
||||
#### Why both sides are required
|
||||
|
||||
Caddy and Authelia each control one half of the gate:
|
||||
|
||||
| What you configure | What it does |
|
||||
|--------------------|-------------|
|
||||
| `import authelia` in a Caddy site block | Sends that site's requests to Authelia for a decision |
|
||||
| Rule in `access_control.rules` | Tells Authelia what decision to make |
|
||||
|
||||
The default policy is `deny`. If a request reaches Authelia with no matching rule, it gets a **403 Forbidden -- no login prompt, no redirect, just blocked**. This is true even for an already-logged-in user.
|
||||
|
||||
Miss either side and here is what happens:
|
||||
|
||||
| Caddy `import authelia` | Rule in `configuration.yml` | Result |
|
||||
|------------------------|----------------------------|--------|
|
||||
| Missing | Present | Site is open -- Authelia is never consulted |
|
||||
| Present | Missing | 403 Forbidden, no login prompt |
|
||||
| Both missing | | Site is open -- Authelia is never consulted |
|
||||
| Both present | | Works correctly |
|
||||
|
||||
#### Which policy to use
|
||||
|
||||
| Policy | Requires |
|
||||
|--------|---------|
|
||||
| `bypass` | Nothing -- Authelia waves the request through. Used for the portal itself only. |
|
||||
| `one_factor` | Password only |
|
||||
| `two_factor` | Password + TOTP. Use this for everything. |
|
||||
|
||||
### 5. Create your first user
|
||||
|
||||
```bash
|
||||
cp authelia/users_database.yml.example authelia/users_database.yml
|
||||
$EDITOR authelia/users_database.yml # set username, email, displayname
|
||||
```
|
||||
|
||||
# Generate the password hash:
|
||||
```bash
|
||||
$EDITOR authelia/users_database.yml
|
||||
```
|
||||
|
||||
Fill in `username`, `email`, and `displayname`. Then generate the password hash:
|
||||
|
||||
```bash
|
||||
docker compose run --rm authelia \
|
||||
authelia crypto hash generate argon2 --password 'your-real-password'
|
||||
# Paste the $argon2id$... output into the password: field.
|
||||
authelia crypto hash generate argon2
|
||||
```
|
||||
|
||||
# 6) Pre-create the Authelia log file.
|
||||
# Docker creates a DIRECTORY at the bind-mount path if the file doesn't
|
||||
# exist, which breaks fail2ban's mount. Create it as an empty file first.
|
||||
Authelia prompts for the password and a confirmation without echoing --
|
||||
the plaintext never hits your shell history or `ps aux`. Copy the
|
||||
`Digest: $argon2id$...` line from the output and paste the digest
|
||||
(everything from `$argon2id` onward) as the `password:` value in
|
||||
`users_database.yml`.
|
||||
|
||||
### 6. Pre-create the Authelia log file
|
||||
|
||||
Docker creates a **directory** at a bind-mount path if the source file does not exist yet. That breaks fail2ban's read-only mount. Create it as an empty file first:
|
||||
|
||||
```bash
|
||||
touch authelia/authelia.log
|
||||
```
|
||||
|
||||
# 7) Validate config before starting.
|
||||
### 7. Validate the config
|
||||
|
||||
```bash
|
||||
docker compose run --rm authelia \
|
||||
authelia validate-config --config /config/configuration.yml
|
||||
# Expect: "Configuration: validation complete" with no errors.
|
||||
|
||||
# 8) Wire up Caddy (see "Wire Caddy into Authelia" below).
|
||||
|
||||
# 9) Bring it up.
|
||||
docker compose up -d
|
||||
docker compose logs -f authelia # expect "Authelia is listening on ..."
|
||||
docker compose logs -f fail2ban # expect "Jail authelia is now active"
|
||||
```
|
||||
|
||||
Expect: `Configuration: validation complete` with no errors. Fix any YAML issues before continuing.
|
||||
|
||||
### 8. Wire Caddy
|
||||
|
||||
See [Wire Caddy into Authelia](#wire-caddy-into-authelia) below. Add the Caddy site block for each site alongside the rule you added in step 4.
|
||||
|
||||
### 9. Bring it up
|
||||
|
||||
```bash
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
Confirm both services started cleanly:
|
||||
|
||||
```bash
|
||||
docker compose logs -f authelia
|
||||
```
|
||||
|
||||
Expect: `Authelia is listening on ...`
|
||||
|
||||
```bash
|
||||
docker compose logs -f fail2ban
|
||||
```
|
||||
|
||||
Expect: `Jail authelia is now active`
|
||||
|
||||
## Wire Caddy into Authelia
|
||||
|
||||
Open `caddy/Caddyfile`. It defines:
|
||||
Open `caddy/snippets.caddyfile`. It contains copy-paste blocks for your
|
||||
existing Caddyfile, not a replacement for it:
|
||||
|
||||
- `(authelia)` -- reusable snippet: add `import authelia` to any site block.
|
||||
- `(accesslog)` -- writes Caddy's JSON access log to `/var/log/caddy/access.log`
|
||||
so fail2ban's `caddy-4xx` jail can watch it.
|
||||
- `auth.example.com` -- the Authelia portal.
|
||||
- Example site blocks for all four cases (cases 1-3 active, case 4 commented).
|
||||
- `(authelia)` and `(accesslog)` snippet definitions -- paste once near the
|
||||
top of your Caddyfile.
|
||||
- `auth.{env.DOMAIN}` -- the Authelia portal block.
|
||||
- Per-service examples for all four cases (Frigate, Grafana, Gitea,
|
||||
Uptime Kuma, Homer, etc.) with the required per-app config notes inline.
|
||||
|
||||
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.
|
||||
Copy the blocks you need into your real Caddyfile and replace `192.168.x.x`
|
||||
with real upstream IPs. The domain is already templated as `{env.DOMAIN}` --
|
||||
just make sure the `DOMAIN` environment variable is available to Caddy:
|
||||
|
||||
```bash
|
||||
# Dockerized Caddy -- add to its .env or compose environment:
|
||||
DOMAIN=yourdomain.com
|
||||
|
||||
# System Caddy -- add to /etc/caddy/caddy.env (or wherever systemd reads env):
|
||||
echo 'DOMAIN=yourdomain.com' | sudo tee -a /etc/caddy/caddy.env
|
||||
# Then make sure the systemd unit loads it:
|
||||
# EnvironmentFile=/etc/caddy/caddy.env (in the [Service] section)
|
||||
sudo systemctl daemon-reload && sudo systemctl reload caddy
|
||||
```
|
||||
|
||||
**Every** site block should have `import accesslog` -- even case 4 sites.
|
||||
fail2ban's caddy-4xx jail watches the one log file and covers all your
|
||||
@@ -287,12 +489,19 @@ sudo chown caddy:caddy /var/log/caddy # adjust to your Caddy UID
|
||||
|
||||
Edit `frigate_config/config.yml` in your Frigate stack:
|
||||
|
||||
First, find your `caddy_net` subnet -- you need this for `trusted_proxies`:
|
||||
|
||||
```bash
|
||||
docker network inspect caddy_net | jq '.[0].IPAM.Config'
|
||||
```
|
||||
|
||||
Then edit `frigate_config/config.yml`:
|
||||
|
||||
```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'
|
||||
- 172.18.0.0/16 # replace with your caddy_net subnet from above
|
||||
|
||||
proxy:
|
||||
header_map:
|
||||
@@ -301,21 +510,28 @@ proxy:
|
||||
default_role: viewer
|
||||
separator: '|'
|
||||
# Optional shared secret -- prevents LAN header spoofing.
|
||||
# Generate: openssl rand -hex 32
|
||||
# Set the same value as `header_up X-Proxy-Secret` in caddy/Caddyfile.
|
||||
# auth_secret: 'your-32-byte-hex'
|
||||
```
|
||||
|
||||
Then uncomment `cam.example.com` in `authelia/configuration.yml`, restart
|
||||
both services:
|
||||
To generate the optional `auth_secret`:
|
||||
|
||||
```bash
|
||||
openssl rand -hex 32
|
||||
```
|
||||
|
||||
Set the same value in the `header_up X-Proxy-Secret` line in your Caddy site block.
|
||||
|
||||
Add the rule to `authelia/configuration.yml` (step 4 of first-run), then restart both services:
|
||||
|
||||
```bash
|
||||
docker compose restart authelia
|
||||
```
|
||||
|
||||
```bash
|
||||
docker compose restart frigate # in your Frigate stack
|
||||
```
|
||||
|
||||
Verify: `https://cam.example.com` in a private window goes to Authelia and
|
||||
back without a Frigate login screen.
|
||||
Verify in a private browser window: `https://cam.yourdomain.com` should go to Authelia and back without a Frigate login screen.
|
||||
|
||||
## First login + TOTP enrollment
|
||||
|
||||
@@ -339,9 +555,10 @@ back without a Frigate login screen.
|
||||
Append to `authelia/users_database.yml`, generate a hash:
|
||||
```bash
|
||||
docker compose run --rm authelia \
|
||||
authelia crypto hash generate argon2 --password 'new-password'
|
||||
authelia crypto hash generate argon2
|
||||
```
|
||||
Paste the hash as `password:`. Restart or wait 5 minutes for auto-reload.
|
||||
Authelia prompts for the password (no echo, not in shell history). Paste
|
||||
the printed digest as `password:`. Restart or wait 5 minutes for auto-reload.
|
||||
|
||||
### Disable a user
|
||||
|
||||
@@ -436,18 +653,26 @@ notifier:
|
||||
# password loaded via AUTHELIA_NOTIFIER_SMTP_PASSWORD_FILE
|
||||
```
|
||||
|
||||
Add the secret and wire it up:
|
||||
Add the secret file:
|
||||
|
||||
```bash
|
||||
echo 'your_smtp_password' > authelia/secrets/SMTP_PASSWORD
|
||||
chmod 600 authelia/secrets/SMTP_PASSWORD
|
||||
```
|
||||
|
||||
Add to the authelia service environment in `docker-compose.yml`:
|
||||
Add to the `authelia` service `environment:` block in `docker-compose.yml`:
|
||||
|
||||
```yaml
|
||||
- AUTHELIA_NOTIFIER_SMTP_PASSWORD_FILE=/secrets/SMTP_PASSWORD
|
||||
```
|
||||
|
||||
Restart and look for `"Notifier SMTP startup check successful"` in logs.
|
||||
Restart and check for a successful startup message:
|
||||
|
||||
```bash
|
||||
docker compose restart authelia && docker compose logs -f authelia
|
||||
```
|
||||
|
||||
Expect: `Notifier SMTP startup check successful`
|
||||
|
||||
## Security notes
|
||||
|
||||
@@ -472,8 +697,18 @@ of it served over HTTPS. Mixed HTTP/HTTPS won't work; the session cookie is
|
||||
|
||||
### "access denied" with no login prompt
|
||||
|
||||
`default_policy: deny` and no `access_control` rule for this domain. Add a
|
||||
rule in `authelia/configuration.yml` and restart Authelia.
|
||||
`default_policy: deny` -- a request reached Authelia with no matching rule for that domain. Add a rule under `access_control.rules` in `authelia/configuration.yml`:
|
||||
|
||||
```yaml
|
||||
- domain: 'yoursite.{{ env "DOMAIN" }}'
|
||||
policy: 'two_factor'
|
||||
```
|
||||
|
||||
Then restart Authelia:
|
||||
|
||||
```bash
|
||||
docker compose restart authelia
|
||||
```
|
||||
|
||||
### Authelia container restarts forever
|
||||
|
||||
@@ -486,8 +721,21 @@ Most often: missing/empty secret files in `authelia/secrets/`, bad YAML in
|
||||
|
||||
### Caddy can't resolve `authelia`
|
||||
|
||||
Caddy isn't on `caddy_net`. Add `networks: [caddy_net]` to your Caddy
|
||||
service and `caddy_net: external: true` at the bottom of its compose, then:
|
||||
Caddy isn't on `caddy_net`. Add this to your Caddy service in its compose file:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
caddy:
|
||||
networks:
|
||||
- caddy_net
|
||||
|
||||
networks:
|
||||
caddy_net:
|
||||
external: true
|
||||
```
|
||||
|
||||
Then recreate the Caddy container:
|
||||
|
||||
```bash
|
||||
docker compose up -d caddy
|
||||
```
|
||||
|
||||
+100
-27
@@ -11,6 +11,10 @@
|
||||
# Secrets are NOT in this file. They are loaded from files mounted at
|
||||
# /secrets via the AUTHELIA_*_FILE env vars in docker-compose.yml.
|
||||
#
|
||||
# Your domain comes from the DOMAIN variable in .env -- no manual
|
||||
# find-and-replace needed. Authelia 4.38+ processes this file as a Go
|
||||
# template, so {{ env "DOMAIN" }} is substituted at startup.
|
||||
#
|
||||
# After editing, validate before restarting:
|
||||
# docker compose run --rm authelia authelia validate-config --config /config/configuration.yml
|
||||
###############################################################################
|
||||
@@ -37,7 +41,7 @@ identity_validation:
|
||||
|
||||
totp:
|
||||
disable: false
|
||||
issuer: 'example.com' # CHANGE: your root domain (shown in authenticator app)
|
||||
issuer: '{{ env "DOMAIN" }}' # shown in your authenticator app
|
||||
algorithm: 'sha1'
|
||||
digits: 6
|
||||
period: 30
|
||||
@@ -75,8 +79,6 @@ authentication_backend:
|
||||
# ---------------------------------------------------------------------------
|
||||
# WHICH SITES NEED A RULE HERE?
|
||||
#
|
||||
# There are four ways a site can relate to Authelia:
|
||||
#
|
||||
# CASE 1 -- App has NO built-in auth (e.g. Pi doorbell PTT page).
|
||||
# -> Rule required + `import authelia` in Caddy.
|
||||
# -> Authelia is the ONLY login. Use two_factor for hardware-control pages.
|
||||
@@ -104,43 +106,114 @@ access_control:
|
||||
rules:
|
||||
|
||||
# The Authelia portal itself is always bypass.
|
||||
- domain: 'auth.example.com' # CHANGE
|
||||
- domain: 'auth.{{ env "DOMAIN" }}'
|
||||
policy: 'bypass'
|
||||
|
||||
# -------------------------------------------------------------------
|
||||
# CASE 1: No app auth -- Authelia is the only gate.
|
||||
# The Pi doorbell PTT page has no built-in authentication.
|
||||
# two_factor is appropriate -- this URL controls a speaker in your house.
|
||||
# -------------------------------------------------------------------
|
||||
# - domain: 'doorbell.example.com' # CHANGE
|
||||
# ===================================================================
|
||||
# CASE 1 -- No app auth. Authelia is the only gate.
|
||||
# Example: Pi doorbell PTT page (Flask server, no built-in auth).
|
||||
# two_factor is right -- this URL controls a speaker in your house.
|
||||
#
|
||||
# ALSO PASTE INTO CADDYFILE (case 1 site block):
|
||||
# doorbell.{env.DOMAIN} {
|
||||
# import accesslog
|
||||
# import authelia
|
||||
# handle_path /frigate/* {
|
||||
# reverse_proxy 192.168.x.x:8971 # CHANGE: Frigate IP
|
||||
# }
|
||||
# handle {
|
||||
# reverse_proxy 192.168.x.x:5555 # CHANGE: Pi IP
|
||||
# }
|
||||
# }
|
||||
# ===================================================================
|
||||
# - domain: 'doorbell.{{ env "DOMAIN" }}'
|
||||
# policy: 'two_factor'
|
||||
|
||||
# -------------------------------------------------------------------
|
||||
# CASE 2: App supports trusted-header proxy auth -- replace app login.
|
||||
# Frigate 0.14+: set `auth.enabled: False` and configure `proxy:` in
|
||||
# frigate_config/config.yml (see README.md "Switching Frigate to Authelia").
|
||||
# Single login: Authelia authenticates, Frigate reads Remote-User/Groups.
|
||||
# -------------------------------------------------------------------
|
||||
# - domain: 'cam.example.com' # CHANGE
|
||||
# ===================================================================
|
||||
# CASE 2a -- App supports trusted-header proxy auth.
|
||||
# Authelia replaces the app's login form. Single login; the app reads
|
||||
# Remote-User from the upstream request for its own role mapping.
|
||||
# Example: Frigate 0.14+.
|
||||
#
|
||||
# ALSO PASTE INTO CADDYFILE (case 2a site block):
|
||||
# cam.{env.DOMAIN} {
|
||||
# import accesslog
|
||||
# import authelia
|
||||
# reverse_proxy 192.168.x.x:8971 { # CHANGE: Frigate IP
|
||||
# transport http { read_timeout 60s; write_timeout 60s }
|
||||
# # header_up X-Proxy-Secret "32-byte-hex" # if Frigate auth_secret set
|
||||
# }
|
||||
# }
|
||||
#
|
||||
# ALSO EDIT frigate_config/config.yml IN THE FRIGATE REPO:
|
||||
# auth:
|
||||
# enabled: False
|
||||
# trusted_proxies: [172.18.0.0/16] # caddy_net subnet
|
||||
# proxy:
|
||||
# header_map: {user: remote-user, role: remote-groups}
|
||||
# default_role: viewer
|
||||
# separator: '|'
|
||||
# ===================================================================
|
||||
# - domain: 'cam.{{ env "DOMAIN" }}'
|
||||
# policy: 'two_factor'
|
||||
|
||||
# -------------------------------------------------------------------
|
||||
# CASE 3: App keeps its own auth; Authelia adds a 2FA gate in front.
|
||||
# The app's login form is still shown after Authelia passes the request.
|
||||
# User logs into Authelia (2FA) then into the app separately.
|
||||
# -------------------------------------------------------------------
|
||||
# - domain: 'nas.example.com' # CHANGE/REMOVE example
|
||||
# ===================================================================
|
||||
# CASE 2b -- App supports OIDC. Authelia is the OIDC provider.
|
||||
# Caddy block is identical to case 2a; the difference is on the app
|
||||
# side (token exchange, not header). REQUIRES additional setup of
|
||||
# identity_providers.oidc below this access_control block, with one
|
||||
# client per app -- see Authelia OIDC docs.
|
||||
# Example: Audiobookshelf.
|
||||
#
|
||||
# ALSO PASTE INTO CADDYFILE (case 2b site block):
|
||||
# books.{env.DOMAIN} {
|
||||
# import accesslog
|
||||
# import authelia
|
||||
# reverse_proxy 192.168.x.x:13378 # CHANGE IP
|
||||
# }
|
||||
# ===================================================================
|
||||
# - domain: 'books.{{ env "DOMAIN" }}'
|
||||
# policy: 'two_factor'
|
||||
|
||||
# CASE 4: No rule here, no `import authelia` in Caddy. App handles auth.
|
||||
# ===================================================================
|
||||
# CASE 3 -- App keeps its own login. Authelia adds a 2FA gate in
|
||||
# front. User authenticates with Authelia (2FA), then with the app
|
||||
# itself. Two logins, but Authelia's 2FA covers apps that don't
|
||||
# support proxy headers OR OIDC OR native TOTP.
|
||||
# Example: Uptime Kuma, Portainer (without OIDC), router admin.
|
||||
#
|
||||
# ALSO PASTE INTO CADDYFILE (case 3 site block):
|
||||
# uptime.{env.DOMAIN} {
|
||||
# import accesslog
|
||||
# import authelia
|
||||
# reverse_proxy 192.168.x.x:3001 # CHANGE IP
|
||||
# }
|
||||
# ===================================================================
|
||||
# - domain: 'uptime.{{ env "DOMAIN" }}'
|
||||
# policy: 'two_factor'
|
||||
|
||||
# ===================================================================
|
||||
# CASE 4 -- App handles its own auth. NO rule needed here.
|
||||
# Authelia is never consulted. Caddy still imports accesslog so
|
||||
# fail2ban watches the subdomain for scanners.
|
||||
# Example: Plex/Emby (native clients break with Authelia redirects),
|
||||
# Syncthing, anything you've decided to leave alone.
|
||||
#
|
||||
# ONLY PASTE INTO CADDYFILE -- nothing here in configuration.yml:
|
||||
# plex.{env.DOMAIN} {
|
||||
# import accesslog # NO import authelia
|
||||
# reverse_proxy 192.168.x.x:32400 # CHANGE IP
|
||||
# }
|
||||
# ===================================================================
|
||||
# (no rule -- case 4 is the absence of one)
|
||||
|
||||
session:
|
||||
# secret loaded via AUTHELIA_SESSION_SECRET_FILE
|
||||
cookies:
|
||||
- name: 'authelia_session'
|
||||
domain: 'example.com' # CHANGE: your root domain
|
||||
authelia_url: 'https://auth.example.com' # CHANGE
|
||||
default_redirection_url: 'https://example.com' # CHANGE
|
||||
domain: '{{ env "DOMAIN" }}'
|
||||
authelia_url: 'https://auth.{{ env "DOMAIN" }}'
|
||||
default_redirection_url: 'https://{{ env "DOMAIN" }}'
|
||||
expiration: '1 hour'
|
||||
inactivity: '5 minutes'
|
||||
remember_me: '1 month'
|
||||
|
||||
@@ -2,13 +2,17 @@
|
||||
###############################################################################
|
||||
# Authelia users database
|
||||
#
|
||||
# Copy this to users_database.yml (gitignored) and edit. Generate the
|
||||
# Copy this to users_database.yml (gitignored) and edit. Generate each
|
||||
# password hash with:
|
||||
#
|
||||
# docker compose run --rm authelia \
|
||||
# authelia crypto hash generate argon2 --password 'your-plaintext-pass'
|
||||
# authelia crypto hash generate argon2
|
||||
#
|
||||
# Authelia prompts for the password and a confirmation without echoing,
|
||||
# so the plaintext never hits shell history or `ps aux`. Output ends
|
||||
# with `Digest: $argon2id$v=19$m=...`. Paste the digest (everything from
|
||||
# `$argon2id` onward) as the `password:` value below.
|
||||
#
|
||||
# Paste the resulting `$argon2id$v=19$m=...` string as the `password:` value.
|
||||
# Restart Authelia for changes to take effect (or wait refresh_interval).
|
||||
###############################################################################
|
||||
|
||||
|
||||
-203
@@ -1,203 +0,0 @@
|
||||
# =============================================================================
|
||||
# Caddyfile -- Authelia + fail2ban integration
|
||||
#
|
||||
# Copy this file into your Caddy setup (or merge the relevant blocks into
|
||||
# your existing Caddyfile), edit all placeholders, then reload:
|
||||
#
|
||||
# # System Caddy:
|
||||
# sudo caddy validate --config /etc/caddy/Caddyfile
|
||||
# sudo systemctl reload caddy
|
||||
#
|
||||
# # Dockerized Caddy:
|
||||
# docker compose exec caddy caddy reload --config /etc/caddy/Caddyfile
|
||||
#
|
||||
# Placeholders to replace:
|
||||
# example.com -> your real root domain
|
||||
# 192.168.x.x -> real upstream LAN IPs
|
||||
#
|
||||
# Requirements:
|
||||
# - Caddy v2.5.1+ (for `forward_auth` directive; tested on v2.11.2)
|
||||
# - Caddy must be on the `caddy_net` Docker network so it can resolve
|
||||
# `authelia` by container name. In your Caddy compose:
|
||||
# networks: [caddy_net]
|
||||
# and at the bottom:
|
||||
# networks:
|
||||
# caddy_net:
|
||||
# external: true
|
||||
#
|
||||
# =============================================================================
|
||||
# DECISION TREE -- which sites go behind Authelia?
|
||||
#
|
||||
# CASE 1 -- App has NO built-in auth (e.g. Pi doorbell PTT page).
|
||||
# `import authelia` + rule in authelia/configuration.yml.
|
||||
# Authelia is the ONLY login. Use two_factor for hardware-control pages.
|
||||
#
|
||||
# CASE 2 -- App has built-in auth AND supports trusted-header proxy auth
|
||||
# (Frigate 0.14+, Grafana, Gitea, Nextcloud, Home Assistant, ...).
|
||||
# `import authelia` + rule in Authelia + disable the app's own login form.
|
||||
# Single Authelia login: Authelia authenticates, app reads Remote-User header.
|
||||
#
|
||||
# CASE 3 -- App has built-in auth and CANNOT switch to proxy auth, but you
|
||||
# want a 2FA gate in front anyway (router admin, legacy apps, etc.).
|
||||
# `import authelia` + rule in Authelia. App auth is untouched.
|
||||
# User logs into Authelia (2FA), then the app's own login form appears.
|
||||
#
|
||||
# CASE 4 -- App handles its own auth; Authelia not involved.
|
||||
# Plain `reverse_proxy`, no `import authelia`, no Authelia rule.
|
||||
# Traffic skips Authelia entirely.
|
||||
#
|
||||
# fail2ban coverage: import (accesslog) in EVERY site block -- gated or not.
|
||||
# The caddy-4xx jail watches /var/log/caddy/access.log and bans scanners
|
||||
# spraying all your subdomains, not just the Authelia-gated ones.
|
||||
# =============================================================================
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# (authelia) -- forward_auth gate.
|
||||
# Import into any site block you want gated (cases 1, 2, 3).
|
||||
# On success Authelia sets Remote-User, Remote-Groups, Remote-Email,
|
||||
# Remote-Name headers that the upstream app can consume for role mapping.
|
||||
# -----------------------------------------------------------------------------
|
||||
(authelia) {
|
||||
forward_auth authelia:9091 {
|
||||
uri /api/authz/forward-auth
|
||||
copy_headers Remote-User Remote-Groups Remote-Email Remote-Name
|
||||
}
|
||||
}
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# (accesslog) -- structured JSON access log consumed by fail2ban's caddy-4xx
|
||||
# jail. Import into EVERY site block so fail2ban covers your whole stack.
|
||||
#
|
||||
# Pre-create the log directory before starting Caddy:
|
||||
# sudo mkdir -p /var/log/caddy
|
||||
# sudo chown caddy:caddy /var/log/caddy # system Caddy
|
||||
# # Dockerized Caddy: add volumes: ["/var/log/caddy:/var/log/caddy"] to compose
|
||||
# -----------------------------------------------------------------------------
|
||||
(accesslog) {
|
||||
log {
|
||||
output file /var/log/caddy/access.log {
|
||||
roll_size 10MiB
|
||||
roll_keep 5
|
||||
roll_keep_for 720h
|
||||
}
|
||||
format json
|
||||
}
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# Authelia login portal
|
||||
# Never add `import authelia` here -- the `bypass` rule in
|
||||
# access_control.rules handles the portal itself. Adding forward_auth here
|
||||
# would cause a redirect loop.
|
||||
# =============================================================================
|
||||
auth.example.com { # CHANGE
|
||||
import accesslog
|
||||
reverse_proxy authelia:9091
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# CASE 1: Pi doorbell PTT page -- Authelia is the ONLY auth.
|
||||
#
|
||||
# The Pi's Flask server has no built-in authentication. Authelia gates it.
|
||||
# two_factor is appropriate -- this URL controls a speaker in your house.
|
||||
# Comment out until the Pi is deployed.
|
||||
# Also add (or uncomment) the doorbell.example.com rule in configuration.yml.
|
||||
# =============================================================================
|
||||
# doorbell.example.com { # CHANGE
|
||||
# import accesslog
|
||||
# import authelia
|
||||
#
|
||||
# # Same-origin proxy to Frigate so WebRTC fetch works without CORS.
|
||||
# handle_path /frigate/* {
|
||||
# reverse_proxy 192.168.x.x:8971 {
|
||||
# transport http {
|
||||
# read_timeout 60s
|
||||
# write_timeout 60s
|
||||
# }
|
||||
# }
|
||||
# }
|
||||
#
|
||||
# handle {
|
||||
# reverse_proxy 192.168.x.x:5555
|
||||
# }
|
||||
# }
|
||||
|
||||
# =============================================================================
|
||||
# CASE 2: Frigate UI -- Authelia replaces Frigate's own login form.
|
||||
#
|
||||
# Frigate 0.14+ supports trusted-header proxy auth. Authelia authenticates
|
||||
# the user (optionally with TOTP 2FA), then passes Remote-User and
|
||||
# Remote-Groups headers to Frigate which maps them to admin/viewer roles.
|
||||
#
|
||||
# To enable proxy auth in Frigate, edit frigate_config/config.yml:
|
||||
#
|
||||
# auth:
|
||||
# enabled: False
|
||||
# trusted_proxies:
|
||||
# - 172.18.0.0/16 # caddy_net subnet; find it with:
|
||||
# # docker network inspect caddy_net
|
||||
# proxy:
|
||||
# header_map:
|
||||
# user: remote-user # matches copy_headers in (authelia) snippet
|
||||
# role: remote-groups
|
||||
# default_role: viewer
|
||||
# separator: '|'
|
||||
# # Optional shared secret -- prevents LAN header spoofing.
|
||||
# # Generate: openssl rand -hex 32
|
||||
# # Set the same value as header_up X-Proxy-Secret below.
|
||||
# # auth_secret: 'your-32-byte-hex'
|
||||
#
|
||||
# Then uncomment the cam.example.com rule in authelia/configuration.yml
|
||||
# and restart: docker compose restart authelia (in the authelia stack)
|
||||
# docker compose restart frigate (in the camera stack)
|
||||
# =============================================================================
|
||||
cam.example.com { # CHANGE
|
||||
import accesslog
|
||||
import authelia
|
||||
|
||||
reverse_proxy 192.168.x.x:8971 { # CHANGE IP
|
||||
transport http {
|
||||
read_timeout 60s
|
||||
write_timeout 60s
|
||||
}
|
||||
# Uncomment if you set auth_secret: in Frigate's proxy: block.
|
||||
# header_up X-Proxy-Secret "your-32-byte-hex-here"
|
||||
}
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# CASE 3: App keeps its own login; Authelia adds a 2FA gate in front.
|
||||
#
|
||||
# Use when an app can't do proxy auth but you still want 2FA before it.
|
||||
# The user authenticates with Authelia (2FA), then the app's own login
|
||||
# form appears. Two separate logins -- the app's auth is untouched.
|
||||
#
|
||||
# Also add a rule in authelia/configuration.yml:
|
||||
# - domain: 'nas.example.com'
|
||||
# policy: 'two_factor'
|
||||
# =============================================================================
|
||||
# nas.example.com { # CHANGE/REMOVE example
|
||||
# import accesslog
|
||||
# import authelia
|
||||
#
|
||||
# reverse_proxy 192.168.x.x:PORT { # CHANGE
|
||||
# transport http {
|
||||
# tls_insecure_skip_verify # only if self-signed TLS
|
||||
# }
|
||||
# }
|
||||
# }
|
||||
|
||||
# =============================================================================
|
||||
# CASE 4: App handles its own auth; Authelia not involved.
|
||||
#
|
||||
# No `import authelia`. No access_control rule in Authelia.
|
||||
# Still import accesslog so fail2ban's caddy-4xx jail covers this site.
|
||||
# =============================================================================
|
||||
# router.example.com { # CHANGE/REMOVE example
|
||||
# import accesslog
|
||||
# reverse_proxy 192.168.x.x:PORT { # CHANGE
|
||||
# transport http {
|
||||
# tls_insecure_skip_verify
|
||||
# }
|
||||
# }
|
||||
# }
|
||||
@@ -0,0 +1,484 @@
|
||||
# =============================================================================
|
||||
# Authelia + fail2ban -- Caddy snippets
|
||||
#
|
||||
# These are SNIPPETS TO ADD to your existing Caddyfile, not a replacement
|
||||
# for it. Copy the (authelia) and (accesslog) snippet definitions once at
|
||||
# the top of your Caddyfile, then copy whichever site blocks apply.
|
||||
#
|
||||
# DOMAIN is read from the environment -- set it wherever your Caddy reads
|
||||
# env vars (Caddy's own .env, systemd EnvironmentFile, or compose env:).
|
||||
# Only the upstream IPs need manual editing.
|
||||
#
|
||||
# Caddy v2.5.1+ required; tested on v2.11.2.
|
||||
# =============================================================================
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Paste these two snippet definitions once, near the top of your Caddyfile.
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
# Gate any site block with Authelia by adding `import authelia` inside it.
|
||||
(authelia) {
|
||||
forward_auth authelia:9091 {
|
||||
uri /api/authz/forward-auth
|
||||
copy_headers Remote-User Remote-Groups Remote-Email Remote-Name
|
||||
}
|
||||
}
|
||||
|
||||
# JSON access log that fail2ban's caddy-4xx jail reads.
|
||||
# Add `import accesslog` to EVERY site block (gated or not) so fail2ban
|
||||
# catches scanners hitting all your subdomains, not just the protected ones.
|
||||
(accesslog) {
|
||||
log {
|
||||
output file /var/log/caddy/access.log {
|
||||
roll_size 10MiB
|
||||
roll_keep 5
|
||||
roll_keep_for 720h
|
||||
}
|
||||
format json
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Authelia portal -- always required; never put `import authelia` here.
|
||||
# =============================================================================
|
||||
auth.{env.DOMAIN} {
|
||||
import accesslog
|
||||
reverse_proxy authelia:9091
|
||||
}
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# CASE 1 -- App has NO built-in auth. Authelia is the only gate.
|
||||
#
|
||||
# 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)
|
||||
homer.{env.DOMAIN} {
|
||||
import accesslog
|
||||
import authelia
|
||||
reverse_proxy 192.168.x.x:8080 # CHANGE IP:PORT
|
||||
}
|
||||
|
||||
# Dozzle (Docker log viewer -- no auth by default)
|
||||
dozzle.{env.DOMAIN} {
|
||||
import accesslog
|
||||
import authelia
|
||||
reverse_proxy 192.168.x.x:8888 # CHANGE IP:PORT
|
||||
}
|
||||
|
||||
# --- 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
|
||||
# import authelia
|
||||
#
|
||||
# handle_path /frigate/* {
|
||||
# reverse_proxy 192.168.x.x:8971 # CHANGE: Frigate IP
|
||||
# }
|
||||
# handle {
|
||||
# reverse_proxy 192.168.x.x:5555 # CHANGE: Pi IP
|
||||
# }
|
||||
# }
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# 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.
|
||||
# =============================================================================
|
||||
|
||||
# --- Frigate 0.14+ ---
|
||||
# In frigate_config/config.yml:
|
||||
# auth:
|
||||
# enabled: False
|
||||
# trusted_proxies: [172.18.0.0/16] # caddy_net subnet
|
||||
# proxy:
|
||||
# header_map: {user: remote-user, role: remote-groups}
|
||||
# default_role: viewer
|
||||
# separator: '|'
|
||||
cam.{env.DOMAIN} {
|
||||
import accesslog
|
||||
import authelia
|
||||
reverse_proxy 192.168.x.x:8971 { # CHANGE IP
|
||||
transport http { read_timeout 60s; write_timeout 60s }
|
||||
# header_up X-Proxy-Secret "32-byte-hex" # if auth_secret: set in Frigate
|
||||
}
|
||||
}
|
||||
|
||||
# --- Grafana ---
|
||||
# In grafana.ini (or GF_* env vars):
|
||||
# [auth.proxy]
|
||||
# enabled = true
|
||||
# header_name = Remote-User
|
||||
# header_property = username
|
||||
# auto_sign_up = true
|
||||
grafana.{env.DOMAIN} {
|
||||
import accesslog
|
||||
import authelia
|
||||
reverse_proxy 192.168.x.x:3000 # CHANGE IP
|
||||
}
|
||||
|
||||
# --- Gitea / Forgejo ---
|
||||
# In app.ini:
|
||||
# [service]
|
||||
# ENABLE_REVERSE_PROXY_AUTHENTICATION = true
|
||||
# REVERSE_PROXY_TRUSTED_PROXIES = *
|
||||
git.{env.DOMAIN} {
|
||||
import accesslog
|
||||
import authelia
|
||||
reverse_proxy 192.168.x.x:3000 # CHANGE IP
|
||||
}
|
||||
|
||||
# --- Nextcloud ---
|
||||
# In config/config.php:
|
||||
# 'trusted_proxies' => ['172.18.0.0/16'],
|
||||
# 'overwriteprotocol' => 'https',
|
||||
# Plus user_external app + HTTP header auth set to Remote-User.
|
||||
cloud.{env.DOMAIN} {
|
||||
import accesslog
|
||||
import authelia
|
||||
reverse_proxy 192.168.x.x:80 { # CHANGE IP
|
||||
header_up Host {upstream_hostport}
|
||||
}
|
||||
}
|
||||
|
||||
# --- Paperless-ngx ---
|
||||
# In compose env:
|
||||
# PAPERLESS_ENABLE_HTTP_REMOTE_USER=true
|
||||
# PAPERLESS_HTTP_REMOTE_USER_HEADER_NAME=HTTP_REMOTE_USER
|
||||
paperless.{env.DOMAIN} {
|
||||
import accesslog
|
||||
import authelia
|
||||
reverse_proxy 192.168.x.x:8000 # CHANGE IP
|
||||
}
|
||||
|
||||
# --- Miniflux ---
|
||||
# In compose env:
|
||||
# AUTH_PROXY_HEADER=Remote-User
|
||||
# AUTH_PROXY_USER_CREATION=true
|
||||
miniflux.{env.DOMAIN} {
|
||||
import accesslog
|
||||
import authelia
|
||||
reverse_proxy 192.168.x.x:8080 # CHANGE IP
|
||||
}
|
||||
|
||||
# --- BookStack ---
|
||||
# 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 ---
|
||||
# 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
|
||||
# }
|
||||
|
||||
# --- 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)
|
||||
# }
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# 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 or OIDC, but you still
|
||||
# want 2FA before they're even reachable.
|
||||
# =============================================================================
|
||||
|
||||
# --- Uptime Kuma ---
|
||||
# No proxy auth, no native TOTP -- Authelia is the only way to add 2FA.
|
||||
uptime.{env.DOMAIN} {
|
||||
import accesslog
|
||||
import authelia
|
||||
reverse_proxy 192.168.x.x:3001 # CHANGE IP
|
||||
}
|
||||
|
||||
# --- Portainer (simple gate, no OIDC) ---
|
||||
portainer.{env.DOMAIN} {
|
||||
import accesslog
|
||||
import authelia
|
||||
reverse_proxy 192.168.x.x:9000 # CHANGE IP
|
||||
}
|
||||
|
||||
# --- Home Assistant ---
|
||||
# Can also do case 2a via the trusted_networks auth provider + header forwarding.
|
||||
# homeassistant.{env.DOMAIN} {
|
||||
# import accesslog
|
||||
# import authelia
|
||||
# reverse_proxy 192.168.x.x:8123 # CHANGE IP
|
||||
# }
|
||||
|
||||
# --- Vaultwarden ---
|
||||
# Has its own strong auth + native TOTP. Many skip Authelia here entirely
|
||||
# and rely on Vaultwarden's own 2FA (totally valid). Or use case 3 as an
|
||||
# extra gate if you want 2FA even before the login page loads.
|
||||
# vault.{env.DOMAIN} {
|
||||
# import accesslog
|
||||
# import authelia
|
||||
# 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 ---
|
||||
# DO NOT use `import authelia` with Plex/Emby. Plex is coupled to plex.tv
|
||||
# cloud auth -- Authelia cannot replace it. More importantly, native clients
|
||||
# (mobile, TV, desktop apps) connect via FQDN through Caddy but CANNOT
|
||||
# complete Authelia's browser-redirect login flow (no cookie, no TOTP prompt).
|
||||
# `import authelia` will break all native clients with a connection error.
|
||||
#
|
||||
# Correct approach: Case 4. Caddy terminates TLS and reverse proxies; Plex's
|
||||
# own token auth handles access control. `import accesslog` keeps fail2ban
|
||||
# watching the subdomain for scanners.
|
||||
#
|
||||
# plex.{env.DOMAIN} {
|
||||
# import accesslog
|
||||
# reverse_proxy 192.168.x.x:32400 # CHANGE IP -- NO import authelia
|
||||
# }
|
||||
|
||||
# --- Emby ---
|
||||
# Same situation as Plex: native clients go through Caddy but cannot handle
|
||||
# Authelia's login redirect. Use Case 4 -- Caddy + TLS, Emby's own auth.
|
||||
#
|
||||
# emby.{env.DOMAIN} {
|
||||
# import accesslog
|
||||
# reverse_proxy 192.168.x.x:8096 # CHANGE IP -- NO import authelia
|
||||
# }
|
||||
|
||||
# --- 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.
|
||||
# Still import accesslog so fail2ban covers this site.
|
||||
# =============================================================================
|
||||
|
||||
# --- Syncthing ---
|
||||
# sync.{env.DOMAIN} {
|
||||
# import accesslog
|
||||
# reverse_proxy 192.168.x.x:8384 # CHANGE IP
|
||||
# }
|
||||
|
||||
# --- Router / NAS admin UI ---
|
||||
# router.{env.DOMAIN} {
|
||||
# import accesslog
|
||||
# reverse_proxy 192.168.x.x:443 {
|
||||
# transport http { tls_insecure_skip_verify }
|
||||
# }
|
||||
# }
|
||||
@@ -34,6 +34,15 @@ services:
|
||||
- AUTHELIA_SESSION_SECRET_FILE=/secrets/SESSION_SECRET
|
||||
- AUTHELIA_STORAGE_ENCRYPTION_KEY_FILE=/secrets/STORAGE_ENCRYPTION_KEY
|
||||
- TZ=${TZ:-UTC}
|
||||
# Enables Go-template substitution in configuration.yml so
|
||||
# `{{ env "DOMAIN" }}` actually expands instead of being read as a
|
||||
# literal string. Without this, Authelia parses the braces as part
|
||||
# of the hostname and validate-config fails with `invalid character
|
||||
# "{" in host name`. Inherited by `docker compose run --rm authelia`,
|
||||
# so validate-config picks it up too.
|
||||
- X_AUTHELIA_CONFIG_FILTERS=template
|
||||
# Passed through so authelia/configuration.yml can use {{ env "DOMAIN" }}.
|
||||
- DOMAIN=${DOMAIN}
|
||||
volumes:
|
||||
- ./authelia:/config
|
||||
- ./authelia/secrets:/secrets:ro
|
||||
|
||||
Reference in New Issue
Block a user