chore(release): v1.9.0 + chart 1.10.0 - #55
Merged
Merged
Conversation
Intercepted GetObject responses streamed the decrypted body without a Content-Length header, so Go's server fell back to chunked transfer encoding. s3cmd 2.4.0 reads content-length unconditionally and downloaded an empty file as a result. The full plaintext is already buffered before any bytes are written, so set Content-Length from len(plaintext) before WriteHeader. The upstream length describes the ciphertext (+28 bytes GCM overhead, or arbitrary for legacy objects) and can't be reused, mirroring the existing omission of x-amz-checksum-* headers.
Intercepted GETs decrypt the body but returned the upstream ETag, which S3 computes over the ciphertext at rest. Clients/SDKs that validate the body against the ETag, or cache by it, saw a mismatch. Override the response ETag with md5(plaintext) — the ETag S3 would have produced for the unencrypted object — computed on the fly from the buffer already in memory, so no stored metadata or migration is needed. Pass-through objects (no DEK tag) keep the upstream ETag. PUT-response and HEAD ETags still reflect the ciphertext and remain a known gap.
ListObjects/ListObjectsV2 previously reported the at-rest ciphertext size (plaintext + 28-byte AES-GCM-SIV envelope). Intercept bucket-level list requests, subtract the fixed encryption overhead from every <Size>, and clamp at 0. Bucket sub-resource GETs (acl, versioning, multipart listings, ?versions, …) are still forwarded unchanged. - cryptoutil: export EncryptionOverhead constant + invariant test - router: bucketOnlyPattern + isListObjects, wired into getHandler - handler: extract forwardUpstream; add handleListObjects (buffer, rewrite on 2xx, fix Content-Length) - listsize: byte-preserving <Size> regex rewrite + unit tests - e2e: assert v1 and v2 listings report plaintext size Known limitation: objects not written through the proxy (legacy plaintext, server-side copies, multipart) are reported 28 bytes short / 0 after clamp, since list responses carry no per-object encryption metadata.
The chart only exposed `podAnnotations`, which renders onto the pod
template. Controllers that watch the workload object itself had no way to
be configured through values.
The concrete failure: the TLS cert secret is mounted with `subPath`, and
kubelet never refreshes a subPath volume in place. When cert-manager
rotates the certificate, the running pods keep serving the old one until
they are restarted. Stakater Reloader does exactly that, but it reads
`reloader.stakater.com/auto` from `Deployment.metadata.annotations` — which
the chart could not render. The proxy therefore served an expired
certificate and every TLS client failed with `x509: certificate has
expired`.
Add `deploymentAnnotations` (default `{}`, declared in values.schema.json)
and render it onto the Deployment metadata. No behavior change when unset.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
# Conflicts: # CHANGELOG.md # s3proxy/internal/router/router_test.go
# Conflicts: # CHANGELOG.md
# Conflicts: # CHANGELOG.md
Bundles the client-compatibility fixes and the configurable operation timeout, and bumps the chart appVersion to match. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This was referenced Jul 29, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Integration branch bundling the five reviewed PRs, with conflicts resolved and the release metadata applied.
Included
Content-Lengthon intercepted GetObject (fixes s3cmd downloading empty files)ETagon intercepted GetObjectListObjects/ListObjectsV2S3PROXY_S3_OPERATION_TIMEOUTdeploymentAnnotations(Reloader on the Deployment)#52 (buffered multipart) is deliberately excluded — it is a 2612-line change to the crypto path that warrants its own review pass. See the notes below.
Conflicts resolved
Merging these in sequence is not clean; the resolutions here are:
router_test.go(fix(router): emit Content-Length on intercepted GetObject responses #49 × fix(router): return plaintext ETag on intercepted GetObject #50) — the two test additions interleave inside function bodies. Resolved by keeping all four tests:TestGetObjectSetsPlaintextContentLength,TestGetObjectSetsZeroContentLengthForEmptyObject,TestGetObjectReturnsPlaintextETag,TestGetObjectPassThroughKeepsUpstreamETag.CHANGELOG.md(fix(router): return plaintext ETag on intercepted GetObject #50, feat(router): report decrypted plaintext size in ListObjects responses #51, Make S3 operation timeout configurable #53) — additive; entries kept and the duplicated### Addedheadings collapsed into one.Release metadata
CHANGELOG.md:[Unreleased]→[1.9.0] — 2026-07-29, fresh empty[Unreleased]on top.charts/s3proxy/Chart.yaml:appVersion1.8.1→1.9.0; chartversion1.10.0(from feat(chart): add deploymentAnnotations for Deployment metadata #54).Once merged, the two release tags are
v1.9.0(app → Docker image + signed binaries) andchart-v1.10.0(chart → ghcr push).Verification
go build ./...— cleango vet ./...— cleango test ./...— 108 tests pass across 7 packagesgofmt -l .— cleanhelm lint charts/s3proxy— passesgolangci-lintlocally reports 12 goconst issues vs 11 onmain— the one addition is"valid"ininternal/config/validation_test.gofrom #53's test table. Note the local linter is v2.12.2 while CI pins v2.11.4, under whichmain's existing 11 do not fire; goconst's test-file handling changed between those versions.Review notes on the included PRs
Not blocking, worth tracking:
bucketOnlyPattern(^/([^/?]+)/?$) only matches path-style addressing, so virtual-host-style list requests are not rewritten. If an upstream ever returns a gzip-encoded list body, the regex silently matches nothing.S3PROXY_S3_OPERATION_TIMEOUTsilently falls back to 120s. Setting3600looks accepted but yields 2 minutes; a startup warning or hard failure would be safer.ETagis emitted unquoted, which deviates from RFC 9110 / S3. This matches the pre-existing convention on the PUT path (object.go:279), so it is not a regression, but both are worth aligning.🤖 Generated with Claude Code