feat(s3): make Accept-Encoding signing configurable - #316
Open
kinorai wants to merge 1 commit into
Open
Conversation
aws-sdk-go-v2 sends `Accept-Encoding: identity` and, unlike v1, includes `accept-encoding` in the SigV4 SignedHeaders set. Any hop that alters the header between the client and the endpoint therefore breaks signature verification at the endpoint with: "AWS Error: SignatureDoesNotMatch The request signature we calculated does not match the signature you provided." ignoreSigningHeaders already works around this, but it is reachable only when the endpoint hostname contains storage.googleapis.com. GCS is not the only hop that alters the header: a reverse proxy or a CDN in front of an S3-compatible endpoint does the same thing, and Cloudflare rewrites the header unconditionally by design. That population cannot be detected from the endpoint string, so today it has no way to reach the workaround and every backup fails. Add AWS_SIGN_ACCEPT_ENCODING, a backup target secret key following the VIRTUAL_HOSTED_STYLE precedent. Unset keeps the existing behavior, so the header is still signed. Setting it to false excludes the header from SignedHeaders. The storage.googleapis.com check is kept and takes priority, so GCS cannot regress even if a user sets the key to true. ignoreSigningHeaders restores the header after signing, so the request on the wire is unchanged either way. The value is parsed with strconv.ParseBool rather than the exact string compare VIRTUAL_HOSTED_STYLE uses, because a rejected value here reproduces the total backup failure this key exists to fix. For the same reason the value is trimmed, since a Secret commonly carries a trailing newline, and a malformed value is logged once instead of failing silently. rclone made the same change for the same reason: a GCS-only quirk became the user-settable --s3-sign-accept-encoding in commit e175c863. Longhorn 13756 Signed-off-by: kinorai <kinorai@users.noreply.github.com>
This was referenced Aug 16, 2026
mantissahz
requested review from
mantissahz
and
a balanced review from Copilot
and removed request for
mantissahz
August 18, 2026 13:48
Member
|
@mantissahz Please review the PR. |
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.
Which issue(s) this PR fixes:
Issue longhorn/longhorn#13756
What this PR does / why we need it:
ignoreSigningHeadersalready exists and already does the right thing, but it isreachable only when the endpoint hostname contains
storage.googleapis.com.aws-sdk-go-v2 sends
Accept-Encoding: identityand, unlike v1, includesaccept-encodingin the SigV4SignedHeadersset. Any hop that alters the headerbetween the client and the endpoint therefore breaks signature verification with
SignatureDoesNotMatch. GCS is not the only such hop: a reverse proxy or a CDN infront of an S3-compatible endpoint does the same thing, and Cloudflare rewrites the
header unconditionally by design
(docs). That population
cannot be detected from the endpoint string, so today it has no way to reach the
workaround and every backup fails.
This replaces the hostname match with
AWS_SIGN_ACCEPT_ENCODING, a new backup targetsecret key, following the
VIRTUAL_HOSTED_STYLEprecedent:AWS_SIGN_ACCEPT_ENCODING=falseexcludes it fromSignedHeaders.user sets the key to
true.ignoreSigningHeadersrestores the header after signing, so the request on the wireis unchanged either way.
The value is parsed with
strconv.ParseBoolrather than the exact string compareVIRTUAL_HOSTED_STYLEuses fifty lines below. The asymmetry is deliberate: a valuethis code rejects reproduces the total backup failure the key exists to fix, so
0,Falseand a trailing newline from the Secret are all accepted, and a genuinelymalformed value is logged once rather than dropped silently. Happy to match the
neighbour instead if you prefer consistency there.
Special notes for your reviewer:
This is the first of three PRs, and the merge order matters.
backupEnvAllowlist.Order 3 before 2 breaks every S3 backup and restore, for all users, not only those
who set the key.
getBackupCredentialEnvappends the key unconditionally, andvalidateBackupEnvin the instance manager rejects the whole request on one keyoutside its allowlist. Each of the two dependent PRs also needs a vendor bump of this
module. The docs change for
longhorn/websiteis separate.On the design choice. The issue proposed either a secret key or "apply the
workaround whenever
AWS_ENDPOINTSis set". I took the secret key, because thesecond option silently changes signing for every existing non-AWS endpoint. Happy to
switch if you prefer the other shape.
Testing, unit.
s3/s3_sign_accept_encoding_test.goadds four tests with noexternal dependency:
TestIgnoreAcceptEncodingSigningcovers the decision across empty /true/false/False/0/ trailing newline / trailing CRLF / malformed, for a customendpoint, no endpoint, and the GCS endpoint with all three values.
TestIgnoreAcceptEncodingSigningWhenUnsetcovers the genuinely unset variable,which
t.Setenvcannot express.TestAcceptEncodingIsSignedByDefaultpins the default.TestSignAcceptEncodingFalseExcludesAcceptEncodingFromSignatureis the regressiontest:
accept-encodingis absent fromSignedHeaders, andAccept-Encoding: identityis still on the wire.The last two read the real
Authorizationheader off the existingfakeS3Serverins3_service_unit_test.go, which I extended to record it.I mutation-tested all three assertions rather than trusting them. Reverting the call
site to the old GCS-only gate fails the exclusion test. Breaking
restoreIgnoredfails the wire test, because Go's
http.Transportsubstitutesgzipwhen the headeris absent. Dropping the trim fails the two Secret-newline cases.
Testing, end to end. Docker only, no cluster. Two real S3 servers, each behind an
nginx that reproduces the Cloudflare edge (
proxy_set_header Accept-Encoding "gzip"plus
gunzip on, since the edge decompresses the origin response before returningit), plus a control arm with no rewrite. Driven by the existing
s3testsuite:AWS_SIGN_ACCEPT_ENCODINGSignatureDoesNotMatchtrueSignatureDoesNotMatchfalse"false\n"falseSignatureDoesNotMatchfalsefalseThe control rows isolate the proxy as the cause. The
false-without-proxy rows showthe key is safe to set on an endpoint that does not need it. SeaweedFS is the server
from the issue report.
golangci-lint runreports 0 issues,go test -racepasses, andgo vetandgofmtare clean. The failures in./testandTestInspectBackupneed the NFSserver that
scripts/teststarts, and fail identically on master in my environment.Additional documentation or context
rclone made exactly this change for the same reason. It began as a GCS-only quirk and
became the user-settable
--s3-sign-accept-encodingin commite175c863("Splitting them like this makes them applicable for other providers such as
ActiveScale"). Users confirmed it fixes the Cloudflare case in rclone/rclone#8206,
and RustFS now documents the flag as required in its Cloudflare Tunnel guide.
apache/iceberg-go solved it with an opt-in property rather than a hostname match
(
s3.compat-mode, PRs #1423 and #1559). litestream took the GCS-gated route andimmediately hit the follow-on report for a different provider behind a proxy
(benbjohnson/litestream#1424, still open).
The upstream SDK will not fix this: aws/aws-sdk-go-v2#1816 is closed with "the SDKs
don't guarantee compatibility with 3rd party platforms".
Prepared with AI assistance (Claude Code). Every test and command reported above was run, not estimated.