From d9d6d3795145b9fb69c941905662073897d59524 Mon Sep 17 00:00:00 2001 From: atlas Date: Thu, 17 Sep 2026 10:01:11 +0200 Subject: [PATCH] swarm-queue-client: trim token_request comment block under the 30-line lint The scope-parameter addition pushed the doc comment on token_request to 39 lines. Moved the HTTP-Basic incident story and the audience/scope rationale to the crate README's new "Token request shape" section (docs/ is markdown, exempt from the lint); the code comment keeps the pointer plus the one-line summary of the invariant. Refs #4464 --- swarm-queue-client/README.md | 35 +++++++++++++++++++++++++++++ swarm-queue-client/src/lib.rs | 42 ++++++++--------------------------- 2 files changed, 44 insertions(+), 33 deletions(-) diff --git a/swarm-queue-client/README.md b/swarm-queue-client/README.md index 221fa029..8ce8b07a 100644 --- a/swarm-queue-client/README.md +++ b/swarm-queue-client/README.md @@ -48,6 +48,41 @@ would publish it to anything that can read `/proc//environ`. It is read per token request rather than cached, so a rotation the operator believes took effect actually did. +## Token request shape: HTTP Basic, `audience`, `scope` + +`token_request` builds a `client_credentials` grant, authenticated with HTTP +Basic — **never** the form body. Both are legal OAuth 2.0 +(`client_secret_basic` vs `client_secret_post`), but a client registration +names one, and authelia's default (and ours) is Basic. Sending the +credentials in the body once got every token request refused with `Client +authentication failed … the registered client is configured to only support +'client_secret_basic'`, which reached the operator as an endless `429` +because the retries tripped a rate limiter whose penalty grew faster than the +retry interval — the 429 arrived _before_ the credentials were ever +evaluated, so the line naming the real cause appeared once an hour. RFC 6749 +§2.3.1 says clients SHOULD use Basic, both introspection callers in this +workspace already do, and a secret in a header is one fewer place for a proxy +to log it. + +`audience` (RFC 8707 resource indicators) and `scope` are both opt-in; +`None` for either reproduces the request this crate sent before the +parameter existed. Omitted, `audience` gets whatever authelia defaults a +scopeless `client_credentials` grant to — the queue connection's own case, +which has always worked without asking. A caller proving this identity to a +specifically audience-checked receiver (the swarm-otel `oidc/swarm` +authenticator being the first one) has to ask by name, the same way +`swarm-otel.nix`'s own prometheus scrape config already does per target +(`endpoint_params.audience`): a token minted without asking carries `aud: +[]`, and an audience-checked receiver refuses that just as readily as the +wrong one. `scope` follows the same rule and travels with `audience`: +registration is not issuance, and the authorisation server grants no scope +the client never requested. A caller reaching a destination behind +authelia's `/api/authz/auth-request` needs `authelia.bearer.authz` here +however completely the client is registered for it — the failure +`swarm-otel.nix` records against its own client (a scopeless token refused +at introspection with "the requested scope is invalid, unknown, or +malformed") is the same one, one layer down. + ## What this crate does not do It ends at a connected client. `jetstream`/`kv` are **off by default** — what a diff --git a/swarm-queue-client/src/lib.rs b/swarm-queue-client/src/lib.rs index 7f04c712..1b6466bb 100644 --- a/swarm-queue-client/src/lib.rs +++ b/swarm-queue-client/src/lib.rs @@ -460,42 +460,18 @@ fn parse_token_response(status: reqwest::StatusCode, body: &str) -> Result