add the agent option privateUsers, with the uid = gid assertion
This commit is contained in:
parent
3ca660e558
commit
1fd591d28b
2 changed files with 76 additions and 0 deletions
|
|
@ -90,8 +90,47 @@ in
|
|||
'';
|
||||
};
|
||||
|
||||
options.services.hyperhive.agent.user.privateUsers = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
example = true;
|
||||
description = ''
|
||||
Run this agent's container in its own user namespace. Off by default.
|
||||
|
||||
**Approving the config PR that sets this authorises a host-level
|
||||
change.** The swarm builder reads the option from the evaluated config
|
||||
into the agent's closure record, and the hive then starts the container
|
||||
with `--private-users=pick`, every hive bind mounted `owneridmap`, and
|
||||
its root idmapped. The first such start has `hive-priv`, as root, chown
|
||||
the agent's `state`, `harness`, `work` and `claude` dirs once to the
|
||||
host's data owner
|
||||
(`services.hyperhive.deploy.hive-controller.userns.dataOwner`). Nothing is
|
||||
deleted. Turning it off afterwards needs the dirs chowned back by hand.
|
||||
|
||||
Needs `user.uid` and `user.gid` set to the same number: nspawn maps a
|
||||
bind's gids with its uid map, so a group of another number has no
|
||||
mapping and writes fail. Start the agent once with the pin before
|
||||
turning this on; `hive-priv` refuses to move a `state` dir whose gid
|
||||
differs from its uid. An agent that holds `ManageRootAgent` should not
|
||||
set it, since the other agents' dirs it mounts are not remapped.
|
||||
'';
|
||||
};
|
||||
|
||||
config = {
|
||||
assertions = [
|
||||
{
|
||||
assertion =
|
||||
!config.services.hyperhive.agent.user.privateUsers
|
||||
|| (
|
||||
config.services.hyperhive.agent.user.uid != null
|
||||
&& config.services.hyperhive.agent.user.uid == config.services.hyperhive.agent.user.gid
|
||||
);
|
||||
message = ''
|
||||
services.hyperhive.agent.user.privateUsers needs services.hyperhive.agent.user.uid
|
||||
and services.hyperhive.agent.user.gid set to the same number: a user namespace
|
||||
maps a bind's gids with its uid map, so a group of another number has no mapping.
|
||||
'';
|
||||
}
|
||||
{
|
||||
assertion =
|
||||
config.services.hyperhive.agent.user.uid == null
|
||||
|
|
|
|||
|
|
@ -27,7 +27,44 @@ let
|
|||
.system.activationScripts.hive-agent-user-migrate.text;
|
||||
# The tmpfiles rules of the same named agent.
|
||||
rules = (agentWith { services.hyperhive.agent.user.name = "iris"; }).systemd.tmpfiles.rules;
|
||||
# `privateUsers` with the given pins; the agent name is the one the cases
|
||||
# above use, so only the pins differ.
|
||||
withUsers =
|
||||
user:
|
||||
agentWith {
|
||||
services.hyperhive.agent.user = {
|
||||
name = "iris";
|
||||
}
|
||||
// user;
|
||||
};
|
||||
# Whether the module refuses the config over the user-namespace pin.
|
||||
refusesPins =
|
||||
cfg: lib.any (a: !a.assertion && lib.hasInfix "privateUsers" a.message) cfg.assertions;
|
||||
cases = [
|
||||
{
|
||||
# The control is the matching pair: it passes, so each refusal is the
|
||||
# assertion's own and the default (off, no pins) passes too.
|
||||
name = "privateUsers needs a uid and a gid that are the same number";
|
||||
ok =
|
||||
!refusesPins (withUsers { })
|
||||
&& !refusesPins (withUsers {
|
||||
privateUsers = true;
|
||||
uid = 1000;
|
||||
gid = 1000;
|
||||
})
|
||||
&& refusesPins (withUsers {
|
||||
privateUsers = true;
|
||||
})
|
||||
&& refusesPins (withUsers {
|
||||
privateUsers = true;
|
||||
uid = 1000;
|
||||
})
|
||||
&& refusesPins (withUsers {
|
||||
privateUsers = true;
|
||||
uid = 1000;
|
||||
gid = 1001;
|
||||
});
|
||||
}
|
||||
{
|
||||
# hive-c0re creates the host work dir as hive-core; this activation is
|
||||
# the only thing that lets the agent write to it.
|
||||
|
|
|
|||
Loading…
Reference in a new issue