Skip to content

feat(sts): accept AssumeRoleWithWebIdentity from form-encoded POST bodies - #196

Merged
alukach merged 1 commit into
mainfrom
worktree-sts-form-body
Jul 27, 2026
Merged

feat(sts): accept AssumeRoleWithWebIdentity from form-encoded POST bodies#196
alukach merged 1 commit into
mainfrom
worktree-sts-form-body

Conversation

@alukach

@alukach alukach commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

AWS SDKs send query-protocol operations as a form-encoded POST body with no query string at all, but the worker only ever surfaced the query string to the router. The STS handler reads its parameters from RequestInfo::form_body, which stayed None, so every SDK-shaped request fell through to the S3 pipeline.

multistore 0.7.0 (developmentseed/multistore#112, pulled in by #190) added RequestParts::absorb_form_body for exactly this. The dep bump alone does not enable it — the runtime has to call it, which is what this PR does.

Behavior

POST /.sts with Action=AssumeRoleWithWebIdentity&RoleArn=_default&WebIdentityToken=… in the body:

before after
response 400 InvalidRequest: unsupported POST operation routed to the STS handler

Verified locally against wrangler dev with the stub API and a throwaway OIDC key, rebuilding the worker each way to confirm the difference is this change and not a stale isolate.

Safety

absorb_form_body is a no-op passthrough for every request shape that is not a form-encoded POST, and it rebuilds the body from the collected bytes — so a mislabeled S3 write (Content-Type is client-controlled: CompleteMultipartUpload, DeleteObjects) still reaches the gateway with its payload intact. Collection is skipped entirely for a form POST with a missing or oversized Content-Length rather than buffering it into WASM memory.

Tests

  • test_sts_form_encoded_body_reaches_the_sts_handler — needs no identity token, so it runs on fork PRs. Asserts the response is an STS rejection (InvalidIdentityToken) rather than the S3 fall-through; the status code alone does not discriminate, since both are 400. Fails on main.
  • test_sts_exchange_accepts_a_form_encoded_body — the positive path, under the existing needs_token gate.

Full local integration run: 24 passed, 11 skipped (credentialed tier, no GitHub OIDC token locally).

Scope

Part of #184. This is one of the two blockers for a true SDK drop-in; the other is #185 — AWS SDKs validate RoleArn client-side (min 20 chars), so _default is rejected before the request is ever sent. Both are needed before boto3.client("sts", endpoint_url=…) works unmodified.

🤖 Generated with Claude Code

…dies

AWS SDKs send query-protocol operations as a form-encoded POST body with
no query string at all, but the worker only ever surfaced the query string
to the router. The STS handler reads its parameters from
`RequestInfo::form_body`, which stayed `None`, so every SDK-shaped request
fell through to the S3 pipeline and came back
`InvalidRequest: unsupported POST operation` instead of credentials.

multistore 0.7.0 (developmentseed/multistore#112) added
`RequestParts::absorb_form_body` for exactly this; wire it in. It is a
no-op passthrough for every other request shape, and rebuilds the body
from the collected bytes so a mislabeled S3 write — `Content-Type` is
client-controlled — still reaches the gateway with its payload intact.
Collection is skipped for a form POST with a missing or oversized
`Content-Length` rather than buffering it into WASM memory.

Verified locally against `wrangler dev` (stub API + throwaway OIDC key):
before, a form-encoded POST /.sts returns `InvalidRequest: unsupported
POST operation`; after, it reaches the STS handler and returns
`InvalidIdentityToken` for a bad token. The new routing test pins that
distinction and needs no OIDC token, so it runs on fork PRs too.

Part of #184. The remaining blocker for a true SDK drop-in is #185: AWS
SDKs validate `RoleArn` client-side (min 20 chars), so `_default` is
rejected before the request is ever sent.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@claude

claude Bot commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Claude finished @alukach's task in 3m 2s —— View job


Review of PR #196

  • Gather context (diff, changed files)
  • Review src/lib.rs changes
  • Review tests/test_writes.py changes
  • Post findings

✅ No blocking issues — safe to merge.

  • The absorb_form_body call is placed before path-mapping and before the .with_form_body(...) builder call is threaded into every RequestInfo, not just the /.sts route (src/lib.rs:143-146, src/lib.rs:328). For any non-form-encoded request (all S3 reads/writes), parts.form_body stays None, so .with_form_body(None) is a no-op — consistent with the PR's stated safety guarantee that a mislabeled S3 write's payload reaches the gateway intact.
  • The reassignment to mut js_body/mut parts (src/lib.rs:125,143) correctly threads the rebuilt body back into the later gateway.handle_request(...) call (src/lib.rs:332), so nothing reads the pre-absorption body by mistake.
  • Test coverage is good: test_sts_form_encoded_body_reaches_the_sts_handler (needs no token, so it runs on fork PRs) asserts on InvalidIdentityToken rather than just the status code — important since both the old (S3 fall-through) and new (STS rejection) behavior return 400, so the status code alone wouldn't catch a regression back to the old behavior.
  • Minor/non-blocking: the absorption call runs before the STS-disabled fail-closed check (src/lib.rs:173), so a POST /.sts while AUTH_AUDIENCE is unset still pays for form-body collection before being rejected with 501. Bounded by the crate's own missing/oversized-Content-Length skip, so not a real risk — just a candidate for reordering if it ever matters.

@github-actions

Copy link
Copy Markdown

🚀 Latest commit deployed to https://source-data-proxy-pr-196.source-coop.workers.dev

  • Date: 2026-07-27T05:56:40Z
  • Commit: eda02db

@alukach
alukach marked this pull request as ready for review July 27, 2026 06:15
@alukach
alukach merged commit f77e8d2 into main Jul 27, 2026
14 checks passed
@alukach
alukach deleted the worktree-sts-form-body branch July 27, 2026 17:35
alukach pushed a commit that referenced this pull request Jul 27, 2026
🤖 I have created a release *beep* *boop*
---


##
[2.3.0](v2.2.2...v2.3.0)
(2026-07-27)


### Features

* **backend:** enable GCS backend (multistore gcp + wasm-compatible
signing)
([#191](#191))
([33f2b25](33f2b25))
* **s3:** enforce conditional-write preconditions on PutObject
([003db0d](003db0d))
* **s3:** support server-side CopyObject via x-amz-copy-source
([97d5121](97d5121))
* **sts:** accept ARN-shaped alias for the _default role
([#185](#185))
([8da7e72](8da7e72))
* **sts:** accept AssumeRoleWithWebIdentity from form-encoded POST
bodies
([#196](#196))
([f77e8d2](f77e8d2))


### Bug Fixes

* **gcs:** pass bucket_name to multistore GCS store
([#193](#193))
([0efb66f](0efb66f))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

Co-authored-by: source-release-bot[bot] <265100246+source-release-bot[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant