Watch
0
0
Fork
You've already forked hyperhive
0

swarm-logs: authenticate as the agent with its own queue secret

swarm-logs reads `swarm/agents/<agent>/queue` from the swarm secret
store with the agent's own store certificate (the read hive-agent makes
for its queue connection) and presents it as
`Bearer swarm-agent.<agent>.<secret>`, which the gateway's
swarm-logs-auth checks. On a 401 it reads the secret again and retries
once, so a secret re-minted between the read and the check does not
fail the query.

The OIDC mint against the hive's shared `hive-<h>-agent` client is gone
from swarm-logs, along with the unused `HIVE_AGENT_OIDC_CA_FILE` anchor.
logs.nix now gates on the store address instead of the queue's token
endpoint and wraps the binary with the paths queue-identity.nix gives
the harness; the host forwards the query URL wherever it forwards a
store identity.

The shared client is still delivered into containers; removing that is

Part of #4459.
This commit is contained in:
atlas 2026-10-12 22:48:40 +02:00 • committed by mara
commit 0f53e9be6c
12 changed files with 260 additions and 331 deletions

View file

@ -20,40 +20,19 @@ attach at the gateway location, not in this binary.
## How it authenticates
The agent container already holds one identity, the per-hive machine client
`hive-<name>-agent`, delivered as a systemd credential pair. This binary
presents a `client_credentials` access token minted from it as
`Authorization: Bearer`.
As the agent itself. On each query it reads the agent's own queue secret,
`swarm/agents/<agent>/queue`, from the swarm secret store with the agent's
store certificate (`nix/agent-modules/bao.nix`), the same read `hive-agent`
makes for its queue connection. It sends the secret as
`Authorization: Bearer swarm-agent.<agent>.<secret>`, spelled by
`swarm_queue_client::agent_token`.
Minting is **not** implemented here — `swarm_queue_client::mint_token_for_blocking`
already owns the request, its error type and its CA handling, and a
token-endpoint fix has to be findable in one place. What this crate adds is
the agent-shaped half: the client id arrives as a _file_ beside the secret, so
nothing outside `nix/agent-modules/queue.nix` spells `hive-<name>-agent`
twice. That is the same problem `hive-agent/src/swarm_queue.rs` solves, and
`src/auth.rs` is its `decide` restated over this binary's inputs.
At the gateway, nginx hands a bearer of that shape to `swarm-logs-auth`, which
compares it with the stored secret (`swarm-agent-verify`, the check the queue's
auth callout also uses). A 401 makes `swarm-logs` read the secret again and
retry once, which covers a secret re-minted between the read and the check.
🩸 Every credential here is a **path**. The secret's contents are read at the
moment of the request, inside `swarm-queue-client`, and are never bound to a
name in this crate, logged, or rendered into an error.
### The audience is the URL
`swarm-logs` mints its token with the query URL as the requested audience,
because that is what authelia checks at the authz endpoint the gateway's
`auth_request` calls. The same rule `swarm-otel.nix` states over its own push
targets: one binding for the address and the audience, since two spellings of
one address present as a valid token refused at the store.
⚠️ That requires the agent client to be _registered_ for that audience and to
hold `authelia.bearer.authz`. Both are set in `swarm-authelia.nix`'s
`agentClients`; without them authelia answers `invalid_target` at the token
endpoint, or the gateway answers 401 with no explanation.
⚠️ Registration is not issuance, so the token request _asks_ for both: the
audience and the scope are named in the `client_credentials` form, because a
client that is registered for a scope it does not request is handed a token
carrying none, and the gateway refuses that with the same bare 401.
🩸 The secret is never logged or rendered into an error.
## Configuration
@ -62,19 +41,17 @@ shape `swarmctl` is configured in, and for the same reason: every value is
derived from an option that module owns, so a default here would be an address
we _hope_ points at something.
| variable | what |
| ------------------------------------ | ---------------------------------------------------- |
| `HIVE_AGENT_LOGS_QUERY_URL` | the LogsQL query endpoint; also the token's audience |
| `HIVE_AGENT_OIDC_TOKEN_ENDPOINT` | authelia's token endpoint |
| `HIVE_AGENT_OIDC_CLIENT_ID_FILE` | path of the delivered client-id credential |
| `HIVE_AGENT_OIDC_CLIENT_SECRET_FILE` | path of the delivered secret credential |
| `HIVE_AGENT_OIDC_CA_FILE` | optional extra trust anchor; unset in this tree |
| variable | what |
| --------------------------- | ------------------------------------------------ |
| `HIVE_AGENT_LOGS_QUERY_URL` | the LogsQL query endpoint |
| `HIVE_AGENT_NAME` | this agent's name; the store path and role |
| `BAO_ADDR` | the secret store's address |
| `BAO_CLIENT_CERT` | path of the agent's live store certificate |
| `BAO_CLIENT_KEY` | path of its key |
| `BAO_CACERT` | path of the store's CA; missing or empty is fine |
`HIVE_AGENT`, the same prefix the harness reads, because it is the same
identity — an agent authenticates as its hive's agent client whether the
caller is the harness or a CLI the agent typed. The first four are all-or-none:
a half-set environment is a deployment bug, and this binary says so by name
rather than behaving like an unconfigured one.
With no `HIVE_AGENT_LOGS_QUERY_URL` the binary says there is no log store;
with the URL and no store address it reports a deployment bug by name.
## Output