Compare commits
10
Commits
711f892ada
...
c8d9c2921d
| 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
|
# so they can be mounted into the container without env-var leakage. This
|
||||||
# .env only holds non-secret tunables.
|
# .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 --
|
# Pin your image versions. Bump to current stable when you upgrade --
|
||||||
# check https://github.com/authelia/authelia/releases and
|
# check https://github.com/authelia/authelia/releases and
|
||||||
# https://github.com/crazy-max/docker-fail2ban/releases.
|
# 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).
|
- 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
|
- fail2ban bans via the `DOCKER-USER` iptables chain: drops happen at the host
|
||||||
edge before traffic reaches any docker-published port.
|
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
|
## Repo layout
|
||||||
|
|
||||||
@@ -56,7 +56,7 @@ authelia-stack/
|
|||||||
│ └── caddy.local # 30 fails/2 min -> 30 min IP ban
|
│ └── caddy.local # 30 fails/2 min -> 30 min IP ban
|
||||||
│
|
│
|
||||||
└── caddy/
|
└── caddy/
|
||||||
└── Caddyfile # copy/merge into your Caddy setup
|
└── snippets.caddyfile # per-service snippets to add to your existing Caddyfile
|
||||||
```
|
```
|
||||||
|
|
||||||
## Prerequisites
|
## 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 |
|
| 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. |
|
| **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. |
|
| **2a** | Yes | Yes (Remote-User header) | `import authelia` + disable app's own login form. Single login via headers. |
|
||||||
| **3** | Yes | No | `import authelia` in Caddy + rule in Authelia. App auth is unchanged. User logs into Authelia then the app. Two logins. |
|
| **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. |
|
| **4** | Yes | — | Plain `reverse_proxy`. No `import authelia`, no rule. App handles auth. |
|
||||||
|
|
||||||
Concretely:
|
Concretely:
|
||||||
|
|
||||||
- **`doorbell.example.com`** (Pi PTT page) -- **case 1**. No app auth at all.
|
- **`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.
|
Authelia is the only gate. Use `two_factor` -- this URL controls a speaker.
|
||||||
- **`cam.example.com`** (Frigate UI) -- **case 2**. Frigate 0.14+ supports
|
- **`cam.example.com`** (Frigate UI) -- **case 2a**. Frigate 0.14+ supports
|
||||||
proxy auth. Disable Frigate's login form and let Authelia drive both the
|
proxy auth via `Remote-User` header. Disable Frigate's login form and let
|
||||||
access gate and the role mapping (admin vs. viewer) via headers.
|
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,
|
- **Router admin / NAS UI** -- **case 3** if you want a 2FA gate in front,
|
||||||
**case 4** if you just leave it to the app.
|
**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
|
Default policy in `configuration.yml` is `deny`, so a domain with no rule
|
||||||
AND no `import authelia` in Caddy never reaches Authelia at all.
|
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)
|
### 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"
|
| App | Case | Notes |
|
||||||
- "Header-based authentication", "SSO via reverse proxy"
|
|-----|------|-------|
|
||||||
- Support for `X-Forwarded-User`, `X-Remote-User`, or `Remote-User`
|
| **Proxy-header auth (case 2a)** | | |
|
||||||
|
| Frigate 0.14+ | 2a | `auth.enabled: False` + `proxy:` block in config.yml |
|
||||||
| App | Proxy auth? | Notes |
|
| Grafana | 2a | `[auth.proxy]` in grafana.ini; `GF_AUTH_PROXY_ENABLED=true` |
|
||||||
|-----|-------------|-------|
|
| Gitea / Forgejo | 2a | `ENABLE_REVERSE_PROXY_AUTHENTICATION=true` in app.ini |
|
||||||
| Frigate 0.14+ | Yes | `auth.enabled: False` + `proxy:` block in config.yml |
|
| Nextcloud | 2a | `trusted_proxies` + `user_external` app + HTTP header auth |
|
||||||
| Grafana | Yes | `[auth.proxy]` section in grafana.ini |
|
| Paperless-ngx | 2a | `PAPERLESS_ENABLE_HTTP_REMOTE_USER=true` |
|
||||||
| Gitea / Forgejo | Yes | `REVERSE_PROXY_AUTHENTICATION_USER` in app.ini |
|
| Miniflux | 2a | `AUTH_PROXY_HEADER=Remote-User` env var |
|
||||||
| Nextcloud | Yes | `TRUSTED_PROXIES` env + `overwriteprotocol = https` |
|
| Home Assistant | 2a | `trusted_networks` auth provider + header forwarding |
|
||||||
| Home Assistant | Yes | `trusted_networks` auth provider + `use_x_forwarded_for` |
|
| BookStack | 2a | `AUTH_METHOD=http` + `HTTP_AUTH_HEADER=Remote-User` in .env |
|
||||||
| Jellyfin | Partial | Community plugin required |
|
| **OIDC auth (case 2b)** | | |
|
||||||
| Portainer | No | Use Authelia OIDC integration instead |
|
| Audiobookshelf | 2b | Native OIDC; configure in Settings > Authentication |
|
||||||
| Vaultwarden | No | Use Authelia OIDC integration instead |
|
| Jellyfin | 2b | Requires `Jellyfin.Plugin.SSO` from Plugin Catalogue |
|
||||||
| Router/NAS admin | Rarely | Use case 3 (2FA gate) or case 4 |
|
| 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
|
## Getting git and authenticating to GitHub
|
||||||
|
|
||||||
@@ -192,73 +281,186 @@ gh auth login
|
|||||||
|
|
||||||
## First-run setup
|
## 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
|
```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
|
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
|
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
|
mkdir -p authelia/secrets
|
||||||
openssl rand -hex 32 > authelia/secrets/JWT_SECRET
|
openssl rand -hex 32 > authelia/secrets/JWT_SECRET
|
||||||
openssl rand -hex 32 > authelia/secrets/SESSION_SECRET
|
openssl rand -hex 32 > authelia/secrets/SESSION_SECRET
|
||||||
openssl rand -hex 32 > authelia/secrets/STORAGE_ENCRYPTION_KEY
|
openssl rand -hex 32 > authelia/secrets/STORAGE_ENCRYPTION_KEY
|
||||||
chmod 600 authelia/secrets/*
|
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
|
cp .env.example .env
|
||||||
$EDITOR .env # set TZ; pin AUTHELIA_VERSION if you want
|
```
|
||||||
|
|
||||||
# 4) Edit authelia/configuration.yml.
|
```bash
|
||||||
# Replace every `example.com` with your real root domain.
|
$EDITOR .env
|
||||||
# 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.
|
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
|
$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
|
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 \
|
docker compose run --rm authelia \
|
||||||
authelia crypto hash generate argon2 --password 'your-real-password'
|
authelia crypto hash generate argon2
|
||||||
# Paste the $argon2id$... output into the password: field.
|
```
|
||||||
|
|
||||||
# 6) Pre-create the Authelia log file.
|
Authelia prompts for the password and a confirmation without echoing --
|
||||||
# Docker creates a DIRECTORY at the bind-mount path if the file doesn't
|
the plaintext never hits your shell history or `ps aux`. Copy the
|
||||||
# exist, which breaks fail2ban's mount. Create it as an empty file first.
|
`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
|
touch authelia/authelia.log
|
||||||
|
```
|
||||||
|
|
||||||
# 7) Validate config before starting.
|
### 7. Validate the config
|
||||||
|
|
||||||
|
```bash
|
||||||
docker compose run --rm authelia \
|
docker compose run --rm authelia \
|
||||||
authelia validate-config --config /config/configuration.yml
|
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
|
## 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.
|
- `(authelia)` and `(accesslog)` snippet definitions -- paste once near the
|
||||||
- `(accesslog)` -- writes Caddy's JSON access log to `/var/log/caddy/access.log`
|
top of your Caddyfile.
|
||||||
so fail2ban's `caddy-4xx` jail can watch it.
|
- `auth.{env.DOMAIN}` -- the Authelia portal block.
|
||||||
- `auth.example.com` -- the Authelia portal.
|
- Per-service examples for all four cases (Frigate, Grafana, Gitea,
|
||||||
- Example site blocks for all four cases (cases 1-3 active, case 4 commented).
|
Uptime Kuma, Homer, etc.) with the required per-app config notes inline.
|
||||||
|
|
||||||
Copy the relevant blocks into your real Caddyfile, replace `example.com` with
|
Copy the blocks you need into your real Caddyfile and replace `192.168.x.x`
|
||||||
your domain and `192.168.x.x` with real upstream IPs, then reload Caddy.
|
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.
|
**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
|
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:
|
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
|
```yaml
|
||||||
auth:
|
auth:
|
||||||
enabled: False
|
enabled: False
|
||||||
trusted_proxies:
|
trusted_proxies:
|
||||||
- 172.18.0.0/16 # the caddy_net subnet -- find it with:
|
- 172.18.0.0/16 # replace with your caddy_net subnet from above
|
||||||
# docker network inspect caddy_net | jq '.[0].IPAM.Config'
|
|
||||||
|
|
||||||
proxy:
|
proxy:
|
||||||
header_map:
|
header_map:
|
||||||
@@ -301,21 +510,28 @@ proxy:
|
|||||||
default_role: viewer
|
default_role: viewer
|
||||||
separator: '|'
|
separator: '|'
|
||||||
# Optional shared secret -- prevents LAN header spoofing.
|
# 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'
|
# auth_secret: 'your-32-byte-hex'
|
||||||
```
|
```
|
||||||
|
|
||||||
Then uncomment `cam.example.com` in `authelia/configuration.yml`, restart
|
To generate the optional `auth_secret`:
|
||||||
both services:
|
|
||||||
|
```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
|
```bash
|
||||||
docker compose restart authelia
|
docker compose restart authelia
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
docker compose restart frigate # in your Frigate stack
|
docker compose restart frigate # in your Frigate stack
|
||||||
```
|
```
|
||||||
|
|
||||||
Verify: `https://cam.example.com` in a private window goes to Authelia and
|
Verify in a private browser window: `https://cam.yourdomain.com` should go to Authelia and back without a Frigate login screen.
|
||||||
back without a Frigate login screen.
|
|
||||||
|
|
||||||
## First login + TOTP enrollment
|
## First login + TOTP enrollment
|
||||||
|
|
||||||
@@ -339,9 +555,10 @@ back without a Frigate login screen.
|
|||||||
Append to `authelia/users_database.yml`, generate a hash:
|
Append to `authelia/users_database.yml`, generate a hash:
|
||||||
```bash
|
```bash
|
||||||
docker compose run --rm authelia \
|
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
|
### Disable a user
|
||||||
|
|
||||||
@@ -436,18 +653,26 @@ notifier:
|
|||||||
# password loaded via AUTHELIA_NOTIFIER_SMTP_PASSWORD_FILE
|
# password loaded via AUTHELIA_NOTIFIER_SMTP_PASSWORD_FILE
|
||||||
```
|
```
|
||||||
|
|
||||||
Add the secret and wire it up:
|
Add the secret file:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
echo 'your_smtp_password' > authelia/secrets/SMTP_PASSWORD
|
echo 'your_smtp_password' > authelia/secrets/SMTP_PASSWORD
|
||||||
chmod 600 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
|
```yaml
|
||||||
- AUTHELIA_NOTIFIER_SMTP_PASSWORD_FILE=/secrets/SMTP_PASSWORD
|
- 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
|
## 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
|
### "access denied" with no login prompt
|
||||||
|
|
||||||
`default_policy: deny` and no `access_control` rule for this domain. Add a
|
`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`:
|
||||||
rule in `authelia/configuration.yml` and restart Authelia.
|
|
||||||
|
```yaml
|
||||||
|
- domain: 'yoursite.{{ env "DOMAIN" }}'
|
||||||
|
policy: 'two_factor'
|
||||||
|
```
|
||||||
|
|
||||||
|
Then restart Authelia:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose restart authelia
|
||||||
|
```
|
||||||
|
|
||||||
### Authelia container restarts forever
|
### 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 can't resolve `authelia`
|
||||||
|
|
||||||
Caddy isn't on `caddy_net`. Add `networks: [caddy_net]` to your Caddy
|
Caddy isn't on `caddy_net`. Add this to your Caddy service in its compose file:
|
||||||
service and `caddy_net: external: true` at the bottom of its compose, then:
|
|
||||||
|
```yaml
|
||||||
|
services:
|
||||||
|
caddy:
|
||||||
|
networks:
|
||||||
|
- caddy_net
|
||||||
|
|
||||||
|
networks:
|
||||||
|
caddy_net:
|
||||||
|
external: true
|
||||||
|
```
|
||||||
|
|
||||||
|
Then recreate the Caddy container:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker compose up -d caddy
|
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 are NOT in this file. They are loaded from files mounted at
|
||||||
# /secrets via the AUTHELIA_*_FILE env vars in docker-compose.yml.
|
# /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:
|
# After editing, validate before restarting:
|
||||||
# docker compose run --rm authelia authelia validate-config --config /config/configuration.yml
|
# docker compose run --rm authelia authelia validate-config --config /config/configuration.yml
|
||||||
###############################################################################
|
###############################################################################
|
||||||
@@ -37,7 +41,7 @@ identity_validation:
|
|||||||
|
|
||||||
totp:
|
totp:
|
||||||
disable: false
|
disable: false
|
||||||
issuer: 'example.com' # CHANGE: your root domain (shown in authenticator app)
|
issuer: '{{ env "DOMAIN" }}' # shown in your authenticator app
|
||||||
algorithm: 'sha1'
|
algorithm: 'sha1'
|
||||||
digits: 6
|
digits: 6
|
||||||
period: 30
|
period: 30
|
||||||
@@ -75,8 +79,6 @@ authentication_backend:
|
|||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# WHICH SITES NEED A RULE HERE?
|
# 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).
|
# CASE 1 -- App has NO built-in auth (e.g. Pi doorbell PTT page).
|
||||||
# -> Rule required + `import authelia` in Caddy.
|
# -> Rule required + `import authelia` in Caddy.
|
||||||
# -> Authelia is the ONLY login. Use two_factor for hardware-control pages.
|
# -> Authelia is the ONLY login. Use two_factor for hardware-control pages.
|
||||||
@@ -104,43 +106,114 @@ access_control:
|
|||||||
rules:
|
rules:
|
||||||
|
|
||||||
# The Authelia portal itself is always bypass.
|
# The Authelia portal itself is always bypass.
|
||||||
- domain: 'auth.example.com' # CHANGE
|
- domain: 'auth.{{ env "DOMAIN" }}'
|
||||||
policy: 'bypass'
|
policy: 'bypass'
|
||||||
|
|
||||||
# -------------------------------------------------------------------
|
# ===================================================================
|
||||||
# CASE 1: No app auth -- Authelia is the only gate.
|
# CASE 1 -- No app auth. Authelia is the only gate.
|
||||||
# The Pi doorbell PTT page has no built-in authentication.
|
# Example: Pi doorbell PTT page (Flask server, no built-in auth).
|
||||||
# two_factor is appropriate -- this URL controls a speaker in your house.
|
# two_factor is right -- this URL controls a speaker in your house.
|
||||||
# -------------------------------------------------------------------
|
#
|
||||||
# - domain: 'doorbell.example.com' # CHANGE
|
# 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'
|
# policy: 'two_factor'
|
||||||
|
|
||||||
# -------------------------------------------------------------------
|
# ===================================================================
|
||||||
# CASE 2: App supports trusted-header proxy auth -- replace app login.
|
# CASE 2a -- App supports trusted-header proxy auth.
|
||||||
# Frigate 0.14+: set `auth.enabled: False` and configure `proxy:` in
|
# Authelia replaces the app's login form. Single login; the app reads
|
||||||
# frigate_config/config.yml (see README.md "Switching Frigate to Authelia").
|
# Remote-User from the upstream request for its own role mapping.
|
||||||
# Single login: Authelia authenticates, Frigate reads Remote-User/Groups.
|
# Example: Frigate 0.14+.
|
||||||
# -------------------------------------------------------------------
|
#
|
||||||
# - domain: 'cam.example.com' # CHANGE
|
# 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'
|
# policy: 'two_factor'
|
||||||
|
|
||||||
# -------------------------------------------------------------------
|
# ===================================================================
|
||||||
# CASE 3: App keeps its own auth; Authelia adds a 2FA gate in front.
|
# CASE 2b -- App supports OIDC. Authelia is the OIDC provider.
|
||||||
# The app's login form is still shown after Authelia passes the request.
|
# Caddy block is identical to case 2a; the difference is on the app
|
||||||
# User logs into Authelia (2FA) then into the app separately.
|
# side (token exchange, not header). REQUIRES additional setup of
|
||||||
# -------------------------------------------------------------------
|
# identity_providers.oidc below this access_control block, with one
|
||||||
# - domain: 'nas.example.com' # CHANGE/REMOVE example
|
# 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'
|
# 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:
|
session:
|
||||||
# secret loaded via AUTHELIA_SESSION_SECRET_FILE
|
# secret loaded via AUTHELIA_SESSION_SECRET_FILE
|
||||||
cookies:
|
cookies:
|
||||||
- name: 'authelia_session'
|
- name: 'authelia_session'
|
||||||
domain: 'example.com' # CHANGE: your root domain
|
domain: '{{ env "DOMAIN" }}'
|
||||||
authelia_url: 'https://auth.example.com' # CHANGE
|
authelia_url: 'https://auth.{{ env "DOMAIN" }}'
|
||||||
default_redirection_url: 'https://example.com' # CHANGE
|
default_redirection_url: 'https://{{ env "DOMAIN" }}'
|
||||||
expiration: '1 hour'
|
expiration: '1 hour'
|
||||||
inactivity: '5 minutes'
|
inactivity: '5 minutes'
|
||||||
remember_me: '1 month'
|
remember_me: '1 month'
|
||||||
|
|||||||
@@ -2,13 +2,17 @@
|
|||||||
###############################################################################
|
###############################################################################
|
||||||
# Authelia users database
|
# 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:
|
# password hash with:
|
||||||
#
|
#
|
||||||
# docker compose run --rm authelia \
|
# 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).
|
# 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_SESSION_SECRET_FILE=/secrets/SESSION_SECRET
|
||||||
- AUTHELIA_STORAGE_ENCRYPTION_KEY_FILE=/secrets/STORAGE_ENCRYPTION_KEY
|
- AUTHELIA_STORAGE_ENCRYPTION_KEY_FILE=/secrets/STORAGE_ENCRYPTION_KEY
|
||||||
- TZ=${TZ:-UTC}
|
- 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:
|
volumes:
|
||||||
- ./authelia:/config
|
- ./authelia:/config
|
||||||
- ./authelia/secrets:/secrets:ro
|
- ./authelia/secrets:/secrets:ro
|
||||||
|
|||||||
Reference in New Issue
Block a user