Watch
0
0
Fork
You've already forked hyperhive
0

document the user namespace opt-in

This commit is contained in:
atlas 2026-11-01 13:53:41 +01:00 • committed by mara
commit da098b1a50
4 changed files with 49 additions and 4 deletions

View file

@ -456,6 +456,43 @@ journal link to the host takes part; nspawn skips `--link-journal=try-guest`
in ephemeral mode without failing. The journal vanishes at stop, and with it
the lines written after the last push.
### User namespace (opt-in, per agent)
An agent named in `services.hyperhive.userns.agents` runs in its own user
namespace. Every other agent keeps the host's uids. The operator sets the list
in the host config; hive-priv reads it from its unit's environment, so an agent
can't switch it on or off.
- **On disk the data belongs to one host uid:gid**, `services.hyperhive.userns.dataOwner`
(default `60000:60000`). Inside the container that owner shows as the agent
user. hive-priv mounts the agent's `state/`, `harness/`, `work/` and
`claude/` dirs and its socket dir with `owneridmap`, and every other bind too
(config, `/knowledge`, the runtime dir), whose owner shows as root inside.
- **The container starts with `--private-users=pick --private-users-ownership=map`.**
nspawn idmaps the root at mount time, so a different range for `pick` never
walks or chowns it.
- **The move happens once.** The first `WriteNspawnFlags` that finds a listed
agent's container stopped chowns those four dirs to `dataOwner` (ownership
only; it removes or copies nothing), writes
`/var/lib/hive-priv/userns/<name>`, and creates the mount points in the root
template owned by the agent user. A later request with the marker present
chowns nothing. A running container waits for its next stop. The socket dir
is on a tmpfs, so `EnsureAgentSocketDir` hands it to `dataOwner` at every
start.
- **The agent's gid must equal its uid.** `owneridmap` maps the gid of a bind
with the uid map, so a group other than the uid's has no mapping and writes
fail. Pin `services.hyperhive.agent.user.uid` and `.gid` to one number in the
agent's config before listing it.
- **Leaving the list is manual.** hive-priv refuses to start an agent whose
marker exists but who is no longer on the list, and one whose `dataOwner`
changed. Chown the four dirs back, then delete the marker.
The pause marker needs nothing extra: hive-priv gives it the owner of the
`harness/` dir. hive-c0re still creates agent dirs and new subvolumes as
`hive-core`, and only the first namespaced start chowns them. A dir created
after the move, such as a recreated subvolume, stays `hive-core`'s and the
agent can't write to it.
### Cross-agent access to state
An agent holding the `manage_root_agent` permission gets every other

View file

@ -119,8 +119,11 @@ both sockets are `0666`, which is all a dialer needs.
hive-priv creates the dir when missing (`EnsureAgentSocketDir`, `0751
root:root`, no symlink followed) and leaves an existing one alone. The
container's own activation (`hive-agent-user-migrate`) then chowns it to
the agent user and sets `0751`. The container has no user namespace, so
that uid is the host inode's owner. Don't add a host-side chown or chmod:
the agent user and sets `0751`. Without a user namespace
that uid is the host inode's owner. (A namespaced agent, see "User namespace" in
`docs/agent-lifecycle/persistence.md`, gets the dir from `EnsureAgentSocketDir`
owned by its data owner instead, because the container can't chown through
the id map.) Don't add another host-side chown or chmod:
two owners of one path revert each other. Until the container activates,
the dir is `0751 root`: nothing but root can plant a socket in it.

View file

@ -261,8 +261,8 @@ known operations; there is no arbitrary command pass-through:
| `DestroyContainer` | `nixos-container destroy <name>` |
| `ListContainers` | `nixos-container list` |
| `ReloadGatewayNginx` | `systemctl reload/start/reset-failed nginx` (host unit; the unit name is hard-coded, not a parameter) |
| `WriteNspawnFlags` | write `/etc/nixos-containers/<container>.conf` (bind-mount list, network isolation vars, and `--ephemeral` after the root's salvage). hive-priv first copies a stopped container's root to the agent's `work/`, then renames it aside (it deletes nothing), following no link the container could have planted |
| `EnsureAgentSocketDir` | `mkdirat` `/run/hive-agent/<name>` `0751 root:root` under an `O_NOFOLLOW` parent fd; leaves an existing directory alone, refuses anything else |
| `WriteNspawnFlags` | write `/etc/nixos-containers/<container>.conf` (bind-mount list, network isolation vars, and `--ephemeral` after the root's salvage). hive-priv first copies a stopped container's root to the agent's `work/`, then renames it aside (it deletes nothing), following no link the container could have planted; for an agent in `services.hyperhive.userns.agents` it also adds `--private-users=pick`, mounts every bind `owneridmap`, and once chowns the agent's `state`, `harness`, `work` and `claude` dirs (never `config`, following no link) to the data owner |
| `EnsureAgentSocketDir` | `mkdirat` `/run/hive-agent/<name>` `0751 root:root` under an `O_NOFOLLOW` parent fd; leaves an existing directory alone, refuses anything else; for an agent in `services.hyperhive.userns.agents` it then chowns the dir to the data owner |
| `WriteResourceLimits` | write `CPUQuota=`/`MemoryMax=`/`CPUWeight=`/`IOWeight=` systemd drop-in for agent container, with an `ExecStartPre=` that runs `hive-priv check-rootfs <container>` (no-follow check of the root paths the start script touches; see persistence.md), and a GC root on the build it names |
| `RemoveServiceDropin` | remove `container@<name>.service.d/` drop-in on destroy, and the GC root of the hive-priv build it named |
| `DaemonReload` | `systemctl daemon-reload` |

View file

@ -40,6 +40,11 @@ in
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.
'';
};