document the user namespace opt-in
This commit is contained in:
parent
bfdf603d12
commit
da098b1a50
4 changed files with 49 additions and 4 deletions
|
|
@ -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
|
in ephemeral mode without failing. The journal vanishes at stop, and with it
|
||||||
the lines written after the last push.
|
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
|
### Cross-agent access to state
|
||||||
|
|
||||||
An agent holding the `manage_root_agent` permission gets every other
|
An agent holding the `manage_root_agent` permission gets every other
|
||||||
|
|
|
||||||
|
|
@ -119,8 +119,11 @@ both sockets are `0666`, which is all a dialer needs.
|
||||||
hive-priv creates the dir when missing (`EnsureAgentSocketDir`, `0751
|
hive-priv creates the dir when missing (`EnsureAgentSocketDir`, `0751
|
||||||
root:root`, no symlink followed) and leaves an existing one alone. The
|
root:root`, no symlink followed) and leaves an existing one alone. The
|
||||||
container's own activation (`hive-agent-user-migrate`) then chowns it to
|
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
|
the agent user and sets `0751`. Without a user namespace
|
||||||
that uid is the host inode's owner. Don't add a host-side chown or chmod:
|
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,
|
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.
|
the dir is `0751 root`: nothing but root can plant a socket in it.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -261,8 +261,8 @@ known operations; there is no arbitrary command pass-through:
|
||||||
| `DestroyContainer` | `nixos-container destroy <name>` |
|
| `DestroyContainer` | `nixos-container destroy <name>` |
|
||||||
| `ListContainers` | `nixos-container list` |
|
| `ListContainers` | `nixos-container list` |
|
||||||
| `ReloadGatewayNginx` | `systemctl reload/start/reset-failed nginx` (host unit; the unit name is hard-coded, not a parameter) |
|
| `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 |
|
| `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 |
|
| `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 |
|
| `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 |
|
| `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` |
|
| `DaemonReload` | `systemctl daemon-reload` |
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,11 @@ in
|
||||||
the container stopped. Take the agent off the list only after chowning
|
the container stopped. Take the agent off the list only after chowning
|
||||||
those dirs back and removing `/var/lib/hive-priv/userns/<agent>`;
|
those dirs back and removing `/var/lib/hive-priv/userns/<agent>`;
|
||||||
until then it will not start.
|
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.
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue