Initial Authelia + fail2ban stack
Self-hosted SSO portal with file-based users, SQLite storage, filesystem notifier, and an iptables-banning fail2ban sidecar. Designed to drop into a DotheEvo-style ~/docker layout next to a dockerized Caddy on the main server, joining the same external caddy_net so Caddy reaches Authelia by container name. fail2ban runs in host network mode with NET_ADMIN/NET_RAW caps so its bans hit DOCKER-USER and actually drop packets at the edge. Includes a Caddy snippet (caddy/snippet.example.caddyfile) to merge into the user's real Caddyfile -- this repo doesn't manage Caddy itself. https://claude.ai/code/session_013XZ1vmgk78k2PEQ5DmJhF3
This commit is contained in:
@@ -0,0 +1,14 @@
|
||||
# Copy to .env and fill in. DO NOT commit .env.
|
||||
#
|
||||
# Secrets themselves live as files under authelia/secrets/ (also gitignored)
|
||||
# so they can be mounted into the container without env-var leakage. This
|
||||
# .env only holds non-secret tunables.
|
||||
|
||||
# Pin your image versions. Bump to current stable when you upgrade --
|
||||
# check https://github.com/authelia/authelia/releases and
|
||||
# https://github.com/crazy-max/docker-fail2ban/releases.
|
||||
AUTHELIA_VERSION=4.39
|
||||
FAIL2BAN_VERSION=latest
|
||||
|
||||
# Used by both containers for log timestamps. Set to your IANA zone.
|
||||
TZ=America/New_York
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
# Secrets — never commit
|
||||
.env
|
||||
authelia/secrets/JWT_SECRET
|
||||
authelia/secrets/SESSION_SECRET
|
||||
authelia/secrets/STORAGE_ENCRYPTION_KEY
|
||||
authelia/secrets/SMTP_PASSWORD
|
||||
|
||||
# Authelia runtime state
|
||||
authelia/users_database.yml
|
||||
authelia/db.sqlite3
|
||||
authelia/db.sqlite3-*
|
||||
authelia/authelia.log
|
||||
authelia/notifications/notification.txt
|
||||
|
||||
# fail2ban runtime state
|
||||
fail2ban/data/db/
|
||||
fail2ban/data/fail2ban.sqlite3
|
||||
fail2ban/data/*.bak
|
||||
fail2ban/data/jail.d/*.bak
|
||||
fail2ban/data/filter.d/*.bak
|
||||
|
||||
# Editor / OS junk
|
||||
*.swp
|
||||
*~
|
||||
.DS_Store
|
||||
.vscode/
|
||||
.idea/
|
||||
@@ -0,0 +1,391 @@
|
||||
# Authelia + fail2ban
|
||||
|
||||
Self-hosted authentication portal (Authelia) with an IP-banning sidecar
|
||||
(fail2ban). Designed to sit next to a dockerized Caddy on the main server
|
||||
and gate every public subdomain (`cam.example.com`, `doorbell.example.com`,
|
||||
etc.) behind a single sign-on portal at `auth.example.com`.
|
||||
|
||||
```
|
||||
Internet
|
||||
|
|
||||
v
|
||||
+-------+ caddy_net (docker) +--------------------+
|
||||
| Caddy |--- forward_auth -------------->| Authelia |
|
||||
+---+---+ | /api/authz/... |
|
||||
| reverse_proxy +--------+-----------+
|
||||
| |
|
||||
v v
|
||||
Frigate (IoT VLAN), ntfy, etc. ./authelia/db.sqlite3
|
||||
./authelia/authelia.log
|
||||
^
|
||||
| tail
|
||||
+------+--------+
|
||||
| fail2ban | host net
|
||||
| DOCKER-USER | +iptables
|
||||
+---------------+
|
||||
```
|
||||
|
||||
- One docker-compose file, two services, one external network (`caddy_net`).
|
||||
- File-backed users database, SQLite storage, in-memory sessions (no Redis).
|
||||
- Filesystem notifier -- password reset / new-device emails get written to
|
||||
a file you `tail -f`. Swap to SMTP later by editing one block.
|
||||
- fail2ban runs in host network mode and bans via the `DOCKER-USER` chain
|
||||
so drops happen at the host edge, before traffic reaches the Caddy
|
||||
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
|
||||
|
||||
```
|
||||
authelia/
|
||||
|-- docker-compose.yml
|
||||
|-- .env.example # copy to .env
|
||||
|-- .gitignore
|
||||
|-- README.md # this file
|
||||
|
|
||||
|-- authelia/
|
||||
| |-- configuration.yml # main config -- edit your domain in here
|
||||
| |-- users_database.yml.example # copy to users_database.yml (gitignored)
|
||||
| |-- secrets/ # gitignored; secret files mounted as /secrets
|
||||
| `-- notifications/ # filesystem notifier writes here
|
||||
|
|
||||
|-- fail2ban/
|
||||
| `-- data/ # mounted as /data in the container
|
||||
| |-- jail.d/
|
||||
| | |-- authelia.local
|
||||
| | `-- caddy.local
|
||||
| `-- filter.d/
|
||||
| |-- authelia.local
|
||||
| `-- caddy-4xx.local
|
||||
|
|
||||
`-- caddy/
|
||||
`-- snippet.example.caddyfile # merge into YOUR Caddyfile
|
||||
```
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Docker + docker compose v2 on the main server.
|
||||
- Caddy already running on the main server, in Docker, joined to an
|
||||
external network named `caddy_net`. If your network is named differently,
|
||||
change `caddy_net` everywhere in this repo.
|
||||
- A root domain you control (the examples use `example.com`). DNS records
|
||||
for `auth.<root>` and every protected subdomain should point at the
|
||||
Caddy host's public IP.
|
||||
- Caddy v2.5.1 or newer (for `forward_auth` directive).
|
||||
|
||||
## First-run setup
|
||||
|
||||
```bash
|
||||
# 0) From wherever you keep ~/docker stacks
|
||||
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
|
||||
|
||||
# 2) Bootstrap the secrets directory
|
||||
mkdir -p authelia/secrets
|
||||
openssl rand -hex 32 > authelia/secrets/JWT_SECRET
|
||||
openssl rand -hex 32 > authelia/secrets/SESSION_SECRET
|
||||
openssl rand -hex 32 > authelia/secrets/STORAGE_ENCRYPTION_KEY
|
||||
chmod 600 authelia/secrets/*
|
||||
|
||||
# 3) Copy and edit the .env
|
||||
cp .env.example .env
|
||||
$EDITOR .env # set TZ; pin AUTHELIA_VERSION if you want
|
||||
|
||||
# 4) Edit configuration.yml -- replace EVERY `example.com` with your real
|
||||
# root domain (look for the CHANGE comments)
|
||||
$EDITOR authelia/configuration.yml
|
||||
|
||||
# 5) Create your first user
|
||||
cp authelia/users_database.yml.example authelia/users_database.yml
|
||||
$EDITOR authelia/users_database.yml # set username, email, displayname
|
||||
|
||||
# Generate the password hash:
|
||||
docker compose run --rm authelia \
|
||||
authelia crypto hash generate argon2 --password 'your-real-password'
|
||||
|
||||
# Paste the resulting `$argon2id$v=19$m=...` into the user's `password:`.
|
||||
|
||||
# 6) Validate the config before starting (catches typos / schema issues)
|
||||
docker compose run --rm authelia \
|
||||
authelia validate-config --config /config/configuration.yml
|
||||
|
||||
# 7) Bring it up
|
||||
docker compose up -d
|
||||
docker compose logs -f authelia # expect "Authelia is listening on ..."
|
||||
```
|
||||
|
||||
## Wire Caddy into Authelia
|
||||
|
||||
Open `caddy/snippet.example.caddyfile`. It defines:
|
||||
|
||||
- `(authelia)` -- a reusable snippet: `import authelia` in any site block to
|
||||
gate it.
|
||||
- `(accesslog)` -- writes Caddy's JSON access log to `/var/log/caddy/access.log`
|
||||
so fail2ban can watch it.
|
||||
- `auth.example.com` -- the Authelia portal subdomain.
|
||||
- Example protected blocks for `cam.example.com` and `doorbell.example.com`.
|
||||
|
||||
Copy the relevant blocks into your real Caddyfile, replace `example.com`
|
||||
with your domain and `192.168.x.x` with real upstream IPs, then reload Caddy:
|
||||
|
||||
```bash
|
||||
docker compose -f ~/docker/caddy/docker-compose.yml exec caddy \
|
||||
caddy reload --config /etc/caddy/Caddyfile
|
||||
```
|
||||
|
||||
For each protected domain, also add a rule under `access_control.rules` in
|
||||
`authelia/configuration.yml` (Authelia's default policy is `deny` -- a
|
||||
domain with no rule will not authenticate). Restart Authelia after editing:
|
||||
|
||||
```bash
|
||||
docker compose restart authelia
|
||||
```
|
||||
|
||||
### 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
|
||||
sudo mkdir -p /var/log/caddy
|
||||
sudo chown -R 1000:1000 /var/log/caddy # adjust UID to match your Caddy
|
||||
```
|
||||
|
||||
## First login + TOTP enrollment
|
||||
|
||||
1. Visit any protected subdomain in a private browser window.
|
||||
2. Caddy bounces you to `https://auth.<your-domain>/` -- log in with the
|
||||
username and plaintext password you set above.
|
||||
3. If the access rule is `two_factor`, Authelia asks you to register a
|
||||
second factor. Pick **TOTP** and scan the QR with Authy / 1Password /
|
||||
Google Authenticator / Bitwarden / etc.
|
||||
4. On first registration Authelia tries to email you a confirmation link.
|
||||
The filesystem notifier writes it to a file -- grab it with:
|
||||
```bash
|
||||
docker compose exec authelia cat /config/notifications/notification.txt
|
||||
```
|
||||
Click that link to confirm registration.
|
||||
5. Re-enter the TOTP code -- you're in.
|
||||
|
||||
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
|
||||
|
||||
### Add a user
|
||||
|
||||
Append to `authelia/users_database.yml`, generate a hash with
|
||||
`docker compose run --rm authelia authelia crypto hash generate argon2 --password '...'`,
|
||||
paste it as `password:`, then `docker compose restart authelia` (or wait
|
||||
five minutes for the file refresh interval).
|
||||
|
||||
### Change a password
|
||||
|
||||
Same as above -- re-generate the hash and replace the `password:` field.
|
||||
|
||||
### Disable a user
|
||||
|
||||
Set `disabled: true` on their entry and restart Authelia.
|
||||
|
||||
### Reset their TOTP
|
||||
|
||||
```bash
|
||||
docker compose exec authelia \
|
||||
authelia storage user totp delete --username yourname \
|
||||
--config /config/configuration.yml
|
||||
```
|
||||
|
||||
They will be prompted to re-enroll on next login.
|
||||
|
||||
## fail2ban
|
||||
|
||||
### Verify it's running and watching the right files
|
||||
|
||||
```bash
|
||||
docker compose exec fail2ban fail2ban-client status
|
||||
docker compose exec fail2ban fail2ban-client status authelia
|
||||
docker compose exec fail2ban fail2ban-client status caddy-4xx
|
||||
```
|
||||
|
||||
Each `status <jail>` shows the active failures, banned IPs, and the log
|
||||
file it's tailing.
|
||||
|
||||
### Test a filter against your real logs
|
||||
|
||||
```bash
|
||||
# Authelia
|
||||
docker compose exec fail2ban fail2ban-regex \
|
||||
/var/log/authelia/authelia.log \
|
||||
/data/filter.d/authelia.local
|
||||
|
||||
# Caddy
|
||||
docker compose exec fail2ban fail2ban-regex \
|
||||
/var/log/caddy/access.log \
|
||||
/data/filter.d/caddy-4xx.local
|
||||
```
|
||||
|
||||
If the failregex doesn't match anything, your log format probably differs
|
||||
from what the filter expects. For Authelia: confirm `log.format: 'text'`
|
||||
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
|
||||
|
||||
```bash
|
||||
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
|
||||
```
|
||||
|
||||
### Tune
|
||||
|
||||
Per-jail `maxretry`, `findtime`, `bantime` live in
|
||||
`fail2ban/data/jail.d/*.local`. Edit and restart fail2ban:
|
||||
|
||||
```bash
|
||||
docker compose restart fail2ban
|
||||
```
|
||||
|
||||
The `caddy-4xx` jail's defaults (30 fails / 2 minutes -> 30 minute ban) are
|
||||
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
|
||||
docker compose ps # everything up?
|
||||
docker compose logs -f authelia # follow Authelia
|
||||
docker compose logs -f fail2ban # follow fail2ban
|
||||
docker compose pull && docker compose up -d # upgrade
|
||||
```
|
||||
|
||||
Bump `AUTHELIA_VERSION` in `.env` when you upgrade Authelia. After any
|
||||
Authelia upgrade, re-run `validate-config` -- the schema does evolve.
|
||||
|
||||
## 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
|
||||
|
||||
When you have a transactional sender (Mailgun, Postmark, Amazon SES, your
|
||||
own postfix), replace the `notifier:` block in `authelia/configuration.yml`:
|
||||
|
||||
```yaml
|
||||
notifier:
|
||||
disable_startup_check: false
|
||||
smtp:
|
||||
address: 'smtps://smtp.example.com:465'
|
||||
username: 'authelia@example.com'
|
||||
sender: 'Authelia <authelia@example.com>'
|
||||
subject: '[Authelia] {title}'
|
||||
# Password loaded via AUTHELIA_NOTIFIER_SMTP_PASSWORD_FILE
|
||||
```
|
||||
|
||||
Add the password file:
|
||||
```bash
|
||||
echo 'your_smtp_password' > authelia/secrets/SMTP_PASSWORD
|
||||
chmod 600 authelia/secrets/SMTP_PASSWORD
|
||||
```
|
||||
|
||||
And add to `docker-compose.yml` under the authelia service `environment:`:
|
||||
```yaml
|
||||
- AUTHELIA_NOTIFIER_SMTP_PASSWORD_FILE=/secrets/SMTP_PASSWORD
|
||||
```
|
||||
|
||||
Restart and verify with `docker compose logs authelia` -- expect a
|
||||
"Notifier SMTP startup check successful" line.
|
||||
|
||||
## Security notes
|
||||
|
||||
- `.env`, `authelia/secrets/*`, `authelia/users_database.yml`,
|
||||
`authelia/db.sqlite3*`, and the notifications file are all gitignored.
|
||||
Verify with `git status` before every commit.
|
||||
- Authelia is *not* port-mapped to the host. Only containers on
|
||||
`caddy_net` can reach it, and only Caddy is configured to forward
|
||||
unauthenticated traffic to it via `forward_auth`.
|
||||
- The TOTP secrets in the SQLite DB are encrypted at rest with
|
||||
`STORAGE_ENCRYPTION_KEY`. Lose that file and you lose every user's TOTP
|
||||
-- back it up alongside the DB.
|
||||
- `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
|
||||
|
||||
Once this is steady-state:
|
||||
|
||||
- Add OIDC clients in Authelia for apps that speak OIDC natively (Grafana,
|
||||
Gitea, etc.) -- they'll do real SSO without forward-auth headers.
|
||||
- Switch the filesystem notifier to SMTP (see above).
|
||||
- Consider a backup job for `authelia/db.sqlite3` and `authelia/secrets/`
|
||||
-- losing either is a recovery mess.
|
||||
@@ -0,0 +1,117 @@
|
||||
---
|
||||
###############################################################################
|
||||
# Authelia configuration
|
||||
#
|
||||
# - File-based user database (no LDAP)
|
||||
# - SQLite local storage (no Redis, no MySQL/Postgres)
|
||||
# - Filesystem notifier (writes "emails" to /config/notifications/...)
|
||||
# - Argon2id password hashing
|
||||
# - Per-domain access policies under access_control.rules
|
||||
#
|
||||
# Secrets are NOT in this file. They are loaded from files mounted at
|
||||
# /secrets via the AUTHELIA_*_FILE env vars in docker-compose.yml.
|
||||
#
|
||||
# After editing, validate before restarting:
|
||||
# docker compose run --rm authelia authelia validate-config --config /config/configuration.yml
|
||||
###############################################################################
|
||||
|
||||
theme: 'dark'
|
||||
|
||||
server:
|
||||
address: 'tcp://0.0.0.0:9091'
|
||||
buffers:
|
||||
read: 8192
|
||||
write: 8192
|
||||
|
||||
log:
|
||||
level: 'info'
|
||||
format: 'text' # fail2ban filter expects text format
|
||||
file_path: '/config/authelia.log'
|
||||
keep_stdout: true # also log to stdout for `docker logs`
|
||||
|
||||
identity_validation:
|
||||
reset_password:
|
||||
jwt_lifespan: '5 minutes'
|
||||
jwt_algorithm: 'HS256'
|
||||
# jwt_secret loaded via AUTHELIA_IDENTITY_VALIDATION_RESET_PASSWORD_JWT_SECRET_FILE
|
||||
|
||||
totp:
|
||||
disable: false
|
||||
issuer: 'example.com' # CHANGE: your root domain (shown in authenticator app)
|
||||
algorithm: 'sha1'
|
||||
digits: 6
|
||||
period: 30
|
||||
|
||||
authentication_backend:
|
||||
password_change:
|
||||
disable: false
|
||||
password_reset:
|
||||
disable: false
|
||||
refresh_interval: '5 minutes'
|
||||
file:
|
||||
path: '/config/users_database.yml'
|
||||
password:
|
||||
algorithm: 'argon2'
|
||||
argon2:
|
||||
variant: 'argon2id'
|
||||
iterations: 3
|
||||
memory: 65536
|
||||
parallelism: 4
|
||||
key_length: 32
|
||||
salt_length: 16
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Access control
|
||||
#
|
||||
# default_policy: deny means every domain Authelia sees must have an
|
||||
# explicit allow rule. Caddy only sends a domain to Authelia when its site
|
||||
# block has `import authelia`, so domains you don't proxy through Authelia
|
||||
# aren't affected.
|
||||
#
|
||||
# Policies:
|
||||
# bypass no auth (Authelia portal itself)
|
||||
# one_factor password only
|
||||
# two_factor password + TOTP
|
||||
# ---------------------------------------------------------------------------
|
||||
access_control:
|
||||
default_policy: 'deny'
|
||||
rules:
|
||||
- domain: 'auth.example.com' # CHANGE
|
||||
policy: 'bypass'
|
||||
|
||||
# Examples -- uncomment / change to your subdomains:
|
||||
# - domain: 'cam.example.com'
|
||||
# policy: 'one_factor'
|
||||
# - domain: 'doorbell.example.com'
|
||||
# policy: 'two_factor'
|
||||
|
||||
session:
|
||||
# secret loaded via AUTHELIA_SESSION_SECRET_FILE
|
||||
cookies:
|
||||
- name: 'authelia_session'
|
||||
domain: 'example.com' # CHANGE: your root domain
|
||||
authelia_url: 'https://auth.example.com' # CHANGE
|
||||
default_redirection_url: 'https://example.com' # CHANGE
|
||||
expiration: '1 hour'
|
||||
inactivity: '5 minutes'
|
||||
remember_me: '1 month'
|
||||
same_site: 'lax'
|
||||
|
||||
# In-app rate limiting. First line of defense; fail2ban is the second.
|
||||
regulation:
|
||||
max_retries: 3
|
||||
find_time: '2 minutes'
|
||||
ban_time: '5 minutes'
|
||||
|
||||
storage:
|
||||
# encryption_key loaded via AUTHELIA_STORAGE_ENCRYPTION_KEY_FILE
|
||||
local:
|
||||
path: '/config/db.sqlite3'
|
||||
|
||||
# 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
|
||||
# transactional sender.
|
||||
notifier:
|
||||
disable_startup_check: false
|
||||
filesystem:
|
||||
filename: '/config/notifications/notification.txt'
|
||||
@@ -0,0 +1,34 @@
|
||||
---
|
||||
###############################################################################
|
||||
# Authelia users database
|
||||
#
|
||||
# Copy this to users_database.yml (gitignored) and edit. Generate the
|
||||
# password hash with:
|
||||
#
|
||||
# docker compose run --rm authelia \
|
||||
# authelia crypto hash generate argon2 --password 'your-plaintext-pass'
|
||||
#
|
||||
# Paste the resulting `$argon2id$v=19$m=...` string as the `password:` value.
|
||||
# Restart Authelia for changes to take effect (or wait refresh_interval).
|
||||
###############################################################################
|
||||
|
||||
users:
|
||||
|
||||
yourname:
|
||||
disabled: false
|
||||
displayname: 'Your Name'
|
||||
password: '$argon2id$v=19$m=65536,t=3,p=4$REPLACE_WITH_GENERATED_HASH'
|
||||
email: 'you@example.com'
|
||||
groups:
|
||||
- 'admins'
|
||||
|
||||
# Add more users here. `groups` are referenced from access_control rules
|
||||
# via `subject: 'group:admins'`.
|
||||
#
|
||||
# guest:
|
||||
# disabled: false
|
||||
# displayname: 'Guest'
|
||||
# password: '$argon2id$v=19$m=65536,t=3,p=4$...'
|
||||
# email: 'guest@example.com'
|
||||
# groups:
|
||||
# - 'guests'
|
||||
@@ -0,0 +1,87 @@
|
||||
# =============================================================================
|
||||
# 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
|
||||
# - access_control.rules in authelia/configuration.yml have an entry
|
||||
# for each protected domain (otherwise Authelia's default_policy of
|
||||
# `deny` will refuse access)
|
||||
# =============================================================================
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Reusable forward_auth snippet -- import into any site you want gated.
|
||||
# -----------------------------------------------------------------------------
|
||||
(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
|
||||
}
|
||||
}
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Authelia login portal -- bypass policy in access_control.rules
|
||||
# -----------------------------------------------------------------------------
|
||||
auth.example.com {
|
||||
import accesslog
|
||||
reverse_proxy authelia:9091
|
||||
}
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Example: gate cam.example.com (Frigate UI on a different VLAN/host)
|
||||
# Per-domain policy lives in authelia/configuration.yml, NOT here.
|
||||
# -----------------------------------------------------------------------------
|
||||
cam.example.com {
|
||||
import accesslog
|
||||
import authelia
|
||||
reverse_proxy 192.168.x.x:8971 {
|
||||
transport http {
|
||||
read_timeout 60s
|
||||
write_timeout 60s
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Example: gate doorbell.example.com (Pi PTT page + same-origin Frigate proxy)
|
||||
# -----------------------------------------------------------------------------
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
# ---------------------------------------------------------------------------
|
||||
# Authelia + fail2ban
|
||||
#
|
||||
# Self-hosted authentication portal (Authelia) plus an IP-banning sidecar
|
||||
# (fail2ban). Sits next to your dockerized Caddy on the main server and
|
||||
# joins the same external `caddy_net` so Caddy reaches Authelia by
|
||||
# container name (`authelia:9091`). Authelia is NOT port-mapped to the
|
||||
# host -- there is no reason for anything outside the docker network to
|
||||
# hit it directly.
|
||||
#
|
||||
# fail2ban runs in host network mode so its iptables bans drop packets
|
||||
# at the host edge, which is the only place the bans actually work for
|
||||
# traffic destined for docker-published ports.
|
||||
#
|
||||
# First-run: see README.md.
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
name: authelia
|
||||
|
||||
services:
|
||||
|
||||
authelia:
|
||||
container_name: authelia
|
||||
image: authelia/authelia:${AUTHELIA_VERSION:-4.39}
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- caddy_net
|
||||
expose:
|
||||
- 9091
|
||||
environment:
|
||||
# Secrets are loaded from files mounted at /secrets (see volumes).
|
||||
# The _FILE suffix is supported for any AUTHELIA_* env var.
|
||||
- AUTHELIA_IDENTITY_VALIDATION_RESET_PASSWORD_JWT_SECRET_FILE=/secrets/JWT_SECRET
|
||||
- AUTHELIA_SESSION_SECRET_FILE=/secrets/SESSION_SECRET
|
||||
- AUTHELIA_STORAGE_ENCRYPTION_KEY_FILE=/secrets/STORAGE_ENCRYPTION_KEY
|
||||
- TZ=${TZ:-UTC}
|
||||
volumes:
|
||||
- ./authelia:/config
|
||||
- ./authelia/secrets:/secrets:ro
|
||||
healthcheck:
|
||||
test: ['CMD', 'authelia', 'healthcheck']
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
start_period: 30s
|
||||
|
||||
fail2ban:
|
||||
container_name: fail2ban
|
||||
image: crazymax/fail2ban:${FAIL2BAN_VERSION:-latest}
|
||||
restart: unless-stopped
|
||||
# Host networking so iptables bans take effect on the host's edge,
|
||||
# including DOCKER-USER chain rules that gate traffic to containers.
|
||||
network_mode: host
|
||||
cap_add:
|
||||
- NET_ADMIN
|
||||
- NET_RAW
|
||||
environment:
|
||||
- TZ=${TZ:-UTC}
|
||||
- F2B_LOG_LEVEL=INFO
|
||||
- F2B_DB_PURGE_AGE=7d
|
||||
volumes:
|
||||
- ./fail2ban/data:/data
|
||||
# Authelia log -- read-only mount so fail2ban can parse 1FA/TOTP
|
||||
# failures. Authelia writes this under /config which is ./authelia.
|
||||
- ./authelia/authelia.log:/var/log/authelia/authelia.log:ro
|
||||
# Caddy access log -- you must configure your Caddyfile to write
|
||||
# JSON access logs to this host path. See README.md.
|
||||
- /var/log/caddy:/var/log/caddy:ro
|
||||
depends_on:
|
||||
- authelia
|
||||
|
||||
networks:
|
||||
caddy_net:
|
||||
external: true
|
||||
@@ -0,0 +1,15 @@
|
||||
# Matches Authelia's text-format log lines for failed authentication.
|
||||
# Targets Authelia 4.38+. If you change `log.format` to `json` in
|
||||
# authelia/configuration.yml, this regex needs updating.
|
||||
#
|
||||
# Test against a real log:
|
||||
# docker compose exec fail2ban fail2ban-regex \
|
||||
# /var/log/authelia/authelia.log \
|
||||
# /data/filter.d/authelia.local
|
||||
|
||||
[Definition]
|
||||
|
||||
failregex = ^.*Unsuccessful (1FA|TOTP|Duo|U2F) authentication attempt by user.*remote_ip"?(:|=)"?<HOST>"?.*$
|
||||
^.*user not found.*path=/api/reset-password/identity/start.*remote_ip"?(:|=)"?<HOST>"?.*$
|
||||
|
||||
ignoreregex =
|
||||
@@ -0,0 +1,12 @@
|
||||
# Bans IPs that spray 401/403/404/429 across many requests against Caddy.
|
||||
# Targets Caddy's default JSON access log shape (one JSON object per line).
|
||||
# Verify against a real log:
|
||||
# docker compose exec fail2ban fail2ban-regex \
|
||||
# /var/log/caddy/access.log \
|
||||
# /data/filter.d/caddy-4xx.local
|
||||
|
||||
[Definition]
|
||||
|
||||
failregex = ^.*"remote_ip":"<HOST>".*"status":(401|403|404|429).*$
|
||||
|
||||
ignoreregex = ^.*"uri":"/(favicon\.ico|robots\.txt|apple-touch-icon[^"]*)".*$
|
||||
@@ -0,0 +1,17 @@
|
||||
[authelia]
|
||||
enabled = true
|
||||
filter = authelia
|
||||
logpath = /var/log/authelia/authelia.log
|
||||
maxretry = 3
|
||||
findtime = 10m
|
||||
bantime = 1h
|
||||
|
||||
# DOCKER-USER is the chain Docker inserts before its own per-container
|
||||
# rules; banning here drops packets destined for docker-published ports
|
||||
# (i.e. your Caddy container's 80/443) before iptables routes them in.
|
||||
chain = DOCKER-USER
|
||||
banaction = iptables-allports
|
||||
|
||||
# Tuple-form action so we record where it came from. `port=anyport` is
|
||||
# fine because chain=DOCKER-USER drops at the chain head regardless.
|
||||
action = iptables-allports[name=authelia, chain=DOCKER-USER]
|
||||
@@ -0,0 +1,14 @@
|
||||
[caddy-4xx]
|
||||
enabled = true
|
||||
filter = caddy-4xx
|
||||
# Adjust if your Caddy writes elsewhere -- this must match the host path
|
||||
# mounted into the fail2ban container in docker-compose.yml.
|
||||
logpath = /var/log/caddy/access.log
|
||||
maxretry = 30
|
||||
findtime = 2m
|
||||
bantime = 30m
|
||||
|
||||
chain = DOCKER-USER
|
||||
banaction = iptables-allports
|
||||
|
||||
action = iptables-allports[name=caddy-4xx, chain=DOCKER-USER]
|
||||
Reference in New Issue
Block a user