run opted-in agents in a user namespace with a dedicated data owner
This commit is contained in:
parent
5ae0205e1d
commit
bfdf603d12
2 changed files with 828 additions and 21 deletions
|
|
@ -10,6 +10,8 @@
|
|||
let
|
||||
controllerCfg = config.services.hyperhive.deploy.hive-controller;
|
||||
rootfsCheck = import ./lib/container-rootfs-check.nix { inherit config; };
|
||||
userns = config.services.hyperhive.userns;
|
||||
dataOwnerProblems = import ./lib/userns-data-owner.nix { inherit lib; };
|
||||
in
|
||||
{
|
||||
# The platform containers run their warn-only root check by this path
|
||||
|
|
@ -24,12 +26,60 @@ in
|
|||
}
|
||||
];
|
||||
|
||||
options.services.hyperhive.userns = {
|
||||
agents = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.str;
|
||||
default = [ ];
|
||||
example = [ "alice" ];
|
||||
description = ''
|
||||
Agents that run in their own user namespace (`--private-users=pick`,
|
||||
root idmapped). Every hive bind into such a container is mounted
|
||||
`owneridmap`, so its data on disk belongs to `dataOwner`, which the
|
||||
agent user is inside. Listing an agent moves its `state`, `harness`,
|
||||
`work` and `claude` dirs to `dataOwner` once, at its next start with
|
||||
the container stopped. Take the agent off the list only after chowning
|
||||
those dirs back and removing `/var/lib/hive-priv/userns/<agent>`;
|
||||
until then it will not start.
|
||||
'';
|
||||
};
|
||||
|
||||
dataOwner = lib.mkOption {
|
||||
type = lib.types.ints.u32;
|
||||
default = 60000;
|
||||
description = ''
|
||||
The host uid and gid that own a namespaced agent's data on disk. One
|
||||
number for both: nspawn copies the uid map for gids, so the gid
|
||||
cannot differ. It must be no host user or group, outside the normal and
|
||||
system users (below 30000) and the dynamic users (61184 and above), and
|
||||
below the range `pick` allocates from (524288 and above), so no
|
||||
container's range covers it. Changing it after an agent has moved is
|
||||
refused at that agent's next start.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
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 - -" ];
|
||||
|
||||
assertions = [
|
||||
(
|
||||
let
|
||||
problems = dataOwnerProblems {
|
||||
owner = userns.dataOwner;
|
||||
uids = lib.filter (uid: uid != null) (lib.mapAttrsToList (_: u: u.uid) config.users.users);
|
||||
gids = lib.filter (gid: gid != null) (lib.mapAttrsToList (_: g: g.gid) config.users.groups);
|
||||
};
|
||||
in
|
||||
{
|
||||
assertion = userns.agents == [ ] || problems == [ ];
|
||||
message = "services.hyperhive.userns.dataOwner: ${lib.concatStringsSep "; " problems}";
|
||||
}
|
||||
)
|
||||
];
|
||||
|
||||
# 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.
|
||||
|
|
@ -93,6 +143,10 @@ in
|
|||
# routing so the closure `RealiseClosure` downloads is in the same
|
||||
# store the container is then pointed at.
|
||||
NIX_REMOTE = "daemon";
|
||||
# Who is namespaced, and the owner their data moves to: set by the
|
||||
# operator in the unit, so only root decides.
|
||||
HIVE_PRIV_USERNS_AGENTS = lib.concatStringsSep " " userns.agents;
|
||||
HIVE_PRIV_USERNS_DATA_OWNER = toString userns.dataOwner;
|
||||
};
|
||||
serviceConfig = {
|
||||
ExecStart = "${controllerCfg.package}/bin/hive-priv";
|
||||
|
|
|
|||
17
nix/host-modules/lib/userns-data-owner.nix
Normal file
17
nix/host-modules/lib/userns-data-owner.nix
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
# What is wrong with a uid:gid chosen as the on-disk owner of a user-namespaced
|
||||
# agent's data. Empty when it is fine.
|
||||
{ lib }:
|
||||
{
|
||||
owner,
|
||||
# Every uid and gid the host defines, from `users.users` and `users.groups`.
|
||||
uids,
|
||||
gids,
|
||||
}:
|
||||
lib.optional (
|
||||
owner < 30000
|
||||
) "${toString owner} is in the range NixOS gives normal users and system users (below 30000)"
|
||||
++
|
||||
lib.optional (owner >= 61184)
|
||||
"${toString owner} is in the range systemd allocates dynamic users from (61184 and above), or is `nobody`"
|
||||
++ lib.optional (builtins.elem owner uids) "${toString owner} is the uid of a user the host defines"
|
||||
++ lib.optional (builtins.elem owner gids) "${toString owner} is the gid of a group the host defines"
|
||||
Loading…
Reference in a new issue