Skip to content

row 74 F25: a base URL's or token endpoint's userinfo rides as Authorization: Basic where 1.5.5 sent it; no plane receives it - #913

Open
MattJackson wants to merge 3 commits into
lane-row74-userinfofrom
lane-row74-userinfo-basic
Open

MattJackson wants to merge 3 commits into
lane-row74-userinfofrom
lane-row74-userinfo-basic

Conversation

@MattJackson

Copy link
Copy Markdown
Collaborator

Stacked on #876 (base lane-row74-userinfo). That PR strips the userinfo; this one adds the ARCHITECT ruling on top: 1.5.5 wins on Authorization: Basic, and a plane never receives a URL that carries userinfo.

1.5.5 citations

  • Lane requests: 1.5.5 built them with reqwest over {base_url}{path}, with the credential added after. See v1.5.5 crates/busbar/src/proxy/engine/mod.rs:1669-1670, proxy/engine/walk.rs:583-584 and health.rs:313-314.
  • oauth-client-credentials mint: crates/busbar/src/egress_auth/oauth_client_credentials.rs:119-126, .post(&self.token_url).form(..).
  • jwt-bearer mint: crates/busbar/src/egress_auth/jwt_bearer.rs:175-181, .post(&self.token_uri).form(..).
  • reqwest 0.12.28: RequestBuilder::new (src/async_impl/request.rs:169-182) takes the userinfo out (extract_authority, :581-605, percent-decoded) and adds it FIRST as a sensitive authorization: Basic (src/util.rs:4-25). .headers(auth) (replace_headers, src/util.rs:50-77) replaces it only when the credential is itself named authorization. A form POST has no authorization field, so the mints always carried it.

What

  • Base URL: the kernel holds the Basic value as UrlUserinfo, an opaque value that never prints. Both send paths put it first unless the request already has authorization:
    • the in-process lane's send, through the kernel's owned client, handed in as a request extension;
    • the door path's attempt head, in far_end.
  • token_url, door path: OutboundAuths::serving_with strips the URL before the auth plugin opens and before its binding. The plugin's connection table is wrapped so each mint leads with the Basic value.
  • token_url, in-process lane: the userinfo is stripped before the plane sees the URL (LaneInput.token_url). The Basic value goes alongside as LaneInput.token_userinfo, through the kernel's bind_presenting, to the new AuthAxis::serving_presenting.
  • jwt-bearer token_uri: the same rule. The mint need is pinned to the stripped endpoint and each mint leads with the Basic value. The assertion's aud stays the plugin's own reading of the service account, as in 1.5.5.
  • Messages: validation and seal refusals repeat base_url, token_url and token_uri with the userinfo redacted. The message literals are unchanged.
  • Scope: no wider than these three URLs.

REDs

  • Kernel:
    • The reqwest decode rules and the Basic value they produce.
    • The send hop's order and condition.
    • A wire test through the owned client: an x-api-key credential gets Basic first; an authorization credential gets none.
    • far_end: an x-api-key binding gets Basic first; a Bearer binding gets none.
    • The endpoint a plane is handed carries no userinfo, and the auth axis receives the Basic value next to the settings, never inside them.
  • busbar-llm:
    • A keyless lane's organic forward reaches the stripped host with the Basic value.
    • A probe with an authorization credential carries the credential alone.
  • Root:
    • The OAuth member test runs once with a plain token_url and once with a userinfo token_url: the need is pinned to the stripped endpoint and the userinfo version's mints lead with Basic.
    • A service-account token_uri with userinfo seals, is pinned to and dialled at the stripped endpoint, and each mint leads with Basic.
    • The in-process shape (stripped settings plus the held Basic) leads the token POST with Basic.
  • config_validate: a planted scan of every base_url and token_url refusal finds no svcuser or hunter2.

…exactly where 1.5.5 sent it

ARCHITECT ruling on #876: 1.5.5 wins. 1.5.5 built every provider request with reqwest over
`{base_url}{path}` and its credential after (v1.5.5 crates/busbar/src/proxy/engine/mod.rs:1669-1670,
proxy/engine/walk.rs:583-584, health.rs:313-314). reqwest 0.12.28's RequestBuilder::new
(src/async_impl/request.rs:169-182) took the userinfo out (extract_authority :581-605, percent-
decoded) and added it FIRST as a sensitive `authorization: Basic` (src/util.rs:4-25); `.headers(auth)`
(replace_headers, src/util.rs:50-77) replaced it only when the credential named `authorization`.

- busbar-kernel egress engine: UrlUserinfo, the Basic value a base URL's userinfo spells, opaque
  (no accessor outside the kernel, Debug is a marker, sensitive). present_url_userinfo, run by the
  owned client's send, puts it FIRST unless the request names `authorization`, and takes the
  extension off either way.
- appbuild seats it on LaneInput (url_userinfo); the lane plane hands it back to the kernel hop as
  an opaque request extension on organic sends and probes. The plane never reads its bytes.
