diff --git a/backend/posix/posix.go b/backend/posix/posix.go index 397398cb..50a6c6a9 100644 --- a/backend/posix/posix.go +++ b/backend/posix/posix.go @@ -325,6 +325,15 @@ func (p *Posix) CreateBucket(ctx context.Context, input *s3.CreateBucketInput, a } if input.ObjectLockEnabledForBucket != nil && *input.ObjectLockEnabledForBucket { + // First enable bucket versioning + // Bucket versioning is enabled automatically with object lock + if p.versioningEnabled() { + err = p.PutBucketVersioning(ctx, bucket, types.BucketVersioningStatusEnabled) + if err != nil { + return err + } + } + now := time.Now() defaultLock := auth.BucketLockConfig{ Enabled: true, @@ -437,7 +446,7 @@ func (p *Posix) DeleteBucketOwnershipControls(_ context.Context, bucket string) return nil } -func (p *Posix) PutBucketVersioning(_ context.Context, bucket string, status types.BucketVersioningStatus) error { +func (p *Posix) PutBucketVersioning(ctx context.Context, bucket string, status types.BucketVersioningStatus) error { if !p.versioningEnabled() { //TODO: Maybe we need to return our custom error here? return nil @@ -457,6 +466,18 @@ func (p *Posix) PutBucketVersioning(_ context.Context, bucket string, status typ // '1' maps to 'Enabled' versioning = []byte{1} case types.BucketVersioningStatusSuspended: + lockRaw, err := p.GetObjectLockConfiguration(ctx, bucket) + if err != nil { + return err + } + lockStatus, err := auth.ParseBucketLockConfigurationOutput(lockRaw) + if err != nil { + return err + } + if lockStatus.ObjectLockEnabled == types.ObjectLockEnabledEnabled { + return s3err.GetAPIError(s3err.ErrSuspendedVersioningNotAllowed) + } + // '0' maps to 'Suspended' versioning = []byte{0} } @@ -3409,7 +3430,7 @@ func (p *Posix) DeleteBucketPolicy(ctx context.Context, bucket string) error { return p.PutBucketPolicy(ctx, bucket, nil) } -func (p *Posix) PutObjectLockConfiguration(_ context.Context, bucket string, config []byte) error { +func (p *Posix) PutObjectLockConfiguration(ctx context.Context, bucket string, config []byte) error { _, err := os.Stat(bucket) if errors.Is(err, fs.ErrNotExist) { return s3err.GetAPIError(s3err.ErrNoSuchBucket) diff --git a/s3err/s3err.go b/s3err/s3err.go index 14fa70ae..dac0e295 100644 --- a/s3err/s3err.go +++ b/s3err/s3err.go @@ -134,6 +134,7 @@ const ( ErrKeyTooLong ErrInvalidVersionId ErrNoSuchVersion + ErrSuspendedVersioningNotAllowed // Non-AWS errors ErrExistingObjectIsDirectory @@ -537,6 +538,11 @@ var errorCodeResponse = map[ErrorCode]APIError{ Description: "The specified version does not exist.", HTTPStatusCode: http.StatusNotFound, }, + ErrSuspendedVersioningNotAllowed: { + Code: "InvalidBucketState", + Description: "An Object Lock configuration is present on this bucket, so the versioning state cannot be changed.", + HTTPStatusCode: http.StatusBadRequest, + }, // non aws errors ErrExistingObjectIsDirectory: { diff --git a/tests/integration/group-tests.go b/tests/integration/group-tests.go index c207081b..a46582aa 100644 --- a/tests/integration/group-tests.go +++ b/tests/integration/group-tests.go @@ -553,6 +553,9 @@ func TestVersioning(s *S3Conf) { Versioning_Multipart_Upload_overwrite_an_object(s) Versioning_UploadPartCopy_non_existing_versionId(s) Versioning_UploadPartCopy_from_an_object_version(s) + // Object lock configuration + Versioning_Enable_object_lock(s) + Versioning_status_switch_to_suspended_with_object_lock(s) // Object-Lock Retention Versionsin_PutObjectRetention_invalid_versionId(s) Versioning_GetObjectRetention_invalid_versionId(s) @@ -912,6 +915,8 @@ func GetIntTests() IntTests { "Versioning_Multipart_Upload_overwrite_an_object": Versioning_Multipart_Upload_overwrite_an_object, "Versioning_UploadPartCopy_non_existing_versionId": Versioning_UploadPartCopy_non_existing_versionId, "Versioning_UploadPartCopy_from_an_object_version": Versioning_UploadPartCopy_from_an_object_version, + "Versioning_Enable_object_lock": Versioning_Enable_object_lock, + "Versioning_status_switch_to_suspended_with_object_lock": Versioning_status_switch_to_suspended_with_object_lock, "Versionsin_PutObjectRetention_invalid_versionId": Versionsin_PutObjectRetention_invalid_versionId, "Versioning_GetObjectRetention_invalid_versionId": Versioning_GetObjectRetention_invalid_versionId, "Versioning_Put_GetObjectRetention_success": Versioning_Put_GetObjectRetention_success, diff --git a/tests/integration/tests.go b/tests/integration/tests.go index e585582d..de44a565 100644 --- a/tests/integration/tests.go +++ b/tests/integration/tests.go @@ -11746,6 +11746,45 @@ func Versioning_UploadPartCopy_from_an_object_version(s *S3Conf) error { }, withVersioning()) } +func Versioning_Enable_object_lock(s *S3Conf) error { + testName := "Versioning_Enable_object_lock" + return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { + ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) + res, err := s3client.GetBucketVersioning(ctx, &s3.GetBucketVersioningInput{ + Bucket: &bucket, + }) + cancel() + if err != nil { + return err + } + + if res.Status != types.BucketVersioningStatusEnabled { + return fmt.Errorf("expected the bucket versioning status to be %v, instead got %v", types.BucketVersioningStatusEnabled, res.Status) + } + + return nil + }, withLock()) +} + +func Versioning_status_switch_to_suspended_with_object_lock(s *S3Conf) error { + testName := "Versioning_status_switch_to_suspended_with_object_lock" + return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { + ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) + _, err := s3client.PutBucketVersioning(ctx, &s3.PutBucketVersioningInput{ + Bucket: &bucket, + VersioningConfiguration: &types.VersioningConfiguration{ + Status: types.BucketVersioningStatusSuspended, + }, + }) + cancel() + if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrSuspendedVersioningNotAllowed)); err != nil { + return err + } + + return nil + }, withLock()) +} + func Versionsin_PutObjectRetention_invalid_versionId(s *S3Conf) error { testName := "Versionsin_PutObjectRetention_invalid_versionId" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {