Watch
0
0
Fork
You've already forked hyperhive
0
hyperhive/hive-priv/README.md
atlas 4590b04cac remove the hive's local build of agent closures
A rebuild or create downloads the closure the swarm built and applies it; with none to deploy it fails and changes nothing. hive-priv takes a store path instead of an internal/meta commit and builds nothing.
2026-10-29 22:17:07 +01:00

3.2 KiB

hive-priv

The minimal root privileged-helper for hive-c0re. It runs as root and exposes a narrow unix socket at /run/hive/priv.sock that accepts PrivRequest JSON lines and performs only the handful of operations that genuinely require root — bind-mount edits, nsenter into a container, btrfs subvolume ops. All coordination logic (broker, HTTP, scheduling) stays in the unprivileged hive-c0re process, which delegates here.

Why it exists

Privsep. hive-c0re runs as the unprivileged hive-core user so a bug or a prompt-injection in the large daemon can't directly wield root. The few root operations it needs are funnelled through this small, auditable helper instead. See docs/trust-boundary/boundary.md and docs/trust-boundary/security.md for the privilege boundary.

Security model

  • Strict allowlist. hive-priv checks every request against a container-name allowlist before any filesystem or process operation, and accepts only names matching the hive convention (h-*, the manager container included).
  • No pass-through. Every PrivRequest variant maps to a single known operation; there is no arbitrary-command escape hatch.
  • Socket-activated, always. systemd binds /run/hive/priv.sock (SocketGroup=hive-core, 0660) and passes the listener as fd 3 (LISTEN_FDS); the helper requires this and has no self-bind fallback, so dev and prod take the identical path and the group grant always holds.

The wire contract (PrivRequest / response types) lives in the separate hive-priv-sock crate so this root binary depends on just the protocol shapes, not the whole daemon-shared crate.

Layout

src/main.rs holds the socket loop and the dispatch table, one arm per PrivRequest variant. Each op family, with its validation, has its own module: container (lifecycle), container_config (nspawn flags, systemd drop-ins), storage (btrfs subvolumes, quotas, snapshots, disk measurement), files (pause marker, socket dir, legacy tmpfiles) and infra (gateway nginx, forge admin, infra containers). Helpers more than one family calls live in shared. Each module's //! header lists its ops.

Implementation notes

Container toplevels (create/update)

container_toplevel_action (in src/container.rs) applies a closure that is already in the store; hive-priv builds nothing. hive-c0re names the closure (toplevel on CreateContainer / UpdateContainer), downloaded beforehand with RealiseClosure, and hive-priv accepts it only in the shape of a store path.

create passes the path to nixos-container create --system-path, so nixos-container's own buildFlake() never runs. Without the flag, it builds to a hardcoded relative .tmp out-link: $systemPath stays undef and "$systemPath.tmp" interpolates to the bare string .tmp in the caller's cwd. With services.hyperhive.deploy.hive-controller.buildSlots > 1, two concurrent calls could share that one .tmp, and one agent's container got another agent's closure.

update points the container's profile at the path with nix-env --set and reloads the unit if it runs, because nixos-container update dies on its version probe before it honors --system-path.