Files
versitygw/tests/integration/PutObject.go
niksis02 a75aa9bad5 fix: fixes if-none-match precondition header logic in object write operations
Fixes #1708

This PR focuses on evaluating the `x-amz-if-none-match` precondition header for object PUT operations. If any value other than `*` is provided, a `NotImplemented` error is returned. If `If-Match` is used together with `If-None-Match`, regardless of the value combination, a `NotImplemented` error is returned. When only `If-None-Match: *` is specified, a `PreconditionFailed` error is returned if the object already exists in `PutObject` or `CompleteMultipartUpload`; if the object does not exist, object creation is allowed.
2026-01-02 22:59:13 +04:00

930 lines
28 KiB
Go

// Copyright 2023 Versity Software
// This file is licensed under the Apache License, Version 2.0
// (the "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package integration
import (
"bytes"
"context"
"fmt"
"strings"
"time"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/s3"
"github.com/aws/aws-sdk-go-v2/service/s3/types"
"github.com/versity/versitygw/s3err"
"golang.org/x/sync/errgroup"
)
func PutObject_non_existing_bucket(s *S3Conf) error {
testName := "PutObject_non_existing_bucket"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
_, err := putObjects(s3client, []string{"my-obj"}, "non-existing-bucket")
if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrNoSuchBucket)); err != nil {
return err
}
return nil
})
}
func PutObject_special_chars(s *S3Conf) error {
testName := "PutObject_special_chars"
objnames := []string{
"my!key", "my-key", "my_key", "my.key", "my'key", "my(key", "my)key",
"my&key", "my@key", "my=key", "my;key", "my:key", "my key", "my,key",
"my?key", "my^key", "my{}key", "my%key", "my`key",
"my[]key", "my~key", "my<>key", "my|key", "my#key",
}
if !s.azureTests {
// azure currently can't handle backslashes in object names
objnames = append(objnames, "my\\key")
}
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
objs, err := putObjects(s3client, objnames, bucket)
if err != nil {
return err
}
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
res, err := s3client.ListObjectsV2(ctx, &s3.ListObjectsV2Input{
Bucket: &bucket,
})
cancel()
if err != nil {
return err
}
if !compareObjects(objs, res.Contents) {
return fmt.Errorf("expected the objects to be %vß, instead got %v",
objStrings(objs), objStrings(res.Contents))
}
return nil
})
}
func PutObject_tagging(s *S3Conf) error {
testName := "PutObject_tagging"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
obj := "my-obj"
testTagging := func(taggging string, result map[string]string, expectedErr error) error {
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
_, err := s3client.PutObject(ctx, &s3.PutObjectInput{
Bucket: &bucket,
Key: &obj,
Tagging: &taggging,
})
cancel()
if err == nil && expectedErr != nil {
return fmt.Errorf("expected err %w, instead got nil", expectedErr)
}
if err != nil {
if expectedErr == nil {
return err
}
switch eErr := expectedErr.(type) {
case s3err.APIError:
return checkApiErr(err, eErr)
default:
return fmt.Errorf("invalid err provided: %w", expectedErr)
}
}
ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
res, err := s3client.GetObjectTagging(ctx, &s3.GetObjectTaggingInput{
Bucket: &bucket,
Key: &obj,
})
cancel()
if err != nil {
return err
}
if len(res.TagSet) != len(result) {
return fmt.Errorf("tag lengths are not equal: (expected): %v, (got): %v", len(result), len(res.TagSet))
}
for _, tag := range res.TagSet {
val, ok := result[getString(tag.Key)]
if !ok {
return fmt.Errorf("tag key not found: %v", getString(tag.Key))
}
if val != getString(tag.Value) {
return fmt.Errorf("expected the %v tag value to be %v, instead got %v", getString(tag.Key), val, getString(tag.Value))
}
}
return nil
}
for i, el := range []struct {
tagging string
result map[string]string
expectedErr error
}{
// success cases
{"&", map[string]string{}, nil},
{"&&&", map[string]string{}, nil},
{"key", map[string]string{"key": ""}, nil},
{"key&", map[string]string{"key": ""}, nil},
{"key=&", map[string]string{"key": ""}, nil},
{"key=val&", map[string]string{"key": "val"}, nil},
{"key1&key2", map[string]string{"key1": "", "key2": ""}, nil},
{"key1=val1&key2=val2", map[string]string{"key1": "val1", "key2": "val2"}, nil},
{"key@=val@", map[string]string{"key@": "val@"}, nil},
// invalid url-encoded
{"=", nil, s3err.GetAPIError(s3err.ErrInvalidURLEncodedTagging)},
{"key%", nil, s3err.GetAPIError(s3err.ErrInvalidURLEncodedTagging)},
// duplicate keys
{"key=val&key=val", nil, s3err.GetAPIError(s3err.ErrInvalidURLEncodedTagging)},
// invalid tag keys
{"key?=val", nil, s3err.GetAPIError(s3err.ErrInvalidTagKey)},
{"key(=val", nil, s3err.GetAPIError(s3err.ErrInvalidTagKey)},
{"key*=val", nil, s3err.GetAPIError(s3err.ErrInvalidTagKey)},
{"key$=val", nil, s3err.GetAPIError(s3err.ErrInvalidTagKey)},
{"key#=val", nil, s3err.GetAPIError(s3err.ErrInvalidTagKey)},
{"key!=val", nil, s3err.GetAPIError(s3err.ErrInvalidTagKey)},
// invalid tag values
{"key=val?", nil, s3err.GetAPIError(s3err.ErrInvalidTagValue)},
{"key=val(", nil, s3err.GetAPIError(s3err.ErrInvalidTagValue)},
{"key=val*", nil, s3err.GetAPIError(s3err.ErrInvalidTagValue)},
{"key=val$", nil, s3err.GetAPIError(s3err.ErrInvalidTagValue)},
{"key=val#", nil, s3err.GetAPIError(s3err.ErrInvalidTagValue)},
{"key=val!", nil, s3err.GetAPIError(s3err.ErrInvalidTagValue)},
// success special chars
{"key-key_key.key/key=value-value_value.value/value", map[string]string{"key-key_key.key/key": "value-value_value.value/value"}, nil},
// should handle supported encoded characters
{"key%2E=value%2F", map[string]string{"key.": "value/"}, nil},
{"key%2D=value%2B", map[string]string{"key-": "value+"}, nil},
{"key++key=value++value", map[string]string{"key key": "value value"}, nil},
{"key%20key=value%20value", map[string]string{"key key": "value value"}, nil},
{"key%5Fkey=value%5Fvalue", map[string]string{"key_key": "value_value"}, nil},
} {
if s.azureTests {
// azure doesn't support '@' character
if strings.Contains(el.tagging, "@") {
continue
}
}
err := testTagging(el.tagging, el.result, el.expectedErr)
if err != nil {
return fmt.Errorf("test case %v failed: %w", i+1, err)
}
}
return nil
})
}
func PutObject_missing_object_lock_retention_config(s *S3Conf) error {
testName := "PutObject_missing_object_lock_retention_config"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
key := "my-obj"
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
_, err := s3client.PutObject(ctx, &s3.PutObjectInput{
Bucket: &bucket,
Key: &key,
ObjectLockMode: types.ObjectLockModeCompliance,
})
cancel()
if err := checkSdkApiErr(err, "InvalidRequest"); err != nil {
return err
}
// client sdk regression issue prevents getting full error message,
// change back to below once this is fixed:
// https://github.com/aws/aws-sdk-go-v2/issues/2921
// if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrObjectLockInvalidHeaders)); err != nil {
// return err
// }
retainDate := time.Now().Add(time.Hour * 48)
ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
_, err = s3client.PutObject(ctx, &s3.PutObjectInput{
Bucket: &bucket,
Key: &key,
ObjectLockRetainUntilDate: &retainDate,
})
cancel()
if err := checkSdkApiErr(err, "InvalidRequest"); err != nil {
return err
}
// client sdk regression issue prevents getting full error message,
// change back to below once this is fixed:
// https://github.com/aws/aws-sdk-go-v2/issues/2921
// if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrObjectLockInvalidHeaders)); err != nil {
// return err
// }
return nil
})
}
func PutObject_with_object_lock(s *S3Conf) error {
testName := "PutObject_with_object_lock"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
obj := "my-obj"
retainUntilDate := time.Now().AddDate(1, 0, 0)
_, err := putObjectWithData(10, &s3.PutObjectInput{
Bucket: &bucket,
Key: &obj,
ObjectLockLegalHoldStatus: types.ObjectLockLegalHoldStatusOn,
ObjectLockMode: types.ObjectLockModeCompliance,
ObjectLockRetainUntilDate: &retainUntilDate,
}, s3client)
if err != nil {
return err
}
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
out, err := s3client.HeadObject(ctx, &s3.HeadObjectInput{
Bucket: &bucket,
Key: &obj,
})
cancel()
if err != nil {
return err
}
if out.ObjectLockMode != types.ObjectLockModeCompliance {
return fmt.Errorf("expected object lock mode to be %v, instead got %v", types.ObjectLockModeCompliance, out.ObjectLockMode)
}
if out.ObjectLockLegalHoldStatus != types.ObjectLockLegalHoldStatusOn {
return fmt.Errorf("expected object lock mode to be %v, instead got %v", types.ObjectLockLegalHoldStatusOn, out.ObjectLockLegalHoldStatus)
}
return cleanupLockedObjects(s3client, bucket, []objToDelete{{key: obj, removeLegalHold: true, isCompliance: true}})
}, withLock())
}
func PutObject_invalid_legal_hold(s *S3Conf) error {
testName := "PutObject_invalid_legal_hold"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
_, err := putObjectWithData(10, &s3.PutObjectInput{
Bucket: &bucket,
Key: getPtr("foo"),
ObjectLockLegalHoldStatus: types.ObjectLockLegalHoldStatus("invalid_status"),
}, s3client)
return checkApiErr(err, s3err.GetAPIError(s3err.ErrInvalidLegalHoldStatus))
}, withLock())
}
func PutObject_invalid_object_lock_mode(s *S3Conf) error {
testName := "PutObject_invalid_object_lock_mode"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
rDate := time.Now().Add(time.Hour * 10)
_, err := putObjectWithData(10, &s3.PutObjectInput{
Bucket: &bucket,
Key: getPtr("foo"),
ObjectLockRetainUntilDate: &rDate,
ObjectLockMode: types.ObjectLockMode("invalid_mode"),
}, s3client)
return checkApiErr(err, s3err.GetAPIError(s3err.ErrInvalidObjectLockMode))
}, withLock())
}
func PutObject_conditional_writes(s *S3Conf) error {
testName := "PutObject_conditional_writes"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
obj := "my-obj"
res, err := putObjectWithData(0, &s3.PutObjectInput{
Bucket: &bucket,
Key: &obj,
Body: bytes.NewReader([]byte("dummy")),
}, s3client)
if err != nil {
return err
}
etag := res.res.ETag
etagTrimmed := strings.Trim(*etag, `"`)
incorrectEtag := getPtr("incorrect_etag")
errPrecond := s3err.GetAPIError(s3err.ErrPreconditionFailed)
errNoSuchKey := s3err.GetAPIError(s3err.ErrNoSuchKey)
errNotImplemented := s3err.GetAPIError(s3err.ErrNotImplemented)
for i, test := range []struct {
obj string
ifMatch *string
ifNoneMatch *string
err error
}{
{obj, etag, nil, nil},
{obj, etag, etag, errNotImplemented},
{obj, etag, incorrectEtag, errNotImplemented},
{obj, incorrectEtag, incorrectEtag, errNotImplemented},
{obj, incorrectEtag, etag, errNotImplemented},
{obj, incorrectEtag, nil, errPrecond},
{obj, nil, incorrectEtag, errNotImplemented},
{obj, nil, etag, errNotImplemented},
{obj, nil, getPtr("*"), errPrecond},
{obj, etag, getPtr("*"), errNotImplemented},
{obj, nil, nil, nil},
// precondition headers without quotes
{obj, &etagTrimmed, nil, nil},
{obj, &etagTrimmed, &etagTrimmed, errNotImplemented},
{obj, &etagTrimmed, incorrectEtag, errNotImplemented},
{obj, incorrectEtag, &etagTrimmed, errNotImplemented},
{obj, nil, &etagTrimmed, errNotImplemented},
// object deson't exist tests
{"obj-1", incorrectEtag, etag, errNotImplemented},
{"obj-2", etag, etag, errNotImplemented},
{"obj-3", etag, nil, errNoSuchKey},
{"obj-4", etag, incorrectEtag, errNotImplemented},
{"obj-5", incorrectEtag, nil, errNoSuchKey},
{"obj-6", nil, etag, errNotImplemented},
{"obj-7", nil, getPtr("*"), nil},
{"obj-8", etag, getPtr("*"), errNotImplemented},
} {
res, err := putObjectWithData(0, &s3.PutObjectInput{
Bucket: &bucket,
Key: &test.obj,
Body: bytes.NewReader([]byte("dummy")),
IfMatch: test.ifMatch,
IfNoneMatch: test.ifNoneMatch,
}, s3client)
if err == nil {
// azure blob storage generates different ETags for
// the exact same data.
// to avoid ETag collision reassign the etag value
*etag = *res.res.ETag
etagTrimmed = strings.Trim(*res.res.ETag, `"`)
}
if test.err == nil && err != nil {
return fmt.Errorf("test case %v: expected no error, instead got %w", i, err)
}
if test.err != nil {
apierr, ok := test.err.(s3err.APIError)
if !ok {
return fmt.Errorf("test case %v: invalid error type: %w", i, test.err)
}
if err := checkApiErr(err, apierr); err != nil {
return fmt.Errorf("test case %v: %w", i, err)
}
}
}
return nil
})
}
func PutObject_with_metadata(s *S3Conf) error {
testName := "PutObject_with_metadata"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
meta := map[string]string{
"Key": "Val",
"X-Test": "Example",
"UPPERCASE": "should-remain",
"MiXeD-CaSe": "normalize-to-lower",
"with-number-123": "numeric-test",
"123numeric-prefix": "value123",
"key_with_underscore": "underscore-ok",
"key-with-dash": "dash-ok",
"key.with.dot": "dot-ok",
"KeyURL": "https://example.com/test?query=1",
"EmptyValue": "",
"LongKeyNameThatShouldStillBeValidButQuiteLongToTestLimits": "some long metadata value to ensure nothing breaks at higher header sizes",
"WhitespaceKey ": " trailing-key",
}
obj := "my-object"
_, err := putObjectWithData(3, &s3.PutObjectInput{
Bucket: &bucket,
Key: &obj,
Metadata: meta,
}, s3client)
if err != nil {
return err
}
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
res, err := s3client.HeadObject(ctx, &s3.HeadObjectInput{
Bucket: &bucket,
Key: &obj,
})
cancel()
if err != nil {
return err
}
expectedMeta := map[string]string{
"key": "Val",
"x-test": "Example",
"uppercase": "should-remain",
"mixed-case": "normalize-to-lower",
"with-number-123": "numeric-test",
"123numeric-prefix": "value123",
"key_with_underscore": "underscore-ok",
"key-with-dash": "dash-ok",
"key.with.dot": "dot-ok",
"keyurl": "https://example.com/test?query=1",
"emptyvalue": "",
"longkeynamethatshouldstillbevalidbutquitelongtotestlimits": "some long metadata value to ensure nothing breaks at higher header sizes",
"whitespacekey": "trailing-key",
}
if !areMapsSame(expectedMeta, res.Metadata) {
return fmt.Errorf("expected the object metadata to be %v, instead got %v", expectedMeta, res.Metadata)
}
return nil
})
}
func PutObject_checksum_algorithm_and_header_mismatch(s *S3Conf) error {
testName := "PutObject_checksum_algorithm_and_header_mismatch"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
obj := "my-obj"
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
_, err := s3client.PutObject(ctx, &s3.PutObjectInput{
Bucket: &bucket,
Key: &obj,
ChecksumAlgorithm: types.ChecksumAlgorithmCrc32,
ChecksumCRC32C: getPtr("m0cB1Q=="),
})
cancel()
// FIXME: The error message for PutObject is not properly serialized by the sdk
// References to aws sdk issue https://github.com/aws/aws-sdk-go-v2/issues/2921
// if err := checkApiErr(err, s3err.GetInvalidChecksumHeaderErr("x-amz-sdk-checksum-algorithm"); err != nil {
// return err
// }
if err := checkSdkApiErr(err, "InvalidRequest"); err != nil {
return err
}
return nil
})
}
func PutObject_multiple_checksum_headers(s *S3Conf) error {
testName := "PutObject_multiple_checksum_headers"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
obj := "my-obj"
_, err := putObjectWithData(10, &s3.PutObjectInput{
Bucket: &bucket,
Key: &obj,
ChecksumSHA1: getPtr("Kq5sNclPz7QV2+lfQIuc6R7oRu0="),
ChecksumCRC32C: getPtr("m0cB1Q=="),
}, s3client)
// FIXME: The error message for PutObject is not properly serialized by the sdk
// References to aws sdk issue https://github.com/aws/aws-sdk-go-v2/issues/2921
// if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrMultipleChecksumHeaders)); err != nil {
// return err
// }
if err := checkSdkApiErr(err, "InvalidRequest"); err != nil {
return err
}
// Empty checksums case
_, err = putObjectWithData(10, &s3.PutObjectInput{
Bucket: &bucket,
Key: &obj,
ChecksumSHA1: getPtr(""),
ChecksumCRC32C: getPtr(""),
}, s3client)
// FIXME: The error message for PutObject is not properly serialized by the sdk
// References to aws sdk issue https://github.com/aws/aws-sdk-go-v2/issues/2921
// if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrMultipleChecksumHeaders)); err != nil {
// return err
// }
if err := checkSdkApiErr(err, "InvalidRequest"); err != nil {
return err
}
return nil
})
}
func PutObject_invalid_checksum_header(s *S3Conf) error {
testName := "PutObject_invalid_checksum_header"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
obj := "my-obj"
for i, el := range []struct {
algo string
crc32 *string
crc32c *string
sha1 *string
sha256 *string
crc64nvme *string
}{
// CRC32 tests
{
algo: "crc32",
crc32: getPtr(""),
},
{
algo: "crc32",
crc32: getPtr("invalid_base64!"), // invalid base64
},
{
algo: "crc32",
crc32: getPtr("YXNrZGpoZ2tqYXNo"), // valid base64 but not crc32
},
// CRC32C tests
{
algo: "crc32c",
crc32c: getPtr(""),
},
{
algo: "crc32c",
crc32c: getPtr("invalid_base64!"), // invalid base64
},
{
algo: "crc32c",
crc32c: getPtr("c2RhZnNhZGZzZGFm"), // valid base64 but not crc32c
},
// SHA1 tests
{
algo: "sha1",
sha1: getPtr(""),
},
{
algo: "sha1",
sha1: getPtr("invalid_base64!"), // invalid base64
},
{
algo: "sha1",
sha1: getPtr("c2RhZmRhc2Zkc2Fmc2RhZnNhZGZzYWRm"), // valid base64 but not sha1
},
// SHA256 tests
{
algo: "sha256",
sha256: getPtr(""),
},
{
algo: "sha256",
sha256: getPtr("invalid_base64!"), // invalid base64
},
{
algo: "sha256",
sha256: getPtr("ZGZnbmRmZ2hoZmRoZmdkaA=="), // valid base64 but not sha56
},
// CRC64Nvme tests
{
algo: "crc64nvme",
sha256: getPtr(""),
},
{
algo: "crc64nvme",
sha256: getPtr("invalid_base64!"), // invalid base64
},
{
algo: "crc64nvme",
sha256: getPtr("ZHNhZmRzYWZzZGFmZHNhZg=="), // valid base64 but not crc64nvme
},
} {
_, err := putObjectWithData(int64(i*100), &s3.PutObjectInput{
Bucket: &bucket,
Key: &obj,
ChecksumCRC32: el.crc32,
ChecksumCRC32C: el.crc32c,
ChecksumSHA1: el.sha1,
ChecksumSHA256: el.sha256,
ChecksumCRC64NVME: el.crc64nvme,
}, s3client)
// FIXME: The error message for PutObject is not properly serialized by the sdk
// References to aws sdk issue https://github.com/aws/aws-sdk-go-v2/issues/2921
// if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrMultipleChecksumHeaders)); err != nil {
// return err
// }
if err := checkSdkApiErr(err, "InvalidRequest"); err != nil {
return err
}
}
return nil
})
}
func PutObject_incorrect_checksums(s *S3Conf) error {
testName := "PutObject_incorrect_checksums"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
obj := "my-obj"
for i, el := range []struct {
algo types.ChecksumAlgorithm
crc32 *string
crc32c *string
sha1 *string
sha256 *string
crc64nvme *string
}{
{
algo: types.ChecksumAlgorithmCrc32,
crc32: getPtr("DUoRhQ=="),
},
{
algo: types.ChecksumAlgorithmCrc32c,
crc32c: getPtr("yZRlqg=="),
},
{
algo: types.ChecksumAlgorithmSha1,
sha1: getPtr("Kq5sNclPz7QV2+lfQIuc6R7oRu0="),
},
{
algo: types.ChecksumAlgorithmSha256,
sha256: getPtr("uU0nuZNNPgilLlLX2n2r+sSE7+N6U4DukIj3rOLvzek="),
},
{
algo: types.ChecksumAlgorithmCrc64nvme,
crc64nvme: getPtr("sV264W+gYBI="),
},
} {
_, err := putObjectWithData(int64(i*100), &s3.PutObjectInput{
Bucket: &bucket,
Key: &obj,
ChecksumCRC32: el.crc32,
ChecksumCRC32C: el.crc32c,
ChecksumSHA1: el.sha1,
ChecksumSHA256: el.sha256,
ChecksumCRC64NVME: el.crc64nvme,
}, s3client)
if err := checkApiErr(err, s3err.GetChecksumBadDigestErr(el.algo)); err != nil {
return err
}
}
return nil
})
}
func PutObject_default_checksum(s *S3Conf) error {
testName := "PutObject_default_checksum"
return actionHandler(s, testName, func(_ *s3.Client, bucket string) error {
customClient := s3.NewFromConfig(s.Config(), func(o *s3.Options) {
o.RequestChecksumCalculation = aws.RequestChecksumCalculationUnset
})
obj := "my-obj"
out, err := putObjectWithData(100, &s3.PutObjectInput{
Bucket: &bucket,
Key: &obj,
}, customClient)
if err != nil {
return err
}
if out.res.ChecksumCRC64NVME == nil {
return fmt.Errorf("expected non nil default crc64nvme checksum")
}
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
res, err := customClient.HeadObject(ctx, &s3.HeadObjectInput{
Bucket: &bucket,
Key: &obj,
ChecksumMode: types.ChecksumModeEnabled,
})
cancel()
if err != nil {
return err
}
if getString(res.ChecksumCRC64NVME) != getString(out.res.ChecksumCRC64NVME) {
return fmt.Errorf("expected the object crc64nvme checksum to be %v, instead got %v", getString(res.ChecksumCRC64NVME), getString(out.res.ChecksumCRC64NVME))
}
return nil
})
}
func PutObject_checksums_success(s *S3Conf) error {
testName := "PutObject_checksums_success"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
obj := "my-obj"
for i, algo := range types.ChecksumAlgorithmCrc32.Values() {
res, err := putObjectWithData(int64(i*200), &s3.PutObjectInput{
Bucket: &bucket,
Key: &obj,
ChecksumAlgorithm: algo,
}, s3client)
if err != nil {
return err
}
if res.res.ChecksumType != types.ChecksumTypeFullObject {
return fmt.Errorf("expected the object checksum type to be %v, instead got %v", types.ChecksumTypeFullObject, res.res.ChecksumType)
}
switch algo {
case types.ChecksumAlgorithmCrc32:
if res.res.ChecksumCRC32 == nil {
return fmt.Errorf("expected non empty crc32 checksum in the response")
}
case types.ChecksumAlgorithmCrc32c:
if res.res.ChecksumCRC32C == nil {
return fmt.Errorf("expected non empty crc32c checksum in the response")
}
case types.ChecksumAlgorithmSha1:
if res.res.ChecksumSHA1 == nil {
return fmt.Errorf("expected non empty sha1 checksum in the response")
}
case types.ChecksumAlgorithmSha256:
if res.res.ChecksumSHA256 == nil {
return fmt.Errorf("expected non empty sha256 checksum in the response")
}
case types.ChecksumAlgorithmCrc64nvme:
if res.res.ChecksumCRC64NVME == nil {
return fmt.Errorf("expected non empty crc64nvme checksum in the response")
}
}
}
return nil
})
}
func PutObject_racey_success(s *S3Conf) error {
testName := "PutObject_racey_success"
runF(testName)
bucket, obj, lockStatus := getBucketName(), "my-obj", true
client := s.GetClient()
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
_, err := client.CreateBucket(ctx, &s3.CreateBucketInput{
Bucket: &bucket,
ObjectLockEnabledForBucket: &lockStatus,
})
cancel()
if err != nil {
failF("%v: %v", testName, err)
return fmt.Errorf("%v: %w", testName, err)
}
eg := errgroup.Group{}
for range 10 {
eg.Go(func() error {
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
_, err := client.PutObject(ctx, &s3.PutObjectInput{
Bucket: &bucket,
Key: &obj,
})
cancel()
return err
})
}
err = eg.Wait()
if err != nil {
failF("%v: %v", testName, err)
return fmt.Errorf("%v: %w", testName, err)
}
err = teardown(s, bucket)
if err != nil {
failF("%v: %v", testName, err)
return fmt.Errorf("%v: %w", testName, err)
}
passF(testName)
return nil
}
func PutObject_success(s *S3Conf) error {
testName := "PutObject_success"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
lgth := int64(100)
res, err := putObjectWithData(lgth, &s3.PutObjectInput{
Bucket: &bucket,
Key: getPtr("my-obj"),
}, s3client)
if err != nil {
return err
}
// skip the ETag check for azure tests
if !s.azureTests {
etag, err := calculateEtag(res.data)
if err != nil {
return err
}
if getString(res.res.ETag) != etag {
return fmt.Errorf("expected ETag to be %s, intead got %s", getString(res.res.ETag), etag)
}
}
if res.res.Size == nil {
return fmt.Errorf("unexpected nil object Size")
}
if *res.res.Size != lgth {
return fmt.Errorf("expected the object size to be %v, instead got %v", lgth, *res.res.Size)
}
return nil
})
}
func PutObject_invalid_credentials(s *S3Conf) error {
testName := "PutObject_invalid_credentials"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
newconf := *s
newconf.awsSecret = newconf.awsSecret + "badpassword"
client := newconf.GetClient()
_, err := putObjects(client, []string{"my-obj"}, bucket)
return checkApiErr(err, s3err.GetAPIError(s3err.ErrSignatureDoesNotMatch))
})
}
func PutObject_invalid_object_names(s *S3Conf) error {
testName := "PutObject_invalid_object_names"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
for _, obj := range []string{
".",
"..",
"./",
"/.",
"//",
"../",
"/..",
"/..",
"../.",
"../../../.",
"../../../etc/passwd",
"../../../../tmp/foo",
"for/../../bar/",
"a/a/a/../../../../../etc/passwd",
"/a/../../b/../../c/../../../etc/passwd",
} {
_, err := putObjects(s3client, []string{obj}, bucket)
if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrBadRequest)); err != nil {
return err
}
}
return nil
})
}
func PutObject_false_negative_object_names(s *S3Conf) error {
testName := "PutObject_false_negative_object_names"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
objs := []string{
"%252e%252e%252fetc/passwd", // double encoding
"%2e%2e/%2e%2e/%2e%2e/.ssh/id_rsa", // double URL-encoded
"%u002e%u002e/%u002e%u002e/etc/passwd", // unicode escape
"..%2f..%2f..%2fsecret/file.txt", // URL-encoded
"..%c0%af..%c0%afetc/passwd", // UTF-8 overlong trick
".../.../.../target.txt",
"..\\u2215..\\u2215etc/passwd", // Unicode division slash
"dir/%20../file.txt", // encoded space
"dir/%c0%ae%c0%ae/%c0%ae%c0%ae/etc/passwd", // overlong UTF-8 encoding
"logs/latest -> /etc/passwd", // symlink attacks
//TODO: add this test case in advanced routing
// "/etc/passwd" // absolute path injection
}
_, err := putObjects(s3client, objs, bucket)
if err != nil {
return err
}
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
res, err := s3client.ListObjects(ctx, &s3.ListObjectsInput{
Bucket: &bucket,
})
cancel()
if err != nil {
return err
}
if len(res.Contents) != len(objs) {
return fmt.Errorf("expected %v objects, instead got %v", len(objs), len(res.Contents))
}
for i, obj := range res.Contents {
if *obj.Key != objs[i] {
return fmt.Errorf("expected the %vth object name to be %s, instead got %s", i+1, objs[i], *obj.Key)
}
}
return nil
})
}