Files
home-network-k3s/docs/15-identity-and-access.md
T
jakman85andClaude Opus 5.5 18f7ba8d0f docs: apply the operator's rulings on Kasm, revocation and fault injection
Retire Kasm from the catalog. Drop ACME revocation from R-TLS-02. Make L3
fault injection operator-run and exclude it from the merge gate (R-TEST-03,
ADR-0026). Update the gap register to match; Homepage authentication is now
ruled must-enforce and stays a gap until the implementation lands.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-08 23:06:26 -06:00

7.0 KiB

15 — Identity & Access (SSO)

Serves: R-IDP-01..06, supports R-C2-02, R-SEC-02/03. Related: ADR-0014 (Keycloak).

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): 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, Semaphore, Headlamp.
  2. Forward-auth gate — for apps with no usable OIDC, a per-app identity-aware proxy (oauth2-proxy) runs as a sidecar in the app's pod, authenticates the user against Keycloak, and only then forwards the request (ADR-0022). Used for: *PiHole admin, the arr web UIs (Sonarr/Radarr/Jackett), qBittorrent UI, the Crafty web UI, and any other bare UI; Homepage requires the same (see known gaps). Prometheus, Alertmanager and Hubble UI are not exposed; any future exposure MUST be gated.

This resolves the C2 auth open question (12): it is both — native OIDC where supported, forward-auth everywhere else, all backed by Keycloak.

Each gate is an independent instance with its own IdP client and credentials, and enforces the service's auth.groups. Session cookies are per-gate: a unique name, scoped to the app's own host, because instances that share a cookie evict one another's sessions. A service MAY declare its API paths so that unauthenticated programmatic calls are refused rather than redirected to a login page.

Exceptions (documented, not oversights)

  • Plex and Seerr use Plex's own account system (Plex OAuth) — they do not federate to Keycloak.
  • Minecraft players authenticate via Mojang/Microsoft — N/A. The Crafty web UI that manages it is behind a forward-auth gate and then presents its own local login (no OIDC); the second login is intended.
  • UniFi keeps its native account model and login on its own L4 VIP (limited SSO support).
  • Runbooks served read-only under /runbooks carry no login.
  • Keycloak itself is native: it is the root of trust.

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, 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). Passkeys (WebAuthn passwordless) are an optional additive login path, bound into the login flow by an explicit switch in git.
  • Kubernetes API: humans authenticate with Keycloak OIDC, and cluster roles are bound to Keycloak groups, so kubectl and the Kubernetes console share this model.

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, and a realm-import CR seeds realm/client/group configuration from git. A realm import is one-shot and fails open, so reconcilers continuously converge what git declares after the first import: client secrets, client URLs, the WebAuthn policy and the login flow.
  • OIDC client secrets and the Keycloak admin credential are SOPS-encrypted (07).

State, exposure & resilience

  • Datastore: Keycloak runs on PostgreSQL via CloudNativePG (ADR-0019); the DB lives on 3-replica Longhorn and is DR-critical → backed up off-cluster (04). 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), minimal exposed endpoints.
  • HA: Keycloak runs multiple replicas against a database with one instance per node and a hot standby to promote, because a single node must not be able to take SSO down. Availability caveat: SSO is a dependency for logging into federated services and the Kubernetes API, so a break-glass path exists: local admin logins on Gitea, Nextcloud and Grafana, the Keycloak bootstrap admin, and a certificate kubeconfig for the cluster.

Interaction with default-deny & the gateway

  • Keycloak and oauth2-proxy are ordinary workloads under default-deny (03); the specific flows they need (app → Keycloak OIDC, gate → Keycloak, Keycloak → its DB) are explicit allow rules.
  • Forward-auth is enforced in the data path: the Gateway (09) routes only to the gate's port and network policy closes the app's own port to it, so protection happens before traffic reaches the app and a failed gate fails closed.

Bootstrapping & order

  • Keycloak is foundational and onboarded early (with PiHole/Gitea, 13) 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 their gate + the 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.

Resolved

  • Declarative-config tool — operator realm-import CRs, plus the reconcilers above.
  • Keycloak exposure — public, hardened.