diff options
Diffstat (limited to 'test/s3/retention/s3_object_lock_headers_test.go')
| -rw-r--r-- | test/s3/retention/s3_object_lock_headers_test.go | 307 |
1 files changed, 307 insertions, 0 deletions
diff --git a/test/s3/retention/s3_object_lock_headers_test.go b/test/s3/retention/s3_object_lock_headers_test.go new file mode 100644 index 000000000..bf7283617 --- /dev/null +++ b/test/s3/retention/s3_object_lock_headers_test.go @@ -0,0 +1,307 @@ +package retention + +import ( + "context" + "strings" + "testing" + "time" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/s3" + "github.com/aws/aws-sdk-go-v2/service/s3/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestPutObjectWithLockHeaders tests that object lock headers in PUT requests +// are properly stored and returned in HEAD responses +func TestPutObjectWithLockHeaders(t *testing.T) { + client := getS3Client(t) + bucketName := getNewBucketName() + + // Create bucket with object lock enabled and versioning + createBucketWithObjectLock(t, client, bucketName) + defer deleteBucket(t, client, bucketName) + + key := "test-object-lock-headers" + content := "test content with object lock headers" + retainUntilDate := time.Now().Add(24 * time.Hour) + + // Test 1: PUT with COMPLIANCE mode and retention date + t.Run("PUT with COMPLIANCE mode", func(t *testing.T) { + testKey := key + "-compliance" + + // PUT object with lock headers + putResp := putObjectWithLockHeaders(t, client, bucketName, testKey, content, + "COMPLIANCE", retainUntilDate, "") + require.NotNil(t, putResp.VersionId) + + // HEAD object and verify lock headers are returned + headResp, err := client.HeadObject(context.TODO(), &s3.HeadObjectInput{ + Bucket: aws.String(bucketName), + Key: aws.String(testKey), + }) + require.NoError(t, err) + + // Verify object lock metadata is present in response + assert.Equal(t, types.ObjectLockModeCompliance, headResp.ObjectLockMode) + assert.NotNil(t, headResp.ObjectLockRetainUntilDate) + assert.WithinDuration(t, retainUntilDate, *headResp.ObjectLockRetainUntilDate, 5*time.Second) + }) + + // Test 2: PUT with GOVERNANCE mode and retention date + t.Run("PUT with GOVERNANCE mode", func(t *testing.T) { + testKey := key + "-governance" + + putResp := putObjectWithLockHeaders(t, client, bucketName, testKey, content, + "GOVERNANCE", retainUntilDate, "") + require.NotNil(t, putResp.VersionId) + + headResp, err := client.HeadObject(context.TODO(), &s3.HeadObjectInput{ + Bucket: aws.String(bucketName), + Key: aws.String(testKey), + }) + require.NoError(t, err) + + assert.Equal(t, types.ObjectLockModeGovernance, headResp.ObjectLockMode) + assert.NotNil(t, headResp.ObjectLockRetainUntilDate) + assert.WithinDuration(t, retainUntilDate, *headResp.ObjectLockRetainUntilDate, 5*time.Second) + }) + + // Test 3: PUT with legal hold + t.Run("PUT with legal hold", func(t *testing.T) { + testKey := key + "-legal-hold" + + putResp := putObjectWithLockHeaders(t, client, bucketName, testKey, content, + "", time.Time{}, "ON") + require.NotNil(t, putResp.VersionId) + + headResp, err := client.HeadObject(context.TODO(), &s3.HeadObjectInput{ + Bucket: aws.String(bucketName), + Key: aws.String(testKey), + }) + require.NoError(t, err) + + assert.Equal(t, types.ObjectLockLegalHoldStatusOn, headResp.ObjectLockLegalHoldStatus) + }) + + // Test 4: PUT with both retention and legal hold + t.Run("PUT with both retention and legal hold", func(t *testing.T) { + testKey := key + "-both" + + putResp := putObjectWithLockHeaders(t, client, bucketName, testKey, content, + "GOVERNANCE", retainUntilDate, "ON") + require.NotNil(t, putResp.VersionId) + + headResp, err := client.HeadObject(context.TODO(), &s3.HeadObjectInput{ + Bucket: aws.String(bucketName), + Key: aws.String(testKey), + }) + require.NoError(t, err) + + assert.Equal(t, types.ObjectLockModeGovernance, headResp.ObjectLockMode) + assert.NotNil(t, headResp.ObjectLockRetainUntilDate) + assert.Equal(t, types.ObjectLockLegalHoldStatusOn, headResp.ObjectLockLegalHoldStatus) + }) +} + +// TestGetObjectWithLockHeaders verifies that GET requests also return object lock metadata +func TestGetObjectWithLockHeaders(t *testing.T) { + client := getS3Client(t) + bucketName := getNewBucketName() + + createBucketWithObjectLock(t, client, bucketName) + defer deleteBucket(t, client, bucketName) + + key := "test-get-object-lock" + content := "test content for GET with lock headers" + retainUntilDate := time.Now().Add(24 * time.Hour) + + // PUT object with lock headers + putResp := putObjectWithLockHeaders(t, client, bucketName, key, content, + "COMPLIANCE", retainUntilDate, "ON") + require.NotNil(t, putResp.VersionId) + + // GET object and verify lock headers are returned + getResp, err := client.GetObject(context.TODO(), &s3.GetObjectInput{ + Bucket: aws.String(bucketName), + Key: aws.String(key), + }) + require.NoError(t, err) + defer getResp.Body.Close() + + // Verify object lock metadata is present in GET response + assert.Equal(t, types.ObjectLockModeCompliance, getResp.ObjectLockMode) + assert.NotNil(t, getResp.ObjectLockRetainUntilDate) + assert.WithinDuration(t, retainUntilDate, *getResp.ObjectLockRetainUntilDate, 5*time.Second) + assert.Equal(t, types.ObjectLockLegalHoldStatusOn, getResp.ObjectLockLegalHoldStatus) +} + +// TestVersionedObjectLockHeaders tests object lock headers work with versioned objects +func TestVersionedObjectLockHeaders(t *testing.T) { + client := getS3Client(t) + bucketName := getNewBucketName() + + createBucketWithObjectLock(t, client, bucketName) + defer deleteBucket(t, client, bucketName) + + key := "test-versioned-lock" + content1 := "version 1 content" + content2 := "version 2 content" + retainUntilDate1 := time.Now().Add(12 * time.Hour) + retainUntilDate2 := time.Now().Add(24 * time.Hour) + + // PUT first version with GOVERNANCE mode + putResp1 := putObjectWithLockHeaders(t, client, bucketName, key, content1, + "GOVERNANCE", retainUntilDate1, "") + require.NotNil(t, putResp1.VersionId) + + // PUT second version with COMPLIANCE mode + putResp2 := putObjectWithLockHeaders(t, client, bucketName, key, content2, + "COMPLIANCE", retainUntilDate2, "ON") + require.NotNil(t, putResp2.VersionId) + require.NotEqual(t, *putResp1.VersionId, *putResp2.VersionId) + + // HEAD latest version (version 2) + headResp, err := client.HeadObject(context.TODO(), &s3.HeadObjectInput{ + Bucket: aws.String(bucketName), + Key: aws.String(key), + }) + require.NoError(t, err) + assert.Equal(t, types.ObjectLockModeCompliance, headResp.ObjectLockMode) + assert.Equal(t, types.ObjectLockLegalHoldStatusOn, headResp.ObjectLockLegalHoldStatus) + + // HEAD specific version 1 + headResp1, err := client.HeadObject(context.TODO(), &s3.HeadObjectInput{ + Bucket: aws.String(bucketName), + Key: aws.String(key), + VersionId: putResp1.VersionId, + }) + require.NoError(t, err) + assert.Equal(t, types.ObjectLockModeGovernance, headResp1.ObjectLockMode) + assert.NotEqual(t, types.ObjectLockLegalHoldStatusOn, headResp1.ObjectLockLegalHoldStatus) +} + +// TestObjectLockHeadersErrorCases tests various error scenarios +func TestObjectLockHeadersErrorCases(t *testing.T) { + client := getS3Client(t) + bucketName := getNewBucketName() + + createBucketWithObjectLock(t, client, bucketName) + defer deleteBucket(t, client, bucketName) + + key := "test-error-cases" + content := "test content for error cases" + + // Test 1: Invalid retention mode should be rejected + t.Run("Invalid retention mode", func(t *testing.T) { + _, err := client.PutObject(context.TODO(), &s3.PutObjectInput{ + Bucket: aws.String(bucketName), + Key: aws.String(key + "-invalid-mode"), + Body: strings.NewReader(content), + ObjectLockMode: "INVALID_MODE", // Invalid mode + ObjectLockRetainUntilDate: aws.Time(time.Now().Add(24 * time.Hour)), + }) + require.Error(t, err) + }) + + // Test 2: Retention date in the past should be rejected + t.Run("Past retention date", func(t *testing.T) { + _, err := client.PutObject(context.TODO(), &s3.PutObjectInput{ + Bucket: aws.String(bucketName), + Key: aws.String(key + "-past-date"), + Body: strings.NewReader(content), + ObjectLockMode: "GOVERNANCE", + ObjectLockRetainUntilDate: aws.Time(time.Now().Add(-24 * time.Hour)), // Past date + }) + require.Error(t, err) + }) + + // Test 3: Mode without date should be rejected + t.Run("Mode without retention date", func(t *testing.T) { + _, err := client.PutObject(context.TODO(), &s3.PutObjectInput{ + Bucket: aws.String(bucketName), + Key: aws.String(key + "-no-date"), + Body: strings.NewReader(content), + ObjectLockMode: "GOVERNANCE", + // Missing ObjectLockRetainUntilDate + }) + require.Error(t, err) + }) +} + +// TestObjectLockHeadersNonVersionedBucket tests that object lock fails on non-versioned buckets +func TestObjectLockHeadersNonVersionedBucket(t *testing.T) { + client := getS3Client(t) + bucketName := getNewBucketName() + + // Create regular bucket without object lock/versioning + createBucket(t, client, bucketName) + defer deleteBucket(t, client, bucketName) + + key := "test-non-versioned" + content := "test content" + retainUntilDate := time.Now().Add(24 * time.Hour) + + // Attempting to PUT with object lock headers should fail + _, err := client.PutObject(context.TODO(), &s3.PutObjectInput{ + Bucket: aws.String(bucketName), + Key: aws.String(key), + Body: strings.NewReader(content), + ObjectLockMode: "GOVERNANCE", + ObjectLockRetainUntilDate: aws.Time(retainUntilDate), + }) + require.Error(t, err) +} + +// Helper Functions + +// putObjectWithLockHeaders puts an object with object lock headers +func putObjectWithLockHeaders(t *testing.T, client *s3.Client, bucketName, key, content string, + mode string, retainUntilDate time.Time, legalHold string) *s3.PutObjectOutput { + + input := &s3.PutObjectInput{ + Bucket: aws.String(bucketName), + Key: aws.String(key), + Body: strings.NewReader(content), + } + + // Add retention mode and date if specified + if mode != "" { + switch mode { + case "COMPLIANCE": + input.ObjectLockMode = types.ObjectLockModeCompliance + case "GOVERNANCE": + input.ObjectLockMode = types.ObjectLockModeGovernance + } + if !retainUntilDate.IsZero() { + input.ObjectLockRetainUntilDate = aws.Time(retainUntilDate) + } + } + + // Add legal hold if specified + if legalHold != "" { + switch legalHold { + case "ON": + input.ObjectLockLegalHoldStatus = types.ObjectLockLegalHoldStatusOn + case "OFF": + input.ObjectLockLegalHoldStatus = types.ObjectLockLegalHoldStatusOff + } + } + + resp, err := client.PutObject(context.TODO(), input) + require.NoError(t, err) + return resp +} + +// createBucketWithObjectLock creates a bucket with object lock enabled +func createBucketWithObjectLock(t *testing.T, client *s3.Client, bucketName string) { + _, err := client.CreateBucket(context.TODO(), &s3.CreateBucketInput{ + Bucket: aws.String(bucketName), + ObjectLockEnabledForBucket: aws.Bool(true), + }) + require.NoError(t, err) + + // Enable versioning (required for object lock) + enableVersioning(t, client, bucketName) +} |
