aboutsummaryrefslogtreecommitdiff
path: root/test/s3/retention/s3_object_lock_headers_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'test/s3/retention/s3_object_lock_headers_test.go')
-rw-r--r--test/s3/retention/s3_object_lock_headers_test.go307
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)
+}