Skip to content

Trust model docs + privops authorize-before-dispatch (1.1.11)#207

Merged
click0 merged 2 commits into
mainfrom
claude/analyze-test-coverage-nCOJW
May 24, 2026
Merged

Trust model docs + privops authorize-before-dispatch (1.1.11)#207
click0 merged 2 commits into
mainfrom
claude/analyze-test-coverage-nCOJW

Conversation

@click0
Copy link
Copy Markdown
Owner

@click0 click0 commented May 23, 2026

Summary

Two related changes on top of main (1.1.11):

1. Trust-model docs (docs/trust-model.{md,uk.md})

Document where cross-tenant isolation is enforced and where it is not, accurate to the rootless model: the privileged plane is no longer setuid crate(1) (removed in 1.0.0) — it relocated into crated's privops surface (HTTP admin-only + libnv group-gated socket). Per-tenant isolation is enforced on the pooled control plane (control sockets / bearer tokens / ws-console); local Unix access to the main API stays a single trust domain. Per-user namespacing separates honest operators but isn't an adversarial boundary on the privops path. Cross-links security-command-paths.{md,uk.md}.

2. privops authorize-before-dispatch (feat)

Closes the cleanly-ownable part of the gap the doc identified. New pure module lib/privops_authz_pure.{h,cpp} + privops_authz_pure_test (12 cases). On the libnv transport (real peer uid via getpeereid), when rootless per-user is on, a verb is denied 403 before dispatch if it names another operator's resource:

  • attach_zfs / detach_zfsdataset must lie within the caller's per-user ZFS prefix <master>/<uid>;
  • set_loginclass_rctl / clear_loginclass_rctlloginclass must be the caller's crate-<uid>.

Wired into dispatchPrivOpFromMap only; the HTTP/admin path (uid==0) is unchanged and host-wide by design. Per-user config registered at startup via setPerUserAuthzConfig (mirrors setUmbrellaConfig).

Deliberately deferred (documented in the doc as the remaining gap):

  • host-global verbs (iface / pf / ipfw / nat / epair) — cannot be pool-scoped; host-wide by design;
  • jid-scoped verbs (set_rctl, signal_jail, create_jail, set_jail_cpuset, devfs, …) — no request-borne owner; need a jid→owner registry (record operator uid at create_jail, check on each jid-keyed verb). Not in this PR.

Verification

  • make test-unit (kyua + libatf) locally on Linux: 1329/1329 passed, incl. the 12 new privops_authz_pure_test cases.
  • The daemon wiring (privops_handlers.cpp, main.cpp) compiles on FreeBSD only (libnv); it uses portable facilities and the existing setUmbrellaConfig pattern. Please confirm FreeBSD CI is green.

Heads-up for reviewers

  • Is the HTTP privops path intended to stay host-wide admin (it does here), or should it eventually carry pool/uid too?
  • The jid→owner registry is the natural follow-up to gate the jid-scoped verbs — happy to do it as a separate PR if you want it.

Test plan

  • FreeBSD CI green (daemon build + kyua).
  • Confirm the dataset/loginclass ownership semantics match intent.
  • Decide whether the jid→owner registry follow-up is wanted.

https://claude.ai/code/session_01X6t6tzVypHye5bDGLxzmZK

claude added 2 commits May 23, 2026 18:00
The per-name list missed test binaries whose sources live on other
branches, leaving stale ELF artifacts untracked. A tests/unit/*_test
glob covers all current and future test binaries; *_test.cpp sources
don't match and stay tracked.

https://claude.ai/code/session_01X6t6tzVypHye5bDGLxzmZK
Adds docs/trust-model.{md,uk.md} stating where cross-tenant isolation
is enforced and where it is not, accurate to 1.1.11 (rootless model):

- The privileged execution plane is no longer setuid crate(1) (removed
  in 1.0.0); it relocated into crated's privops surface (HTTP admin-only
  + libnv group-gated socket). That plane performs root ops with no
  per-resource pool/ownership check before dispatch, so it is a single
  trust domain — equivalent to the old setuid crate(1).
- Per-tenant isolation is enforced only on the pooled control plane:
  dedicated control sockets (getpeereid gid + pool ACL), bearer tokens
  (scope + pool ACL), and ws-console. Local Unix access to the main API
  remains a single trust domain.
- Notes that per-user namespacing (uid-derived paths/datasets/CIDRs)
  separates honest operators but is not an adversarial boundary on the
  privops path, since handlers take request args at face value.
- Restates the authorize-before-dispatch guardrail as the open gap for
  making privops safe for mutually-distrusting operators, plus
  deployment guidance.

Cross-links security-command-paths.{md,uk.md} to the trust model and
clarifies that command-path hardening now protects crated (the root
process that execs host tools).

https://claude.ai/code/session_01X6t6tzVypHye5bDGLxzmZK
@click0 click0 force-pushed the claude/analyze-test-coverage-nCOJW branch from 09b3b1c to ccf8ed0 Compare May 23, 2026 18:33
@click0 click0 changed the title docs: trust model and multi-tenant isolation boundaries docs: trust model and multi-tenant isolation (1.1.11) May 23, 2026
@click0 click0 merged commit 322b2ba into main May 24, 2026
2 checks passed
@click0 click0 changed the title docs: trust model and multi-tenant isolation (1.1.11) Trust model docs + privops authorize-before-dispatch (1.1.11) May 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants