From bf77b722178936a86a561076071c287999c19a64 Mon Sep 17 00:00:00 2001 From: atlas Date: Fri, 13 Nov 2026 07:37:18 +0100 Subject: [PATCH] hive-priv: create the nixos-containers directories at boot --- .../trust-boundary/hive-c0re-privilege.md | 5 +++- nix/host-modules/hive-priv.nix | 15 ++++++++--- nix/module-eval/core-toggle.nix | 27 +++++++++++++++++++ 3 files changed, 42 insertions(+), 5 deletions(-) diff --git a/docs/4-implementation/trust-boundary/hive-c0re-privilege.md b/docs/4-implementation/trust-boundary/hive-c0re-privilege.md index 732fd1606..f0e5f5c31 100644 --- a/docs/4-implementation/trust-boundary/hive-c0re-privilege.md +++ b/docs/4-implementation/trust-boundary/hive-c0re-privilege.md @@ -163,7 +163,10 @@ above for how the hard-coded unit name scopes its access instead. **Socket-activated** — systemd starts `hive-priv` on the first incoming connection (`LISTEN_FDS=1`); it's not running between calls. The `ProtectSystem=strict` + `ReadWritePaths` sandbox limits filesystem -writes to only the paths `hive-priv` legitimately needs. The unit also sets +writes to only the paths `hive-priv` legitimately needs. A `ReadWritePaths` +entry that's missing fails the unit with `226/NAMESPACE`, so tmpfiles rules +create `/etc/nixos-containers`, `/var/lib/nixos-containers` and +`/run/hive-agent` on a hive that has no declared container yet. The unit also sets `LockPersonality`, `RestrictRealtime`, `ProtectKernelLogs` and `ProtectKernelModules`: it changes no execution domain, schedules nothing in real time, reads no kernel log, and loads no module. It sets none of diff --git a/nix/host-modules/hive-priv.nix b/nix/host-modules/hive-priv.nix index f4d9e5e83..e441599a2 100644 --- a/nix/host-modules/hive-priv.nix +++ b/nix/host-modules/hive-priv.nix @@ -43,10 +43,17 @@ in }; config = lib.mkIf config.services.hyperhive.deploy.hive-controller.enable { - # The parent of every agent socket dir. hive-priv is its only writer - # (`EnsureAgentSocketDir`), and its unit lists it in `ReadWritePaths`, - # which fails the unit when the path is missing. - systemd.tmpfiles.rules = [ "d /run/hive-agent 0755 root root - -" ]; + # Every directory hive-priv writes to that nothing else creates before it + # starts. Its unit lists them in `ReadWritePaths`, which fails the unit + # (226/NAMESPACE) when a path is missing: + # /run/hive-agent parent of every agent socket dir + # /etc/nixos-containers exists only once a container is declared + # /var/lib/nixos-containers same; 0700 is the mode nixos-container gives it + systemd.tmpfiles.rules = [ + "d /run/hive-agent 0755 root root - -" + "d /etc/nixos-containers 0755 root root - -" + "d /var/lib/nixos-containers 0700 root root - -" + ]; assertions = [ ( diff --git a/nix/module-eval/core-toggle.nix b/nix/module-eval/core-toggle.nix index 573644db7..af041f356 100644 --- a/nix/module-eval/core-toggle.nix +++ b/nix/module-eval/core-toggle.nix @@ -2339,6 +2339,33 @@ let && (sc.ProtectKernelLogs or false) && (sc.ProtectKernelModules or false); } + { + # A path in `ReadWritePaths` without a `-` prefix must exist or the unit + # fails with 226/NAMESPACE, and a hive with no declared container has no + # /etc/nixos-containers. Each required-to-exist path is created by a + # tmpfiles rule. Presence control: the other rule in the list is found by + # the same lookup; the toggled-off fixture has no hive-priv and no rule. + name = "hive-priv's ReadWritePaths directories are all created by tmpfiles rules"; + ok = + let + rules = allLocal.systemd.tmpfiles.rules; + rw = allLocal.systemd.services.hive-priv.serviceConfig.ReadWritePaths; + created = [ + "/run/hive-agent" + "/etc/nixos-containers" + "/var/lib/nixos-containers" + ]; + hasRule = p: lib.any (r: lib.hasPrefix "d ${p} " r) rules; + in + lib.all (p: lib.elem p rw && hasRule p) created + && lib.elem "d /etc/nixos-containers 0755 root root - -" rules + && lib.elem "d /var/lib/nixos-containers 0700 root root - -" rules + && !(lib.any (p: lib.hasPrefix "-" p) rw) + && hasRule "/var/lib/hyperhive/agents" + && !(lib.any ( + r: lib.hasPrefix "d /etc/nixos-containers " r + ) centralToggleOff.systemd.tmpfiles.rules); + } { # Hyperhive's own declarative containers each run the root check from # their own unit, warn-only. Every container in these fixtures is one of