| Filename | Latest commit message | Latest commit date |
|---|---|---|
The queue client built a bare reqwest::Client, so it trusted only the platform roots. Against a swarm whose authelia is signed by the swarm CA that is fatal: minting a token dies with 'invalid peer certificate: UnknownIssuer', inside the auth callback, on a four-second retry loop, with the queue never connecting. The anchor was never missing. hive-tls.nix assembles trust-bundle.pem and already hands it to hive-c0re as HIVE_TLS_CA_PATH; nothing pointed the queue client at it. QueueConfig gains an optional ca_file from <prefix>_OIDC_CA_FILE, read outside the all-or-none tuple on purpose: a CA path with no queue is meaningless rather than half-configured, and requiring it would break a swarm fronted by a public certificate in order to fix one that is not. add_root_certificate extends the default roots rather than replacing them, so both deployments work. A bad path fails loudly instead of falling back to the platform roots. An operator who names a CA file wants that anchor; a silent fallback turns their typo into UnknownIssuer five layers away. hive-tls.nix names the bundle for both clients, beside the line that already does it for hive-c0re, rather than having each consumer re-derive the path. |
||
| .. | ||
| src | ||
| Cargo.toml | ||
| README.md | ||
swarm-queue-client
Connecting to the swarm message queue as an authenticated client. Shared by every process that participates: the swarm controller reads hive status out of the queue, a hive publishes its own status into it.
Why a crate and not a module per binary
The connect is identical for every participant — mint an authelia token,
present it at CONNECT for the auth_callout responder to introspect, let
async-nats re-run the callback on each connection attempt. Only the use
differs.
Two copies of that would be two copies of credential handling, and a
token-refresh fix would have to be found twice. The same reasoning already put
hive-sock-client in its own crate rather than in each daemon that speaks to a
unix socket.
The two properties that constrain the code
A token expires. Authelia issues client_credentials access tokens with
expires_in: 3599. Authentication happens at CONNECT, so a long-lived
connection is fine — but a reconnect an hour later needs a token minted an
hour later.
The refresh therefore lives in the auth callback, not in a timer.
async-nats invokes it per connection attempt, so there is no window in which
the client holds a token it minted for a previous connection. The alternative —
mint once, own the reconnect loop — fails in the way this subsystem exists to
prevent: the process keeps serving while its data quietly stops moving, and
nothing says so until someone reads a dashboard.
Configuration
QueueConfig::from_env(prefix) reads <prefix>_NATS_URL,
<prefix>_OIDC_TOKEN_ENDPOINT, <prefix>_OIDC_CLIENT_ID and
<prefix>_OIDC_CLIENT_SECRET_FILE.
The prefix is a parameter because the variables belong to the consuming unit — a NixOS module sets them alongside its other options. What is shared is the rule, not the spelling: all four together or none at all. A half-set environment is a hard error, because the failure it would otherwise produce is the expensive kind — the process comes up "fine", never connects, and the data it was supposed to move silently stops.
The client secret is a path, not a value: putting it in the environment
would publish it to anything that can read /proc/<pid>/environ. It is read
per token request rather than cached, so a rotation the operator believes took
effect actually did.
What this crate does not do
It ends at a connected client. jetstream/kv are off by default — what a
consumer does with the connection is its own business, and its Cargo.toml is
where that requirement should be visible. The auth-callout responder speaks the
connect and nothing else, and pays for nothing else.
The one exception: the kv feature
kv adds status, which holds the name and the creation config of the
hive-status bucket — nothing more.
It is here because that bucket has two ends in two crates: a hive writes its
own key, the controller reads every key. The name being a repeated literal is
the mild half of the problem; the sharp half is that either end may arrive first
on a fresh swarm, so both create the bucket if it is missing. Two Configs that
drift means whichever end created it wins and the other opens a bucket it did
not ask for — no error, no log, just a retention policy nobody chose.
An agreement between two crates has to live in one of them, and neither end of this bucket is senior to the other. Behind a default-off feature, the consumer that needs none of it still pays nothing.