The handbook's sections were agent-lifecycle, integrations, swarm, tools, trust-boundary and web-ui, which do not follow how the system is built, so a reader could not tell which layer a page is about. Its pages now sit in swarm/, hives/ and agents/, with the accounts an agent can hold outside the system in external-accounts/. - swarm/: forge, matrix and the knowledge repo join the swarm pages, and so does the swarmctl reference. - hives/: the dashboard, hivectl and its reference, and the operator/agent boundary, with a README that takes over the old web-ui and tools pages. - agents/: the roster, persistence, config changes, the agent's own web page (was web-ui/agent) and the security model, with a README. - external-accounts/: GitHub and GitLab. Pages are moved whole; none is split yet. Every reference follows: relative links in the docs, `docs/2-handbook/...` paths in code comments, the nix checks that regenerate the CLI references and the ignore list, and docs/README.md, whose handbook section is now by layer.
72 lines
3.7 KiB
Markdown
72 lines
3.7 KiB
Markdown
# Socket mechanics behind the operator/agent boundary
|
|
|
|
How systemd activates `hive-priv` and how the per-agent socket directory
|
|
gets its owner and mode, the two pieces of the boundary described in
|
|
[the operator/agent boundary](../../2-handbook/hives/boundary.md).
|
|
|
|
## hive-priv socket activation
|
|
|
|
`hive-priv` is **always** socket-activated by the `hive-priv.socket`
|
|
systemd unit. The unit binds `/run/hive/priv.sock` with
|
|
`SocketGroup=hive-core` and mode `0660` and passes the ready listener
|
|
to the helper as fd 3 (`LISTEN_FDS`). The helper requires this and
|
|
bails if it isn't socket-activated.
|
|
|
|
⚠️ Intentionally, no self-bind fallback exists: if `hive-priv` bound
|
|
the socket itself, it would create the file owned by root's primary
|
|
group rather than `hive-core`, and a `hive-core` client couldn't
|
|
connect the way the socket unit's `SocketGroup` grant intends.
|
|
Requiring socket activation everywhere keeps dev and prod on the
|
|
exact same path, so the group grant always holds.
|
|
|
|
## the per-agent socket dir
|
|
|
|
**Three principals that share no group** reach `/run/hive-agent/<name>/`,
|
|
which is why its mode is what it's:
|
|
|
|
| principal | reaches | needs |
|
|
| ------------------- | ---------------------------------------- | ------------ |
|
|
| the agent's harness | binds + unlinks `agent.sock`, `web.sock` | owner, `rwx` |
|
|
| `hive-c0re` | dials `agent.sock` (todo wakes) | traverse |
|
|
| the gateway's nginx | dials `web.sock` | traverse |
|
|
|
|
The last two land in "other," so the dir is **`0751`, owned by the
|
|
agent's container uid/gid** — `o=--x` is traverse without listing, and
|
|
both sockets are `0666`, which is all a dialer needs.
|
|
|
|
<!-- vale write-good.Passive = NO -->
|
|
|
|
**One mechanism creates it, one sets its owner.** Before every start,
|
|
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`. Without a user namespace
|
|
that uid is the host inode's owner. (A namespaced agent, see "User namespace" in
|
|
`docs/2-handbook/agents/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.
|
|
|
|
<!-- vale write-good.Passive = YES -->
|
|
|
|
The mode is load-bearing, not cosmetic. Write permission on a
|
|
_directory_ is what confers the right to unlink its entries, whoever owns
|
|
them, and the sticky bit is the only thing that would restrain that (it
|
|
isn't set here). A world-writable socket dir therefore lets anything
|
|
able to reach the path delete an agent's socket and bind its own. On the
|
|
host, any non-root process whose `/run` is writable can do that, such as
|
|
a login session or dnsmasq. nginx only dials: `ProtectSystem=strict` makes its view
|
|
of `/run` read-only. Dropping `o=w` removes that permission rather than
|
|
qualifying it.
|
|
|
|
⚠️ **The gateway's nginx and dnsmasq are host services, next to
|
|
`hive-c0re`** (see `docs/4-implementation/networking/gateway.md`) — there is no namespace between
|
|
them and the rest of the host. That costs no network isolation: nginx
|
|
binds the host's `:80`/`:443` and reaches `localhost` upstreams, which
|
|
would require opening a netns anyway.
|
|
🔑 It does mean nothing _implicitly_ scopes the privileged reload verb —
|
|
see [`docs/4-implementation/trust-boundary/hive-c0re-privilege.md`](hive-c0re-privilege.md) for
|
|
how `PrivRequest::ReloadGatewayNginx`'s containment works.
|
|
|
|
⚠️ This dir has exactly one writer, so it needs no world write at all.
|