diff --git a/auth/bucket_policy.go b/auth/bucket_policy.go
index 116f62a2..e703853d 100644
--- a/auth/bucket_policy.go
+++ b/auth/bucket_policy.go
@@ -131,7 +131,6 @@ func verifyBucketPolicy(policy []byte, access, bucket, object string, action Act
resource += "/" + object
}
- fmt.Println(access, action, resource)
if !bucketPolicy.isAllowed(access, action, resource) {
return s3err.GetAPIError(s3err.ErrAccessDenied)
}
diff --git a/auth/bucket_policy_actions.go b/auth/bucket_policy_actions.go
index 028a7b9c..ea7988df 100644
--- a/auth/bucket_policy_actions.go
+++ b/auth/bucket_policy_actions.go
@@ -23,79 +23,97 @@ import (
type Action string
const (
- GetBucketAclAction Action = "s3:GetBucketAcl"
- CreateBucketAction Action = "s3:CreateBucket"
- PutBucketAclAction Action = "s3:PutBucketAcl"
- DeleteBucketAction Action = "s3:DeleteBucket"
- PutBucketVersioningAction Action = "s3:PutBucketVersioning"
- GetBucketVersioningAction Action = "s3:GetBucketVersioning"
- PutBucketPolicyAction Action = "s3:PutBucketPolicy"
- GetBucketPolicyAction Action = "s3:GetBucketPolicy"
- DeleteBucketPolicyAction Action = "s3:DeleteBucketPolicy"
- AbortMultipartUploadAction Action = "s3:AbortMultipartUpload"
- ListMultipartUploadPartsAction Action = "s3:ListMultipartUploadParts"
- ListBucketMultipartUploadsAction Action = "s3:ListBucketMultipartUploads"
- PutObjectAction Action = "s3:PutObject"
- GetObjectAction Action = "s3:GetObject"
- DeleteObjectAction Action = "s3:DeleteObject"
- GetObjectAclAction Action = "s3:GetObjectAcl"
- GetObjectAttributesAction Action = "s3:GetObjectAttributes"
- PutObjectAclAction Action = "s3:PutObjectAcl"
- RestoreObjectAction Action = "s3:RestoreObject"
- GetBucketTaggingAction Action = "s3:GetBucketTagging"
- PutBucketTaggingAction Action = "s3:PutBucketTagging"
- GetObjectTaggingAction Action = "s3:GetObjectTagging"
- PutObjectTaggingAction Action = "s3:PutObjectTagging"
- DeleteObjectTaggingAction Action = "s3:DeleteObjectTagging"
- ListBucketVersionsAction Action = "s3:ListBucketVersions"
- ListBucketAction Action = "s3:ListBucket"
- AllActions Action = "s3:*"
+ GetBucketAclAction Action = "s3:GetBucketAcl"
+ CreateBucketAction Action = "s3:CreateBucket"
+ PutBucketAclAction Action = "s3:PutBucketAcl"
+ DeleteBucketAction Action = "s3:DeleteBucket"
+ PutBucketVersioningAction Action = "s3:PutBucketVersioning"
+ GetBucketVersioningAction Action = "s3:GetBucketVersioning"
+ PutBucketPolicyAction Action = "s3:PutBucketPolicy"
+ GetBucketPolicyAction Action = "s3:GetBucketPolicy"
+ DeleteBucketPolicyAction Action = "s3:DeleteBucketPolicy"
+ AbortMultipartUploadAction Action = "s3:AbortMultipartUpload"
+ ListMultipartUploadPartsAction Action = "s3:ListMultipartUploadParts"
+ ListBucketMultipartUploadsAction Action = "s3:ListBucketMultipartUploads"
+ PutObjectAction Action = "s3:PutObject"
+ GetObjectAction Action = "s3:GetObject"
+ DeleteObjectAction Action = "s3:DeleteObject"
+ GetObjectAclAction Action = "s3:GetObjectAcl"
+ GetObjectAttributesAction Action = "s3:GetObjectAttributes"
+ PutObjectAclAction Action = "s3:PutObjectAcl"
+ RestoreObjectAction Action = "s3:RestoreObject"
+ GetBucketTaggingAction Action = "s3:GetBucketTagging"
+ PutBucketTaggingAction Action = "s3:PutBucketTagging"
+ GetObjectTaggingAction Action = "s3:GetObjectTagging"
+ PutObjectTaggingAction Action = "s3:PutObjectTagging"
+ DeleteObjectTaggingAction Action = "s3:DeleteObjectTagging"
+ ListBucketVersionsAction Action = "s3:ListBucketVersions"
+ ListBucketAction Action = "s3:ListBucket"
+ GetBucketObjectLockConfigurationAction Action = "s3:GetBucketObjectLockConfiguration"
+ PutBucketObjectLockConfigurationAction Action = "s3:PutBucketObjectLockConfiguration"
+ GetObjectLegalHoldAction Action = "s3:GetObjectLegalHold"
+ PutObjectLegalHoldAction Action = "s3:PutObjectLegalHold"
+ GetObjectRetentionAction Action = "s3:GetObjectRetention"
+ PutObjectRetentionAction Action = "s3:PutObjectRetention"
+ BypassGovernanceRetentionAction Action = "s3:BypassGovernanceRetention"
+ AllActions Action = "s3:*"
)
var supportedActionList = map[Action]struct{}{
- GetBucketAclAction: {},
- CreateBucketAction: {},
- PutBucketAclAction: {},
- DeleteBucketAction: {},
- PutBucketVersioningAction: {},
- GetBucketVersioningAction: {},
- PutBucketPolicyAction: {},
- GetBucketPolicyAction: {},
- DeleteBucketPolicyAction: {},
- AbortMultipartUploadAction: {},
- ListMultipartUploadPartsAction: {},
- ListBucketMultipartUploadsAction: {},
- PutObjectAction: {},
- GetObjectAction: {},
- DeleteObjectAction: {},
- GetObjectAclAction: {},
- GetObjectAttributesAction: {},
- PutObjectAclAction: {},
- RestoreObjectAction: {},
- GetBucketTaggingAction: {},
- PutBucketTaggingAction: {},
- GetObjectTaggingAction: {},
- PutObjectTaggingAction: {},
- DeleteObjectTaggingAction: {},
- ListBucketVersionsAction: {},
- ListBucketAction: {},
- AllActions: {},
+ GetBucketAclAction: {},
+ CreateBucketAction: {},
+ PutBucketAclAction: {},
+ DeleteBucketAction: {},
+ PutBucketVersioningAction: {},
+ GetBucketVersioningAction: {},
+ PutBucketPolicyAction: {},
+ GetBucketPolicyAction: {},
+ DeleteBucketPolicyAction: {},
+ AbortMultipartUploadAction: {},
+ ListMultipartUploadPartsAction: {},
+ ListBucketMultipartUploadsAction: {},
+ PutObjectAction: {},
+ GetObjectAction: {},
+ DeleteObjectAction: {},
+ GetObjectAclAction: {},
+ GetObjectAttributesAction: {},
+ PutObjectAclAction: {},
+ RestoreObjectAction: {},
+ GetBucketTaggingAction: {},
+ PutBucketTaggingAction: {},
+ GetObjectTaggingAction: {},
+ PutObjectTaggingAction: {},
+ DeleteObjectTaggingAction: {},
+ ListBucketVersionsAction: {},
+ ListBucketAction: {},
+ PutBucketObjectLockConfigurationAction: {},
+ GetObjectLegalHoldAction: {},
+ PutObjectLegalHoldAction: {},
+ GetObjectRetentionAction: {},
+ PutObjectRetentionAction: {},
+ BypassGovernanceRetentionAction: {},
+ AllActions: {},
}
var supportedObjectActionList = map[Action]struct{}{
- AbortMultipartUploadAction: {},
- ListMultipartUploadPartsAction: {},
- PutObjectAction: {},
- GetObjectAction: {},
- DeleteObjectAction: {},
- GetObjectAclAction: {},
- GetObjectAttributesAction: {},
- PutObjectAclAction: {},
- RestoreObjectAction: {},
- GetObjectTaggingAction: {},
- PutObjectTaggingAction: {},
- DeleteObjectTaggingAction: {},
- AllActions: {},
+ AbortMultipartUploadAction: {},
+ ListMultipartUploadPartsAction: {},
+ PutObjectAction: {},
+ GetObjectAction: {},
+ DeleteObjectAction: {},
+ GetObjectAclAction: {},
+ GetObjectAttributesAction: {},
+ PutObjectAclAction: {},
+ RestoreObjectAction: {},
+ GetObjectTaggingAction: {},
+ PutObjectTaggingAction: {},
+ DeleteObjectTaggingAction: {},
+ GetObjectLegalHoldAction: {},
+ PutObjectLegalHoldAction: {},
+ GetObjectRetentionAction: {},
+ PutObjectRetentionAction: {},
+ BypassGovernanceRetentionAction: {},
+ AllActions: {},
}
// Validates Action: it should either wildcard match with supported actions list or be in it
diff --git a/auth/object_lock.go b/auth/object_lock.go
new file mode 100644
index 00000000..6a5fbcbe
--- /dev/null
+++ b/auth/object_lock.go
@@ -0,0 +1,235 @@
+// 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 auth
+
+import (
+ "context"
+ "encoding/json"
+ "encoding/xml"
+ "errors"
+ "fmt"
+ "time"
+
+ "github.com/aws/aws-sdk-go-v2/service/s3/types"
+ "github.com/versity/versitygw/backend"
+ "github.com/versity/versitygw/s3err"
+)
+
+type BucketLockConfig struct {
+ Enabled bool
+ DefaultRetention *types.DefaultRetention
+ CreatedAt *time.Time
+}
+
+type ObjectLockConfig struct {
+ LegalHoldEnabled bool
+ Retention *types.ObjectLockRetention
+}
+
+func ParseBucketLockConfigurationInput(input []byte) ([]byte, error) {
+ var lockConfig types.ObjectLockConfiguration
+ if err := xml.Unmarshal(input, &lockConfig); err != nil {
+ return nil, s3err.GetAPIError(s3err.ErrInvalidRequest)
+ }
+
+ config := BucketLockConfig{
+ Enabled: lockConfig.ObjectLockEnabled == types.ObjectLockEnabledEnabled,
+ }
+
+ if lockConfig.Rule != nil && lockConfig.Rule.DefaultRetention != nil {
+ retention := lockConfig.Rule.DefaultRetention
+ if retention.Years != nil && retention.Days != nil {
+ return nil, s3err.GetAPIError(s3err.ErrInvalidRequest)
+ }
+
+ config.DefaultRetention = retention
+ now := time.Now()
+ config.CreatedAt = &now
+ }
+
+ return json.Marshal(config)
+}
+
+func ParseBucketLockConfigurationOutput(input []byte) (*types.ObjectLockConfiguration, error) {
+ var config BucketLockConfig
+ if err := json.Unmarshal(input, &config); err != nil {
+ return nil, fmt.Errorf("parse object lock config: %w", err)
+ }
+
+ result := &types.ObjectLockConfiguration{
+ Rule: &types.ObjectLockRule{
+ DefaultRetention: config.DefaultRetention,
+ },
+ }
+
+ if config.Enabled {
+ result.ObjectLockEnabled = types.ObjectLockEnabledEnabled
+ }
+
+ return result, nil
+}
+
+func ParseObjectLockRetentionInput(input []byte) ([]byte, error) {
+ var retention types.ObjectLockRetention
+ if err := xml.Unmarshal(input, &retention); err != nil {
+ return nil, s3err.GetAPIError(s3err.ErrInvalidRequest)
+ }
+
+ if retention.RetainUntilDate == nil || retention.RetainUntilDate.Before(time.Now()) {
+ return nil, s3err.GetAPIError(s3err.ErrPastObjectLockRetainDate)
+ }
+ switch retention.Mode {
+ case types.ObjectLockRetentionModeCompliance:
+ case types.ObjectLockRetentionModeGovernance:
+ default:
+ return nil, s3err.GetAPIError(s3err.ErrInvalidRequest)
+ }
+
+ return json.Marshal(retention)
+}
+
+func ParseObjectLockRetentionOutput(input []byte) (*types.ObjectLockRetention, error) {
+ var retention types.ObjectLockRetention
+ if err := json.Unmarshal(input, &retention); err != nil {
+ return nil, fmt.Errorf("parse object lock retention: %w", err)
+ }
+
+ return &retention, nil
+}
+
+func ParseObjectLegalHoldOutput(status *bool) *types.ObjectLockLegalHold {
+ if status == nil {
+ return nil
+ }
+
+ if *status {
+ return &types.ObjectLockLegalHold{
+ Status: types.ObjectLockLegalHoldStatusOn,
+ }
+ }
+
+ return &types.ObjectLockLegalHold{
+ Status: types.ObjectLockLegalHoldStatusOff,
+ }
+}
+
+func CheckObjectAccess(ctx context.Context, bucket, userAccess string, objects []string, isAdminOrRoot bool, be backend.Backend) error {
+ data, err := be.GetObjectLockConfiguration(ctx, bucket)
+ if err != nil {
+ if errors.Is(err, s3err.GetAPIError(s3err.ErrObjectLockConfigurationNotFound)) {
+ return nil
+ }
+
+ return err
+ }
+
+ var bucketLockConfig BucketLockConfig
+ if err := json.Unmarshal(data, &bucketLockConfig); err != nil {
+ return fmt.Errorf("parse object lock config: %w", err)
+ }
+
+ if !bucketLockConfig.Enabled {
+ return nil
+ }
+
+ objExists := true
+
+ for _, obj := range objects {
+ var checkRetention bool = true
+ retentionData, err := be.GetObjectRetention(ctx, bucket, obj, "")
+ if errors.Is(err, s3err.GetAPIError(s3err.ErrNoSuchKey)) {
+ objExists = false
+ continue
+ }
+ if errors.Is(err, s3err.GetAPIError(s3err.ErrNoSuchObjectLockConfiguration)) {
+ checkRetention = false
+ }
+ if err != nil && checkRetention {
+ return err
+ }
+
+ if checkRetention {
+ retention, err := ParseObjectLockRetentionOutput(retentionData)
+ if err != nil {
+ return err
+ }
+
+ if retention.Mode != "" && retention.RetainUntilDate != nil {
+ if retention.RetainUntilDate.After(time.Now()) {
+ switch retention.Mode {
+ case types.ObjectLockRetentionModeGovernance:
+ if !isAdminOrRoot {
+ policy, err := be.GetBucketPolicy(ctx, bucket)
+ if err != nil {
+ return err
+ }
+ if len(policy) == 0 {
+ return s3err.GetAPIError(s3err.ErrObjectLocked)
+ }
+ err = verifyBucketPolicy(policy, userAccess, bucket, obj, BypassGovernanceRetentionAction)
+ if err != nil {
+ return s3err.GetAPIError(s3err.ErrObjectLocked)
+ }
+ }
+ case types.ObjectLockRetentionModeCompliance:
+ return s3err.GetAPIError(s3err.ErrObjectLocked)
+ }
+ }
+ }
+ }
+
+ status, err := be.GetObjectLegalHold(ctx, bucket, obj, "")
+ if errors.Is(err, s3err.GetAPIError(s3err.ErrNoSuchObjectLockConfiguration)) {
+ continue
+ }
+ if err != nil {
+ return err
+ }
+
+ if *status && !isAdminOrRoot {
+ return s3err.GetAPIError(s3err.ErrObjectLocked)
+ }
+ }
+
+ if bucketLockConfig.DefaultRetention != nil && bucketLockConfig.CreatedAt != nil && objExists {
+ expirationDate := *bucketLockConfig.CreatedAt
+ if bucketLockConfig.DefaultRetention.Days != nil {
+ expirationDate = expirationDate.AddDate(0, 0, int(*bucketLockConfig.DefaultRetention.Days))
+ }
+ if bucketLockConfig.DefaultRetention.Years != nil {
+ expirationDate = expirationDate.AddDate(int(*bucketLockConfig.DefaultRetention.Years), 0, 0)
+ }
+
+ if expirationDate.After(time.Now()) {
+ switch bucketLockConfig.DefaultRetention.Mode {
+ case types.ObjectLockRetentionModeGovernance:
+ if !isAdminOrRoot {
+ policy, err := be.GetBucketPolicy(ctx, bucket)
+ if err != nil {
+ return err
+ }
+ err = verifyBucketPolicy(policy, userAccess, bucket, "", BypassGovernanceRetentionAction)
+ if err != nil {
+ return s3err.GetAPIError(s3err.ErrObjectLocked)
+ }
+ }
+ case types.ObjectLockRetentionModeCompliance:
+ return s3err.GetAPIError(s3err.ErrObjectLocked)
+ }
+ }
+ }
+
+ return nil
+}
diff --git a/backend/backend.go b/backend/backend.go
index b56e654a..fa060d58 100644
--- a/backend/backend.go
+++ b/backend/backend.go
@@ -81,6 +81,14 @@ type Backend interface {
PutObjectTagging(_ context.Context, bucket, object string, tags map[string]string) error
DeleteObjectTagging(_ context.Context, bucket, object string) error
+ // object lock operations
+ PutObjectLockConfiguration(_ context.Context, bucket string, config []byte) error
+ GetObjectLockConfiguration(_ context.Context, bucket string) ([]byte, error)
+ PutObjectRetention(_ context.Context, bucket, object, versionId string, retention []byte) error
+ GetObjectRetention(_ context.Context, bucket, object, versionId string) ([]byte, error)
+ PutObjectLegalHold(_ context.Context, bucket, object, versionId string, status bool) error
+ GetObjectLegalHold(_ context.Context, bucket, object, versionId string) (*bool, error)
+
// non AWS actions
ChangeBucketOwner(_ context.Context, bucket, newOwner string) error
ListBucketsAndOwners(context.Context) ([]s3response.Bucket, error)
@@ -229,6 +237,25 @@ func (BackendUnsupported) DeleteObjectTagging(_ context.Context, bucket, object
return s3err.GetAPIError(s3err.ErrNotImplemented)
}
+func (BackendUnsupported) PutObjectLockConfiguration(_ context.Context, bucket string, config []byte) error {
+ return s3err.GetAPIError(s3err.ErrNotImplemented)
+}
+func (BackendUnsupported) GetObjectLockConfiguration(_ context.Context, bucket string) ([]byte, error) {
+ return nil, s3err.GetAPIError(s3err.ErrNotImplemented)
+}
+func (BackendUnsupported) PutObjectRetention(_ context.Context, bucket, object, versionId string, retention []byte) error {
+ return s3err.GetAPIError(s3err.ErrNotImplemented)
+}
+func (BackendUnsupported) GetObjectRetention(_ context.Context, bucket, object, versionId string) ([]byte, error) {
+ return nil, s3err.GetAPIError(s3err.ErrNotImplemented)
+}
+func (BackendUnsupported) PutObjectLegalHold(_ context.Context, bucket, object, versionId string, status bool) error {
+ return s3err.GetAPIError(s3err.ErrNotImplemented)
+}
+func (BackendUnsupported) GetObjectLegalHold(_ context.Context, bucket, object, versionId string) (*bool, error) {
+ return nil, s3err.GetAPIError(s3err.ErrNotImplemented)
+}
+
func (BackendUnsupported) ChangeBucketOwner(_ context.Context, bucket, newOwner string) error {
return s3err.GetAPIError(s3err.ErrNotImplemented)
}
diff --git a/backend/posix/posix.go b/backend/posix/posix.go
index 0cb69ad8..fd17035c 100644
--- a/backend/posix/posix.go
+++ b/backend/posix/posix.go
@@ -75,6 +75,9 @@ const (
aclkey = "acl"
etagkey = "etag"
policykey = "policy"
+ bucketLockKey = "bucket-lock"
+ objectRetentionKey = "object-retention"
+ objectLegalHoldKey = "object-legal-hold"
)
type PosixOpts struct {
@@ -2011,6 +2014,171 @@ func (p *Posix) DeleteBucketPolicy(ctx context.Context, bucket string) error {
return p.PutBucketPolicy(ctx, bucket, nil)
}
+func (p *Posix) PutObjectLockConfiguration(_ context.Context, bucket string, config []byte) error {
+ _, err := os.Stat(bucket)
+ if errors.Is(err, fs.ErrNotExist) {
+ return s3err.GetAPIError(s3err.ErrNoSuchBucket)
+ }
+ if err != nil {
+ return fmt.Errorf("stat bucket: %w", err)
+ }
+
+ if err := p.meta.StoreAttribute(bucket, "", bucketLockKey, config); err != nil {
+ return fmt.Errorf("set object lock config: %w", err)
+ }
+
+ return nil
+}
+
+func (p *Posix) GetObjectLockConfiguration(_ context.Context, bucket string) ([]byte, error) {
+ _, err := os.Stat(bucket)
+ if errors.Is(err, fs.ErrNotExist) {
+ return nil, s3err.GetAPIError(s3err.ErrNoSuchBucket)
+ }
+ if err != nil {
+ return nil, fmt.Errorf("stat bucket: %w", err)
+ }
+
+ cfg, err := p.meta.RetrieveAttribute(bucket, "", bucketLockKey)
+ if errors.Is(err, meta.ErrNoSuchKey) {
+ return nil, s3err.GetAPIError(s3err.ErrObjectLockConfigurationNotFound)
+ }
+ if err != nil {
+ return nil, fmt.Errorf("get object lock config: %w", err)
+ }
+
+ return cfg, nil
+}
+
+func (p *Posix) PutObjectLegalHold(_ context.Context, bucket, object, versionId string, status bool) error {
+ _, err := os.Stat(bucket)
+ if errors.Is(err, fs.ErrNotExist) {
+ return s3err.GetAPIError(s3err.ErrNoSuchBucket)
+ }
+ if err != nil {
+ return fmt.Errorf("stat bucket: %w", err)
+ }
+
+ cfg, err := p.meta.RetrieveAttribute(bucket, "", bucketLockKey)
+ if errors.Is(err, meta.ErrNoSuchKey) {
+ return s3err.GetAPIError(s3err.ErrInvalidBucketObjectLockConfiguration)
+ }
+ if err != nil {
+ return fmt.Errorf("get object lock config: %w", err)
+ }
+
+ var bucketLockConfig auth.BucketLockConfig
+ if err := json.Unmarshal(cfg, &bucketLockConfig); err != nil {
+ return fmt.Errorf("parse bucket lock config: %w", err)
+ }
+
+ if !bucketLockConfig.Enabled {
+ return s3err.GetAPIError(s3err.ErrInvalidBucketObjectLockConfiguration)
+ }
+
+ var statusData []byte
+ if status {
+ statusData = []byte{1}
+ } else {
+ statusData = []byte{0}
+ }
+
+ err = p.meta.StoreAttribute(bucket, object, objectLegalHoldKey, statusData)
+ if errors.Is(err, fs.ErrNotExist) {
+ return s3err.GetAPIError(s3err.ErrNoSuchKey)
+ }
+ if err != nil {
+ return fmt.Errorf("set object lock config: %w", err)
+ }
+
+ return nil
+}
+
+func (p *Posix) GetObjectLegalHold(_ context.Context, bucket, object, versionId string) (*bool, error) {
+ _, err := os.Stat(bucket)
+ if errors.Is(err, fs.ErrNotExist) {
+ return nil, s3err.GetAPIError(s3err.ErrNoSuchBucket)
+ }
+ if err != nil {
+ return nil, fmt.Errorf("stat bucket: %w", err)
+ }
+
+ data, err := p.meta.RetrieveAttribute(bucket, object, objectLegalHoldKey)
+ if errors.Is(err, fs.ErrNotExist) {
+ return nil, s3err.GetAPIError(s3err.ErrNoSuchKey)
+ }
+ if errors.Is(err, meta.ErrNoSuchKey) {
+ return nil, s3err.GetAPIError(s3err.ErrNoSuchObjectLockConfiguration)
+ }
+ if err != nil {
+ return nil, fmt.Errorf("get object lock config: %w", err)
+ }
+
+ result := data[0] == 1
+
+ return &result, nil
+}
+
+func (p *Posix) PutObjectRetention(_ context.Context, bucket, object, versionId string, retention []byte) error {
+ _, err := os.Stat(bucket)
+ if errors.Is(err, fs.ErrNotExist) {
+ return s3err.GetAPIError(s3err.ErrNoSuchBucket)
+ }
+ if err != nil {
+ return fmt.Errorf("stat bucket: %w", err)
+ }
+
+ cfg, err := p.meta.RetrieveAttribute(bucket, "", bucketLockKey)
+ if errors.Is(err, meta.ErrNoSuchKey) {
+ return s3err.GetAPIError(s3err.ErrInvalidBucketObjectLockConfiguration)
+ }
+ if err != nil {
+ return fmt.Errorf("get object lock config: %w", err)
+ }
+
+ var bucketLockConfig auth.BucketLockConfig
+ if err := json.Unmarshal(cfg, &bucketLockConfig); err != nil {
+ return fmt.Errorf("parse bucket lock config: %w", err)
+ }
+
+ if !bucketLockConfig.Enabled {
+ return s3err.GetAPIError(s3err.ErrInvalidBucketObjectLockConfiguration)
+ }
+
+ err = p.meta.StoreAttribute(bucket, object, objectRetentionKey, retention)
+ if errors.Is(err, fs.ErrNotExist) {
+ return s3err.GetAPIError(s3err.ErrNoSuchKey)
+ }
+ if err != nil {
+ return fmt.Errorf("set object lock config: %w", err)
+ }
+
+ return nil
+}
+
+func (p *Posix) GetObjectRetention(_ context.Context, bucket, object, versionId string) ([]byte, error) {
+ _, err := os.Stat(bucket)
+ if errors.Is(err, fs.ErrNotExist) {
+ return nil, s3err.GetAPIError(s3err.ErrNoSuchBucket)
+ }
+ if err != nil {
+ return nil, fmt.Errorf("stat bucket: %w", err)
+ }
+
+ data, err := p.meta.RetrieveAttribute(bucket, object, objectRetentionKey)
+ if errors.Is(err, fs.ErrNotExist) {
+ return nil, s3err.GetAPIError(s3err.ErrNoSuchKey)
+ }
+ if errors.Is(err, meta.ErrNoSuchKey) {
+ return nil, s3err.GetAPIError(s3err.ErrNoSuchObjectLockConfiguration)
+ }
+ if err != nil {
+ return nil, fmt.Errorf("get object lock config: %w", err)
+ }
+
+ return data, nil
+}
+
func (p *Posix) ChangeBucketOwner(ctx context.Context, bucket, newOwner string) error {
_, err := os.Stat(bucket)
if errors.Is(err, fs.ErrNotExist) {
diff --git a/s3api/controllers/backend_moq_test.go b/s3api/controllers/backend_moq_test.go
index d8b97cb0..b943fede 100644
--- a/s3api/controllers/backend_moq_test.go
+++ b/s3api/controllers/backend_moq_test.go
@@ -80,6 +80,15 @@ var _ backend.Backend = &BackendMock{}
// GetObjectAttributesFunc: func(contextMoqParam context.Context, getObjectAttributesInput *s3.GetObjectAttributesInput) (*s3.GetObjectAttributesOutput, error) {
// panic("mock out the GetObjectAttributes method")
// },
+// GetObjectLegalHoldFunc: func(contextMoqParam context.Context, bucket string, object string, versionId string) (*bool, error) {
+// panic("mock out the GetObjectLegalHold method")
+// },
+// GetObjectLockConfigurationFunc: func(contextMoqParam context.Context, bucket string) ([]byte, error) {
+// panic("mock out the GetObjectLockConfiguration method")
+// },
+// GetObjectRetentionFunc: func(contextMoqParam context.Context, bucket string, object string, versionId string) ([]byte, error) {
+// panic("mock out the GetObjectRetention method")
+// },
// GetObjectTaggingFunc: func(contextMoqParam context.Context, bucket string, object string) (map[string]string, error) {
// panic("mock out the GetObjectTagging method")
// },
@@ -128,6 +137,15 @@ var _ backend.Backend = &BackendMock{}
// PutObjectAclFunc: func(contextMoqParam context.Context, putObjectAclInput *s3.PutObjectAclInput) error {
// panic("mock out the PutObjectAcl method")
// },
+// PutObjectLegalHoldFunc: func(contextMoqParam context.Context, bucket string, object string, versionId string, status bool) error {
+// panic("mock out the PutObjectLegalHold method")
+// },
+// PutObjectLockConfigurationFunc: func(contextMoqParam context.Context, bucket string, config []byte) error {
+// panic("mock out the PutObjectLockConfiguration method")
+// },
+// PutObjectRetentionFunc: func(contextMoqParam context.Context, bucket string, object string, versionId string, retention []byte) error {
+// panic("mock out the PutObjectRetention method")
+// },
// PutObjectTaggingFunc: func(contextMoqParam context.Context, bucket string, object string, tags map[string]string) error {
// panic("mock out the PutObjectTagging method")
// },
@@ -213,6 +231,15 @@ type BackendMock struct {
// GetObjectAttributesFunc mocks the GetObjectAttributes method.
GetObjectAttributesFunc func(contextMoqParam context.Context, getObjectAttributesInput *s3.GetObjectAttributesInput) (*s3.GetObjectAttributesOutput, error)
+ // GetObjectLegalHoldFunc mocks the GetObjectLegalHold method.
+ GetObjectLegalHoldFunc func(contextMoqParam context.Context, bucket string, object string, versionId string) (*bool, error)
+
+ // GetObjectLockConfigurationFunc mocks the GetObjectLockConfiguration method.
+ GetObjectLockConfigurationFunc func(contextMoqParam context.Context, bucket string) ([]byte, error)
+
+ // GetObjectRetentionFunc mocks the GetObjectRetention method.
+ GetObjectRetentionFunc func(contextMoqParam context.Context, bucket string, object string, versionId string) ([]byte, error)
+
// GetObjectTaggingFunc mocks the GetObjectTagging method.
GetObjectTaggingFunc func(contextMoqParam context.Context, bucket string, object string) (map[string]string, error)
@@ -261,6 +288,15 @@ type BackendMock struct {
// PutObjectAclFunc mocks the PutObjectAcl method.
PutObjectAclFunc func(contextMoqParam context.Context, putObjectAclInput *s3.PutObjectAclInput) error
+ // PutObjectLegalHoldFunc mocks the PutObjectLegalHold method.
+ PutObjectLegalHoldFunc func(contextMoqParam context.Context, bucket string, object string, versionId string, status bool) error
+
+ // PutObjectLockConfigurationFunc mocks the PutObjectLockConfiguration method.
+ PutObjectLockConfigurationFunc func(contextMoqParam context.Context, bucket string, config []byte) error
+
+ // PutObjectRetentionFunc mocks the PutObjectRetention method.
+ PutObjectRetentionFunc func(contextMoqParam context.Context, bucket string, object string, versionId string, retention []byte) error
+
// PutObjectTaggingFunc mocks the PutObjectTagging method.
PutObjectTaggingFunc func(contextMoqParam context.Context, bucket string, object string, tags map[string]string) error
@@ -425,6 +461,35 @@ type BackendMock struct {
// GetObjectAttributesInput is the getObjectAttributesInput argument value.
GetObjectAttributesInput *s3.GetObjectAttributesInput
}
+ // GetObjectLegalHold holds details about calls to the GetObjectLegalHold method.
+ GetObjectLegalHold []struct {
+ // ContextMoqParam is the contextMoqParam argument value.
+ ContextMoqParam context.Context
+ // Bucket is the bucket argument value.
+ Bucket string
+ // Object is the object argument value.
+ Object string
+ // VersionId is the versionId argument value.
+ VersionId string
+ }
+ // GetObjectLockConfiguration holds details about calls to the GetObjectLockConfiguration method.
+ GetObjectLockConfiguration []struct {
+ // ContextMoqParam is the contextMoqParam argument value.
+ ContextMoqParam context.Context
+ // Bucket is the bucket argument value.
+ Bucket string
+ }
+ // GetObjectRetention holds details about calls to the GetObjectRetention method.
+ GetObjectRetention []struct {
+ // ContextMoqParam is the contextMoqParam argument value.
+ ContextMoqParam context.Context
+ // Bucket is the bucket argument value.
+ Bucket string
+ // Object is the object argument value.
+ Object string
+ // VersionId is the versionId argument value.
+ VersionId string
+ }
// GetObjectTagging holds details about calls to the GetObjectTagging method.
GetObjectTagging []struct {
// ContextMoqParam is the contextMoqParam argument value.
@@ -545,6 +610,41 @@ type BackendMock struct {
// PutObjectAclInput is the putObjectAclInput argument value.
PutObjectAclInput *s3.PutObjectAclInput
}
+ // PutObjectLegalHold holds details about calls to the PutObjectLegalHold method.
+ PutObjectLegalHold []struct {
+ // ContextMoqParam is the contextMoqParam argument value.
+ ContextMoqParam context.Context
+ // Bucket is the bucket argument value.
+ Bucket string
+ // Object is the object argument value.
+ Object string
+ // VersionId is the versionId argument value.
+ VersionId string
+ // Status is the status argument value.
+ Status bool
+ }
+ // PutObjectLockConfiguration holds details about calls to the PutObjectLockConfiguration method.
+ PutObjectLockConfiguration []struct {
+ // ContextMoqParam is the contextMoqParam argument value.
+ ContextMoqParam context.Context
+ // Bucket is the bucket argument value.
+ Bucket string
+ // Config is the config argument value.
+ Config []byte
+ }
+ // PutObjectRetention holds details about calls to the PutObjectRetention method.
+ PutObjectRetention []struct {
+ // ContextMoqParam is the contextMoqParam argument value.
+ ContextMoqParam context.Context
+ // Bucket is the bucket argument value.
+ Bucket string
+ // Object is the object argument value.
+ Object string
+ // VersionId is the versionId argument value.
+ VersionId string
+ // Retention is the retention argument value.
+ Retention []byte
+ }
// PutObjectTagging holds details about calls to the PutObjectTagging method.
PutObjectTagging []struct {
// ContextMoqParam is the contextMoqParam argument value.
@@ -591,48 +691,54 @@ type BackendMock struct {
UploadPartCopyInput *s3.UploadPartCopyInput
}
}
- lockAbortMultipartUpload sync.RWMutex
- lockChangeBucketOwner sync.RWMutex
- lockCompleteMultipartUpload sync.RWMutex
- lockCopyObject sync.RWMutex
- lockCreateBucket sync.RWMutex
- lockCreateMultipartUpload sync.RWMutex
- lockDeleteBucket sync.RWMutex
- lockDeleteBucketPolicy sync.RWMutex
- lockDeleteBucketTagging sync.RWMutex
- lockDeleteObject sync.RWMutex
- lockDeleteObjectTagging sync.RWMutex
- lockDeleteObjects sync.RWMutex
- lockGetBucketAcl sync.RWMutex
- lockGetBucketPolicy sync.RWMutex
- lockGetBucketTagging sync.RWMutex
- lockGetBucketVersioning sync.RWMutex
- lockGetObject sync.RWMutex
- lockGetObjectAcl sync.RWMutex
- lockGetObjectAttributes sync.RWMutex
- lockGetObjectTagging sync.RWMutex
- lockHeadBucket sync.RWMutex
- lockHeadObject sync.RWMutex
- lockListBuckets sync.RWMutex
- lockListBucketsAndOwners sync.RWMutex
- lockListMultipartUploads sync.RWMutex
- lockListObjectVersions sync.RWMutex
- lockListObjects sync.RWMutex
- lockListObjectsV2 sync.RWMutex
- lockListParts sync.RWMutex
- lockPutBucketAcl sync.RWMutex
- lockPutBucketPolicy sync.RWMutex
- lockPutBucketTagging sync.RWMutex
- lockPutBucketVersioning sync.RWMutex
- lockPutObject sync.RWMutex
- lockPutObjectAcl sync.RWMutex
- lockPutObjectTagging sync.RWMutex
- lockRestoreObject sync.RWMutex
- lockSelectObjectContent sync.RWMutex
- lockShutdown sync.RWMutex
- lockString sync.RWMutex
- lockUploadPart sync.RWMutex
- lockUploadPartCopy sync.RWMutex
+ lockAbortMultipartUpload sync.RWMutex
+ lockChangeBucketOwner sync.RWMutex
+ lockCompleteMultipartUpload sync.RWMutex
+ lockCopyObject sync.RWMutex
+ lockCreateBucket sync.RWMutex
+ lockCreateMultipartUpload sync.RWMutex
+ lockDeleteBucket sync.RWMutex
+ lockDeleteBucketPolicy sync.RWMutex
+ lockDeleteBucketTagging sync.RWMutex
+ lockDeleteObject sync.RWMutex
+ lockDeleteObjectTagging sync.RWMutex
+ lockDeleteObjects sync.RWMutex
+ lockGetBucketAcl sync.RWMutex
+ lockGetBucketPolicy sync.RWMutex
+ lockGetBucketTagging sync.RWMutex
+ lockGetBucketVersioning sync.RWMutex
+ lockGetObject sync.RWMutex
+ lockGetObjectAcl sync.RWMutex
+ lockGetObjectAttributes sync.RWMutex
+ lockGetObjectLegalHold sync.RWMutex
+ lockGetObjectLockConfiguration sync.RWMutex
+ lockGetObjectRetention sync.RWMutex
+ lockGetObjectTagging sync.RWMutex
+ lockHeadBucket sync.RWMutex
+ lockHeadObject sync.RWMutex
+ lockListBuckets sync.RWMutex
+ lockListBucketsAndOwners sync.RWMutex
+ lockListMultipartUploads sync.RWMutex
+ lockListObjectVersions sync.RWMutex
+ lockListObjects sync.RWMutex
+ lockListObjectsV2 sync.RWMutex
+ lockListParts sync.RWMutex
+ lockPutBucketAcl sync.RWMutex
+ lockPutBucketPolicy sync.RWMutex
+ lockPutBucketTagging sync.RWMutex
+ lockPutBucketVersioning sync.RWMutex
+ lockPutObject sync.RWMutex
+ lockPutObjectAcl sync.RWMutex
+ lockPutObjectLegalHold sync.RWMutex
+ lockPutObjectLockConfiguration sync.RWMutex
+ lockPutObjectRetention sync.RWMutex
+ lockPutObjectTagging sync.RWMutex
+ lockRestoreObject sync.RWMutex
+ lockSelectObjectContent sync.RWMutex
+ lockShutdown sync.RWMutex
+ lockString sync.RWMutex
+ lockUploadPart sync.RWMutex
+ lockUploadPartCopy sync.RWMutex
}
// AbortMultipartUpload calls AbortMultipartUploadFunc.
@@ -1335,6 +1441,130 @@ func (mock *BackendMock) GetObjectAttributesCalls() []struct {
return calls
}
+// GetObjectLegalHold calls GetObjectLegalHoldFunc.
+func (mock *BackendMock) GetObjectLegalHold(contextMoqParam context.Context, bucket string, object string, versionId string) (*bool, error) {
+ if mock.GetObjectLegalHoldFunc == nil {
+ panic("BackendMock.GetObjectLegalHoldFunc: method is nil but Backend.GetObjectLegalHold was just called")
+ }
+ callInfo := struct {
+ ContextMoqParam context.Context
+ Bucket string
+ Object string
+ VersionId string
+ }{
+ ContextMoqParam: contextMoqParam,
+ Bucket: bucket,
+ Object: object,
+ VersionId: versionId,
+ }
+ mock.lockGetObjectLegalHold.Lock()
+ mock.calls.GetObjectLegalHold = append(mock.calls.GetObjectLegalHold, callInfo)
+ mock.lockGetObjectLegalHold.Unlock()
+ return mock.GetObjectLegalHoldFunc(contextMoqParam, bucket, object, versionId)
+}
+
+// GetObjectLegalHoldCalls gets all the calls that were made to GetObjectLegalHold.
+// Check the length with:
+//
+// len(mockedBackend.GetObjectLegalHoldCalls())
+func (mock *BackendMock) GetObjectLegalHoldCalls() []struct {
+ ContextMoqParam context.Context
+ Bucket string
+ Object string
+ VersionId string
+} {
+ var calls []struct {
+ ContextMoqParam context.Context
+ Bucket string
+ Object string
+ VersionId string
+ }
+ mock.lockGetObjectLegalHold.RLock()
+ calls = mock.calls.GetObjectLegalHold
+ mock.lockGetObjectLegalHold.RUnlock()
+ return calls
+}
+
+// GetObjectLockConfiguration calls GetObjectLockConfigurationFunc.
+func (mock *BackendMock) GetObjectLockConfiguration(contextMoqParam context.Context, bucket string) ([]byte, error) {
+ if mock.GetObjectLockConfigurationFunc == nil {
+ panic("BackendMock.GetObjectLockConfigurationFunc: method is nil but Backend.GetObjectLockConfiguration was just called")
+ }
+ callInfo := struct {
+ ContextMoqParam context.Context
+ Bucket string
+ }{
+ ContextMoqParam: contextMoqParam,
+ Bucket: bucket,
+ }
+ mock.lockGetObjectLockConfiguration.Lock()
+ mock.calls.GetObjectLockConfiguration = append(mock.calls.GetObjectLockConfiguration, callInfo)
+ mock.lockGetObjectLockConfiguration.Unlock()
+ return mock.GetObjectLockConfigurationFunc(contextMoqParam, bucket)
+}
+
+// GetObjectLockConfigurationCalls gets all the calls that were made to GetObjectLockConfiguration.
+// Check the length with:
+//
+// len(mockedBackend.GetObjectLockConfigurationCalls())
+func (mock *BackendMock) GetObjectLockConfigurationCalls() []struct {
+ ContextMoqParam context.Context
+ Bucket string
+} {
+ var calls []struct {
+ ContextMoqParam context.Context
+ Bucket string
+ }
+ mock.lockGetObjectLockConfiguration.RLock()
+ calls = mock.calls.GetObjectLockConfiguration
+ mock.lockGetObjectLockConfiguration.RUnlock()
+ return calls
+}
+
+// GetObjectRetention calls GetObjectRetentionFunc.
+func (mock *BackendMock) GetObjectRetention(contextMoqParam context.Context, bucket string, object string, versionId string) ([]byte, error) {
+ if mock.GetObjectRetentionFunc == nil {
+ panic("BackendMock.GetObjectRetentionFunc: method is nil but Backend.GetObjectRetention was just called")
+ }
+ callInfo := struct {
+ ContextMoqParam context.Context
+ Bucket string
+ Object string
+ VersionId string
+ }{
+ ContextMoqParam: contextMoqParam,
+ Bucket: bucket,
+ Object: object,
+ VersionId: versionId,
+ }
+ mock.lockGetObjectRetention.Lock()
+ mock.calls.GetObjectRetention = append(mock.calls.GetObjectRetention, callInfo)
+ mock.lockGetObjectRetention.Unlock()
+ return mock.GetObjectRetentionFunc(contextMoqParam, bucket, object, versionId)
+}
+
+// GetObjectRetentionCalls gets all the calls that were made to GetObjectRetention.
+// Check the length with:
+//
+// len(mockedBackend.GetObjectRetentionCalls())
+func (mock *BackendMock) GetObjectRetentionCalls() []struct {
+ ContextMoqParam context.Context
+ Bucket string
+ Object string
+ VersionId string
+} {
+ var calls []struct {
+ ContextMoqParam context.Context
+ Bucket string
+ Object string
+ VersionId string
+ }
+ mock.lockGetObjectRetention.RLock()
+ calls = mock.calls.GetObjectRetention
+ mock.lockGetObjectRetention.RUnlock()
+ return calls
+}
+
// GetObjectTagging calls GetObjectTaggingFunc.
func (mock *BackendMock) GetObjectTagging(contextMoqParam context.Context, bucket string, object string) (map[string]string, error) {
if mock.GetObjectTaggingFunc == nil {
@@ -1927,6 +2157,142 @@ func (mock *BackendMock) PutObjectAclCalls() []struct {
return calls
}
+// PutObjectLegalHold calls PutObjectLegalHoldFunc.
+func (mock *BackendMock) PutObjectLegalHold(contextMoqParam context.Context, bucket string, object string, versionId string, status bool) error {
+ if mock.PutObjectLegalHoldFunc == nil {
+ panic("BackendMock.PutObjectLegalHoldFunc: method is nil but Backend.PutObjectLegalHold was just called")
+ }
+ callInfo := struct {
+ ContextMoqParam context.Context
+ Bucket string
+ Object string
+ VersionId string
+ Status bool
+ }{
+ ContextMoqParam: contextMoqParam,
+ Bucket: bucket,
+ Object: object,
+ VersionId: versionId,
+ Status: status,
+ }
+ mock.lockPutObjectLegalHold.Lock()
+ mock.calls.PutObjectLegalHold = append(mock.calls.PutObjectLegalHold, callInfo)
+ mock.lockPutObjectLegalHold.Unlock()
+ return mock.PutObjectLegalHoldFunc(contextMoqParam, bucket, object, versionId, status)
+}
+
+// PutObjectLegalHoldCalls gets all the calls that were made to PutObjectLegalHold.
+// Check the length with:
+//
+// len(mockedBackend.PutObjectLegalHoldCalls())
+func (mock *BackendMock) PutObjectLegalHoldCalls() []struct {
+ ContextMoqParam context.Context
+ Bucket string
+ Object string
+ VersionId string
+ Status bool
+} {
+ var calls []struct {
+ ContextMoqParam context.Context
+ Bucket string
+ Object string
+ VersionId string
+ Status bool
+ }
+ mock.lockPutObjectLegalHold.RLock()
+ calls = mock.calls.PutObjectLegalHold
+ mock.lockPutObjectLegalHold.RUnlock()
+ return calls
+}
+
+// PutObjectLockConfiguration calls PutObjectLockConfigurationFunc.
+func (mock *BackendMock) PutObjectLockConfiguration(contextMoqParam context.Context, bucket string, config []byte) error {
+ if mock.PutObjectLockConfigurationFunc == nil {
+ panic("BackendMock.PutObjectLockConfigurationFunc: method is nil but Backend.PutObjectLockConfiguration was just called")
+ }
+ callInfo := struct {
+ ContextMoqParam context.Context
+ Bucket string
+ Config []byte
+ }{
+ ContextMoqParam: contextMoqParam,
+ Bucket: bucket,
+ Config: config,
+ }
+ mock.lockPutObjectLockConfiguration.Lock()
+ mock.calls.PutObjectLockConfiguration = append(mock.calls.PutObjectLockConfiguration, callInfo)
+ mock.lockPutObjectLockConfiguration.Unlock()
+ return mock.PutObjectLockConfigurationFunc(contextMoqParam, bucket, config)
+}
+
+// PutObjectLockConfigurationCalls gets all the calls that were made to PutObjectLockConfiguration.
+// Check the length with:
+//
+// len(mockedBackend.PutObjectLockConfigurationCalls())
+func (mock *BackendMock) PutObjectLockConfigurationCalls() []struct {
+ ContextMoqParam context.Context
+ Bucket string
+ Config []byte
+} {
+ var calls []struct {
+ ContextMoqParam context.Context
+ Bucket string
+ Config []byte
+ }
+ mock.lockPutObjectLockConfiguration.RLock()
+ calls = mock.calls.PutObjectLockConfiguration
+ mock.lockPutObjectLockConfiguration.RUnlock()
+ return calls
+}
+
+// PutObjectRetention calls PutObjectRetentionFunc.
+func (mock *BackendMock) PutObjectRetention(contextMoqParam context.Context, bucket string, object string, versionId string, retention []byte) error {
+ if mock.PutObjectRetentionFunc == nil {
+ panic("BackendMock.PutObjectRetentionFunc: method is nil but Backend.PutObjectRetention was just called")
+ }
+ callInfo := struct {
+ ContextMoqParam context.Context
+ Bucket string
+ Object string
+ VersionId string
+ Retention []byte
+ }{
+ ContextMoqParam: contextMoqParam,
+ Bucket: bucket,
+ Object: object,
+ VersionId: versionId,
+ Retention: retention,
+ }
+ mock.lockPutObjectRetention.Lock()
+ mock.calls.PutObjectRetention = append(mock.calls.PutObjectRetention, callInfo)
+ mock.lockPutObjectRetention.Unlock()
+ return mock.PutObjectRetentionFunc(contextMoqParam, bucket, object, versionId, retention)
+}
+
+// PutObjectRetentionCalls gets all the calls that were made to PutObjectRetention.
+// Check the length with:
+//
+// len(mockedBackend.PutObjectRetentionCalls())
+func (mock *BackendMock) PutObjectRetentionCalls() []struct {
+ ContextMoqParam context.Context
+ Bucket string
+ Object string
+ VersionId string
+ Retention []byte
+} {
+ var calls []struct {
+ ContextMoqParam context.Context
+ Bucket string
+ Object string
+ VersionId string
+ Retention []byte
+ }
+ mock.lockPutObjectRetention.RLock()
+ calls = mock.calls.PutObjectRetention
+ mock.lockPutObjectRetention.RUnlock()
+ return calls
+}
+
// PutObjectTagging calls PutObjectTaggingFunc.
func (mock *BackendMock) PutObjectTagging(contextMoqParam context.Context, bucket string, object string, tags map[string]string) error {
if mock.PutObjectTaggingFunc == nil {
diff --git a/s3api/controllers/base.go b/s3api/controllers/base.go
index 7bef1bc5..97509d5a 100644
--- a/s3api/controllers/base.go
+++ b/s3api/controllers/base.go
@@ -131,6 +131,72 @@ func (c S3ApiController) GetActions(ctx *fiber.Ctx) error {
})
}
+ if ctx.Request().URI().QueryArgs().Has("retention") {
+ err := auth.VerifyAccess(ctx.Context(), c.be, auth.AccessOptions{
+ Acl: parsedAcl,
+ AclPermission: types.PermissionRead,
+ IsRoot: isRoot,
+ Acc: acct,
+ Bucket: bucket,
+ Object: key,
+ Action: auth.GetObjectRetentionAction,
+ })
+ if err != nil {
+ return SendXMLResponse(ctx, nil, err,
+ &MetaOpts{
+ Logger: c.logger,
+ Action: "GetObjectRetention",
+ BucketOwner: parsedAcl.Owner,
+ })
+ }
+
+ data, err := c.be.GetObjectRetention(ctx.Context(), bucket, key, versionId)
+ if err != nil {
+ return SendXMLResponse(ctx, data, err,
+ &MetaOpts{
+ Logger: c.logger,
+ Action: "GetObjectRetention",
+ BucketOwner: parsedAcl.Owner,
+ })
+ }
+
+ retention, err := auth.ParseObjectLockRetentionOutput(data)
+ return SendXMLResponse(ctx, retention, err,
+ &MetaOpts{
+ Logger: c.logger,
+ Action: "GetObjectRetention",
+ BucketOwner: parsedAcl.Owner,
+ })
+ }
+
+ if ctx.Request().URI().QueryArgs().Has("legal-hold") {
+ err := auth.VerifyAccess(ctx.Context(), c.be, auth.AccessOptions{
+ Acl: parsedAcl,
+ AclPermission: types.PermissionRead,
+ IsRoot: isRoot,
+ Acc: acct,
+ Bucket: bucket,
+ Object: key,
+ Action: auth.GetObjectLegalHoldAction,
+ })
+ if err != nil {
+ return SendXMLResponse(ctx, nil, err,
+ &MetaOpts{
+ Logger: c.logger,
+ Action: "GetObjectLegalHold",
+ BucketOwner: parsedAcl.Owner,
+ })
+ }
+
+ data, err := c.be.GetObjectLegalHold(ctx.Context(), bucket, key, versionId)
+ return SendXMLResponse(ctx, auth.ParseObjectLegalHoldOutput(data), err,
+ &MetaOpts{
+ Logger: c.logger,
+ Action: "GetObjectLegalHold",
+ BucketOwner: parsedAcl.Owner,
+ })
+ }
+
if uploadId != "" {
if maxParts < 0 && ctx.Request().URI().QueryArgs().Has("max-parts") {
return SendResponse(ctx,
@@ -547,6 +613,43 @@ func (c S3ApiController) ListActions(ctx *fiber.Ctx) error {
})
}
+ if ctx.Request().URI().QueryArgs().Has("object-lock") {
+ err := auth.VerifyAccess(ctx.Context(), c.be, auth.AccessOptions{
+ Acl: parsedAcl,
+ AclPermission: types.PermissionRead,
+ IsRoot: isRoot,
+ Acc: acct,
+ Bucket: bucket,
+ Action: auth.GetBucketObjectLockConfigurationAction,
+ })
+ if err != nil {
+ return SendXMLResponse(ctx, nil, err,
+ &MetaOpts{
+ Logger: c.logger,
+ Action: "GetObjectLockConfiguration",
+ BucketOwner: parsedAcl.Owner,
+ })
+ }
+
+ data, err := c.be.GetObjectLockConfiguration(ctx.Context(), bucket)
+ if err != nil {
+ return SendXMLResponse(ctx, nil, err,
+ &MetaOpts{
+ Logger: c.logger,
+ Action: "GetObjectLockConfiguration",
+ BucketOwner: parsedAcl.Owner,
+ })
+ }
+
+ resp, err := auth.ParseBucketLockConfigurationOutput(data)
+ return SendXMLResponse(ctx, resp, err,
+ &MetaOpts{
+ Logger: c.logger,
+ Action: "GetObjectLockConfiguration",
+ BucketOwner: parsedAcl.Owner,
+ })
+ }
+
if ctx.Request().URI().QueryArgs().Has("acl") {
err := auth.VerifyAccess(ctx.Context(), c.be, auth.AccessOptions{
Acl: parsedAcl,
@@ -845,6 +948,44 @@ func (c S3ApiController) PutBucketActions(ctx *fiber.Ctx) error {
})
}
+ if ctx.Request().URI().QueryArgs().Has("object-lock") {
+ parsedAcl := ctx.Locals("parsedAcl").(auth.ACL)
+
+ if err := auth.VerifyAccess(ctx.Context(), c.be, auth.AccessOptions{
+ Acl: parsedAcl,
+ AclPermission: types.PermissionWrite,
+ IsRoot: isRoot,
+ Acc: acct,
+ Bucket: bucket,
+ Action: auth.PutBucketObjectLockConfigurationAction,
+ }); err != nil {
+ return SendResponse(ctx, err,
+ &MetaOpts{
+ Logger: c.logger,
+ Action: "PutObjectLockConfiguration",
+ BucketOwner: parsedAcl.Owner,
+ })
+ }
+
+ config, err := auth.ParseBucketLockConfigurationInput(ctx.Body())
+ if err != nil {
+ return SendResponse(ctx, err,
+ &MetaOpts{
+ Logger: c.logger,
+ Action: "PutObjectLockConfiguration",
+ BucketOwner: parsedAcl.Owner,
+ })
+ }
+
+ err = c.be.PutObjectLockConfiguration(ctx.Context(), bucket, config)
+ return SendResponse(ctx, err,
+ &MetaOpts{
+ Logger: c.logger,
+ Action: "PutObjectLockConfiguration",
+ BucketOwner: parsedAcl.Owner,
+ })
+ }
+
if ctx.Request().URI().QueryArgs().Has("policy") {
parsedAcl := ctx.Locals("parsedAcl").(auth.ACL)
err := auth.VerifyAccess(ctx.Context(), c.be, auth.AccessOptions{
@@ -1076,6 +1217,7 @@ func (c S3ApiController) PutActions(ctx *fiber.Ctx) error {
keyStart := ctx.Params("key")
keyEnd := ctx.Params("*1")
uploadId := ctx.Query("uploadId")
+ versionId := ctx.Query("versionId")
acct := ctx.Locals("account").(auth.Account)
isRoot := ctx.Locals("isRoot").(bool)
parsedAcl := ctx.Locals("parsedAcl").(auth.ACL)
@@ -1176,6 +1318,76 @@ func (c S3ApiController) PutActions(ctx *fiber.Ctx) error {
})
}
+ if ctx.Request().URI().QueryArgs().Has("retention") {
+ if err := auth.VerifyAccess(ctx.Context(), c.be, auth.AccessOptions{
+ Acl: parsedAcl,
+ AclPermission: types.PermissionWrite,
+ IsRoot: isRoot,
+ Acc: acct,
+ Bucket: bucket,
+ Object: keyStart,
+ Action: auth.PutObjectRetentionAction,
+ }); err != nil {
+ return SendResponse(ctx, err,
+ &MetaOpts{
+ Logger: c.logger,
+ Action: "PutObjectRetention",
+ BucketOwner: parsedAcl.Owner,
+ })
+ }
+
+ retention, err := auth.ParseObjectLockRetentionInput(ctx.Body())
+ if err != nil {
+ return SendResponse(ctx, err, &MetaOpts{
+ Logger: c.logger,
+ Action: "PutObjectRetention",
+ BucketOwner: parsedAcl.Owner,
+ })
+ }
+
+ err = c.be.PutObjectRetention(ctx.Context(), bucket, keyStart, versionId, retention)
+ return SendResponse(ctx, err, &MetaOpts{
+ Logger: c.logger,
+ Action: "PutObjectRetention",
+ BucketOwner: parsedAcl.Owner,
+ })
+ }
+
+ if ctx.Request().URI().QueryArgs().Has("legal-hold") {
+ var legalHold types.ObjectLockLegalHold
+ if err := xml.Unmarshal(ctx.Body(), &legalHold); err != nil {
+ return SendResponse(ctx, s3err.GetAPIError(s3err.ErrInvalidRequest), &MetaOpts{
+ Logger: c.logger,
+ Action: "PutObjectLegalHold",
+ BucketOwner: parsedAcl.Owner,
+ })
+ }
+
+ if err := auth.VerifyAccess(ctx.Context(), c.be, auth.AccessOptions{
+ Acl: parsedAcl,
+ AclPermission: types.PermissionWrite,
+ IsRoot: isRoot,
+ Acc: acct,
+ Bucket: bucket,
+ Object: keyStart,
+ Action: auth.PutObjectLegalHoldAction,
+ }); err != nil {
+ return SendResponse(ctx, err,
+ &MetaOpts{
+ Logger: c.logger,
+ Action: "PutObjectLegalHold",
+ BucketOwner: parsedAcl.Owner,
+ })
+ }
+
+ err := c.be.PutObjectLegalHold(ctx.Context(), bucket, keyStart, versionId, legalHold.Status == types.ObjectLockLegalHoldStatusOn)
+ return SendResponse(ctx, err, &MetaOpts{
+ Logger: c.logger,
+ Action: "PutObjectLegalHold",
+ BucketOwner: parsedAcl.Owner,
+ })
+ }
+
if ctx.Request().URI().QueryArgs().Has("uploadId") &&
ctx.Request().URI().QueryArgs().Has("partNumber") &&
copySource != "" {
@@ -1525,6 +1737,16 @@ func (c S3ApiController) PutActions(ctx *fiber.Ctx) error {
})
}
+ err = auth.CheckObjectAccess(ctx.Context(), bucket, acct.Access, []string{keyStart}, isRoot || acct.Role == auth.RoleAdmin, c.be)
+ if err != nil {
+ return SendResponse(ctx, err,
+ &MetaOpts{
+ Logger: c.logger,
+ Action: "PutObject",
+ BucketOwner: parsedAcl.Owner,
+ })
+ }
+
contentLength, err := strconv.ParseInt(contentLengthStr, 10, 64)
if err != nil {
if c.debug {
@@ -1703,6 +1925,16 @@ func (c S3ApiController) DeleteObjects(ctx *fiber.Ctx) error {
})
}
+ err = auth.CheckObjectAccess(ctx.Context(), bucket, acct.Access, utils.ParseDeleteObjects(dObj.Objects), isRoot || acct.Role == auth.RoleAdmin, c.be)
+ if err != nil {
+ return SendResponse(ctx, err,
+ &MetaOpts{
+ Logger: c.logger,
+ Action: "DeleteObjects",
+ BucketOwner: parsedAcl.Owner,
+ })
+ }
+
res, err := c.be.DeleteObjects(ctx.Context(),
&s3.DeleteObjectsInput{
Bucket: &bucket,
@@ -1823,6 +2055,16 @@ func (c S3ApiController) DeleteActions(ctx *fiber.Ctx) error {
})
}
+ err = auth.CheckObjectAccess(ctx.Context(), bucket, acct.Access, []string{key}, isRoot || acct.Role == auth.RoleAdmin, c.be)
+ if err != nil {
+ return SendResponse(ctx, err,
+ &MetaOpts{
+ Logger: c.logger,
+ Action: "DeleteObject",
+ BucketOwner: parsedAcl.Owner,
+ })
+ }
+
err = c.be.DeleteObject(ctx.Context(),
&s3.DeleteObjectInput{
Bucket: &bucket,
diff --git a/s3api/controllers/base_test.go b/s3api/controllers/base_test.go
index f34e8656..b522e38a 100644
--- a/s3api/controllers/base_test.go
+++ b/s3api/controllers/base_test.go
@@ -205,6 +205,19 @@ func TestS3ApiController_GetActions(t *testing.T) {
GetObjectTaggingFunc: func(_ context.Context, bucket, object string) (map[string]string, error) {
return map[string]string{"hello": "world"}, nil
},
+ GetObjectRetentionFunc: func(contextMoqParam context.Context, bucket, object, versionId string) ([]byte, error) {
+ result, err := json.Marshal(types.ObjectLockRetention{
+ Mode: types.ObjectLockRetentionModeCompliance,
+ })
+ if err != nil {
+ return nil, err
+ }
+ return result, nil
+ },
+ GetObjectLegalHoldFunc: func(contextMoqParam context.Context, bucket, object, versionId string) (*bool, error) {
+ result := true
+ return &result, nil
+ },
},
}
app.Use(func(ctx *fiber.Ctx) error {
@@ -236,6 +249,24 @@ func TestS3ApiController_GetActions(t *testing.T) {
wantErr: false,
statusCode: 200,
},
+ {
+ name: "Get-actions-get-object-retention-success",
+ app: app,
+ args: args{
+ req: httptest.NewRequest(http.MethodGet, "/my-bucket/my-obj?retention", nil),
+ },
+ wantErr: false,
+ statusCode: 200,
+ },
+ {
+ name: "Get-actions-get-object-legal-hold-success",
+ app: app,
+ args: args{
+ req: httptest.NewRequest(http.MethodGet, "/my-bucket/my-obj?legal-hold", nil),
+ },
+ wantErr: false,
+ statusCode: 200,
+ },
{
name: "Get-actions-invalid-max-parts-string",
app: app,
@@ -329,6 +360,11 @@ func TestS3ApiController_ListActions(t *testing.T) {
req *http.Request
}
+ objectLockResult, err := json.Marshal(auth.BucketLockConfig{})
+ if err != nil {
+ t.Errorf("failed to parse object lock result %v", err)
+ }
+
app := fiber.New()
s3ApiController := S3ApiController{
be: &BackendMock{
@@ -356,6 +392,9 @@ func TestS3ApiController_ListActions(t *testing.T) {
GetBucketPolicyFunc: func(contextMoqParam context.Context, bucket string) ([]byte, error) {
return []byte{}, nil
},
+ GetObjectLockConfigurationFunc: func(contextMoqParam context.Context, bucket string) ([]byte, error) {
+ return objectLockResult, nil
+ },
},
}
@@ -369,7 +408,7 @@ func TestS3ApiController_ListActions(t *testing.T) {
app.Get("/:bucket", s3ApiController.ListActions)
- //Error case
+ // Error case
s3ApiControllerError := S3ApiController{
be: &BackendMock{
GetBucketAclFunc: func(context.Context, *s3.GetBucketAclInput) ([]byte, error) {
@@ -418,6 +457,15 @@ func TestS3ApiController_ListActions(t *testing.T) {
wantErr: false,
statusCode: 200,
},
+ {
+ name: "Get-object-lock-configuration-success",
+ app: app,
+ args: args{
+ req: httptest.NewRequest(http.MethodGet, "/my-bucket?object-lock", nil),
+ },
+ wantErr: false,
+ statusCode: 200,
+ },
{
name: "Get-bucket-acl-success",
app: app,
@@ -584,6 +632,18 @@ func TestS3ApiController_PutBucketActions(t *testing.T) {
}
`
+ objectLockBody := `
+
+ Enabled
+
+
+ GOVERNANCE
+ 2
+
+
+
+ `
+
s3ApiController := S3ApiController{
be: &BackendMock{
GetBucketAclFunc: func(context.Context, *s3.GetBucketAclInput) ([]byte, error) {
@@ -604,6 +664,9 @@ func TestS3ApiController_PutBucketActions(t *testing.T) {
PutBucketPolicyFunc: func(contextMoqParam context.Context, bucket string, policy []byte) error {
return nil
},
+ PutObjectLockConfigurationFunc: func(contextMoqParam context.Context, bucket string, config []byte) error {
+ return nil
+ },
},
}
// Mock ctx.Locals
@@ -662,6 +725,24 @@ func TestS3ApiController_PutBucketActions(t *testing.T) {
wantErr: false,
statusCode: 200,
},
+ {
+ name: "Put-object-lock-configuration-invalid-body",
+ app: app,
+ args: args{
+ req: httptest.NewRequest(http.MethodPut, "/my-bucket?object-lock", nil),
+ },
+ wantErr: false,
+ statusCode: 400,
+ },
+ {
+ name: "Put-object-lock-configuration-success",
+ app: app,
+ args: args{
+ req: httptest.NewRequest(http.MethodPut, "/my-bucket?object-lock", strings.NewReader(objectLockBody)),
+ },
+ wantErr: false,
+ statusCode: 200,
+ },
{
name: "Put-bucket-versioning-invalid-body",
app: app,
@@ -806,6 +887,19 @@ func TestS3ApiController_PutActions(t *testing.T) {
`
+ retentionBody := `
+
+ GOVERNANCE
+ 2025-01-01T00:00:00Z
+
+ `
+
+ legalHoldBody := `
+
+ string
+
+ `
+
app := fiber.New()
s3ApiController := S3ApiController{
be: &BackendMock{
@@ -832,6 +926,15 @@ func TestS3ApiController_PutActions(t *testing.T) {
UploadPartCopyFunc: func(context.Context, *s3.UploadPartCopyInput) (s3response.CopyObjectResult, error) {
return s3response.CopyObjectResult{}, nil
},
+ PutObjectLegalHoldFunc: func(contextMoqParam context.Context, bucket, object, versionId string, status bool) error {
+ return nil
+ },
+ PutObjectRetentionFunc: func(contextMoqParam context.Context, bucket, object, versionId string, retention []byte) error {
+ return nil
+ },
+ GetObjectLockConfigurationFunc: func(contextMoqParam context.Context, bucket string) ([]byte, error) {
+ return nil, s3err.GetAPIError(s3err.ErrObjectLockConfigurationNotFound)
+ },
},
}
app.Use(func(ctx *fiber.Ctx) error {
@@ -910,6 +1013,42 @@ func TestS3ApiController_PutActions(t *testing.T) {
wantErr: false,
statusCode: 200,
},
+ {
+ name: "put-object-retention-invalid-request",
+ app: app,
+ args: args{
+ req: httptest.NewRequest(http.MethodPut, "/my-bucket/my-key?retention", nil),
+ },
+ wantErr: false,
+ statusCode: 400,
+ },
+ {
+ name: "put-object-retention-success",
+ app: app,
+ args: args{
+ req: httptest.NewRequest(http.MethodPut, "/my-bucket/my-key?retention", strings.NewReader(retentionBody)),
+ },
+ wantErr: false,
+ statusCode: 200,
+ },
+ {
+ name: "put-legal-hold-invalid-request",
+ app: app,
+ args: args{
+ req: httptest.NewRequest(http.MethodPut, "/my-bucket/my-key?legal-hold", nil),
+ },
+ wantErr: false,
+ statusCode: 400,
+ },
+ {
+ name: "put-legal-hold-success",
+ app: app,
+ args: args{
+ req: httptest.NewRequest(http.MethodPut, "/my-bucket/my-key?legal-hold", strings.NewReader(legalHoldBody)),
+ },
+ wantErr: false,
+ statusCode: 200,
+ },
{
name: "Put-object-acl-invalid-acl",
app: app,
@@ -1096,6 +1235,9 @@ func TestS3ApiController_DeleteObjects(t *testing.T) {
DeleteObjectsFunc: func(context.Context, *s3.DeleteObjectsInput) (s3response.DeleteResult, error) {
return s3response.DeleteResult{}, nil
},
+ GetObjectLockConfigurationFunc: func(contextMoqParam context.Context, bucket string) ([]byte, error) {
+ return nil, s3err.GetAPIError(s3err.ErrObjectLockConfigurationNotFound)
+ },
},
}
@@ -1173,6 +1315,9 @@ func TestS3ApiController_DeleteActions(t *testing.T) {
DeleteObjectTaggingFunc: func(_ context.Context, bucket, object string) error {
return nil
},
+ GetObjectLockConfigurationFunc: func(contextMoqParam context.Context, bucket string) ([]byte, error) {
+ return nil, s3err.GetAPIError(s3err.ErrObjectLockConfigurationNotFound)
+ },
},
}
@@ -1195,6 +1340,9 @@ func TestS3ApiController_DeleteActions(t *testing.T) {
DeleteObjectFunc: func(context.Context, *s3.DeleteObjectInput) error {
return s3err.GetAPIError(7)
},
+ GetObjectLockConfigurationFunc: func(contextMoqParam context.Context, bucket string) ([]byte, error) {
+ return nil, s3err.GetAPIError(s3err.ErrObjectLockConfigurationNotFound)
+ },
}}
appErr.Use(func(ctx *fiber.Ctx) error {
diff --git a/s3api/middlewares/acl-parser.go b/s3api/middlewares/acl-parser.go
index 54446554..b966bbea 100644
--- a/s3api/middlewares/acl-parser.go
+++ b/s3api/middlewares/acl-parser.go
@@ -48,7 +48,8 @@ func AclParser(be backend.Backend, logger s3log.AuditLogger) fiber.Handler {
!ctx.Request().URI().QueryArgs().Has("acl") &&
!ctx.Request().URI().QueryArgs().Has("tagging") &&
!ctx.Request().URI().QueryArgs().Has("versioning") &&
- !ctx.Request().URI().QueryArgs().Has("policy") {
+ !ctx.Request().URI().QueryArgs().Has("policy") &&
+ !ctx.Request().URI().QueryArgs().Has("object-lock") {
if err := auth.MayCreateBucket(acct, isRoot); err != nil {
return controllers.SendXMLResponse(ctx, nil, err, &controllers.MetaOpts{Logger: logger, Action: "CreateBucket"})
}
diff --git a/s3api/utils/utils.go b/s3api/utils/utils.go
index f0241a3a..17e9be59 100644
--- a/s3api/utils/utils.go
+++ b/s3api/utils/utils.go
@@ -26,6 +26,7 @@ import (
"strings"
"time"
+ "github.com/aws/aws-sdk-go-v2/service/s3/types"
"github.com/aws/smithy-go/encoding/httpbinding"
"github.com/gofiber/fiber/v2"
"github.com/valyala/fasthttp"
@@ -244,3 +245,11 @@ func ValidateDate(date time.Time) error {
return nil
}
+
+func ParseDeleteObjects(objs []types.ObjectIdentifier) (result []string) {
+ for _, obj := range objs {
+ result = append(result, *obj.Key)
+ }
+
+ return
+}
diff --git a/s3err/s3err.go b/s3err/s3err.go
index aa3d11af..35b76765 100644
--- a/s3err/s3err.go
+++ b/s3err/s3err.go
@@ -111,6 +111,11 @@ const (
ErrInvalidObjectState
ErrInvalidRange
ErrInvalidURI
+ ErrObjectLockConfigurationNotFound
+ ErrNoSuchObjectLockConfiguration
+ ErrInvalidBucketObjectLockConfiguration
+ ErrObjectLocked
+ ErrPastObjectLockRetainDate
// Non-AWS errors
ErrExistingObjectIsDirectory
@@ -400,6 +405,31 @@ var errorCodeResponse = map[ErrorCode]APIError{
Description: "The specified URI couldn't be parsed.",
HTTPStatusCode: http.StatusBadRequest,
},
+ ErrObjectLockConfigurationNotFound: {
+ Code: "ObjectLockConfigurationNotFoundError",
+ Description: "Object Lock configuration does not exist for this bucket",
+ HTTPStatusCode: http.StatusNotFound,
+ },
+ ErrNoSuchObjectLockConfiguration: {
+ Code: "NoSuchObjectLockConfiguration",
+ Description: "The specified object does not have an ObjectLock configuration",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrInvalidBucketObjectLockConfiguration: {
+ Code: "InvalidRequest",
+ Description: "Bucket is missing ObjectLockConfiguration",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrObjectLocked: {
+ Code: "InvalidRequest",
+ Description: "Object is WORM protected and cannot be overwritten",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrPastObjectLockRetainDate: {
+ Code: "InvalidRequest",
+ Description: "the retain until date must be in the future",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
ErrExistingObjectIsDirectory: {
Code: "ExistingObjectIsDirectory",
Description: "Existing Object is a directory.",
diff --git a/tests/integration/group-tests.go b/tests/integration/group-tests.go
index a5cb6dd5..a8ad3516 100644
--- a/tests/integration/group-tests.go
+++ b/tests/integration/group-tests.go
@@ -277,6 +277,61 @@ func TestDeleteBucketPolicy(s *S3Conf) {
DeleteBucketPolicy_success(s)
}
+func TestPutObjectLockConfiguration(s *S3Conf) {
+ PutObjectLockConfiguration_non_existing_bucket(s)
+ PutObjectLockConfiguration_empty_config(s)
+ PutObjectLockConfiguration_both_years_and_days(s)
+ PutObjectLockConfiguration_success(s)
+}
+
+func TestGetObjectLockConfiguration(s *S3Conf) {
+ GetObjectLockConfiguration_non_existing_bucket(s)
+ GetObjectLockConfiguration_unset_config(s)
+ GetObjectLockConfiguration_success(s)
+}
+
+func TestPutObjectRetention(s *S3Conf) {
+ PutObjectRetention_non_existing_bucket(s)
+ PutObjectRetention_non_existing_object(s)
+ PutObjectRetention_unset_bucket_object_lock_config(s)
+ PutObjectRetention_disabled_bucket_object_lock_config(s)
+ PutObjectRetention_expired_retain_until_date(s)
+ PutObjectRetention_success(s)
+}
+
+func TestGetObjectRetention(s *S3Conf) {
+ GetObjectRetention_non_existing_bucket(s)
+ GetObjectRetention_non_existing_object(s)
+ GetObjectRetention_unset_config(s)
+ GetObjectRetention_success(s)
+}
+
+func TestPutObjectLegalHold(s *S3Conf) {
+ PutObjectLegalHold_non_existing_bucket(s)
+ PutObjectLegalHold_non_existing_object(s)
+ PutObjectLegalHold_invalid_body(s)
+ PutObjectLegalHold_unset_bucket_object_lock_config(s)
+ PutObjectLegalHold_disabled_bucket_object_lock_config(s)
+ PutObjectLegalHold_success(s)
+}
+
+func TestGetObjectLegalHold(s *S3Conf) {
+ GetObjectLegalHold_non_existing_bucket(s)
+ GetObjectLegalHold_non_existing_object(s)
+ GetObjectLegalHold_unset_config(s)
+ GetObjectLegalHold_success(s)
+}
+
+func TestWORMProtection(s *S3Conf) {
+ WORMProtection_bucket_object_lock_configuration_compliance_mode(s)
+ WORMProtection_bucket_object_lock_governance_root_overwrite(s)
+ WORMProtection_object_lock_retention_compliance_root_access_denied(s)
+ WORMProtection_object_lock_retention_governance_root_overwrite(s)
+ WORMProtection_object_lock_retention_governance_user_access_denied(s)
+ WORMProtection_object_lock_legal_hold_user_access_denied(s)
+ WORMProtection_object_lock_legal_hold_root_overwrite(s)
+}
+
func TestFullFlow(s *S3Conf) {
TestAuthentication(s)
TestPresignedAuthentication(s)
@@ -309,6 +364,13 @@ func TestFullFlow(s *S3Conf) {
TestPutBucketPolicy(s)
TestGetBucketPolicy(s)
TestDeleteBucketPolicy(s)
+ TestPutObjectLockConfiguration(s)
+ TestGetObjectLockConfiguration(s)
+ TestPutObjectRetention(s)
+ TestGetObjectRetention(s)
+ TestPutObjectLegalHold(s)
+ TestGetObjectLegalHold(s)
+ TestWORMProtection(s)
TestAccessControl(s)
}
@@ -340,193 +402,227 @@ type IntTests map[string]func(s *S3Conf) error
func GetIntTests() IntTests {
return IntTests{
- "Authentication_empty_auth_header": Authentication_empty_auth_header,
- "Authentication_invalid_auth_header": Authentication_invalid_auth_header,
- "Authentication_unsupported_signature_version": Authentication_unsupported_signature_version,
- "Authentication_malformed_credentials": Authentication_malformed_credentials,
- "Authentication_malformed_credentials_invalid_parts": Authentication_malformed_credentials_invalid_parts,
- "Authentication_credentials_terminated_string": Authentication_credentials_terminated_string,
- "Authentication_credentials_incorrect_service": Authentication_credentials_incorrect_service,
- "Authentication_credentials_incorrect_region": Authentication_credentials_incorrect_region,
- "Authentication_credentials_invalid_date": Authentication_credentials_invalid_date,
- "Authentication_credentials_future_date": Authentication_credentials_future_date,
- "Authentication_credentials_past_date": Authentication_credentials_past_date,
- "Authentication_credentials_non_existing_access_key": Authentication_credentials_non_existing_access_key,
- "Authentication_invalid_signed_headers": Authentication_invalid_signed_headers,
- "Authentication_missing_date_header": Authentication_missing_date_header,
- "Authentication_invalid_date_header": Authentication_invalid_date_header,
- "Authentication_date_mismatch": Authentication_date_mismatch,
- "Authentication_incorrect_payload_hash": Authentication_incorrect_payload_hash,
- "Authentication_incorrect_md5": Authentication_incorrect_md5,
- "Authentication_signature_error_incorrect_secret_key": Authentication_signature_error_incorrect_secret_key,
- "PresignedAuth_missing_algo_query_param": PresignedAuth_missing_algo_query_param,
- "PresignedAuth_unsupported_algorithm": PresignedAuth_unsupported_algorithm,
- "PresignedAuth_missing_credentials_query_param": PresignedAuth_missing_credentials_query_param,
- "PresignedAuth_malformed_creds_invalid_parts": PresignedAuth_malformed_creds_invalid_parts,
- "PresignedAuth_creds_invalid_terminator": PresignedAuth_creds_invalid_terminator,
- "PresignedAuth_creds_incorrect_service": PresignedAuth_creds_incorrect_service,
- "PresignedAuth_creds_incorrect_region": PresignedAuth_creds_incorrect_region,
- "PresignedAuth_creds_invalid_date": PresignedAuth_creds_invalid_date,
- "PresignedAuth_missing_date_query": PresignedAuth_missing_date_query,
- "PresignedAuth_dates_mismatch": PresignedAuth_dates_mismatch,
- "PresignedAuth_non_existing_access_key_id": PresignedAuth_non_existing_access_key_id,
- "PresignedAuth_missing_signed_headers_query_param": PresignedAuth_missing_signed_headers_query_param,
- "PresignedAuth_missing_expiration_query_param": PresignedAuth_missing_expiration_query_param,
- "PresignedAuth_invalid_expiration_query_param": PresignedAuth_invalid_expiration_query_param,
- "PresignedAuth_negative_expiration_query_param": PresignedAuth_negative_expiration_query_param,
- "PresignedAuth_exceeding_expiration_query_param": PresignedAuth_exceeding_expiration_query_param,
- "PresignedAuth_expired_request": PresignedAuth_expired_request,
- "PresignedAuth_incorrect_secret_key": PresignedAuth_incorrect_secret_key,
- "PresignedAuth_PutObject_success": PresignedAuth_PutObject_success,
- "PresignedAuth_Put_GetObject_with_data": PresignedAuth_Put_GetObject_with_data,
- "PresignedAuth_Put_GetObject_with_UTF8_chars": PresignedAuth_Put_GetObject_with_UTF8_chars,
- "PresignedAuth_UploadPart": PresignedAuth_UploadPart,
- "CreateBucket_invalid_bucket_name": CreateBucket_invalid_bucket_name,
- "CreateBucket_existing_bucket": CreateBucket_existing_bucket,
- "CreateBucket_as_user": CreateBucket_as_user,
- "CreateDeleteBucket_success": CreateDeleteBucket_success,
- "CreateBucket_default_acl": CreateBucket_default_acl,
- "CreateBucket_non_default_acl": CreateBucket_non_default_acl,
- "HeadBucket_non_existing_bucket": HeadBucket_non_existing_bucket,
- "HeadBucket_success": HeadBucket_success,
- "ListBuckets_as_user": ListBuckets_as_user,
- "ListBuckets_as_admin": ListBuckets_as_admin,
- "ListBuckets_success": ListBuckets_success,
- "DeleteBucket_non_existing_bucket": DeleteBucket_non_existing_bucket,
- "DeleteBucket_non_empty_bucket": DeleteBucket_non_empty_bucket,
- "DeleteBucket_success_status_code": DeleteBucket_success_status_code,
- "PutBucketTagging_non_existing_bucket": PutBucketTagging_non_existing_bucket,
- "PutBucketTagging_long_tags": PutBucketTagging_long_tags,
- "PutBucketTagging_success": PutBucketTagging_success,
- "GetBucketTagging_non_existing_bucket": GetBucketTagging_non_existing_bucket,
- "GetBucketTagging_success": GetBucketTagging_success,
- "DeleteBucketTagging_non_existing_object": DeleteBucketTagging_non_existing_object,
- "DeleteBucketTagging_success_status": DeleteBucketTagging_success_status,
- "DeleteBucketTagging_success": DeleteBucketTagging_success,
- "PutObject_non_existing_bucket": PutObject_non_existing_bucket,
- "PutObject_special_chars": PutObject_special_chars,
- "PutObject_invalid_long_tags": PutObject_invalid_long_tags,
- "PutObject_success": PutObject_success,
- "HeadObject_non_existing_object": HeadObject_non_existing_object,
- "HeadObject_success": HeadObject_success,
- "GetObject_non_existing_key": GetObject_non_existing_key,
- "GetObject_invalid_ranges": GetObject_invalid_ranges,
- "GetObject_with_meta": GetObject_with_meta,
- "GetObject_success": GetObject_success,
- "GetObject_by_range_success": GetObject_by_range_success,
- "ListObjects_non_existing_bucket": ListObjects_non_existing_bucket,
- "ListObjects_with_prefix": ListObjects_with_prefix,
- "ListObject_truncated": ListObject_truncated,
- "ListObjects_invalid_max_keys": ListObjects_invalid_max_keys,
- "ListObjects_max_keys_0": ListObjects_max_keys_0,
- "ListObjects_delimiter": ListObjects_delimiter,
- "ListObjects_max_keys_none": ListObjects_max_keys_none,
- "ListObjects_marker_not_from_obj_list": ListObjects_marker_not_from_obj_list,
- "ListObjectsV2_start_after": ListObjectsV2_start_after,
- "ListObjectsV2_both_start_after_and_continuation_token": ListObjectsV2_both_start_after_and_continuation_token,
- "ListObjectsV2_start_after_not_in_list": ListObjectsV2_start_after_not_in_list,
- "ListObjectsV2_start_after_empty_result": ListObjectsV2_start_after_empty_result,
- "DeleteObject_non_existing_object": DeleteObject_non_existing_object,
- "DeleteObject_success": DeleteObject_success,
- "DeleteObject_success_status_code": DeleteObject_success_status_code,
- "DeleteObjects_empty_input": DeleteObjects_empty_input,
- "DeleteObjects_non_existing_objects": DeleteObjects_non_existing_objects,
- "DeleteObjects_success": DeleteObjects_success,
- "CopyObject_non_existing_dst_bucket": CopyObject_non_existing_dst_bucket,
- "CopyObject_not_owned_source_bucket": CopyObject_not_owned_source_bucket,
- "CopyObject_copy_to_itself": CopyObject_copy_to_itself,
- "CopyObject_to_itself_with_new_metadata": CopyObject_to_itself_with_new_metadata,
- "CopyObject_success": CopyObject_success,
- "PutObjectTagging_non_existing_object": PutObjectTagging_non_existing_object,
- "PutObjectTagging_long_tags": PutObjectTagging_long_tags,
- "PutObjectTagging_success": PutObjectTagging_success,
- "GetObjectTagging_non_existing_object": GetObjectTagging_non_existing_object,
- "GetObjectTagging_success": GetObjectTagging_success,
- "DeleteObjectTagging_non_existing_object": DeleteObjectTagging_non_existing_object,
- "DeleteObjectTagging_success_status": DeleteObjectTagging_success_status,
- "DeleteObjectTagging_success": DeleteObjectTagging_success,
- "CreateMultipartUpload_non_existing_bucket": CreateMultipartUpload_non_existing_bucket,
- "CreateMultipartUpload_success": CreateMultipartUpload_success,
- "UploadPart_non_existing_bucket": UploadPart_non_existing_bucket,
- "UploadPart_invalid_part_number": UploadPart_invalid_part_number,
- "UploadPart_non_existing_key": UploadPart_non_existing_key,
- "UploadPart_non_existing_mp_upload": UploadPart_non_existing_mp_upload,
- "UploadPart_success": UploadPart_success,
- "UploadPartCopy_non_existing_bucket": UploadPartCopy_non_existing_bucket,
- "UploadPartCopy_incorrect_uploadId": UploadPartCopy_incorrect_uploadId,
- "UploadPartCopy_incorrect_object_key": UploadPartCopy_incorrect_object_key,
- "UploadPartCopy_invalid_part_number": UploadPartCopy_invalid_part_number,
- "UploadPartCopy_invalid_copy_source": UploadPartCopy_invalid_copy_source,
- "UploadPartCopy_non_existing_source_bucket": UploadPartCopy_non_existing_source_bucket,
- "UploadPartCopy_non_existing_source_object_key": UploadPartCopy_non_existing_source_object_key,
- "UploadPartCopy_success": UploadPartCopy_success,
- "UploadPartCopy_by_range_invalid_range": UploadPartCopy_by_range_invalid_range,
- "UploadPartCopy_greater_range_than_obj_size": UploadPartCopy_greater_range_than_obj_size,
- "UploadPartCopy_by_range_success": UploadPartCopy_by_range_success,
- "ListParts_incorrect_uploadId": ListParts_incorrect_uploadId,
- "ListParts_incorrect_object_key": ListParts_incorrect_object_key,
- "ListParts_success": ListParts_success,
- "ListMultipartUploads_non_existing_bucket": ListMultipartUploads_non_existing_bucket,
- "ListMultipartUploads_empty_result": ListMultipartUploads_empty_result,
- "ListMultipartUploads_invalid_max_uploads": ListMultipartUploads_invalid_max_uploads,
- "ListMultipartUploads_max_uploads": ListMultipartUploads_max_uploads,
- "ListMultipartUploads_incorrect_next_key_marker": ListMultipartUploads_incorrect_next_key_marker,
- "ListMultipartUploads_ignore_upload_id_marker": ListMultipartUploads_ignore_upload_id_marker,
- "ListMultipartUploads_success": ListMultipartUploads_success,
- "AbortMultipartUpload_non_existing_bucket": AbortMultipartUpload_non_existing_bucket,
- "AbortMultipartUpload_incorrect_uploadId": AbortMultipartUpload_incorrect_uploadId,
- "AbortMultipartUpload_incorrect_object_key": AbortMultipartUpload_incorrect_object_key,
- "AbortMultipartUpload_success": AbortMultipartUpload_success,
- "AbortMultipartUpload_success_status_code": AbortMultipartUpload_success_status_code,
- "CompletedMultipartUpload_non_existing_bucket": CompletedMultipartUpload_non_existing_bucket,
- "CompleteMultipartUpload_invalid_part_number": CompleteMultipartUpload_invalid_part_number,
- "CompleteMultipartUpload_invalid_ETag": CompleteMultipartUpload_invalid_ETag,
- "CompleteMultipartUpload_success": CompleteMultipartUpload_success,
- "PutBucketAcl_non_existing_bucket": PutBucketAcl_non_existing_bucket,
- "PutBucketAcl_invalid_acl_canned_and_acp": PutBucketAcl_invalid_acl_canned_and_acp,
- "PutBucketAcl_invalid_acl_canned_and_grants": PutBucketAcl_invalid_acl_canned_and_grants,
- "PutBucketAcl_invalid_acl_acp_and_grants": PutBucketAcl_invalid_acl_acp_and_grants,
- "PutBucketAcl_invalid_owner": PutBucketAcl_invalid_owner,
- "PutBucketAcl_success_access_denied": PutBucketAcl_success_access_denied,
- "PutBucketAcl_success_grants": PutBucketAcl_success_grants,
- "PutBucketAcl_success_canned_acl": PutBucketAcl_success_canned_acl,
- "PutBucketAcl_success_acp": PutBucketAcl_success_acp,
- "GetBucketAcl_non_existing_bucket": GetBucketAcl_non_existing_bucket,
- "GetBucketAcl_access_denied": GetBucketAcl_access_denied,
- "GetBucketAcl_success": GetBucketAcl_success,
- "PutBucketPolicy_non_existing_bucket": PutBucketPolicy_non_existing_bucket,
- "PutBucketPolicy_invalid_effect": PutBucketPolicy_invalid_effect,
- "PutBucketPolicy_empty_actions_string": PutBucketPolicy_empty_actions_string,
- "PutBucketPolicy_empty_actions_array": PutBucketPolicy_empty_actions_array,
- "PutBucketPolicy_invalid_action": PutBucketPolicy_invalid_action,
- "PutBucketPolicy_unsupported_action": PutBucketPolicy_unsupported_action,
- "PutBucketPolicy_incorrect_action_wildcard_usage": PutBucketPolicy_incorrect_action_wildcard_usage,
- "PutBucketPolicy_empty_principals_string": PutBucketPolicy_empty_principals_string,
- "PutBucketPolicy_empty_principals_array": PutBucketPolicy_empty_principals_array,
- "PutBucketPolicy_principals_incorrect_wildcard_usage": PutBucketPolicy_principals_incorrect_wildcard_usage,
- "PutBucketPolicy_non_existing_principals": PutBucketPolicy_non_existing_principals,
- "PutBucketPolicy_empty_resources_string": PutBucketPolicy_empty_resources_string,
- "PutBucketPolicy_empty_resources_array": PutBucketPolicy_empty_resources_array,
- "PutBucketPolicy_invalid_resource_prefix": PutBucketPolicy_invalid_resource_prefix,
- "PutBucketPolicy_invalid_resource_with_starting_slash": PutBucketPolicy_invalid_resource_with_starting_slash,
- "PutBucketPolicy_duplicate_resource": PutBucketPolicy_duplicate_resource,
- "PutBucketPolicy_incorrect_bucket_name": PutBucketPolicy_incorrect_bucket_name,
- "PutBucketPolicy_object_action_on_bucket_resource": PutBucketPolicy_object_action_on_bucket_resource,
- "PutBucketPolicy_bucket_action_on_object_resource": PutBucketPolicy_bucket_action_on_object_resource,
- "PutBucketPolicy_success": PutBucketPolicy_success,
- "GetBucketPolicy_non_existing_bucket": GetBucketPolicy_non_existing_bucket,
- "GetBucketPolicy_default_empty_policy": GetBucketPolicy_default_empty_policy,
- "GetBucketPolicy_success": GetBucketPolicy_success,
- "DeleteBucketPolicy_non_existing_bucket": DeleteBucketPolicy_non_existing_bucket,
- "DeleteBucketPolicy_remove_before_setting": DeleteBucketPolicy_remove_before_setting,
- "DeleteBucketPolicy_success": DeleteBucketPolicy_success,
- "PutObject_overwrite_dir_obj": PutObject_overwrite_dir_obj,
- "PutObject_overwrite_file_obj": PutObject_overwrite_file_obj,
- "PutObject_dir_obj_with_data": PutObject_dir_obj_with_data,
- "CreateMultipartUpload_dir_obj": CreateMultipartUpload_dir_obj,
- "IAM_user_access_denied": IAM_user_access_denied,
- "IAM_userplus_access_denied": IAM_userplus_access_denied,
- "IAM_userplus_CreateBucket": IAM_userplus_CreateBucket,
- "IAM_admin_ChangeBucketOwner": IAM_admin_ChangeBucketOwner,
+ "Authentication_empty_auth_header": Authentication_empty_auth_header,
+ "Authentication_invalid_auth_header": Authentication_invalid_auth_header,
+ "Authentication_unsupported_signature_version": Authentication_unsupported_signature_version,
+ "Authentication_malformed_credentials": Authentication_malformed_credentials,
+ "Authentication_malformed_credentials_invalid_parts": Authentication_malformed_credentials_invalid_parts,
+ "Authentication_credentials_terminated_string": Authentication_credentials_terminated_string,
+ "Authentication_credentials_incorrect_service": Authentication_credentials_incorrect_service,
+ "Authentication_credentials_incorrect_region": Authentication_credentials_incorrect_region,
+ "Authentication_credentials_invalid_date": Authentication_credentials_invalid_date,
+ "Authentication_credentials_future_date": Authentication_credentials_future_date,
+ "Authentication_credentials_past_date": Authentication_credentials_past_date,
+ "Authentication_credentials_non_existing_access_key": Authentication_credentials_non_existing_access_key,
+ "Authentication_invalid_signed_headers": Authentication_invalid_signed_headers,
+ "Authentication_missing_date_header": Authentication_missing_date_header,
+ "Authentication_invalid_date_header": Authentication_invalid_date_header,
+ "Authentication_date_mismatch": Authentication_date_mismatch,
+ "Authentication_incorrect_payload_hash": Authentication_incorrect_payload_hash,
+ "Authentication_incorrect_md5": Authentication_incorrect_md5,
+ "Authentication_signature_error_incorrect_secret_key": Authentication_signature_error_incorrect_secret_key,
+ "PresignedAuth_missing_algo_query_param": PresignedAuth_missing_algo_query_param,
+ "PresignedAuth_unsupported_algorithm": PresignedAuth_unsupported_algorithm,
+ "PresignedAuth_missing_credentials_query_param": PresignedAuth_missing_credentials_query_param,
+ "PresignedAuth_malformed_creds_invalid_parts": PresignedAuth_malformed_creds_invalid_parts,
+ "PresignedAuth_creds_invalid_terminator": PresignedAuth_creds_invalid_terminator,
+ "PresignedAuth_creds_incorrect_service": PresignedAuth_creds_incorrect_service,
+ "PresignedAuth_creds_incorrect_region": PresignedAuth_creds_incorrect_region,
+ "PresignedAuth_creds_invalid_date": PresignedAuth_creds_invalid_date,
+ "PresignedAuth_missing_date_query": PresignedAuth_missing_date_query,
+ "PresignedAuth_dates_mismatch": PresignedAuth_dates_mismatch,
+ "PresignedAuth_non_existing_access_key_id": PresignedAuth_non_existing_access_key_id,
+ "PresignedAuth_missing_signed_headers_query_param": PresignedAuth_missing_signed_headers_query_param,
+ "PresignedAuth_missing_expiration_query_param": PresignedAuth_missing_expiration_query_param,
+ "PresignedAuth_invalid_expiration_query_param": PresignedAuth_invalid_expiration_query_param,
+ "PresignedAuth_negative_expiration_query_param": PresignedAuth_negative_expiration_query_param,
+ "PresignedAuth_exceeding_expiration_query_param": PresignedAuth_exceeding_expiration_query_param,
+ "PresignedAuth_expired_request": PresignedAuth_expired_request,
+ "PresignedAuth_incorrect_secret_key": PresignedAuth_incorrect_secret_key,
+ "PresignedAuth_PutObject_success": PresignedAuth_PutObject_success,
+ "PresignedAuth_Put_GetObject_with_data": PresignedAuth_Put_GetObject_with_data,
+ "PresignedAuth_Put_GetObject_with_UTF8_chars": PresignedAuth_Put_GetObject_with_UTF8_chars,
+ "PresignedAuth_UploadPart": PresignedAuth_UploadPart,
+ "CreateBucket_invalid_bucket_name": CreateBucket_invalid_bucket_name,
+ "CreateBucket_existing_bucket": CreateBucket_existing_bucket,
+ "CreateBucket_as_user": CreateBucket_as_user,
+ "CreateDeleteBucket_success": CreateDeleteBucket_success,
+ "CreateBucket_default_acl": CreateBucket_default_acl,
+ "CreateBucket_non_default_acl": CreateBucket_non_default_acl,
+ "HeadBucket_non_existing_bucket": HeadBucket_non_existing_bucket,
+ "HeadBucket_success": HeadBucket_success,
+ "ListBuckets_as_user": ListBuckets_as_user,
+ "ListBuckets_as_admin": ListBuckets_as_admin,
+ "ListBuckets_success": ListBuckets_success,
+ "DeleteBucket_non_existing_bucket": DeleteBucket_non_existing_bucket,
+ "DeleteBucket_non_empty_bucket": DeleteBucket_non_empty_bucket,
+ "DeleteBucket_success_status_code": DeleteBucket_success_status_code,
+ "PutBucketTagging_non_existing_bucket": PutBucketTagging_non_existing_bucket,
+ "PutBucketTagging_long_tags": PutBucketTagging_long_tags,
+ "PutBucketTagging_success": PutBucketTagging_success,
+ "GetBucketTagging_non_existing_bucket": GetBucketTagging_non_existing_bucket,
+ "GetBucketTagging_success": GetBucketTagging_success,
+ "DeleteBucketTagging_non_existing_object": DeleteBucketTagging_non_existing_object,
+ "DeleteBucketTagging_success_status": DeleteBucketTagging_success_status,
+ "DeleteBucketTagging_success": DeleteBucketTagging_success,
+ "PutObject_non_existing_bucket": PutObject_non_existing_bucket,
+ "PutObject_special_chars": PutObject_special_chars,
+ "PutObject_invalid_long_tags": PutObject_invalid_long_tags,
+ "PutObject_success": PutObject_success,
+ "HeadObject_non_existing_object": HeadObject_non_existing_object,
+ "HeadObject_success": HeadObject_success,
+ "GetObject_non_existing_key": GetObject_non_existing_key,
+ "GetObject_invalid_ranges": GetObject_invalid_ranges,
+ "GetObject_with_meta": GetObject_with_meta,
+ "GetObject_success": GetObject_success,
+ "GetObject_by_range_success": GetObject_by_range_success,
+ "ListObjects_non_existing_bucket": ListObjects_non_existing_bucket,
+ "ListObjects_with_prefix": ListObjects_with_prefix,
+ "ListObject_truncated": ListObject_truncated,
+ "ListObjects_invalid_max_keys": ListObjects_invalid_max_keys,
+ "ListObjects_max_keys_0": ListObjects_max_keys_0,
+ "ListObjects_delimiter": ListObjects_delimiter,
+ "ListObjects_max_keys_none": ListObjects_max_keys_none,
+ "ListObjects_marker_not_from_obj_list": ListObjects_marker_not_from_obj_list,
+ "ListObjectsV2_start_after": ListObjectsV2_start_after,
+ "ListObjectsV2_both_start_after_and_continuation_token": ListObjectsV2_both_start_after_and_continuation_token,
+ "ListObjectsV2_start_after_not_in_list": ListObjectsV2_start_after_not_in_list,
+ "ListObjectsV2_start_after_empty_result": ListObjectsV2_start_after_empty_result,
+ "DeleteObject_non_existing_object": DeleteObject_non_existing_object,
+ "DeleteObject_success": DeleteObject_success,
+ "DeleteObject_success_status_code": DeleteObject_success_status_code,
+ "DeleteObjects_empty_input": DeleteObjects_empty_input,
+ "DeleteObjects_non_existing_objects": DeleteObjects_non_existing_objects,
+ "DeleteObjects_success": DeleteObjects_success,
+ "CopyObject_non_existing_dst_bucket": CopyObject_non_existing_dst_bucket,
+ "CopyObject_not_owned_source_bucket": CopyObject_not_owned_source_bucket,
+ "CopyObject_copy_to_itself": CopyObject_copy_to_itself,
+ "CopyObject_to_itself_with_new_metadata": CopyObject_to_itself_with_new_metadata,
+ "CopyObject_success": CopyObject_success,
+ "PutObjectTagging_non_existing_object": PutObjectTagging_non_existing_object,
+ "PutObjectTagging_long_tags": PutObjectTagging_long_tags,
+ "PutObjectTagging_success": PutObjectTagging_success,
+ "GetObjectTagging_non_existing_object": GetObjectTagging_non_existing_object,
+ "GetObjectTagging_success": GetObjectTagging_success,
+ "DeleteObjectTagging_non_existing_object": DeleteObjectTagging_non_existing_object,
+ "DeleteObjectTagging_success_status": DeleteObjectTagging_success_status,
+ "DeleteObjectTagging_success": DeleteObjectTagging_success,
+ "CreateMultipartUpload_non_existing_bucket": CreateMultipartUpload_non_existing_bucket,
+ "CreateMultipartUpload_success": CreateMultipartUpload_success,
+ "UploadPart_non_existing_bucket": UploadPart_non_existing_bucket,
+ "UploadPart_invalid_part_number": UploadPart_invalid_part_number,
+ "UploadPart_non_existing_key": UploadPart_non_existing_key,
+ "UploadPart_non_existing_mp_upload": UploadPart_non_existing_mp_upload,
+ "UploadPart_success": UploadPart_success,
+ "UploadPartCopy_non_existing_bucket": UploadPartCopy_non_existing_bucket,
+ "UploadPartCopy_incorrect_uploadId": UploadPartCopy_incorrect_uploadId,
+ "UploadPartCopy_incorrect_object_key": UploadPartCopy_incorrect_object_key,
+ "UploadPartCopy_invalid_part_number": UploadPartCopy_invalid_part_number,
+ "UploadPartCopy_invalid_copy_source": UploadPartCopy_invalid_copy_source,
+ "UploadPartCopy_non_existing_source_bucket": UploadPartCopy_non_existing_source_bucket,
+ "UploadPartCopy_non_existing_source_object_key": UploadPartCopy_non_existing_source_object_key,
+ "UploadPartCopy_success": UploadPartCopy_success,
+ "UploadPartCopy_by_range_invalid_range": UploadPartCopy_by_range_invalid_range,
+ "UploadPartCopy_greater_range_than_obj_size": UploadPartCopy_greater_range_than_obj_size,
+ "UploadPartCopy_by_range_success": UploadPartCopy_by_range_success,
+ "ListParts_incorrect_uploadId": ListParts_incorrect_uploadId,
+ "ListParts_incorrect_object_key": ListParts_incorrect_object_key,
+ "ListParts_success": ListParts_success,
+ "ListMultipartUploads_non_existing_bucket": ListMultipartUploads_non_existing_bucket,
+ "ListMultipartUploads_empty_result": ListMultipartUploads_empty_result,
+ "ListMultipartUploads_invalid_max_uploads": ListMultipartUploads_invalid_max_uploads,
+ "ListMultipartUploads_max_uploads": ListMultipartUploads_max_uploads,
+ "ListMultipartUploads_incorrect_next_key_marker": ListMultipartUploads_incorrect_next_key_marker,
+ "ListMultipartUploads_ignore_upload_id_marker": ListMultipartUploads_ignore_upload_id_marker,
+ "ListMultipartUploads_success": ListMultipartUploads_success,
+ "AbortMultipartUpload_non_existing_bucket": AbortMultipartUpload_non_existing_bucket,
+ "AbortMultipartUpload_incorrect_uploadId": AbortMultipartUpload_incorrect_uploadId,
+ "AbortMultipartUpload_incorrect_object_key": AbortMultipartUpload_incorrect_object_key,
+ "AbortMultipartUpload_success": AbortMultipartUpload_success,
+ "AbortMultipartUpload_success_status_code": AbortMultipartUpload_success_status_code,
+ "CompletedMultipartUpload_non_existing_bucket": CompletedMultipartUpload_non_existing_bucket,
+ "CompleteMultipartUpload_invalid_part_number": CompleteMultipartUpload_invalid_part_number,
+ "CompleteMultipartUpload_invalid_ETag": CompleteMultipartUpload_invalid_ETag,
+ "CompleteMultipartUpload_success": CompleteMultipartUpload_success,
+ "PutBucketAcl_non_existing_bucket": PutBucketAcl_non_existing_bucket,
+ "PutBucketAcl_invalid_acl_canned_and_acp": PutBucketAcl_invalid_acl_canned_and_acp,
+ "PutBucketAcl_invalid_acl_canned_and_grants": PutBucketAcl_invalid_acl_canned_and_grants,
+ "PutBucketAcl_invalid_acl_acp_and_grants": PutBucketAcl_invalid_acl_acp_and_grants,
+ "PutBucketAcl_invalid_owner": PutBucketAcl_invalid_owner,
+ "PutBucketAcl_success_access_denied": PutBucketAcl_success_access_denied,
+ "PutBucketAcl_success_grants": PutBucketAcl_success_grants,
+ "PutBucketAcl_success_canned_acl": PutBucketAcl_success_canned_acl,
+ "PutBucketAcl_success_acp": PutBucketAcl_success_acp,
+ "GetBucketAcl_non_existing_bucket": GetBucketAcl_non_existing_bucket,
+ "GetBucketAcl_access_denied": GetBucketAcl_access_denied,
+ "GetBucketAcl_success": GetBucketAcl_success,
+ "PutBucketPolicy_non_existing_bucket": PutBucketPolicy_non_existing_bucket,
+ "PutBucketPolicy_invalid_effect": PutBucketPolicy_invalid_effect,
+ "PutBucketPolicy_empty_actions_string": PutBucketPolicy_empty_actions_string,
+ "PutBucketPolicy_empty_actions_array": PutBucketPolicy_empty_actions_array,
+ "PutBucketPolicy_invalid_action": PutBucketPolicy_invalid_action,
+ "PutBucketPolicy_unsupported_action": PutBucketPolicy_unsupported_action,
+ "PutBucketPolicy_incorrect_action_wildcard_usage": PutBucketPolicy_incorrect_action_wildcard_usage,
+ "PutBucketPolicy_empty_principals_string": PutBucketPolicy_empty_principals_string,
+ "PutBucketPolicy_empty_principals_array": PutBucketPolicy_empty_principals_array,
+ "PutBucketPolicy_principals_incorrect_wildcard_usage": PutBucketPolicy_principals_incorrect_wildcard_usage,
+ "PutBucketPolicy_non_existing_principals": PutBucketPolicy_non_existing_principals,
+ "PutBucketPolicy_empty_resources_string": PutBucketPolicy_empty_resources_string,
+ "PutBucketPolicy_empty_resources_array": PutBucketPolicy_empty_resources_array,
+ "PutBucketPolicy_invalid_resource_prefix": PutBucketPolicy_invalid_resource_prefix,
+ "PutBucketPolicy_invalid_resource_with_starting_slash": PutBucketPolicy_invalid_resource_with_starting_slash,
+ "PutBucketPolicy_duplicate_resource": PutBucketPolicy_duplicate_resource,
+ "PutBucketPolicy_incorrect_bucket_name": PutBucketPolicy_incorrect_bucket_name,
+ "PutBucketPolicy_object_action_on_bucket_resource": PutBucketPolicy_object_action_on_bucket_resource,
+ "PutBucketPolicy_bucket_action_on_object_resource": PutBucketPolicy_bucket_action_on_object_resource,
+ "PutBucketPolicy_success": PutBucketPolicy_success,
+ "GetBucketPolicy_non_existing_bucket": GetBucketPolicy_non_existing_bucket,
+ "GetBucketPolicy_default_empty_policy": GetBucketPolicy_default_empty_policy,
+ "GetBucketPolicy_success": GetBucketPolicy_success,
+ "DeleteBucketPolicy_non_existing_bucket": DeleteBucketPolicy_non_existing_bucket,
+ "DeleteBucketPolicy_remove_before_setting": DeleteBucketPolicy_remove_before_setting,
+ "DeleteBucketPolicy_success": DeleteBucketPolicy_success,
+ "PutObjectLockConfiguration_non_existing_bucket": PutObjectLockConfiguration_non_existing_bucket,
+ "PutObjectLockConfiguration_empty_config": PutObjectLockConfiguration_empty_config,
+ "PutObjectLockConfiguration_both_years_and_days": PutObjectLockConfiguration_both_years_and_days,
+ "PutObjectLockConfiguration_success": PutObjectLockConfiguration_success,
+ "GetObjectLockConfiguration_non_existing_bucket": GetObjectLockConfiguration_non_existing_bucket,
+ "GetObjectLockConfiguration_unset_config": GetObjectLockConfiguration_unset_config,
+ "GetObjectLockConfiguration_success": GetObjectLockConfiguration_success,
+ "PutObjectRetention_non_existing_bucket": PutObjectRetention_non_existing_bucket,
+ "PutObjectRetention_non_existing_object": PutObjectRetention_non_existing_object,
+ "PutObjectRetention_unset_bucket_object_lock_config": PutObjectRetention_unset_bucket_object_lock_config,
+ "PutObjectRetention_disabled_bucket_object_lock_config": PutObjectRetention_disabled_bucket_object_lock_config,
+ "PutObjectRetention_expired_retain_until_date": PutObjectRetention_expired_retain_until_date,
+ "PutObjectRetention_success": PutObjectRetention_success,
+ "GetObjectRetention_non_existing_bucket": GetObjectRetention_non_existing_bucket,
+ "GetObjectRetention_non_existing_object": GetObjectRetention_non_existing_object,
+ "GetObjectRetention_unset_config": GetObjectRetention_unset_config,
+ "GetObjectRetention_success": GetObjectRetention_success,
+ "PutObjectLegalHold_non_existing_bucket": PutObjectLegalHold_non_existing_bucket,
+ "PutObjectLegalHold_non_existing_object": PutObjectLegalHold_non_existing_object,
+ "PutObjectLegalHold_invalid_body": PutObjectLegalHold_invalid_body,
+ "PutObjectLegalHold_unset_bucket_object_lock_config": PutObjectLegalHold_unset_bucket_object_lock_config,
+ "PutObjectLegalHold_disabled_bucket_object_lock_config": PutObjectLegalHold_disabled_bucket_object_lock_config,
+ "PutObjectLegalHold_success": PutObjectLegalHold_success,
+ "GetObjectLegalHold_non_existing_bucket": GetObjectLegalHold_non_existing_bucket,
+ "GetObjectLegalHold_non_existing_object": GetObjectLegalHold_non_existing_object,
+ "GetObjectLegalHold_unset_config": GetObjectLegalHold_unset_config,
+ "GetObjectLegalHold_success": GetObjectLegalHold_success,
+ "WORMProtection_bucket_object_lock_configuration_compliance_mode": WORMProtection_bucket_object_lock_configuration_compliance_mode,
+ "WORMProtection_bucket_object_lock_governance_root_overwrite": WORMProtection_bucket_object_lock_governance_root_overwrite,
+ "WORMProtection_object_lock_retention_compliance_root_access_denied": WORMProtection_object_lock_retention_compliance_root_access_denied,
+ "WORMProtection_object_lock_retention_governance_root_overwrite": WORMProtection_object_lock_retention_governance_root_overwrite,
+ "WORMProtection_object_lock_retention_governance_user_access_denied": WORMProtection_object_lock_retention_governance_user_access_denied,
+ "WORMProtection_object_lock_legal_hold_user_access_denied": WORMProtection_object_lock_legal_hold_user_access_denied,
+ "WORMProtection_object_lock_legal_hold_root_overwrite": WORMProtection_object_lock_legal_hold_root_overwrite,
+ "PutObject_overwrite_dir_obj": PutObject_overwrite_dir_obj,
+ "PutObject_overwrite_file_obj": PutObject_overwrite_file_obj,
+ "PutObject_dir_obj_with_data": PutObject_dir_obj_with_data,
+ "CreateMultipartUpload_dir_obj": CreateMultipartUpload_dir_obj,
+ "IAM_user_access_denied": IAM_user_access_denied,
+ "IAM_userplus_access_denied": IAM_userplus_access_denied,
+ "IAM_userplus_CreateBucket": IAM_userplus_CreateBucket,
+ "IAM_admin_ChangeBucketOwner": IAM_admin_ChangeBucketOwner,
}
}
diff --git a/tests/integration/tests.go b/tests/integration/tests.go
index f1b94109..0b54add9 100644
--- a/tests/integration/tests.go
+++ b/tests/integration/tests.go
@@ -5805,6 +5805,1017 @@ func DeleteBucketPolicy_success(s *S3Conf) error {
})
}
+// Object lock tests
+func PutObjectLockConfiguration_non_existing_bucket(s *S3Conf) error {
+ testName := "PutObjectLockConfiguration_non_existing_bucket"
+ return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
+ ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
+ _, err := s3client.PutObjectLockConfiguration(ctx, &s3.PutObjectLockConfigurationInput{
+ Bucket: getPtr(getBucketName()),
+ })
+ cancel()
+ if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrNoSuchBucket)); err != nil {
+ return err
+ }
+
+ return nil
+ })
+}
+
+func PutObjectLockConfiguration_empty_config(s *S3Conf) error {
+ testName := "PutObjectLockConfiguration_empty_config"
+ return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
+ ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
+ _, err := s3client.PutObjectLockConfiguration(ctx, &s3.PutObjectLockConfigurationInput{
+ Bucket: &bucket,
+ })
+ cancel()
+ if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrInvalidRequest)); err != nil {
+ return err
+ }
+ return nil
+ })
+}
+
+func PutObjectLockConfiguration_both_years_and_days(s *S3Conf) error {
+ testName := "PutObjectLockConfiguration_both_years_and_days"
+ return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
+ var days, years int32 = 12, 24
+ ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
+ _, err := s3client.PutObjectLockConfiguration(ctx, &s3.PutObjectLockConfigurationInput{
+ Bucket: &bucket,
+ ObjectLockConfiguration: &types.ObjectLockConfiguration{
+ ObjectLockEnabled: types.ObjectLockEnabledEnabled,
+ Rule: &types.ObjectLockRule{
+ DefaultRetention: &types.DefaultRetention{
+ Days: &days,
+ Years: &years,
+ },
+ },
+ },
+ })
+ cancel()
+ if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrInvalidRequest)); err != nil {
+ return err
+ }
+ return nil
+ })
+}
+
+func PutObjectLockConfiguration_success(s *S3Conf) error {
+ testName := "PutObjectLockConfiguration_success"
+ return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
+ ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
+ _, err := s3client.PutObjectLockConfiguration(ctx, &s3.PutObjectLockConfigurationInput{
+ Bucket: &bucket,
+ ObjectLockConfiguration: &types.ObjectLockConfiguration{
+ ObjectLockEnabled: types.ObjectLockEnabledEnabled,
+ },
+ })
+ cancel()
+ if err != nil {
+ return err
+ }
+ return nil
+ })
+}
+
+func GetObjectLockConfiguration_non_existing_bucket(s *S3Conf) error {
+ testName := "GetObjectLockConfiguration_non_existing_bucket"
+ return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
+ ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
+ _, err := s3client.GetObjectLockConfiguration(ctx, &s3.GetObjectLockConfigurationInput{
+ Bucket: getPtr(getBucketName()),
+ })
+ cancel()
+ if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrNoSuchBucket)); err != nil {
+ return err
+ }
+
+ return nil
+ })
+}
+
+func GetObjectLockConfiguration_unset_config(s *S3Conf) error {
+ testName := "GetObjectLockConfiguration_unset_config"
+ return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
+ ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
+ _, err := s3client.GetObjectLockConfiguration(ctx, &s3.GetObjectLockConfigurationInput{
+ Bucket: &bucket,
+ })
+ cancel()
+ if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrObjectLockConfigurationNotFound)); err != nil {
+ return err
+ }
+
+ return nil
+ })
+}
+
+func GetObjectLockConfiguration_success(s *S3Conf) error {
+ testName := "GetObjectLockConfiguration_success"
+ return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
+ var days int32 = 20
+ config := types.ObjectLockConfiguration{
+ ObjectLockEnabled: types.ObjectLockEnabledEnabled,
+ Rule: &types.ObjectLockRule{
+ DefaultRetention: &types.DefaultRetention{
+ Mode: types.ObjectLockRetentionModeCompliance,
+ Days: &days,
+ },
+ },
+ }
+ ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
+ _, err := s3client.PutObjectLockConfiguration(ctx, &s3.PutObjectLockConfigurationInput{
+ Bucket: &bucket,
+ ObjectLockConfiguration: &config,
+ })
+ cancel()
+ if err != nil {
+ return err
+ }
+
+ ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
+ resp, err := s3client.GetObjectLockConfiguration(ctx, &s3.GetObjectLockConfigurationInput{
+ Bucket: &bucket,
+ })
+ cancel()
+ if err != nil {
+ return err
+ }
+
+ if resp.ObjectLockConfiguration == nil {
+ return fmt.Errorf("got nil object lock configuration")
+ }
+
+ respConfig := resp.ObjectLockConfiguration
+
+ if respConfig.ObjectLockEnabled != config.ObjectLockEnabled {
+ return fmt.Errorf("expected lock status to be %v, instead got %v", config.ObjectLockEnabled, respConfig.ObjectLockEnabled)
+ }
+ if *respConfig.Rule.DefaultRetention.Days != *config.Rule.DefaultRetention.Days {
+ return fmt.Errorf("expected lock config days to be %v, instead got %v", *config.Rule.DefaultRetention.Days, *respConfig.Rule.DefaultRetention.Days)
+ }
+ if respConfig.Rule.DefaultRetention.Mode != config.Rule.DefaultRetention.Mode {
+ return fmt.Errorf("expected lock config mode to be %v, instead got %v", config.Rule.DefaultRetention.Mode, respConfig.Rule.DefaultRetention.Mode)
+ }
+
+ return nil
+ })
+}
+
+func PutObjectRetention_non_existing_bucket(s *S3Conf) error {
+ testName := "PutObjectRetention_non_existing_bucket"
+ return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
+ ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
+ _, err := s3client.PutObjectRetention(ctx, &s3.PutObjectRetentionInput{
+ Bucket: getPtr(getBucketName()),
+ Key: getPtr("my-obj"),
+ })
+ cancel()
+ if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrNoSuchBucket)); err != nil {
+ return err
+ }
+
+ return nil
+ })
+}
+
+func PutObjectRetention_non_existing_object(s *S3Conf) error {
+ testName := "PutObjectRetention_non_existing_object"
+ return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
+ if err := changeBucketObjectLockStatus(s3client, bucket, true); err != nil {
+ return err
+ }
+
+ date := time.Now().Add(time.Hour * 3)
+ ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
+ _, err := s3client.PutObjectRetention(ctx, &s3.PutObjectRetentionInput{
+ Bucket: &bucket,
+ Key: getPtr("my-obj"),
+ Retention: &types.ObjectLockRetention{
+ Mode: types.ObjectLockRetentionModeCompliance,
+ RetainUntilDate: &date,
+ },
+ })
+ cancel()
+ if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrNoSuchKey)); err != nil {
+ return err
+ }
+
+ return nil
+ })
+}
+
+func PutObjectRetention_unset_bucket_object_lock_config(s *S3Conf) error {
+ testName := "PutObjectRetention_unset_bucket_object_lock_config"
+ return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
+ date := time.Now().Add(time.Hour * 3)
+ key := "my-obj"
+
+ if err := putObjects(s3client, []string{key}, bucket); err != nil {
+ return err
+ }
+
+ ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
+ _, err := s3client.PutObjectRetention(ctx, &s3.PutObjectRetentionInput{
+ Bucket: &bucket,
+ Key: &key,
+ Retention: &types.ObjectLockRetention{
+ Mode: types.ObjectLockRetentionModeCompliance,
+ RetainUntilDate: &date,
+ },
+ })
+ cancel()
+ if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrInvalidBucketObjectLockConfiguration)); err != nil {
+ return err
+ }
+
+ return nil
+ })
+}
+
+func PutObjectRetention_disabled_bucket_object_lock_config(s *S3Conf) error {
+ testName := "PutObjectRetention_disabled_bucket_object_lock_config"
+ return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
+ ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
+ _, err := s3client.PutObjectLockConfiguration(ctx, &s3.PutObjectLockConfigurationInput{
+ Bucket: &bucket,
+ ObjectLockConfiguration: &types.ObjectLockConfiguration{},
+ })
+ cancel()
+ if err != nil {
+ return err
+ }
+
+ date := time.Now().Add(time.Hour * 3)
+ key := "my-obj"
+
+ if err := putObjects(s3client, []string{key}, bucket); err != nil {
+ return err
+ }
+
+ ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
+ _, err = s3client.PutObjectRetention(ctx, &s3.PutObjectRetentionInput{
+ Bucket: &bucket,
+ Key: &key,
+ Retention: &types.ObjectLockRetention{
+ Mode: types.ObjectLockRetentionModeCompliance,
+ RetainUntilDate: &date,
+ },
+ })
+ cancel()
+ if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrInvalidBucketObjectLockConfiguration)); err != nil {
+ return err
+ }
+
+ return nil
+ })
+}
+
+func PutObjectRetention_expired_retain_until_date(s *S3Conf) error {
+ testName := "PutObjectRetention_expired_retain_until_date"
+ return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
+ if err := changeBucketObjectLockStatus(s3client, bucket, true); err != nil {
+ return err
+ }
+
+ date := time.Now().Add(-time.Hour * 3)
+
+ ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
+ _, err := s3client.PutObjectRetention(ctx, &s3.PutObjectRetentionInput{
+ Bucket: &bucket,
+ Key: getPtr("my-obj"),
+ Retention: &types.ObjectLockRetention{
+ Mode: types.ObjectLockRetentionModeCompliance,
+ RetainUntilDate: &date,
+ },
+ })
+ cancel()
+ if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrPastObjectLockRetainDate)); err != nil {
+ return err
+ }
+
+ return nil
+ })
+}
+
+func PutObjectRetention_success(s *S3Conf) error {
+ testName := "PutObjectRetention_success"
+ return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
+ if err := changeBucketObjectLockStatus(s3client, bucket, true); err != nil {
+ return err
+ }
+
+ date := time.Now().Add(time.Hour * 3)
+ key := "my-obj"
+
+ if err := putObjects(s3client, []string{key}, bucket); err != nil {
+ return err
+ }
+
+ ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
+ _, err := s3client.PutObjectRetention(ctx, &s3.PutObjectRetentionInput{
+ Bucket: &bucket,
+ Key: &key,
+ Retention: &types.ObjectLockRetention{
+ Mode: types.ObjectLockRetentionModeCompliance,
+ RetainUntilDate: &date,
+ },
+ })
+ cancel()
+ if err != nil {
+ return err
+ }
+
+ if err := changeBucketObjectLockStatus(s3client, bucket, false); err != nil {
+ return err
+ }
+
+ return nil
+ })
+}
+
+func GetObjectRetention_non_existing_bucket(s *S3Conf) error {
+ testName := "GetObjectRetention_non_existing_bucket"
+ return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
+ ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
+ _, err := s3client.GetObjectRetention(ctx, &s3.GetObjectRetentionInput{
+ Bucket: getPtr(getBucketName()),
+ Key: getPtr("my-obj"),
+ })
+ cancel()
+ if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrNoSuchBucket)); err != nil {
+ return err
+ }
+
+ return nil
+ })
+}
+
+func GetObjectRetention_non_existing_object(s *S3Conf) error {
+ testName := "GetObjectRetention_non_existing_object"
+ return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
+ ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
+ _, err := s3client.GetObjectRetention(ctx, &s3.GetObjectRetentionInput{
+ Bucket: &bucket,
+ Key: getPtr("my-obj"),
+ })
+ cancel()
+ if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrNoSuchKey)); err != nil {
+ return err
+ }
+
+ return nil
+ })
+}
+
+func GetObjectRetention_unset_config(s *S3Conf) error {
+ testName := "GetObjectRetention_unset_config"
+ return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
+ key := "my-obj"
+ if err := putObjects(s3client, []string{key}, bucket); err != nil {
+ return err
+ }
+
+ ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
+ _, err := s3client.GetObjectRetention(ctx, &s3.GetObjectRetentionInput{
+ Bucket: &bucket,
+ Key: &key,
+ })
+ cancel()
+ if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrNoSuchObjectLockConfiguration)); err != nil {
+ return err
+ }
+
+ return nil
+ })
+}
+
+func GetObjectRetention_success(s *S3Conf) error {
+ testName := "GetObjectRetention_success"
+ return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
+ if err := changeBucketObjectLockStatus(s3client, bucket, true); err != nil {
+ return err
+ }
+ key := "my-obj"
+ if err := putObjects(s3client, []string{key}, bucket); err != nil {
+ return err
+ }
+
+ date := time.Now().Add(time.Hour * 3)
+ retention := types.ObjectLockRetention{
+ Mode: types.ObjectLockRetentionModeCompliance,
+ RetainUntilDate: &date,
+ }
+
+ ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
+ _, err := s3client.PutObjectRetention(ctx, &s3.PutObjectRetentionInput{
+ Bucket: &bucket,
+ Key: &key,
+ Retention: &retention,
+ })
+ cancel()
+ if err != nil {
+ return err
+ }
+
+ ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
+ resp, err := s3client.GetObjectRetention(ctx, &s3.GetObjectRetentionInput{
+ Bucket: &bucket,
+ Key: &key,
+ })
+ cancel()
+ if err != nil {
+ return err
+ }
+
+ if resp.Retention == nil {
+ return fmt.Errorf("got nil object lock retention")
+ }
+
+ ret := resp.Retention
+
+ if ret.Mode != retention.Mode {
+ return fmt.Errorf("expected retention mode to be %v, instead got %v", retention.Mode, ret.Mode)
+ }
+ // FIXME: There's a problem with storing retainUnitDate, most probably SDK changes the date before sending
+ // if ret.RetainUntilDate.Format(iso8601Format)[:8] != retention.RetainUntilDate.Format(iso8601Format)[:8] {
+ // return fmt.Errorf("expected retain until date to be %v, instead got %v", retention.RetainUntilDate.Format(iso8601Format), ret.RetainUntilDate.Format(iso8601Format))
+ // }
+
+ if err := changeBucketObjectLockStatus(s3client, bucket, false); err != nil {
+ return err
+ }
+
+ return nil
+ })
+}
+
+func PutObjectLegalHold_non_existing_bucket(s *S3Conf) error {
+ testName := "PutObjectLegalHold_non_existing_bucket"
+ return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
+ ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
+ _, err := s3client.PutObjectLegalHold(ctx, &s3.PutObjectLegalHoldInput{
+ Bucket: getPtr(getBucketName()),
+ Key: getPtr("my-obj"),
+ })
+ cancel()
+ if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrNoSuchBucket)); err != nil {
+ return err
+ }
+
+ return nil
+ })
+}
+
+func PutObjectLegalHold_non_existing_object(s *S3Conf) error {
+ testName := "PutObjectLegalHold_non_existing_object"
+ return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
+ if err := changeBucketObjectLockStatus(s3client, bucket, true); err != nil {
+ return err
+ }
+
+ ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
+ _, err := s3client.PutObjectLegalHold(ctx, &s3.PutObjectLegalHoldInput{
+ Bucket: &bucket,
+ Key: getPtr("my-obj"),
+ LegalHold: &types.ObjectLockLegalHold{
+ Status: types.ObjectLockLegalHoldStatusOn,
+ },
+ })
+ cancel()
+ if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrNoSuchKey)); err != nil {
+ return err
+ }
+
+ return nil
+ })
+}
+
+func PutObjectLegalHold_invalid_body(s *S3Conf) error {
+ testName := "PutObjectLegalHold_invalid_body"
+ return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
+ ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
+ _, err := s3client.PutObjectLegalHold(ctx, &s3.PutObjectLegalHoldInput{
+ Bucket: &bucket,
+ Key: getPtr("my-obj"),
+ })
+ cancel()
+ if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrInvalidRequest)); err != nil {
+ return err
+ }
+
+ return nil
+ })
+}
+
+func PutObjectLegalHold_unset_bucket_object_lock_config(s *S3Conf) error {
+ testName := "PutObjectLegalHold_unset_bucket_object_lock_config"
+ return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
+ key := "my-obj"
+
+ if err := putObjects(s3client, []string{key}, bucket); err != nil {
+ return err
+ }
+
+ ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
+ _, err := s3client.PutObjectLegalHold(ctx, &s3.PutObjectLegalHoldInput{
+ Bucket: &bucket,
+ Key: &key,
+ LegalHold: &types.ObjectLockLegalHold{
+ Status: types.ObjectLockLegalHoldStatusOn,
+ },
+ })
+ cancel()
+ if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrInvalidBucketObjectLockConfiguration)); err != nil {
+ return err
+ }
+
+ return nil
+ })
+}
+
+func PutObjectLegalHold_disabled_bucket_object_lock_config(s *S3Conf) error {
+ testName := "PutObjectLegalHold_disabled_bucket_object_lock_config"
+ return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
+ ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
+ _, err := s3client.PutObjectLockConfiguration(ctx, &s3.PutObjectLockConfigurationInput{
+ Bucket: &bucket,
+ ObjectLockConfiguration: &types.ObjectLockConfiguration{},
+ })
+ cancel()
+ if err != nil {
+ return err
+ }
+
+ key := "my-obj"
+
+ if err := putObjects(s3client, []string{key}, bucket); err != nil {
+ return err
+ }
+
+ ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
+ _, err = s3client.PutObjectLegalHold(ctx, &s3.PutObjectLegalHoldInput{
+ Bucket: &bucket,
+ Key: &key,
+ LegalHold: &types.ObjectLockLegalHold{
+ Status: types.ObjectLockLegalHoldStatusOn,
+ },
+ })
+ cancel()
+ if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrInvalidBucketObjectLockConfiguration)); err != nil {
+ return err
+ }
+
+ return nil
+ })
+}
+
+func PutObjectLegalHold_success(s *S3Conf) error {
+ testName := "PutObjectLegalHold_success"
+ return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
+ if err := changeBucketObjectLockStatus(s3client, bucket, true); err != nil {
+ return err
+ }
+
+ key := "my-obj"
+
+ if err := putObjects(s3client, []string{key}, bucket); err != nil {
+ return err
+ }
+
+ ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
+ _, err := s3client.PutObjectLegalHold(ctx, &s3.PutObjectLegalHoldInput{
+ Bucket: &bucket,
+ Key: &key,
+ LegalHold: &types.ObjectLockLegalHold{
+ Status: types.ObjectLockLegalHoldStatusOn,
+ },
+ })
+ cancel()
+ if err != nil {
+ return err
+ }
+
+ if err := changeBucketObjectLockStatus(s3client, bucket, false); err != nil {
+ return err
+ }
+
+ return nil
+ })
+}
+
+func GetObjectLegalHold_non_existing_bucket(s *S3Conf) error {
+ testName := "GetObjectLegalHold_non_existing_bucket"
+ return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
+ ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
+ _, err := s3client.GetObjectLegalHold(ctx, &s3.GetObjectLegalHoldInput{
+ Bucket: getPtr(getBucketName()),
+ Key: getPtr("my-obj"),
+ })
+ cancel()
+ if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrNoSuchBucket)); err != nil {
+ return err
+ }
+
+ return nil
+ })
+}
+
+func GetObjectLegalHold_non_existing_object(s *S3Conf) error {
+ testName := "GetObjectLegalHold_non_existing_object"
+ return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
+ ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
+ _, err := s3client.GetObjectLegalHold(ctx, &s3.GetObjectLegalHoldInput{
+ Bucket: &bucket,
+ Key: getPtr("my-obj"),
+ })
+ cancel()
+ if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrNoSuchKey)); err != nil {
+ return err
+ }
+
+ return nil
+ })
+}
+
+func GetObjectLegalHold_unset_config(s *S3Conf) error {
+ testName := "GetObjectLegalHold_unset_config"
+ return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
+ key := "my-obj"
+ if err := putObjects(s3client, []string{key}, bucket); err != nil {
+ return err
+ }
+
+ ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
+ _, err := s3client.GetObjectLegalHold(ctx, &s3.GetObjectLegalHoldInput{
+ Bucket: &bucket,
+ Key: &key,
+ })
+ cancel()
+ if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrNoSuchObjectLockConfiguration)); err != nil {
+ return err
+ }
+
+ return nil
+ })
+}
+
+func GetObjectLegalHold_success(s *S3Conf) error {
+ testName := "GetObjectLegalHold_success"
+ return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
+ if err := changeBucketObjectLockStatus(s3client, bucket, true); err != nil {
+ return err
+ }
+ key := "my-obj"
+ if err := putObjects(s3client, []string{key}, bucket); err != nil {
+ return err
+ }
+
+ ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
+ _, err := s3client.PutObjectLegalHold(ctx, &s3.PutObjectLegalHoldInput{
+ Bucket: &bucket,
+ Key: &key,
+ LegalHold: &types.ObjectLockLegalHold{
+ Status: types.ObjectLockLegalHoldStatusOn,
+ },
+ })
+ cancel()
+ if err != nil {
+ return err
+ }
+
+ ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
+ resp, err := s3client.GetObjectLegalHold(ctx, &s3.GetObjectLegalHoldInput{
+ Bucket: &bucket,
+ Key: &key,
+ })
+ cancel()
+ if err != nil {
+ return err
+ }
+
+ if resp.LegalHold.Status != types.ObjectLockLegalHoldStatusOn {
+ return fmt.Errorf("expected legal hold status to be On, instead got %v", resp.LegalHold.Status)
+ }
+
+ if err := changeBucketObjectLockStatus(s3client, bucket, false); err != nil {
+ return err
+ }
+
+ return nil
+ })
+}
+
+func WORMProtection_bucket_object_lock_configuration_compliance_mode(s *S3Conf) error {
+ testName := "WORMProtection_bucket_object_lock_configuration_compliance_mode"
+ return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
+ var days int32 = 10
+ object := "my-obj"
+ ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
+ _, err := s3client.PutObjectLockConfiguration(ctx, &s3.PutObjectLockConfigurationInput{
+ Bucket: &bucket,
+ ObjectLockConfiguration: &types.ObjectLockConfiguration{
+ ObjectLockEnabled: types.ObjectLockEnabledEnabled,
+ Rule: &types.ObjectLockRule{
+ DefaultRetention: &types.DefaultRetention{
+ Mode: types.ObjectLockRetentionModeCompliance,
+ Days: &days,
+ },
+ },
+ },
+ })
+ cancel()
+ if err != nil {
+ return err
+ }
+
+ if err := putObjects(s3client, []string{object}, bucket); err != nil {
+ return err
+ }
+
+ if err := checkWORMProtection(s3client, bucket, object); err != nil {
+ return err
+ }
+ if err := changeBucketObjectLockStatus(s3client, bucket, false); err != nil {
+ return err
+ }
+
+ return nil
+ })
+}
+
+func WORMProtection_bucket_object_lock_governance_root_overwrite(s *S3Conf) error {
+ testName := "WORMProtection_bucket_object_lock_governance_root_overwrite"
+ return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
+ var days int32 = 10
+ object := "my-obj"
+ ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
+ _, err := s3client.PutObjectLockConfiguration(ctx, &s3.PutObjectLockConfigurationInput{
+ Bucket: &bucket,
+ ObjectLockConfiguration: &types.ObjectLockConfiguration{
+ ObjectLockEnabled: types.ObjectLockEnabledEnabled,
+ Rule: &types.ObjectLockRule{
+ DefaultRetention: &types.DefaultRetention{
+ Mode: types.ObjectLockRetentionModeGovernance,
+ Days: &days,
+ },
+ },
+ },
+ })
+ cancel()
+ if err != nil {
+ return err
+ }
+
+ // create an object
+ if err := putObjects(s3client, []string{object}, bucket); err != nil {
+ return err
+ }
+
+ // overwrite the object
+ if err := putObjects(s3client, []string{object}, bucket); err != nil {
+ return err
+ }
+
+ if err := changeBucketObjectLockStatus(s3client, bucket, false); err != nil {
+ return err
+ }
+
+ return nil
+ })
+}
+
+func WORMProtection_object_lock_retention_compliance_root_access_denied(s *S3Conf) error {
+ testName := "WORMProtection_object_lock_retention_compliance_root_access_denied"
+ return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
+ if err := changeBucketObjectLockStatus(s3client, bucket, true); err != nil {
+ return err
+ }
+
+ object := "my-obj"
+
+ if err := putObjects(s3client, []string{object}, bucket); err != nil {
+ return err
+ }
+
+ date := time.Now().Add(time.Hour * 3)
+ ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
+ _, err := s3client.PutObjectRetention(ctx, &s3.PutObjectRetentionInput{
+ Bucket: &bucket,
+ Key: &object,
+ Retention: &types.ObjectLockRetention{
+ Mode: types.ObjectLockRetentionModeCompliance,
+ RetainUntilDate: &date,
+ },
+ })
+ cancel()
+ if err != nil {
+ return err
+ }
+
+ if err := checkWORMProtection(s3client, bucket, object); err != nil {
+ return err
+ }
+ if err := changeBucketObjectLockStatus(s3client, bucket, false); err != nil {
+ return err
+ }
+
+ return nil
+ })
+}
+
+func WORMProtection_object_lock_retention_governance_root_overwrite(s *S3Conf) error {
+ testName := "WORMProtection_object_lock_retention_governance_root_overwrite"
+ return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
+ if err := changeBucketObjectLockStatus(s3client, bucket, true); err != nil {
+ return err
+ }
+
+ object := "my-obj"
+
+ if err := putObjects(s3client, []string{object}, bucket); err != nil {
+ return err
+ }
+
+ date := time.Now().Add(time.Hour * 3)
+ ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
+ _, err := s3client.PutObjectRetention(ctx, &s3.PutObjectRetentionInput{
+ Bucket: &bucket,
+ Key: &object,
+ Retention: &types.ObjectLockRetention{
+ Mode: types.ObjectLockRetentionModeGovernance,
+ RetainUntilDate: &date,
+ },
+ })
+ cancel()
+ if err != nil {
+ return err
+ }
+
+ if err := putObjects(s3client, []string{object}, bucket); err != nil {
+ return err
+ }
+
+ if err := changeBucketObjectLockStatus(s3client, bucket, false); err != nil {
+ return err
+ }
+
+ return nil
+ })
+}
+
+func WORMProtection_object_lock_retention_governance_user_access_denied(s *S3Conf) error {
+ testName := "WORMProtection_object_lock_retention_governance_user_access_denied"
+ return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
+ if err := changeBucketObjectLockStatus(s3client, bucket, true); err != nil {
+ return err
+ }
+
+ object := "my-obj"
+
+ if err := putObjects(s3client, []string{object}, bucket); err != nil {
+ return err
+ }
+
+ date := time.Now().Add(time.Hour * 3)
+ ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
+ _, err := s3client.PutObjectRetention(ctx, &s3.PutObjectRetentionInput{
+ Bucket: &bucket,
+ Key: &object,
+ Retention: &types.ObjectLockRetention{
+ Mode: types.ObjectLockRetentionModeGovernance,
+ RetainUntilDate: &date,
+ },
+ })
+ cancel()
+ if err != nil {
+ return err
+ }
+
+ usr := user{
+ access: "grt1",
+ secret: "grt1secret",
+ role: "user",
+ }
+ if err := createUsers(s, []user{usr}); err != nil {
+ return err
+ }
+ if err := changeBucketsOwner(s, []string{bucket}, usr.access); err != nil {
+ return err
+ }
+
+ cfg := *s
+ cfg.awsID = usr.access
+ cfg.awsSecret = usr.secret
+
+ err = putObjects(s3.NewFromConfig(cfg.Config()), []string{object}, bucket)
+ if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrObjectLocked)); err != nil {
+ return err
+ }
+
+ if err := changeBucketObjectLockStatus(s3client, bucket, false); err != nil {
+ return err
+ }
+
+ return nil
+ })
+}
+
+func WORMProtection_object_lock_legal_hold_user_access_denied(s *S3Conf) error {
+ testName := "WORMProtection_object_lock_legal_hold_user_access_denied"
+ return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
+ if err := changeBucketObjectLockStatus(s3client, bucket, true); err != nil {
+ return err
+ }
+
+ object := "my-obj"
+
+ if err := putObjects(s3client, []string{object}, bucket); err != nil {
+ return err
+ }
+
+ ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
+ _, err := s3client.PutObjectLegalHold(ctx, &s3.PutObjectLegalHoldInput{
+ Bucket: &bucket,
+ Key: &object,
+ LegalHold: &types.ObjectLockLegalHold{
+ Status: types.ObjectLockLegalHoldStatusOn,
+ },
+ })
+ cancel()
+ if err != nil {
+ return err
+ }
+
+ usr := user{
+ access: "grt1",
+ secret: "grt1secret",
+ role: "user",
+ }
+ if err := createUsers(s, []user{usr}); err != nil {
+ return err
+ }
+ if err := changeBucketsOwner(s, []string{bucket}, usr.access); err != nil {
+ return err
+ }
+
+ cfg := *s
+ cfg.awsID = usr.access
+ cfg.awsSecret = usr.secret
+
+ err = putObjects(s3.NewFromConfig(cfg.Config()), []string{object}, bucket)
+ if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrObjectLocked)); err != nil {
+ return err
+ }
+
+ if err := changeBucketObjectLockStatus(s3client, bucket, false); err != nil {
+ return err
+ }
+
+ return nil
+ })
+}
+
+func WORMProtection_object_lock_legal_hold_root_overwrite(s *S3Conf) error {
+ testName := "WORMProtection_object_lock_legal_hold_root_overwrite"
+ return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
+ if err := changeBucketObjectLockStatus(s3client, bucket, true); err != nil {
+ return err
+ }
+
+ object := "my-obj"
+
+ if err := putObjects(s3client, []string{object}, bucket); err != nil {
+ return err
+ }
+
+ ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
+ _, err := s3client.PutObjectLegalHold(ctx, &s3.PutObjectLegalHoldInput{
+ Bucket: &bucket,
+ Key: &object,
+ LegalHold: &types.ObjectLockLegalHold{
+ Status: types.ObjectLockLegalHoldStatusOn,
+ },
+ })
+ cancel()
+ if err != nil {
+ return err
+ }
+
+ if err := putObjects(s3client, []string{object}, bucket); err != nil {
+ return err
+ }
+
+ if err := changeBucketObjectLockStatus(s3client, bucket, false); err != nil {
+ return err
+ }
+
+ return nil
+ })
+}
+
// Access control tests (with bucket ACLs and Policies)
func AccessControl_default_ACL_user_access_denied(s *S3Conf) error {
testName := "AccessControl_default_ACL_user_access_denied"
diff --git a/tests/integration/utils.go b/tests/integration/utils.go
index bd13800a..71305588 100644
--- a/tests/integration/utils.go
+++ b/tests/integration/utils.go
@@ -645,3 +645,62 @@ func getUserS3Client(usr user, cfg *S3Conf) *s3.Client {
return s3.NewFromConfig(config.Config())
}
+
+// if true enables, otherwise disables
+func changeBucketObjectLockStatus(client *s3.Client, bucket string, status bool) error {
+ cfg := types.ObjectLockConfiguration{}
+ if status {
+ cfg.ObjectLockEnabled = types.ObjectLockEnabledEnabled
+ }
+ ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
+ _, err := client.PutObjectLockConfiguration(ctx, &s3.PutObjectLockConfigurationInput{
+ Bucket: &bucket,
+ ObjectLockConfiguration: &cfg,
+ })
+ cancel()
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func checkWORMProtection(client *s3.Client, bucket, object string) error {
+ ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
+ _, err := client.PutObject(ctx, &s3.PutObjectInput{
+ Bucket: &bucket,
+ Key: &object,
+ })
+ cancel()
+ if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrObjectLocked)); err != nil {
+ return err
+ }
+
+ ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
+ _, err = client.DeleteObject(ctx, &s3.DeleteObjectInput{
+ Bucket: &bucket,
+ Key: &object,
+ })
+ cancel()
+ if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrObjectLocked)); err != nil {
+ return err
+ }
+
+ ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
+ _, err = client.DeleteObjects(ctx, &s3.DeleteObjectsInput{
+ Bucket: &bucket,
+ Delete: &types.Delete{
+ Objects: []types.ObjectIdentifier{
+ {
+ Key: &object,
+ },
+ },
+ },
+ })
+ cancel()
+ if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrObjectLocked)); err != nil {
+ return err
+ }
+
+ return nil
+}