Update authelia stack: 4 auth cases, full Caddyfile, improved docs
Keeps this as a standalone authelia+fail2ban stack (no Frigate services). Changes: - docker-compose.yml: fail2ban depends_on authelia with service_healthy condition so authelia.log exists before fail2ban tries to bind-mount it; add inline note about pre-creating the log file - authelia/configuration.yml: expand access_control comment block to cover all 4 cases (added Case 3: app keeps own auth + Authelia as 2FA gate, and Case 4: app handles auth alone); clearer per-case commented rules - caddy/Caddyfile (replaces snippet.example.caddyfile): complete Caddyfile with all 4 auth-case examples; (accesslog) imported in every block so fail2ban caddy-4xx jail covers all subdomains, not just gated ones; full inline docs for enabling Frigate proxy auth - README.md: expand "Which sites" from 3 to 4 cases; add proxy-auth service compatibility table (Frigate, Grafana, Gitea, Nextcloud, HA, Portainer etc.); clarify fail2ban covers all sites via single caddy-4xx jail; add touch authelia/authelia.log to first-run; add troubleshooting entries for authelia.log bind-mount directory bug and fail2ban chain verification https://claude.ai/code/session_012eTokAaGiZo7aGt1T2W9BC
This commit is contained in:
@@ -1,9 +1,8 @@
|
|||||||
# Authelia + fail2ban
|
# Authelia + fail2ban
|
||||||
|
|
||||||
Self-hosted authentication portal (Authelia) with an IP-banning sidecar
|
Self-hosted authentication portal (Authelia) with an IP-banning sidecar
|
||||||
(fail2ban). Designed to sit next to a dockerized Caddy on the main server
|
(fail2ban). Sits next to your dockerized Caddy and gates every public
|
||||||
and gate every public subdomain (`cam.example.com`, `doorbell.example.com`,
|
subdomain behind a single sign-on portal at `auth.example.com`.
|
||||||
etc.) behind a single sign-on portal at `auth.example.com`.
|
|
||||||
|
|
||||||
```
|
```
|
||||||
Internet
|
Internet
|
||||||
@@ -15,168 +14,195 @@ etc.) behind a single sign-on portal at `auth.example.com`.
|
|||||||
| reverse_proxy +--------+-----------+
|
| reverse_proxy +--------+-----------+
|
||||||
| |
|
| |
|
||||||
v v
|
v v
|
||||||
Frigate (IoT VLAN), ntfy, etc. ./authelia/db.sqlite3
|
Frigate (LAN), NAS, Pi, etc. ./authelia/db.sqlite3
|
||||||
./authelia/authelia.log
|
./authelia/authelia.log
|
||||||
^
|
^
|
||||||
| tail
|
| tail
|
||||||
+------+--------+
|
+------+--------+
|
||||||
| fail2ban | host net
|
| fail2ban | host net
|
||||||
| DOCKER-USER | +iptables
|
| DOCKER-USER | + iptables
|
||||||
+---------------+
|
+---------------+
|
||||||
```
|
```
|
||||||
|
|
||||||
- One docker-compose file, two services, one external network (`caddy_net`).
|
- One docker-compose file, two services, one external network (`caddy_net`).
|
||||||
- File-backed users database, SQLite storage, in-memory sessions (no Redis).
|
- File-backed users database, SQLite storage, no Redis, no external DB.
|
||||||
- Filesystem notifier -- password reset / new-device emails get written to
|
- Filesystem notifier for password reset (swap to SMTP later, one block change).
|
||||||
a file you `tail -f`. Swap to SMTP later by editing one block.
|
- fail2ban bans via the `DOCKER-USER` iptables chain: drops happen at the host
|
||||||
- fail2ban runs in host network mode and bans via the `DOCKER-USER` chain
|
edge before traffic reaches any docker-published port.
|
||||||
so drops happen at the host edge, before traffic reaches the Caddy
|
- Caddy is not in this stack. Copy `caddy/Caddyfile` into your Caddy setup.
|
||||||
container's published ports.
|
|
||||||
- Caddy is *not* in this repo. You merge a snippet into your real Caddyfile
|
|
||||||
(see `caddy/snippet.example.caddyfile`).
|
|
||||||
|
|
||||||
## Repo layout
|
## Repo layout
|
||||||
|
|
||||||
```
|
```
|
||||||
authelia/
|
authelia-stack/
|
||||||
|-- docker-compose.yml
|
├── docker-compose.yml
|
||||||
|-- .env.example # copy to .env
|
├── .env.example # copy to .env
|
||||||
|-- .gitignore
|
├── .gitignore
|
||||||
|-- README.md # this file
|
├── README.md
|
||||||
|
|
│
|
||||||
|-- authelia/
|
├── authelia/
|
||||||
| |-- configuration.yml # main config -- edit your domain in here
|
│ ├── configuration.yml # main config -- edit your domain here
|
||||||
| |-- users_database.yml.example # copy to users_database.yml (gitignored)
|
│ ├── users_database.yml.example # copy to users_database.yml (gitignored)
|
||||||
| |-- secrets/ # gitignored; secret files mounted as /secrets
|
│ ├── secrets/ # gitignored; secret files mounted as /secrets
|
||||||
| `-- notifications/ # filesystem notifier writes here
|
│ └── notifications/ # filesystem notifier writes here (gitignored)
|
||||||
|
|
│
|
||||||
|-- fail2ban/
|
├── fail2ban/
|
||||||
| `-- data/ # mounted as /data in the container
|
│ └── data/ # mounted as /data in the container
|
||||||
| |-- jail.d/
|
│ ├── filter.d/
|
||||||
| | |-- authelia.local
|
│ │ ├── authelia.local # matches Authelia text-log auth failures
|
||||||
| | `-- caddy.local
|
│ │ └── caddy-4xx.local # matches Caddy JSON 4xx responses
|
||||||
| `-- filter.d/
|
│ └── jail.d/
|
||||||
| |-- authelia.local
|
│ ├── authelia.local # 3 fails/10 min -> 1 hr IP ban
|
||||||
| `-- caddy-4xx.local
|
│ └── caddy.local # 30 fails/2 min -> 30 min IP ban
|
||||||
|
|
│
|
||||||
`-- caddy/
|
└── caddy/
|
||||||
`-- snippet.example.caddyfile # merge into YOUR Caddyfile
|
└── Caddyfile # copy/merge into your Caddy setup
|
||||||
```
|
```
|
||||||
|
|
||||||
## Prerequisites
|
## Prerequisites
|
||||||
|
|
||||||
- Docker + docker compose v2 on the main server.
|
- Docker + docker compose v2.
|
||||||
- Caddy already running on the main server, in Docker, joined to an
|
- Caddy already running, in Docker, joined to an external network named
|
||||||
external network named `caddy_net`. If your network is named differently,
|
`caddy_net`. If your network is named differently, replace `caddy_net`
|
||||||
change `caddy_net` everywhere in this repo.
|
everywhere in this repo.
|
||||||
- A root domain you control (the examples use `example.com`). DNS records
|
- Caddy v2.5.1 or newer (needed for the `forward_auth` directive).
|
||||||
for `auth.<root>` and every protected subdomain should point at the
|
- A root domain you control. DNS A records for `auth.<root>` and every
|
||||||
Caddy host's public IP.
|
protected subdomain must point at the Caddy host's public IP.
|
||||||
- Caddy v2.5.1 or newer (for `forward_auth` directive).
|
|
||||||
|
## Which sites go behind Authelia?
|
||||||
|
|
||||||
|
There are four ways a site can relate to Authelia. Pick one per site.
|
||||||
|
|
||||||
|
| Case | App has built-in auth? | Supports proxy auth? | What to do |
|
||||||
|
|------|------------------------|----------------------|------------|
|
||||||
|
| **1** | No | n/a | `import authelia` in Caddy + rule in Authelia. Authelia is the only login. |
|
||||||
|
| **2** | Yes | Yes | `import authelia` in Caddy + rule in Authelia + disable app's own login form. Single login. |
|
||||||
|
| **3** | Yes | No | `import authelia` in Caddy + rule in Authelia. App auth is unchanged. User logs into Authelia then the app. Two logins. |
|
||||||
|
| **4** | Yes | — | Plain `reverse_proxy`. No `import authelia`, no rule. App handles auth. |
|
||||||
|
|
||||||
|
Concretely:
|
||||||
|
|
||||||
|
- **`doorbell.example.com`** (Pi PTT page) -- **case 1**. No app auth at all.
|
||||||
|
Authelia is the only gate. Use `two_factor` -- this URL controls a speaker.
|
||||||
|
- **`cam.example.com`** (Frigate UI) -- **case 2**. Frigate 0.14+ supports
|
||||||
|
proxy auth. Disable Frigate's login form and let Authelia drive both the
|
||||||
|
access gate and the role mapping (admin vs. viewer) via headers.
|
||||||
|
- **Router admin / NAS UI** -- **case 3** if you want a 2FA gate in front,
|
||||||
|
**case 4** if you just leave it to the app.
|
||||||
|
|
||||||
|
Default policy in `configuration.yml` is `deny`, so a domain with no rule
|
||||||
|
AND no `import authelia` in Caddy never reaches Authelia at all.
|
||||||
|
|
||||||
|
### How to tell if an app supports proxy auth (case 2)
|
||||||
|
|
||||||
|
Look for any of these in the app's docs:
|
||||||
|
|
||||||
|
- "Remote-User header", "trusted upstream", "trusted proxies"
|
||||||
|
- "Header-based authentication", "SSO via reverse proxy"
|
||||||
|
- Support for `X-Forwarded-User`, `X-Remote-User`, or `Remote-User`
|
||||||
|
|
||||||
|
| App | Proxy auth? | Notes |
|
||||||
|
|-----|-------------|-------|
|
||||||
|
| Frigate 0.14+ | Yes | `auth.enabled: False` + `proxy:` block in config.yml |
|
||||||
|
| Grafana | Yes | `[auth.proxy]` section in grafana.ini |
|
||||||
|
| Gitea / Forgejo | Yes | `REVERSE_PROXY_AUTHENTICATION_USER` in app.ini |
|
||||||
|
| Nextcloud | Yes | `TRUSTED_PROXIES` env + `overwriteprotocol = https` |
|
||||||
|
| Home Assistant | Yes | `trusted_networks` auth provider + `use_x_forwarded_for` |
|
||||||
|
| Jellyfin | Partial | Community plugin required |
|
||||||
|
| Portainer | No | Use Authelia OIDC integration instead |
|
||||||
|
| Vaultwarden | No | Use Authelia OIDC integration instead |
|
||||||
|
| Router/NAS admin | Rarely | Use case 3 (2FA gate) or case 4 |
|
||||||
|
|
||||||
## First-run setup
|
## First-run setup
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 0) From wherever you keep ~/docker stacks
|
# 1) Create the external docker network (Caddy must also be on this).
|
||||||
cd ~/docker
|
|
||||||
git clone <this repo's url> authelia
|
|
||||||
cd authelia
|
|
||||||
|
|
||||||
# 1) External docker network -- Caddy must already be on this. If it isn't,
|
|
||||||
# create it now and make sure your Caddy compose joins it.
|
|
||||||
docker network create caddy_net 2>/dev/null || true
|
docker network create caddy_net 2>/dev/null || true
|
||||||
|
|
||||||
# 2) Bootstrap the secrets directory
|
# 2) Bootstrap the secrets directory.
|
||||||
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 the .env
|
# 3) Copy and edit .env.
|
||||||
cp .env.example .env
|
cp .env.example .env
|
||||||
$EDITOR .env # set TZ; pin AUTHELIA_VERSION if you want
|
$EDITOR .env # set TZ; pin AUTHELIA_VERSION if you want
|
||||||
|
|
||||||
# 4) Edit configuration.yml -- replace EVERY `example.com` with your real
|
# 4) Edit authelia/configuration.yml.
|
||||||
# root domain (look for the CHANGE comments)
|
# Replace every `example.com` with your real root domain.
|
||||||
|
# Look for the four CHANGE comments: totp.issuer, session.cookies[].domain,
|
||||||
|
# session.cookies[].authelia_url, session.cookies[].default_redirection_url.
|
||||||
|
# Also uncomment access_control.rules entries for the sites you want to gate.
|
||||||
$EDITOR authelia/configuration.yml
|
$EDITOR authelia/configuration.yml
|
||||||
|
|
||||||
# 5) Create your first user
|
# 5) Create your first user.
|
||||||
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
|
$EDITOR authelia/users_database.yml # set username, email, displayname
|
||||||
|
|
||||||
# Generate the password hash:
|
# Generate the password hash:
|
||||||
docker compose run --rm authelia \
|
docker compose run --rm authelia \
|
||||||
authelia crypto hash generate argon2 --password 'your-real-password'
|
authelia crypto hash generate argon2 --password 'your-real-password'
|
||||||
|
# Paste the $argon2id$... output into the password: field.
|
||||||
|
|
||||||
# Paste the resulting `$argon2id$v=19$m=...` into the user's `password:`.
|
# 6) Pre-create the Authelia log file.
|
||||||
|
# Docker creates a DIRECTORY at the bind-mount path if the file doesn't
|
||||||
|
# exist, which breaks fail2ban's mount. Create it as an empty file first.
|
||||||
|
touch authelia/authelia.log
|
||||||
|
|
||||||
# 6) Validate the config before starting (catches typos / schema issues)
|
# 7) Validate config before starting.
|
||||||
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.
|
||||||
|
|
||||||
# 7) Bring it up
|
# 8) Wire up Caddy (see "Wire Caddy into Authelia" below).
|
||||||
|
|
||||||
|
# 9) Bring it up.
|
||||||
docker compose up -d
|
docker compose up -d
|
||||||
docker compose logs -f authelia # expect "Authelia is listening on ..."
|
docker compose logs -f authelia # expect "Authelia is listening on ..."
|
||||||
|
docker compose logs -f fail2ban # expect "Jail authelia is now active"
|
||||||
```
|
```
|
||||||
|
|
||||||
## Which sites go behind Authelia?
|
|
||||||
|
|
||||||
Authelia is opt-in per site. The goal is one login (Authelia, with 2FA)
|
|
||||||
across everything that *can* use it -- and zero double-prompts for things
|
|
||||||
that already authenticate themselves and can't be retrofitted.
|
|
||||||
|
|
||||||
| Case | App has built-in auth? | Switchable to proxy auth? | What to do |
|
|
||||||
|------|-----------------------|---------------------------|------------|
|
|
||||||
| 1 | No | n/a | Gate with Authelia. Use `two_factor` for anything that controls hardware. |
|
|
||||||
| 2 | Yes | Yes (Authelia, Authentik, oauth2_proxy headers) | Disable the app's login form, point it at Authelia headers, gate with Authelia. Single login. |
|
|
||||||
| 3 | Yes | No | Don't involve Authelia. Plain `reverse_proxy` in Caddy. The app handles its own login. |
|
|
||||||
|
|
||||||
Concretely, in this household:
|
|
||||||
|
|
||||||
- **`doorbell.example.com`** (Pi PTT page) -- case 1. No app auth. Authelia
|
|
||||||
is the only gate. `two_factor`.
|
|
||||||
- **`cam.example.com`** (Frigate UI) -- case 2. Frigate 0.14+ supports
|
|
||||||
proxy auth, so disable Frigate's login form and let Authelia drive both
|
|
||||||
the auth and the role mapping. Single login covers Frigate too.
|
|
||||||
- **router admin / NAS UI / odd one-offs** -- case 3 territory. Plain
|
|
||||||
`reverse_proxy`, no `import authelia`, no Authelia rule.
|
|
||||||
|
|
||||||
Default policy in `configuration.yml` is `deny`, so a domain with no rule
|
|
||||||
*and* no `import authelia` in Caddy never reaches Authelia at all -- the
|
|
||||||
deny doesn't apply.
|
|
||||||
|
|
||||||
## Wire Caddy into Authelia
|
## Wire Caddy into Authelia
|
||||||
|
|
||||||
Open `caddy/snippet.example.caddyfile`. It defines:
|
Open `caddy/Caddyfile`. It defines:
|
||||||
|
|
||||||
- `(authelia)` -- reusable snippet: `import authelia` in any site block
|
- `(authelia)` -- reusable snippet: add `import authelia` to any site block.
|
||||||
you want gated.
|
|
||||||
- `(accesslog)` -- writes Caddy's JSON access log to `/var/log/caddy/access.log`
|
- `(accesslog)` -- writes Caddy's JSON access log to `/var/log/caddy/access.log`
|
||||||
so fail2ban can watch it.
|
so fail2ban's `caddy-4xx` jail can watch it.
|
||||||
- `auth.example.com` -- the Authelia portal subdomain.
|
- `auth.example.com` -- the Authelia portal.
|
||||||
- Example blocks for the three cases above.
|
- Example site blocks for all four cases (cases 1-3 active, case 4 commented).
|
||||||
|
|
||||||
Copy the relevant blocks into your real Caddyfile, replace `example.com`
|
Copy the relevant blocks into your real Caddyfile, replace `example.com` with
|
||||||
with your domain and `192.168.x.x` with real upstream IPs, then reload Caddy:
|
your domain and `192.168.x.x` with real upstream IPs, then reload Caddy.
|
||||||
|
|
||||||
```bash
|
**Every** site block should have `import accesslog` -- even case 4 sites.
|
||||||
docker compose -f ~/docker/caddy/docker-compose.yml exec caddy \
|
fail2ban's caddy-4xx jail watches the one log file and covers all your
|
||||||
caddy reload --config /etc/caddy/Caddyfile
|
subdomains automatically. Scanners spray everything, not just gated sites.
|
||||||
|
|
||||||
|
### Caddy access log path
|
||||||
|
|
||||||
|
fail2ban mounts `/var/log/caddy` from the host as read-only. Your Caddy
|
||||||
|
service must write to the same path. In your Caddy compose:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
services:
|
||||||
|
caddy:
|
||||||
|
volumes:
|
||||||
|
- /var/log/caddy:/var/log/caddy
|
||||||
```
|
```
|
||||||
|
|
||||||
For each case-1 or case-2 domain, also add a rule under `access_control.rules`
|
Create the directory before starting:
|
||||||
in `authelia/configuration.yml`. Restart Authelia after editing:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker compose restart authelia
|
sudo mkdir -p /var/log/caddy
|
||||||
|
sudo chown caddy:caddy /var/log/caddy # adjust to your Caddy UID
|
||||||
```
|
```
|
||||||
|
|
||||||
### Switching Frigate to Authelia (case 2)
|
## Switching Frigate to Authelia (case 2)
|
||||||
|
|
||||||
Frigate 0.14+ has a `proxy:` config block that consumes a username header
|
Edit `frigate_config/config.yml` in your Frigate stack:
|
||||||
from the upstream and skips its own login form. Edit
|
|
||||||
`frigate_config/config.yml` in your Frigate repo:
|
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
auth:
|
auth:
|
||||||
@@ -187,92 +213,83 @@ auth:
|
|||||||
|
|
||||||
proxy:
|
proxy:
|
||||||
header_map:
|
header_map:
|
||||||
user: remote-user # what Authelia sends; matches `copy_headers` in Caddy
|
user: remote-user # matches `copy_headers Remote-User` in (authelia) snippet
|
||||||
role: remote-groups
|
role: remote-groups
|
||||||
default_role: viewer
|
default_role: viewer
|
||||||
separator: '|'
|
separator: '|'
|
||||||
# Optional but recommended when Caddy crosses VLANs to reach Frigate.
|
# Optional shared secret -- prevents LAN header spoofing.
|
||||||
# Generate with `openssl rand -hex 32`. Caddy must send the same value
|
# Generate: openssl rand -hex 32
|
||||||
# as `X-Proxy-Secret` -- see the cam.* block in caddy/snippet.example.caddyfile.
|
# Set the same value as `header_up X-Proxy-Secret` in caddy/Caddyfile.
|
||||||
# auth_secret: 'paste-32-byte-hex-here'
|
# auth_secret: 'your-32-byte-hex'
|
||||||
```
|
```
|
||||||
|
|
||||||
Restart Frigate (`docker compose restart frigate` in the Frigate repo).
|
Then uncomment `cam.example.com` in `authelia/configuration.yml`, restart
|
||||||
Confirm the Frigate UI now jumps straight to Authelia and back without
|
both services:
|
||||||
a Frigate login screen.
|
|
||||||
|
|
||||||
If you want Authelia groups to drive Frigate roles (admin vs. viewer),
|
|
||||||
add a `role_map:` under `proxy:` (see Frigate docs).
|
|
||||||
|
|
||||||
### Caddy access log path
|
|
||||||
|
|
||||||
fail2ban mounts `/var/log/caddy` from the host as read-only. Your Caddy
|
|
||||||
compose needs to mount the same host path read-write so Caddy can write to
|
|
||||||
it. In your Caddy compose:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
services:
|
|
||||||
caddy:
|
|
||||||
volumes:
|
|
||||||
- /var/log/caddy:/var/log/caddy
|
|
||||||
```
|
|
||||||
|
|
||||||
Make sure the host directory exists and is writable by Caddy's UID:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo mkdir -p /var/log/caddy
|
docker compose restart authelia
|
||||||
sudo chown -R 1000:1000 /var/log/caddy # adjust UID to match your Caddy
|
docker compose restart frigate # in your Frigate stack
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Verify: `https://cam.example.com` in a private window goes to Authelia and
|
||||||
|
back without a Frigate login screen.
|
||||||
|
|
||||||
## First login + TOTP enrollment
|
## First login + TOTP enrollment
|
||||||
|
|
||||||
1. Visit any protected subdomain in a private browser window.
|
1. Visit a protected subdomain in a private browser window.
|
||||||
2. Caddy bounces you to `https://auth.<your-domain>/` -- log in with the
|
2. Caddy bounces you to `https://auth.example.com` -- log in with your
|
||||||
username and plaintext password you set above.
|
username and plaintext password.
|
||||||
3. If the access rule is `two_factor`, Authelia asks you to register a
|
3. If the rule is `two_factor`, Authelia prompts you to register a second
|
||||||
second factor. Pick **TOTP** and scan the QR with Authy / 1Password /
|
factor. Pick **TOTP** and scan the QR with your authenticator app.
|
||||||
Google Authenticator / Bitwarden / etc.
|
4. Authelia writes a confirmation link to the filesystem notifier file:
|
||||||
4. On first registration Authelia tries to email you a confirmation link.
|
|
||||||
The filesystem notifier writes it to a file -- grab it with:
|
|
||||||
```bash
|
```bash
|
||||||
docker compose exec authelia cat /config/notifications/notification.txt
|
docker compose exec authelia cat /config/notifications/notification.txt
|
||||||
```
|
```
|
||||||
Click that link to confirm registration.
|
Click the link to confirm TOTP registration.
|
||||||
5. Re-enter the TOTP code -- you're in.
|
5. Enter the TOTP code -- you're in. The `authelia_session` cookie is scoped
|
||||||
|
to your root domain and covers every protected subdomain automatically.
|
||||||
A successful login sets the `authelia_session` cookie scoped to your root
|
|
||||||
domain, so it covers every subdomain protected by the same Authelia.
|
|
||||||
|
|
||||||
## User management
|
## User management
|
||||||
|
|
||||||
### Add a user
|
### Add a user
|
||||||
|
|
||||||
Append to `authelia/users_database.yml`, generate a hash with
|
Append to `authelia/users_database.yml`, generate a hash:
|
||||||
`docker compose run --rm authelia authelia crypto hash generate argon2 --password '...'`,
|
```bash
|
||||||
paste it as `password:`, then `docker compose restart authelia` (or wait
|
docker compose run --rm authelia \
|
||||||
five minutes for the file refresh interval).
|
authelia crypto hash generate argon2 --password 'new-password'
|
||||||
|
```
|
||||||
### Change a password
|
Paste the hash as `password:`. Restart or wait 5 minutes for auto-reload.
|
||||||
|
|
||||||
Same as above -- re-generate the hash and replace the `password:` field.
|
|
||||||
|
|
||||||
### Disable a user
|
### Disable a user
|
||||||
|
|
||||||
Set `disabled: true` on their entry and restart Authelia.
|
Set `disabled: true` on their entry. Takes effect at next refresh.
|
||||||
|
|
||||||
### Reset their TOTP
|
### Reset TOTP (force re-enrollment)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker compose exec authelia \
|
docker compose exec authelia \
|
||||||
authelia storage user totp delete --username yourname \
|
authelia storage user totp delete --username USERNAME \
|
||||||
--config /config/configuration.yml
|
--config /config/configuration.yml
|
||||||
```
|
```
|
||||||
|
|
||||||
They will be prompted to re-enroll on next login.
|
|
||||||
|
|
||||||
## fail2ban
|
## fail2ban
|
||||||
|
|
||||||
### Verify it's running and watching the right files
|
fail2ban does **not** need its own separate stack or compose file. It lives
|
||||||
|
alongside Authelia in this same `docker-compose.yml`. It uses host networking
|
||||||
|
(no docker network needed) and watches two log sources:
|
||||||
|
|
||||||
|
| Jail | Log | Trigger | Ban |
|
||||||
|
|------|-----|---------|-----|
|
||||||
|
| `authelia` | `./authelia/authelia.log` | 3 failed logins in 10 min | 1 hour |
|
||||||
|
| `caddy-4xx` | `/var/log/caddy/access.log` | 30 HTTP 4xx in 2 min | 30 min |
|
||||||
|
|
||||||
|
The **caddy-4xx jail covers every site** on your Caddyfile as long as each
|
||||||
|
block has `import accesslog`. You don't need per-site jails.
|
||||||
|
|
||||||
|
Defense in depth: Authelia's `regulation` block locks the *user account*
|
||||||
|
after 3 bad passwords. fail2ban bans the *source IP* independently.
|
||||||
|
|
||||||
|
### Verify jails are active
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker compose exec fail2ban fail2ban-client status
|
docker compose exec fail2ban fail2ban-client status
|
||||||
@@ -280,27 +297,20 @@ docker compose exec fail2ban fail2ban-client status authelia
|
|||||||
docker compose exec fail2ban fail2ban-client status caddy-4xx
|
docker compose exec fail2ban fail2ban-client status caddy-4xx
|
||||||
```
|
```
|
||||||
|
|
||||||
Each `status <jail>` shows the active failures, banned IPs, and the log
|
### Test filters against real logs
|
||||||
file it's tailing.
|
|
||||||
|
|
||||||
### Test a filter against your real logs
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Authelia
|
|
||||||
docker compose exec fail2ban fail2ban-regex \
|
docker compose exec fail2ban fail2ban-regex \
|
||||||
/var/log/authelia/authelia.log \
|
/var/log/authelia/authelia.log \
|
||||||
/data/filter.d/authelia.local
|
/data/filter.d/authelia.local
|
||||||
|
|
||||||
# Caddy
|
|
||||||
docker compose exec fail2ban fail2ban-regex \
|
docker compose exec fail2ban fail2ban-regex \
|
||||||
/var/log/caddy/access.log \
|
/var/log/caddy/access.log \
|
||||||
/data/filter.d/caddy-4xx.local
|
/data/filter.d/caddy-4xx.local
|
||||||
```
|
```
|
||||||
|
|
||||||
If the failregex doesn't match anything, your log format probably differs
|
If nothing matches: confirm `log.format: 'text'` in `authelia/configuration.yml`
|
||||||
from what the filter expects. For Authelia: confirm `log.format: 'text'`
|
and `format json` in the `(accesslog)` snippet in your Caddyfile.
|
||||||
in `configuration.yml`. For Caddy: confirm the `(accesslog)` snippet is
|
|
||||||
imported into the site you're testing and that `format json` is set.
|
|
||||||
|
|
||||||
### Manually unban an IP
|
### Manually unban an IP
|
||||||
|
|
||||||
@@ -309,91 +319,28 @@ docker compose exec fail2ban fail2ban-client set authelia unbanip 1.2.3.4
|
|||||||
docker compose exec fail2ban fail2ban-client set caddy-4xx unbanip 1.2.3.4
|
docker compose exec fail2ban fail2ban-client set caddy-4xx unbanip 1.2.3.4
|
||||||
```
|
```
|
||||||
|
|
||||||
### Tune
|
### Tune thresholds
|
||||||
|
|
||||||
Per-jail `maxretry`, `findtime`, `bantime` live in
|
|
||||||
`fail2ban/data/jail.d/*.local`. Edit and restart fail2ban:
|
|
||||||
|
|
||||||
|
Edit `fail2ban/data/jail.d/*.local`, then:
|
||||||
```bash
|
```bash
|
||||||
docker compose restart fail2ban
|
docker compose restart fail2ban
|
||||||
```
|
```
|
||||||
|
|
||||||
The `caddy-4xx` jail's defaults (30 fails / 2 minutes -> 30 minute ban) are
|
## Day-to-day
|
||||||
intentionally loose -- a single failed request shouldn't ban you, but a
|
|
||||||
scanner spraying `/wp-admin`, `/.env`, `/admin.php` etc. will hit it fast.
|
|
||||||
The `authelia` jail is tighter (3 fails / 10 minutes -> 1 hour ban) on top
|
|
||||||
of Authelia's own in-app `regulation` (3 fails / 2 minutes -> 5 minute
|
|
||||||
account lockout), giving you defense in depth: Authelia locks the *user*,
|
|
||||||
fail2ban bans the *IP*.
|
|
||||||
|
|
||||||
## Day-to-day operation
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker compose ps # everything up?
|
docker compose ps # services running?
|
||||||
docker compose logs -f authelia # follow Authelia
|
docker compose logs -f authelia # follow Authelia
|
||||||
docker compose logs -f fail2ban # follow fail2ban
|
docker compose logs -f fail2ban # follow fail2ban
|
||||||
docker compose pull && docker compose up -d # upgrade
|
docker compose pull && docker compose up -d # upgrade images
|
||||||
```
|
```
|
||||||
|
|
||||||
Bump `AUTHELIA_VERSION` in `.env` when you upgrade Authelia. After any
|
Bump `AUTHELIA_VERSION` in `.env` when upgrading Authelia. After any
|
||||||
Authelia upgrade, re-run `validate-config` -- the schema does evolve.
|
upgrade, re-run `validate-config` -- the schema evolves between releases.
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
### Redirect loop between site and `auth.<domain>`
|
|
||||||
|
|
||||||
Cookie domain mismatch. The `domain` under `session.cookies[]` must be the
|
|
||||||
*root* domain (e.g. `example.com`), and every protected site must be a
|
|
||||||
subdomain of that root, served over HTTPS. Mixed `http://` and `https://`
|
|
||||||
won't work; the cookie is `Secure`.
|
|
||||||
|
|
||||||
### "Configuration: session: option 'domain' and option 'cookies' can't be specified at the same time"
|
|
||||||
|
|
||||||
Old-style `session.domain: ...` left over from pre-4.38 config. Remove it;
|
|
||||||
this repo's `configuration.yml` already uses the new `session.cookies[]`
|
|
||||||
form.
|
|
||||||
|
|
||||||
### "access denied" with no login prompt
|
|
||||||
|
|
||||||
Default policy is `deny`. Add a rule under `access_control.rules` for the
|
|
||||||
domain you're hitting and restart Authelia.
|
|
||||||
|
|
||||||
### Authelia container restarts forever
|
|
||||||
|
|
||||||
```bash
|
|
||||||
docker compose logs authelia | head -50
|
|
||||||
```
|
|
||||||
|
|
||||||
Most often: missing/empty secret files, bad YAML in `configuration.yml`,
|
|
||||||
or a `users_database.yml` with an invalid hash.
|
|
||||||
|
|
||||||
### Caddy can't resolve `authelia`
|
|
||||||
|
|
||||||
Caddy isn't on `caddy_net`. Add `networks: [caddy_net]` to your Caddy
|
|
||||||
service and `caddy_net: external: true` at the bottom of its compose,
|
|
||||||
then `docker compose up -d caddy`.
|
|
||||||
|
|
||||||
### fail2ban bans don't actually block
|
|
||||||
|
|
||||||
Almost always: fail2ban isn't writing to the right iptables chain. With
|
|
||||||
dockerized Caddy, you need `chain = DOCKER-USER` (already set in the
|
|
||||||
shipped jail files). Verify:
|
|
||||||
```bash
|
|
||||||
sudo iptables -L DOCKER-USER -n
|
|
||||||
```
|
|
||||||
You should see jump rules pointing at f2b-* chains.
|
|
||||||
|
|
||||||
### Authelia logs show nothing
|
|
||||||
|
|
||||||
`log.file_path` is `/config/authelia.log` (i.e. `./authelia/authelia.log`
|
|
||||||
on the host). If the file isn't appearing, Authelia probably isn't
|
|
||||||
writing logs because it failed to start -- check `docker compose logs
|
|
||||||
authelia`.
|
|
||||||
|
|
||||||
## Switching the notifier to SMTP
|
## Switching the notifier to SMTP
|
||||||
|
|
||||||
When you have a transactional sender (Mailgun, Postmark, Amazon SES, your
|
Replace `notifier:` in `authelia/configuration.yml`:
|
||||||
own postfix), replace the `notifier:` block in `authelia/configuration.yml`:
|
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
notifier:
|
notifier:
|
||||||
@@ -403,46 +350,90 @@ notifier:
|
|||||||
username: 'authelia@example.com'
|
username: 'authelia@example.com'
|
||||||
sender: 'Authelia <authelia@example.com>'
|
sender: 'Authelia <authelia@example.com>'
|
||||||
subject: '[Authelia] {title}'
|
subject: '[Authelia] {title}'
|
||||||
# Password loaded via AUTHELIA_NOTIFIER_SMTP_PASSWORD_FILE
|
# password loaded via AUTHELIA_NOTIFIER_SMTP_PASSWORD_FILE
|
||||||
```
|
```
|
||||||
|
|
||||||
Add the password file:
|
Add the secret and wire it up:
|
||||||
```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
|
||||||
```
|
```
|
||||||
|
|
||||||
And add to `docker-compose.yml` under the authelia service `environment:`:
|
Add to the authelia service environment in `docker-compose.yml`:
|
||||||
```yaml
|
```yaml
|
||||||
- AUTHELIA_NOTIFIER_SMTP_PASSWORD_FILE=/secrets/SMTP_PASSWORD
|
- AUTHELIA_NOTIFIER_SMTP_PASSWORD_FILE=/secrets/SMTP_PASSWORD
|
||||||
```
|
```
|
||||||
|
|
||||||
Restart and verify with `docker compose logs authelia` -- expect a
|
Restart and look for `"Notifier SMTP startup check successful"` in logs.
|
||||||
"Notifier SMTP startup check successful" line.
|
|
||||||
|
|
||||||
## Security notes
|
## Security notes
|
||||||
|
|
||||||
- `.env`, `authelia/secrets/*`, `authelia/users_database.yml`,
|
- `.env`, `authelia/secrets/*`, `authelia/users_database.yml`, and
|
||||||
`authelia/db.sqlite3*`, and the notifications file are all gitignored.
|
`authelia/db.sqlite3*` are all gitignored. Run `git status` before every
|
||||||
Verify with `git status` before every commit.
|
commit to confirm nothing sensitive is staged.
|
||||||
- Authelia is *not* port-mapped to the host. Only containers on
|
- Authelia is not port-mapped to the host. Only containers on `caddy_net`
|
||||||
`caddy_net` can reach it, and only Caddy is configured to forward
|
can reach it; only Caddy is configured to forward_auth there.
|
||||||
unauthenticated traffic to it via `forward_auth`.
|
- TOTP secrets in the SQLite DB are encrypted at rest with
|
||||||
- The TOTP secrets in the SQLite DB are encrypted at rest with
|
`STORAGE_ENCRYPTION_KEY`. Back up both the DB and the key file -- losing
|
||||||
`STORAGE_ENCRYPTION_KEY`. Lose that file and you lose every user's TOTP
|
either means every user must re-enroll TOTP.
|
||||||
-- back it up alongside the DB.
|
- `regulation` is per-user account lockout; fail2ban is per-IP. Both are on.
|
||||||
- `regulation` is per-user; fail2ban is per-IP. Both are on by default.
|
|
||||||
- The shipped `caddy-4xx` filter ignores `favicon.ico`, `robots.txt`, and
|
|
||||||
Apple touch icons so accidentally-missing static assets don't ban your
|
|
||||||
own browser. Add to `ignoreregex` if other false-positives show up in
|
|
||||||
`fail2ban-regex` testing.
|
|
||||||
|
|
||||||
## What's next
|
## Troubleshooting
|
||||||
|
|
||||||
Once this is steady-state:
|
### Redirect loop between a site and `auth.example.com`
|
||||||
|
|
||||||
- Add OIDC clients in Authelia for apps that speak OIDC natively (Grafana,
|
Cookie domain mismatch. The `domain:` under `session.cookies[]` must be the
|
||||||
Gitea, etc.) -- they'll do real SSO without forward-auth headers.
|
bare root domain (`example.com`), and every protected site must be a subdomain
|
||||||
- Switch the filesystem notifier to SMTP (see above).
|
of it served over HTTPS. Mixed HTTP/HTTPS won't work; the session cookie is
|
||||||
- Consider a backup job for `authelia/db.sqlite3` and `authelia/secrets/`
|
`Secure`.
|
||||||
-- losing either is a recovery mess.
|
|
||||||
|
### "access denied" with no login prompt
|
||||||
|
|
||||||
|
`default_policy: deny` and no `access_control` rule for this domain. Add a
|
||||||
|
rule in `authelia/configuration.yml` and restart Authelia.
|
||||||
|
|
||||||
|
### Authelia container restarts forever
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose logs authelia | head -50
|
||||||
|
```
|
||||||
|
|
||||||
|
Most often: missing/empty secret files in `authelia/secrets/`, bad YAML in
|
||||||
|
`configuration.yml`, or an invalid argon2 hash in `users_database.yml`.
|
||||||
|
|
||||||
|
### Caddy can't resolve `authelia`
|
||||||
|
|
||||||
|
Caddy isn't on `caddy_net`. Add `networks: [caddy_net]` to your Caddy
|
||||||
|
service and `caddy_net: external: true` at the bottom of its compose, then:
|
||||||
|
```bash
|
||||||
|
docker compose up -d caddy
|
||||||
|
```
|
||||||
|
|
||||||
|
### fail2ban bans don't block traffic
|
||||||
|
|
||||||
|
fail2ban is writing to the wrong iptables chain. With dockerized Caddy you
|
||||||
|
need `chain = DOCKER-USER` (already set in the shipped jail files). Verify:
|
||||||
|
```bash
|
||||||
|
sudo iptables -L DOCKER-USER -n
|
||||||
|
# Should show f2b-* jump rules.
|
||||||
|
```
|
||||||
|
|
||||||
|
### fail2ban: authelia jail missing / "No such file" on authelia.log
|
||||||
|
|
||||||
|
Docker created a directory at `./authelia/authelia.log` instead of a file
|
||||||
|
because the file didn't exist when the container started:
|
||||||
|
```bash
|
||||||
|
docker compose down fail2ban
|
||||||
|
rm -rf authelia/authelia.log # remove the directory Docker created
|
||||||
|
touch authelia/authelia.log # create as an empty file
|
||||||
|
docker compose up -d fail2ban
|
||||||
|
```
|
||||||
|
|
||||||
|
### Frigate still shows its own login after switching to proxy auth
|
||||||
|
|
||||||
|
1. Confirm `auth.enabled: False` in `frigate_config/config.yml`.
|
||||||
|
2. Confirm `trusted_proxies` subnet matches your `caddy_net` subnet:
|
||||||
|
```bash
|
||||||
|
docker network inspect caddy_net | grep -A2 '"Config"'
|
||||||
|
```
|
||||||
|
3. Restart Frigate: `docker compose restart frigate` (in your Frigate stack).
|
||||||
|
|||||||
+54
-36
@@ -25,7 +25,7 @@ server:
|
|||||||
|
|
||||||
log:
|
log:
|
||||||
level: 'info'
|
level: 'info'
|
||||||
format: 'text' # fail2ban filter expects text format
|
format: 'text' # fail2ban filter expects text format -- don't change to json
|
||||||
file_path: '/config/authelia.log'
|
file_path: '/config/authelia.log'
|
||||||
keep_stdout: true # also log to stdout for `docker logs`
|
keep_stdout: true # also log to stdout for `docker logs`
|
||||||
|
|
||||||
@@ -63,60 +63,76 @@ authentication_backend:
|
|||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Access control
|
# Access control
|
||||||
#
|
#
|
||||||
# default_policy: deny means every domain that Caddy forwards here must
|
# default_policy: deny -- every domain Caddy forward_auths here must have
|
||||||
# have an explicit allow rule. Caddy only forwards when a site block has
|
# an explicit rule. Domains with no `import authelia` in Caddy never reach
|
||||||
# `import authelia`, so domains where you keep the app's own auth (no
|
# Authelia at all, so the deny doesn't apply to them.
|
||||||
# `import authelia` in Caddy) bypass Authelia entirely and don't need
|
|
||||||
# rules here.
|
|
||||||
#
|
#
|
||||||
# Policies:
|
# Policies:
|
||||||
# bypass Authelia waves the request through (used for the portal)
|
# bypass Authelia waves the request through (used for the portal)
|
||||||
# one_factor password only
|
# one_factor password only
|
||||||
# two_factor password + TOTP
|
# two_factor password + TOTP
|
||||||
#
|
#
|
||||||
# Decision tree for whether a site needs an Authelia rule at all:
|
# ---------------------------------------------------------------------------
|
||||||
|
# WHICH SITES NEED A RULE HERE?
|
||||||
#
|
#
|
||||||
# - App has NO built-in auth (doorbell PTT page) -> rule here +
|
# There are four ways a site can relate to Authelia:
|
||||||
# `import authelia` in Caddy. Use two_factor for anything that
|
|
||||||
# controls hardware in the house.
|
|
||||||
#
|
#
|
||||||
# - App has built-in auth but supports trusted-header proxy auth
|
# CASE 1 -- App has NO built-in auth (e.g. Pi doorbell PTT page).
|
||||||
# (Frigate 0.14+, Grafana, Gitea, Jellyfin, ...) -> rule here +
|
# -> Rule required + `import authelia` in Caddy.
|
||||||
# `import authelia` in Caddy AND switch the app to proxy auth in
|
# -> Authelia is the ONLY login. Use two_factor for hardware-control pages.
|
||||||
# its own config. Single login (Authelia), 2FA, app keeps its own
|
|
||||||
# user/role mapping driven from Authelia headers.
|
|
||||||
#
|
#
|
||||||
# - App has built-in auth and CAN'T switch (router admin, legacy
|
# CASE 2 -- App has built-in auth AND supports trusted-header proxy auth
|
||||||
# things) -> NO rule here, NO `import authelia` in Caddy. The
|
# (Frigate 0.14+, Grafana, Gitea, Nextcloud, Home Assistant ...).
|
||||||
# traffic skips Authelia entirely; the app handles its own login.
|
# -> Rule required + `import authelia` in Caddy + disable the app's login
|
||||||
|
# form in its own config (see README.md per-app instructions).
|
||||||
|
# -> Single Authelia login covers both access AND the app's user/role
|
||||||
|
# mapping via Remote-User / Remote-Groups headers.
|
||||||
|
#
|
||||||
|
# CASE 3 -- App has built-in auth and CANNOT switch to proxy auth, but you
|
||||||
|
# still want a 2FA gate in front of it (extra security layer).
|
||||||
|
# -> Rule required + `import authelia` in Caddy.
|
||||||
|
# -> User logs into Authelia (2FA) THEN into the app's own login form.
|
||||||
|
# Two separate logins -- the app auth is unchanged.
|
||||||
|
# -> Useful for: router admin pages, NAS UIs, any legacy app.
|
||||||
|
#
|
||||||
|
# CASE 4 -- App handles its own auth and you don't want Authelia involved.
|
||||||
|
# -> NO rule here + NO `import authelia` in Caddy.
|
||||||
|
# -> Traffic skips Authelia entirely; the app handles everything.
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
access_control:
|
access_control:
|
||||||
default_policy: 'deny'
|
default_policy: 'deny'
|
||||||
rules:
|
rules:
|
||||||
|
|
||||||
# The Authelia portal itself is always bypass.
|
# The Authelia portal itself is always bypass.
|
||||||
- domain: 'auth.example.com' # CHANGE
|
- domain: 'auth.example.com' # CHANGE
|
||||||
policy: 'bypass'
|
policy: 'bypass'
|
||||||
|
|
||||||
# ----- Apps with NO built-in auth: Authelia is the only gate -----
|
# -------------------------------------------------------------------
|
||||||
# The Pi doorbell PTT page has no app-level auth, so Authelia is it.
|
# CASE 1: No app auth -- Authelia is the only gate.
|
||||||
# 2FA is appropriate -- this URL controls a speaker in your house.
|
# The Pi doorbell PTT page has no built-in authentication.
|
||||||
# - domain: 'doorbell.example.com'
|
# two_factor is appropriate -- this URL controls a speaker in your house.
|
||||||
|
# -------------------------------------------------------------------
|
||||||
|
# - domain: 'doorbell.example.com' # CHANGE
|
||||||
# policy: 'two_factor'
|
# policy: 'two_factor'
|
||||||
|
|
||||||
# ----- Apps that switched FROM their own auth TO Authelia -----
|
# -------------------------------------------------------------------
|
||||||
# Frigate 0.14+ supports trusted-header proxy auth. To use this:
|
# CASE 2: App supports trusted-header proxy auth -- replace app login.
|
||||||
# 1. In frigate_config/config.yml, set `auth.enabled: False`
|
# Frigate 0.14+: set `auth.enabled: False` and configure `proxy:` in
|
||||||
# and add a `proxy:` block with `header_map.user: remote-user`
|
# frigate_config/config.yml (see README.md "Switching Frigate to Authelia").
|
||||||
# and `header_map.role: remote-groups` (see README).
|
# Single login: Authelia authenticates, Frigate reads Remote-User/Groups.
|
||||||
# 2. `import authelia` in the cam.* Caddy block.
|
# -------------------------------------------------------------------
|
||||||
# 3. Add the rule below.
|
# - domain: 'cam.example.com' # CHANGE
|
||||||
# - domain: 'cam.example.com'
|
|
||||||
# policy: 'two_factor'
|
# policy: 'two_factor'
|
||||||
|
|
||||||
# ----- Apps that KEEP their own auth -----
|
# -------------------------------------------------------------------
|
||||||
# Don't add a rule and don't `import authelia` in their Caddy block.
|
# CASE 3: App keeps its own auth; Authelia adds a 2FA gate in front.
|
||||||
# Example: a router admin page on `router.example.com` -- no rule
|
# The app's login form is still shown after Authelia passes the request.
|
||||||
# appears here, the request never reaches Authelia.
|
# User logs into Authelia (2FA) then into the app separately.
|
||||||
|
# -------------------------------------------------------------------
|
||||||
|
# - domain: 'nas.example.com' # CHANGE/REMOVE example
|
||||||
|
# policy: 'two_factor'
|
||||||
|
|
||||||
|
# CASE 4: No rule here, no `import authelia` in Caddy. App handles auth.
|
||||||
|
|
||||||
session:
|
session:
|
||||||
# secret loaded via AUTHELIA_SESSION_SECRET_FILE
|
# secret loaded via AUTHELIA_SESSION_SECRET_FILE
|
||||||
@@ -130,7 +146,9 @@ session:
|
|||||||
remember_me: '1 month'
|
remember_me: '1 month'
|
||||||
same_site: 'lax'
|
same_site: 'lax'
|
||||||
|
|
||||||
# In-app rate limiting. First line of defense; fail2ban is the second.
|
# In-app rate limiting. Locks the user account after repeated failures.
|
||||||
|
# fail2ban is the second line of defense: it bans the source IP.
|
||||||
|
# Together: Authelia locks the *user*, fail2ban bans the *IP*.
|
||||||
regulation:
|
regulation:
|
||||||
max_retries: 3
|
max_retries: 3
|
||||||
find_time: '2 minutes'
|
find_time: '2 minutes'
|
||||||
@@ -143,7 +161,7 @@ storage:
|
|||||||
|
|
||||||
# Filesystem notifier -- password reset / new device emails get written to
|
# Filesystem notifier -- password reset / new device emails get written to
|
||||||
# a file you can `tail -f`. Swap to `smtp:` when you wire up a real
|
# a file you can `tail -f`. Swap to `smtp:` when you wire up a real
|
||||||
# transactional sender.
|
# transactional sender (see README.md "Switching the notifier to SMTP").
|
||||||
notifier:
|
notifier:
|
||||||
disable_startup_check: false
|
disable_startup_check: false
|
||||||
filesystem:
|
filesystem:
|
||||||
|
|||||||
+203
@@ -0,0 +1,203 @@
|
|||||||
|
# =============================================================================
|
||||||
|
# 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)
|
||||||
|
# - 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
|
||||||
|
# }
|
||||||
|
# }
|
||||||
|
# }
|
||||||
@@ -1,144 +0,0 @@
|
|||||||
# =============================================================================
|
|
||||||
# Authelia + Caddy integration snippets
|
|
||||||
#
|
|
||||||
# Merge these blocks into your real Caddyfile (typically the one your
|
|
||||||
# dockerized Caddy mounts from its own ~/docker/caddy/ folder). Reload Caddy
|
|
||||||
# after editing:
|
|
||||||
# docker compose -f ~/docker/caddy/docker-compose.yml exec caddy \
|
|
||||||
# caddy reload --config /etc/caddy/Caddyfile
|
|
||||||
#
|
|
||||||
# Requires:
|
|
||||||
# - Caddy v2.5.1 or newer
|
|
||||||
# - Caddy joined to the external `caddy_net` docker network so it can
|
|
||||||
# resolve `authelia` by container name
|
|
||||||
#
|
|
||||||
# -----------------------------------------------------------------------------
|
|
||||||
# DECISION TREE: which sites go behind Authelia?
|
|
||||||
#
|
|
||||||
# 1. App has NO built-in auth (e.g. the Pi doorbell PTT page).
|
|
||||||
# -> `import authelia` here AND add a rule in
|
|
||||||
# authelia/configuration.yml access_control.rules. Use
|
|
||||||
# two_factor for anything that controls hardware.
|
|
||||||
#
|
|
||||||
# 2. App has built-in auth AND supports trusted-header proxy auth
|
|
||||||
# (Frigate 0.14+, Grafana, Gitea, Jellyfin, Portainer, ...).
|
|
||||||
# -> `import authelia` here, add a rule in Authelia, AND switch
|
|
||||||
# the app's own config to consume Remote-User from upstream
|
|
||||||
# (disable its built-in login form). One login, 2FA, app
|
|
||||||
# still owns its user/role mapping. See the cam.* example
|
|
||||||
# and the README for Frigate specifics.
|
|
||||||
#
|
|
||||||
# 3. App has built-in auth and CAN'T switch (router admin pages,
|
|
||||||
# odd legacy things).
|
|
||||||
# -> Plain `reverse_proxy` block. NO `import authelia`, NO
|
|
||||||
# Authelia rule. The traffic skips Authelia entirely.
|
|
||||||
# =============================================================================
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
|
||||||
# Reusable forward_auth snippet for cases (1) and (2). Import into any site
|
|
||||||
# block you want gated by Authelia.
|
|
||||||
# -----------------------------------------------------------------------------
|
|
||||||
(authelia) {
|
|
||||||
forward_auth authelia:9091 {
|
|
||||||
uri /api/authz/forward-auth
|
|
||||||
copy_headers Remote-User Remote-Groups Remote-Email Remote-Name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
|
||||||
# Caddy access logging -- fail2ban needs JSON access logs at a host path
|
|
||||||
# both Caddy and fail2ban can see. Mount /var/log/caddy in BOTH compose
|
|
||||||
# files (Caddy as rw, fail2ban as ro). The roll directives keep it bounded.
|
|
||||||
# -----------------------------------------------------------------------------
|
|
||||||
(accesslog) {
|
|
||||||
log {
|
|
||||||
output file /var/log/caddy/access.log {
|
|
||||||
roll_size 10MiB
|
|
||||||
roll_keep 5
|
|
||||||
roll_keep_for 720h
|
|
||||||
}
|
|
||||||
format json
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# =============================================================================
|
|
||||||
# Site blocks
|
|
||||||
# =============================================================================
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
|
||||||
# Authelia login portal -- always bypass in access_control.rules.
|
|
||||||
# -----------------------------------------------------------------------------
|
|
||||||
auth.example.com {
|
|
||||||
import accesslog
|
|
||||||
reverse_proxy authelia:9091
|
|
||||||
}
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
|
||||||
# CASE 1: app has NO built-in auth.
|
|
||||||
# Pi doorbell PTT page -- Authelia is the only gate. two_factor in Authelia.
|
|
||||||
# -----------------------------------------------------------------------------
|
|
||||||
doorbell.example.com {
|
|
||||||
import accesslog
|
|
||||||
import authelia
|
|
||||||
|
|
||||||
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: app supports trusted-header proxy auth.
|
|
||||||
# Frigate UI -- Authelia gates access AND Frigate consumes Remote-User from
|
|
||||||
# upstream so its own role mapping (admin/viewer) still works. To use this,
|
|
||||||
# also edit frigate_config/config.yml:
|
|
||||||
#
|
|
||||||
# auth:
|
|
||||||
# enabled: False
|
|
||||||
# trusted_proxies:
|
|
||||||
# - 172.18.0.0/16 # your caddy_net subnet, see README
|
|
||||||
# proxy:
|
|
||||||
# header_map:
|
|
||||||
# user: remote-user
|
|
||||||
# role: remote-groups
|
|
||||||
# default_role: viewer
|
|
||||||
# separator: '|'
|
|
||||||
# # Optional but recommended when Caddy and Frigate are on different
|
|
||||||
# # hosts/VLANs. Generate with `openssl rand -hex 32` and add the
|
|
||||||
# # matching `header_up X-Proxy-Secret <value>` below.
|
|
||||||
# # auth_secret: 'paste-32-byte-hex-here'
|
|
||||||
# -----------------------------------------------------------------------------
|
|
||||||
cam.example.com {
|
|
||||||
import accesslog
|
|
||||||
import authelia
|
|
||||||
reverse_proxy 192.168.x.x:8971 {
|
|
||||||
transport http {
|
|
||||||
read_timeout 60s
|
|
||||||
write_timeout 60s
|
|
||||||
}
|
|
||||||
# Uncomment and match Frigate's auth_secret if you set one above.
|
|
||||||
# header_up X-Proxy-Secret "paste-same-32-byte-hex-here"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
|
||||||
# CASE 3: app keeps its own auth (no Authelia involvement).
|
|
||||||
# Example: a router admin page or a service that can't do proxy auth.
|
|
||||||
# Do NOT add `import authelia` and do NOT add an access_control rule for it.
|
|
||||||
# -----------------------------------------------------------------------------
|
|
||||||
# router.example.com {
|
|
||||||
# import accesslog
|
|
||||||
# reverse_proxy 192.168.1.1:443 {
|
|
||||||
# transport http {
|
|
||||||
# tls_insecure_skip_verify
|
|
||||||
# }
|
|
||||||
# }
|
|
||||||
# }
|
|
||||||
+11
-5
@@ -60,14 +60,20 @@ services:
|
|||||||
- F2B_DB_PURGE_AGE=7d
|
- F2B_DB_PURGE_AGE=7d
|
||||||
volumes:
|
volumes:
|
||||||
- ./fail2ban/data:/data
|
- ./fail2ban/data:/data
|
||||||
# Authelia log -- read-only mount so fail2ban can parse 1FA/TOTP
|
# Authelia text log -- fail2ban watches this for 1FA/TOTP failures.
|
||||||
# failures. Authelia writes this under /config which is ./authelia.
|
# Authelia writes it to /config/authelia.log = ./authelia/authelia.log.
|
||||||
|
# IMPORTANT: `touch authelia/authelia.log` before first `docker compose up`
|
||||||
|
# so Docker creates it as a file, not a directory (see README.md).
|
||||||
- ./authelia/authelia.log:/var/log/authelia/authelia.log:ro
|
- ./authelia/authelia.log:/var/log/authelia/authelia.log:ro
|
||||||
# Caddy access log -- you must configure your Caddyfile to write
|
# Caddy JSON access log -- covers ALL sites that import (accesslog),
|
||||||
# JSON access logs to this host path. See README.md.
|
# not just Authelia-gated ones. Mount the host directory read-only.
|
||||||
|
# Your Caddy must write here; see README.md "Caddy access log path".
|
||||||
- /var/log/caddy:/var/log/caddy:ro
|
- /var/log/caddy:/var/log/caddy:ro
|
||||||
|
# Wait for Authelia to pass its healthcheck before starting, so the
|
||||||
|
# authelia.log file exists before fail2ban tries to bind-mount it.
|
||||||
depends_on:
|
depends_on:
|
||||||
- authelia
|
authelia:
|
||||||
|
condition: service_healthy
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
caddy_net:
|
caddy_net:
|
||||||
|
|||||||
Reference in New Issue
Block a user