From 6434b46e91099ee3af5448f6264953d6c5daf424 Mon Sep 17 00:00:00 2001 From: Lukas Wuttke Date: Wed, 22 Jul 2026 15:10:47 +0200 Subject: [PATCH 1/4] =?UTF-8?q?docs(rfc):=20least-privilege=20install=20(0?= =?UTF-8?q?001)=20=E2=80=94=20for=20discussion?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Draft RFC: the installer assumes blanket root/sudo, which excludes non-admin users (hospital/university/HPC researchers). Audit shows the ONLY privileged surface is the container runtime + two kernel modules; tools + the cluster are already user-space. Proposes a tiered model (zero-root when a runtime exists → rootless → one scoped privileged step) and ties in A2 (sudo/root) + B2 (PATH). Co-Authored-By: Claude Opus 4.8 --- docs/rfcs/0001-least-privilege-install.md | 154 ++++++++++++++++++++++ 1 file changed, 154 insertions(+) create mode 100644 docs/rfcs/0001-least-privilege-install.md diff --git a/docs/rfcs/0001-least-privilege-install.md b/docs/rfcs/0001-least-privilege-install.md new file mode 100644 index 00000000..a07be2ce --- /dev/null +++ b/docs/rfcs/0001-least-privilege-install.md @@ -0,0 +1,154 @@ +# RFC 0001 — Least-privilege install + +**Status:** Draft (for discussion) +**Author:** Lukas (drafted with Claude) +**Reviewers:** @saadqbal +**Repos affected:** `client` (installer), `cli` (CLI install + PATH) +**Related:** backlog A2 (sudo/root handling), A3 (rootless), B2 (post-install PATH) + +## Summary + +The installer today assumes the operator has **root / blanket sudo**. That +assumption silently excludes a large and important slice of our users — +**data scientists and researchers on machines they don't administer** +(hospitals, universities, HPC login nodes). They may have an admin somewhere, +or not, and they can rarely get "give me sudo." + +This RFC argues we should require the **minimum privilege actually needed** to +stand up a secure environment, and design the install as **tiers** that degrade +gracefully — from "zero root" up to "one clearly-scoped privileged step" — never +a blanket sudo demand up front. The goal: a non-admin researcher can install on +their instance with, ideally, **no elevated rights at all**, or at worst a +single nameable request an admin can grant once. + +## Motivation + +Our ICP includes the hospital/university data scientist who has a powerful +machine (or a slice of a shared one) and wants to run tracebloc on their own +data. Two real installs this week hit the wall: + +- A shared cluster login node (`/data01/home/…`), regular user, no `sudo` + installed → the installer aborted at "needs your password" with a misleading + "re-run with a user that has sudo access." +- Even where sudo exists, requiring it is a non-starter in environments where + admin is one central IT team for the whole institution. + +If "install tracebloc" reads as "get root on a managed machine," we lose these +users before they start. + +## Current state — what actually needs root + +Audit of every `sudo` call site in the installer (`scripts/lib/setup-linux.sh`, +`setup-macos.sh`, `install-cli.sh`, `cluster.sh`). **Every one is about the +container runtime or its kernel/daemon prerequisites** — nothing else: + +| Touchpoint | Where | Why root | +|---|---|---| +| Install Docker (apt/dnf/yum/zypper/pacman, get.docker.com, Docker Desktop) | setup-linux/macos | system package / daemon | +| `usermod -aG docker $USER` | setup-linux | add user to the docker group | +| `modprobe br_netfilter/overlay` + `/etc/modules-load.d/tracebloc.conf` | setup-linux | k3s kernel prereqs | +| Docker daemon proxy config `/etc/systemd/system/docker.service.d/…` + `systemctl daemon-reload/restart` | setup-linux | daemon config | +| `systemctl enable/start docker` | setup-linux | start the daemon | +| `rm -rf /Applications/Docker.app` (wrong-arch/uninstall only) | setup-macos | protected paths | + +**What does NOT need root, today, already:** the CLI tools — `kubectl`, `helm`, +`k3d`, and the `tracebloc` CLI itself — are downloaded to `~/.local/bin` +(`install-cli.sh`), no sudo. And the environment itself (k3d = k3s-in-Docker, +plus kubectl/helm operations) runs **inside Docker as the user** — no additional +root once a usable Docker exists. + +**The insight:** we don't need "admin." We need **a usable container runtime** +(plus, on Linux, two kernel modules). That is the *entire* privileged surface. +Everything else is user-space. + +## Proposal — tiered, least-privilege install + +Probe the machine and pick the **lowest tier that works**, instead of demanding +sudo up front: + +**Tier 0 — zero root (target for shared/managed hosts).** +A usable container runtime already exists — Docker with the user in the +`docker` group, rootless Docker, or Podman — and the kernel modules are loaded +(common on well-run shared hosts). Then: download tools to `~/.local/bin`, +create the k3d/k3s cluster in the existing runtime, register, done. **No sudo at +any point.** Many hospital/HPC boxes already provide a container runtime; we +should detect and use it. + +**Tier 1 — rootless runtime, no system Docker.** +No usable system Docker, but a rootless runtime can run as the user (rootless +Docker, Podman, or k3s rootless mode). Setup may need a **one-time, narrow** +privileged touch on some hosts (subuid/subgid ranges, a `sysctl`, or loading a +module) — nameable and grantable, not blanket admin. Investigate which of +rootless-Docker / Podman / k3s-rootless is the most portable target. + +**Tier 2 — install a runtime (the only genuine root need).** +Nothing usable and rootless isn't viable → install Docker + load the kernel +modules. This is the **one** privileged step. It should be: +- **Scoped and explicit** — print exactly what will run (the Docker install + + `modprobe br_netfilter overlay`), not a vague "enter your password." +- **Decoupled** — runnable as a standalone "prepare this machine" step an admin + does **once** (`tracebloc prepare-host` / a documented snippet), after which + the researcher installs with zero privilege (drops to Tier 0). + +**Detection + honest failure.** The installer probes: is a runtime usable? are +the modules loaded? am I root? is sudo available? It selects the lowest workable +tier. If it can only reach Tier 2 and there's no admin path, it fails with an +**actionable** message — the exact commands to hand to IT — never the current +misleading "re-run with a user that has sudo access." + +## The B2 connection — post-install PATH (why the issue exists) + +Least-privilege means the CLI lands in `~/.local/bin` (no sudo to write +`/usr/local/bin`). A child-process installer **cannot** change the PATH of the +shell that launched it, so the binary isn't resolvable in the current shell — +"command not found" right after a successful install. That's the whole of B2: +it's a *consequence* of (correctly) avoiding sudo, not a separate bug. + +Design it away, in this order: +1. **Prefer a writable directory already on `$PATH`.** If one exists (e.g. a + `~/bin` or a Homebrew bin already set up), install there → usable in the same + terminal, no PATH edit, no new shell. +2. **Otherwise `~/.local/bin` + activate in the current shell.** Print the exact + one-liner to run *now* (`source ~/.zshrc`, or `export PATH="$HOME/.local/bin:$PATH"`) + so the user stays in the same terminal — instead of "open a new terminal." +3. The final summary's CTA must reflect this (don't say "Run `tracebloc`" when it + isn't yet on the current PATH). + +So "run it in the same terminal" is achievable: either we land on-PATH (no +action) or we hand the user one paste. A brand-new machine where *nothing* we +control is yet on PATH is the only case that truly needs a re-source — and even +then it's one command, not a new terminal. + +## Open questions (for discussion) + +1. **Rootless target:** rootless Docker vs Podman vs k3s-rootless — which is the + most portable, and what's the security posture we're comfortable with? +2. **Kernel modules on locked-down hosts:** if `br_netfilter`/`overlay` aren't + loaded and we can't `modprobe`, is there a userspace fallback, or is that a + hard Tier-2 admin requirement? +3. **The "prepare-host once" flow:** a `tracebloc prepare-host` command (or a + documented admin snippet) that does the Tier-2 privileged bits, so the + researcher install is then zero-privilege — worth building? +4. **Windows/WSL2:** same tiering? WSL2 + Docker Desktop has its own admin story. +5. **Detection cost/robustness:** probing "is Docker usable" reliably across + distros without side effects. + +## Non-goals / risks + +- Not proposing to *drop* the convenient root path for users who have it — just + to stop *requiring* it and to pick the least-privilege tier automatically. +- Rootless has real caveats (cgroup v2, overlayfs/fuse-overlayfs, port <1024, + some storage drivers) — needs validation per target. +- Some managed hosts forbid user containers entirely; there we should fail + honestly (it genuinely isn't installable there) rather than pretend. + +## Rollout + +Incremental, no big-bang: +1. **Detect + reuse an existing usable runtime → Tier 0** (biggest unlock for the + least effort; also fixes A2's "sudo not installed / already root" abort). +2. **A2 mechanics:** root-detection (`$SUDO` empty when root) + accurate + "sudo missing vs no sudo access" messaging. +3. **B2:** prefer-on-PATH dir + same-shell activation. +4. **Tier 1 (rootless)** as a follow-up spike. +5. **Tier 2 "prepare-host"** decoupled privileged step. From 43edc17c5a5185b77eb57b3d732bd1ad24254c0a Mon Sep 17 00:00:00 2001 From: Lukas Wuttke Date: Wed, 22 Jul 2026 16:37:34 +0200 Subject: [PATCH 2/4] RFC 0001: record decisions (Docker-only, kernel=Tier-2, build prepare-host + host audit, WSL2=Linux, detection yes) Resolves the five open questions from the first draft per Lukas 2026-07-22: Docker as the single runtime (rootless Docker at Tier 1); kernel modules a hard Tier-2 requirement with no userspace fallback; build a standalone prepare-host step plus a short host-audit report shared with doctor; WSL2 treated as Linux and native Windows preferring rootless Docker; and yes to side-effect-free detection. Rollout re-sequenced so detection + audit land first (also closing A2/B2). Co-Authored-By: Claude Opus 4.8 --- docs/rfcs/0001-least-privilege-install.md | 155 ++++++++++++++++------ 1 file changed, 112 insertions(+), 43 deletions(-) diff --git a/docs/rfcs/0001-least-privilege-install.md b/docs/rfcs/0001-least-privilege-install.md index a07be2ce..d0826432 100644 --- a/docs/rfcs/0001-least-privilege-install.md +++ b/docs/rfcs/0001-least-privilege-install.md @@ -1,6 +1,6 @@ # RFC 0001 — Least-privilege install -**Status:** Draft (for discussion) +**Status:** Decisions recorded 2026-07-22 — pending @saadqbal sign-off **Author:** Lukas (drafted with Claude) **Reviewers:** @saadqbal **Repos affected:** `client` (installer), `cli` (CLI install + PATH) @@ -66,35 +66,73 @@ Everything else is user-space. Probe the machine and pick the **lowest tier that works**, instead of demanding sudo up front: +> **Runtime decision (2026-07-22): Docker only.** We standardize on Docker as +> the single supported runtime for now — Docker with the user in the `docker` +> group at Tier 0, **rootless Docker** at Tier 1. We are *not* taking on Podman +> or k3s-rootless as parallel targets; one runtime keeps the detection matrix +> and the support surface small. (Revisit if a customer environment forces it.) + **Tier 0 — zero root (target for shared/managed hosts).** -A usable container runtime already exists — Docker with the user in the -`docker` group, rootless Docker, or Podman — and the kernel modules are loaded -(common on well-run shared hosts). Then: download tools to `~/.local/bin`, -create the k3d/k3s cluster in the existing runtime, register, done. **No sudo at -any point.** Many hospital/HPC boxes already provide a container runtime; we -should detect and use it. - -**Tier 1 — rootless runtime, no system Docker.** -No usable system Docker, but a rootless runtime can run as the user (rootless -Docker, Podman, or k3s rootless mode). Setup may need a **one-time, narrow** -privileged touch on some hosts (subuid/subgid ranges, a `sysctl`, or loading a -module) — nameable and grantable, not blanket admin. Investigate which of -rootless-Docker / Podman / k3s-rootless is the most portable target. - -**Tier 2 — install a runtime (the only genuine root need).** +A usable Docker already exists — the user is in the `docker` group (or rootless +Docker is already running) — and the kernel modules are loaded (common on +well-run shared hosts). Then: download tools to `~/.local/bin`, create the +k3d/k3s cluster in the existing runtime, register, done. **No sudo at any +point.** Many hospital/HPC boxes already provide Docker; we should detect and +use it. + +**Tier 1 — rootless Docker, no system Docker.** +No usable system Docker, but **rootless Docker** can run as the user. Setup may +need a **one-time, narrow** privileged touch on some hosts (subuid/subgid +ranges, a `sysctl`) — nameable and grantable, not blanket admin. rootless Docker +is the chosen rootless target (not Podman / k3s-rootless). + +**Tier 2 — install a runtime + load modules (the only genuine root need).** Nothing usable and rootless isn't viable → install Docker + load the kernel modules. This is the **one** privileged step. It should be: - **Scoped and explicit** — print exactly what will run (the Docker install + `modprobe br_netfilter overlay`), not a vague "enter your password." - **Decoupled** — runnable as a standalone "prepare this machine" step an admin - does **once** (`tracebloc prepare-host` / a documented snippet), after which - the researcher installs with zero privilege (drops to Tier 0). - -**Detection + honest failure.** The installer probes: is a runtime usable? are -the modules loaded? am I root? is sudo available? It selects the lowest workable -tier. If it can only reach Tier 2 and there's no admin path, it fails with an -**actionable** message — the exact commands to hand to IT — never the current -misleading "re-run with a user that has sudo access." + does **once** (`tracebloc prepare-host`), after which the researcher installs + with zero privilege (drops to Tier 0). + +**Kernel modules are a hard Tier-2 requirement (2026-07-22).** If +`br_netfilter` / `overlay` aren't loaded and we can't `modprobe` them, there is +**no userspace fallback** — loading a kernel module is inherently privileged. +That host needs the Tier-2 `prepare-host` step run by an admin once. We do not +attempt to fake or work around it; we detect it and route to `prepare-host`. + +**Detection + honest failure (2026-07-22: yes, build it).** The installer probes +— is Docker usable? are the modules loaded? am I root? is sudo available? — and +selects the lowest workable tier. Probes must be **side-effect-free** (e.g. +`docker info` / a throwaway `hello-world`, `lsmod`, `id -u`, `command -v sudo`). +If it can only reach Tier 2 and there's no admin path, it fails with an +**actionable** message — the exact `prepare-host` command to hand to IT — never +the current misleading "re-run with a user that has sudo access." + +**Audit report (2026-07-22: yes).** Before doing anything, the installer prints +a short, plain-language **host audit** so the user sees *why* a given tier was +chosen and what (if anything) an admin must do. Sketch: + +``` +Host check + Container runtime Docker 27.0 — usable (you're in the 'docker' group) ✓ + Kernel modules br_netfilter loaded, overlay loaded ✓ + Privilege regular user (no root, sudo available) – + → Install tier Tier 0 (zero root). Proceeding with no privileged steps. +``` + +…and when it can't proceed unprivileged, the report names the single blocker +and the exact remedy: + +``` + Kernel modules br_netfilter NOT loaded, cannot modprobe (no root) ✗ + → Blocked at Tier 2. Ask an admin to run once: + curl -fsSL https://tracebloc.io/i.sh | bash -s -- prepare-host + Then re-run this installer as yourself — it will proceed at Tier 0. +``` + +The same audit is what `tracebloc doctor` / `prepare-host` reuse — one probe, +one report format. ## The B2 connection — post-install PATH (why the issue exists) @@ -119,19 +157,36 @@ action) or we hand the user one paste. A brand-new machine where *nothing* we control is yet on PATH is the only case that truly needs a re-source — and even then it's one command, not a new terminal. -## Open questions (for discussion) - -1. **Rootless target:** rootless Docker vs Podman vs k3s-rootless — which is the - most portable, and what's the security posture we're comfortable with? -2. **Kernel modules on locked-down hosts:** if `br_netfilter`/`overlay` aren't - loaded and we can't `modprobe`, is there a userspace fallback, or is that a - hard Tier-2 admin requirement? -3. **The "prepare-host once" flow:** a `tracebloc prepare-host` command (or a - documented admin snippet) that does the Tier-2 privileged bits, so the - researcher install is then zero-privilege — worth building? -4. **Windows/WSL2:** same tiering? WSL2 + Docker Desktop has its own admin story. -5. **Detection cost/robustness:** probing "is Docker usable" reliably across - distros without side effects. +## Decisions (resolved 2026-07-22, Lukas — pending @saadqbal confirmation) + +The open questions from the first draft, now decided: + +1. **Rootless target → Docker.** We use **Docker** for now: `docker`-group at + Tier 0, **rootless Docker** at Tier 1. Podman and k3s-rootless are explicitly + out of scope until an environment forces the question. Rationale: one runtime, + one detection matrix, one support surface. +2. **Kernel modules on locked-down hosts → hard Tier-2.** No userspace fallback. + If the modules aren't loaded and we can't `modprobe`, the host requires the + admin-run `prepare-host` step; we detect and route, never work around. +3. **`prepare-host` → build it, with an audit report.** Ship `tracebloc + prepare-host` (the standalone Tier-2 privileged step an admin runs once) **and** + surface a short host-audit report (see the Proposal) so the user always sees + which tier was picked and what an admin must do. The audit is shared with + `doctor` and the main install path. +4. **Windows/WSL2 → WSL2 is Linux; native Windows prefers rootless Docker.** + Under WSL2 the environment *is* Linux, so the Linux tiers apply unchanged + (probe the WSL2 distro exactly as a native Linux host). For **native Windows** + we prefer **rootless Docker** (Docker Desktop's non-admin path) rather than a + separate privileged Windows story. WSL2 is the recommended and primary Windows + route; native Windows is best-effort on rootless Docker. +5. **Detection → yes, build robust side-effect-free probing.** As described under + Proposal → *Detection + honest failure*. + +**Still open for @saadqbal:** the rootless-Docker security posture we're +comfortable endorsing (Tier 1 caveats below), and whether `prepare-host` lives in +the CLI (`tracebloc prepare-host`) or stays a documented installer sub-command +(`i.sh -s -- prepare-host`) — the RFC currently assumes the latter for the audit +snippet, since a non-admin may not have the CLI yet. ## Non-goals / risks @@ -144,11 +199,25 @@ then it's one command, not a new terminal. ## Rollout -Incremental, no big-bang: -1. **Detect + reuse an existing usable runtime → Tier 0** (biggest unlock for the +Incremental, no big-bang. Ordered by unlock-per-effort: + +1. **Detection + host-audit report.** The side-effect-free probe (runtime usable? + modules loaded? root? sudo?) plus the short audit report that prints the chosen + tier and any admin remedy. This is the foundation every later step reads from, + and on its own it replaces the misleading "re-run with sudo access" abort with + an honest, actionable message. +2. **Detect + reuse an existing usable Docker → Tier 0** (biggest unlock for the least effort; also fixes A2's "sudo not installed / already root" abort). -2. **A2 mechanics:** root-detection (`$SUDO` empty when root) + accurate +3. **A2 mechanics:** root-detection (`$SUDO` empty when root) + accurate "sudo missing vs no sudo access" messaging. -3. **B2:** prefer-on-PATH dir + same-shell activation. -4. **Tier 1 (rootless)** as a follow-up spike. -5. **Tier 2 "prepare-host"** decoupled privileged step. +4. **B2:** prefer-on-PATH dir + same-shell activation. +5. **`prepare-host`** — the decoupled Tier-2 privileged step (Docker install + + `modprobe br_netfilter overlay`), runnable once by an admin so the researcher + then installs at Tier 0. Reuses the audit report from step 1. +6. **Tier 1 (rootless Docker)** as a follow-up spike, once the posture in + "still open for @saadqbal" is settled. +7. **Windows/WSL2:** verify the Linux probe path works unchanged under WSL2; + document native-Windows rootless Docker as best-effort. + +Steps 1–4 are the near-term batch (they also close A2 + B2 from the backlog); +5–7 follow once Asad signs off on the posture questions. From 5fbc46b082f91cec9a72cc1cb5f4b06533c46d6c Mon Sep 17 00:00:00 2001 From: Lukas Wuttke Date: Wed, 22 Jul 2026 16:48:42 +0200 Subject: [PATCH 3/4] =?UTF-8?q?RFC=200001:=20adopt=20@saadqbal=20review=20?= =?UTF-8?q?=E2=80=94=20rootless=20Docker=20as=20primary=20target?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Folds in Asad review of #369. Key flip: rootless Docker is the primary path, not a fallback, which dissolves the kernel-module question (fuse-overlayfs + slirp4netns remove overlay/br_netfilter from the privileged surface). Cant-modprobe now falls to rootless rather than failing; we probe cgroup v2 + unprivileged userns instead. prepare-host shipped as snippet + subcommand; WSL2 prefers rootless over Docker Desktop (licensing); detection uses docker-info + the id/sudo/sudo-n trio. Five open questions collapse to one hands-on spike: validate rootless Docker as the k3d backend across target hosts. Rollout re-sequenced so that spike leads the core build. Co-Authored-By: Claude Opus 4.8 --- docs/rfcs/0001-least-privilege-install.md | 272 ++++++++++++++-------- 1 file changed, 170 insertions(+), 102 deletions(-) diff --git a/docs/rfcs/0001-least-privilege-install.md b/docs/rfcs/0001-least-privilege-install.md index d0826432..c62dd178 100644 --- a/docs/rfcs/0001-least-privilege-install.md +++ b/docs/rfcs/0001-least-privilege-install.md @@ -1,6 +1,6 @@ # RFC 0001 — Least-privilege install -**Status:** Decisions recorded 2026-07-22 — pending @saadqbal sign-off +**Status:** Decisions recorded 2026-07-22, revised after @saadqbal review (rootless Docker primary) — one validation spike open **Author:** Lukas (drafted with Claude) **Reviewers:** @saadqbal **Repos affected:** `client` (installer), `cli` (CLI install + PATH) @@ -57,78 +57,128 @@ container runtime or its kernel/daemon prerequisites** — nothing else: plus kubectl/helm operations) runs **inside Docker as the user** — no additional root once a usable Docker exists. -**The insight:** we don't need "admin." We need **a usable container runtime** -(plus, on Linux, two kernel modules). That is the *entire* privileged surface. -Everything else is user-space. +**The insight:** we don't need "admin." We need **a usable container runtime**. +On Linux, a *system* Docker also pulls in two kernel modules — but **rootless +Docker removes even those from the privileged surface** (`overlay` → +fuse-overlayfs in userspace; `br_netfilter` → unneeded once networking goes +through slirp4netns). So if we make rootless the primary target, the entire +privileged surface collapses to a single question — *can this user run a +container at all?* — which on any modern kernel (cgroup v2 + unprivileged +userns, default-on for Ubuntu 22.04+ / RHEL 9+) is **yes, with no root**. +Everything else — kubectl, helm, k3d, the CLI, the k3d/k3s cluster — is already +user-space. ## Proposal — tiered, least-privilege install Probe the machine and pick the **lowest tier that works**, instead of demanding sudo up front: -> **Runtime decision (2026-07-22): Docker only.** We standardize on Docker as -> the single supported runtime for now — Docker with the user in the `docker` -> group at Tier 0, **rootless Docker** at Tier 1. We are *not* taking on Podman -> or k3s-rootless as parallel targets; one runtime keeps the detection matrix -> and the support surface small. (Revisit if a customer environment forces it.) - -**Tier 0 — zero root (target for shared/managed hosts).** -A usable Docker already exists — the user is in the `docker` group (or rootless -Docker is already running) — and the kernel modules are loaded (common on -well-run shared hosts). Then: download tools to `~/.local/bin`, create the -k3d/k3s cluster in the existing runtime, register, done. **No sudo at any -point.** Many hospital/HPC boxes already provide Docker; we should detect and -use it. - -**Tier 1 — rootless Docker, no system Docker.** -No usable system Docker, but **rootless Docker** can run as the user. Setup may -need a **one-time, narrow** privileged touch on some hosts (subuid/subgid -ranges, a `sysctl`) — nameable and grantable, not blanket admin. rootless Docker -is the chosen rootless target (not Podman / k3s-rootless). - -**Tier 2 — install a runtime + load modules (the only genuine root need).** -Nothing usable and rootless isn't viable → install Docker + load the kernel -modules. This is the **one** privileged step. It should be: -- **Scoped and explicit** — print exactly what will run (the Docker install + - `modprobe br_netfilter overlay`), not a vague "enter your password." -- **Decoupled** — runnable as a standalone "prepare this machine" step an admin - does **once** (`tracebloc prepare-host`), after which the researcher installs - with zero privilege (drops to Tier 0). - -**Kernel modules are a hard Tier-2 requirement (2026-07-22).** If -`br_netfilter` / `overlay` aren't loaded and we can't `modprobe` them, there is -**no userspace fallback** — loading a kernel module is inherently privileged. -That host needs the Tier-2 `prepare-host` step run by an admin once. We do not -attempt to fake or work around it; we detect it and route to `prepare-host`. - -**Detection + honest failure (2026-07-22: yes, build it).** The installer probes -— is Docker usable? are the modules loaded? am I root? is sudo available? — and -selects the lowest workable tier. Probes must be **side-effect-free** (e.g. -`docker info` / a throwaway `hello-world`, `lsmod`, `id -u`, `command -v sudo`). -If it can only reach Tier 2 and there's no admin path, it fails with an -**actionable** message — the exact `prepare-host` command to hand to IT — never -the current misleading "re-run with a user that has sudo access." +> **Runtime decision (updated 2026-07-22, post-review): rootless Docker is the +> *primary* target.** Docker stays the one supported runtime, but we lead with +> **rootless Docker**, not system Docker. It keeps the entire k3d code path +> intact (just a different socket), it's a genuine security *selling point* to +> hospital/uni IT, and — critically — it removes the kernel modules from the +> privileged surface entirely (see the insight above). Podman-as-k3d-backend is +> too flaky to be primary (best-effort only); k3s-rootless is the cleaner +> long-term end state but a bigger rewrite — parked as a future spike, not a +> competing option now. *(First draft had system Docker primary with rootless as +> a fallback; Asad's review flipped this, which is what dissolves the +> kernel-module question below.)* + +**Tier 0 — zero root (the common case).** +The user can already run a container — rootless Docker is already up, **or** the +user is in the `docker` group. Then: download tools to `~/.local/bin`, create +the k3d/k3s cluster in the existing runtime, register, done. **No sudo at any +point.** Many hospital/HPC boxes already provide one of these; detect and use it. + +**Tier 1 — set up rootless Docker as the user (the primary path when nothing +exists yet).** +No runtime yet, but the kernel supports unprivileged containers (cgroup v2 + +unprivileged userns — default-on for Ubuntu 22.04+ / RHEL 9+). Install rootless +Docker into the user's account and run everything through it. On a modern kernel +this needs **no root**; on some hosts it needs a **one-time, narrow** privileged +touch (subuid/subgid ranges or a single `sysctl`) — nameable and grantable, not +blanket admin. **No kernel modules**: rootless uses fuse-overlayfs + slirp4netns. + +**Tier 2 — the rare genuine root need.** +Only when the kernel itself can't run an unprivileged container (old kernel: +no cgroup v2 / userns disabled) or the host has no container capability at all. +Then something privileged must happen once — update/configure the kernel or +install a system runtime — plus, on Windows, **enabling WSL2 is the equivalent +one-time admin step**. This is the *only* privileged tier, and it should be: +- **Scoped and explicit** — print exactly what will run, not a vague "enter your + password." +- **Decoupled** — a standalone "prepare this machine" step an admin does **once** + (see `prepare-host` below), after which the researcher installs at Tier 0/1 + with zero privilege. + +**"Can't `modprobe`" now means *fall to rootless*, not fail (updated +2026-07-22).** This reverses the first draft. Because rootless sidesteps both +modules, a host where `br_netfilter` / `overlay` aren't loaded is **not** +blocked — it drops to rootless Docker (Tier 1). The thing we actually probe is +**cgroup v2 + unprivileged userns**; only their absence (a genuinely old +kernel) forces Tier 2. We never fail a host just because a module isn't loaded. + +**`prepare-host` — thin, and shipped two ways.** The Tier-2 privileged step is a +single reviewable snippet IT approves once, exposed as **both** a readable shell +snippet (`curl … | bash -s -- prepare-host`, for a host with no CLI yet) **and** +a `tracebloc prepare-host` subcommand that runs the *same* audited snippet. IT +reviews one thing; researchers then self-serve at Tier 0. + +**Detection — behavioral and side-effect-free (updated 2026-07-22).** One +test settles the tier: +- **`docker info` exit 0 is the Tier-0 check.** It proves binary + daemon + + user-in-group all at once — no separate group/socket probing. Do **not** run + `docker run hello-world` by default (it pulls an image); gate that behind an + opt-in `--verify`. +- **If `docker info` fails**, probe **cgroup v2 + unprivileged userns** + (`/sys/fs/cgroup/cgroup.controllers` present; `kernel.unprivileged_userns_clone` + / `max_user_namespaces` > 0). Present → Tier 1 (set up rootless). Absent → + Tier 2. +- **Privilege trio for honest messaging:** `id -u` (already root?), + `command -v sudo` (sudo installed?), `sudo -n true` (sudo without a password?). + This cleanly separates *already root* vs *sudo missing* vs *sudo needs a + password* — and kills the current misleading "re-run with a user that has sudo + access" abort (backlog A2). + +If the only workable tier is 2 and there's no admin path, fail with an +**actionable** message — the exact `prepare-host` command to hand to IT. **Audit report (2026-07-22: yes).** Before doing anything, the installer prints a short, plain-language **host audit** so the user sees *why* a given tier was chosen and what (if anything) an admin must do. Sketch: +Tier 0 — a container is already runnable, nothing to do: + ``` Host check - Container runtime Docker 27.0 — usable (you're in the 'docker' group) ✓ - Kernel modules br_netfilter loaded, overlay loaded ✓ - Privilege regular user (no root, sudo available) – + Container runtime Docker 27.0 — usable (docker info OK) ✓ + Privilege regular user (no root; sudo available) – → Install tier Tier 0 (zero root). Proceeding with no privileged steps. ``` -…and when it can't proceed unprivileged, the report names the single blocker -and the exact remedy: +Tier 1 — no runtime yet, but the kernel supports unprivileged containers, so we +set up rootless Docker as the user — still no root: ``` - Kernel modules br_netfilter NOT loaded, cannot modprobe (no root) ✗ +Host check + Container runtime none found – + Kernel cgroup v2 + unprivileged userns present ✓ + Privilege regular user (no root) – + → Install tier Tier 1. Setting up rootless Docker in your account + (no admin needed) — fuse-overlayfs, slirp4netns. +``` + +Tier 2 — the rare block: the kernel itself can't run unprivileged containers. +The report names the single blocker and the exact remedy: + +``` +Host check + Container runtime none found – + Kernel unprivileged userns DISABLED (kernel too old/locked) ✗ → Blocked at Tier 2. Ask an admin to run once: curl -fsSL https://tracebloc.io/i.sh | bash -s -- prepare-host - Then re-run this installer as yourself — it will proceed at Tier 0. + Then re-run this installer as yourself — it will proceed at Tier 0/1. ``` The same audit is what `tracebloc doctor` / `prepare-host` reuse — one probe, @@ -157,67 +207,85 @@ action) or we hand the user one paste. A brand-new machine where *nothing* we control is yet on PATH is the only case that truly needs a re-source — and even then it's one command, not a new terminal. -## Decisions (resolved 2026-07-22, Lukas — pending @saadqbal confirmation) - -The open questions from the first draft, now decided: - -1. **Rootless target → Docker.** We use **Docker** for now: `docker`-group at - Tier 0, **rootless Docker** at Tier 1. Podman and k3s-rootless are explicitly - out of scope until an environment forces the question. Rationale: one runtime, - one detection matrix, one support surface. -2. **Kernel modules on locked-down hosts → hard Tier-2.** No userspace fallback. - If the modules aren't loaded and we can't `modprobe`, the host requires the - admin-run `prepare-host` step; we detect and route, never work around. -3. **`prepare-host` → build it, with an audit report.** Ship `tracebloc - prepare-host` (the standalone Tier-2 privileged step an admin runs once) **and** - surface a short host-audit report (see the Proposal) so the user always sees - which tier was picked and what an admin must do. The audit is shared with - `doctor` and the main install path. -4. **Windows/WSL2 → WSL2 is Linux; native Windows prefers rootless Docker.** - Under WSL2 the environment *is* Linux, so the Linux tiers apply unchanged - (probe the WSL2 distro exactly as a native Linux host). For **native Windows** - we prefer **rootless Docker** (Docker Desktop's non-admin path) rather than a - separate privileged Windows story. WSL2 is the recommended and primary Windows - route; native Windows is best-effort on rootless Docker. -5. **Detection → yes, build robust side-effect-free probing.** As described under - Proposal → *Detection + honest failure*. - -**Still open for @saadqbal:** the rootless-Docker security posture we're -comfortable endorsing (Tier 1 caveats below), and whether `prepare-host` lives in -the CLI (`tracebloc prepare-host`) or stays a documented installer sub-command -(`i.sh -s -- prepare-host`) — the RFC currently assumes the latter for the audit -snippet, since a non-admin may not have the CLI yet. +## Decisions (2026-07-22 — Lukas's first pass, then revised after @saadqbal review) + +The first draft's five open questions are decided. Asad's review collapsed the +first two into one and flipped the kernel-module call — that revision is folded +in here and is now the RFC's position. + +1. **Runtime → rootless Docker, primary (was: system Docker primary, rootless + fallback).** Docker is the one supported runtime; we lead with **rootless + Docker**. It keeps the k3d code path intact, is a security selling point, and + removes the kernel modules from the privileged surface. Podman → best-effort + only; k3s-rootless → parked future spike. +2. **Kernel modules → moot; "can't `modprobe`" falls to rootless, not fail (was: + hard Tier-2).** Rootless uses fuse-overlayfs (`overlay`) + slirp4netns + (`br_netfilter` unneeded). We probe **cgroup v2 + unprivileged userns** + instead; only their absence (a genuinely old/locked kernel) forces Tier 2. +3. **`prepare-host` → build it thin, shipped two ways.** A single reviewable + snippet IT approves once, exposed as **both** `curl … | bash -s -- prepare-host` + (host with no CLI yet) **and** a `tracebloc prepare-host` subcommand running + that same audited snippet — plus the shared host-audit report. Last in the + rollout. *(This also settles my earlier "CLI vs installer sub-command" open + item: both.)* +4. **Windows/WSL2 → WSL2 is Linux; enabling WSL2 is the Windows Tier-2 step.** + Inside the distro the Linux tiers apply unchanged. **Prefer rootless Docker + inside WSL over Docker Desktop** — Docker Desktop's commercial licensing is a + real blocker for large hospital systems; use its WSL integration only if it's + already present. +5. **Detection → yes; behavioral + side-effect-free.** `docker info` exit 0 is + the single Tier-0 test; `hello-world` only behind `--verify`; the + `id -u` / `command -v sudo` / `sudo -n true` trio for honest A2 messaging; + cgroup v2 + userns probe to split Tier 1 from Tier 2. (See Proposal → Detection.) + +**The one remaining question needing a hands-on spike** (Asad): validate +**rootless Docker as the k3d backend across our target hosts** — confirm cgroup +v2 + unprivileged userns coverage (Ubuntu 22.04+ / RHEL 9+ and the specific +hospital/HPC images we've seen), and settle the security posture we endorse +(fuse-overlayfs, slirp4netns, port <1024, storage-driver caveats). Everything +else is build work, not open design. ## Non-goals / risks -- Not proposing to *drop* the convenient root path for users who have it — just - to stop *requiring* it and to pick the least-privilege tier automatically. -- Rootless has real caveats (cgroup v2, overlayfs/fuse-overlayfs, port <1024, - some storage drivers) — needs validation per target. -- Some managed hosts forbid user containers entirely; there we should fail - honestly (it genuinely isn't installable there) rather than pretend. +- Not proposing to *drop* the system-Docker path for users who already have it + (Tier 0 uses it when the user is in the `docker` group) — just to stop + *requiring* it and to lead with rootless. +- **Rootless is now the primary path, so its caveats are load-bearing, not + footnotes** — cgroup v2, fuse-overlayfs vs native overlay (slower large-file + I/O, which matters for dataset copies), slirp4netns networking, port <1024, + storage-driver support. This is exactly what the spike must validate on our + target hosts before we commit the rollout. +- Some managed hosts disable unprivileged userns entirely (hardened kernels, + older RHEL). There rootless genuinely can't run — we detect it, route to Tier 2 + `prepare-host`, and fail honestly rather than pretend. ## Rollout Incremental, no big-bang. Ordered by unlock-per-effort: -1. **Detection + host-audit report.** The side-effect-free probe (runtime usable? - modules loaded? root? sudo?) plus the short audit report that prints the chosen - tier and any admin remedy. This is the foundation every later step reads from, - and on its own it replaces the misleading "re-run with sudo access" abort with - an honest, actionable message. +1. **Detection + host-audit report.** The side-effect-free probe (`docker info`; + cgroup v2 + userns; the `id -u` / `sudo` / `sudo -n` trio) plus the short audit + report that prints the chosen tier and any admin remedy. Foundation every later + step reads from, and on its own it replaces the misleading "re-run with sudo + access" abort with an honest, actionable message (closes A2's messaging half). 2. **Detect + reuse an existing usable Docker → Tier 0** (biggest unlock for the least effort; also fixes A2's "sudo not installed / already root" abort). 3. **A2 mechanics:** root-detection (`$SUDO` empty when root) + accurate "sudo missing vs no sudo access" messaging. 4. **B2:** prefer-on-PATH dir + same-shell activation. -5. **`prepare-host`** — the decoupled Tier-2 privileged step (Docker install + - `modprobe br_netfilter overlay`), runnable once by an admin so the researcher - then installs at Tier 0. Reuses the audit report from step 1. -6. **Tier 1 (rootless Docker)** as a follow-up spike, once the posture in - "still open for @saadqbal" is settled. -7. **Windows/WSL2:** verify the Linux probe path works unchanged under WSL2; - document native-Windows rootless Docker as best-effort. +5. **Rootless spike (the gating unknown).** Validate rootless Docker as the k3d + backend across our target hosts — cgroup v2 + userns coverage, fuse-overlayfs + dataset-copy performance, security posture. Because rootless is now the primary + path, this gates the core build, so it moves *up* — not a tail-end follow-up. +6. **Tier 1 — set up rootless Docker as the user.** The core no-runtime path, + built on the spike's findings. +7. **`prepare-host`** — the decoupled Tier-2 step (the rare old/locked-kernel + case: enable userns / install a runtime), thin, shipped as snippet + + `tracebloc prepare-host`. Reuses the audit report from step 1. +8. **Windows/WSL2:** verify the Linux probe path works unchanged under WSL2; + prefer rootless Docker over Docker Desktop; document enabling WSL2 as the + Windows one-time admin step. Steps 1–4 are the near-term batch (they also close A2 + B2 from the backlog); -5–7 follow once Asad signs off on the posture questions. +5 is the spike that unblocks the rest; 6–8 follow. The whole thing degrades +gracefully — a host that already has Docker never sees any of the tier machinery. From f303d547bfd336573e5016a6a584df6973052383 Mon Sep 17 00:00:00 2001 From: Lukas Wuttke Date: Sat, 25 Jul 2026 12:27:04 +0200 Subject: [PATCH 4/4] docs(rfc): renumber to RFC-CLIENT-0002 and record the accepted status MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two fixes to get this RFC out of limbo. **Renumber 0001 -> 0002.** `client` already has a `0001-rootless-spike.md` on develop, so this file collided with it inside its own repo. 0002 is the next free number in this repo. The header now carries the qualified ID `RFC-CLIENT-0002` — RFC numbers are per-repo, so a bare "RFC 0001" names four different documents across the org. **Status: Accepted (2026-07-25).** The design has plainly been accepted in practice: the tracking epic tracebloc/backend#1168 is 6-of-9 children merged, including the foundation, tier routing, sudo handling, Tier 0 and the rootless spike. Leaving the document as a permanent "do not merge" draft left the repo with no record of a decision the team had already acted on. Implementation status stays where it belongs — on the epic, not in this header. Co-Authored-By: Claude Fable 5 --- ...rivilege-install.md => 0002-least-privilege-install.md} | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) rename docs/rfcs/{0001-least-privilege-install.md => 0002-least-privilege-install.md} (95%) diff --git a/docs/rfcs/0001-least-privilege-install.md b/docs/rfcs/0002-least-privilege-install.md similarity index 95% rename from docs/rfcs/0001-least-privilege-install.md rename to docs/rfcs/0002-least-privilege-install.md index c62dd178..035904e8 100644 --- a/docs/rfcs/0001-least-privilege-install.md +++ b/docs/rfcs/0002-least-privilege-install.md @@ -1,10 +1,11 @@ -# RFC 0001 — Least-privilege install +# RFC 0002 — Least-privilege install -**Status:** Decisions recorded 2026-07-22, revised after @saadqbal review (rootless Docker primary) — one validation spike open +**Qualified ID:** `RFC-CLIENT-0002` — renumbered from 0001 on 2026-07-25 because `client` already had a 0001 (the rootless spike). Cite by qualified ID, never as a bare "RFC 0002". Org-wide index: `docs/rfcs/README.md` in `tracebloc/backend` (private). +**Status:** **Accepted** — 2026-07-25. Decisions were recorded 2026-07-22 and revised after @saadqbal's review (rootless Docker as the primary target); one validation spike remains open. Implementation is tracked in the epic **tracebloc/backend#1168**, which was 6-of-9 children merged when this was accepted. **Author:** Lukas (drafted with Claude) **Reviewers:** @saadqbal **Repos affected:** `client` (installer), `cli` (CLI install + PATH) -**Related:** backlog A2 (sudo/root handling), A3 (rootless), B2 (post-install PATH) +**Related:** [`RFC-CLIENT-0001` — spike: rootless Docker as the k3d backend](./0001-rootless-spike.md) · backlog A2 (sudo/root handling), A3 (rootless), B2 (post-install PATH) ## Summary