Files
serverAppConfigs/docs/Infrastructure/Authentik.md
T
Frank 3cc99d81cc Authentik SSO + homepage naked domain
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.
2026-08-24 23:48:27 -07:00

153 lines
6.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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