Skip to content

proxy: support presigned (query string auth) SigV4 requests - #256

Open
FredNass wants to merge 1 commit into
mainfrom
proxy-presigned-v4
Open

proxy: support presigned (query string auth) SigV4 requests#256
FredNass wants to merge 1 commit into
mainfrom
proxy-presigned-v4

Conversation

@FredNass

@FredNass FredNass commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Description

The proxy auth middleware only recognizes header-based Authorization signatures (SigV4, and SigV2 when enabled). A presigned URL carries its SigV4 parameters in the query string (X-Amz-Algorithm, X-Amz-Credential, X-Amz-Date, X-Amz-Expires, X-Amz-SignedHeaders, X-Amz-Signature) and no Authorization header, so isReqAuthenticated() falls through to 400 CredentialsNotSupported without contacting the backend. Any client relying on presigned URLs (browser-based download flows of S3 gateways and web front-ends, aws s3 presign, rclone link, SDK generate_presigned_url) is broken behind the proxy.

Changes:

  • pkg/s3/presign.go: ParsePreSignV4(url.Values) parses the query-string authentication parameters into PreSignValues (credential scope, date, expires, signed headers, signature), mirroring the existing ParseSignV4 for the Authorization header.
  • service/proxy/auth/signature_v4_presign.go:
    • isRequestPresignedSignatureV4() detects query-string auth (presence of X-Amz-Credential, as in MinIO).
    • doesPresignedSignatureV4Match() validates X-Amz-Expires bounds (1s..7d), rejects not-yet-valid (15 min skew allowance) and expired requests with the AWS error codes (AuthorizationQueryParametersError, AccessDenied), then verifies the signature over the canonical request — all query parameters except X-Amz-Signature, UNSIGNED-PAYLOAD unless a payload checksum was signed — reusing the existing SigV4 helpers, so region handling is identical to the header path (the scope region is used as-is, e.g. RGW zonegroup names like default).
    • removePresignParams() strips the X-Amz-* auth parameters after successful validation, so the request forwarded to the backend carries a single authentication mechanism: the Authorization header computed by the proxy in pkg/s3client. Non-auth query parameters (response-content-disposition, versionId, ...) are preserved.
  • service/proxy/auth/middleware.go: dispatch presigned requests in isReqAuthenticated(), strip the auth params in Wrap() after successful authentication.

Presigned SigV2 URLs remain unsupported (out of scope; the SigV2 path still requires the Authorization header).

Testing:

  • Unit tests in service/proxy/auth/signature_v4_presign_test.go: valid presigned GET (including non-AWS region scope default and extra response-* query params), wrong secret, unknown access key, expired / not-yet-valid dates, out-of-bounds X-Amz-Expires, dispatch from isReqAuthenticated, CredentialsNotSupported preserved for unauthenticated requests, and auth-param stripping through Wrap().
  • The verification logic was additionally cross-checked against presigned URLs generated by botocore (path-style, custom port, region default, unicode/space object keys, response-* params, HEAD, versionId, tampered URL and wrong-secret rejection).

Related Issue

Fixes #255

Checklist

Note: By submitting this PR, you agree to license your contributions under the Apache 2.0 License and follow our Code of Conduct.

🤖 Generated with Claude Code

The proxy auth middleware only recognized header-based AWS SigV4/SigV2
signatures. A presigned URL carries its SigV4 parameters in the query
string (X-Amz-Algorithm, X-Amz-Credential, X-Amz-Signature, ...) and no
Authorization header, so such requests were rejected with
400 CredentialsNotSupported before reaching the storage backend.

This broke clients that rely on presigned URLs for downloads/uploads,
e.g. web applications that redirect the browser to a presigned URL,
aws-sdk presign, or 'rclone link', when pointed at the chorus proxy.

Add query-string SigV4 authentication:
- s3.ParsePreSignV4 parses the X-Amz-* auth query parameters
- doesPresignedSignatureV4Match validates X-Amz-Expires bounds,
  signing date and expiry, then verifies the signature over the
  canonical request (all query params except X-Amz-Signature,
  UNSIGNED-PAYLOAD unless a payload checksum was signed)
- after successful validation, the X-Amz-* auth params are stripped
  from the URL so the request forwarded to the backend carries only
  the Authorization header computed by the proxy

Presigned SigV2 URLs remain unsupported.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Frédéric Nass <frederic.nass@clyso.com>
@FredNass

FredNass commented Sep 8, 2026

Copy link
Copy Markdown
Contributor Author

@arttor ready for review. ;-)

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.

Proxy rejects presigned URLs with 400 CredentialsNotSupported

1 participant