swarm-queue-client: re-read a held OIDC client secret when the IdP refuses it
hive-c0re (`hive-<name>`), swarm-controller and swarm-review-classifier read their queue client secret from the store once and held it as `ClientSecret::Value` for the life of the process. After authelia re-minted a secret and swarm-secret-publish stored the new one, every later token mint presented the old value and was refused until the daemon restarted. `ClientSecret::Stored` replaces `Value`: a `StoredSecret` holds the secret in memory with a caller-supplied read callback, so the crate takes no store dependency. When the token endpoint answers 400 or 401, `mint_token` (and its blocking sibling, used by the controller's OTLP exporters) re-reads it and, if the store holds a different secret, mints once more with it. One re-read runs at a time and is spawned so it outlives a caller that gives up. A re-read that fails or finds the refused value delays the next one, 5 s doubling to 5 min, so a secret that stays wrong costs one store read per delay. Logs name the client id and never the secret. swarm-nats-auth's `oidc_secret` was the template; its re-read machinery now sits on `StoredSecret`, so the tree has one copy. The responder gains the same re-read backoff. The controller keeps its `IDENTITY` OnceLock: it now holds a `QueueConfig` whose secret is re-read, and the lazily built vcs-metrics exporter reads the identity from it. agent_renewal.rs's module doc said a re-minted agent queue secret only reaches the agent at its next start; hive-agent reads the store on every connection attempt after its first, so the doc now says that. Part of #5017 (slice Q).
This commit is contained in:
parent
be9b2475a9
commit
a6935e57c3
11 changed files with 819 additions and 376 deletions
|
|
@ -55,6 +55,9 @@ agent-limits = ["async-nats/jetstream"]
|
|||
# Bare (no `kv`/`jetstream`) unless a consumer opts into the `kv` feature
|
||||
# above - the connect itself needs none of them.
|
||||
async-nats.workspace = true
|
||||
# `Shared`, so every caller refused while a secret re-read runs waits on that
|
||||
# one read (`stored_secret`).
|
||||
futures-util.workspace = true
|
||||
# `blocking` on top of the workspace default (`form`/`json`/`rustls`) —
|
||||
# `mint_token_for_blocking` needs `reqwest::blocking::Client` for a caller
|
||||
# with no tokio reactor to `.await` an async request on (an OTLP exporter's
|
||||
|
|
@ -76,5 +79,16 @@ tokio.workspace = true
|
|||
tracing.workspace = true
|
||||
utoipa = { workspace = true, optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
# A loopback stand-in token endpoint for the client secret re-read tests.
|
||||
axum.workspace = true
|
||||
data-encoding.workspace = true
|
||||
# `test-util` for `#[tokio::test(start_paused = true)]`: the re-read backoff
|
||||
# tests run on paused time.
|
||||
tokio = { workspace = true, features = ["test-util"] }
|
||||
# Capturing what a re-read logs, to check it names the client and never the
|
||||
# secret.
|
||||
tracing-subscriber.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
|
|
|||
Loading…
Reference in a new issue