dev-worker: Implement AWS SigV4 authentication for the warehouse S3-com… #2

Merged
pchapman merged 1 commit from dev-worker/01KYV61K35407545HQHG4PVKE3 into main 2026-07-30 23:33:27 -05:00
Collaborator

Implement AWS SigV4 authentication for the warehouse S3-compatible server, supporting both the Authorization header and presigned URL (query-string) variants.

Changes:

  1. internal/auth/sigv4.go (new) — SigV4 verification package: Verify() dispatches header vs. presigned variant; ExtractAccessKeyID(); credential-scope parsing; canonical-request reconstruction (S3-style path encoding preserving '/', canonical query string excluding X-Amz-Signature, header canonicalization); string-to-sign; signing-key derivation; HMAC-SHA256 verification. Sentinel errors (ErrMissingAuth, ErrUnsupportedAlgorithm, ErrMalformedAuth, ErrMissingDate, ErrInvalidDateFormat, ErrExpired) and *SignatureError carrying StringToSign/CanonicalRequest/SignatureProvided diagnostics. Also exports SignRequest/SignPresigned helpers.
  2. internal/server/middleware.go (new) — Server.WithAuth chi middleware: extracts access key id -> Store.GetAccessKey (-> InvalidAccessKeyId on ErrAccessKeyNotFound) -> rejects disabled keys -> auth.Verify -> forwards. No anonymous path on protected routes. S3 XML error responses (S3Error struct) for SignatureDoesNotMatch, InvalidAccessKeyId, AccessDenied, RequestExpired, AuthorizationHeaderMalformed.
  3. internal/server/server.go — router wired so /healthz is the only public route; GET / (ListBuckets) plus bucket HEAD/GET/PUT/DELETE sit inside the auth group. Added ListBuckets/headBucket/createBucket/deleteBucket/listObjects handlers so aws s3 ls works against a signed request.
  4. Tests: internal/auth/sigv4_test.go (independently re-derives signing key as cross-check; covers valid/invalid header+presigned, missing/invalid date, bad algorithm, malformed credentials, expired vs. not-yet-expired, bad X-Amz-Expires, canonical query string, URI encoding, payload-hash restoration) and internal/server/middleware_test.go (full chi-router integration: missing auth 403, valid header+presigned -> ListBuckets, wrong secret -> SignatureDoesNotMatch, unknown key id -> InvalidAccessKeyId, expired -> RequestExpired, disabled key, public /healthz, bucket routes require auth, bad algorithm, tampered presigned query).

Assumptions made:

  • Updated the pre-existing TestHealthzNotFound (expected 404 for /nope) to expect 403, since enforcing auth on all non-healthz routes is the mission requirement ("no anonymous access path"). /healthz remains intentionally public for liveness probes.
  • listObjects returns a minimal ListBucketResult envelope; deeper object-storage handlers were left for a later increment since the mission scoped auth.

All builds, go vet, and tests pass.

go build ./...

(build succeeded, no output)

go test ./...

?   	pcsw/warehouse/cmd/warehouse	[no test files]
ok  	pcsw/warehouse/internal/auth	(cached)
ok  	pcsw/warehouse/internal/db	(cached)
ok  	pcsw/warehouse/internal/server	(cached)

Opened by saga-dev-worker for mission 01KYV61K35407545HQHG4PVKE3.

