mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-10-05 22:12:04 +02:00
s3api: allow unsigned SSE-C customer key headers on presigned requests (#11578)
* s3api: allow unsigned SSE-C customer key headers on presigned requests AWS requires only x-amz-server-side-encryption-customer-algorithm to be signed on presigned URLs; the key and key-MD5 headers are supplied at request time. Since #9121 rejected any x-amz-* header outside SignedHeaders, SDK-generated presigned SSE-C requests (e.g. .NET GetPreSignedUrlRequest) fail with SignatureDoesNotMatch. Exempt the customer key and copy-source key headers for presigned requests only. * s3api: test presigned SSE-C requests carrying unsigned key headers
This commit is contained in:
1 parent
0ca484c354
commit
793ce06b10
2 files changed
+80
No files matched your search
@@ -848,6 +848,16 @@ var presignedSigV4ProtocolHeaders = map[string]struct{}{
|
||||
"x-amz-signature": {},
|
||||
}
|
||||
|
||||
// presignedUnsignedSSECHeaders exempts SSE-C key material that AWS requires to
|
||||
// be sent but not signed on presigned requests: only the algorithm header is
|
||||
// part of the signature calculation.
|
||||
var presignedUnsignedSSECHeaders = map[string]struct{}{
|
||||
strings.ToLower(s3_constants.AmzServerSideEncryptionCustomerKey): {},
|
||||
strings.ToLower(s3_constants.AmzServerSideEncryptionCustomerKeyMD5): {},
|
||||
strings.ToLower(s3_constants.AmzCopySourceServerSideEncryptionCustomerKey): {},
|
||||
strings.ToLower(s3_constants.AmzCopySourceServerSideEncryptionCustomerKeyMD5): {},
|
||||
}
|
||||
|
||||
// verifySignedHeadersCoverage rejects requests that carry x-amz-* headers
|
||||
// outside of the SignedHeaders list. AWS SigV4 requires every x-amz-* header
|
||||
// present in the request to be covered by the signature; without this check a
|
||||
@@ -874,6 +884,9 @@ func verifySignedHeadersCoverage(r *http.Request, signedHeaders []string, isPres
|
||||
if _, exempt := presignedSigV4ProtocolHeaders[lower]; exempt {
|
||||
continue
|
||||
}
|
||||
if _, exempt := presignedUnsignedSSECHeaders[lower]; exempt {
|
||||
continue
|
||||
}
|
||||
}
|
||||
glog.V(2).Infof("reject %s %s: unsigned header %q not in SignedHeaders", r.Method, r.URL.Path, name)
|
||||
return s3err.ErrSignatureDoesNotMatch
|
||||
|
||||
@@ -142,6 +142,50 @@ func TestVerifySignedHeadersCoverage_Unit(t *testing.T) {
|
||||
signedHeaders: []string{"HOST", "X-Amz-Tagging"},
|
||||
want: s3err.ErrNone,
|
||||
},
|
||||
{
|
||||
name: "presigned exempts unsigned SSE-C key headers",
|
||||
headers: map[string]string{
|
||||
"X-Amz-Server-Side-Encryption-Customer-Algorithm": "AES256",
|
||||
"X-Amz-Server-Side-Encryption-Customer-Key": "key",
|
||||
"X-Amz-Server-Side-Encryption-Customer-Key-MD5": "md5",
|
||||
},
|
||||
signedHeaders: []string{"host", "x-amz-server-side-encryption-customer-algorithm"},
|
||||
isPresigned: true,
|
||||
want: s3err.ErrNone,
|
||||
},
|
||||
{
|
||||
name: "presigned exempts unsigned SSE-C copy-source key headers",
|
||||
headers: map[string]string{
|
||||
"X-Amz-Copy-Source-Server-Side-Encryption-Customer-Algorithm": "AES256",
|
||||
"X-Amz-Copy-Source-Server-Side-Encryption-Customer-Key": "key",
|
||||
"X-Amz-Copy-Source-Server-Side-Encryption-Customer-Key-MD5": "md5",
|
||||
},
|
||||
signedHeaders: []string{"host", "x-amz-copy-source-server-side-encryption-customer-algorithm"},
|
||||
isPresigned: true,
|
||||
want: s3err.ErrNone,
|
||||
},
|
||||
{
|
||||
name: "presigned still requires SSE-C algorithm to be signed",
|
||||
headers: map[string]string{
|
||||
"X-Amz-Server-Side-Encryption-Customer-Algorithm": "AES256",
|
||||
"X-Amz-Server-Side-Encryption-Customer-Key": "key",
|
||||
"X-Amz-Server-Side-Encryption-Customer-Key-MD5": "md5",
|
||||
},
|
||||
signedHeaders: []string{"host"},
|
||||
isPresigned: true,
|
||||
want: s3err.ErrSignatureDoesNotMatch,
|
||||
},
|
||||
{
|
||||
name: "header-based does NOT exempt unsigned SSE-C key headers",
|
||||
headers: map[string]string{
|
||||
"X-Amz-Server-Side-Encryption-Customer-Algorithm": "AES256",
|
||||
"X-Amz-Server-Side-Encryption-Customer-Key": "key",
|
||||
"X-Amz-Server-Side-Encryption-Customer-Key-MD5": "md5",
|
||||
},
|
||||
signedHeaders: []string{"host", "x-amz-server-side-encryption-customer-algorithm"},
|
||||
isPresigned: false,
|
||||
want: s3err.ErrSignatureDoesNotMatch,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
@@ -203,6 +247,29 @@ func TestPresignedPutAcceptsSignedTagging(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestPresignedGetAcceptsUnsignedSSECKeyHeaders mirrors the AWS SDK presign
|
||||
// flow for SSE-C objects: only the algorithm header is signed while the key
|
||||
// and key-MD5 headers are attached to the request unsigned.
|
||||
func TestPresignedGetAcceptsUnsignedSSECKeyHeaders(t *testing.T) {
|
||||
iam := newTestIAM()
|
||||
|
||||
req, err := newTestRequest(http.MethodGet, "http://127.0.0.1:9000/bucket/key", 0, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("newTestRequest: %v", err)
|
||||
}
|
||||
req.Header.Set("X-Amz-Server-Side-Encryption-Customer-Algorithm", "AES256")
|
||||
if err := preSignV4WithHeaders(iam, req, "AKIAIOSFODNN7EXAMPLE", "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", 600, []string{"host", "x-amz-server-side-encryption-customer-algorithm"}); err != nil {
|
||||
t.Fatalf("preSignV4WithHeaders: %v", err)
|
||||
}
|
||||
req.Header.Set("X-Amz-Server-Side-Encryption-Customer-Key", "MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTIzNDU2Nzg5MDE=")
|
||||
req.Header.Set("X-Amz-Server-Side-Encryption-Customer-Key-MD5", "md5")
|
||||
|
||||
_, errCode := iam.reqSignatureV4Verify(req)
|
||||
if errCode != s3err.ErrNone {
|
||||
t.Fatalf("expected ErrNone for presigned SSE-C with unsigned key headers, got %v", errCode)
|
||||
}
|
||||
}
|
||||
|
||||
// preSignV4WithHeaders is a test helper that builds a presigned URL whose
|
||||
// SignedHeaders list covers the specified headers (which must already be set
|
||||
// on the request), then computes the signature over those headers and
|
||||
|
||||
Reference in new issue
Block a user