Skip to content

Commit 36f5784

Browse files
committed
feature(s3/manager): add option to control default checksums
As announced in #2960, AWS SDK for Go v2 service/s3 v1.73.0 shipped a change (#2808) that adopted new default integrity protections, automatically calculating CRC32 checksums for operations like PutObject and UploadPart. While it is possible to revert to the previous behavior by setting `AWS_REQUEST_CHECKSUM_CALCULATION=when_required`, this config setting did not apply to the S3 Manager multipart uploader, which always enabled CRC32 checksums by default regardless of the global setting. This commit adds a new `RequestChecksumCalculation` field to the Uploader struct that allows users to control checksum behavior: - `RequestChecksumCalculationWhenSupported` (default): Always calculates CRC32 checksums for multipart uploads - `RequestChecksumCalculationWhenRequired`: Only calculates checksums when explicitly set by the user, preserving backwards compatibility For example: ```go uploader := manager.NewUploader(client, func(u *manager.Uploader) { u.RequestChecksumCalculation = aws.RequestChecksumCalculationWhenRequired }) ``` S3 Express One Zone buckets always require CRC32 checksums regardless of this setting, as mandated by the S3 Express service requirements. The uploader automatically detects S3 Express buckets (names ending with `--x-s3`) and applies CRC32 checksums unconditionally. Fixes #3007 Signed-off-by: Stan Hu <[email protected]>
1 parent 4f215b9 commit 36f5784

File tree

2 files changed

+113
-7
lines changed

2 files changed

+113
-7
lines changed