Implement AWS SigV4 authentication for the warehouse S3-compatible server, supporting both the Authorization header and presigned URL (query-string) variants. Changes: 1. internal/auth/sigv4.go (new) — SigV4 verification package: Verify() dispatches header vs. presigned variant; ExtractAccessKeyID(); credential-scope parsing; canonical-request reconstruction (S3-style path encoding preserving '/', canonical query string excluding X-Amz-Signature, header canonicalization); string-to-sign; signing-key derivation; HMAC-SHA256 verification. Sentinel errors (ErrMissingAuth, ErrUnsupportedAlgorithm, ErrMalformedAuth, ErrMissingDate, ErrInvalidDateFormat, ErrExpired) and *SignatureError carrying StringToSign/CanonicalRequest/SignatureProvided diagnostics. Also exports SignRequest/SignPresigned helpers. 2. internal/server/middleware.go (new) — Server.WithAuth chi middleware: extracts access key id -> Store.GetAccessKey (-> InvalidAccessKeyId on ErrAccessKeyNotFound) -> rejects disabled keys -> auth.Verify -> forwards. No anonymous path on protected routes. S3 XML error responses (S3Error struct) for SignatureDoesNotMatch, InvalidAccessKeyId, AccessDenied, RequestExpired, AuthorizationHeaderMalformed. 3. internal/server/server.go — router wired so /healthz is the only public route; GET / (ListBuckets) plus bucket HEAD/GET/PUT/DELETE sit inside the auth group. Added ListBuckets/headBucket/createBucket/deleteBucket/listObjects handlers so `aws s3 ls` works against a signed request. 4. Tests: internal/auth/sigv4_test.go (independently re-derives signing key as cross-check; covers valid/invalid header+presigned, missing/invalid date, bad algorithm, malformed credentials, expired vs. not-yet-expired, bad X-Amz-Expires, canonical query string, URI encoding, payload-hash restoration) and internal/server/middleware_test.go (full chi-router integration: missing auth 403, valid header+presigned -> ListBuckets, wrong secret -> SignatureDoesNotMatch, unknown key id -> InvalidAccessKeyId, expired -> RequestExpired, disabled key, public /healthz, bucket routes require auth, bad algorithm, tampered presigned query). Assumptions made: - Updated the pre-existing TestHealthzNotFound (expected 404 for /nope) to expect 403, since enforcing auth on all non-healthz routes is the mission requirement ("no anonymous access path"). /healthz remains intentionally public for liveness probes. - listObjects returns a minimal ListBucketResult envelope; deeper object-storage handlers were left for a later increment since the mission scoped auth. All builds, go vet, and tests pass. ## go build ./... ``` (build succeeded, no output) ``` ## go test ./... ``` ? pcsw/warehouse/cmd/warehouse [no test files] ok pcsw/warehouse/internal/auth (cached) ok pcsw/warehouse/internal/db (cached) ok pcsw/warehouse/internal/server (cached) ``` --- Opened by saga-dev-worker for mission `01KYV61K35407545HQHG4PVKE3`.
Changes:
1. internal/auth/sigv4.go (new) — SigV4 verification package: Verify() dispatches header vs. presigned variant; ExtractAccessKeyID(); credential-scope parsing; canonical-request reconstruction (S3-style path encoding preserving '/', canonical query string excluding X-Amz-Signature, header canonicalization); string-to-sign; signing-key derivation; HMAC-SHA256 verification. Sentinel errors (ErrMissingAuth, ErrUnsupportedAlgorithm, ErrMalformedAuth, ErrMissingDate, ErrInvalidDateFormat, ErrExpired) and *SignatureError carrying StringToSign/CanonicalRequest/SignatureProvided diagnostics. Also exports SignRequest/SignPresigned helpers.
2. internal/server/middleware.go (new) — Server.WithAuth chi middleware: extracts access key id -> Store.GetAccessKey (-> InvalidAccessKeyId on ErrAccessKeyNotFound) -> rejects disabled keys -> auth.Verify -> forwards. No anonymous path on protected routes. S3 XML error responses (S3Error struct) for SignatureDoesNotMatch, InvalidAccessKeyId, AccessDenied, RequestExpired, AuthorizationHeaderMalformed.
3. internal/server/server.go — router wired so /healthz is the only public route; GET / (ListBuckets) plus bucket HEAD/GET/PUT/DELETE sit inside the auth group. Added ListBuckets/headBucket/createBucket/deleteBucket/listObjects handlers so `aws s3 ls` works against a signed request.
4. Tests: internal/auth/sigv4_test.go (independently re-derives signing key as cross-check; covers valid/invalid header+presigned, missing/invalid date, bad algorithm, malformed credentials, expired vs. not-yet-expired, bad X-Amz-Expires, canonical query string, URI encoding, payload-hash restoration) and internal/server/middleware_test.go (full chi-router integration: missing auth 403, valid header+presigned -> ListBuckets, wrong secret -> SignatureDoesNotMatch, unknown key id -> InvalidAccessKeyId, expired -> RequestExpired, disabled key, public /healthz, bucket routes require auth, bad algorithm, tampered presigned query).

Assumptions made:
- Updated the pre-existing TestHealthzNotFound (expected 404 for /nope) to expect 403, since enforcing auth on all non-healthz routes is the mission requirement ("no anonymous access path"). /healthz remains intentionally public for liveness probes.
- listObjects returns a minimal ListBucketResult envelope; deeper object-storage handlers were left for a later increment since the mission scoped auth.

All builds, go vet, and tests pass.
pchapman deleted branch dev-worker/01KYV61K35407545HQHG4PVKE3 2026-07-30 23:33:27 -05:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
pcsw/warehouse!2
No description provided.