Skip to content

feat(auth): verify signatures against the original request path - #1006

Open
renepardon wants to merge 1 commit into
s3s-project:mainfrom
renepardon:local/signed-uri-path-pinned
Open

renepardon wants to merge 1 commit into
s3s-project:mainfrom
renepardon:local/signed-uri-path-pinned

Conversation

@renepardon

Copy link
Copy Markdown

Summary

Servers built on s3s cannot be deployed behind a reverse proxy under a path prefix (for example https://host/tools/rustfs/api/bucket/key). SigV4/SigV2 sign the request path the client sent, and s3s uses the same req.uri.path() for both routing and signature verification. A server that strips the prefix before calling S3Service breaks every signature. A server that does not strip it routes /tools/rustfs/api/bucket/key to bucket tools.

This PR adds an opt-in request extension, s3s::auth::SignedUriPath, holding the path exactly as the client sent and signed it. When it is present, s3s uses it for every path-signing input:

  • SigV4 header auth and presigned URLs (raw_uri_path and the decoded canonical URI are derived from it)
  • SigV2 header auth and presigned URLs (they now use the same raw_uri_path instead of req_uri.path())

Routing (classify_request_path) keeps using req.uri, and POST Object policy signing is unaffected because it signs no path. Without the extension, behaviour is byte-identical to today (map_or(req.uri.path(), ...)). The extension is meant to be set only by trusted server code from static configuration, never from request headers, which the doc comment states. Streaming (aws-chunked) chunk signatures chain from the seed signature and therefore inherit the signed path.

Related issues

Downstream use cases:
rustfs/rustfs#2433 (serving several RustFS instances under path prefixes on one host).
rustfs/rustfs#1069 (Does rustfs support serving under a subpath (e.g., https://xxx.com/rustfs) with configurable basePath?)
rustfs/rustfs#2948 (helm support for subpath domain)
rustfs/rustfs#1427 (console login api with subpath or subrouter support)

Tests

New integration test crates/s3s/tests/signed_uri_path.rs, run for SigV4 header, SigV4 presigned, SigV2 header and SigV2 presigned. For each scheme it checks:

  • signed over /prefix/bkt/k, dispatched as /bkt/k with the extension: 200, handler sees bucket bkt, key k
  • the same request without the extension: 403 SignatureDoesNotMatch, handler not called
  • an extension that differs from the signed path (/other/bkt/k, /prefix/bkt/k/, /bkt/k): 403 SignatureDoesNotMatch
  • no extension, signed over the URI path: 200 (unchanged behaviour)

Checks run on top of current main:

  • cargo fmt --all --check
  • cargo clippy --workspace --all-features --all-targets (clean)
  • cargo test -p s3s --all-features --test signed_uri_path (5 passed)
  • cargo test -p s3s --all-features --lib (1048 passed)

The change was also exercised end to end in RustFS (aws-sdk-s3 and aws-cli, header auth, multipart, presigned GET/PUT, prefix tampering in both directions rejected with 403) against a local k3s deployment.

Add the opt-in request extension `auth::SignedUriPath`. Servers that
route on a rewritten path (for example after stripping a deployment
path prefix) insert the path the client sent and signed; s3s then uses
it for SigV4 and SigV2 header and presigned verification while routing
keeps using the request URI. Without the extension behaviour is
unchanged.

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