Skip to content

chore(release): v1.9.0 + chart 1.10.0 - #55

Merged
louisgls merged 10 commits into
mainfrom
release/1.9.0
Jul 29, 2026
Merged

chore(release): v1.9.0 + chart 1.10.0#55
louisgls merged 10 commits into
mainfrom
release/1.9.0

Conversation

@louisgls

Copy link
Copy Markdown
Collaborator

Integration branch bundling the five reviewed PRs, with conflicts resolved and the release metadata applied.

Included

PR Change
#49 Content-Length on intercepted GetObject (fixes s3cmd downloading empty files)
#50 Plaintext ETag on intercepted GetObject
#51 Decrypted plaintext sizes in ListObjects/ListObjectsV2
#53 Configurable S3PROXY_S3_OPERATION_TIMEOUT
#54 Chart deploymentAnnotations (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:

Release metadata

Once merged, the two release tags are v1.9.0 (app → Docker image + signed binaries) and chart-v1.10.0 (chart → ghcr push).

Verification

  • go build ./... — clean
  • go vet ./... — clean
  • go test ./... — 108 tests pass across 7 packages
  • gofmt -l . — clean
  • helm lint charts/s3proxy — passes

golangci-lint locally reports 12 goconst issues vs 11 on main — the one addition is "valid" in internal/config/validation_test.go from #53's test table. Note the local linter is v2.12.2 while CI pins v2.11.4, under which main'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:

🤖 Generated with Claude Code

Piotr Szafarczyk and others added 10 commits June 25, 2026 09:14
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
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>
@louisgls
louisgls merged commit d15d5b4 into main Jul 29, 2026
4 of 5 checks passed
@louisgls
louisgls deleted the release/1.9.0 branch July 29, 2026 13:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants