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
This commit is contained in:
parent
d8f6d99bf9
commit
d9d6d37951
2 changed files with 44 additions and 33 deletions
|
|
@ -48,6 +48,41 @@ 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
|
per token request rather than cached, so a rotation the operator believes took
|
||||||
effect actually did.
|
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
|
## What this crate does not do
|
||||||
|
|
||||||
It ends at a connected client. `jetstream`/`kv` are **off by default** — what a
|
It ends at a connected client. `jetstream`/`kv` are **off by default** — what a
|
||||||
|
|
|
||||||
|
|
@ -460,42 +460,18 @@ fn parse_token_response(status: reqwest::StatusCode, body: &str) -> Result<Cache
|
||||||
/// Build the token request: `client_credentials`, authenticated with HTTP
|
/// Build the token request: `client_credentials`, authenticated with HTTP
|
||||||
/// Basic.
|
/// Basic.
|
||||||
///
|
///
|
||||||
/// 🩸 **The credentials go in the `Authorization` header, not the form body.**
|
/// 🩸 **The credentials go in the `Authorization` header, not the form
|
||||||
/// Both are legal OAuth 2.0 — `client_secret_basic` and `client_secret_post` —
|
/// body.** A client registration names one flavor, and authelia's default
|
||||||
/// but a client registration names *one*, and authelia's default (and ours) is
|
/// (and ours) is Basic — the other one once produced an hour of misleading
|
||||||
/// Basic. Sending them in the body got every token request refused with
|
/// `429`s before the real cause (every request refused at the auth step)
|
||||||
/// `Client authentication failed … the registered client is configured to only
|
/// surfaced. `audience` and `scope` are both opt-in, RFC 8707 /
|
||||||
/// support 'client_secret_basic'`, which reached the operator as an endless
|
/// registered-scope territory; `None` for either reproduces the request
|
||||||
/// `429` because the retries tripped a rate limiter whose penalty grew faster
|
/// this crate sent before the parameter existed. See the crate README's
|
||||||
/// than the retry interval. The 429 then arrived *before* the credentials were
|
/// "Token request shape" section for the incident, the RFC citations, and
|
||||||
/// ever evaluated, so the one line naming the real cause appeared once an hour.
|
/// why each parameter exists.
|
||||||
///
|
|
||||||
/// 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.
|
|
||||||
///
|
///
|
||||||
/// Split out of [`mint_token`] so the request's *shape* is testable without a
|
/// Split out of [`mint_token`] so the request's *shape* is testable without a
|
||||||
/// running identity provider — see the tests at the bottom of this file.
|
/// running identity provider — see the tests at the bottom of this file.
|
||||||
///
|
|
||||||
/// `audience` is RFC 8707 resource-indicator territory, and it is opt-in:
|
|
||||||
/// omitted, this identity gets whatever audience authelia defaults a
|
|
||||||
/// scopeless `client_credentials` grant to (the queue connection's own
|
|
||||||
/// case — it has always worked without asking). A caller proving this
|
|
||||||
/// identity to a SPECIFIC audience-checked receiver — the swarm-otel
|
|
||||||
/// `oidc/swarm` authenticator being the first one — has to ask for it 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` is opt-in for the same reason 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.
|
|
||||||
fn token_request(
|
fn token_request(
|
||||||
http: &reqwest::Client,
|
http: &reqwest::Client,
|
||||||
cfg: &QueueConfig,
|
cfg: &QueueConfig,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue