4.6 KiB
Authentication and Signature V4
The Worker accepts AWS Signature Version 4 in either form:
- an
Authorization: AWS4-HMAC-SHA256 …header withx-amz-date; or - a presigned request with
X-Amz-Algorithm,X-Amz-Credential,X-Amz-Date,X-Amz-SignedHeaders, andX-Amz-Signaturequery parameters.
The Credential access-key ID is looked up in the gateway's access-key store (managed from the dashboard under /api/integration/keys); the signing key is derived from that key's secret, REGION, service s3, and the YYYYMMDD request date. Unknown or expired key IDs are rejected with 403 AccessDenied.
Access keys
S3 credentials are named key pairs stored in AUTH_KV (s3-credentials), managed via the dashboard:
- Up to 5 live keys — one per integration, revocable independently.
- Rotation creates a replacement with the same label; the old key either dies immediately (
graceSeconds: 0) or keeps authenticating for a grace period of 1 hour, 24 hours, or 7 days. - The legacy
ACCESS_KEY/SECRET_KEYsecrets act as bootstrap only: on first use they are seeded into the store and are superseded by dashboard-managed keys afterwards.
Key changes are edge-cached for up to 60 seconds. A rotated-away or revoked key may continue to authenticate for at most one minute after the change.
Secrets are stored readable in KV because SigV4 verification must derive the signing key from the raw secret — they cannot be hashed. Treat dashboard sessions as full credential access.
Presigned URL expiry
Query-authenticated requests require X-Amz-Expires from 1 through 604800 seconds (seven days). The Worker returns 403 AccessDenied with Request has expired when it is missing, invalid, out of range, or its signed timestamp plus expiry is in the past.
BFFs should normally use a much shorter period such as five minutes.
Header-authenticated requests
The Worker requires a valid x-amz-date and rejects a request when the server time differs from it by more than 15 minutes. This returns 403 RequestTimeTooSkewed.
Canonical request behavior
The canonical request is built from:
- method;
- URL path;
- sorted query string (excluding
X-Amz-Signaturefor presigned URLs); - the signed-header list and normalized values;
- the signed-header list; and
x-amz-content-sha256, defaulting toUNSIGNED-PAYLOAD.
A signed accept-encoding header gets special treatment. Cloudflare rewrites the received value at the edge (usually to gzip, br), so the delivered header cannot be compared against what the client signed, and clients disagree on the value anyway: aws-sdk-go-v2 (memos and most Go clients) signs identity on every operation, while rclone and AWS CLI v2 sign gzip for GetObject and identity elsewhere. The Worker therefore verifies against the pre-rewrite value in request.cf.clientAcceptEncoding when the edge supplies it, and otherwise retries the signature against each value a client plausibly signs (the delivered header, identity, gzip, empty). Only this header's canonical value varies — the signature must still be produced with the secret key. Clients that sign some other value can drop the header from the signature instead (rclone: --s3-sign-accept-encoding=false). Browser code must not sign accept-encoding because browser networking controls it.
Payload hashes are not verified. Browser and BFF clients should use x-amz-content-sha256: UNSIGNED-PAYLOAD; this is a deliberate streaming limitation, not an integrity guarantee.
Public-read buckets
Buckets configured with publicRead: true (managed via the dashboard or API) permit unsigned GET and HEAD requests. All write operations still require valid Signature V4 authentication. Bucket metadata is tracked via Drive appProperties.
Dashboard login API
The Worker provides management API endpoints under /auth/* for the management dashboard (s3-drive-storage-manage). These endpoints do not use AWS Signature V4:
POST /auth/login— Verifies{ passwordHash }againstDASHBOARD_PASSWORD(SHA-256 compared in constant time). Returns{ token, expiresIn }on success (opaque 256-bit base64url token with a 12-hour TTL stored inAUTH_KV). Rate-limited to 5 failed attempts per IP within 15 minutes.GET /auth/session— Validates the session token supplied inAuthorization: Bearer <token>. Returns{ valid: true }if valid, or401if expired/invalid.POST /auth/logout— Revokes the session token supplied inAuthorization: Bearer <token>. Returns204 No Content.
For a tested signing reference, see the signed() helper in test/s3.test.ts.