Skip to content

Verify inbound SigV4 presigned URLs (query-string auth) in resolve_identity #53

@alukach

Description

@alukach

Summary

Add support for verifying inbound SigV4 presigned URLs (signature carried in the query string) in the auth layer. Today resolve_identity only authenticates requests whose SigV4 material is in the Authorization header; presigned requests are treated as anonymous.

This unblocks presigned-URL support in the Source Cooperative data proxy — see downstream issue: source-cooperative/data.source.coop#136. The proxy wants to issue standard AWS SigV4 presigned GET URLs (generated with STS temp creds) that browser data-viewers can fetch directly, while the proxy still streams the bytes.

Current behavior (the gap)

crates/core/src/auth/identity.rs::resolve_identity:

  • Returns ResolvedIdentity::Anonymous whenever there is no Authorization header.
  • Reads the session token (x-amz-security-token) and payload hash (x-amz-content-sha256) from headers.

For a presigned URL, all SigV4 material is in the query string (X-Amz-Algorithm, X-Amz-Credential, X-Amz-Date, X-Amz-Expires, X-Amz-SignedHeaders, X-Amz-Security-Token, X-Amz-Signature), and the canonical request is constructed differently. So presigned requests currently fail to authenticate.

Much of the machinery already exists and is reusable: crates/core/src/auth/sigv4.rs::verify_sigv4_signature already takes a query_string and canonicalize_query_string already sorts it; the SigV4Auth struct, signing-key derivation, and constant_time_eq can all be shared.

Proposed change

In crates/core/src/auth/sigv4.rs + crates/core/src/auth/identity.rs:

  1. Detect presigned requests in resolve_identity — if the query contains X-Amz-Algorithm=AWS4-HMAC-SHA256 (or X-Amz-Signature), take a presigned branch before the Anonymous early-return.
  2. Parse SigV4 from the query — new parse_sigv4_presigned(query) producing a SigV4Auth (reuse the existing credential-scope split) plus expires and the security token. Pull X-Amz-Credential, X-Amz-SignedHeaders, X-Amz-Signature, X-Amz-Date, X-Amz-Expires, X-Amz-Security-Token.
  3. Presigned canonical-request variant — either parameterize verify_sigv4_signature or add a sibling:
    • Canonical query = all query params except X-Amz-Signature, sorted/encoded (strip it, then canonicalize_query_string).
    • Payload hash = the literal string UNSIGNED-PAYLOAD.
    • string_to_sign date comes from the X-Amz-Date query param (not the x-amz-date header).
    • Reuse the existing signing-key derivation and constant_time_eq.
  4. Enforce expiry — reject when X-Amz-Date + X-Amz-Expires < now (small clock-skew allowance).
  5. Session token — resolve X-Amz-Security-Token through the existing credential_resolver (TemporaryCredentialResolver), keep the access-key↔token match check, and return the same ResolvedIdentity::Authenticated. Everything downstream (authorize → presign-backend → stream) is unchanged.

Tests

Add AWS SigV4 presigned test vectors alongside the existing header-auth vectors in crates/core/src/auth/tests.rs:

  • A known presigned canonical request → expected signature (positive).
  • Tampered query param → signature mismatch.
  • X-Amz-Date + X-Amz-Expires in the past → expired/denied.
  • With X-Amz-Security-Token resolved via a stub TemporaryCredentialResolverAuthenticated.

Release

Cut a minor release so downstream consumers (the data proxy, currently on 0.4.0) can bump.


Filed alongside source-cooperative/data.source.coop#136.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions