docs: fix genuine passive-voice hits in docs/networking
Eighth batch of the ongoing write-good.Passive pass (hyperhive#4042):
read all 69 hits across network.md/snapshot-store.md/gateway.md in
context and rewrote 34 with a clearly nameable actor -- mostly
hive-c0re, dnsmasq, nginx, or a specific systemd unit/fn named right
there or a sentence or two earlier. snapshot-store.md's operator-facing
prose ("you can deploy it", "you must tell every hive") reuses this
doc's own established "you" address (already present two sentences
away in one case) rather than inventing a new register.
Left 35 alone -- the largest leave-alone count yet, dominated by two
shapes gateway.md is dense with: negative-capability/requirement
idioms ("no X is required/needed/exposed", "can't be added/wired",
"must not be exposed") and predicate-adjective state descriptions
("is misconfigured"/"is broken" in negative-contrast "not that X"
framing, "is privileged" matching the established "is trusted" bucket,
"is closed/internal" deployment-posture, "is encrypted and
unauthenticated" connection-state pair). Also: two "X was removed"
changelog-style facts with no actor clause (same family left alone in
the scheduler batch), a gerund nix-conditional pair ("X being enabled
and Y being set are assertions, not documentation"), and a passive
embedded inside an already-active sentence ("X controls whether Y is
opened" -- the real verb is already active).
One caught-and-reverted mid-edit mistake: an early attempt at the
"nginx is handed the leaf" fix landed on the wrong paragraph (an
unrelated CA-generation sentence two paragraphs up) and left a
nonsensical forward-reference behind. Caught by re-reading the diff
before running vale, not by the count -- same discipline as the
docs/process batch's original catch. Reverted cleanly and reapplied at
the actual target sentence.
One small sibling-consistency fix beyond pure voice-flipping: "The
gateway emits the following headers..." (rewritten) sat two paragraphs
above "a header is added alongside the other security headers" (still
passive) describing the same mechanism -- rewrote the second to match
("the gateway adds a header...") rather than leave one active and one
passive version of the identical fact sitting near each other.
Verified via vale before/after: 69 -> 35 write-good.Passive hits,
exactly the 35 left alone above; error count and other warning
categories unchanged. Re-read every changed line in full surrounding
context after editing before running the final vale check.
This commit is contained in:
parent
0ad0376fe3
commit
f457bf3c15
3 changed files with 47 additions and 47 deletions
|
|
@ -17,7 +17,7 @@ Single nginx in front of every hyperhive web surface. Runs on the **host**, next
|
|||
| `auth.<swarm>/` | `auth.<swarm>` | authelia (`9091`) | `deploy.authelia` |
|
||||
| `<swarm>/` | `<swarm>` | swarm-ui dist (static), behind an authelia subrequest | `deploy.swarm-ui` |
|
||||
|
||||
The authelia vhost is declared only by the host that **runs** authelia, not by every hive that uses it — a client hive knows the swarm's `authelia.url` but must not answer for a name it doesn't serve. Its server name is exactly `swarm.authelia.domain`: authelia validates `authelia_url ⊂ session cookie domain` at startup, so a near-miss is a container that refuses to boot. It carries no `auth_basic` — the login page must not sit behind the login mechanism it replaces — and sets the four `X-Forwarded-{Proto,Host,Uri,For}` headers, since authelia decides by the *original* request rather than the hop it sees.
|
||||
Only the host that **runs** authelia declares the authelia vhost, not every hive that uses it — a client hive knows the swarm's `authelia.url` but must not answer for a name it doesn't serve. Its server name is exactly `swarm.authelia.domain`: authelia validates `authelia_url ⊂ session cookie domain` at startup, so a near-miss is a container that refuses to boot. It carries no `auth_basic` — the login page must not sit behind the login mechanism it replaces — and sets the four `X-Forwarded-{Proto,Host,Uri,For}` headers, since authelia decides by the *original* request rather than the hop it sees.
|
||||
|
||||
⚠️ **A `502` from this vhost typically means authelia has no users yet, not that the proxy is misconfigured.** Authelia treats an empty user store as a fatal startup error, so an enabled-but-unbootstrapped swarm crash-loops the container while the vhost in front of it works perfectly. Check `journalctl -M swarm-authelia -u authelia-swarm` before suspecting anything here; the bootstrap step is in [`swarm/sso.md`](../swarm/sso.md).
|
||||
|
||||
|
|
@ -28,7 +28,7 @@ Per-agent UIs stay sub-path, forge and matrix get sub-domains — see
|
|||
|
||||
Operator points client at `<hive>`. Sequence:
|
||||
|
||||
1. Client fetches `https://<hive>/.well-known/matrix/client` → `{"m.homeserver":{"base_url":"https://chat.<swarm>"}}` (no port suffix when gateway listens on 443). The gateway always terminates TLS, so the scheme is always `https`; a non-default `httpsPort` is reflected as the port suffix.
|
||||
1. Client fetches `https://<hive>/.well-known/matrix/client` → `{"m.homeserver":{"base_url":"https://chat.<swarm>"}}` (no port suffix when gateway listens on 443). The gateway always terminates TLS, so the scheme is always `https`; a non-default `httpsPort` shows up as the port suffix.
|
||||
2. Client connects to `chat.<swarm>/_matrix/client/...`.
|
||||
3. Gateway routes `/_matrix/*` → tuwunel at `127.0.0.1:8008`.
|
||||
|
||||
|
|
@ -95,8 +95,8 @@ All agents bind their web UI on a unix-domain socket at
|
|||
`/run/hive-agent/<name>/web.sock` — the `HIVE_WEB_SOCKET` env var is
|
||||
now set unconditionally for every agent. The mechanism:
|
||||
|
||||
1. **Agent side**. `HIVE_WEB_SOCKET=/run/hive-agent/<name>/web.sock`
|
||||
is set on every harness service env; `web_ui::serve` binds a
|
||||
1. **Agent side**. The nix module sets `HIVE_WEB_SOCKET=/run/hive-agent/<name>/web.sock`
|
||||
on every harness service env; `web_ui::serve` binds a
|
||||
`UnixListener` at that path.
|
||||
2. **Host side**. `hive-c0re` bind-mounts the per-agent subdir
|
||||
(`/run/hive-agent/<name>/`) into the agent's container. Dir
|
||||
|
|
@ -137,9 +137,9 @@ now set unconditionally for every agent. The mechanism:
|
|||
c0re regenerates `agents.conf` (and triggers a reload) on two
|
||||
triggers: every topology change (new/removed agents) and every 10s
|
||||
marker poll tick (`agent_sockets::spawn_poll`). `write()` is
|
||||
idempotent — skips the rename when content is unchanged. Failed reloads
|
||||
are retried automatically on subsequent poll ticks via
|
||||
`gateway_nginx::reload_if_pending`.
|
||||
idempotent — skips the rename when content is unchanged.
|
||||
`gateway_nginx::reload_if_pending` automatically retries failed reloads
|
||||
on subsequent poll ticks.
|
||||
|
||||
`agents.conf` uses atomic `<path>.tmp` + `rename()` writes so a crashing
|
||||
c0re process never leaves a partial or unparseable file behind.
|
||||
|
|
@ -153,7 +153,7 @@ as same-origin `/agent/<name>/…` URLs instead of the legacy direct
|
|||
`HIVE_GATEWAY_ENABLED` env the c0re NixOS module now always sets
|
||||
(`services.hyperhive.gateway.enable` was removed — the gateway runs
|
||||
unconditionally alongside hyperhive), so this is effectively always
|
||||
true; the `false` branch is retained as a defensive fallback for the
|
||||
true; the `false` branch stays as a defensive fallback for the
|
||||
env being unset. Three render sites
|
||||
flip together: the primary agent-name link, the favicon fetch
|
||||
(`<url>/icon`), and the nav-strip `container`-kind links from
|
||||
|
|
@ -216,15 +216,15 @@ swarm-services fallback below is for.
|
|||
|
||||
**Why a CA, not a bare leaf**: a bare self-signed leaf is its own trust anchor, so every regeneration is a new anchor every consumer must re-trust — and a runtime-generated leaf can't be wired into an agent's build-time trust store at all. With a stable CA, agents and federation peers trust it *once*; leaf rotation never re-breaks them.
|
||||
|
||||
**What consumers trust**: `trust-bundle.pem` in the same state dir, not `ca.pem`. The hive CA is itself issued under the swarm root ([`swarm/ca.md`](../swarm/ca.md) has the hierarchy), and an intermediate isn't a chain a verifier can terminate at — so the bundle carries the hive CA plus whatever it's rooted at. nginx is handed the leaf with the hive CA appended for the same reason. Everything that trusts the hive's TLS reads the bundle: agents (via `security.pki.certificateFiles`), the CI and forge containers, and a federating peer.
|
||||
**What consumers trust**: `trust-bundle.pem` in the same state dir, not `ca.pem`. The hive CA is itself issued under the swarm root ([`swarm/ca.md`](../swarm/ca.md) has the hierarchy), and an intermediate isn't a chain a verifier can terminate at — so the bundle carries the hive CA plus whatever it's rooted at. `hive-gateway-self-signed-cert` hands nginx the leaf with the hive CA appended for the same reason. Everything that trusts the hive's TLS reads the bundle: agents (via `security.pki.certificateFiles`), the CI and forge containers, and a federating peer.
|
||||
|
||||
**Why on by default**: matrix-dart-sdk (FluffyChat's SDK) hardcodes `https://<host>/.well-known/matrix/client` for homeserver discovery and refuses to fall back to plain http. Without TLS the browser client can't bootstrap.
|
||||
|
||||
**Cert shape**: leaf subject CN = bare hive domain; subjectAltName is `<hive>` plus wildcard `*.<hive>`, so all current and future sub-domain vhosts validate under the same leaf + the hive CA. A swarm service whose name is *not* under this hive's domain can't be added here — the hive CA is name-constrained to `<hive>`, and a violating SAN invalidates the whole leaf, not just that name. Those names get the swarm-services leaf instead ([`swarm/ca.md`](../swarm/ca.md)).
|
||||
|
||||
**Rotation**: `hive-tls-ca.service` is idempotent — it re-signs the leaf when it's missing or within 30 days of expiry, always under the same CA (so consumer trust is undisturbed). The CA itself is regenerated only if missing or already expired. To force a leaf rotation, delete `gateway.pem` under the state dir and restart the unit, then reload `nginx`.
|
||||
**Rotation**: `hive-tls-ca.service` is idempotent — it re-signs the leaf when it's missing or within 30 days of expiry, always under the same CA (so consumer trust is undisturbed). It regenerates the CA itself only if missing or already expired. To force a leaf rotation, delete `gateway.pem` under the state dir and restart the unit, then reload `nginx`.
|
||||
|
||||
**Cert prompts**: browsers still warn once per host until the hive's `trust-bundle.pem` is added to the browser/OS trust store (an anchor, not the leaf, is the thing to trust). Agent trust is wired separately (see the agent-trust work for `/run/hive-ca`).
|
||||
**Cert prompts**: browsers still warn once per host until the operator adds the hive's `trust-bundle.pem` to the browser/OS trust store (an anchor, not the leaf, is the thing to trust). Agent trust is wired separately (see the agent-trust work for `/run/hive-ca`).
|
||||
|
||||
### Operator-provided cert (`tls.certDir`)
|
||||
|
||||
|
|
@ -451,13 +451,13 @@ so they see the same store.
|
|||
|
||||
**Graceful degradation**: if `HIVE_AGENT_FRONTEND_DIR` is empty or
|
||||
unset (for example a build that predates `cfg.frontend`), each agent gets the
|
||||
legacy single-proxy block and all traffic is forwarded to the agent
|
||||
legacy single-proxy block and nginx forwards all traffic to the agent
|
||||
daemon as before.
|
||||
|
||||
**`extraFiles`**: per-agent `hyperhive.frontend.extraFiles` are in
|
||||
`mergedDist`, not in the base `cfg.frontend` dist. They're not under
|
||||
the nix-store `alias` path, so requests for them fall through
|
||||
`try_files` to `@<name>_dynamic` and are served by the agent daemon
|
||||
`try_files` to `@<name>_dynamic`, and the agent daemon serves them
|
||||
as before.
|
||||
|
||||
## Per-agent error pages
|
||||
|
|
@ -476,9 +476,9 @@ HTML pages instead of nginx's default error chrome:
|
|||
recovery, etc.). `proxy_intercept_errors on` + `error_page 502 503
|
||||
504 = /__hive_agent_unreachable` rewrites to `unreachable.html`.
|
||||
|
||||
Both pages are built at deploy time via `pkgs.runCommand` (one nix
|
||||
`pkgs.runCommand` builds both pages at deploy time (one nix
|
||||
derivation `hyperhive-agent-error-pages` with `not-found.html` +
|
||||
`unreachable.html` inside) and served via two `internal` nginx
|
||||
`unreachable.html` inside), served via two `internal` nginx
|
||||
locations with `alias` to the exact file. `internal` keeps the
|
||||
files from being directly request-able by operators — only nginx's
|
||||
own error-handling can reach them.
|
||||
|
|
@ -564,7 +564,7 @@ ordering, preventing the subrequest from looping back through
|
|||
|
||||
## Security headers
|
||||
|
||||
The following headers are emitted at server scope on every gateway
|
||||
The gateway emits the following headers at server scope on every
|
||||
vhost (`_`, `forge.<swarm-domain>`, `chat.<swarm-domain>`):
|
||||
|
||||
| Header | Value |
|
||||
|
|
@ -592,8 +592,8 @@ services.hyperhive.gateway.hsts = {
|
|||
};
|
||||
```
|
||||
|
||||
When enabled, a `Strict-Transport-Security: max-age=...[; includeSubDomains]`
|
||||
header is added alongside the other security headers.
|
||||
When enabled, the gateway adds a `Strict-Transport-Security: max-age=...[; includeSubDomains]`
|
||||
header alongside the other security headers.
|
||||
|
||||
**Opt-in rationale**: HSTS pins HTTPS in the browser's preload cache;
|
||||
enabling it on a deployment that later loses TLS locks browsers out
|
||||
|
|
@ -624,16 +624,16 @@ the opposite arm run as a control:
|
|||
that's every service on the hive.
|
||||
- `server_name on` — sends SNI, or the far end can't pick a cert.
|
||||
|
||||
**⚠️ Session-cache footgun**: `proxy_ssl_session_reuse` is left at its
|
||||
default (on), deliberately — this is used on per-request auth
|
||||
subrequests, so the handshake it avoids is paid on every request. Worth
|
||||
knowing when testing though: the session cache is keyed by upstream
|
||||
**⚠️ Session-cache footgun**: the module leaves `proxy_ssl_session_reuse` at its
|
||||
default (on), deliberately — nginx uses this on per-request auth
|
||||
subrequests, so every request pays the handshake it avoids. Worth
|
||||
knowing when testing though: nginx keys the session cache by upstream
|
||||
address and NOT by trust config, so two locations pointing at one
|
||||
upstream with different trust don't verify independently.
|
||||
|
||||
**⚠️ Host-header clobber footgun**: `verifiedProxyTo` also pins `Host`
|
||||
(and reinstates the rest of nginx's `recommendedProxySettings` header
|
||||
set) to the target `name` rather than leaving it to be filled in later.
|
||||
set) to the target `name` rather than letting nginx fill it in later.
|
||||
`name` here resolves back to THIS gateway — every consumer dials another
|
||||
vhost on the same nginx, not a separate host — and nginx picks the vhost
|
||||
to answer an HTTPS request from the `Host` header, not from the TLS SNI
|
||||
|
|
|
|||
Loading…
Reference in a new issue