diff --git a/auth/object_lock.go b/auth/object_lock.go index 4a1f1b2..6a5fbcb 100644 --- a/auth/object_lock.go +++ b/auth/object_lock.go @@ -17,6 +17,7 @@ package auth import ( "context" "encoding/json" + "encoding/xml" "errors" "fmt" "time" @@ -37,6 +38,30 @@ type ObjectLockConfig struct { 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 { @@ -56,6 +81,50 @@ func ParseBucketLockConfigurationOutput(input []byte) (*types.ObjectLockConfigur 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 { @@ -78,48 +147,58 @@ func CheckObjectAccess(ctx context.Context, bucket, userAccess string, objects [ objExists := true for _, obj := range objects { - retention, err := be.GetObjectRetention(ctx, bucket, obj, "") - if err != nil { - if errors.Is(err, s3err.GetAPIError(s3err.ErrNoSuchKey)) { - objExists = false - continue - } - if errors.Is(err, s3err.GetAPIError(s3err.ErrNoSuchObjectLockConfiguration)) { - continue - } - + 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 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) + 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) } - case types.ObjectLockRetentionModeCompliance: - return s3err.GetAPIError(s3err.ErrObjectLocked) } } } - legalHold, err := be.GetObjectLegalHold(ctx, bucket, obj, "") + status, err := be.GetObjectLegalHold(ctx, bucket, obj, "") + if errors.Is(err, s3err.GetAPIError(s3err.ErrNoSuchObjectLockConfiguration)) { + continue + } if err != nil { return err } - if legalHold.Status == types.ObjectLockLegalHoldStatusOn && !isAdminOrRoot { + if *status && !isAdminOrRoot { return s3err.GetAPIError(s3err.ErrObjectLocked) } } diff --git a/backend/backend.go b/backend/backend.go index a0a79d1..fa060d5 100644 --- a/backend/backend.go +++ b/backend/backend.go @@ -21,7 +21,6 @@ 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" @@ -83,12 +82,12 @@ type Backend interface { DeleteObjectTagging(_ context.Context, bucket, object string) error // object lock operations - PutObjectLockConfiguration(context.Context, *s3.PutObjectLockConfigurationInput) error + PutObjectLockConfiguration(_ context.Context, bucket string, config []byte) 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) + 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 @@ -238,22 +237,22 @@ func (BackendUnsupported) DeleteObjectTagging(_ context.Context, bucket, object return s3err.GetAPIError(s3err.ErrNotImplemented) } -func (BackendUnsupported) PutObjectLockConfiguration(context.Context, *s3.PutObjectLockConfigurationInput) error { +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, *s3.PutObjectRetentionInput) error { +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) (*types.ObjectLockRetention, error) { +func (BackendUnsupported) GetObjectRetention(_ context.Context, bucket, object, versionId string) ([]byte, error) { return nil, s3err.GetAPIError(s3err.ErrNotImplemented) } -func (BackendUnsupported) PutObjectLegalHold(context.Context, *s3.PutObjectLegalHoldInput) error { +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) (*types.ObjectLockLegalHold, error) { +func (BackendUnsupported) GetObjectLegalHold(_ context.Context, bucket, object, versionId string) (*bool, error) { return nil, s3err.GetAPIError(s3err.ErrNotImplemented) } diff --git a/backend/posix/posix.go b/backend/posix/posix.go index 7ce5abb..fd17035 100644 --- a/backend/posix/posix.go +++ b/backend/posix/posix.go @@ -30,12 +30,10 @@ 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" @@ -78,7 +76,8 @@ const ( etagkey = "etag" policykey = "policy" bucketLockKey = "bucket-lock" - objectLockKey = "object-lock" + objectRetentionKey = "object-retention" + objectLegalHoldKey = "object-legal-hold" ) type PosixOpts struct { @@ -2015,8 +2014,8 @@ 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) +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) } @@ -2024,30 +2023,8 @@ func (p *Posix) PutObjectLockConfiguration(_ context.Context, input *s3.PutObjec 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) + if err := p.meta.StoreAttribute(bucket, "", bucketLockKey, config); err != nil { + return fmt.Errorf("set object lock config: %w", err) } return nil @@ -2062,7 +2039,7 @@ func (p *Posix) GetObjectLockConfiguration(_ context.Context, bucket string) ([] return nil, fmt.Errorf("stat bucket: %w", err) } - cfg, err := xattr.Get(bucket, bucketLockKey) + cfg, err := p.meta.RetrieveAttribute(bucket, "", bucketLockKey) if errors.Is(err, meta.ErrNoSuchKey) { return nil, s3err.GetAPIError(s3err.ErrObjectLockConfigurationNotFound) } @@ -2073,8 +2050,8 @@ func (p *Posix) GetObjectLockConfiguration(_ context.Context, bucket string) ([] return cfg, nil } -func (p *Posix) PutObjectLegalHold(_ context.Context, input *s3.PutObjectLegalHoldInput) error { - _, err := os.Stat(*input.Bucket) +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) } @@ -2082,7 +2059,7 @@ func (p *Posix) PutObjectLegalHold(_ context.Context, input *s3.PutObjectLegalHo return fmt.Errorf("stat bucket: %w", err) } - cfg, err := xattr.Get(*input.Bucket, bucketLockKey) + cfg, err := p.meta.RetrieveAttribute(bucket, "", bucketLockKey) if errors.Is(err, meta.ErrNoSuchKey) { return s3err.GetAPIError(s3err.ErrInvalidBucketObjectLockConfiguration) } @@ -2099,40 +2076,14 @@ func (p *Posix) PutObjectLegalHold(_ context.Context, input *s3.PutObjectLegalHo 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{} + var statusData []byte + if status { + statusData = []byte{1} } else { - if err := json.Unmarshal(data, &config); err != nil { - return fmt.Errorf("parse object lock data %w", err) - } + statusData = []byte{0} } - 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) + err = p.meta.StoreAttribute(bucket, object, objectLegalHoldKey, statusData) if errors.Is(err, fs.ErrNotExist) { return s3err.GetAPIError(s3err.ErrNoSuchKey) } @@ -2143,7 +2094,7 @@ func (p *Posix) PutObjectLegalHold(_ context.Context, input *s3.PutObjectLegalHo return nil } -func (p *Posix) GetObjectLegalHold(_ context.Context, bucket, object, versionId string) (*types.ObjectLockLegalHold, error) { +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) @@ -2152,7 +2103,7 @@ func (p *Posix) GetObjectLegalHold(_ context.Context, bucket, object, versionId return nil, fmt.Errorf("stat bucket: %w", err) } - data, err := xattr.Get(filepath.Join(bucket, object), objectLockKey) + data, err := p.meta.RetrieveAttribute(bucket, object, objectLegalHoldKey) if errors.Is(err, fs.ErrNotExist) { return nil, s3err.GetAPIError(s3err.ErrNoSuchKey) } @@ -2163,24 +2114,13 @@ func (p *Posix) GetObjectLegalHold(_ context.Context, bucket, object, versionId 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 := data[0] == 1 - result := &types.ObjectLockLegalHold{} - - if config.LegalHoldEnabled { - result.Status = types.ObjectLockLegalHoldStatusOn - } else { - result.Status = types.ObjectLockLegalHoldStatusOff - } - - return result, nil + return &result, nil } -func (p *Posix) PutObjectRetention(_ context.Context, input *s3.PutObjectRetentionInput) error { - _, err := os.Stat(*input.Bucket) +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) } @@ -2188,7 +2128,7 @@ func (p *Posix) PutObjectRetention(_ context.Context, input *s3.PutObjectRetenti return fmt.Errorf("stat bucket: %w", err) } - cfg, err := xattr.Get(*input.Bucket, bucketLockKey) + cfg, err := p.meta.RetrieveAttribute(bucket, "", bucketLockKey) if errors.Is(err, meta.ErrNoSuchKey) { return s3err.GetAPIError(s3err.ErrInvalidBucketObjectLockConfiguration) } @@ -2205,33 +2145,7 @@ func (p *Posix) PutObjectRetention(_ context.Context, input *s3.PutObjectRetenti 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) + err = p.meta.StoreAttribute(bucket, object, objectRetentionKey, retention) if errors.Is(err, fs.ErrNotExist) { return s3err.GetAPIError(s3err.ErrNoSuchKey) } @@ -2242,7 +2156,7 @@ func (p *Posix) PutObjectRetention(_ context.Context, input *s3.PutObjectRetenti return nil } -func (p *Posix) GetObjectRetention(_ context.Context, bucket, object, versionId string) (*types.ObjectLockRetention, error) { +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) @@ -2251,7 +2165,7 @@ func (p *Posix) GetObjectRetention(_ context.Context, bucket, object, versionId return nil, fmt.Errorf("stat bucket: %w", err) } - data, err := xattr.Get(filepath.Join(bucket, object), objectLockKey) + data, err := p.meta.RetrieveAttribute(bucket, object, objectRetentionKey) if errors.Is(err, fs.ErrNotExist) { return nil, s3err.GetAPIError(s3err.ErrNoSuchKey) } @@ -2262,16 +2176,7 @@ func (p *Posix) GetObjectRetention(_ context.Context, bucket, object, versionId 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 + return data, nil } func (p *Posix) ChangeBucketOwner(ctx context.Context, bucket, newOwner string) error { diff --git a/s3api/controllers/backend_moq_test.go b/s3api/controllers/backend_moq_test.go index f56e6ce..b943fed 100644 --- a/s3api/controllers/backend_moq_test.go +++ b/s3api/controllers/backend_moq_test.go @@ -7,7 +7,6 @@ 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" @@ -81,13 +80,13 @@ 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) { +// 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) (*types.ObjectLockRetention, error) { +// 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) { @@ -138,13 +137,13 @@ 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 { +// 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, putObjectLockConfigurationInput *s3.PutObjectLockConfigurationInput) error { +// PutObjectLockConfigurationFunc: func(contextMoqParam context.Context, bucket string, config []byte) error { // panic("mock out the PutObjectLockConfiguration method") // }, -// PutObjectRetentionFunc: func(contextMoqParam context.Context, putObjectRetentionInput *s3.PutObjectRetentionInput) error { +// 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 { @@ -233,13 +232,13 @@ type BackendMock struct { 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) + 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) (*types.ObjectLockRetention, error) + 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) @@ -290,13 +289,13 @@ type BackendMock struct { PutObjectAclFunc func(contextMoqParam context.Context, putObjectAclInput *s3.PutObjectAclInput) error // PutObjectLegalHoldFunc mocks the PutObjectLegalHold method. - PutObjectLegalHoldFunc func(contextMoqParam context.Context, putObjectLegalHoldInput *s3.PutObjectLegalHoldInput) error + PutObjectLegalHoldFunc func(contextMoqParam context.Context, bucket string, object string, versionId string, status bool) error // PutObjectLockConfigurationFunc mocks the PutObjectLockConfiguration method. - PutObjectLockConfigurationFunc func(contextMoqParam context.Context, putObjectLockConfigurationInput *s3.PutObjectLockConfigurationInput) error + PutObjectLockConfigurationFunc func(contextMoqParam context.Context, bucket string, config []byte) error // PutObjectRetentionFunc mocks the PutObjectRetention method. - PutObjectRetentionFunc func(contextMoqParam context.Context, putObjectRetentionInput *s3.PutObjectRetentionInput) error + 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 @@ -615,22 +614,36 @@ type BackendMock struct { PutObjectLegalHold []struct { // ContextMoqParam is the contextMoqParam argument value. ContextMoqParam context.Context - // PutObjectLegalHoldInput is the putObjectLegalHoldInput argument value. - PutObjectLegalHoldInput *s3.PutObjectLegalHoldInput + // 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 - // PutObjectLockConfigurationInput is the putObjectLockConfigurationInput argument value. - PutObjectLockConfigurationInput *s3.PutObjectLockConfigurationInput + // 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 - // PutObjectRetentionInput is the putObjectRetentionInput argument value. - PutObjectRetentionInput *s3.PutObjectRetentionInput + // 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 { @@ -1429,7 +1442,7 @@ func (mock *BackendMock) GetObjectAttributesCalls() []struct { } // GetObjectLegalHold calls GetObjectLegalHoldFunc. -func (mock *BackendMock) GetObjectLegalHold(contextMoqParam context.Context, bucket string, object string, versionId string) (*types.ObjectLockLegalHold, error) { +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") } @@ -1509,7 +1522,7 @@ func (mock *BackendMock) GetObjectLockConfigurationCalls() []struct { } // GetObjectRetention calls GetObjectRetentionFunc. -func (mock *BackendMock) GetObjectRetention(contextMoqParam context.Context, bucket string, object string, versionId string) (*types.ObjectLockRetention, error) { +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") } @@ -2145,21 +2158,27 @@ func (mock *BackendMock) PutObjectAclCalls() []struct { } // PutObjectLegalHold calls PutObjectLegalHoldFunc. -func (mock *BackendMock) PutObjectLegalHold(contextMoqParam context.Context, putObjectLegalHoldInput *s3.PutObjectLegalHoldInput) error { +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 - PutObjectLegalHoldInput *s3.PutObjectLegalHoldInput + ContextMoqParam context.Context + Bucket string + Object string + VersionId string + Status bool }{ - ContextMoqParam: contextMoqParam, - PutObjectLegalHoldInput: putObjectLegalHoldInput, + 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, putObjectLegalHoldInput) + return mock.PutObjectLegalHoldFunc(contextMoqParam, bucket, object, versionId, status) } // PutObjectLegalHoldCalls gets all the calls that were made to PutObjectLegalHold. @@ -2167,12 +2186,18 @@ func (mock *BackendMock) PutObjectLegalHold(contextMoqParam context.Context, put // // len(mockedBackend.PutObjectLegalHoldCalls()) func (mock *BackendMock) PutObjectLegalHoldCalls() []struct { - ContextMoqParam context.Context - PutObjectLegalHoldInput *s3.PutObjectLegalHoldInput + ContextMoqParam context.Context + Bucket string + Object string + VersionId string + Status bool } { var calls []struct { - ContextMoqParam context.Context - PutObjectLegalHoldInput *s3.PutObjectLegalHoldInput + ContextMoqParam context.Context + Bucket string + Object string + VersionId string + Status bool } mock.lockPutObjectLegalHold.RLock() calls = mock.calls.PutObjectLegalHold @@ -2181,21 +2206,23 @@ func (mock *BackendMock) PutObjectLegalHoldCalls() []struct { } // PutObjectLockConfiguration calls PutObjectLockConfigurationFunc. -func (mock *BackendMock) PutObjectLockConfiguration(contextMoqParam context.Context, putObjectLockConfigurationInput *s3.PutObjectLockConfigurationInput) error { +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 - PutObjectLockConfigurationInput *s3.PutObjectLockConfigurationInput + ContextMoqParam context.Context + Bucket string + Config []byte }{ - ContextMoqParam: contextMoqParam, - PutObjectLockConfigurationInput: putObjectLockConfigurationInput, + ContextMoqParam: contextMoqParam, + Bucket: bucket, + Config: config, } mock.lockPutObjectLockConfiguration.Lock() mock.calls.PutObjectLockConfiguration = append(mock.calls.PutObjectLockConfiguration, callInfo) mock.lockPutObjectLockConfiguration.Unlock() - return mock.PutObjectLockConfigurationFunc(contextMoqParam, putObjectLockConfigurationInput) + return mock.PutObjectLockConfigurationFunc(contextMoqParam, bucket, config) } // PutObjectLockConfigurationCalls gets all the calls that were made to PutObjectLockConfiguration. @@ -2203,12 +2230,14 @@ func (mock *BackendMock) PutObjectLockConfiguration(contextMoqParam context.Cont // // len(mockedBackend.PutObjectLockConfigurationCalls()) func (mock *BackendMock) PutObjectLockConfigurationCalls() []struct { - ContextMoqParam context.Context - PutObjectLockConfigurationInput *s3.PutObjectLockConfigurationInput + ContextMoqParam context.Context + Bucket string + Config []byte } { var calls []struct { - ContextMoqParam context.Context - PutObjectLockConfigurationInput *s3.PutObjectLockConfigurationInput + ContextMoqParam context.Context + Bucket string + Config []byte } mock.lockPutObjectLockConfiguration.RLock() calls = mock.calls.PutObjectLockConfiguration @@ -2217,21 +2246,27 @@ func (mock *BackendMock) PutObjectLockConfigurationCalls() []struct { } // PutObjectRetention calls PutObjectRetentionFunc. -func (mock *BackendMock) PutObjectRetention(contextMoqParam context.Context, putObjectRetentionInput *s3.PutObjectRetentionInput) error { +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 - PutObjectRetentionInput *s3.PutObjectRetentionInput + ContextMoqParam context.Context + Bucket string + Object string + VersionId string + Retention []byte }{ - ContextMoqParam: contextMoqParam, - PutObjectRetentionInput: putObjectRetentionInput, + 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, putObjectRetentionInput) + return mock.PutObjectRetentionFunc(contextMoqParam, bucket, object, versionId, retention) } // PutObjectRetentionCalls gets all the calls that were made to PutObjectRetention. @@ -2239,12 +2274,18 @@ func (mock *BackendMock) PutObjectRetention(contextMoqParam context.Context, put // // len(mockedBackend.PutObjectRetentionCalls()) func (mock *BackendMock) PutObjectRetentionCalls() []struct { - ContextMoqParam context.Context - PutObjectRetentionInput *s3.PutObjectRetentionInput + ContextMoqParam context.Context + Bucket string + Object string + VersionId string + Retention []byte } { var calls []struct { - ContextMoqParam context.Context - PutObjectRetentionInput *s3.PutObjectRetentionInput + ContextMoqParam context.Context + Bucket string + Object string + VersionId string + Retention []byte } mock.lockPutObjectRetention.RLock() calls = mock.calls.PutObjectRetention diff --git a/s3api/controllers/base.go b/s3api/controllers/base.go index fa6d632..97509d5 100644 --- a/s3api/controllers/base.go +++ b/s3api/controllers/base.go @@ -151,7 +151,17 @@ func (c S3ApiController) GetActions(ctx *fiber.Ctx) error { } data, err := c.be.GetObjectRetention(ctx.Context(), bucket, key, versionId) - return SendXMLResponse(ctx, data, err, + 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", @@ -179,7 +189,7 @@ func (c S3ApiController) GetActions(ctx *fiber.Ctx) error { } data, err := c.be.GetObjectLegalHold(ctx.Context(), bucket, key, versionId) - return SendXMLResponse(ctx, data, err, + return SendXMLResponse(ctx, auth.ParseObjectLegalHoldOutput(data), err, &MetaOpts{ Logger: c.logger, Action: "GetObjectLegalHold", @@ -941,16 +951,6 @@ 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, @@ -967,10 +967,17 @@ func (c S3ApiController) PutBucketActions(ctx *fiber.Ctx) error { }) } - err := c.be.PutObjectLockConfiguration(ctx.Context(), &s3.PutObjectLockConfigurationInput{ - Bucket: &bucket, - ObjectLockConfiguration: &input, - }) + 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, @@ -1211,7 +1218,6 @@ func (c S3ApiController) PutActions(ctx *fiber.Ctx) error { 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) @@ -1313,24 +1319,6 @@ 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, @@ -1348,15 +1336,16 @@ func (c S3ApiController) PutActions(ctx *fiber.Ctx) error { }) } - pass := bypassGovernanceRetention == "True" + 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(), &s3.PutObjectRetentionInput{ - Bucket: &bucket, - Key: &keyStart, - VersionId: &versionId, - Retention: &retention, - BypassGovernanceRetention: &pass, - }) + err = c.be.PutObjectRetention(ctx.Context(), bucket, keyStart, versionId, retention) return SendResponse(ctx, err, &MetaOpts{ Logger: c.logger, Action: "PutObjectRetention", @@ -1391,12 +1380,7 @@ func (c S3ApiController) PutActions(ctx *fiber.Ctx) error { }) } - err := c.be.PutObjectLegalHold(ctx.Context(), &s3.PutObjectLegalHoldInput{ - Bucket: &bucket, - Key: &keyStart, - VersionId: &versionId, - LegalHold: &legalHold, - }) + err := c.be.PutObjectLegalHold(ctx.Context(), bucket, keyStart, versionId, legalHold.Status == types.ObjectLockLegalHoldStatusOn) return SendResponse(ctx, err, &MetaOpts{ Logger: c.logger, Action: "PutObjectLegalHold", diff --git a/s3api/controllers/base_test.go b/s3api/controllers/base_test.go index a9f09a3..b522e38 100644 --- a/s3api/controllers/base_test.go +++ b/s3api/controllers/base_test.go @@ -205,11 +205,18 @@ 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 + 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) (*types.ObjectLockLegalHold, error) { - return &types.ObjectLockLegalHold{}, nil + GetObjectLegalHoldFunc: func(contextMoqParam context.Context, bucket, object, versionId string) (*bool, error) { + result := true + return &result, nil }, }, } @@ -657,7 +664,7 @@ 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 { + PutObjectLockConfigurationFunc: func(contextMoqParam context.Context, bucket string, config []byte) error { return nil }, }, @@ -919,10 +926,10 @@ 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 { + PutObjectLegalHoldFunc: func(contextMoqParam context.Context, bucket, object, versionId string, status bool) error { return nil }, - PutObjectRetentionFunc: func(contextMoqParam context.Context, putObjectRetentionInput *s3.PutObjectRetentionInput) error { + PutObjectRetentionFunc: func(contextMoqParam context.Context, bucket, object, versionId string, retention []byte) error { return nil }, GetObjectLockConfigurationFunc: func(contextMoqParam context.Context, bucket string) ([]byte, error) {