From 4aeeb8c9d5c716116d1abf794789c544f56e3667 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 10 May 2026 14:37:53 +0000 Subject: [PATCH] =?UTF-8?q?0.9.30=20=E2=80=94=20flip=20rootless=5Fper=5Fus?= =?UTF-8?q?er=20default=20to=20true?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Last config-only release of the 0.9.x rootless track. The master toggle in daemon/config.h now defaults to true, so new installs (and old installs whose crated.conf doesn't set the field) compose paths, ZFS prefix, network sub-CIDR, and RCTL umbrella from the connecting operator's uid. Sample config rewritten to show the new default; migration doc gained a "Rolling back" section covering the opt-out procedure (rootless_per_user: false + restart + jail recycle). Wire-format unchanged. Suite stays at 1303. Remaining for 1.0.0: setuid bit removed from Makefile install target. --- CHANGELOG.md | 110 +++++++++++++++++++++++++++++++ cli/args.cpp | 4 +- daemon/config.h | 10 ++- daemon/crated.conf.sample | 25 ++++--- docs/rootless-migration.md | 129 +++++++++++++++++++++++++++---------- 5 files changed, 229 insertions(+), 49 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9decfdf..8fef81d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,116 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). --- +## [0.9.30] — 2026-05-10 + +**Rootless track, default flip.** Thirty-first 0.9.x release. +The `rootless_per_user` master toggle now defaults to **true**. +New installs (and old installs whose `crated.conf` does not +set the field explicitly) compose paths, ZFS dataset prefix, +network sub-CIDR, and RCTL umbrella loginclass from the +connecting operator's uid. Operators who want the legacy +single-tenant shape must opt out with `rootless_per_user: false`. + +### What changes + +#### Default value flipped + +```cpp +// daemon/config.h — was 0.9.12 .. 0.9.29 +bool rootlessPerUser = false; + +// daemon/config.h — 0.9.30+ +bool rootlessPerUser = true; +``` + +The field default flips at the C++ level. The YAML key name +is unchanged (`rootless_per_user`), and the parser still +honours an explicit `false` value byte-for-byte the same way +it always did. + +#### Sample config rewritten + +`daemon/crated.conf.sample` now shows the toggle commented +out at its new default value (`# rootless_per_user: true`), +with prose explaining that operators wanting the legacy shape +must uncomment to `false` explicitly. The "0.9.14" forward- +reference is replaced with a "default since 0.9.30" note. + +#### Migration doc updated + +`docs/rootless-migration.md` rewrote two sections: + +1. **Status** — bumped to "rootless model is on by default", + with explicit `rootless_per_user: false` callout for + operators who want to opt out +2. **Single-tenant migration** — split into two paths + (accept the flip vs. pin to legacy), added a dedicated + "Rolling back" subsection covering the toggle pin + + restart + jail recycle procedure + +The 0.9.13–0.9.30 release-by-release changelog inside the +doc was also brought up to date (was last touched at 0.9.13 +planning). + +### Behaviour for upgraders + +| Pre-0.9.30 `crated.conf` state | 0.9.30 effective value | Action needed | +|---------------------------------------------|------------------------|---------------------| +| `rootless_per_user: true` (explicit) | `true` | None | +| `rootless_per_user: false` (explicit) | `false` | None | +| key absent (most 0.8.x → 0.9.x upgrades) | `true` (was `false`) | Recycle jails OR pin to false | + +The third row is the breaking case: a 0.8.x deployment that +upgraded through 0.9.x without ever touching `crated.conf` +gets rootless mode at the next `service crated restart`. +Operators in that boat who don't want to migrate jails should +add `rootless_per_user: false` before restarting crated. + +The daemon does not auto-rearrange ZFS datasets or migrate +existing jails — operators control the cutover by stopping +and re-running their jails after the flip. See the migration +doc's "Path A — accept the flip" section. + +### Wire compatibility + +No wire changes. All 21 privops verbs from 0.9.0–0.9.28 +unchanged. JSON + libnv schemas unchanged. Bearer token +format unchanged. The flip is purely a config-default change +in the daemon. + +### Series state + +Track complete except for setuid removal: + +- 0.9.0–0.9.7: privops verb taxonomy (14 verbs, JSON) +- 0.9.8–0.9.13: per-user namespacing pure modules + audit +- 0.9.14: libnv listener (FreeBSD-native, getpeereid) +- 0.9.15–0.9.29: 14 CLI call sites wired through privops; + verb set grew to 21 +- **0.9.30: default flip (this release)** +- 1.0.0: setuid bit removed from `Makefile install` + +### Tests + +No new tests — the change is a single struct member default. +Suite stays at 1303. Existing `Crated::Config::load` coverage +exercises both the absent-field path (now defaults true) and +the explicit-false path. FreeBSD CI smoke-runs the daemon +startup which validates the default end-to-end. + +### Files + +- `daemon/config.h` — default flipped to `true`, comment + expanded with 0.9.30 paragraph and rollback pointer +- `daemon/crated.conf.sample` — block rewritten to show + new default + opt-out instructions +- `docs/rootless-migration.md` — status, schema example, + migration path, and release-by-release log updated +- `cli/args.cpp` — version `crate 0.9.30` +- `CHANGELOG.md` — this entry + +--- + ## [0.9.29] — 2026-05-10 **Rootless track, RCTL umbrella auto-apply.** Thirtieth 0.9.x diff --git a/cli/args.cpp b/cli/args.cpp index fd547c8..9b5512e 100644 --- a/cli/args.cpp +++ b/cli/args.cpp @@ -753,7 +753,7 @@ Args parseArguments(int argc, char** argv, unsigned &processed) { args.noColor = true; break; } else if (strEq(argv[a], "--version")) { - std::cout << "crate 0.9.29" << std::endl; + std::cout << "crate 0.9.30" << std::endl; exit(0); } else if (auto argShort = isShort(argv[a])) { switch (argShort) { @@ -764,7 +764,7 @@ Args parseArguments(int argc, char** argv, unsigned &processed) { args.logProgress = true; break; case 'V': - std::cout << "crate 0.9.29" << std::endl; + std::cout << "crate 0.9.30" << std::endl; exit(0); default: err("unsupported short option '%s'", argv[a]); diff --git a/daemon/config.h b/daemon/config.h index f418ef6..10a919a 100644 --- a/daemon/config.h +++ b/daemon/config.h @@ -83,9 +83,13 @@ struct Config { // back to legacy single-tenant shape so existing 0.8.x deployments // are byte-identical. // - // Default: false. Operators flip after reading - // docs/rootless-migration.md. The default flips to true in 0.9.14. - bool rootlessPerUser = false; + // 0.9.30: DEFAULT FLIPPED to true. New installs (and old installs + // whose crated.conf doesn't set this field explicitly) now run + // in rootless mode. Operators wanting the legacy single-tenant + // path must add `rootless_per_user: false` to crated.conf + // explicitly. See docs/rootless-migration.md "Rolling back" for + // the procedure. + bool rootlessPerUser = true; // Master ZFS prefix; per-user datasets land under // `//`. Empty string means "no diff --git a/daemon/crated.conf.sample b/daemon/crated.conf.sample index eae2466..09838e6 100644 --- a/daemon/crated.conf.sample +++ b/daemon/crated.conf.sample @@ -139,17 +139,22 @@ log: ## ## --- 0.9.12: rootless per-user namespacing --- ## -## Master toggle. When false (default), runtime helpers fall back to -## legacy single-tenant shape — every operator shares /var/run/crate/, -## one ZFS prefix, one IP pool, no RCTL umbrella. Existing 0.8.x -## deployments are byte-identical at false. -## -## When true, per-user separation kicks in. See -## docs/rootless-migration.md for the full migration guide. -## -## The default flips to true in 0.9.14. +## Master toggle. When true (default since 0.9.30), per-user +## separation kicks in: runtime helpers compose paths, ZFS dataset +## prefix, network sub-CIDR, and RCTL umbrella loginclass from the +## connecting operator's uid (via getpeereid(2) on the privops +## socket). +## +## Set to false to fall back to legacy single-tenant shape — every +## operator shares /var/run/crate/, one ZFS prefix, one IP pool, +## no RCTL umbrella. Existing 0.8.x deployments that need +## byte-identical behaviour after upgrade should set this to false +## explicitly. +## +## See docs/rootless-migration.md for the full migration guide, +## including the "Rolling back" section for downgrade procedure. # -# rootless_per_user: false +# rootless_per_user: true ## ## ZFS master prefix. Per-user datasets land under diff --git a/docs/rootless-migration.md b/docs/rootless-migration.md index 4c03eaa..be8c95f 100644 --- a/docs/rootless-migration.md +++ b/docs/rootless-migration.md @@ -4,13 +4,16 @@ considering the move to the rootless model that lands in **1.0.0**. -**Current status (0.9.12):** the rootless model is opt-in. The -default install is unchanged — `crate(1)` is still installed -with `mode 04755` (setuid root), and `crated.conf` ships with -`rootless_per_user: false`. This document tells you what -changes when you flip the toggle, what infrastructure the -daemon takes over, and how to migrate existing single-tenant -deployments without downtime. +**Current status (0.9.30):** the rootless model is **on by +default**. New installs (and old installs whose `crated.conf` +doesn't set `rootless_per_user` explicitly) compose paths, +ZFS prefixes, network sub-CIDRs, and RCTL umbrellas from the +connecting operator's uid. `crate(1)` is still installed with +`mode 04755` (setuid root) until 1.0.0; the setuid bit is +removed in the 1.0.0 release. Operators wanting the legacy +single-tenant shape must opt out explicitly with +`rootless_per_user: false` — see the "Rolling back" section +below. --- @@ -111,12 +114,14 @@ Stable across crated restarts; collisions at slot capacity. Each operator gets a loginclass `crate-` whose RCTL umbrella caps total usage across all of that operator's jails. -### 0.9.12 — config schema (this release) +### 0.9.12 — config schema `crated.conf` gains: ```yaml -rootless_per_user: false # master toggle, default off +rootless_per_user: true # master toggle (default + # since 0.9.30; was off + # in 0.9.12–0.9.29) zfs_master_prefix: "zroot/jails" # per-user datasets land # under // @@ -126,22 +131,47 @@ network_master_cidr_v6: "" # empty disables v6 per-user network_sub_prefix_len_v6: 64 ``` -When `rootless_per_user: false` (the default), every helper -that consults the config falls back to legacy single-tenant -behaviour. **Existing deployments are byte-identical to 0.8.x** -until the operator explicitly opts in. +Setting `rootless_per_user: false` makes every helper fall +back to legacy single-tenant behaviour, byte-identical to +0.8.x. Set this if you upgraded from 0.8.x and don't yet +want the per-user split. -### 0.9.13 (planned) — wiring flip +### 0.9.13 — wiring flip `lib/network_lease.cpp` switches to per-user lease files when the toggle is on. RCTL handlers apply both per-jail and loginclass umbrella rules. Audit log gets a per-user copy. -### 0.9.14 (planned) — default flip +### 0.9.14 — libnv privops listener -`crated.conf.sample` ships with `rootless_per_user: true` by -default. Operators upgrading see a one-line `pkg upgrade` -warning pointing at this doc. +`crated` opens an AF_UNIX socket and accepts nvlist-encoded +privops requests from local clients. `getpeereid(2)` extracts +the operator's uid for free, feeding the per-user audit hook. +HTTP `/api/v1/privops/` remains for remote/CI clients. + +### 0.9.15–0.9.29 — call-site wiring + verb expansion + +`crate(1)` call sites moved through privops one mini-PR at a +time: `set_rctl`/`clear_rctl`, `attach_zfs`, `destroy_jail` +on stop, `mount_nullfs`, `iface` atoms (`set_iface_up`, +`disable_iface_offload`, `bridge_add_member`, `bridge_del_member`, +`set_iface_inet_addr`, `create_epair`), `set_loginclass_rctl` / +`clear_loginclass_rctl`. The privops verb taxonomy grew from +14 to 21 verbs; the original 14 stayed wire-stable. + +0.9.27 lazy-resolved network leases to per-user paths when +the privops socket is detected. 0.9.29 added an opt-in +`rctl_umbrella:` block that the daemon auto-applies to the +operator's `crate-` loginclass after `create_jail`. + +### 0.9.30 — default flip + +`bool rootlessPerUser = true;` in `daemon/config.h`. The +sample `crated.conf` shows the flag commented out at its new +default value; existing crated.conf files without the field +auto-flip on upgrade. Operators wanting the legacy single- +tenant path must add `rootless_per_user: false` explicitly — +see "Rolling back" below. ### 1.0.0 (planned) — setuid removed @@ -154,40 +184,71 @@ patch the Makefile or pin to 0.9.x. ## Migration steps for a single-tenant deployment -Single-tenant operators don't need rootless mode but may want -to opt in to dry-run the new flow before 1.0.0 lands. +Single-tenant operators upgrading from ≤ 0.9.29 to ≥ 0.9.30 +auto-flip into rootless mode unless their `crated.conf` sets +`rootless_per_user: false` explicitly. Choose one of the two +paths below. + +### Path A — accept the flip ```sh -# 1. Update crated.conf — opt-in toggle plus defaults +# 1. Upgrade the package (default flips to true). +pkg upgrade crate + +# 2. Restart crated. +service crated restart + +# 3. Stop and restart your jails so they re-create with the +# per-user layout. (No automatic in-place migration; the +# daemon does not rearrange ZFS datasets on upgrade.) +crate stop myjail +crate run myjail +ls /var/run/crate/$(id -u)/leases/ +``` + +### Path B — keep the legacy single-tenant shape + +```sh +# 1. Pin the toggle off before restarting crated. cat >> /usr/local/etc/crated.conf <<'EOF' -# 0.9.12 — rootless namespacing (off by default) +# Pinned to legacy single-tenant shape (was the default +# pre-0.9.30; explicit setting required from 0.9.30 onward). rootless_per_user: false EOF -# 2. Restart crated +# 2. Restart crated. service crated restart -# 3. Verify legacy behaviour preserved +# 3. Verify legacy behaviour preserved. crate run myjail # works as before ls /var/run/crate/ # contains crated.sock + legacy files, # no per-uid subdirs +``` + +### Rolling back + +If you upgraded to 0.9.30+ and the per-user split misbehaves: -# 4. Opt in (dry-run) -sed -i '' 's/rootless_per_user: false/rootless_per_user: true/' \ +```sh +# 1. Stop active jails. +crate stop --all + +# 2. Pin the toggle off. +sed -i '' 's/^# *rootless_per_user:.*/rootless_per_user: false/' \ /usr/local/etc/crated.conf -service crated restart +grep -q '^rootless_per_user:' /usr/local/etc/crated.conf || \ + echo 'rootless_per_user: false' >> /usr/local/etc/crated.conf -# 5. Stop and restart your jails so they re-create with the -# per-user layout. (No automatic in-place migration in 0.9.12; -# that lands in 0.9.13 with the wiring flip.) -crate stop myjail +# 3. Restart and recycle jails. +service crated restart crate run myjail -ls /var/run/crate/$(id -u)/leases/ ``` -Rolling back is `rootless_per_user: false` + `service crated -restart` + jail recycle. +No package downgrade needed — the toggle is preserved across +0.9.30+ releases. Downgrading to ≤ 0.9.29 is also supported +(the YAML key is the same and the legacy code path is the +0.9.29 default). ---