You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							117 lines
						
					
					
						
							4.9 KiB
						
					
					
				
			
		
		
		
			
			
			
		
		
	
	
							117 lines
						
					
					
						
							4.9 KiB
						
					
					
				| package retention | |
| 
 | |
| import ( | |
| 	"context" | |
| 	"fmt" | |
| 	"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/require" | |
| ) | |
| 
 | |
| // TestObjectLockValidation tests that S3 Object Lock functionality works end-to-end | |
| // This test focuses on the complete Object Lock workflow that S3 clients expect | |
| func TestObjectLockValidation(t *testing.T) { | |
| 	client := getS3Client(t) | |
| 	bucketName := fmt.Sprintf("object-lock-test-%d", time.Now().UnixNano()) | |
| 
 | |
| 	t.Logf("=== Validating S3 Object Lock Functionality ===") | |
| 	t.Logf("Bucket: %s", bucketName) | |
| 
 | |
| 	// Step 1: Create bucket with Object Lock header | |
| 	t.Log("\n1. Creating bucket with x-amz-bucket-object-lock-enabled: true") | |
| 	_, err := client.CreateBucket(context.TODO(), &s3.CreateBucketInput{ | |
| 		Bucket:                     aws.String(bucketName), | |
| 		ObjectLockEnabledForBucket: aws.Bool(true), // This sends x-amz-bucket-object-lock-enabled: true | |
| 	}) | |
| 	require.NoError(t, err, "Bucket creation should succeed") | |
| 	defer client.DeleteBucket(context.TODO(), &s3.DeleteBucketInput{Bucket: aws.String(bucketName)}) | |
| 	t.Log("   Bucket created successfully") | |
| 
 | |
| 	// Step 2: Check if Object Lock is supported (standard S3 client behavior) | |
| 	t.Log("\n2. Testing Object Lock support detection") | |
| 	_, err = client.GetObjectLockConfiguration(context.TODO(), &s3.GetObjectLockConfigurationInput{ | |
| 		Bucket: aws.String(bucketName), | |
| 	}) | |
| 	require.NoError(t, err, "GetObjectLockConfiguration should succeed for Object Lock enabled bucket") | |
| 	t.Log("   GetObjectLockConfiguration succeeded - Object Lock is properly enabled") | |
| 
 | |
| 	// Step 3: Verify versioning is enabled (required for Object Lock) | |
| 	t.Log("\n3. Verifying versioning is automatically enabled") | |
| 	versioningResp, err := client.GetBucketVersioning(context.TODO(), &s3.GetBucketVersioningInput{ | |
| 		Bucket: aws.String(bucketName), | |
| 	}) | |
| 	require.NoError(t, err) | |
| 	require.Equal(t, types.BucketVersioningStatusEnabled, versioningResp.Status, "Versioning should be automatically enabled") | |
| 	t.Log("   Versioning automatically enabled") | |
| 
 | |
| 	// Step 4: Test actual Object Lock functionality | |
| 	t.Log("\n4. Testing Object Lock retention functionality") | |
| 
 | |
| 	// Create an object | |
| 	key := "protected-object.dat" | |
| 	content := "Important data that needs immutable protection" | |
| 	putResp, err := client.PutObject(context.TODO(), &s3.PutObjectInput{ | |
| 		Bucket: aws.String(bucketName), | |
| 		Key:    aws.String(key), | |
| 		Body:   strings.NewReader(content), | |
| 	}) | |
| 	require.NoError(t, err) | |
| 	require.NotNil(t, putResp.VersionId, "Object should have a version ID") | |
| 	t.Log("   Object created with versioning") | |
| 
 | |
| 	// Apply Object Lock retention | |
| 	retentionUntil := time.Now().Add(24 * time.Hour) | |
| 	_, err = client.PutObjectRetention(context.TODO(), &s3.PutObjectRetentionInput{ | |
| 		Bucket: aws.String(bucketName), | |
| 		Key:    aws.String(key), | |
| 		Retention: &types.ObjectLockRetention{ | |
| 			Mode:            types.ObjectLockRetentionModeCompliance, | |
| 			RetainUntilDate: aws.Time(retentionUntil), | |
| 		}, | |
| 	}) | |
| 	require.NoError(t, err, "Setting Object Lock retention should succeed") | |
| 	t.Log("   Object Lock retention applied successfully") | |
| 
 | |
| 	// Verify retention allows simple DELETE (creates delete marker) but blocks version deletion | |
| 	// AWS S3 behavior: Simple DELETE (without version ID) is ALWAYS allowed and creates delete marker | |
| 	_, err = client.DeleteObject(context.TODO(), &s3.DeleteObjectInput{ | |
| 		Bucket: aws.String(bucketName), | |
| 		Key:    aws.String(key), | |
| 	}) | |
| 	require.NoError(t, err, "Simple DELETE should succeed and create delete marker (AWS S3 behavior)") | |
| 	t.Log("   Simple DELETE succeeded (creates delete marker - correct AWS behavior)") | |
| 
 | |
| 	// Now verify that DELETE with version ID is properly blocked by retention | |
| 	_, err = client.DeleteObject(context.TODO(), &s3.DeleteObjectInput{ | |
| 		Bucket:    aws.String(bucketName), | |
| 		Key:       aws.String(key), | |
| 		VersionId: putResp.VersionId, | |
| 	}) | |
| 	require.Error(t, err, "DELETE with version ID should be blocked by COMPLIANCE retention") | |
| 	t.Log("   Object version is properly protected by retention policy") | |
| 
 | |
| 	// Verify we can read the object version (should still work) | |
| 	// Note: Need to specify version ID since latest version is now a delete marker | |
| 	getResp, err := client.GetObject(context.TODO(), &s3.GetObjectInput{ | |
| 		Bucket:    aws.String(bucketName), | |
| 		Key:       aws.String(key), | |
| 		VersionId: putResp.VersionId, | |
| 	}) | |
| 	require.NoError(t, err, "Reading protected object version should still work") | |
| 	defer getResp.Body.Close() | |
| 	t.Log("   Protected object can still be read") | |
| 
 | |
| 	t.Log("\nS3 OBJECT LOCK VALIDATION SUCCESSFUL!") | |
| 	t.Log("   - Bucket creation with Object Lock header works") | |
| 	t.Log("   - Object Lock support detection works (GetObjectLockConfiguration succeeds)") | |
| 	t.Log("   - Versioning is automatically enabled") | |
| 	t.Log("   - Object Lock retention functionality works") | |
| 	t.Log("   - Objects are properly protected from deletion") | |
| 	t.Log("") | |
| 	t.Log("S3 clients will now recognize SeaweedFS as supporting Object Lock!") | |
| }
 |