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..6882790c
--- /dev/null
+++ b/auth/object_lock.go
@@ -0,0 +1,150 @@
+// 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"
+ "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 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 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
+ }
+
+ for _, obj := range objects {
+ retention, err := be.GetObjectRetention(ctx, bucket, obj, "")
+ if err != nil {
+ if errors.Is(err, s3err.GetAPIError(s3err.ErrNoSuchKey)) {
+ continue
+ }
+ if errors.Is(err, s3err.GetAPIError(s3err.ErrNoSuchObjectLockConfiguration)) {
+ continue
+ }
+
+ 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
+ }
+ err = verifyBucketPolicy(policy, userAccess, bucket, obj, BypassGovernanceRetentionAction)
+ if err != nil {
+ return s3err.GetAPIError(s3err.ErrObjectLocked)
+ }
+ }
+ case types.ObjectLockRetentionModeCompliance:
+ return s3err.GetAPIError(s3err.ErrObjectLocked)
+ }
+ }
+ }
+
+ legalHold, err := be.GetObjectLegalHold(ctx, bucket, obj, "")
+ if err != nil {
+ return err
+ }
+
+ if legalHold.Status == types.ObjectLockLegalHoldStatusOn && !isAdminOrRoot {
+ return s3err.GetAPIError(s3err.ErrObjectLocked)
+ }
+ }
+
+ if bucketLockConfig.DefaultRetention != nil && bucketLockConfig.CreatedAt != nil {
+ 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..a0a79d17 100644
--- a/backend/backend.go
+++ b/backend/backend.go
@@ -21,6 +21,7 @@ import (
"io"
"github.com/aws/aws-sdk-go-v2/service/s3"
+ "github.com/aws/aws-sdk-go-v2/service/s3/types"
"github.com/versity/versitygw/s3err"
"github.com/versity/versitygw/s3response"
"github.com/versity/versitygw/s3select"
@@ -81,6 +82,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, *s3.PutObjectLockConfigurationInput) error
+ GetObjectLockConfiguration(_ context.Context, bucket string) ([]byte, error)
+ PutObjectRetention(context.Context, *s3.PutObjectRetentionInput) error
+ GetObjectRetention(_ context.Context, bucket, object, versionId string) (*types.ObjectLockRetention, error)
+ PutObjectLegalHold(context.Context, *s3.PutObjectLegalHoldInput) error
+ GetObjectLegalHold(_ context.Context, bucket, object, versionId string) (*types.ObjectLockLegalHold, error)
+
// non AWS actions
ChangeBucketOwner(_ context.Context, bucket, newOwner string) error
ListBucketsAndOwners(context.Context) ([]s3response.Bucket, error)
@@ -229,6 +238,25 @@ func (BackendUnsupported) DeleteObjectTagging(_ context.Context, bucket, object
return s3err.GetAPIError(s3err.ErrNotImplemented)
}
+func (BackendUnsupported) PutObjectLockConfiguration(context.Context, *s3.PutObjectLockConfigurationInput) 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, *s3.PutObjectRetentionInput) error {
+ return s3err.GetAPIError(s3err.ErrNotImplemented)
+}
+func (BackendUnsupported) GetObjectRetention(_ context.Context, bucket, object, versionId string) (*types.ObjectLockRetention, error) {
+ return nil, s3err.GetAPIError(s3err.ErrNotImplemented)
+}
+func (BackendUnsupported) PutObjectLegalHold(context.Context, *s3.PutObjectLegalHoldInput) error {
+ return s3err.GetAPIError(s3err.ErrNotImplemented)
+}
+func (BackendUnsupported) GetObjectLegalHold(_ context.Context, bucket, object, versionId string) (*types.ObjectLockLegalHold, 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..3fc6e320 100644
--- a/backend/posix/posix.go
+++ b/backend/posix/posix.go
@@ -30,10 +30,12 @@ import (
"strconv"
"strings"
"syscall"
+ "time"
"github.com/aws/aws-sdk-go-v2/service/s3"
"github.com/aws/aws-sdk-go-v2/service/s3/types"
"github.com/google/uuid"
+ "github.com/pkg/xattr"
"github.com/versity/versitygw/auth"
"github.com/versity/versitygw/backend"
"github.com/versity/versitygw/backend/meta"
@@ -75,6 +77,8 @@ const (
aclkey = "acl"
etagkey = "etag"
policykey = "policy"
+ bucketLockKey = "bucket-lock"
+ objectLockKey = "object-lock"
)
type PosixOpts struct {
@@ -2011,6 +2015,259 @@ func (p *Posix) DeleteBucketPolicy(ctx context.Context, bucket string) error {
return p.PutBucketPolicy(ctx, bucket, nil)
}
+func (p *Posix) PutObjectLockConfiguration(_ context.Context, input *s3.PutObjectLockConfigurationInput) error {
+ _, err := os.Stat(*input.Bucket)
+ if errors.Is(err, fs.ErrNotExist) {
+ return s3err.GetAPIError(s3err.ErrNoSuchBucket)
+ }
+ if err != nil {
+ return fmt.Errorf("stat bucket: %w", err)
+ }
+
+ lockConfig := input.ObjectLockConfiguration
+
+ config := auth.BucketLockConfig{
+ Enabled: lockConfig.ObjectLockEnabled == types.ObjectLockEnabledEnabled,
+ }
+
+ if lockConfig.Rule != nil && lockConfig.Rule.DefaultRetention != nil {
+ retentation := lockConfig.Rule.DefaultRetention
+ if retentation.Years != nil && retentation.Days != nil {
+ return s3err.GetAPIError(s3err.ErrInvalidRequest)
+ }
+
+ config.DefaultRetention = retentation
+ now := time.Now()
+ config.CreatedAt = &now
+ }
+
+ configParsed, err := json.Marshal(config)
+ if err != nil {
+ return fmt.Errorf("parse object lock config: %w", err)
+ }
+
+ if err := xattr.Set(*input.Bucket, bucketLockKey, configParsed); err != nil {
+ return fmt.Errorf("set tags: %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 := xattr.Get(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, input *s3.PutObjectLegalHoldInput) error {
+ _, err := os.Stat(*input.Bucket)
+ if errors.Is(err, fs.ErrNotExist) {
+ return s3err.GetAPIError(s3err.ErrNoSuchBucket)
+ }
+ if err != nil {
+ return fmt.Errorf("stat bucket: %w", err)
+ }
+
+ cfg, err := xattr.Get(*input.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)
+ }
+
+ path := filepath.Join(*input.Bucket, *input.Key)
+ var config auth.ObjectLockConfig
+
+ data, err := xattr.Get(path, objectLockKey)
+ if err != nil {
+ if errors.Is(err, fs.ErrNotExist) {
+ return s3err.GetAPIError(s3err.ErrNoSuchKey)
+ }
+ if errors.Is(err, meta.ErrNoSuchKey) {
+ return fmt.Errorf("get object lock config: %w", err)
+ }
+
+ config = auth.ObjectLockConfig{}
+ } else {
+ if err := json.Unmarshal(data, &config); err != nil {
+ return fmt.Errorf("parse object lock data %w", err)
+ }
+ }
+
+ switch input.LegalHold.Status {
+ case types.ObjectLockLegalHoldStatusOff:
+ config.LegalHoldEnabled = false
+ case types.ObjectLockLegalHoldStatusOn:
+ config.LegalHoldEnabled = true
+ default:
+ return s3err.GetAPIError(s3err.ErrInvalidRequest)
+ }
+
+ b, err := json.Marshal(config)
+ if err != nil {
+ return fmt.Errorf("marshal object lock config: %w", err)
+ }
+
+ err = xattr.Set(path, objectLockKey, b)
+ 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) (*types.ObjectLockLegalHold, 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 := xattr.Get(filepath.Join(bucket, object), objectLockKey)
+ 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)
+ }
+
+ var config auth.ObjectLockConfig
+ if err := json.Unmarshal(data, &config); err != nil {
+ return nil, fmt.Errorf("pare object lock config: %w", err)
+ }
+
+ result := &types.ObjectLockLegalHold{}
+
+ if config.LegalHoldEnabled {
+ result.Status = types.ObjectLockLegalHoldStatusOn
+ } else {
+ result.Status = types.ObjectLockLegalHoldStatusOff
+ }
+
+ return result, nil
+}
+
+func (p *Posix) PutObjectRetention(_ context.Context, input *s3.PutObjectRetentionInput) error {
+ _, err := os.Stat(*input.Bucket)
+ if errors.Is(err, fs.ErrNotExist) {
+ return s3err.GetAPIError(s3err.ErrNoSuchBucket)
+ }
+ if err != nil {
+ return fmt.Errorf("stat bucket: %w", err)
+ }
+
+ cfg, err := xattr.Get(*input.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)
+ }
+
+ path := filepath.Join(*input.Bucket, *input.Key)
+ var config auth.ObjectLockConfig
+
+ data, err := xattr.Get(path, objectLockKey)
+ if err != nil {
+ if errors.Is(err, fs.ErrNotExist) {
+ return s3err.GetAPIError(s3err.ErrNoSuchKey)
+ }
+ if errors.Is(err, meta.ErrNoSuchKey) {
+ return fmt.Errorf("get object lock config: %w", err)
+ }
+
+ config = auth.ObjectLockConfig{}
+ } else {
+ if err := json.Unmarshal(data, &config); err != nil {
+ return fmt.Errorf("parse object lock data %w", err)
+ }
+ }
+
+ config.Retention = input.Retention
+
+ b, err := json.Marshal(config)
+ if err != nil {
+ return fmt.Errorf("marshal object lock config: %w", err)
+ }
+
+ err = xattr.Set(path, objectLockKey, b)
+ 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) (*types.ObjectLockRetention, 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 := xattr.Get(filepath.Join(bucket, object), objectLockKey)
+ 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)
+ }
+
+ var config auth.ObjectLockConfig
+ if err := json.Unmarshal(data, &config); err != nil {
+ return nil, fmt.Errorf("pare object lock config: %w", err)
+ }
+
+ if config.Retention == nil {
+ return &types.ObjectLockRetention{}, nil
+ }
+
+ return config.Retention, 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..f56e6ce3 100644
--- a/s3api/controllers/backend_moq_test.go
+++ b/s3api/controllers/backend_moq_test.go
@@ -7,6 +7,7 @@ import (
"bufio"
"context"
"github.com/aws/aws-sdk-go-v2/service/s3"
+ "github.com/aws/aws-sdk-go-v2/service/s3/types"
"github.com/versity/versitygw/backend"
"github.com/versity/versitygw/s3response"
"io"
@@ -80,6 +81,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) (*types.ObjectLockLegalHold, 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) (*types.ObjectLockRetention, 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 +138,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, putObjectLegalHoldInput *s3.PutObjectLegalHoldInput) error {
+// panic("mock out the PutObjectLegalHold method")
+// },
+// PutObjectLockConfigurationFunc: func(contextMoqParam context.Context, putObjectLockConfigurationInput *s3.PutObjectLockConfigurationInput) error {
+// panic("mock out the PutObjectLockConfiguration method")
+// },
+// PutObjectRetentionFunc: func(contextMoqParam context.Context, putObjectRetentionInput *s3.PutObjectRetentionInput) 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 +232,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) (*types.ObjectLockLegalHold, 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) (*types.ObjectLockRetention, error)
+
// GetObjectTaggingFunc mocks the GetObjectTagging method.
GetObjectTaggingFunc func(contextMoqParam context.Context, bucket string, object string) (map[string]string, error)
@@ -261,6 +289,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, putObjectLegalHoldInput *s3.PutObjectLegalHoldInput) error
+
+ // PutObjectLockConfigurationFunc mocks the PutObjectLockConfiguration method.
+ PutObjectLockConfigurationFunc func(contextMoqParam context.Context, putObjectLockConfigurationInput *s3.PutObjectLockConfigurationInput) error
+
+ // PutObjectRetentionFunc mocks the PutObjectRetention method.
+ PutObjectRetentionFunc func(contextMoqParam context.Context, putObjectRetentionInput *s3.PutObjectRetentionInput) error
+
// PutObjectTaggingFunc mocks the PutObjectTagging method.
PutObjectTaggingFunc func(contextMoqParam context.Context, bucket string, object string, tags map[string]string) error
@@ -425,6 +462,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 +611,27 @@ 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
+ // PutObjectLegalHoldInput is the putObjectLegalHoldInput argument value.
+ PutObjectLegalHoldInput *s3.PutObjectLegalHoldInput
+ }
+ // PutObjectLockConfiguration holds details about calls to the PutObjectLockConfiguration method.
+ PutObjectLockConfiguration []struct {
+ // ContextMoqParam is the contextMoqParam argument value.
+ ContextMoqParam context.Context
+ // PutObjectLockConfigurationInput is the putObjectLockConfigurationInput argument value.
+ PutObjectLockConfigurationInput *s3.PutObjectLockConfigurationInput
+ }
+ // PutObjectRetention holds details about calls to the PutObjectRetention method.
+ PutObjectRetention []struct {
+ // ContextMoqParam is the contextMoqParam argument value.
+ ContextMoqParam context.Context
+ // PutObjectRetentionInput is the putObjectRetentionInput argument value.
+ PutObjectRetentionInput *s3.PutObjectRetentionInput
+ }
// PutObjectTagging holds details about calls to the PutObjectTagging method.
PutObjectTagging []struct {
// ContextMoqParam is the contextMoqParam argument value.
@@ -591,48 +678,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 +1428,130 @@ func (mock *BackendMock) GetObjectAttributesCalls() []struct {
return calls
}
+// GetObjectLegalHold calls GetObjectLegalHoldFunc.
+func (mock *BackendMock) GetObjectLegalHold(contextMoqParam context.Context, bucket string, object string, versionId string) (*types.ObjectLockLegalHold, 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) (*types.ObjectLockRetention, 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 +2144,114 @@ func (mock *BackendMock) PutObjectAclCalls() []struct {
return calls
}
+// PutObjectLegalHold calls PutObjectLegalHoldFunc.
+func (mock *BackendMock) PutObjectLegalHold(contextMoqParam context.Context, putObjectLegalHoldInput *s3.PutObjectLegalHoldInput) error {
+ if mock.PutObjectLegalHoldFunc == nil {
+ panic("BackendMock.PutObjectLegalHoldFunc: method is nil but Backend.PutObjectLegalHold was just called")
+ }
+ callInfo := struct {
+ ContextMoqParam context.Context
+ PutObjectLegalHoldInput *s3.PutObjectLegalHoldInput
+ }{
+ ContextMoqParam: contextMoqParam,
+ PutObjectLegalHoldInput: putObjectLegalHoldInput,
+ }
+ mock.lockPutObjectLegalHold.Lock()
+ mock.calls.PutObjectLegalHold = append(mock.calls.PutObjectLegalHold, callInfo)
+ mock.lockPutObjectLegalHold.Unlock()
+ return mock.PutObjectLegalHoldFunc(contextMoqParam, putObjectLegalHoldInput)
+}
+
+// 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
+ PutObjectLegalHoldInput *s3.PutObjectLegalHoldInput
+} {
+ var calls []struct {
+ ContextMoqParam context.Context
+ PutObjectLegalHoldInput *s3.PutObjectLegalHoldInput
+ }
+ mock.lockPutObjectLegalHold.RLock()
+ calls = mock.calls.PutObjectLegalHold
+ mock.lockPutObjectLegalHold.RUnlock()
+ return calls
+}
+
+// PutObjectLockConfiguration calls PutObjectLockConfigurationFunc.
+func (mock *BackendMock) PutObjectLockConfiguration(contextMoqParam context.Context, putObjectLockConfigurationInput *s3.PutObjectLockConfigurationInput) error {
+ if mock.PutObjectLockConfigurationFunc == nil {
+ panic("BackendMock.PutObjectLockConfigurationFunc: method is nil but Backend.PutObjectLockConfiguration was just called")
+ }
+ callInfo := struct {
+ ContextMoqParam context.Context
+ PutObjectLockConfigurationInput *s3.PutObjectLockConfigurationInput
+ }{
+ ContextMoqParam: contextMoqParam,
+ PutObjectLockConfigurationInput: putObjectLockConfigurationInput,
+ }
+ mock.lockPutObjectLockConfiguration.Lock()
+ mock.calls.PutObjectLockConfiguration = append(mock.calls.PutObjectLockConfiguration, callInfo)
+ mock.lockPutObjectLockConfiguration.Unlock()
+ return mock.PutObjectLockConfigurationFunc(contextMoqParam, putObjectLockConfigurationInput)
+}
+
+// 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
+ PutObjectLockConfigurationInput *s3.PutObjectLockConfigurationInput
+} {
+ var calls []struct {
+ ContextMoqParam context.Context
+ PutObjectLockConfigurationInput *s3.PutObjectLockConfigurationInput
+ }
+ mock.lockPutObjectLockConfiguration.RLock()
+ calls = mock.calls.PutObjectLockConfiguration
+ mock.lockPutObjectLockConfiguration.RUnlock()
+ return calls
+}
+
+// PutObjectRetention calls PutObjectRetentionFunc.
+func (mock *BackendMock) PutObjectRetention(contextMoqParam context.Context, putObjectRetentionInput *s3.PutObjectRetentionInput) error {
+ if mock.PutObjectRetentionFunc == nil {
+ panic("BackendMock.PutObjectRetentionFunc: method is nil but Backend.PutObjectRetention was just called")
+ }
+ callInfo := struct {
+ ContextMoqParam context.Context
+ PutObjectRetentionInput *s3.PutObjectRetentionInput
+ }{
+ ContextMoqParam: contextMoqParam,
+ PutObjectRetentionInput: putObjectRetentionInput,
+ }
+ mock.lockPutObjectRetention.Lock()
+ mock.calls.PutObjectRetention = append(mock.calls.PutObjectRetention, callInfo)
+ mock.lockPutObjectRetention.Unlock()
+ return mock.PutObjectRetentionFunc(contextMoqParam, putObjectRetentionInput)
+}
+
+// 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
+ PutObjectRetentionInput *s3.PutObjectRetentionInput
+} {
+ var calls []struct {
+ ContextMoqParam context.Context
+ PutObjectRetentionInput *s3.PutObjectRetentionInput
+ }
+ 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..fa6d6328 100644
--- a/s3api/controllers/base.go
+++ b/s3api/controllers/base.go
@@ -131,6 +131,62 @@ 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)
+ return SendXMLResponse(ctx, data, 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, 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 +603,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 +938,47 @@ func (c S3ApiController) PutBucketActions(ctx *fiber.Ctx) error {
})
}
+ if ctx.Request().URI().QueryArgs().Has("object-lock") {
+ parsedAcl := ctx.Locals("parsedAcl").(auth.ACL)
+
+ var input types.ObjectLockConfiguration
+ if err := xml.Unmarshal(ctx.Body(), &input); err != nil {
+ return SendResponse(ctx, s3err.GetAPIError(s3err.ErrInvalidRequest),
+ &MetaOpts{
+ Logger: c.logger,
+ Action: "PutObjectLockConfiguration",
+ BucketOwner: parsedAcl.Owner,
+ })
+ }
+
+ 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,
+ })
+ }
+
+ err := c.be.PutObjectLockConfiguration(ctx.Context(), &s3.PutObjectLockConfigurationInput{
+ Bucket: &bucket,
+ ObjectLockConfiguration: &input,
+ })
+ 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 +1210,8 @@ func (c S3ApiController) PutActions(ctx *fiber.Ctx) error {
keyStart := ctx.Params("key")
keyEnd := ctx.Params("*1")
uploadId := ctx.Query("uploadId")
+ versionId := ctx.Query("versionId")
+ bypassGovernanceRetention := ctx.Get("X-Amz-Bypass-Governance-Retention")
acct := ctx.Locals("account").(auth.Account)
isRoot := ctx.Locals("isRoot").(bool)
parsedAcl := ctx.Locals("parsedAcl").(auth.ACL)
@@ -1176,6 +1312,98 @@ func (c S3ApiController) PutActions(ctx *fiber.Ctx) error {
})
}
+ if ctx.Request().URI().QueryArgs().Has("retention") {
+ var retention types.ObjectLockRetention
+ if err := xml.Unmarshal(ctx.Body(), &retention); err != nil {
+ return SendResponse(ctx, s3err.GetAPIError(s3err.ErrInvalidRequest), &MetaOpts{
+ Logger: c.logger,
+ Action: "PutObjectRetention",
+ BucketOwner: parsedAcl.Owner,
+ })
+ }
+
+ if retention.RetainUntilDate == nil || retention.RetainUntilDate.Before(time.Now()) {
+ return SendResponse(ctx, s3err.GetAPIError(s3err.ErrPastObjectLockRetainDate),
+ &MetaOpts{
+ Logger: c.logger,
+ Action: "PutObjectRetention",
+ 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.PutObjectRetentionAction,
+ }); err != nil {
+ return SendResponse(ctx, err,
+ &MetaOpts{
+ Logger: c.logger,
+ Action: "PutObjectRetention",
+ BucketOwner: parsedAcl.Owner,
+ })
+ }
+
+ pass := bypassGovernanceRetention == "True"
+
+ err := c.be.PutObjectRetention(ctx.Context(), &s3.PutObjectRetentionInput{
+ Bucket: &bucket,
+ Key: &keyStart,
+ VersionId: &versionId,
+ Retention: &retention,
+ BypassGovernanceRetention: &pass,
+ })
+ 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(), &s3.PutObjectLegalHoldInput{
+ Bucket: &bucket,
+ Key: &keyStart,
+ VersionId: &versionId,
+ LegalHold: &legalHold,
+ })
+ 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 +1753,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 +1941,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 +2071,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..a9f09a39 100644
--- a/s3api/controllers/base_test.go
+++ b/s3api/controllers/base_test.go
@@ -205,6 +205,12 @@ 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) (*types.ObjectLockRetention, error) {
+ return &types.ObjectLockRetention{}, nil
+ },
+ GetObjectLegalHoldFunc: func(contextMoqParam context.Context, bucket, object, versionId string) (*types.ObjectLockLegalHold, error) {
+ return &types.ObjectLockLegalHold{}, nil
+ },
},
}
app.Use(func(ctx *fiber.Ctx) error {
@@ -236,6 +242,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 +353,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 +385,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 +401,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 +450,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 +625,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 +657,9 @@ func TestS3ApiController_PutBucketActions(t *testing.T) {
PutBucketPolicyFunc: func(contextMoqParam context.Context, bucket string, policy []byte) error {
return nil
},
+ PutObjectLockConfigurationFunc: func(contextMoqParam context.Context, putObjectLockConfigurationInput *s3.PutObjectLockConfigurationInput) error {
+ return nil
+ },
},
}
// Mock ctx.Locals
@@ -662,6 +718,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 +880,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 +919,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, putObjectLegalHoldInput *s3.PutObjectLegalHoldInput) error {
+ return nil
+ },
+ PutObjectRetentionFunc: func(contextMoqParam context.Context, putObjectRetentionInput *s3.PutObjectRetentionInput) 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 +1006,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 +1228,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 +1308,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 +1333,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.",