Auth layer (Authentik 2026.8.0): - compose: 3-service stack (authentik-postgres, authentik-server, authentik-worker), postgres health-gated, bind mounts under /home/dank/authentik - Caddyfile: auth.franksdank.cloud vhost; forward_auth on the no-auth vhosts (homepage, SearXNG, Hermes, Prometheus, RomM, Copyparty); outpost path proxied first in a route block - .env.example: AUTHENTIK_PG_PASS + AUTHENTIK_SECRET_KEY - deploy.sh: render only CADDY_BASICAUTH_HASH (the homepage per-user hashes are retired; grafana + pihole keep basicauth) - tests/authentik.bats (new) + updated homepage/caddyfile/pihole seams - docs: Authentik.md + per-service pages; Caddy.md + Homepage.md updated Homepage (naked domain): - Caddyfile: dankclaw.franksdank.cloud -> franksdank.cloud www.franksdank.cloud; dankclaw + dankserver redirect to it; bare-IP catch-all updated - addn-hosts: A records for the naked domain, www, and auth (all -> 143) - tests/pihole.bats: vhost_names handles multi-address blocks; TLD check allows the naked domain All 146 tests pass.
153 lines
6.3 KiB
Markdown
153 lines
6.3 KiB
Markdown
# Authentik — the homelab's SSO / OIDC provider
|
||
|
||
Authentik is the single sign-on (SSO) and OpenID Connect (OIDC) provider for the
|
||
homelab. It fronts the Caddy vhosts that do not carry their own
|
||
application-level authentication. Apps that have their own login (Grafana,
|
||
Gitea, OpenWebUI, Jellyfin, the *arr stack, Pi-hole) are not wrapped in
|
||
forward_auth — they handle their own session.
|
||
|
||
- **Version:** `2026.8.0` (pinned in `dankclaw/compose.yaml`)
|
||
- **Host:** dankclaw (`192.168.0.143`)
|
||
- **Name:** `auth.franksdank.cloud`
|
||
- **Stack:** 3 compose services — `authentik-postgres`, `authentik-server`,
|
||
`authentik-worker` (see [Pi-hole](Pi-hole.md) for the LAN-DNS pattern)
|
||
|
||
## Why Authentik
|
||
|
||
The comparison that chose it (against Authelia, Keycloak, Zitadel, Ory) is in
|
||
`.docaudit/research/auth.md`. In short: full OIDC with single-logout, a web GUI
|
||
for all configuration, a native Caddy `forward_auth` outpost, and a footprint
|
||
that fits on a 3-user homelab (~1–1.5 GB idle). Redis was removed in 2025.10,
|
||
so the stack is 3 services, not 4.
|
||
|
||
## Architecture
|
||
|
||
```mermaid
|
||
graph LR
|
||
subgraph Browser
|
||
U[User]
|
||
end
|
||
subgraph Caddy [Caddy - auth gate]
|
||
FA[forward_auth]
|
||
end
|
||
subgraph AK [Authentik]
|
||
SRV[server :9000]
|
||
WKR[worker]
|
||
PG[(postgres)]
|
||
OUT[outpost /auth/caddy]
|
||
end
|
||
subgraph App [Target app]
|
||
A1[SearXNG / Hermes / Prometheus<br/>RomM / Copyparty / homepage]
|
||
end
|
||
|
||
U -->|1. GET /| FA
|
||
FA -->|2. GET /outpost.../auth/caddy| OUT
|
||
OUT -->|3. no session: 302 login| U
|
||
U -->|4. login at auth.franksdank.cloud| SRV
|
||
SRV --> PG
|
||
WKR --> PG
|
||
SRV -->|5. 2xx: session valid| FA
|
||
FA -->|6. proxy with headers| A1
|
||
```
|
||
|
||
1. The browser hits a protected vhost (e.g. `search.franksdank.cloud`).
|
||
2. Caddy `forward_auth` clones the request to the Authentik outpost
|
||
(`/outpost.goauthentik.io/auth/caddy`).
|
||
3. With no valid session, the outpost returns a `302` to the Authentik login
|
||
page (`auth.franksdank.cloud`).
|
||
4. The user logs in at Authentik (email + password, TOTP/WebAuthn MFA, or an
|
||
upstream provider).
|
||
5. On the next request the outpost returns `2xx`; Caddy copies the
|
||
`X-Authentik-*` headers and proxies to the app.
|
||
|
||
## The three compose services
|
||
|
||
| Service | Role | Image |
|
||
|---|---|---|
|
||
| `authentik-postgres` | PostgreSQL 16 (users, flows, sessions, events) | `docker.io/library/postgres:16-alpine` |
|
||
| `authentik-server` | Core + admin API + the embedded Caddy outpost (port 9000) | `ghcr.io/goauthentik/server:2026.8.0` |
|
||
| `authentik-worker` | Background tasks (event processing, provider sync, flows) | `ghcr.io/goauthentik/server:2026.8.0` |
|
||
|
||
- **Postgres** is the only stateful store. Sessions, caching, and WebSockets
|
||
moved to PostgreSQL in 2025.10 (Redis removed).
|
||
- **Server** and **worker** run the same image; the `command` selects the role
|
||
(`server` / `worker`).
|
||
- The worker runs as `0:0` (rootless: container root maps to the host user that
|
||
owns the data dir).
|
||
- Data persists under `/home/dank/authentik/` (bind mounts, repo policy — no
|
||
named volumes).
|
||
|
||
## Caddy integration
|
||
|
||
The Caddyfile wraps the no-auth vhosts in a `route` block. The pattern (one
|
||
example; the others are identical):
|
||
|
||
```caddyfile
|
||
search.franksdank.cloud {
|
||
route {
|
||
# 1. The outpost path is proxied to Authentik (login UI + session).
|
||
reverse_proxy /outpost.goauthentik.io/* authentik-server:9000
|
||
# 2. forward_auth checks the session before serving the app.
|
||
forward_auth authentik-server:9000 {
|
||
uri /outpost.goauthentik.io/auth/caddy
|
||
copy_headers X-Authentik-Username X-Authentik-Groups X-Authentik-Email X-Authentik-Name
|
||
trusted_proxies private_ranges
|
||
}
|
||
# 3. The app.
|
||
reverse_proxy 192.168.0.143:8081
|
||
}
|
||
}
|
||
```
|
||
|
||
Vhosts protected by forward_auth: the homepage (`franksdank.cloud`,
|
||
`www.franksdank.cloud`), SearXNG, Hermes, Prometheus, RomM, Copyparty. The
|
||
`auth.franksdank.cloud` vhost proxies to the server directly (no forward_auth —
|
||
it *is* the login).
|
||
|
||
## Configuration (secrets)
|
||
|
||
Two secrets live in the host `.env` (git-ignored; placeholders in
|
||
`.env.example`):
|
||
|
||
- `AUTHENTIK_PG_PASS` — the PostgreSQL password. Generate: `openssl rand -base64 32`.
|
||
- `AUTHENTIK_SECRET_KEY` — the server's signing key. Generate: `openssl rand -base64 48`.
|
||
|
||
## First-time setup
|
||
|
||
1. `./scripts/deploy.sh dankclaw` — pulls, starts the 3 services (postgres
|
||
health-gated), reloads Caddy.
|
||
2. Open `https://auth.franksdank.cloud` — the initial setup wizard runs. It
|
||
creates the superuser (email + password) and generates the DB schema.
|
||
3. In the admin interface: **Directory → Applications** → add a **Proxy**
|
||
application (or an **OAuth2/OpenID Connect** provider) for each vhost you
|
||
protect. The Caddy outpost reads the application's policy.
|
||
4. For apps with native OIDC (Grafana, Gitea, OpenWebUI): add an **OAuth2/OpenID
|
||
Connect** provider, create the app with the correct redirect URI, and paste
|
||
the `client_id` / `client_secret` into the app's SSO settings.
|
||
|
||
## Operations
|
||
|
||
- **Health:** `https://auth.franksdank.cloud/-/health/` (the server healthcheck
|
||
probes this).
|
||
- **Logs:** `podman logs authentik-server` / `podman logs authentik-worker`.
|
||
- **Backups:** `pg_dump` the `authentik-postgres` database (the single source of
|
||
truth). The `/home/dank/authentik/` dir holds media + static files.
|
||
- **Upgrades:** bump the image tag in `dankclaw/compose.yaml`, run
|
||
`./scripts/deploy.sh dankclaw`. Authentik migrates the schema on startup.
|
||
|
||
## Failure modes
|
||
|
||
| Symptom | Cause | Fix |
|
||
|---|---|---|
|
||
| Every vhost 502s at the outpost | `authentik-server` down | `podman logs authentik-server`; check postgres health |
|
||
| Login loops (302 → login → 302) | `trusted_proxies` / `X-Forwarded-*` mismatch | Confirm Caddy sets `X-Forwarded-For`; the outpost trusts `private_ranges` |
|
||
| "Invalid signature" on the outpost | `AUTHENTIK_SECRET_KEY` differs between server and worker | Both services read the same `.env` var; re-deploy |
|
||
| Postgres "password authentication failed" | `AUTHENTIK_PG_PASS` changed after first init | Reset via `pg_ctl` or re-init the data dir |
|
||
|
||
## Cross-references
|
||
|
||
- [Caddy](Caddy.md) — the reverse proxy that carries the forward_auth directives
|
||
- [Pi-hole](Pi-hole.md) — the LAN DNS that resolves `auth.franksdank.cloud`
|
||
- [Network Architecture](../Architecture/Network%20Architecture.md) — the flat naming scheme
|
||
- Research: `.docaudit/research/auth.md` — the full provider comparison
|