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 +}