Watch
0
0
Fork
You've already forked hyperhive
0

document squid's proxy credentials

This commit is contained in:
atlas 2026-10-28 13:07:15 +01:00 • committed by mara
commit 8328c38a07
3 changed files with 34 additions and 10 deletions

View file

@ -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 `<bridgeIp>:3128` Every hive host runs squid in the host netns, listening on `<bridgeIp>:3128`
(`nix/host-modules/hive-squid.nix`). The port is open on the bridge interface (`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 only. **No agent uses it yet**: none sends a credential, and agent traffic
the NAT. The proxy goes first so you can check its metrics and logs before still goes straight out through the NAT. The proxy goes first so you can check
anything reroutes. its metrics and logs before anything reroutes.
Squid filters on the destination hostname: a `CONNECT` target or a plain-HTTP 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. 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/<agent>/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 - **Allow-list.** `services.hyperhive.squid.allowedDomains` defaults to the
swarm's own service names (`services.hyperhive.swarm.serviceDomains`). Each swarm's own service names (`services.hyperhive.swarm.serviceDomains`). Each
entry matches that exact name. Everything else gets a 403. 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 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. client's source address.
- **Self-check.** `squid-selfcheck.timer` runs `squid-selfcheck.service` - **Self-check.** `squid-selfcheck.timer` runs `squid-selfcheck.service`
every minute from the bridge IP: `CONNECT` to `services.hyperhive.squid.selfcheck.canary` every minute from the bridge IP, logging in as `selfcheck.squid`: `CONNECT`
(default: the forge's name) must answer 200, and to `deny-canary.invalid` to `services.hyperhive.squid.selfcheck.canary` (default: the forge's name)
must answer 403. It writes one `squid-selfcheck ok=… allow_status=… must answer 200, to `deny-canary.invalid` 403, and to the canary without a
deny_status=…` line per run and fails the unit when either is wrong. The 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/<hive>/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 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 that name to a loopback or bridge address (a single-host swarm with
`gateway.localHostsEntry`) fails the check. `gateway.localHostsEntry`) fails the check.

View file

@ -22,8 +22,10 @@ hive's domain and `*.<hive domain>`, read from
`services.hyperhive.swarm.hives.<name>.domain` on the store's host, with no `services.hyperhive.swarm.hives.<name>.domain` on the store's host, with no
IP SANs and no `localhost`. A request for any other name is a refusal from 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 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` issue path and `read` on its own `attic/public-key`, `attic/pull-token` and
(see [the cache facts for hive-priv](credentials-reference.md)), and nothing `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 else. `swarm-bao-hive-leaf-policy.service`, on the
store's host, writes the role, the policy and the cert-auth role for every store's host, writes the role, the policy and the cert-auth role for every
hive in `swarm.hives`. hive in `swarm.hives`.

View file

@ -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/<id>.secret` on the host (tmpfs), bound into the forge container, handed to `forgejo-sso-source` through `LoadCredential` | | 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/<id>.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/<id>.secret` on the host (tmpfs), bound into the homeserver container, handed to tuwunel 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/<id>.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/<hive>/forge/token`, for the `hive-<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 | | this hive's forge read token | `swarm-controller`, into the secret store at `swarm/hives/<hive>/forge/token`, for the `hive-<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`): `<user>:<hash>` 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`): `<user>:<hash>` 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/<hive>/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 ### Store identities, one per principal