apply each agent's internet flag to its squid credential
This commit is contained in:
parent
0b99f17bc3
commit
501445ebc3
4 changed files with 323 additions and 91 deletions
|
|
@ -230,17 +230,33 @@ host. It never decrypts TLS, so it holds no signing key. It caches nothing.
|
|||
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.
|
||||
A request without a valid credential gets a 407, once the destination
|
||||
checks below have passed it.
|
||||
|
||||
- **Two lists.** The credential decides the policy. `hive-c0re` writes a
|
||||
second file beside the users file, `services.hyperhive.squid.unrestrictedFile`
|
||||
(`/var/lib/hive-squid/unrestricted`, `hive-core:squid`, `0640`): the names of
|
||||
the users whose agent holds the `unrestricted_internet` flag in the
|
||||
permissions the hive last read from the swarm, one per line, rewritten when
|
||||
it changes. An agent with no record, or a record that never set the flag,
|
||||
holds it, the same default as the
|
||||
`hyperhive.agent.unrestricted_internet` metric. Squid reads the list when it
|
||||
parses its configuration, so `squid-unrestricted-reload.path` reloads squid
|
||||
when the file changes. A missing or empty file lists no one, and squid
|
||||
starts either way. A flag change reaches squid after the hive's next read of
|
||||
the permissions and its next 30-second pass.
|
||||
|
||||
- **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.
|
||||
- **Unlisted names.** Refused first, without a DNS lookup.
|
||||
entry matches that exact name. A user on the unrestricted list reaches any
|
||||
other name too, on ports 80 and 443. Everyone else gets a 403 for it.
|
||||
- **Unlisted names.** For a user who isn't on the unrestricted list, refused
|
||||
after the destination checks, which look the name up on the host.
|
||||
- **Denied before any allow.** Destinations that resolve to loopback,
|
||||
link-local or the bridge subnet, and IP-literal hosts. Squid resolves names
|
||||
on the host, so without these a listed name pointing at `127.0.0.1` would
|
||||
walk around the bridge→loopback DROP rule. Only `CONNECT` to port 443 and
|
||||
link-local or the bridge subnet, and IP-literal hosts, for every user
|
||||
including the unrestricted ones. Squid resolves names on the host, so
|
||||
without these a name pointing at `127.0.0.1` would walk around the
|
||||
bridge→loopback DROP rule. Only `CONNECT` to port 443 and
|
||||
plain HTTP to ports 80 and 443 pass.
|
||||
- **Metrics.** `squid-exporter` serves `prometheus-squid-exporter` on
|
||||
`127.0.0.1:9301`. The hive collector scrapes it as job `squid` through
|
||||
|
|
@ -254,9 +270,13 @@ host. It never decrypts TLS, so it holds no signing key. It caches nothing.
|
|||
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
|
||||
credential 407. It then logs in as `selfcheck-unrestricted.squid`, which has
|
||||
the same secret and is always on the unrestricted list, and a `CONNECT` to
|
||||
`services.hyperhive.squid.selfcheck.unrestrictedCanary` (default:
|
||||
`example.com`, a name outside the allow-list) must answer 200. It writes one
|
||||
`squid-selfcheck ok=… allow_status=… deny_status=… anon_status=…
|
||||
credential=present|absent unrestricted_canary=… unrestricted_status=…` 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
|
||||
|
|
|
|||
|
|
@ -320,7 +320,10 @@ an absent flag included, and a value in the request wins. A `PUT` that leaves
|
|||
the field out and a `DELETE` (`swarmctl agent permissions clear`) also keep the
|
||||
stored value; a `DELETE` on an agent that has one rewrites the record to just
|
||||
that value. Read it with
|
||||
`swarm_queue_client::agent_permissions::unrestricted`. Nothing acts on it yet.
|
||||
`swarm_queue_client::agent_permissions::unrestricted`. The hive reads it for
|
||||
the agent's squid policy: [the egress proxy](../networking/network.md#egress-proxy-squid)
|
||||
lets an unrestricted agent's credential reach any destination, and holds the
|
||||
others to the allow-list. No agent sends a credential yet.
|
||||
|
||||
**How a change applies** —
|
||||
- The agent's harness reads its own key every 10 seconds and builds every
|
||||
|
|
|
|||
|
|
@ -148,16 +148,16 @@ pieces fit, and the reasoning behind them, is in [`credentials.md`](credentials.
|
|||
|
||||
### Hive-level — one of each per hive
|
||||
|
||||
| secret | generated by | lives at |
|
||||
| ---------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| hive leaf certs | `hive-tls.nix`, issued by the secret store's `pki` mount | `<deploy.hive-controller.tls.stateDir>/<name>.pem` |
|
||||
| the hive's own store identity | `glue-bao-tls.nix` on the store's host; placed by hand on every other host ([`ca.md`](ca.md#the-gateway-leaf-comes-from-the-secret-store)) | `deploy.hive-controller.tls.baoHiveIdentityCertFile` / `baoHiveIdentityKeyFile` |
|
||||
| the cache's public key and pull token, for hive-priv | `swarm-controller` copies them to `swarm/hives/<hive>/attic/{public-key,pull-token}` (see the cache-facts row above) | `hive-priv-cache-facts` (root) reads those two under the hive's own store identity every ten minutes and writes `/run/hyperhive-priv/attic/public-key` and `netrc` (tmpfs, root, each `0600`, directory `0700`); the cache's address comes from `services.hyperhive.swarm.attic.domain` and `cache`, not from the store. A store that holds no facts, or that doesn't answer, leaves the files as they stand, and a hive that never got them has `hive-priv` answer `not available`. |
|
||||
| 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` |
|
||||
| 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 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 |
|
||||
| secret | generated by | lives at |
|
||||
| ---------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| hive leaf certs | `hive-tls.nix`, issued by the secret store's `pki` mount | `<deploy.hive-controller.tls.stateDir>/<name>.pem` |
|
||||
| the hive's own store identity | `glue-bao-tls.nix` on the store's host; placed by hand on every other host ([`ca.md`](ca.md#the-gateway-leaf-comes-from-the-secret-store)) | `deploy.hive-controller.tls.baoHiveIdentityCertFile` / `baoHiveIdentityKeyFile` |
|
||||
| the cache's public key and pull token, for hive-priv | `swarm-controller` copies them to `swarm/hives/<hive>/attic/{public-key,pull-token}` (see the cache-facts row above) | `hive-priv-cache-facts` (root) reads those two under the hive's own store identity every ten minutes and writes `/run/hyperhive-priv/attic/public-key` and `netrc` (tmpfs, root, each `0600`, directory `0700`); the cache's address comes from `services.hyperhive.swarm.attic.domain` and `cache`, not from the store. A store that holds no facts, or that doesn't answer, leaves the files as they stand, and a hive that never got them has `hive-priv` answer `not available`. |
|
||||
| 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` |
|
||||
| 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. `unrestricted` beside it lists the users whose agent holds the swarm's `unrestricted_internet` flag, names only; it lists every agent the hive never read as off `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. Both self-check users (`selfcheck.squid` and the always-unrestricted `selfcheck-unrestricted.squid`) use it, so whoever holds it has unrestricted egress through this squid 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
|
||||
|
||||
|
|
|
|||
|
|
@ -568,7 +568,9 @@ the swarm never set the flag, which counts as on. The **restrict** or
|
|||
**allow unrestricted** badge flips it with a `PUT` that carries the stored
|
||||
permission list along with the flag, because a `PUT` without the list resets a
|
||||
stored list to the default. An agent with no stored list sends the flag alone.
|
||||
Nothing acts on the flag yet.
|
||||
The hive's egress proxy applies the flag to each agent's proxy credential (see
|
||||
[the egress proxy](../networking/network.md#egress-proxy-squid)); no agent sends
|
||||
one yet, so the flag changes no traffic today.
|
||||
|
||||
A saved change isn't instant. The hive reads the caps about every 30 seconds,
|
||||
and a running agent takes a changed cap at its next spawn or rebuild, so the
|
||||
|
|
|
|||
Loading…
Reference in a new issue