docs(boundary): drop changelog framing, dedupe ReloadGatewayNginx explainer

This commit is contained in:
iris 2026-08-15 11:46:35 +02:00 committed by mara
commit c6eda124b1

View file

@ -5,12 +5,10 @@ _implementation_ work — container network isolation, the unifying
gateway, core-daemon privsep — is tracked as `area:ops` issues on gateway, core-daemon privsep — is tracked as `area:ops` issues on
the forge. the forge.
The operator/agent boundary is now technically enforced, not just a The operator/agent boundary is technically enforced, not just a
convention. Containers run in private netns (network isolation is convention: containers run in private netns (network isolation is
always on), the gateway proxies all operator-facing traffic, and always on), the gateway proxies all operator-facing traffic, and
`hive-c0re` runs as the unprivileged `hive-core` user. All three `hive-c0re` runs as the unprivileged `hive-core` user.
`area:ops` pillars — network isolation, the gateway, and privsep —
are complete and active.
## Two principals, two paths ## Two principals, two paths
@ -47,9 +45,9 @@ every boundary claim above is aspirational. Network isolation is what
makes the boundary _real_; the gateway and privsep are ergonomics and makes the boundary _real_; the gateway and privsep are ergonomics and
defence-in-depth layered on top. defence-in-depth layered on top.
Network isolation is now complete and always on: every agent container Network isolation is complete and always on: every agent container
runs in a private netns behind the hive bridge. The shared-netns mode runs in a private netns behind the hive bridge, and there is no
was removed. See `docs/network.md`. shared-netns mode. See `docs/network.md`.
Concretely, the core daemon's dashboard `/api` carries **no Concretely, the core daemon's dashboard `/api` carries **no
application-layer authentication** — operator-authority routes are served application-layer authentication** — operator-authority routes are served
@ -64,15 +62,18 @@ operator-authority route inherits that assumption. `hive-ci` is treated like an
agent for this purpose — it runs untrusted PR code and is netns-isolated for agent for this purpose — it runs untrusted PR code and is netns-isolated for
the same reason. the same reason.
The `area:ops` issues followed this sequencing: The boundary rests on three layers:
1. **Gateway** — pure ergonomics win, unblocks same-origin (lets the 1. **Gateway** — fronts all surfaces (dashboard + every per-agent UI)
cross-origin CORS shim on `/answer-question/{id}` go away), no on one origin. An nginx nixos-container proxies per-agent UIs under
behavioural risk. An nginx nixos-container now sits in front of all `/agent/<name>/`, which is what lets the inline-answer POST to
surfaces; per-agent UIs are proxied under `/agent/<name>/`. `/answer-question/{id}` go same-origin instead of needing a
2. **Network isolation** — the load-bearing step that turns the cross-origin CORS shim. Pure ergonomics — no behavioural risk on
honour-system split into an enforced boundary. **Complete** its own.
always-on, unconditional; the shared-netns mode was removed. 2. **Network isolation** — the load-bearing layer: every agent
container runs in a private netns behind the hive bridge, always
on and unconditional. This is what turns the operator/agent split
from an honour-system convention into an enforced boundary.
3. **Privsep** — defence in depth on the core process; `hive-c0re` 3. **Privsep** — defence in depth on the core process; `hive-c0re`
runs as the unprivileged `hive-core` user and delegates root runs as the unprivileged `hive-core` user and delegates root
operations to `hive-priv`, a narrow socket-activated helper. See operations to `hive-priv`, a narrow socket-activated helper. See
@ -84,15 +85,14 @@ The `area:ops` issues followed this sequencing:
systemd unit. The unit binds `/run/hive/priv.sock` with systemd unit. The unit binds `/run/hive/priv.sock` with
`SocketGroup=hive-core` and mode `0660` and passes the ready listener `SocketGroup=hive-core` and mode `0660` and passes the ready listener
to the helper as fd 3 (`LISTEN_FDS`). The helper requires this and to the helper as fd 3 (`LISTEN_FDS`). The helper requires this and
bails if it isn't socket-activated — there is intentionally no bails if it isn't socket-activated.
self-bind fallback.
Dropping the old fallback removed a dev/prod divergence: when ⚠️ There is intentionally no self-bind fallback: if `hive-priv` bound
`hive-priv` bound the socket itself it created the file owned by the socket itself, it would create the file owned by root's primary
root's primary group rather than `hive-core`, so a `hive-core` client group rather than `hive-core`, and a `hive-core` client couldn't
couldn't connect the way the socket unit's `SocketGroup` grant connect the way the socket unit's `SocketGroup` grant intends.
intends. Requiring socket activation everywhere means dev and prod Requiring socket activation everywhere keeps dev and prod on the
take the exact same path and the group grant always holds. exact same path, so the group grant always holds.
### the per-agent socket dir ### the per-agent socket dir
@ -129,10 +129,9 @@ nginx reaches all of `/run/hive-agent` as a plain host path. Dropping
them and the rest of the host. That costs no network isolation: nginx them and the rest of the host. That costs no network isolation: nginx
binds the host's `:80`/`:443` and reaches `localhost` upstreams, which a binds the host's `:80`/`:443` and reaches `localhost` upstreams, which a
netns would have to be opened up for anyway. netns would have to be opened up for anyway.
🔑 It does mean nothing *implicitly* scopes the privileged reload verb, 🔑 It does mean nothing *implicitly* scopes the privileged reload verb —
so the scope is explicit: the unit name is hard-coded in `hive-priv` see [`docs/security.md`](security.md#hive-c0re-privilege-separation) for
see `PrivRequest::ReloadGatewayNginx`. **A caller cannot name the unit, how `PrivRequest::ReloadGatewayNginx`'s containment works.
so the verb cannot be steered at another service.**
⚠️ Contrast `/shared`, which *is* sticky world-writable (`1777`): it has ⚠️ Contrast `/shared`, which *is* sticky world-writable (`1777`): it has
many legitimate writers, so sticky is the best available answer there. many legitimate writers, so sticky is the best available answer there.