read privateUsers from the closure record at start, and move the data owner option under hive-controller
This commit is contained in:
parent
1fd591d28b
commit
f5a609911c
3 changed files with 208 additions and 97 deletions
|
|
@ -10,7 +10,7 @@
|
|||
let
|
||||
controllerCfg = config.services.hyperhive.deploy.hive-controller;
|
||||
rootfsCheck = import ./lib/container-rootfs-check.nix { inherit config; };
|
||||
userns = config.services.hyperhive.userns;
|
||||
userns = controllerCfg.userns;
|
||||
dataOwnerProblems = import ./lib/userns-data-owner.nix { inherit lib; };
|
||||
in
|
||||
{
|
||||
|
|
@ -26,41 +26,20 @@ 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.
|
||||
|
||||
The agent's own config must pin `services.hyperhive.agent.user.uid`
|
||||
and `.gid` to one number: nspawn maps a bind's gids with its uid map.
|
||||
Keep an agent that holds `ManageRootAgent` off the list, since the
|
||||
other agents' dirs it mounts are not owned by its data owner.
|
||||
'';
|
||||
};
|
||||
|
||||
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.
|
||||
'';
|
||||
};
|
||||
options.services.hyperhive.deploy.hive-controller.userns.dataOwner = lib.mkOption {
|
||||
type = lib.types.ints.u32;
|
||||
default = 60000;
|
||||
description = ''
|
||||
The host uid and gid that own the data of an agent running in its own
|
||||
user namespace (the agent sets `services.hyperhive.agent.user.privateUsers`).
|
||||
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. `hive-priv` chowns such an agent's data to
|
||||
it once. 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 {
|
||||
|
|
@ -79,8 +58,8 @@ in
|
|||
};
|
||||
in
|
||||
{
|
||||
assertion = userns.agents == [ ] || problems == [ ];
|
||||
message = "services.hyperhive.userns.dataOwner: ${lib.concatStringsSep "; " problems}";
|
||||
assertion = problems == [ ];
|
||||
message = "services.hyperhive.deploy.hive-controller.userns.dataOwner: ${lib.concatStringsSep "; " problems}";
|
||||
}
|
||||
)
|
||||
];
|
||||
|
|
@ -148,9 +127,8 @@ 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;
|
||||
# The owner a namespaced agent's data moves to. Which agents are
|
||||
# namespaced is not set here: hive-c0re sends it with each start.
|
||||
HIVE_PRIV_USERNS_DATA_OWNER = toString userns.dataOwner;
|
||||
};
|
||||
serviceConfig = {
|
||||
|
|
|
|||
|
|
@ -178,15 +178,15 @@ let
|
|||
{
|
||||
# Read back from the rendered unit and assertion list: a misspelt
|
||||
# variable or a dropped assertion fails here.
|
||||
name = "hive-priv carries the userns agents and data owner, and the owner assertion passes";
|
||||
name = "hive-priv carries the userns data owner, no agent list, and the owner assertion passes";
|
||||
ok =
|
||||
let
|
||||
e = allLocal.systemd.services.hive-priv.environment;
|
||||
in
|
||||
e.HIVE_PRIV_USERNS_AGENTS == ""
|
||||
!(e ? HIVE_PRIV_USERNS_AGENTS)
|
||||
&& e.HIVE_PRIV_USERNS_DATA_OWNER == "60000"
|
||||
&& lib.any (
|
||||
a: lib.hasPrefix "services.hyperhive.userns.dataOwner" a.message && a.assertion
|
||||
a: lib.hasInfix "hive-controller.userns.dataOwner" a.message && a.assertion
|
||||
) allLocal.assertions;
|
||||
}
|
||||
{
|
||||
|
|
|
|||
|
|
@ -18,10 +18,12 @@
|
|||
# leaves the link's target untouched, and the start goes through once the
|
||||
# link is gone.
|
||||
#
|
||||
# - an agent listed in `services.hyperhive.userns.agents` starts in its own
|
||||
# - an agent whose config sets `services.hyperhive.agent.user.privateUsers`
|
||||
# (hive-c0re sends the flag from the closure record; the test sends it)
|
||||
# starts in its own
|
||||
# user namespace with state it already had, owned by uid 1000 on disk: the
|
||||
# data moves once to `dataOwner`, the agent user reads and writes it as
|
||||
# itself, the unlisted agent's conf is untouched, and a second
|
||||
# itself, the other agent's conf (no flag sent) is untouched, and a second
|
||||
# `WriteNspawnFlags` moves nothing.
|
||||
#
|
||||
# The agent's journal forwarder itself (an OTLP push to the hive's collector) is
|
||||
|
|
@ -118,7 +120,6 @@ pkgs.testers.runNixOSTest {
|
|||
prefixLength = 24;
|
||||
}
|
||||
];
|
||||
services.hyperhive.userns.agents = [ nsAgent ];
|
||||
systemd.tmpfiles.rules =
|
||||
map (dir: "d ${agentDir}/${dir} 0755 root root - -") hostDirs
|
||||
# What the namespaced agent has from before: dirs its user owned.
|
||||
|
|
@ -154,6 +155,7 @@ pkgs.testers.runNixOSTest {
|
|||
name = nsAgent;
|
||||
uid = 1000;
|
||||
gid = 1000;
|
||||
privateUsers = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
|
@ -312,19 +314,20 @@ pkgs.testers.runNixOSTest {
|
|||
host.succeed(f"rm {ROOT}/root && mkdir -m 0700 {ROOT}/root")
|
||||
boot()
|
||||
|
||||
with subtest("a listed agent starts in its own user namespace with the state it had"):
|
||||
with subtest("an agent whose config sets privateUsers starts in its own user namespace with the state it had"):
|
||||
NS = "${nsMachine}"
|
||||
NS_AGENT = "${nsAgent}"
|
||||
NS_DIR = "${nsDir}"
|
||||
NS_BINDS = json.loads(${builtins.toJSON (builtins.toJSON nsBinds)})
|
||||
host.succeed(f"echo before > {NS_DIR}/state/pre.txt && chown 1000:1000 {NS_DIR}/state/pre.txt")
|
||||
host.succeed(f"stat -c %u:%g {NS_DIR}/state/pre.txt | grep -x 1000:1000")
|
||||
priv_request(host, {"op": "ensure_agent_socket_dir", "name": NS_AGENT})
|
||||
priv_request(host, {"op": "ensure_agent_socket_dir", "name": NS_AGENT, "private_users": True})
|
||||
write = {
|
||||
"op": "write_nspawn_flags",
|
||||
"container": NS,
|
||||
"binds": NS_BINDS,
|
||||
"isolation": {"bridge": "${bridge}", "gateway_ip": "${gateway}"},
|
||||
"private_users": True,
|
||||
}
|
||||
priv_request(host, write)
|
||||
ns_conf = host.succeed(f"cat /etc/nixos-containers/{NS}.conf")
|
||||
|
|
@ -332,7 +335,7 @@ pkgs.testers.runNixOSTest {
|
|||
assert "--private-users-ownership=map" in ns_conf, ns_conf
|
||||
# Every bind, the read-only config included, is mounted owneridmap.
|
||||
assert ns_conf.count(":owneridmap") == len(NS_BINDS), ns_conf
|
||||
# The unlisted agent's conf has none of it: the control that the lookups
|
||||
# The other agent's conf has none of it: the control that the lookups
|
||||
# above find these strings where they are.
|
||||
assert "owneridmap" not in conf() and "PRIVATE_USERS" not in conf(), conf()
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue