Watch
0
0
Fork
You've already forked hyperhive
0

swarm-nats-auth: re-read the OIDC client secret only on a 400 or 401

The responder re-read its secret on any non-2xx introspection answer.
With the re-read backoff added in the previous commit, an IdP outage
(503s, 502s from nginx, 429s) made each re-read find the unchanged
secret, so the delay grew to minutes. A real 401 after the rotation
then landed inside that window, and the responder denied every new
NATS connect until it ran out.

`Answer::Refused` now carries the status. `OidcSecret::introspect`
re-reads only when `stored_secret::is_credential_refusal` says the
status is 400 or 401, the same predicate `ClientSecret::refused` uses on
the client path. Every other non-2xx is a denial with no re-read.

The client-path test for a 429 now runs the mint against a loopback
endpoint answering 429 and 503, instead of calling `refused()` directly.
swarm-queue-client's README no longer names `ClientSecret::Value`.

Part of #5017 (slice Q).
This commit is contained in:
atlas 2026-10-12 22:02:53 +02:00 • committed by mara
commit 70c13aee7a
5 changed files with 106 additions and 43 deletions

View file

@ -47,10 +47,11 @@ pub const INTROSPECTION_TIMEOUT: Duration = Duration::from_millis(1500);
pub enum Answer {
/// A 2xx: the caller, or `None` for every shape that is not an admission.
Caller(Option<String>),
/// A non-2xx: the `IdP` did not answer about the token at all, which is
/// also how it refuses this responder's own credential. `oidc_secret`
/// re-reads that credential and retries once before this denies.
Refused,
/// A non-2xx, with its status: the `IdP` did not answer about the token
/// at all. A 400 or 401 is how it refuses this responder's own
/// credential, which `oidc_secret` re-reads and retries once before this
/// denies.
Refused(reqwest::StatusCode),
}
/// The two fields this responder acts on — one per decision. Authelia returns
@ -92,7 +93,7 @@ impl IntrospectionResponse {
/// Returns `Ok(Answer::Caller(Some(client_id)))` **only** on a 2xx whose
/// body says `active: true` and names the client. Every other 2xx is
/// `Ok(Answer::Caller(None))` with the reason logged, a non-2xx is
/// `Ok(Answer::Refused)`, and `Err` is a call that could not be made at all
/// `Ok(Answer::Refused(_))`, and `Err` is a call that could not be made at all
/// or did not finish by `deadline` — callers must treat all but the first as
/// a denial.
///
@ -123,7 +124,7 @@ pub async fn identify_caller(
let status = resp.status();
if !status.is_success() {
tracing::warn!(%status, "introspection returned non-2xx");
return Ok(Answer::Refused);
return Ok(Answer::Refused(status));
}
let body: IntrospectionResponse = match resp.json().await {
Ok(b) => b,