From 8328c38a0738292a395266627c6b9bfa625c3832 Mon Sep 17 00:00:00 2001 From: atlas Date: Wed, 28 Oct 2026 13:07:15 +0100 Subject: [PATCH] document squid's proxy credentials --- docs/networking/network.md | 35 +++++++++++++++++++++++------ docs/swarm/ca.md | 6 +++-- docs/swarm/credentials-reference.md | 3 ++- 3 files changed, 34 insertions(+), 10 deletions(-) diff --git a/docs/networking/network.md b/docs/networking/network.md index e12748dc..3af91582 100644 --- a/docs/networking/network.md +++ b/docs/networking/network.md @@ -212,13 +212,27 @@ DNS/gateway), so only expose services safe for any agent to reach. Every hive host runs squid in the host netns, listening on `:3128` (`nix/host-modules/hive-squid.nix`). The port is open on the bridge interface -only. **No agent uses it yet**: agent traffic still goes straight out through -the NAT. The proxy goes first so you can check its metrics and logs before -anything reroutes. +only. **No agent uses it yet**: none sends a credential, and agent traffic +still goes straight out through the NAT. The proxy goes first so you can check +its metrics and logs before anything reroutes. Squid filters on the destination hostname: a `CONNECT` target or a plain-HTTP host. It never decrypts TLS, so it holds no signing key. It caches nothing. +- **Credentials.** A request for a listed name must carry a per-agent proxy + credential (HTTP Basic, the agent's name and its secret). `swarm-controller` + mints one per agent at `swarm/agents//proxy-credential`, and never + replaces it. `hive-c0re` hashes the credentials of this hive's agents, and + one more for the self-check, into `services.hyperhive.squid.usersFile` + every 30 seconds. Squid's `basic_ncsa_auth` helper re-reads the file when it + changes. A stopped agent stays in the file. Squid caches a good login for a + minute. Nothing rotates a credential: to replace one, delete its leaf in the + store and the next pass mints a new one. HTTP Basic crosses the bridge in + clear between an agent and the proxy. See + [the credentials reference](../swarm/credentials-reference.md). + A name that isn't on the allow-list gets the 403 whether or not the request + has a credential; a listed name without one gets a 407. + - **Allow-list.** `services.hyperhive.squid.allowedDomains` defaults to the swarm's own service names (`services.hyperhive.swarm.serviceDomains`). Each entry matches that exact name. Everything else gets a 403. @@ -237,10 +251,17 @@ host. It never decrypts TLS, so it holds no signing key. It caches nothing. unit and the self-check's to the swarm's log store. A refusal shows as `TCP_DENIED/403` with the client's source address. - **Self-check.** `squid-selfcheck.timer` runs `squid-selfcheck.service` - every minute from the bridge IP: `CONNECT` to `services.hyperhive.squid.selfcheck.canary` - (default: the forge's name) must answer 200, and to `deny-canary.invalid` - must answer 403. It writes one `squid-selfcheck ok=… allow_status=… -deny_status=…` line per run and fails the unit when either is wrong. The + every minute from the bridge IP, logging in as `selfcheck.squid`: `CONNECT` + to `services.hyperhive.squid.selfcheck.canary` (default: the forge's name) + must answer 200, to `deny-canary.invalid` 403, and to the canary without a + credential 407. It writes one `squid-selfcheck ok=… allow_status=… +deny_status=… anon_status=… credential=present|absent` line per run and + fails the unit when any is wrong. The password is the hive's + `swarm/hives//squid-selfcheck` secret, which + `squid-selfcheck-credential` lands at `/run/credstore/hive-squid.selfcheck`. + Before `swarm-controller` has minted it, or while `hive-c0re` hasn't + written the users file, the line reads `credential=absent` or + `allow_status=407` and `ok=false`, and nothing else breaks. The host's resolver looks up the canary, so a host whose resolver maps that name to a loopback or bridge address (a single-host swarm with `gateway.localHostsEntry`) fails the check. diff --git a/docs/swarm/ca.md b/docs/swarm/ca.md index 0b55be95..696094f0 100644 --- a/docs/swarm/ca.md +++ b/docs/swarm/ca.md @@ -22,8 +22,10 @@ hive's domain and `*.`, read from `services.hyperhive.swarm.hives..domain` on the store's host, with no IP SANs and no `localhost`. A request for any other name is a refusal from the store naming that name. The hive's policy grants `update` on that one -issue path and `read` on its own `attic/public-key` and `attic/pull-token` -(see [the cache facts for hive-priv](credentials-reference.md)), and nothing +issue path and `read` on its own `attic/public-key`, `attic/pull-token` and +`squid-selfcheck` +(see [the cache facts for hive-priv](credentials-reference.md) and the +squid self-check password), and nothing else. `swarm-bao-hive-leaf-policy.service`, on the store's host, writes the role, the policy and the cert-auth role for every hive in `swarm.hives`. diff --git a/docs/swarm/credentials-reference.md b/docs/swarm/credentials-reference.md index e87bbcdb..7c75ab53 100644 --- a/docs/swarm/credentials-reference.md +++ b/docs/swarm/credentials-reference.md @@ -156,7 +156,8 @@ pieces fit, and the reasoning behind them, is in [`credentials.md`](credentials. | the forge's copy of its OIDC secret | `swarm-bao-forge-oidc.service` reads it out of the swarm secret store under the forge publisher's leaf, wherever authelia runs | `/run/hive-forge-oidc/.secret` on the host (tmpfs), bound into the forge container, handed to `forgejo-sso-source` through `LoadCredential` | | the homeserver's copy of its OIDC secret | `swarm-bao-matrix-oidc.service` reads it out of the swarm secret store under matrix-ctl's leaf, wherever authelia runs | `/run/hive-matrix-oidc/.secret` on the host (tmpfs), bound into the homeserver container, handed to tuwunel through `LoadCredential` | | this hive's forge read token | `swarm-controller`, into the secret store at `swarm/hives//forge/token`, for the `hive-` account; a pass at start and every five minutes re-mints a missing or stale token | `hive-c0re` reads it under the hive's own store identity, and copies it (account on the first line, token on the second) to `/run/hyperhive/forge-hive-token` (tmpfs, `0600` `hive-core`) for nix's `git`, which `hive-priv` also runs. Each `internal/meta` read and each boot rewrites the copy when it differs; where they differ after a rotation, a build that starts before the next read fails to authenticate, and a retry reads the new token | -| squid's users file | `hive-c0re` (`workers::squid_users`), every 30 seconds, from the proxy credentials of its agents and the self-check | `services.hyperhive.squid.usersFile` (`/var/lib/hive-squid/users`, `hive-core:squid`, `0640`): `:` lines, SHA-512 crypt hashes only, renamed into place when it changed. `systemd-tmpfiles` creates it empty so squid's helper never sees a missing file. A hive whose agents have no credential yet (an older controller) has an empty file: every login fails and no agent sends a credential and squid doesn't read the file yet, so no traffic changes | +| squid's users file | `hive-c0re` (`workers::squid_users`), every 30 seconds, from the proxy credentials of its agents and the self-check | `services.hyperhive.squid.usersFile` (`/var/lib/hive-squid/users`, `hive-core:squid`, `0640`): `:` lines, SHA-512 crypt hashes only, renamed into place when it changed. `systemd-tmpfiles` creates it empty so squid's helper never sees a missing file. A hive whose agents have no credential yet (an older controller) has an empty file: every login fails and the self-check reports `ok=false`; no agent sends a credential, so no traffic changes | +| the squid self-check's password | `swarm-controller` mints `swarm/hives//squid-selfcheck`; `squid-selfcheck-credential` (root, every 10 minutes) reads it as the hive | `/run/credstore/hive-squid.selfcheck` (tmpfs, root, `0400`), imported by `squid-selfcheck.service` as a systemd credential and handed to `curl` on its stdin. Missing, or different from the users file: the self-check logs `credential=absent` or a 407 and fails, and nothing else breaks | ### Store identities, one per principal