nix(#702): add hive-priv systemd socket + service units

Nix side of the hive-priv phase 1 work. hive-priv is socket-activated
exclusively: systemd holds /run/hive/priv.sock (mode 0660) and starts
the service on first connection. LISTEN_FDS + LISTEN_PID are passed;
hive-priv reads them in socket_listener() to accept the pre-bound fd.

Phase 2 note (comment in file): when hive-c0re drops to a non-root
user, add SocketGroup = hive-core to the socket unit so the
unprivileged caller can still connect. No code change needed in
hive-priv itself.
This commit is contained in:
atlas 2026-06-01 17:08:15 +02:00 committed by mara
commit 84f5c2722e

View file

@ -100,23 +100,27 @@ in
# `identity.rs::peers()` + the dashboard's `peer_hives` state field
# (feeds the P33RS dashboard tab).
options.services.hyperhive.swarm.peers = lib.mkOption {
type = lib.types.attrsOf (lib.types.submodule {
options = {
certFingerprint = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
example = "sha256:abc123...";
description = ''
Expected TLS certificate fingerprint for this peer's HTTPS
endpoint. Null = trust the system CA bundle (for Let's
Encrypt peers). Set to pin a self-signed cert.
'';
type = lib.types.attrsOf (
lib.types.submodule {
options = {
certFingerprint = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
example = "sha256:abc123...";
description = ''
Expected TLS certificate fingerprint for this peer's HTTPS
endpoint. Null = trust the system CA bundle (for Let's
Encrypt peers). Set to pin a self-signed cert.
'';
};
};
};
});
}
);
default = { };
example = {
"lab.example.com" = { certFingerprint = "sha256:abc123"; };
"lab.example.com" = {
certFingerprint = "sha256:abc123";
};
"edge.corp" = { };
};
description = ''
@ -396,5 +400,53 @@ in
DirectoryMode = "0750";
};
};
# Socket unit for hive-priv — the narrow root helper that executes
# privileged operations on behalf of hive-c0re. Systemd creates and
# holds `/run/hive/priv.sock` before the first connection arrives.
#
# Mode 0660 root:root is correct for phase 1 (hive-c0re still runs as
# root and is the only caller). Phase 2 (privsep: hive-c0re drops to a
# non-root user) will add `SocketGroup = hive-core` so the unprivileged
# hive-c0re process can still connect.
systemd.sockets.hive-priv = {
description = "hive-priv privileged helper socket";
wantedBy = [ "sockets.target" ];
socketConfig = {
ListenStream = "/run/hive/priv.sock";
SocketMode = "0660";
# Create /run/hive/ if absent; 0755 so future unprivileged callers
# can traverse into it to reach the socket.
DirectoryMode = "0755";
};
};
# Service unit for hive-priv. Runs as root — it genuinely needs root to
# invoke `nixos-container`, write `/etc/nixos-containers/`, write
# systemd drop-ins in `/run/systemd/system/`, and call `chown(2)`.
# Every request is validated against a strict container-name allowlist
# inside the binary; the attack surface is narrow by design.
#
# Socket-activated: systemd starts hive-priv on the first connection
# (no earlier). LISTEN_FDS + LISTEN_PID are set by systemd; hive-priv
# reads them to accept the pre-bound socket fd instead of binding its
# own.
systemd.services.hive-priv = {
description = "hive-priv privileged helper";
# No wantedBy — socket-activated exclusively. The socket unit is the
# entry point; systemd starts this service on first connect.
after = [ "hive-priv.socket" ];
requires = [ "hive-priv.socket" ];
serviceConfig = {
ExecStart = "${cfg.package}/bin/hive-priv";
Type = "simple";
User = "root";
PrivateTmp = true;
ProtectHome = true;
# hive-priv needs to write to /etc/nixos-containers/ and
# /run/systemd/system/ — "strict" would block both.
ProtectSystem = "false";
};
};
};
}