mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-10-31 01:10:41 +01:00
s3: validate per-part checksums in CompleteMultipartUpload
Parse the Checksum* elements of each completed part and enforce what AWS does for uploads created with x-amz-checksum-algorithm: every part must carry a checksum in the complete request (InvalidRequest when missing, BadDigest when it differs from the stored part checksum), and an x-amz-checksum-type header must match the upload resolved checksum type (BadDigest). Add the issue-11401 reproduction as a regression test.
This commit is contained in:
1 parent
4eb30f3b5b
commit
e9d2bdd02b
4 files changed
+168
-2
No files matched your search
@@ -3,6 +3,9 @@ package checksum_test
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/aws/aws-sdk-go-v2/aws"
|
||||
@@ -10,6 +13,7 @@ import (
|
||||
"github.com/aws/aws-sdk-go-v2/credentials"
|
||||
"github.com/aws/aws-sdk-go-v2/service/s3"
|
||||
"github.com/aws/aws-sdk-go-v2/service/s3/types"
|
||||
"github.com/aws/smithy-go"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
@@ -83,3 +87,127 @@ func TestMultipartPartInheritsChecksumAlgorithm(t *testing.T) {
|
||||
require.Equal(t, types.ChecksumTypeComposite, done.ChecksumType)
|
||||
require.NotEmpty(t, aws.ToString(done.ChecksumSHA256))
|
||||
}
|
||||
|
||||
// Mimics the .NET repro in https://github.com/seaweedfs/seaweedfs/issues/11401:
|
||||
// UploadPart carries an explicit client-computed x-amz-checksum-sha256 value,
|
||||
// CompleteMultipartUpload echoes it back per part, and HeadObject returns the
|
||||
// object checksum when x-amz-checksum-mode: ENABLED is sent (same as AWS).
|
||||
func TestIssue11401(t *testing.T) {
|
||||
client := getS3Client(t)
|
||||
|
||||
bucket := uniqueBucket()
|
||||
createBucket(t, client, bucket)
|
||||
defer cleanupBucket(t, client, bucket)
|
||||
|
||||
body := bytes.Repeat([]byte("x"), 1024)
|
||||
checksum := base64.StdEncoding.EncodeToString(func() []byte { s := sha256.Sum256(body); return s[:] }())
|
||||
key := "issue-11401"
|
||||
|
||||
create, err := client.CreateMultipartUpload(context.Background(), &s3.CreateMultipartUploadInput{
|
||||
Bucket: aws.String(bucket),
|
||||
Key: aws.String(key),
|
||||
ContentType: aws.String("application/octet-stream"),
|
||||
ChecksumAlgorithm: types.ChecksumAlgorithmSha256,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
part, err := client.UploadPart(context.Background(), &s3.UploadPartInput{
|
||||
Bucket: aws.String(bucket),
|
||||
Key: aws.String(key),
|
||||
UploadId: create.UploadId,
|
||||
PartNumber: aws.Int32(1),
|
||||
Body: bytes.NewReader(body),
|
||||
ChecksumSHA256: aws.String(checksum),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, checksum, aws.ToString(part.ChecksumSHA256))
|
||||
|
||||
done, err := client.CompleteMultipartUpload(context.Background(), &s3.CompleteMultipartUploadInput{
|
||||
Bucket: aws.String(bucket),
|
||||
Key: aws.String(key),
|
||||
UploadId: create.UploadId,
|
||||
MultipartUpload: &types.CompletedMultipartUpload{Parts: []types.CompletedPart{{
|
||||
ETag: part.ETag,
|
||||
PartNumber: aws.Int32(1),
|
||||
ChecksumSHA256: part.ChecksumSHA256,
|
||||
}}},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, types.ChecksumTypeComposite, done.ChecksumType)
|
||||
require.NotEmpty(t, aws.ToString(done.ChecksumSHA256))
|
||||
|
||||
head, err := client.HeadObject(context.Background(), &s3.HeadObjectInput{
|
||||
Bucket: aws.String(bucket),
|
||||
Key: aws.String(key),
|
||||
ChecksumMode: types.ChecksumModeEnabled,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, aws.ToString(done.ChecksumSHA256), aws.ToString(head.ChecksumSHA256))
|
||||
|
||||
headNoMode, err := client.HeadObject(context.Background(), &s3.HeadObjectInput{
|
||||
Bucket: aws.String(bucket),
|
||||
Key: aws.String(key),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, aws.ToString(headNoMode.ChecksumSHA256))
|
||||
}
|
||||
|
||||
// CompleteMultipartUpload validates the per-part checksums it is given, like
|
||||
// AWS: missing checksums fail with InvalidRequest and wrong ones with
|
||||
// BadDigest.
|
||||
func TestCompleteMultipartUploadValidatesPartChecksums(t *testing.T) {
|
||||
client := newWhenRequiredChecksumClient(t)
|
||||
|
||||
bucket := uniqueBucket()
|
||||
createBucket(t, client, bucket)
|
||||
defer cleanupBucket(t, client, bucket)
|
||||
|
||||
body := bytes.Repeat([]byte("z"), 1024)
|
||||
key := "validate-parts"
|
||||
|
||||
create, err := client.CreateMultipartUpload(context.Background(), &s3.CreateMultipartUploadInput{
|
||||
Bucket: aws.String(bucket),
|
||||
Key: aws.String(key),
|
||||
ChecksumAlgorithm: types.ChecksumAlgorithmSha256,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
part, err := client.UploadPart(context.Background(), &s3.UploadPartInput{
|
||||
Bucket: aws.String(bucket),
|
||||
Key: aws.String(key),
|
||||
UploadId: create.UploadId,
|
||||
PartNumber: aws.Int32(1),
|
||||
Body: bytes.NewReader(body),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, aws.ToString(part.ChecksumSHA256))
|
||||
|
||||
complete := func(checksum *string) error {
|
||||
_, err := client.CompleteMultipartUpload(context.Background(), &s3.CompleteMultipartUploadInput{
|
||||
Bucket: aws.String(bucket),
|
||||
Key: aws.String(key),
|
||||
UploadId: create.UploadId,
|
||||
MultipartUpload: &types.CompletedMultipartUpload{Parts: []types.CompletedPart{{
|
||||
ETag: part.ETag,
|
||||
PartNumber: aws.Int32(1),
|
||||
ChecksumSHA256: checksum,
|
||||
}}},
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
var apiErr smithy.APIError
|
||||
|
||||
err = complete(nil)
|
||||
require.Error(t, err)
|
||||
require.True(t, errors.As(err, &apiErr))
|
||||
require.Equal(t, "InvalidRequest", apiErr.ErrorCode())
|
||||
|
||||
wrong := base64.StdEncoding.EncodeToString(func() []byte { s := sha256.Sum256([]byte("other")); return s[:] }())
|
||||
err = complete(aws.String(wrong))
|
||||
require.Error(t, err)
|
||||
require.True(t, errors.As(err, &apiErr))
|
||||
require.Equal(t, "BadDigest", apiErr.ErrorCode())
|
||||
|
||||
require.NoError(t, complete(part.ChecksumSHA256))
|
||||
}
|
||||
Reference in new issue
Block a user