diff --git a/.claude/reference/service-catalog.md b/.claude/reference/service-catalog.md index 7c2651b5..72d9dfd0 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`. **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 | +| k8s-dashboard | Kubernetes dashboard at `k8s.viktorbarzin.me`. **Forward-auth + auto-injected SA token** (apiserver OIDC blocked, see design §12). nginx token-injector (`dashboard_injector.tf`) maps `X-authentik-username` → the user's `dashboard-` SA token (ns admin + cluster read-only, `rbac/.../dashboard-sa.tf`; admins → cluster-admin SA) and sets `Authorization: Bearer` → no token-paste, dashboard auto-authenticates per user. Forward-auth admits `kubernetes-*` groups for this host (`stacks/authentik/admin-services-restriction.tf`). 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 877610aa..28d1e5b0 100644 --- a/.claude/skills/add-user/SKILL.md +++ b/.claude/skills/add-user/SKILL.md @@ -177,16 +177,19 @@ 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.) +**Web dashboard access** (auto-login, no token paste): the `rbac` stack +auto-creates a `dashboard-` SA + token for every namespace-owner +(`dashboard-sa.tf`), and the **k8s-dashboard** stack's token-injector maps the +user's Authentik identity → that token (`dashboard_injector.tf`, auto-derived +from `k8s_users`). The new user just logs into `https://k8s.viktorbarzin.me` and +lands in the dashboard scoped to their namespace (`admin` + cluster read-only). + +> **Apply order for a new namespace-owner:** after the vault/rbac/woodpecker +> applies above, ALSO `cd stacks/k8s-dashboard && ../../scripts/tg apply` so the +> injector map picks up the new user. (Manual token fallback: +> `kubectl -n NAMESPACE get secret dashboard-USERNAME-token -o jsonpath='{.data.token}' | base64 -d`.) +> Seamless OIDC SSO is built but blocked — see +> `docs/plans/2026-06-04-k8s-dashboard-sso-design.md` §12. The user can decrypt their stack's state with: ```bash diff --git a/docs/architecture/authentication.md b/docs/architecture/authentication.md index 61808c53..7f6fe7b6 100644 --- a/docs/architecture/authentication.md +++ b/docs/architecture/authentication.md @@ -122,26 +122,32 @@ 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. -### Kubernetes Dashboard access (token-paste, interim) +### Kubernetes Dashboard access (auto-injected SA token) -Because OIDC SSO is blocked, the web dashboard at `k8s.viktorbarzin.me` uses: +Because OIDC SSO is blocked, the web dashboard at `k8s.viktorbarzin.me` uses a +**token-injector** instead — users never see the dashboard's token prompt: -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`). +1. **Authentik forward-auth** (`auth=required`) gates access AND injects + `X-authentik-username` (the user's email). The `admin-services-restriction` + policy admits `Home Server Admins` plus `kubernetes-admins` / + `kubernetes-power-users` / `kubernetes-namespace-owners` for this host + (`stacks/authentik/admin-services-restriction.tf`). +2. **Token-injector** (`stacks/k8s-dashboard/dashboard_injector.tf`): an nginx + that maps `X-authentik-username` → that user's ServiceAccount token and sets + `Authorization: Bearer` before proxying to kong-proxy, so the dashboard + auto-authenticates. Namespace-owners → `dashboard-` SA (admin on their + namespace + cluster read-only, `stacks/rbac/modules/rbac/dashboard-sa.tf`), + auto-derived from `k8s_users`. Admins → the cluster-admin `kubernetes-dashboard` + SA token (admin identities listed explicitly in `dashboard_injector.tf`, since + their Authentik login email ≠ their `k8s_users` email). + The injected token is the per-namespace security boundary; the map lives in a + **Secret** (namespace-owners' cluster-read covers configmaps, not secrets). + +> Manual token (fallback / break-glass): `kubectl -n get secret dashboard--token -o jsonpath='{.data.token}' | base64 -d`, or `kubectl create token kubernetes-dashboard -n kubernetes-dashboard` for admin. 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. +apiserver-OIDC fix. ### Authentik Groups diff --git a/docs/architecture/multi-tenancy.md b/docs/architecture/multi-tenancy.md index 0db9d6d0..3d84460d 100644 --- a/docs/architecture/multi-tenancy.md +++ b/docs/architecture/multi-tenancy.md @@ -171,22 +171,20 @@ Each user receives: ``` 6. User can now run `kubectl` commands -### Web Dashboard (token-paste) +### Web Dashboard (auto-login, no token paste) -Namespace-owners can manage their namespace from the **Kubernetes Dashboard** at -`https://k8s.viktorbarzin.me`: +Namespace-owners just log into `https://k8s.viktorbarzin.me` with their Authentik +account and land straight in the dashboard scoped to their namespace — **no token +to paste**. A token-injector (`stacks/k8s-dashboard/dashboard_injector.tf`) maps +their Authentik identity (`X-authentik-username`) to their `dashboard-` SA +token (`admin` on their namespace + cluster read-only) and injects it as +`Authorization: Bearer`. Forward-auth admits the `kubernetes-*` groups for this +host (`stacks/authentik/admin-services-restriction.tf`). -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 +> **Why not seamless OIDC SSO:** the intended oauth2-proxy OIDC path is built but +> blocked — the apiserver rejects all Authentik OIDC tokens. The injector uses SA +> tokens (which the apiserver accepts) keyed off the forward-auth identity. See +> `docs/architecture/authentication.md` and > `docs/plans/2026-06-04-k8s-dashboard-sso-design.md` §12. ### RBAC Groups