Watch
0
0
Fork
You've already forked hyperhive
0
hyperhive/swarm-logs
Repository files (latest commit first)
Filename Latest commit message Latest commit date
atlas 87b1f94ac6 move agent_token from swarm-queue-client to swarm-agent-verify
per #5471, verifiers stop depending on the queue client. the module is
store-free and needs only thiserror, so swarm-agent-verify gains one
dependency and loses its swarm-queue-client edge. consumers import it
from swarm_agent_verify; swarm-logs and swarm-metrics, whose only use of
swarm-queue-client was this module, drop that dependency, as does
swarm-logs-auth.
2026-10-18 16:58:39 +02:00
..
src move agent_token from swarm-queue-client to swarm-agent-verify 2026-10-18 16:58:39 +02:00
Cargo.toml move agent_token from swarm-queue-client to swarm-agent-verify 2026-10-18 16:58:39 +02:00
README.md move agent_token from swarm-queue-client to swarm-agent-verify 2026-10-18 16:58:39 +02:00

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

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_agent_verify::agent_token.

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. swarm-logs-auth grants a secret it has verified for 60 s by default without asking the store again, so a replaced secret also keeps working for up to that long. A 403 is the gateway's per-address rate limit on these checks; it is not retried.

🩸 The secret is never logged or rendered into an error.

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
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

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

$ 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.