- door path: ProviderRoute and MemberRoute carry it; far_end puts it first in the attempt head
  when the auth binding's fields name no `authorization`, and a plane field named `authorization`
  never doubles it.

REDs: kernel unit cases for the reqwest decode/emptiness arms; the hop's order and condition; an
h1 wire test through the owned client (x-api-key credential: Basic first; authorization
credential: none); far_end (x-api-key binding: Basic first; Bearer binding: none); the lane's
organic forward with a keyless lane hears the Basic at the stripped host; the probe with an
authorization credential hears it alone.
…Authorization: Basic, as 1.5.5 did

ARCHITECT ruling on #876 (2): 1.5.5 POSTed the client credentials with reqwest to token_url as
written (v1.5.5 crates/busbar/src/egress_auth/oauth_client_credentials.rs:119-126,
`.post(&self.token_url).form(..)`); reqwest 0.12.28's builder took a `user:pass@` out of the URL
and sent it FIRST as `authorization: Basic` (src/async_impl/request.rs:169-182, extract_authority
:581-605, util::basic_auth src/util.rs:4-25), and the form named no authorization, so it always
rode. 1.6.0 refused it (the connector's carries_userinfo on the mint need's pinned target).

- root OutboundAuths::serving (the one choke point both the door path and the in-process lane
  path bind through): a settings `token_url` spelling userinfo is stripped before the minting
  plugin's instance is opened (its need is pinned to, and its mint dials, the stripped endpoint)
  and before its binding's open_outbound (EndpointStripped); the instance is loaded over a
  connection table (PresentingConns) that leads every open naming no `authorization` with the
  endpoint's Basic value (kernel UrlUserinfo::leading). The instance key stays the settings as
  written, so endpoints differing only in userinfo never share an instance.
- config_validate: the token_url refusals repeat it redacted (literals unchanged); the credential
  dry-run hands the plugin the stripped endpoint.
- config sections destinations(): the carve-out keys on the stripped token_url too.

REDs: the oauth member test runs over a plain and a userinfo token_url: the need is declared and the
mint dialled at the stripped endpoint, the userinfo's mints lead with the Basic value, the plain
one's carry no authorization; the config_validate planted scan covers the token_url refusals.
…des the mint as 1.5.5 did

ARCHITECT ruling (stack on #876):
(i) The in-process lane plane is handed token_url with its userinfo stripped (appbuild
lane_token_endpoint); the Basic value it spelled rides beside it opaque (LaneInput.token_userinfo,
kernel UrlUserinfo), handed back through kernel bound_credential::bind_presenting to the auth axis's
new AuthAxis::serving_presenting (contract default: not presented), which the root implements over
OutboundAuths::serving_with: the minting instance is loaded over the presenting connection table,
so the token POST leads with `authorization: Basic` exactly as 1.5.5's reqwest POST to token_url
did (v1.5.5 crates/busbar/src/egress_auth/oauth_client_credentials.rs:119-126).
(ii) jwt-bearer: 1.5.5 POSTed the signed assertion with reqwest to the service account's token_uri
as written (v1.5.5 crates/busbar/src/egress_auth/jwt_bearer.rs:175-181, `.post(&self.token_uri)
.form(..)`), so the same userinfo -> Basic rule held (reqwest 0.12.28 request.rs:169-182,
extract_authority :581-605, util.rs:4-25; the form names no authorization, so it always rode). The
root's settings reader now covers token_uri beside token_url: the mint need is pinned to the stripped
endpoint, the mint's target is stripped and led by the Basic value; the assertion's aud stays the
plugin's own reading of the service account, as 1.5.5's did. vet_token_uri's refusals repeat it
redacted (literals unchanged).
(iii) Nothing wider: base_url, token_url and token_uri are the operator URLs 1.5.5 sent through that
reqwest rule on these paths.

REDs: kernel: the endpoint a plane is handed carries no userinfo and the axis receives the Basic
beside the settings, never inside them; root: a service account token_uri spelling a userinfo seals,
pins and dials the stripped endpoint and leads each mint with the Basic; the in-process shape (axis
handed stripped settings plus the held Basic) leads the token POST with the Basic.
@MattJackson

Copy link
Copy Markdown
Collaborator Author

FYI (queue rule 20): $ draft #870 also edits crates/busbar-kernel/src/config_validate/{mod.rs:1553 rate-refusal message, tests/tests.rs:4178-4216 rate test}; hunks are disjoint from yours and merge clean.

@MattJackson

Copy link
Copy Markdown
Collaborator Author

row 155 B2 (#974) edits crates/busbar-kernel/src/bound_credential.rs at DeclaredBuilder::headers_for; disjoint from this PR's hunks.

@MattJackson

Copy link
Copy Markdown
Collaborator Author

Rule 20 note from #1031 (row 155, draft): disjoint hunks in root/door_steps.rs (kernel_sections limits) and root/tests/door_steps.rs (one test appended). A test merge is clean.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant