From 9dc60061e7ce234c60b263a21b5949c606a93e6c Mon Sep 17 00:00:00 2001 From: atlas Date: Wed, 19 Aug 2026 15:06:15 +0200 Subject: [PATCH] docs: authenticated telemetry ingest, and where the hive's secret comes from MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three readers, three different questions: `observability.md` gets the operator's version — what to set (nothing, on an all-local swarm), what to set elsewhere, what a misconfiguration looks like (401s and a missing hive), and what turning it off actually costs. `swarm/secrets.md` extends the existing client-secret row rather than adding one, because this is a new *reader* of a secret already described. It notes the one thing that makes this reader unusual: a host unit reads the file in place, so unlike every other consumer there is no delivery step to get wrong. `setup.md` gets one bullet under security notes, since a first-run hive gets the property without doing anything and should know it has it. --- docs/observability.md | 32 ++++++++++++++++++++++++++++++++ docs/setup.md | 5 +++++ docs/swarm/secrets.md | 9 ++++++++- 3 files changed, 45 insertions(+), 1 deletion(-) diff --git a/docs/observability.md b/docs/observability.md index 4b23a4bb..20a51a47 100644 --- a/docs/observability.md +++ b/docs/observability.md @@ -186,6 +186,38 @@ redefines it — the agent-facing value is *derived* keeps working unchanged. The bridge port is contributed to `exposeHostPorts` automatically; there is nothing to open by hand. +### Authenticated ingest + +The swarm tier gives **each hive its own receiver**, and stamps the `hive` label +from whichever receiver accepted a sample. A hive therefore cannot report +metrics as another hive, and cannot relabel its own by editing what it sends — +the label is not taken from the payload at all. + +**On an all-local swarm there is nothing to set.** Each hive already has an +identity, and its collector reads the secret that host's own authelia minted. + +**On a hive that does not host the swarm's services**, the secret has to arrive +somehow — copy it across and name it: + +```nix +services.hyperhive.otel.clientSecretFile = "/run/secrets/hive-telemetry.secret"; +``` + +Getting that wrong shows up as the hive's collector logging 401s from the swarm +tier and no metrics appearing for that hive. When a single host runs both tiers +the build catches it instead, because it can see both sides. + +To accept unauthenticated ingest — every writer on the swarm network already +trusted, or a swarm with no authelia: + +```nix +services.hyperhive.swarm.otel.requireHiveIdentity = false; +``` + +⚠️ That reopens the original hole rather than merely skipping a check: while an +unauthenticated port is listening, anything that can reach the collector can +attribute metrics to any hive. + ### `services.hyperhive.otel.collector.port` — port, default `4318` The OTLP/HTTP port the hive tier listens on, bound to the bridge IP only. The diff --git a/docs/setup.md b/docs/setup.md index d5ec447c..cdebe400 100644 --- a/docs/setup.md +++ b/docs/setup.md @@ -156,6 +156,11 @@ See [`tools/hivectl.md`](tools/hivectl.md) for every `hivectl` verb. - All config changes (forge PRs on `agent-configs/`) go through operator approval — agents can't unilaterally rebuild containers, by design. See [`boundary.md`](boundary.md) and [`security.md`](security.md). +- **Telemetry ingest is authenticated per hive**, and the `hive` label comes + from which hive authenticated rather than from the payload — so no hive can + report metrics as another. A first-run all-local hive gets this with nothing + to configure; joining a swarm you don't host needs one secret copied across. + See [`observability.md`](observability.md#authenticated-ingest). Once the hive is running, ruth records anything it needs to remember across restarts in `/agents/ruth/state/notes.md`. diff --git a/docs/swarm/secrets.md b/docs/swarm/secrets.md index 23547b7c..4d324944 100644 --- a/docs/swarm/secrets.md +++ b/docs/swarm/secrets.md @@ -45,7 +45,7 @@ Every row below is read against one of these. | authelia session, JWT and storage-encryption keys | authelia's first-boot unit, in-container | `/var/lib/authelia-swarm/{session,jwt,storage-encryption}.key` | generated in place; nothing outside that container reads them | | authelia OIDC HMAC key | same unit | `/var/lib/authelia-swarm/oidc-hmac.key` | same | | authelia OIDC issuer key (RSA) | same unit | `/var/lib/authelia-swarm/oidc-issuer.key` | same — relying parties verify against the **public** half at `/jwks.json` | -| OIDC client secret, plaintext half | `authelia crypto hash generate --random` | `/var/lib/authelia-swarm/oidc-clients/.secret` | operator provides the file and names it in the service's `sso.clientSecretFile` | +| OIDC client secret, plaintext half | `authelia crypto hash generate --random` | `/var/lib/authelia-swarm/oidc-clients/.secret` | operator provides the file and names it in whichever option reads it — `sso.clientSecretFile` for a service, `otel.clientSecretFile` for the hive's telemetry collector | | OIDC client secret, digest half | the same mint | `oidc-clients/.digest` | authelia's own half; merged at runtime via `settingsFiles` | | authelia subject store | `swarmctl` and `swarm-authelia-bridge` | `users.yml` — one file, read and written by both | `swarmctl`, on the host that runs authelia | | wireguard private key | **the operator** — `wg genkey` | whatever `swarm.wireguard.privateKeyFile` names | always operator-provided; nothing generates this for you | @@ -56,6 +56,13 @@ because nothing outside that container ever reads them. **That is the test worth applying to any secret added here** — and the client secret's plaintext half is the one row that fails it, which is the entire reason a delivery step exists. +One reader needs no delivery step: the **hive's telemetry collector**, which +authenticates to the swarm's collector as its own hive. It is a host unit rather +than a container, so on an all-local swarm it reads authelia's file where it +lies (through `LoadCredential`) and no second copy is made. On any other +topology it is an ordinary "operator provides the file" case — see +`services.hyperhive.otel.clientSecretFile`. + ### Minting the queue's callout nkeys `nats.autoGenerateCallout` mints both keypairs on the host before the queue