developmentseed / developmentseed/multistore

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

Open
#53 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
Rust
Stars
19
Forks
1
PR merge metrics
No merged PRs in 30d

Description

## 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 `TemporaryCredentialResolver` → `Authenticated`.

## 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.*

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.