diff --git a/docs/boundary.md b/docs/boundary.md index 868a051e..e5093371 100644 --- a/docs/boundary.md +++ b/docs/boundary.md @@ -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//` 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 / diff --git a/docs/gateway.md b/docs/gateway.md index 38d86296..1a725575 100644 --- a/docs/gateway.md +++ b/docs/gateway.md @@ -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/ 0751 -`. - 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 `/hyperhive-socket-bound` next to the socket. c0re's `agent_sockets::write` filters its JSON map by marker presence —