mirror of
https://github.com/versity/versitygw.git
synced 2025-12-23 05:05:16 +00:00
feat: adds integration tests for unsigned streaming payload trailer uploads
This commit is contained in:
@@ -2502,53 +2502,56 @@ func (p *Posix) UploadPartWithPostFunc(ctx context.Context, input *s3.UploadPart
|
|||||||
hash := md5.New()
|
hash := md5.New()
|
||||||
tr := io.TeeReader(r, hash)
|
tr := io.TeeReader(r, hash)
|
||||||
|
|
||||||
hashConfigs := []hashConfig{
|
chRdr, chunkUpload := input.Body.(middlewares.ChecksumReader)
|
||||||
{input.ChecksumCRC32, utils.HashTypeCRC32},
|
isTrailingChecksum := chunkUpload && chRdr.Algorithm() != ""
|
||||||
{input.ChecksumCRC32C, utils.HashTypeCRC32C},
|
|
||||||
{input.ChecksumSHA1, utils.HashTypeSha1},
|
|
||||||
{input.ChecksumSHA256, utils.HashTypeSha256},
|
|
||||||
{input.ChecksumCRC64NVME, utils.HashTypeCRC64NVME},
|
|
||||||
}
|
|
||||||
|
|
||||||
var hashRdr *utils.HashReader
|
var hashRdr *utils.HashReader
|
||||||
for _, config := range hashConfigs {
|
|
||||||
if config.value != nil {
|
if !isTrailingChecksum {
|
||||||
hashRdr, err = utils.NewHashReader(tr, *config.value, config.hashType)
|
hashConfigs := []hashConfig{
|
||||||
if err != nil {
|
{input.ChecksumCRC32, utils.HashTypeCRC32},
|
||||||
return nil, fmt.Errorf("initialize hash reader: %w", err)
|
{input.ChecksumCRC32C, utils.HashTypeCRC32C},
|
||||||
|
{input.ChecksumSHA1, utils.HashTypeSha1},
|
||||||
|
{input.ChecksumSHA256, utils.HashTypeSha256},
|
||||||
|
{input.ChecksumCRC64NVME, utils.HashTypeCRC64NVME},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, config := range hashConfigs {
|
||||||
|
if config.value != nil {
|
||||||
|
hashRdr, err = utils.NewHashReader(tr, *config.value, config.hashType)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("initialize hash reader: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
tr = hashRdr
|
||||||
}
|
}
|
||||||
|
|
||||||
tr = hashRdr
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If only the checksum algorithm is provided register
|
|
||||||
// a new HashReader to calculate the object checksum
|
|
||||||
if hashRdr == nil && input.ChecksumAlgorithm != "" {
|
|
||||||
hashRdr, err = utils.NewHashReader(tr, "", utils.HashType(strings.ToLower(string(input.ChecksumAlgorithm))))
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("initialize hash reader: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
tr = hashRdr
|
|
||||||
}
|
|
||||||
|
|
||||||
checksums, chErr := p.retrieveChecksums(nil, bucket, mpPath)
|
checksums, chErr := p.retrieveChecksums(nil, bucket, mpPath)
|
||||||
if chErr != nil && !errors.Is(chErr, meta.ErrNoSuchKey) {
|
if chErr != nil && !errors.Is(chErr, meta.ErrNoSuchKey) {
|
||||||
return nil, fmt.Errorf("retreive mp checksum: %w", chErr)
|
return nil, fmt.Errorf("retreive mp checksum: %w", chErr)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var inputChAlgo utils.HashType
|
||||||
|
if isTrailingChecksum {
|
||||||
|
inputChAlgo = utils.HashType(chRdr.Algorithm())
|
||||||
|
}
|
||||||
|
if hashRdr != nil {
|
||||||
|
inputChAlgo = hashRdr.Type()
|
||||||
|
}
|
||||||
|
|
||||||
// If checksum isn't provided for the part,
|
// If checksum isn't provided for the part,
|
||||||
// but it has been provided on mp initalization
|
// but it has been provided on mp initalization
|
||||||
// and checksum type is 'COMPOSITE', return mismatch error
|
// and checksum type is 'COMPOSITE', return mismatch error
|
||||||
if hashRdr == nil && chErr == nil && checksums.Type == types.ChecksumTypeComposite {
|
if inputChAlgo == "" && checksums.Type == types.ChecksumTypeComposite {
|
||||||
return nil, s3err.GetChecksumTypeMismatchErr(checksums.Algorithm, "null")
|
return nil, s3err.GetChecksumTypeMismatchErr(checksums.Algorithm, "null")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the provided checksum algorithm match
|
// Check if the provided checksum algorithm match
|
||||||
// the one specified on mp initialization
|
// the one specified on mp initialization
|
||||||
if hashRdr != nil && chErr == nil && checksums.Type != "" {
|
if inputChAlgo != "" && checksums.Type != "" {
|
||||||
algo := types.ChecksumAlgorithm(strings.ToUpper(string(hashRdr.Type())))
|
algo := types.ChecksumAlgorithm(strings.ToUpper(string(inputChAlgo)))
|
||||||
if checksums.Algorithm != algo {
|
if checksums.Algorithm != algo {
|
||||||
return nil, s3err.GetChecksumTypeMismatchErr(checksums.Algorithm, algo)
|
return nil, s3err.GetChecksumTypeMismatchErr(checksums.Algorithm, algo)
|
||||||
}
|
}
|
||||||
@@ -2557,11 +2560,13 @@ func (p *Posix) UploadPartWithPostFunc(ctx context.Context, input *s3.UploadPart
|
|||||||
// if no checksum algorithm or precalculated checksum is
|
// if no checksum algorithm or precalculated checksum is
|
||||||
// provided, but one has been on multipart upload initialization,
|
// provided, but one has been on multipart upload initialization,
|
||||||
// anyways calculate and store the uploaded part checksum
|
// anyways calculate and store the uploaded part checksum
|
||||||
if hashRdr == nil && checksums.Algorithm != "" {
|
if inputChAlgo == "" && checksums.Algorithm != "" {
|
||||||
hashRdr, err = utils.NewHashReader(tr, "", utils.HashType(strings.ToLower(string(checksums.Algorithm))))
|
hashType := utils.HashType(strings.ToLower(string(checksums.Algorithm)))
|
||||||
|
hashRdr, err = utils.NewHashReader(tr, "", hashType)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("initialize hash reader: %w", err)
|
return nil, fmt.Errorf("initialize hash reader: %w", err)
|
||||||
}
|
}
|
||||||
|
inputChAlgo = hashType
|
||||||
|
|
||||||
tr = hashRdr
|
tr = hashRdr
|
||||||
}
|
}
|
||||||
@@ -2588,14 +2593,21 @@ func (p *Posix) UploadPartWithPostFunc(ctx context.Context, input *s3.UploadPart
|
|||||||
ETag: &etag,
|
ETag: &etag,
|
||||||
}
|
}
|
||||||
|
|
||||||
if hashRdr != nil {
|
if inputChAlgo != "" {
|
||||||
checksum := s3response.Checksum{
|
checksum := s3response.Checksum{
|
||||||
Algorithm: input.ChecksumAlgorithm,
|
Algorithm: input.ChecksumAlgorithm,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate the provided checksum
|
var sum string
|
||||||
sum := hashRdr.Sum()
|
if isTrailingChecksum {
|
||||||
switch hashRdr.Type() {
|
sum = chRdr.Checksum()
|
||||||
|
}
|
||||||
|
if hashRdr != nil {
|
||||||
|
sum = hashRdr.Sum()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assign the checksum
|
||||||
|
switch inputChAlgo {
|
||||||
case utils.HashTypeCRC32:
|
case utils.HashTypeCRC32:
|
||||||
checksum.CRC32 = &sum
|
checksum.CRC32 = &sum
|
||||||
res.ChecksumCRC32 = &sum
|
res.ChecksumCRC32 = &sum
|
||||||
@@ -3115,7 +3127,6 @@ func (p *Posix) PutObjectWithPostFunc(ctx context.Context, po s3response.PutObje
|
|||||||
var sum string
|
var sum string
|
||||||
|
|
||||||
if isTrailingChecksum {
|
if isTrailingChecksum {
|
||||||
fmt.Println("reading from result reader")
|
|
||||||
chAlgo = utils.HashType(chRdr.Algorithm())
|
chAlgo = utils.HashType(chRdr.Algorithm())
|
||||||
sum = chRdr.Checksum()
|
sum = chRdr.Checksum()
|
||||||
} else if hashRdr != nil {
|
} else if hashRdr != nil {
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
423
tests/integration/unsigned_streaming_payload_trailer.go
Normal file
423
tests/integration/unsigned_streaming_payload_trailer.go
Normal file
@@ -0,0 +1,423 @@
|
|||||||
|
package integration
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/aws/aws-sdk-go-v2/service/s3"
|
||||||
|
"github.com/aws/aws-sdk-go-v2/service/s3/types"
|
||||||
|
"github.com/versity/versitygw/s3err"
|
||||||
|
)
|
||||||
|
|
||||||
|
func UnsignedStreaminPayloadTrailer_malformed_trailer(s *S3Conf) error {
|
||||||
|
testName := "UnsignedStreaminPayloadTrailer_malformed_trailer"
|
||||||
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
||||||
|
object := "my-obj"
|
||||||
|
for i, test := range []struct {
|
||||||
|
trailer string
|
||||||
|
decContentLength string
|
||||||
|
payload string
|
||||||
|
}{
|
||||||
|
// missing trailer in the payload
|
||||||
|
{"x-amz-checksum-crc64nvme", "5", "5\r\nhello\r\n0\r\n\r\n"},
|
||||||
|
// empty checksum key
|
||||||
|
{"x-amz-checksum-crc64nvme", "5", "5\r\nhello\r\n0\r\n:M3eFcAZSQlc=\r\n\r\n"},
|
||||||
|
// missing x-amz-trailer
|
||||||
|
{"", "5", "5\r\nhello\r\n0\r\nx-amz-checksum-crc64nvme:M3eFcAZSQlc=\r\n\r\n"},
|
||||||
|
// invalid trailer in payload
|
||||||
|
{"x-amz-checksum-crc64nvme", "5", "5\r\nhello\r\n0\r\ninvalid_trailer:M3eFcAZSQlc=\r\n\r\n"},
|
||||||
|
} {
|
||||||
|
reqHeaders := map[string]string{
|
||||||
|
"x-amz-decoded-content-length": test.decContentLength,
|
||||||
|
}
|
||||||
|
if test.trailer != "" {
|
||||||
|
reqHeaders["x-amz-trailer"] = test.trailer
|
||||||
|
}
|
||||||
|
|
||||||
|
_, apiErr, err := testUnsignedStreamingPayloadTrailerObjectPut(s, bucket, object, []byte(test.payload), reqHeaders)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("test %v failed: %w", i+1, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := compareS3ApiError(s3err.GetAPIError(s3err.ErrMalformedTrailer), apiErr); err != nil {
|
||||||
|
return fmt.Errorf("test %v failed: %w", i+1, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func UnsignedStreamingPayloadTrailer_missing_invalid_dec_content_length(s *S3Conf) error {
|
||||||
|
testName := "UnsignedStreamingPayloadTrailer_missing_invalid_dec_content_length"
|
||||||
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
||||||
|
object := "object"
|
||||||
|
for i, clength := range []string{"", "abc", "12x"} {
|
||||||
|
reqHeaders := map[string]string{
|
||||||
|
"x-amz-trailer": "x-amz-checksum-crc64nvme",
|
||||||
|
}
|
||||||
|
if clength != "" {
|
||||||
|
reqHeaders["x-amz-decoded-content-length"] = clength
|
||||||
|
}
|
||||||
|
body := []byte("5\r\nhello\r\n0\r\nx-amz-checksum-crc64nvme:M3eFcAZSQlc=\r\n\r\n")
|
||||||
|
|
||||||
|
_, apiErr, err := testUnsignedStreamingPayloadTrailerObjectPut(s, bucket, object, body, reqHeaders)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("test %v failed: %w", i+1, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := compareS3ApiError(s3err.GetAPIError(s3err.ErrMissingContentLength), apiErr); err != nil {
|
||||||
|
return fmt.Errorf("test %v failed: %w", i+1, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func UnsignedStreamingPayloadTrailer_invalid_trailing_checksum(s *S3Conf) error {
|
||||||
|
testName := "UnsignedStreamingPayloadTrailer_invalid_trailing_checksum"
|
||||||
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
||||||
|
object := "my-object"
|
||||||
|
|
||||||
|
reqHeaders := map[string]string{
|
||||||
|
"x-amz-decoded-content-length": "5",
|
||||||
|
"x-amz-trailer": "x-amz-checksum-crc64nvme",
|
||||||
|
}
|
||||||
|
|
||||||
|
body := []byte("5\r\nhello\r\n0\r\nx-amz-checksum-crc64nvme:invalid_checksum\r\n\r\n")
|
||||||
|
|
||||||
|
_, apiErr, err := testUnsignedStreamingPayloadTrailerObjectPut(s, bucket, object, body, reqHeaders)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return compareS3ApiError(s3err.GetInvalidTrailingChecksumHeaderErr("x-amz-checksum-crc64nvme"), apiErr)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func UnsignedStreamingPayloadTrailer_incorrect_trailing_checksum(s *S3Conf) error {
|
||||||
|
testName := "UnsignedStreamingPayloadTrailer_incorrect_trailing_checksum"
|
||||||
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
||||||
|
object := "my-object"
|
||||||
|
|
||||||
|
reqHeaders := map[string]string{
|
||||||
|
"x-amz-decoded-content-length": "5",
|
||||||
|
"x-amz-trailer": "x-amz-checksum-crc64nvme",
|
||||||
|
}
|
||||||
|
|
||||||
|
// valid crc64nvme, but incorrect
|
||||||
|
body := []byte("5\r\nhello\r\n0\r\nx-amz-checksum-crc64nvme:QFRKMGE3tuw=\r\n\r\n")
|
||||||
|
|
||||||
|
_, apiErr, err := testUnsignedStreamingPayloadTrailerObjectPut(s, bucket, object, body, reqHeaders)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return compareS3ApiError(s3err.GetChecksumBadDigestErr("CRC64NVME"), apiErr)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func UnsignedStreamingPayloadTrailer_multiple_checksum_headers(s *S3Conf) error {
|
||||||
|
testName := "UnsignedStreamingPayloadTrailer_multiple_checksum_headers"
|
||||||
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
||||||
|
object := "my-object"
|
||||||
|
body := []byte("5\r\nhello\r\n0\r\nx-amz-checksum-crc64nvme:M3eFcAZSQlc=\r\n\r\n")
|
||||||
|
|
||||||
|
for i, test := range []struct {
|
||||||
|
key string
|
||||||
|
value string
|
||||||
|
}{
|
||||||
|
{"crc32", "NhCmhg=="},
|
||||||
|
{"crc32c", "+Cy97w=="},
|
||||||
|
{"crc64nvme", "QFRKMGE3tuw="},
|
||||||
|
{"sha1", "qvTGHdzF6KLavt4PO0gs2a6pQ00="},
|
||||||
|
{"sha256", "LPJNul+wow4m6DsqxbninhsWHlwfp0JecwQzYpOLmCQ="},
|
||||||
|
} {
|
||||||
|
reqHeaders := map[string]string{
|
||||||
|
"x-amz-decoded-content-length": "5",
|
||||||
|
"x-amz-trailer": "x-amz-checksum-crc64nvme",
|
||||||
|
fmt.Sprintf("x-amz-checksum-%s", test.key): test.value,
|
||||||
|
}
|
||||||
|
|
||||||
|
_, apiErr, err := testUnsignedStreamingPayloadTrailerObjectPut(s, bucket, object, body, reqHeaders)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("test %v failed: %w", i+1, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := compareS3ApiError(s3err.GetAPIError(s3err.ErrMultipleChecksumHeaders), apiErr); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func UnsignedStreamingPayloadTrailer_sdk_algo_and_trailer_mismatch(s *S3Conf) error {
|
||||||
|
testName := "UnsignedStreamingPayloadTrailer_sdk_algo_and_trailer_mismatch"
|
||||||
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
||||||
|
object := "my-object"
|
||||||
|
|
||||||
|
reqHeaders := map[string]string{
|
||||||
|
"x-amz-decoded-content-length": "5",
|
||||||
|
"x-amz-trailer": "x-amz-checksum-crc64nvme",
|
||||||
|
"x-amz-sdk-checksum-algorithm": "sha1",
|
||||||
|
}
|
||||||
|
|
||||||
|
// valid crc64nvme, but incorrect
|
||||||
|
body := []byte("5\r\nhello\r\n0\r\nx-amz-checksum-crc64nvme:M3eFcAZSQlc=\r\n\r\n")
|
||||||
|
|
||||||
|
_, apiErr, err := testUnsignedStreamingPayloadTrailerObjectPut(s, bucket, object, body, reqHeaders)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return compareS3ApiError(s3err.GetInvalidChecksumHeaderErr("x-amz-sdk-checksum-algorithm"), apiErr)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func UnsignedStreamingPayloadTrailer_no_trailer_should_calculate_crc64nvme(s *S3Conf) error {
|
||||||
|
testName := "UnsignedStreamingPayloadTrailer_no_trailer_should_calculate_crc64nvme"
|
||||||
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
||||||
|
object := "my-object"
|
||||||
|
reqHeaders := map[string]string{
|
||||||
|
"x-amz-decoded-content-length": "11",
|
||||||
|
}
|
||||||
|
|
||||||
|
body := []byte("B\r\nhello world\r\n0\r\n\r\n")
|
||||||
|
|
||||||
|
headers, apiErr, err := testUnsignedStreamingPayloadTrailerObjectPut(s, bucket, object, body, reqHeaders)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if apiErr != nil {
|
||||||
|
return fmt.Errorf("%s: %s", apiErr.Code, apiErr.Message)
|
||||||
|
}
|
||||||
|
|
||||||
|
csum := headers["x-amz-checksum-crc64nvme"]
|
||||||
|
expectedCsum := "jSnVw/bqjr4="
|
||||||
|
if csum != expectedCsum {
|
||||||
|
return fmt.Errorf("expected the crc64nvme to be %s, instead got %s", expectedCsum, csum)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func UnsignedStreamingPayloadTrailer_no_payload_trailer_only_headers(s *S3Conf) error {
|
||||||
|
testName := "UnsignedStreamingPayloadTrailer_no_payload_trailer_only_headers"
|
||||||
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
||||||
|
object := "my-object"
|
||||||
|
body := []byte("7\r\nabcdefg\r\n0\r\n\r\n")
|
||||||
|
|
||||||
|
for i, test := range []struct {
|
||||||
|
key string
|
||||||
|
value string
|
||||||
|
}{
|
||||||
|
{"crc32", "MSpqpg=="},
|
||||||
|
{"crc32c", "5if0QQ=="},
|
||||||
|
{"crc64nvme", "SmzZ/LTp1CA="},
|
||||||
|
{"sha1", "L7XhNBn8iSRoZeejJPR27GJOh0A="},
|
||||||
|
{"sha256", "fRpUEnsiJQL1t5tfsIAwYRUqRPkrN+I8ZSe69mXU2po="},
|
||||||
|
} {
|
||||||
|
csumHdr := fmt.Sprintf("x-amz-checksum-%s", test.key)
|
||||||
|
reqHeaders := map[string]string{
|
||||||
|
"x-amz-decoded-content-length": "7",
|
||||||
|
csumHdr: test.value,
|
||||||
|
}
|
||||||
|
|
||||||
|
headers, apiErr, err := testUnsignedStreamingPayloadTrailerObjectPut(s, bucket, object, body, reqHeaders)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("test %v failed: %w", i+1, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if apiErr != nil {
|
||||||
|
return fmt.Errorf("test %v failed: (%s) %s", i+1, apiErr.Code, apiErr.Message)
|
||||||
|
}
|
||||||
|
|
||||||
|
if headers[csumHdr] != test.value {
|
||||||
|
return fmt.Errorf("expected the %s to be %s, instead got %s", csumHdr, test.value, headers[csumHdr])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func UnsignedStreamingPayloadTrailer_success_both_sdk_algo_and_trailer(s *S3Conf) error {
|
||||||
|
testName := "UnsignedStreamingPayloadTrailer_success_both_sdk_algo_and_trailer"
|
||||||
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
||||||
|
object := "my-object"
|
||||||
|
|
||||||
|
for i, test := range []struct {
|
||||||
|
key string
|
||||||
|
value string
|
||||||
|
}{
|
||||||
|
{"crc32", "MSpqpg=="},
|
||||||
|
{"crc32c", "5if0QQ=="},
|
||||||
|
{"crc64nvme", "SmzZ/LTp1CA="},
|
||||||
|
{"sha1", "L7XhNBn8iSRoZeejJPR27GJOh0A="},
|
||||||
|
{"sha256", "fRpUEnsiJQL1t5tfsIAwYRUqRPkrN+I8ZSe69mXU2po="},
|
||||||
|
} {
|
||||||
|
csumHdr := fmt.Sprintf("x-amz-checksum-%s", test.key)
|
||||||
|
reqHeaders := map[string]string{
|
||||||
|
"x-amz-decoded-content-length": "7",
|
||||||
|
"x-amz-sdk-checksum-algorithm": strings.ToUpper(test.key),
|
||||||
|
"x-amz-trailer": csumHdr,
|
||||||
|
}
|
||||||
|
body := bytes.NewBuffer([]byte("7\r\nabcdefg\r\n0\r\n"))
|
||||||
|
|
||||||
|
_, err := body.WriteString(fmt.Sprintf("%s:%s\r\n\r\n", csumHdr, test.value))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("test %v failed: %w", i+1, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
headers, apiErr, err := testUnsignedStreamingPayloadTrailerObjectPut(s, bucket, object, body.Bytes(), reqHeaders)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("test %v failed: %w", i+1, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if apiErr != nil {
|
||||||
|
return fmt.Errorf("test %v failed: (%s) %s", i+1, apiErr.Code, apiErr.Message)
|
||||||
|
}
|
||||||
|
|
||||||
|
if headers[csumHdr] != test.value {
|
||||||
|
return fmt.Errorf("expected the %s to be %s, instead got %s", csumHdr, test.value, headers[csumHdr])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func UnsignedStreamingPayloadTrailer_UploadPart_no_trailer_composite_checksum(s *S3Conf) error {
|
||||||
|
testName := "UnsignedStreamingPayloadTrailer_UploadPart_no_trailer_composite_checksum"
|
||||||
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
||||||
|
object := "my-object"
|
||||||
|
mp, err := createMp(s3client, bucket, object, withChecksumType(types.ChecksumTypeComposite), withChecksum(types.ChecksumAlgorithmCrc32))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
reqHeaders := map[string]string{
|
||||||
|
"x-amz-decoded-content-length": "7",
|
||||||
|
}
|
||||||
|
|
||||||
|
body := []byte("7\r\nabcdefg\r\n0\r\n\r\n")
|
||||||
|
|
||||||
|
_, apiErr, err := testUnsignedStreamingPayloadTrailerUploadPart(s, bucket, object, mp.UploadId, body, reqHeaders)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return compareS3ApiError(s3err.GetChecksumTypeMismatchErr(types.ChecksumAlgorithmCrc32, "null"), apiErr)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func UnsignedStreamingPayloadTrailer_UploadPart_no_trailer_full_object(s *S3Conf) error {
|
||||||
|
testName := "UnsignedStreamingPayloadTrailer_UploadPart_no_trailer_composite_checksum"
|
||||||
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
||||||
|
object := "my-object"
|
||||||
|
mp, err := createMp(s3client, bucket, object, withChecksumType(types.ChecksumTypeFullObject), withChecksum(types.ChecksumAlgorithmCrc32))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
reqHeaders := map[string]string{
|
||||||
|
"x-amz-decoded-content-length": "7",
|
||||||
|
}
|
||||||
|
|
||||||
|
body := []byte("7\r\nabcdefg\r\n0\r\n\r\n")
|
||||||
|
|
||||||
|
headers, apiErr, err := testUnsignedStreamingPayloadTrailerUploadPart(s, bucket, object, mp.UploadId, body, reqHeaders)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if apiErr != nil {
|
||||||
|
return fmt.Errorf("(%s) %s", apiErr.Code, apiErr.Message)
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedCsum := "MSpqpg=="
|
||||||
|
actualCsum := headers["x-amz-checksum-crc32"]
|
||||||
|
|
||||||
|
if expectedCsum != actualCsum {
|
||||||
|
return fmt.Errorf("expected the crc32 checksum to be %s, instead got %s", expectedCsum, actualCsum)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func UnsignedStreamingPayloadTrailer_UploadPart_trailer_and_mp_algo_mismatch(s *S3Conf) error {
|
||||||
|
testName := "UnsignedStreamingPayloadTrailer_UploadPart_trailer_and_mp_algo_mismatch"
|
||||||
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
||||||
|
object := "my-object"
|
||||||
|
mp, err := createMp(s3client, bucket, object, withChecksumType(types.ChecksumTypeFullObject), withChecksum(types.ChecksumAlgorithmCrc32))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
reqHeaders := map[string]string{
|
||||||
|
"x-amz-decoded-content-length": "7",
|
||||||
|
"x-amz-trailer": "x-amz-checksum-sha256",
|
||||||
|
}
|
||||||
|
|
||||||
|
body := []byte("7\r\nabcdefg\r\n0\r\nx-amz-checksum-sha256:fRpUEnsiJQL1t5tfsIAwYRUqRPkrN+I8ZSe69mXU2po=\r\n\r\n")
|
||||||
|
|
||||||
|
_, apiErr, err := testUnsignedStreamingPayloadTrailerUploadPart(s, bucket, object, mp.UploadId, body, reqHeaders)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return compareS3ApiError(s3err.GetChecksumTypeMismatchErr(types.ChecksumAlgorithmCrc32, types.ChecksumAlgorithmSha256), apiErr)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func UnsignedStreamingPayloadTrailer_UploadPart_success_with_trailer(s *S3Conf) error {
|
||||||
|
testName := "UnsignedStreamingPayloadTrailer_UploadPart_success_with_trailer"
|
||||||
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
||||||
|
object := "my-object"
|
||||||
|
|
||||||
|
for i, test := range []struct {
|
||||||
|
key string
|
||||||
|
value string
|
||||||
|
}{
|
||||||
|
{"crc32", "QWaN2w=="},
|
||||||
|
{"crc32c", "R/I7iQ=="},
|
||||||
|
{"crc64nvme", "dPVWc2vU1+Q="},
|
||||||
|
{"sha1", "YR/1TvTYOJz5gtqVFoBJBtmTibY="},
|
||||||
|
{"sha256", "eXuwq/95jXIAr3aF3KeQHt/8Ur8mUA1b2XKCZY7iQVI="},
|
||||||
|
} {
|
||||||
|
mp, err := createMp(s3client, bucket, object, withChecksum(types.ChecksumAlgorithm(strings.ToUpper(test.key))))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
csumHdr := fmt.Sprintf("x-amz-checksum-%s", test.key)
|
||||||
|
reqHeaders := map[string]string{
|
||||||
|
"x-amz-decoded-content-length": "10",
|
||||||
|
"x-amz-sdk-checksum-algorithm": test.key,
|
||||||
|
"x-amz-trailer": csumHdr,
|
||||||
|
}
|
||||||
|
body := bytes.NewBuffer([]byte("A\r\ndummy data\r\n0\r\n"))
|
||||||
|
|
||||||
|
_, err = body.WriteString(fmt.Sprintf("%s:%s\r\n\r\n", csumHdr, test.value))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("test %v failed: %w", i+1, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
headers, apiErr, err := testUnsignedStreamingPayloadTrailerUploadPart(s, bucket, object, mp.UploadId, body.Bytes(), reqHeaders)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("test %v failed: %w", i+1, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if apiErr != nil {
|
||||||
|
return fmt.Errorf("test %v failed: (%s) %s", i+1, apiErr.Code, apiErr.Message)
|
||||||
|
}
|
||||||
|
|
||||||
|
if headers[csumHdr] != test.value {
|
||||||
|
return fmt.Errorf("expected the %s to be %s, instead got %s", csumHdr, test.value, headers[csumHdr])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -371,11 +371,19 @@ func checkHTTPResponseApiErr(resp *http.Response, apiErr s3err.APIError) error {
|
|||||||
if resp.StatusCode != apiErr.HTTPStatusCode {
|
if resp.StatusCode != apiErr.HTTPStatusCode {
|
||||||
return fmt.Errorf("expected response status code to be %v, instead got %v", apiErr.HTTPStatusCode, resp.StatusCode)
|
return fmt.Errorf("expected response status code to be %v, instead got %v", apiErr.HTTPStatusCode, resp.StatusCode)
|
||||||
}
|
}
|
||||||
if errResp.Code != apiErr.Code {
|
return compareS3ApiError(apiErr, &errResp)
|
||||||
return fmt.Errorf("expected error code to be %v, instead got %v", apiErr.Code, errResp.Code)
|
}
|
||||||
|
|
||||||
|
func compareS3ApiError(expected s3err.APIError, received *s3err.APIErrorResponse) error {
|
||||||
|
if received == nil {
|
||||||
|
return fmt.Errorf("expected %w, received nil", expected)
|
||||||
}
|
}
|
||||||
if errResp.Message != apiErr.Description {
|
|
||||||
return fmt.Errorf("expected error message to be %v, instead got %v", apiErr.Description, errResp.Message)
|
if received.Code != expected.Code {
|
||||||
|
return fmt.Errorf("expected error code to be %v, instead got %v", expected.Code, received.Code)
|
||||||
|
}
|
||||||
|
if received.Message != expected.Description {
|
||||||
|
return fmt.Errorf("expected error message to be %v, instead got %v", expected.Description, received.Message)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -2010,3 +2018,78 @@ func putBucketPolicy(client *s3.Client, bucket, policy string) error {
|
|||||||
cancel()
|
cancel()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func sendSignedRequest(s *S3Conf, req *http.Request, cancel context.CancelFunc) (map[string]string, *s3err.APIErrorResponse, error) {
|
||||||
|
signer := v4.NewSigner()
|
||||||
|
signErr := signer.SignHTTP(req.Context(), aws.Credentials{AccessKeyID: s.awsID, SecretAccessKey: s.awsSecret}, req, "STREAMING-UNSIGNED-PAYLOAD-TRAILER", "s3", s.awsRegion, time.Now())
|
||||||
|
if signErr != nil {
|
||||||
|
cancel()
|
||||||
|
return nil, nil, fmt.Errorf("failed to sign the request: %w", signErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := s.httpClient.Do(req)
|
||||||
|
cancel()
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("failed to send the request: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode >= 300 {
|
||||||
|
defer resp.Body.Close()
|
||||||
|
bodyBytes, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("failed to read the request body: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var errResp s3err.APIErrorResponse
|
||||||
|
err = xml.Unmarshal(bodyBytes, &errResp)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("failed to unmarshal response body: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, &errResp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
headers := map[string]string{}
|
||||||
|
for key, val := range resp.Header {
|
||||||
|
headers[strings.ToLower(key)] = val[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
return headers, nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func testUnsignedStreamingPayloadTrailerObjectPut(s *S3Conf, bucket, object string, body []byte, reqHeaders map[string]string) (map[string]string, *s3err.APIErrorResponse, error) {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
||||||
|
req, err := http.NewRequestWithContext(ctx, http.MethodPut, s.endpoint+"/"+bucket+"/"+object, bytes.NewReader(body))
|
||||||
|
if err != nil {
|
||||||
|
cancel()
|
||||||
|
return nil, nil, fmt.Errorf("failed to create a request: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Header.Add("x-amz-content-sha256", "STREAMING-UNSIGNED-PAYLOAD-TRAILER")
|
||||||
|
for key, val := range reqHeaders {
|
||||||
|
req.Header.Add(key, val)
|
||||||
|
}
|
||||||
|
|
||||||
|
return sendSignedRequest(s, req, cancel)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testUnsignedStreamingPayloadTrailerUploadPart(s *S3Conf, bucket, object string, uploadId *string, body []byte, reqHeaders map[string]string) (map[string]string, *s3err.APIErrorResponse, error) {
|
||||||
|
if uploadId == nil {
|
||||||
|
return nil, nil, fmt.Errorf("empty upload id")
|
||||||
|
}
|
||||||
|
|
||||||
|
uri := fmt.Sprintf("%s/%s/%s?uploadId=%s&partNumber=%v", s.endpoint, bucket, object, *uploadId, 1)
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
||||||
|
req, err := http.NewRequestWithContext(ctx, http.MethodPut, uri, bytes.NewReader(body))
|
||||||
|
if err != nil {
|
||||||
|
cancel()
|
||||||
|
return nil, nil, fmt.Errorf("failed to create a request: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Header.Add("x-amz-content-sha256", "STREAMING-UNSIGNED-PAYLOAD-TRAILER")
|
||||||
|
for key, val := range reqHeaders {
|
||||||
|
req.Header.Add(key, val)
|
||||||
|
}
|
||||||
|
|
||||||
|
return sendSignedRequest(s, req, cancel)
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user