diff --git a/.claude/reference/authentik-state.md b/.claude/reference/authentik-state.md index 75efc08a..28ac87ba 100644 --- a/.claude/reference/authentik-state.md +++ b/.claude/reference/authentik-state.md @@ -18,12 +18,16 @@ | wrongmove | OAuth2/OIDC | implicit consent | > **Kubernetes Dashboard** (TF-managed in `stacks/k8s-dashboard/authentik.tf`): -> confidential client `k8s-dashboard` consumed by oauth2-proxy in front of the -> web dashboard. Has a custom scope mapping `k8s-dashboard audience` (scope -> `k8s-dashboard-audience`) emitting `aud=[kubernetes,k8s-dashboard]`, plus a -> group-access policy restricting login to `kubernetes-admins` / -> `kubernetes-power-users` / `kubernetes-namespace-owners`. The apiserver trusts -> this app's issuer via the `rbac` stack structured `AuthenticationConfiguration`. +> confidential client `k8s-dashboard`, built for seamless dashboard SSO via +> oauth2-proxy. **Currently IDLE** — the apiserver rejects all OIDC tokens (see +> `docs/plans/2026-06-04-k8s-dashboard-sso-design.md` §12), so the dashboard runs +> on forward-auth + token-paste instead and oauth2-proxy is unwired. Kept for a +> future SSO retry once apiserver OIDC is fixed. +> +> **admin-services-restriction** policy (TF-managed in +> `stacks/authentik/admin-services-restriction.tf`, adopted 2026-06-04): gates the +> 15 admin-only hostnames to `Home Server Admins`, with a carve-out admitting the +> `kubernetes-*` RBAC groups to `k8s.viktorbarzin.me` (dashboard login page). ## Groups (9) | Group | Parent | Superuser | Purpose | diff --git a/.claude/reference/service-catalog.md b/.claude/reference/service-catalog.md index 90ee7367..7c2651b5 100644 --- a/.claude/reference/service-catalog.md +++ b/.claude/reference/service-catalog.md @@ -30,7 +30,7 @@ ## Admin | Service | Description | Stack | |---------|-------------|-------| -| k8s-dashboard | Kubernetes dashboard at `k8s.viktorbarzin.me`. Authentik SSO via **oauth2-proxy** (`auth=none`; oauth2-proxy injects the user's OIDC id_token from the `k8s-dashboard` confidential client as Bearer → per-user RBAC at the apiserver). Multi-issuer apiserver auth in `stacks/rbac`. | k8s-dashboard | +| k8s-dashboard | Kubernetes dashboard at `k8s.viktorbarzin.me`. **Forward-auth + token-paste** (interim — apiserver OIDC blocked, see `docs/plans/2026-06-04-k8s-dashboard-sso-design.md` §12). Forward-auth admits `kubernetes-*` groups for this host; each namespace-owner pastes a per-namespace SA token (`dashboard-` in their ns, `stacks/rbac/.../dashboard-sa.tf` — ns admin + cluster read-only). Admins use the `kubernetes-dashboard` cluster-admin SA token. oauth2-proxy + `k8s-dashboard` OIDC app built but idle. | k8s-dashboard | | reverse-proxy | Generic reverse proxy | reverse-proxy | | t3code | Multi-user coding-agent GUI at t3.viktorbarzin.me. `auth=required` (Authentik) → DevVM `t3-dispatch` service (`10.0.10.10:3780`, unprivileged user) maps `X-authentik-username` → that user's own `t3-serve@` instance (file perms enforced by uid; wizard→:3773, emo→:3774; unmapped→403) and **auto-injects the t3 session on first visit** (mints via the root `t3-mint` wrapper, scoped sudoers → `/api/auth/bootstrap` `t3_session` cookie). Source of truth `/etc/ttyd-user-map`; `t3-provision-users` reconcile (systemd timer) turns map entries into `t3-serve@` instances + `dispatch.json`. **Add a user:** one line in `/etc/ttyd-user-map` (must already be an OS account + Authentik identity) → reconcile. DevVM artifacts versioned in `infra/scripts/` (`t3-serve@.service`, `t3-provision-users`, `t3-dispatch/`, `t3-mint`, `sudoers-t3-autopair`, `t3-autoupdate.*`); TF (`stacks/t3code`) owns only the ingress + Endpoints→:3780. **t3 binary tracks `nightly`** via `t3-autoupdate` (daily systemd timer; health-check + auto-rollback on a bad build; restarts only idle instances) — so new models (e.g. Opus 4.8) land as t3 ships them. Native app/app.t3.codes unsupported (cross-origin) — deferred until published. Design: `docs/plans/2026-06-01-t3-auto-provision-*`. | t3code | diff --git a/.claude/skills/add-user/SKILL.md b/.claude/skills/add-user/SKILL.md index 2e817bd5..877610aa 100644 --- a/.claude/skills/add-user/SKILL.md +++ b/.claude/skills/add-user/SKILL.md @@ -177,6 +177,17 @@ Tell the user to share these onboarding instructions with the new user: - K8s Portal: `https://k8s-portal.viktorbarzin.me/onboarding?role=namespace-owner` - README: `https://github.com/ViktorBarzin/infra#new-user-onboarding` +**Web dashboard access** (the `rbac` stack auto-creates a `dashboard-` SA + +token for every namespace-owner — `stacks/rbac/modules/rbac/dashboard-sa.tf`): +the new user logs into `https://k8s.viktorbarzin.me` (forward-auth admits the +`kubernetes-*` groups) and pastes the **Token**: +```bash +kubectl -n NAMESPACE get secret dashboard-USERNAME-token -o jsonpath='{.data.token}' | base64 -d +``` +Gives them `admin` on their namespace(s) + cluster read-only. (Token-paste is the +interim model while seamless OIDC SSO is blocked — see +`docs/plans/2026-06-04-k8s-dashboard-sso-design.md` §12.) + The user can decrypt their stack's state with: ```bash vault login -method=oidc # authenticates via Authentik SSO diff --git a/docs/architecture/authentication.md b/docs/architecture/authentication.md index 85a9cf78..61808c53 100644 --- a/docs/architecture/authentication.md +++ b/docs/architecture/authentication.md @@ -100,54 +100,48 @@ Authentik provides OIDC for 10 applications: | Headscale | OIDC | Tailscale control plane auth | | Immich | OIDC | Photo management SSO | | Kubernetes | OIDC (public client) | K8s API authentication (kubectl / kubelogin CLI) | -| Kubernetes Dashboard | OIDC (confidential, via oauth2-proxy) | Web dashboard SSO with per-user RBAC | +| Kubernetes Dashboard | OIDC (confidential) | Built for dashboard SSO — currently **idle** (apiserver OIDC blocked; dashboard uses forward-auth + token-paste) | | Linkwarden | OIDC | Bookmark manager SSO | | Matrix | OIDC | Matrix homeserver SSO | | Wrongmove | OIDC | Real estate app SSO | -### Kubernetes RBAC via OIDC +### Kubernetes API authentication (OIDC) — CURRENTLY NON-FUNCTIONAL -The kube-apiserver uses a **structured `AuthenticationConfiguration`** -(`apiserver.config.k8s.io/v1`, file `/etc/kubernetes/pki/auth-config.yaml`, -flag `--authentication-config`) that trusts **two** Authentik issuers — managed -by `stacks/rbac/modules/rbac/apiserver-oidc.tf`: +> ⚠️ **apiserver OIDC does not work in this cluster** (as of 2026-06-04). The +> kube-apiserver rejects *every* valid Authentik OIDC token — with both the +> legacy `--oidc-*` flags AND a structured `AuthenticationConfiguration`, for +> both the `kubernetes` and `k8s-dashboard` issuers — despite verified +> signature, issuer, audience, `email_verified=true`, synced clock, and a +> reachable + publicly-trusted JWKS. Root cause is still open; see +> `docs/plans/2026-06-04-k8s-dashboard-sso-design.md` §12. A kubeadm v1.34 +> upgrade had earlier silently wiped the apiserver `--oidc-*` flags, so OIDC +> CLI/dashboard login has effectively been off. **Do not assume `kubectl` +> OIDC (kubelogin) works until this is resolved.** -| Issuer (Authentik app) | Audience | Used by | -|---|---|---| -| `…/application/o/kubernetes/` | `kubernetes` | `kubectl` / kubelogin CLI (public client) | -| `…/application/o/k8s-dashboard/` | `k8s-dashboard` | oauth2-proxy in front of the web Dashboard (confidential client) | +The intended model (binds by `email`, see `stacks/rbac/modules/rbac/main.tf`): +`admin` → `cluster-admin`; `power-user` → custom read-mostly ClusterRole; +`namespace-owner` → `admin` RoleBinding in their namespace(s) + cluster read-only. -Both map `username <- email` and `groups <- groups` with **empty prefixes** (so -tokens map to RBAC subjects `kind: User, name: ` and verbatim group -names). This replaced the legacy single `--oidc-*` flags (one issuer only), -which a kubeadm upgrade had silently wiped. +### Kubernetes Dashboard access (token-paste, interim) -The flow: +Because OIDC SSO is blocked, the web dashboard at `k8s.viktorbarzin.me` uses: -1. User authenticates to Authentik (via the `kubectl` plugin, or via oauth2-proxy - for the web Dashboard). -2. Receives an OIDC id_token with `email` + `groups` claims. -3. K8s API validates the token against the matching issuer's Authentik JWKS. -4. RBAC binds the user (by email) to roles — see `stacks/rbac/modules/rbac/main.tf`: - - `admin` role users → `cluster-admin` - - `power-user` role → custom cluster ClusterRole (read-mostly, limited write) - - `namespace-owner` role → `admin` RoleBinding in their namespace(s) + cluster read-only +1. **Authentik forward-auth** gates *who reaches the login page* + (`admin-services-restriction` policy — admits `Home Server Admins` plus the + `kubernetes-admins` / `kubernetes-power-users` / `kubernetes-namespace-owners` + groups for this host; see `stacks/authentik/admin-services-restriction.tf`). +2. **Token paste**: each namespace-owner has a ServiceAccount + (`dashboard-` in their namespace, `stacks/rbac/modules/rbac/dashboard-sa.tf`) + scoped to `admin` on their namespace(s) + cluster read-only, with a long-lived + token they paste into the Dashboard's "Token" login. The pasted token — not + forward-auth — is the per-namespace security boundary. + Retrieve: `kubectl -n get secret dashboard--token -o jsonpath='{.data.token}' | base64 -d`. + Admins use the cluster-admin `kubernetes-dashboard` SA token + (`kubectl create token kubernetes-dashboard -n kubernetes-dashboard`). -> **Web Dashboard SSO:** the `k8s.viktorbarzin.me` ingress points at -> **oauth2-proxy** (`stacks/k8s-dashboard/oauth2_proxy.tf`, `auth = "none"` — -> oauth2-proxy is the gate), which runs the Authentik OIDC code-flow against the -> `k8s-dashboard` confidential client and injects the user's id_token as -> `Authorization: Bearer` upstream to the Dashboard's Kong proxy. The Dashboard -> then talks to the apiserver **as the user**, so per-user RBAC applies (a -> namespace-owner manages only their namespace; admins see everything). A group -> policy on the Authentik app restricts login to the `kubernetes-*` groups. -> Replaced the prior forward-auth + static cluster-admin ServiceAccount (which -> made every authenticated user cluster-admin). Design: -> `docs/plans/2026-06-04-k8s-dashboard-sso-design.md`. - -> **Upgrade caveat:** `--authentication-config` lives in the kube-apiserver -> static-pod manifest, which `kubeadm upgrade` regenerates — **re-apply the -> `rbac` stack after any control-plane upgrade** to restore apiserver OIDC. +The oauth2-proxy + `k8s-dashboard` Authentik OIDC app (built for the +seamless-SSO design) remain deployed but **idle/unwired** pending the +apiserver-OIDC fix; the dashboard ingress is on forward-auth. ### Authentik Groups diff --git a/docs/architecture/multi-tenancy.md b/docs/architecture/multi-tenancy.md index d4fb8015..0db9d6d0 100644 --- a/docs/architecture/multi-tenancy.md +++ b/docs/architecture/multi-tenancy.md @@ -171,17 +171,23 @@ Each user receives: ``` 6. User can now run `kubectl` commands -### Web Dashboard (no CLI needed) +### Web Dashboard (token-paste) -Namespace-owners can also manage their namespace from the **Kubernetes -Dashboard** at `https://k8s.viktorbarzin.me` using their Authentik account — no -kubectl, no token paste. oauth2-proxy runs the SSO flow and injects the user's -OIDC id_token, so the dashboard talks to the apiserver **as the user**: a -namespace-owner gets full control of their namespace(s) and read-only -visibility elsewhere; admins see everything. Login is restricted (Authentik -group policy) to the `kubernetes-*` groups. See -`docs/architecture/authentication.md` → "Kubernetes RBAC via OIDC" and -`docs/plans/2026-06-04-k8s-dashboard-sso-design.md`. +Namespace-owners can manage their namespace from the **Kubernetes Dashboard** at +`https://k8s.viktorbarzin.me`: + +1. Log in via Authentik (forward-auth admits the `kubernetes-*` groups for this + host — `stacks/authentik/admin-services-restriction.tf`). +2. On the Dashboard login page, choose **Token** and paste the personal token: + `kubectl -n get secret dashboard--token -o jsonpath='{.data.token}' | base64 -d` + (the `dashboard-` SA is created per namespace-owner in + `stacks/rbac/modules/rbac/dashboard-sa.tf` — `admin` on their namespace(s) + + cluster read-only). + +> **Why token-paste, not seamless SSO:** the intended oauth2-proxy SSO is built +> but blocked — the apiserver currently rejects all Authentik OIDC tokens. See +> `docs/architecture/authentication.md` → "Kubernetes API authentication" and +> `docs/plans/2026-06-04-k8s-dashboard-sso-design.md` §12. ### RBAC Groups