Files
home-network-k3s/docs/15-identity-and-access.md
T
jakman85andClaude Opus 4.8 fb153f0bb5 Add Keycloak IdP / SSO (ADR-0014, docs/15, R-IDP-*)
New identity subsystem, and resolves the C2 auth open question:
- docs/15-identity-and-access.md + ADR-0014: Keycloak as central IdP; native
  OIDC where supported (Gitea, Nextcloud, Grafana, Kasm, Semaphore, Headlamp)
  and oauth2-proxy forward-auth for bare UIs (PiHole, *arr, qBittorrent,
  Homepage, Prometheus, Alertmanager, Hubble). Group-based authz + MFA;
  declarative realm/clients (GitOps); PostgreSQL on Longhorn, DR-critical +
  break-glass; hardened public exposure. Plex/Seerr use Plex auth; Minecraft N/A.
- Requirements R-IDP-01..06; ServiceDefinition gains an  block.
- Onboarded early (with Gitea) so services adopt SSO as they come up.
- Threaded through: docs 00,01,07,10,12,13; roadmap phases+workstreams;
  task board (T-6.1c/T-6.1d); glossary; ADR index; README.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-08-15 23:33:49 -05:00

105 lines
5.8 KiB
Markdown

# 15 — Identity & Access (SSO)
Serves: **R-IDP-01..06**, supports **R-C2-02**, **R-SEC-02/03**. Related:
[ADR-0014 (Keycloak)](../decisions/adr-0014-idp-keycloak.md).
## Purpose
One place to log in. A central **Identity Provider (IdP)** gives the platform single sign-on (SSO),
one user directory, group-based authorization, and MFA — so every service is protected by the same
identity, and no management UI sits unauthenticated on the network.
## Choice: Keycloak
**Keycloak** is the platform IdP ([ADR-0014](../decisions/adr-0014-idp-keycloak.md)): a mature,
self-hostable OpenID Connect (OIDC) + SAML provider with users, groups, roles, clients, MFA
(TOTP/WebAuthn), and brute-force protection.
## Two integration patterns (behind one identity)
Every service authenticates against Keycloak, via whichever pattern it supports:
1. **Native OIDC** — the app is configured as a Keycloak OIDC **client** and does its own login
redirect. Preferred where available. Initial set: **Gitea, Nextcloud, Grafana, Kasm, Semaphore,
Headlamp**.
2. **Forward-auth proxy** — for apps with **no** usable OIDC, an identity-aware proxy
(**oauth2-proxy**) sits in front at the Gateway, authenticates the user against Keycloak, and only
then forwards the request. Used for: **PiHole admin, the *arr web UIs (Sonarr/Radarr/Jackett),
qBittorrent UI, Homepage, Prometheus, Alertmanager, Hubble UI**, and any other bare UI.
> This resolves the C2 auth open question ([12](12-command-center-ui.md)): it is **both** — native
> OIDC where supported, forward-auth everywhere else, all backed by Keycloak.
### Exceptions (documented, not oversights)
- **Plex** and **Seerr** use **Plex's own account system** (Plex OAuth) — they do not federate to
Keycloak.
- **Minecraft** authenticates players via Mojang/Microsoft — N/A.
- **UniFi** keeps its native account model (limited SSO support); protect its UI via forward-auth if
desired.
## Authorization model (R-IDP-06)
- **Groups drive access.** Keycloak groups (e.g. `platform-admins`, `media-users`, `viewers`) map to
per-service access and roles. A service's ServiceDefinition declares which groups may reach it
([13](13-service-catalog.md), `auth.groups`).
- **Least privilege**: the C2 console, Headlamp, Alertmanager, and other sensitive UIs require an
authenticated user **in an authorized group** — not merely "logged in."
- **MFA**: TOTP/WebAuthn available for all users and **required for privileged groups**
(`platform-admins`).
## Identity config as code (R-IDP-03)
Realms, clients, groups, and roles are **declarative and GitOps-managed** — not clicked into the
admin console and forgotten:
- The Keycloak instance is deployed via the **Keycloak Operator** (or Helm), and **realm/client/group
configuration is reconciled declaratively** (e.g. `keycloak-config-cli` or realm-import CRs). Exact
tool is an implementation detail; the requirement is that identity config lives in git and
reconciles like everything else.
- **OIDC client secrets and the Keycloak admin credential are SOPS-encrypted** ([07](07-security-and-secrets.md)).
## State, exposure & resilience
- **Datastore**: Keycloak runs on **PostgreSQL**; the DB lives on **3-replica Longhorn** and is
**DR-critical → backed up off-cluster** ([04](04-storage-and-state.md)). Keycloak is stateless
itself and rebuilds from the DB + git-managed realm config.
- **Exposure**: `keycloak.home.keithjackson.dev` is **`public`** so it can serve login/redirect flows
for the internet-exposed services (Nextcloud, Gitea) — but it is **security-sensitive** and
hardened: brute-force detection on, strong admin auth + MFA, trusted TLS ([08](08-tls-and-certificates.md)),
minimal exposed endpoints. If the operator prefers internal-only, public services fall back to
their native auth.
- **HA**: a single replica with DB on Longhorn + backups is acceptable for the home scale; note
clustered Keycloak as a future hardening. **Availability caveat:** SSO becomes a dependency for
logging into federated services — document a break-glass path (e.g. a local admin on Gitea) so an
IdP outage doesn't lock the operator out of recovery tools.
## Interaction with default-deny & the gateway
- Keycloak and oauth2-proxy are ordinary workloads under **default-deny** ([03](03-networking-isolation-and-egress.md));
the specific flows they need (app → Keycloak OIDC, proxy → Keycloak, Keycloak → its DB) are
explicit allow rules.
- Forward-auth is enforced at the **Gateway** ([09](09-dns-and-ingress.md)) so protection happens
before traffic reaches the app.
## Bootstrapping & order
- Keycloak is **foundational** and onboarded **early** (with PiHole/Gitea, [13](13-service-catalog.md)) so
later services can integrate SSO as they come up.
- No hard circular dependency at bring-up: Gitea can start with a local admin and add OIDC once
Keycloak is live; the C2 and sensitive UIs gain SSO as soon as the proxy + realm exist.
## Acceptance gates
- A user logs in once at Keycloak and reaches all OIDC-integrated services without re-entering
credentials (SSO proven).
- A forward-auth-protected UI (e.g. PiHole admin) is **unreachable without a valid Keycloak session**
and reachable with one, restricted to the allowed group.
- Realm/client/group config applied purely from git reproduces a working SSO setup (no console
clicking); destroying and rebuilding Keycloak from DB + git restores identity.
- Privileged access (C2/Headlamp) requires MFA and group membership; a non-member is denied.
- Client secrets and admin credentials exist only SOPS-encrypted.
## Open questions
- Final declarative-config tool (`keycloak-config-cli` vs operator realm-import CRs) — implementation
detail for WS-SEC.
- Confirm `public` exposure for Keycloak (recommended) vs internal-only with native-auth fallback for
public services.