feature/s3/manager/upload.go

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,21 @@ type Uploader struct {
245245
// Defines the buffer strategy used when uploading a part
246246
BufferProvider ReadSeekerWriteToProvider
247247

248+
// RequestChecksumCalculation determines when request checksum calculation is performed
249+
// for multipart uploads.
250+
//
251+
// There are two possible values for this setting:
252+
//
253+
// 1. RequestChecksumCalculationWhenSupported (default): The checksum is always calculated
254+
// if the operation supports it, regardless of whether the user sets an algorithm in the request.
255+
//
256+
// 2. RequestChecksumCalculationWhenRequired: The checksum is only calculated if the user
257+
// explicitly sets a checksum algorithm in the request. This preserves backwards compatibility
258+
// for applications that don't want automatic checksum calculation.
259+
//
260+
// Note: S3 Express buckets always require CRC32 checksums regardless of this setting.
261+
RequestChecksumCalculation aws.RequestChecksumCalculation
262+
248263
// partPool allows for the re-usage of streaming payload part buffers between upload calls
249264
partPool byteSlicePool
250265
}
@@ -274,12 +289,13 @@ type Uploader struct {
274289
// })
275290
func NewUploader(client UploadAPIClient, options ...func(*Uploader)) *Uploader {
276291
u := &Uploader{
277-
S3: client,
278-
PartSize: DefaultUploadPartSize,
279-
Concurrency: DefaultUploadConcurrency,
280-
LeavePartsOnError: false,
281-
MaxUploadParts: MaxUploadParts,
282-
BufferProvider: defaultUploadBufferProvider(),
292+
S3: client,
293+
PartSize: DefaultUploadPartSize,
294+
Concurrency: DefaultUploadConcurrency,
295+
LeavePartsOnError: false,
296+
MaxUploadParts: MaxUploadParts,
297+
RequestChecksumCalculation: aws.RequestChecksumCalculationWhenSupported,
298+
BufferProvider: defaultUploadBufferProvider(),
283299
}
284300

285301
for _, option := range options {
@@ -776,7 +792,9 @@ func (u *multiuploader) initChecksumAlgorithm() {
776792
case u.in.ChecksumSHA256 != nil:
777793
u.in.ChecksumAlgorithm = types.ChecksumAlgorithmSha256
778794
default:
779-
u.in.ChecksumAlgorithm = types.ChecksumAlgorithmCrc32
795+
if u.cfg.RequestChecksumCalculation != aws.RequestChecksumCalculationWhenRequired {
796+
u.in.ChecksumAlgorithm = types.ChecksumAlgorithmCrc32
797+
}
780798
}
781799
}
782800

feature/s3/manager/upload_test.go

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1013,6 +1013,94 @@ func TestUploaderValidARN(t *testing.T) {
10131013
}
10141014
}
10151015

1016+
// TestUploadRequestChecksumCalculation tests that checksum behavior respects
1017+
// the RequestChecksumCalculation setting for backwards compatibility.
1018+
func TestUploadRequestChecksumCalculation(t *testing.T) {
1019+
testCases := []struct {
1020+
name string
1021+
requestChecksumCalculation aws.RequestChecksumCalculation
1022+
inputChecksumAlgorithm types.ChecksumAlgorithm
1023+
expectedChecksumAlgorithm types.ChecksumAlgorithm
1024+
description string
1025+
}{
1026+
{
1027+
name: "WhenRequired_NoDefault",
1028+
requestChecksumCalculation: aws.RequestChecksumCalculationWhenRequired,
1029+
inputChecksumAlgorithm: "",
1030+
expectedChecksumAlgorithm: "",
1031+
description: "When RequestChecksumCalculationWhenRequired is set, no default checksum should be applied (backwards compatibility)",
1032+
},
1033+
{
1034+
name: "WhenSupported_HasDefault",
1035+
requestChecksumCalculation: aws.RequestChecksumCalculationWhenSupported,
1036+
inputChecksumAlgorithm: "",
1037+
expectedChecksumAlgorithm: types.ChecksumAlgorithmCrc32,
1038+
description: "When RequestChecksumCalculationWhenSupported is set, default CRC32 checksum should be applied",
1039+
},
1040+
{
1041+
name: "WhenRequired_ExplicitPreserved",
1042+
requestChecksumCalculation: aws.RequestChecksumCalculationWhenRequired,
1043+
inputChecksumAlgorithm: types.ChecksumAlgorithmSha256,
1044+
expectedChecksumAlgorithm: types.ChecksumAlgorithmSha256,
1045+
description: "Explicit checksums should always be preserved regardless of RequestChecksumCalculation setting",
1046+
},
1047+
{
1048+
name: "WhenSupported_ExplicitPreserved",
1049+
requestChecksumCalculation: aws.RequestChecksumCalculationWhenSupported,
1050+
inputChecksumAlgorithm: types.ChecksumAlgorithmSha1,
1051+
expectedChecksumAlgorithm: types.ChecksumAlgorithmSha1,
1052+
description: "Explicit checksums should override defaults even with WhenSupported",
1053+
},
1054+
}
1055+
1056+
for _, tc := range testCases {
1057+
t.Run(tc.name, func(t *testing.T) {
1058+
c, invocations, args := s3testing.NewUploadLoggingClient(nil)
1059+
1060+
mgr := manager.NewUploader(c, func(u *manager.Uploader) {
1061+
u.RequestChecksumCalculation = tc.requestChecksumCalculation
1062+
})
1063+
1064+
input := &s3.PutObjectInput{
1065+
Bucket: aws.String("Bucket"),
1066+
Key: aws.String("Key"),
1067+
Body: bytes.NewReader(buf12MB),
1068+
}
1069+
if tc.inputChecksumAlgorithm != "" {
1070+
input.ChecksumAlgorithm = tc.inputChecksumAlgorithm
1071+
}
1072+
1073+
resp, err := mgr.Upload(context.Background(), input)
1074+
if err != nil {
1075+
t.Errorf("Expected no error but received %v", err)
1076+
}
1077+
1078+
expectedOps := []string{"CreateMultipartUpload", "UploadPart", "UploadPart", "UploadPart", "CompleteMultipartUpload"}
1079+
if diff := cmpDiff(expectedOps, *invocations); len(diff) > 0 {
1080+
t.Error(diff)
1081+
}
1082+
1083+
if resp.UploadID != "UPLOAD-ID" {
1084+
t.Errorf("expect %q, got %q", "UPLOAD-ID", resp.UploadID)
1085+
}
1086+
1087+
cmu := (*args)[0].(*s3.CreateMultipartUploadInput)
1088+
if cmu.ChecksumAlgorithm != tc.expectedChecksumAlgorithm {
1089+
t.Errorf("%s: Expected checksum algorithm %v in CreateMultipartUpload, but got %v",
1090+
tc.description, tc.expectedChecksumAlgorithm, cmu.ChecksumAlgorithm)
1091+
}
1092+
1093+
for i := 1; i <= 3; i++ {
1094+
uploadPart := (*args)[i].(*s3.UploadPartInput)
1095+
if uploadPart.ChecksumAlgorithm != tc.expectedChecksumAlgorithm {
1096+
t.Errorf("%s: Expected checksum algorithm %v in UploadPart %d, but got %v",
1097+
tc.description, tc.expectedChecksumAlgorithm, i, uploadPart.ChecksumAlgorithm)
1098+
}
1099+
}
1100+
})
1101+
}
1102+
}
1103+
10161104
type mockS3UploadServer struct {
10171105
*http.ServeMux
10181106

0 commit comments

Comments
 (0)