| Filename | Latest commit message | Latest commit date |
|---|---|---|
hive-c0re publishes this hive's status to the swarm queue as the OIDC client `hive-<name>`. Its secret came from `deploy.hive-controller.statusPublish.clientSecretFile`, which defaulted to authelia's minted file only on a host running both authelia and the queue. Every other hive had no secret and never published, so the controller showed it `never_reported`. Now `swarm-secret-publish`, beside authelia, also copies each hive's `hive-<name>` secret to `swarm/hives/<hive>/oidc/client`. Its policy already allows `swarm/hives/*`. hive-c0re reads that path under the hive's own store leaf, which the existing `swarm/hives/<hive>/*` grant covers, and holds the value in memory. The shared queue client no longer remembers a failed first connect for the life of the process. A failed attempt is retried with a backoff from 5s, doubling to a cap of 5 minutes; the dashboard banner stays up until an attempt succeeds. The two status publishers wait for the client instead of giving up on the first `None`, so a secret published after boot is picked up without a restart. Removed: the `statusPublish.clientSecretFile` option (both spellings, now removed options with a message), its same-host default, its LoadCredential, the assertion tying it to the other two coordinates, and `swarm_queue_client::QueueConfig::from_env`, whose only caller was this read. Publishing is now on wherever the queue URL and token endpoint are set and the hive holds a store leaf. Refs #5015 |
||
| .. | ||
| src | ||
| Cargo.toml | ||
| README.md | ||
swarm-logs
An agent's CLI for the swarm log store. One verb — swarm-logs query '<LogsQL>' — so reading logs pipes and greps like any other command
instead of being a hand-rolled token request plus a curl, per query.
Read-only and query-only. There is no ingest path here (that is the
collector's), and no tail: streaming is a different LogsQL endpoint with a
different response shape, left for a follow-up rather than folded in.
Distinct from hive-metric, the other half of the same surface from an
agent's seat: that one writes a custom metric, this one reads logs.
⚠️ There is no scoping
The gateway forwards the query unmodified, so any authenticated caller reads the whole swarm's logs — every hive's, not only its own. That is the rule in force rather than an omission here; when read permissions exist they 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.
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.
🩸 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.
Configuration
Supplied by nix/agent-modules/logs.nix, which wraps the binary — the same
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 |
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.
Output
$ swarm-logs query 'atlas-otel-probe' --limit 5
One log message per line by default (the record's _msg), which is what a
grep pattern is written against. --format json passes the store's NDJSON
through unmodified, for _time, _stream and jq.
A record the projection cannot understand — no string _msg, or not JSON at
all — is printed whole rather than dropped. Silently losing a row would make
an incomplete answer indistinguishable from a complete one.
A non-200 is reported with its body and a non-zero exit, never as an empty result. That is the whole reason the gateway grew a separate machine route: the operator's browser route answers an unauthenticated caller with authelia's login page as a 200 with an HTML body, and a client that reads only the status code records a query that succeeded and matched nothing.