The operator ruled the implementation repo's main (50c8719) the latest and accepted state. Sync the spec to it. Accepted changes are reflected: one bare-metal environment, TrueNAS bulk storage, R2 off-cluster backup, CloudNativePG, single-node VPN gateway with Cilium fail-closed, inbound WireGuard, per-app auth sidecars, Renovate, the CI gate as approver, and the pm-agent. New ADR-0017..0025; ADR-0016 corrected and accepted; R-* IDs unchanged, eight amended. Where the implementation has simply not met a requirement, the requirement stands and the shortfall is recorded in docs/16-known-gaps.md. Co-Authored-By: Claude Opus 5.5 <[email protected]>
46 lines
2.9 KiB
Markdown
46 lines
2.9 KiB
Markdown
# ADR-0014 — Identity Provider: Keycloak (SSO via OIDC + forward-auth)
|
|
- Status: Accepted — Keycloak, native OIDC, groups and MFA stand (forward-auth enforcement point and app list superseded by [ADR-0022](adr-0022-forward-auth-per-app-oauth2-proxy-sidecars.md))
|
|
- Date: 2026-08-15
|
|
- Serves: R-IDP-01, R-IDP-02, R-IDP-03, R-IDP-04, R-IDP-05, R-IDP-06
|
|
- Supersedes: —
|
|
|
|
## Context
|
|
The platform needs one identity and single sign-on: a central user directory, group-based
|
|
authorization, MFA, and a way to protect both OIDC-capable apps and bare management UIs — without
|
|
per-app passwords scattered across services. This also resolves the previously-open C2 auth question
|
|
([12](../docs/12-command-center-ui.md)).
|
|
|
|
## Decision
|
|
Use **Keycloak** as the platform IdP, integrated two ways behind one identity:
|
|
- **Native OIDC** for apps that support it (Gitea, Nextcloud, Grafana, Kasm, Semaphore, Headlamp).
|
|
- **Forward-auth proxy (oauth2-proxy → Keycloak)** at the Gateway for apps without usable OIDC
|
|
(PiHole, *arr UIs, qBittorrent, Homepage, Prometheus, Alertmanager, Hubble UI).
|
|
|
|
Supporting decisions:
|
|
- **Config as code**: realms/clients/groups are declarative and GitOps-reconciled (Keycloak Operator/
|
|
Helm for the instance; `keycloak-config-cli` or realm-import for config). Secrets SOPS-encrypted.
|
|
- **Authorization by groups**; **MFA** required for privileged groups.
|
|
- **PostgreSQL on Longhorn**, DR-critical, backed up off-cluster; Keycloak rebuilds from DB + git.
|
|
- **`public` exposure** (hardened) so login flows work for internet-exposed services.
|
|
- **Exceptions**: Plex/Seerr use Plex auth; Minecraft uses Mojang/Microsoft; UniFi keeps native auth.
|
|
|
|
## Alternatives considered
|
|
- **Authelia / Authentik** — lighter, and Authelia pairs cleanly with forward-auth; but Keycloak is
|
|
the operator's explicit choice and offers the broadest OIDC/SAML, realm, and federation feature set
|
|
for heterogeneous apps. (Chosen.)
|
|
- **Per-app local auth only** — rejected: no SSO, password sprawl, weak central policy/MFA (fails
|
|
R-IDP-01/06).
|
|
- **Cloud IdP (e.g. hosted OIDC)** — rejected: this is a self-hosted, offline-capable home platform;
|
|
identity should not depend on an external SaaS.
|
|
|
|
## Consequences
|
|
- One login, one directory, central MFA and group policy; management UIs are never unauthenticated on
|
|
the network (R-IDP-02).
|
|
- **Keycloak becomes a critical dependency**: an IdP outage impairs SSO logins → DR-critical storage
|
|
+ a documented **break-glass** local-admin path (e.g. on Gitea) so recovery tools stay reachable.
|
|
- New platform components to run and secure: Keycloak, PostgreSQL, oauth2-proxy — all under
|
|
default-deny with explicit allows and trusted TLS.
|
|
- Onboarded **early** (with PiHole/Gitea) so later services integrate SSO as they come up.
|
|
- Adds an `auth` block to the ServiceDefinition ([13](../docs/13-service-catalog.md)) so SSO mode +
|
|
allowed groups are declarative per service.
|