docs: move the socket-dir ownership rule to boundary.md

It was written into gateway.md, which only owns half the story: that
doc describes the web.sock path, and before this branch it never
mentioned agent.sock at all. Putting a rule shared by hive-c0re, the
harness and nginx inside the gateway walkthrough means someone asking
"why can't c0re dial agent.sock" has no reason to look there.

boundary.md already covers who may touch what across the trust boundary
-- including the sibling case of hive-priv's socket getting its mode
from the unit rather than the process -- so the rule lives there now,
with the three principals as a table. gateway.md keeps a two-line note
about the one fact it needs (nginx traverses via o=--x) and links out.
This commit is contained in:
atlas 2026-08-04 00:45:58 +02:00 committed by mara
commit 289db00321
2 changed files with 39 additions and 17 deletions

View file

@ -94,6 +94,40 @@ couldn't connect the way the socket unit's `SocketGroup` grant
intends. Requiring socket activation everywhere means dev and prod
take the exact same path and the group grant always holds.
### the per-agent socket dir
`/run/hive-agent/<name>/` is shared by **three principals that share no
group**, which is why its mode is what it is:
| 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.
**Ownership is declared, not repaired.** The tmpfiles.d entry written by
`SyncAgentTmpfiles` names the uid/gid directly. Do not add a chown
alongside it: `d` re-applies on every boot *and* every agent
spawn/destroy, so ownership set afterwards is reverted the next time any
agent changes — which is exactly how this dir spent a long time at
`0777 root root` while a privileged chown appeared to be fixing it.
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
is not set here). A world-writable socket dir therefore lets anything
able to reach the path delete an agent's socket and bind its own — and
the gateway container has all of `/run/hive-agent` bind-mounted in.
Dropping `o=w` removes that permission rather than qualifying it.
⚠️ Contrast `/shared`, which *is* sticky world-writable (`1777`): it has
many legitimate writers, so sticky is the best available answer there.
This dir has exactly one writer, so it needs no world write at all.
### host admin socket access (`hivectl`)
`hivectl` drives the whole hive — spawn / kill / destroy / rebuild /

View file

@ -96,23 +96,11 @@ now set unconditionally for every agent. The mechanism:
harness's `unlink + bind(2)` cycle on socket replace. Per-agent
subdir keeps each agent's container blind to siblings' sockets.
**Ownership of that dir is declared, not repaired.** The tmpfiles.d
entry written by `SyncAgentTmpfiles` names the agent's container
uid/gid directly — `d /run/hive-agent/<name> 0751 <uid> <gid> -`.
Three principals need the dir and no two share a group: the harness
(owner, `rwx`, binds + unlinks its sockets), `hive-c0re` (dials
`agent.sock`) and the gateway's nginx (dials `web.sock`, and has all
of `/run/hive-agent` bind-mounted in). The latter two only need
traverse, which is what `o=--x` grants; both sockets are `0666`.
Do **not** reintroduce a chown here: tmpfiles re-applies this entry
on every boot *and* every agent spawn/destroy, so any ownership set
afterwards is reverted the next time any agent changes. The mode is
also load-bearing — 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 is not set here). So a
world-writable socket dir would let anything able to reach the path
replace an agent's socket with its own; `o=--x` removes that
permission outright rather than qualifying it.
The dir is `0751`, owned by the agent's container uid/gid, so
nginx reaches `web.sock` through `o=--x` (traverse) and the socket's
own `0666`. The gateway is one of three principals sharing that dir
and does not own its ownership rules — see
[`docs/boundary.md`](boundary.md#the-per-agent-socket-dir).
3. **Marker gate**. After successful `bind_unix`, the harness drops
`<dir>/hyperhive-socket-bound` next to the socket. c0re's
`agent_sockets::write` filters its JSON map by marker presence —