swarm-logs: an agent's CLI for the swarm log store
An agent can reach VictoriaLogs only through the gateway, and since the
machine query route landed the way to read it has been to hand-roll a
client_credentials token request and a curl, per query. This is the CLI
that closes that: `swarm-logs query '<LogsQL>'`, matched log lines on
stdout, so the answer pipes into grep like any other command's.
Built to the plan posted on the tracker thread: own crate, own
docs/tools reference generated off the clap tree, `query` as the one
verb, and the JSON error body surfaced on a non-200 rather than
swallowed. No `tail`: streaming is a different endpoint with a different
response shape, and folding it in here would be a fatter scope than the
ask.
Minting the token is NOT implemented here — swarm-queue-client already
owns the client_credentials 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's
swarm_queue module solves, and swarm-logs/src/auth.rs is its `decide`
restated over this binary's inputs.
⚠️ The plan named one thing to verify empirically before calling the auth
settled: whether authelia's bearer policy for the logs vhost accepts the
agent client's audience. Measured from inside a container: it does not.
The client minted a token fine but with `aud: []` and `scp: []`, asking
for the logs URL as an audience answered `invalid_target`, and presenting
the audience-less token to the gateway answered a bare 401. So
swarm-authelia.nix's agentClients gains `authelia.bearer.authz` and the
query URL as a second audience — authelia authorises a bearer token by
the URL being requested, and that URL is now one binding read by three
places rather than three spellings of one address.
The URL reaches an agent the same way its queue coordinates do: computed
on the host (a container cannot derive a gateway address), forwarded by
hive_c0re::meta into the container's option set, and consumed by a new
agent module that installs the binary *wrapped* with its coordinates —
the shape swarm-controller.nix installs swarmctl in. Gated on the queue
credential as well as on the URL: a binary that can only answer 401 is
worse than no binary, because an agent reads a 401 as "no logs", which is
the exact confusion the store's machine route was added to end.
This commit is contained in:
parent
2186b82485
commit
a39399f037
18 changed files with 939 additions and 5 deletions
|
|
@ -144,6 +144,15 @@ let
|
|||
accessTokenSignedResponseAlg = "RS256";
|
||||
}) hyperhiveCfg.swarm.hives;
|
||||
|
||||
# The one URL an agent's token has to be authorised at beyond the queue:
|
||||
# the log store's machine query route. Read off `swarm.victorialogs.domain`
|
||||
# — an unconditional option, declared whether or not this host runs the
|
||||
# store — rather than restated, because it is also the string `swarm-logs`
|
||||
# requests as its audience and the string `hive-c0re/environment.nix` hands
|
||||
# an agent as its endpoint. Three readers, one formula; two spellings
|
||||
# present as a valid token refused at the store.
|
||||
logsQueryUrl = "https://${hyperhiveCfg.swarm.victorialogs.domain}/select/logsql/query";
|
||||
|
||||
# The identity an agent container presents to the swarm's queue. One per
|
||||
# HIVE, not per agent, and that is the load-bearing choice rather than a
|
||||
# shortcut: agents are created at **runtime**, so anything minted per agent
|
||||
|
|
@ -165,16 +174,38 @@ let
|
|||
# match site, and the assertion below covers the case that ordering cannot.
|
||||
#
|
||||
# Mirrors `hiveClients` field for field so the two stay comparable. The
|
||||
# signing algorithm is not load-bearing here — this client's only consumer is
|
||||
# signing algorithm is not load-bearing here — this client's consumers are
|
||||
# the queue's auth-callout responder, which *introspects* rather than
|
||||
# verifying offline — but it matches its sibling rather than inventing a
|
||||
# second answer to a question nobody asked.
|
||||
# verifying offline, and authelia's own authz endpoint — but it matches its
|
||||
# sibling rather than inventing a second answer to a question nobody asked.
|
||||
agentClients = lib.mapAttrsToList (name: _: {
|
||||
id = "${cfg.hiveClientPrefix}${name}${cfg.agentClientSuffix}";
|
||||
description = "HyperHive agents on hive ${name}";
|
||||
kind = "machine";
|
||||
redirectUris = [ ];
|
||||
audience = [ "${cfg.hiveClientPrefix}${name}${cfg.agentClientSuffix}" ];
|
||||
# ⚠️ **Two** audiences, where `hiveClients` has one, because an agent
|
||||
# presents this token at two different kinds of consumer. The queue
|
||||
# introspects it and reads the client id; the log store's gateway
|
||||
# location runs `auth_request` against authelia, which authorises a
|
||||
# bearer token **by the URL being requested** and refuses one whose
|
||||
# audience does not name it. Measured, not assumed: without the URL
|
||||
# registered here the token endpoint answers `invalid_target` for it,
|
||||
# and a token minted with no audience at all is answered by the gateway
|
||||
# with a bare 401 that names no cause.
|
||||
audience = [
|
||||
"${cfg.hiveClientPrefix}${name}${cfg.agentClientSuffix}"
|
||||
logsQueryUrl
|
||||
];
|
||||
# Without it the authz endpoint refuses an otherwise valid token and
|
||||
# blames the token rather than the missing grant — the same note
|
||||
# `glue-swarm-otel-oidc-client.nix` carries over the collector's client.
|
||||
bearerAuthz = true;
|
||||
# Stated rather than left on authelia's default, for
|
||||
# `glue-swarm-otel-oidc-client.nix`'s reason: authelia permits only
|
||||
# basic / JWT methods for a confidential client holding this scope, and
|
||||
# enforces it in the startup validator. `swarm-queue-client` sends the
|
||||
# credential as basic auth, so this is also what the minter already does.
|
||||
tokenEndpointAuthMethod = "client_secret_basic";
|
||||
accessTokenSignedResponseAlg = "RS256";
|
||||
}) hyperhiveCfg.swarm.hives;
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue