diff --git a/docs/accepted-limitations.md b/docs/accepted-limitations.md index a278c84..0d4ee3f 100644 --- a/docs/accepted-limitations.md +++ b/docs/accepted-limitations.md @@ -75,11 +75,79 @@ usual `[Reason] message` format: ::: -:::warning For admins +### Before you delegate these rights (admins) -Delegating `securityContext` delegates the **whole struct** — -`privileged: true` included. Only grant it on templates whose audience -you would trust with `kubectl` on the target namespace. +:::danger These rights gate the *field*, not its *content* + +`allowedFields` decides **whether** a user may set `volumes`, +`securityContext` or `podSecurityContext`. It does not — and +deliberately will not — inspect **what** they put in it. Read literally, +that means: + +**`volumes`** accepts any Kubernetes volume source. A user who has the +right can attach: + +```yaml +volumes: + - name: anything + secret: + secretName: +``` + +and read that Secret from their desktop. The workspace namespace +normally holds at least the registry pull secret and the per-workspace +SSH key. The same reachability comes through `projected:`, which can +also re-introduce a ServiceAccount token WaaS otherwise disables. On +clusters not enforcing Pod Security Admission `restricted`, `cephfs`, +`rbd` and `iscsi` are reachable too, each with its own `secretRef`. + +**`securityContext`** covers the whole struct, `privileged: true` +included. **`podSecurityContext`** is the pod-level twin — `runAsUser: 0`, +`fsGroup`, `supplementalGroups`, `sysctls`. Only the namespace's Pod +Security Admission level stops either. + +::: + +**Why WaaS does not validate the content.** Pod Security Admission is +not the safety net here: the `restricted` level explicitly **permits** +`secret` and `projected` volumes, because mounting a Secret from your +own namespace is ordinary Kubernetes. Re-implementing that judgement +inside WaaS would mean running a second policy engine over a volume +schema that grows with every Kubernetes release — and it would put the +decision in the wrong place. **Constraining how a delegated pod-spec +field may be used is cluster administration**, and Kubernetes already +ships the tools for it. + +**What to do instead.** Treat all three rights as equivalent to handing +out namespace-level access: + +1. **Delegate them only to people you would trust with `kubectl` on the + target namespace.** That is the honest mental model — everything + above follows from it. +2. **Prefer the alternatives when they fit.** A shared filesystem is + better served by an admin-provisioned PersistentVolumeClaim, or by a + volume declared **in the template**, than by a user-authored inline + volume. Both keep you in control of *which* storage is reachable. + Same for secrets: inject them from the template, never through a + user override. +3. **If you must delegate with partial trust, pair it with an admission + policy.** A `ValidatingAdmissionPolicy` (GA in Kubernetes 1.30, beta + since 1.28, no extra component), Kyverno or Gatekeeper, scoped to your + workspace namespaces, is where you express rules like "no `secret` + volumes in tenant-created pods". + +:::info The default policy does not grant these + +The bootstrap `WorkspacePolicy` shipped by the Helm chart grants +`env`, `resources` and `schedule` — **not** `volumes`, and never the +security contexts. The reference GitOps policy set does the same. +Granting them is an explicit, auditable change on your side. + +**Chart 0.2.x and earlier did grant `volumes`** in that bootstrap +policy; 0.3.0 drops it. Upgrading does not rewrite a policy you already +have, so check `defaultPolicy.overrides.allowedFields` on an existing +install. If you relied on it, add it back deliberately — after reading +the warning above. ::: diff --git a/docs/admin/bootstrap-governance.md b/docs/admin/bootstrap-governance.md index 497a4a2..66529d7 100644 --- a/docs/admin/bootstrap-governance.md +++ b/docs/admin/bootstrap-governance.md @@ -37,7 +37,7 @@ may: | Idle suspend | paused after **2 h** without a session (compute freed, home kept) | | Max lifetime | deleted after **336 h / 14 days** — **home volume included** (TTL contract) | | Clipboard | both directions allowed | -| Template overrides | `env`, `resources`, `schedule`, `volumes` — still intersected with each template's own allow-list | +| Template overrides | `env`, `resources`, `schedule` — still intersected with each template's own allow-list | | Remote workspaces | **off** | Two things worth a second look before going to production: @@ -45,10 +45,13 @@ Two things worth a second look before going to production: - **`maxLifetime: 336h` destroys data**: a policy TTL deletes the home volume along with the workspace — that is the documented contract of a TTL. If your users keep long-lived desktops, raise or remove it. -- **`volumes` is in the default override list**: on any template that - *also* delegates `volumes`, a user can mount arbitrary volume - sources (including `hostPath`). The template list is the effective - gate — just be aware the policy side is open by default. +- **`volumes` is no longer in the default override list**: chart 0.2.x + and earlier granted it, so an upgraded install may still carry it in + `defaultPolicy.overrides.allowedFields`. Check yours. Granting + `volumes` — or either security context — on a template *and* a policy + lets a user mount arbitrary volume sources, including any Secret in + the workspace namespace; see [what delegating these rights + grants](../accepted-limitations#before-you-delegate-these-rights-admins). ## The admin policy — off by default, and why you probably want it diff --git a/docs/concepts/governance.md b/docs/concepts/governance.md index 0de4576..c3f083f 100644 --- a/docs/concepts/governance.md +++ b/docs/concepts/governance.md @@ -140,6 +140,24 @@ with the template's own list. Absent block = no policy restriction; empty list = all overrides forbidden. Platform admins bypass both lists; a template's `owner` bypasses the template list only. +:::warning `volumes` and the security contexts are not like the others + +Most fields on that list are bounded by their own type — `resources` is +capped by your limits, `schedule` is a pair of crons. `volumes`, +`securityContext` and `podSecurityContext` are not: they are +pod-spec-shaped, and the allow-list gates the field without inspecting +its content. Granting `volumes` lets a user mount any Secret present in +the workspace namespace; `securityContext` includes `privileged: true`, +and `podSecurityContext` includes `runAsUser: 0` and `sysctls`. + +Read [what delegating these rights +grants](../accepted-limitations#before-you-delegate-these-rights-admins) +before adding any of them to a policy. The bootstrap default policy +grants none of them — but an install upgraded from chart 0.2.x may +still carry `volumes`. + +::: + ## Everyday admin procedures **Approve an image**: add a `WorkspaceImage` with the exact ref (Git or diff --git a/docs/concepts/templates-and-protocols.md b/docs/concepts/templates-and-protocols.md index 9cb5064..6e5e0fa 100644 --- a/docs/concepts/templates-and-protocols.md +++ b/docs/concepts/templates-and-protocols.md @@ -169,10 +169,12 @@ by the operator when the workload is rendered). Wiring a Secret into a desktop is a **template** decision: the admin who authors the template references it through env `valueFrom` or [`credentialsSecretRef`](#credentials), both resolved against the -workspace's own namespace. The template — authored by an admin, -typically through GitOps — is therefore the only channel that can point -a desktop at a cluster Secret; a workspace creator tunes values, never -references Secrets. +workspace's own namespace. Through `env`, therefore, a workspace creator +tunes values and never references Secrets. + +This holds for `env` only. A delegated `volumes` override reaches +Secrets directly, by mounting them — see [what delegating that right +grants](../accepted-limitations#before-you-delegate-these-rights-admins). ::: :::warning diff --git a/versioned_docs/version-v0.2.0/accepted-limitations.md b/versioned_docs/version-v0.2.0/accepted-limitations.md index a278c84..c30cabd 100644 --- a/versioned_docs/version-v0.2.0/accepted-limitations.md +++ b/versioned_docs/version-v0.2.0/accepted-limitations.md @@ -75,14 +75,67 @@ usual `[Reason] message` format: ::: -:::warning For admins +### Before you delegate these rights (admins) -Delegating `securityContext` delegates the **whole struct** — -`privileged: true` included. Only grant it on templates whose audience -you would trust with `kubectl` on the target namespace. +:::danger These rights gate the *field*, not its *content* + +`allowedFields` decides **whether** a user may set `volumes`, +`securityContext` or `podSecurityContext`. It does not — and +deliberately will not — inspect **what** they put in it. Read literally, +that means: + +**`volumes`** accepts any Kubernetes volume source. A user who has the +right can attach: + +```yaml +volumes: + - name: anything + secret: + secretName: +``` + +and read that Secret from their desktop. The workspace namespace +normally holds at least the registry pull secret and the per-workspace +SSH key. The same reachability comes through `projected:`, which can +also re-introduce a ServiceAccount token WaaS otherwise disables. On +clusters not enforcing Pod Security Admission `restricted`, `cephfs`, +`rbd` and `iscsi` are reachable too, each with its own `secretRef`. + +**`securityContext`** covers the whole struct, `privileged: true` +included. **`podSecurityContext`** is the pod-level twin — `runAsUser: 0`, +`fsGroup`, `supplementalGroups`, `sysctls`. Only the namespace's Pod +Security Admission level stops either. ::: +**Why WaaS does not validate the content.** Pod Security Admission is +not the safety net here: the `restricted` level explicitly **permits** +`secret` and `projected` volumes, because mounting a Secret from your +own namespace is ordinary Kubernetes. Re-implementing that judgement +inside WaaS would mean running a second policy engine over a volume +schema that grows with every Kubernetes release — and it would put the +decision in the wrong place. **Constraining how a delegated pod-spec +field may be used is cluster administration**, and Kubernetes already +ships the tools for it. + +**What to do instead.** Treat all three rights as equivalent to handing +out namespace-level access: + +1. **Delegate them only to people you would trust with `kubectl` on the + target namespace.** That is the honest mental model — everything + above follows from it. +2. **Prefer the alternatives when they fit.** A shared filesystem is + better served by an admin-provisioned PersistentVolumeClaim, or by a + volume declared **in the template**, than by a user-authored inline + volume. Both keep you in control of *which* storage is reachable. + Same for secrets: inject them from the template, never through a + user override. +3. **If you must delegate with partial trust, pair it with an admission + policy.** A `ValidatingAdmissionPolicy` (GA in Kubernetes 1.30, beta + since 1.28, no extra component), Kyverno or Gatekeeper, scoped to your + workspace namespaces, is where you express rules like "no `secret` + volumes in tenant-created pods". + ## Choosing the target namespace at creation A template (or policy) can delegate the `placement` right, and the diff --git a/versioned_docs/version-v0.2.0/concepts/governance.md b/versioned_docs/version-v0.2.0/concepts/governance.md index 0de4576..ce9d0d6 100644 --- a/versioned_docs/version-v0.2.0/concepts/governance.md +++ b/versioned_docs/version-v0.2.0/concepts/governance.md @@ -140,6 +140,22 @@ with the template's own list. Absent block = no policy restriction; empty list = all overrides forbidden. Platform admins bypass both lists; a template's `owner` bypasses the template list only. +:::warning `volumes` and the security contexts are not like the others + +Most fields on that list are bounded by their own type — `resources` is +capped by your limits, `schedule` is a pair of crons. `volumes`, +`securityContext` and `podSecurityContext` are not: they are +pod-spec-shaped, and the allow-list gates the field without inspecting +its content. Granting `volumes` lets a user mount any Secret present in +the workspace namespace; `securityContext` includes `privileged: true`, +and `podSecurityContext` includes `runAsUser: 0` and `sysctls`. + +Read [what delegating these rights +grants](../accepted-limitations#before-you-delegate-these-rights-admins) +before adding any of them to a policy. + +::: + ## Everyday admin procedures **Approve an image**: add a `WorkspaceImage` with the exact ref (Git or diff --git a/versioned_docs/version-v0.2.0/concepts/templates-and-protocols.md b/versioned_docs/version-v0.2.0/concepts/templates-and-protocols.md index 6728955..4620ef2 100644 --- a/versioned_docs/version-v0.2.0/concepts/templates-and-protocols.md +++ b/versioned_docs/version-v0.2.0/concepts/templates-and-protocols.md @@ -160,8 +160,10 @@ applied override is audited (field and env var _names_, never values). :::warning Allow-listing `volumes` lets users mount arbitrary volume sources — -including `hostPath`. Only enable it on templates aimed at trusted -groups. +including `hostPath`, and including any Secret in the workspace +namespace. Only enable it on templates aimed at trusted groups; see +[what delegating that right +grants](../accepted-limitations#before-you-delegate-these-rights-admins). ::: In the portal, users who hold override rights see an **Advanced