Watch
0
0
Fork
You've already forked hyperhive
0
hyperhive/swarm-logs/README.md
atlas 8be4030d03 swarm-logs-auth: rate-limit verification and cache positive verdicts
Any request on logs.<swarm> carrying a well-formed swarm-agent bearer
made swarm-logs-auth do a bao cert login and KV read before it could
refuse, with no cap on the rate.

nginx now applies limit_req (zone swarm_logs_agent_auth, keyed by
client address, 5r/s burst 20 nodelay by default) on the internal
location that forwards to swarm-logs-auth, answering 403 when over,
since auth_request passes through only 401/403 and swarm-logs retries
a 401. The authelia path and the query location carry no limit.

swarm-logs-auth remembers bearers it has granted, keyed by SHA-256 of
the bearer, for --verdict-ttl seconds (deploy.victorialogs.authVerdictTtl,
60 by default), holding at most 4096 and evicting the oldest. Denials
are never remembered. A replaced or deleted queue secret therefore
keeps working at the log store for up to the TTL after its last check.

Closes #5190
2026-10-14 01:54:54 +02:00

78 lines
3.8 KiB
Markdown

# 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_queue_client::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
```console
$ 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.