refactor(swarm-queue-client): extract the queue connect into a shared crate
A hive publishing its own status needs the same connect the controller already has - mint an authelia token, present it at CONNECT for the callout responder, let async-nats re-run the callback per attempt. Only the use differs: the controller reads, a hive writes. Copying it would put credential handling in two places, and a token-refresh fix would then have to be found twice. That is the same reasoning that already put hive-sock-client in its own crate rather than in each daemon that speaks to a unix socket. `from_env` takes a prefix rather than hardcoding SWARM_CONTROLLER_*: the variables belong to the consuming unit, since a NixOS module sets them alongside its other options. What is shared is the RULE - all four together or none at all - not the spelling. The half-set case gains a test, because it is the case the rule exists for and it previously had none. No jetstream/kv feature on the crate: it ends at a connected client, and what a consumer does with it should be visible in that consumer's own Cargo.toml. Behaviour-preserving, and proven that way rather than by inspection: the full behavioural gate (real nats-server, credential rotation, mutation) is 20/0 unchanged, and the controller's own tests still pass.
This commit is contained in:
parent
bc594a36ef
commit
a9603214c2
7 changed files with 166 additions and 33 deletions
55
swarm-queue-client/README.md
Normal file
55
swarm-queue-client/README.md
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
# 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. No `jetstream`/`kv` feature is enabled here —
|
||||
what a consumer does with the connection is its own business, and its
|
||||
`Cargo.toml` is where that requirement should be visible.
|
||||
Loading…
Reference in a new issue