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.
		
		
		
		
		
			
		
			
				
					
					
						
							596 lines
						
					
					
						
							18 KiB
						
					
					
				
			
		
		
		
			
			
			
		
		
	
	
							596 lines
						
					
					
						
							18 KiB
						
					
					
				| package iam | |
| 
 | |
| import ( | |
| 	"bytes" | |
| 	"fmt" | |
| 	"io" | |
| 	"strings" | |
| 	"testing" | |
| 
 | |
| 	"github.com/aws/aws-sdk-go/aws" | |
| 	"github.com/aws/aws-sdk-go/aws/awserr" | |
| 	"github.com/aws/aws-sdk-go/service/s3" | |
| 	"github.com/stretchr/testify/assert" | |
| 	"github.com/stretchr/testify/require" | |
| ) | |
| 
 | |
| const ( | |
| 	testEndpoint     = "http://localhost:8333" | |
| 	testRegion       = "us-west-2" | |
| 	testBucketPrefix = "test-iam-bucket" | |
| 	testObjectKey    = "test-object.txt" | |
| 	testObjectData   = "Hello, SeaweedFS IAM Integration!" | |
| ) | |
| 
 | |
| var ( | |
| 	testBucket = testBucketPrefix | |
| ) | |
| 
 | |
| // TestS3IAMAuthentication tests S3 API authentication with IAM JWT tokens | |
| func TestS3IAMAuthentication(t *testing.T) { | |
| 	framework := NewS3IAMTestFramework(t) | |
| 	defer framework.Cleanup() | |
| 
 | |
| 	t.Run("valid_jwt_token_authentication", func(t *testing.T) { | |
| 		// Create S3 client with valid JWT token | |
| 		s3Client, err := framework.CreateS3ClientWithJWT("admin-user", "TestAdminRole") | |
| 		require.NoError(t, err) | |
| 
 | |
| 		// Test bucket operations | |
| 		err = framework.CreateBucket(s3Client, testBucket) | |
| 		require.NoError(t, err) | |
| 
 | |
| 		// Verify bucket exists | |
| 		buckets, err := s3Client.ListBuckets(&s3.ListBucketsInput{}) | |
| 		require.NoError(t, err) | |
| 
 | |
| 		found := false | |
| 		for _, bucket := range buckets.Buckets { | |
| 			if *bucket.Name == testBucket { | |
| 				found = true | |
| 				break | |
| 			} | |
| 		} | |
| 		assert.True(t, found, "Created bucket should be listed") | |
| 	}) | |
| 
 | |
| 	t.Run("invalid_jwt_token_authentication", func(t *testing.T) { | |
| 		// Create S3 client with invalid JWT token | |
| 		s3Client, err := framework.CreateS3ClientWithInvalidJWT() | |
| 		require.NoError(t, err) | |
| 
 | |
| 		// Attempt bucket operations - should fail | |
| 		err = framework.CreateBucket(s3Client, testBucket+"-invalid") | |
| 		require.Error(t, err) | |
| 
 | |
| 		// Verify it's an access denied error | |
| 		if awsErr, ok := err.(awserr.Error); ok { | |
| 			assert.Equal(t, "AccessDenied", awsErr.Code()) | |
| 		} else { | |
| 			t.Error("Expected AWS error with AccessDenied code") | |
| 		} | |
| 	}) | |
| 
 | |
| 	t.Run("expired_jwt_token_authentication", func(t *testing.T) { | |
| 		// Create S3 client with expired JWT token | |
| 		s3Client, err := framework.CreateS3ClientWithExpiredJWT("expired-user", "TestAdminRole") | |
| 		require.NoError(t, err) | |
| 
 | |
| 		// Attempt bucket operations - should fail | |
| 		err = framework.CreateBucket(s3Client, testBucket+"-expired") | |
| 		require.Error(t, err) | |
| 
 | |
| 		// Verify it's an access denied error | |
| 		if awsErr, ok := err.(awserr.Error); ok { | |
| 			assert.Equal(t, "AccessDenied", awsErr.Code()) | |
| 		} else { | |
| 			t.Error("Expected AWS error with AccessDenied code") | |
| 		} | |
| 	}) | |
| } | |
| 
 | |
| // TestS3IAMPolicyEnforcement tests policy enforcement for different S3 operations | |
| func TestS3IAMPolicyEnforcement(t *testing.T) { | |
| 	framework := NewS3IAMTestFramework(t) | |
| 	defer framework.Cleanup() | |
| 
 | |
| 	// Setup test bucket with admin client | |
| 	adminClient, err := framework.CreateS3ClientWithJWT("admin-user", "TestAdminRole") | |
| 	require.NoError(t, err) | |
| 
 | |
| 	err = framework.CreateBucket(adminClient, testBucket) | |
| 	require.NoError(t, err) | |
| 
 | |
| 	// Put test object with admin client | |
| 	_, err = adminClient.PutObject(&s3.PutObjectInput{ | |
| 		Bucket: aws.String(testBucket), | |
| 		Key:    aws.String(testObjectKey), | |
| 		Body:   strings.NewReader(testObjectData), | |
| 	}) | |
| 	require.NoError(t, err) | |
| 
 | |
| 	t.Run("read_only_policy_enforcement", func(t *testing.T) { | |
| 		// Create S3 client with read-only role | |
| 		readOnlyClient, err := framework.CreateS3ClientWithJWT("read-user", "TestReadOnlyRole") | |
| 		require.NoError(t, err) | |
| 
 | |
| 		// Should be able to read objects | |
| 		result, err := readOnlyClient.GetObject(&s3.GetObjectInput{ | |
| 			Bucket: aws.String(testBucket), | |
| 			Key:    aws.String(testObjectKey), | |
| 		}) | |
| 		require.NoError(t, err) | |
| 
 | |
| 		data, err := io.ReadAll(result.Body) | |
| 		require.NoError(t, err) | |
| 		assert.Equal(t, testObjectData, string(data)) | |
| 		result.Body.Close() | |
| 
 | |
| 		// Should be able to list objects | |
| 		listResult, err := readOnlyClient.ListObjects(&s3.ListObjectsInput{ | |
| 			Bucket: aws.String(testBucket), | |
| 		}) | |
| 		require.NoError(t, err) | |
| 		assert.Len(t, listResult.Contents, 1) | |
| 		assert.Equal(t, testObjectKey, *listResult.Contents[0].Key) | |
| 
 | |
| 		// Should NOT be able to put objects | |
| 		_, err = readOnlyClient.PutObject(&s3.PutObjectInput{ | |
| 			Bucket: aws.String(testBucket), | |
| 			Key:    aws.String("forbidden-object.txt"), | |
| 			Body:   strings.NewReader("This should fail"), | |
| 		}) | |
| 		require.Error(t, err) | |
| 		if awsErr, ok := err.(awserr.Error); ok { | |
| 			assert.Equal(t, "AccessDenied", awsErr.Code()) | |
| 		} | |
| 
 | |
| 		// Should NOT be able to delete objects | |
| 		_, err = readOnlyClient.DeleteObject(&s3.DeleteObjectInput{ | |
| 			Bucket: aws.String(testBucket), | |
| 			Key:    aws.String(testObjectKey), | |
| 		}) | |
| 		require.Error(t, err) | |
| 		if awsErr, ok := err.(awserr.Error); ok { | |
| 			assert.Equal(t, "AccessDenied", awsErr.Code()) | |
| 		} | |
| 	}) | |
| 
 | |
| 	t.Run("write_only_policy_enforcement", func(t *testing.T) { | |
| 		// Create S3 client with write-only role | |
| 		writeOnlyClient, err := framework.CreateS3ClientWithJWT("write-user", "TestWriteOnlyRole") | |
| 		require.NoError(t, err) | |
| 
 | |
| 		// Should be able to put objects | |
| 		testWriteKey := "write-test-object.txt" | |
| 		testWriteData := "Write-only test data" | |
| 
 | |
| 		_, err = writeOnlyClient.PutObject(&s3.PutObjectInput{ | |
| 			Bucket: aws.String(testBucket), | |
| 			Key:    aws.String(testWriteKey), | |
| 			Body:   strings.NewReader(testWriteData), | |
| 		}) | |
| 		require.NoError(t, err) | |
| 
 | |
| 		// Should be able to delete objects | |
| 		_, err = writeOnlyClient.DeleteObject(&s3.DeleteObjectInput{ | |
| 			Bucket: aws.String(testBucket), | |
| 			Key:    aws.String(testWriteKey), | |
| 		}) | |
| 		require.NoError(t, err) | |
| 
 | |
| 		// Should NOT be able to read objects | |
| 		_, err = writeOnlyClient.GetObject(&s3.GetObjectInput{ | |
| 			Bucket: aws.String(testBucket), | |
| 			Key:    aws.String(testObjectKey), | |
| 		}) | |
| 		require.Error(t, err) | |
| 		if awsErr, ok := err.(awserr.Error); ok { | |
| 			assert.Equal(t, "AccessDenied", awsErr.Code()) | |
| 		} | |
| 
 | |
| 		// Should NOT be able to list objects | |
| 		_, err = writeOnlyClient.ListObjects(&s3.ListObjectsInput{ | |
| 			Bucket: aws.String(testBucket), | |
| 		}) | |
| 		require.Error(t, err) | |
| 		if awsErr, ok := err.(awserr.Error); ok { | |
| 			assert.Equal(t, "AccessDenied", awsErr.Code()) | |
| 		} | |
| 	}) | |
| 
 | |
| 	t.Run("admin_policy_enforcement", func(t *testing.T) { | |
| 		// Admin client should be able to do everything | |
| 		testAdminKey := "admin-test-object.txt" | |
| 		testAdminData := "Admin test data" | |
| 
 | |
| 		// Should be able to put objects | |
| 		_, err = adminClient.PutObject(&s3.PutObjectInput{ | |
| 			Bucket: aws.String(testBucket), | |
| 			Key:    aws.String(testAdminKey), | |
| 			Body:   strings.NewReader(testAdminData), | |
| 		}) | |
| 		require.NoError(t, err) | |
| 
 | |
| 		// Should be able to read objects | |
| 		result, err := adminClient.GetObject(&s3.GetObjectInput{ | |
| 			Bucket: aws.String(testBucket), | |
| 			Key:    aws.String(testAdminKey), | |
| 		}) | |
| 		require.NoError(t, err) | |
| 
 | |
| 		data, err := io.ReadAll(result.Body) | |
| 		require.NoError(t, err) | |
| 		assert.Equal(t, testAdminData, string(data)) | |
| 		result.Body.Close() | |
| 
 | |
| 		// Should be able to list objects | |
| 		listResult, err := adminClient.ListObjects(&s3.ListObjectsInput{ | |
| 			Bucket: aws.String(testBucket), | |
| 		}) | |
| 		require.NoError(t, err) | |
| 		assert.GreaterOrEqual(t, len(listResult.Contents), 1) | |
| 
 | |
| 		// Should be able to delete objects | |
| 		_, err = adminClient.DeleteObject(&s3.DeleteObjectInput{ | |
| 			Bucket: aws.String(testBucket), | |
| 			Key:    aws.String(testAdminKey), | |
| 		}) | |
| 		require.NoError(t, err) | |
| 
 | |
| 		// Should be able to delete buckets | |
| 		// First delete remaining objects | |
| 		_, err = adminClient.DeleteObject(&s3.DeleteObjectInput{ | |
| 			Bucket: aws.String(testBucket), | |
| 			Key:    aws.String(testObjectKey), | |
| 		}) | |
| 		require.NoError(t, err) | |
| 
 | |
| 		// Then delete the bucket | |
| 		_, err = adminClient.DeleteBucket(&s3.DeleteBucketInput{ | |
| 			Bucket: aws.String(testBucket), | |
| 		}) | |
| 		require.NoError(t, err) | |
| 	}) | |
| } | |
| 
 | |
| // TestS3IAMSessionExpiration tests session expiration handling | |
| func TestS3IAMSessionExpiration(t *testing.T) { | |
| 	framework := NewS3IAMTestFramework(t) | |
| 	defer framework.Cleanup() | |
| 
 | |
| 	t.Run("session_expiration_enforcement", func(t *testing.T) { | |
| 		// Create S3 client with valid JWT token | |
| 		s3Client, err := framework.CreateS3ClientWithJWT("session-user", "TestAdminRole") | |
| 		require.NoError(t, err) | |
| 
 | |
| 		// Initially should work | |
| 		err = framework.CreateBucket(s3Client, testBucket+"-session") | |
| 		require.NoError(t, err) | |
| 
 | |
| 		// Create S3 client with expired JWT token | |
| 		expiredClient, err := framework.CreateS3ClientWithExpiredJWT("session-user", "TestAdminRole") | |
| 		require.NoError(t, err) | |
| 
 | |
| 		// Now operations should fail with expired token | |
| 		err = framework.CreateBucket(expiredClient, testBucket+"-session-expired") | |
| 		require.Error(t, err) | |
| 		if awsErr, ok := err.(awserr.Error); ok { | |
| 			assert.Equal(t, "AccessDenied", awsErr.Code()) | |
| 		} | |
| 
 | |
| 		// Cleanup the successful bucket | |
| 		adminClient, err := framework.CreateS3ClientWithJWT("admin-user", "TestAdminRole") | |
| 		require.NoError(t, err) | |
| 
 | |
| 		_, err = adminClient.DeleteBucket(&s3.DeleteBucketInput{ | |
| 			Bucket: aws.String(testBucket + "-session"), | |
| 		}) | |
| 		require.NoError(t, err) | |
| 	}) | |
| } | |
| 
 | |
| // TestS3IAMMultipartUploadPolicyEnforcement tests multipart upload with IAM policies | |
| func TestS3IAMMultipartUploadPolicyEnforcement(t *testing.T) { | |
| 	framework := NewS3IAMTestFramework(t) | |
| 	defer framework.Cleanup() | |
| 
 | |
| 	// Setup test bucket with admin client | |
| 	adminClient, err := framework.CreateS3ClientWithJWT("admin-user", "TestAdminRole") | |
| 	require.NoError(t, err) | |
| 
 | |
| 	err = framework.CreateBucket(adminClient, testBucket) | |
| 	require.NoError(t, err) | |
| 
 | |
| 	t.Run("multipart_upload_with_write_permissions", func(t *testing.T) { | |
| 		// Create S3 client with admin role (has multipart permissions) | |
| 		s3Client := adminClient | |
| 
 | |
| 		// Initiate multipart upload | |
| 		multipartKey := "large-test-file.txt" | |
| 		initResult, err := s3Client.CreateMultipartUpload(&s3.CreateMultipartUploadInput{ | |
| 			Bucket: aws.String(testBucket), | |
| 			Key:    aws.String(multipartKey), | |
| 		}) | |
| 		require.NoError(t, err) | |
| 
 | |
| 		uploadId := initResult.UploadId | |
| 
 | |
| 		// Upload a part | |
| 		partNumber := int64(1) | |
| 		partData := strings.Repeat("Test data for multipart upload. ", 1000) // ~30KB | |
|  | |
| 		uploadResult, err := s3Client.UploadPart(&s3.UploadPartInput{ | |
| 			Bucket:     aws.String(testBucket), | |
| 			Key:        aws.String(multipartKey), | |
| 			PartNumber: aws.Int64(partNumber), | |
| 			UploadId:   uploadId, | |
| 			Body:       strings.NewReader(partData), | |
| 		}) | |
| 		require.NoError(t, err) | |
| 
 | |
| 		// Complete multipart upload | |
| 		_, err = s3Client.CompleteMultipartUpload(&s3.CompleteMultipartUploadInput{ | |
| 			Bucket:   aws.String(testBucket), | |
| 			Key:      aws.String(multipartKey), | |
| 			UploadId: uploadId, | |
| 			MultipartUpload: &s3.CompletedMultipartUpload{ | |
| 				Parts: []*s3.CompletedPart{ | |
| 					{ | |
| 						ETag:       uploadResult.ETag, | |
| 						PartNumber: aws.Int64(partNumber), | |
| 					}, | |
| 				}, | |
| 			}, | |
| 		}) | |
| 		require.NoError(t, err) | |
| 
 | |
| 		// Verify object was created | |
| 		result, err := s3Client.GetObject(&s3.GetObjectInput{ | |
| 			Bucket: aws.String(testBucket), | |
| 			Key:    aws.String(multipartKey), | |
| 		}) | |
| 		require.NoError(t, err) | |
| 
 | |
| 		data, err := io.ReadAll(result.Body) | |
| 		require.NoError(t, err) | |
| 		assert.Equal(t, partData, string(data)) | |
| 		result.Body.Close() | |
| 
 | |
| 		// Cleanup | |
| 		_, err = s3Client.DeleteObject(&s3.DeleteObjectInput{ | |
| 			Bucket: aws.String(testBucket), | |
| 			Key:    aws.String(multipartKey), | |
| 		}) | |
| 		require.NoError(t, err) | |
| 	}) | |
| 
 | |
| 	t.Run("multipart_upload_denied_for_read_only", func(t *testing.T) { | |
| 		// Create S3 client with read-only role | |
| 		readOnlyClient, err := framework.CreateS3ClientWithJWT("read-user", "TestReadOnlyRole") | |
| 		require.NoError(t, err) | |
| 
 | |
| 		// Attempt to initiate multipart upload - should fail | |
| 		multipartKey := "denied-multipart-file.txt" | |
| 		_, err = readOnlyClient.CreateMultipartUpload(&s3.CreateMultipartUploadInput{ | |
| 			Bucket: aws.String(testBucket), | |
| 			Key:    aws.String(multipartKey), | |
| 		}) | |
| 		require.Error(t, err) | |
| 		if awsErr, ok := err.(awserr.Error); ok { | |
| 			assert.Equal(t, "AccessDenied", awsErr.Code()) | |
| 		} | |
| 	}) | |
| 
 | |
| 	// Cleanup | |
| 	_, err = adminClient.DeleteBucket(&s3.DeleteBucketInput{ | |
| 		Bucket: aws.String(testBucket), | |
| 	}) | |
| 	require.NoError(t, err) | |
| } | |
| 
 | |
| // TestS3IAMBucketPolicyIntegration tests bucket policy integration with IAM | |
| func TestS3IAMBucketPolicyIntegration(t *testing.T) { | |
| 	framework := NewS3IAMTestFramework(t) | |
| 	defer framework.Cleanup() | |
| 
 | |
| 	// Setup test bucket with admin client | |
| 	adminClient, err := framework.CreateS3ClientWithJWT("admin-user", "TestAdminRole") | |
| 	require.NoError(t, err) | |
| 
 | |
| 	err = framework.CreateBucket(adminClient, testBucket) | |
| 	require.NoError(t, err) | |
| 
 | |
| 	t.Run("bucket_policy_allows_public_read", func(t *testing.T) { | |
| 		// Set bucket policy to allow public read access | |
| 		bucketPolicy := fmt.Sprintf(`{ | |
| 			"Version": "2012-10-17", | |
| 			"Statement": [ | |
| 				{ | |
| 					"Sid": "PublicReadGetObject", | |
| 					"Effect": "Allow", | |
| 					"Principal": "*", | |
| 					"Action": ["s3:GetObject"], | |
| 					"Resource": ["arn:seaweed:s3:::%s/*"] | |
| 				} | |
| 			] | |
| 		}`, testBucket) | |
| 
 | |
| 		_, err = adminClient.PutBucketPolicy(&s3.PutBucketPolicyInput{ | |
| 			Bucket: aws.String(testBucket), | |
| 			Policy: aws.String(bucketPolicy), | |
| 		}) | |
| 		require.NoError(t, err) | |
| 
 | |
| 		// Put test object | |
| 		_, err = adminClient.PutObject(&s3.PutObjectInput{ | |
| 			Bucket: aws.String(testBucket), | |
| 			Key:    aws.String(testObjectKey), | |
| 			Body:   strings.NewReader(testObjectData), | |
| 		}) | |
| 		require.NoError(t, err) | |
| 
 | |
| 		// Test with read-only client - should now be allowed due to bucket policy | |
| 		readOnlyClient, err := framework.CreateS3ClientWithJWT("read-user", "TestReadOnlyRole") | |
| 		require.NoError(t, err) | |
| 
 | |
| 		result, err := readOnlyClient.GetObject(&s3.GetObjectInput{ | |
| 			Bucket: aws.String(testBucket), | |
| 			Key:    aws.String(testObjectKey), | |
| 		}) | |
| 		require.NoError(t, err) | |
| 
 | |
| 		data, err := io.ReadAll(result.Body) | |
| 		require.NoError(t, err) | |
| 		assert.Equal(t, testObjectData, string(data)) | |
| 		result.Body.Close() | |
| 	}) | |
| 
 | |
| 	t.Run("bucket_policy_denies_specific_action", func(t *testing.T) { | |
| 		// Set bucket policy to deny delete operations | |
| 		bucketPolicy := fmt.Sprintf(`{ | |
| 			"Version": "2012-10-17", | |
| 			"Statement": [ | |
| 				{ | |
| 					"Sid": "DenyDelete", | |
| 					"Effect": "Deny", | |
| 					"Principal": "*", | |
| 					"Action": ["s3:DeleteObject"], | |
| 					"Resource": ["arn:seaweed:s3:::%s/*"] | |
| 				} | |
| 			] | |
| 		}`, testBucket) | |
| 
 | |
| 		_, err = adminClient.PutBucketPolicy(&s3.PutBucketPolicyInput{ | |
| 			Bucket: aws.String(testBucket), | |
| 			Policy: aws.String(bucketPolicy), | |
| 		}) | |
| 		require.NoError(t, err) | |
| 
 | |
| 		// Verify that the bucket policy was stored successfully by retrieving it | |
| 		policyResult, err := adminClient.GetBucketPolicy(&s3.GetBucketPolicyInput{ | |
| 			Bucket: aws.String(testBucket), | |
| 		}) | |
| 		require.NoError(t, err) | |
| 		assert.Contains(t, *policyResult.Policy, "s3:DeleteObject") | |
| 		assert.Contains(t, *policyResult.Policy, "Deny") | |
| 
 | |
| 		// IMPLEMENTATION NOTE: Bucket policy enforcement in authorization flow | |
| 		// is planned for a future phase. Currently, this test validates policy | |
| 		// storage and retrieval. When enforcement is implemented, this test | |
| 		// should be extended to verify that delete operations are actually denied. | |
| 	}) | |
| 
 | |
| 	// Cleanup - delete bucket policy first, then objects and bucket | |
| 	_, err = adminClient.DeleteBucketPolicy(&s3.DeleteBucketPolicyInput{ | |
| 		Bucket: aws.String(testBucket), | |
| 	}) | |
| 	require.NoError(t, err) | |
| 
 | |
| 	_, err = adminClient.DeleteObject(&s3.DeleteObjectInput{ | |
| 		Bucket: aws.String(testBucket), | |
| 		Key:    aws.String(testObjectKey), | |
| 	}) | |
| 	require.NoError(t, err) | |
| 
 | |
| 	_, err = adminClient.DeleteBucket(&s3.DeleteBucketInput{ | |
| 		Bucket: aws.String(testBucket), | |
| 	}) | |
| 	require.NoError(t, err) | |
| } | |
| 
 | |
| // TestS3IAMContextualPolicyEnforcement tests context-aware policy enforcement | |
| func TestS3IAMContextualPolicyEnforcement(t *testing.T) { | |
| 	framework := NewS3IAMTestFramework(t) | |
| 	defer framework.Cleanup() | |
| 
 | |
| 	// This test would verify IP-based restrictions, time-based restrictions, | |
| 	// and other context-aware policy conditions | |
| 	// For now, we'll focus on the basic structure | |
|  | |
| 	t.Run("ip_based_policy_enforcement", func(t *testing.T) { | |
| 		// IMPLEMENTATION NOTE: IP-based policy testing framework planned for future release | |
| 		// Requirements: | |
| 		// - Configure IAM policies with IpAddress/NotIpAddress conditions | |
| 		// - Multi-container test setup with controlled source IP addresses | |
| 		// - Test policy enforcement from allowed vs denied IP ranges | |
| 		t.Skip("IP-based policy testing requires advanced network configuration and multi-container setup") | |
| 	}) | |
| 
 | |
| 	t.Run("time_based_policy_enforcement", func(t *testing.T) { | |
| 		// IMPLEMENTATION NOTE: Time-based policy testing framework planned for future release | |
| 		// Requirements: | |
| 		// - Configure IAM policies with DateGreaterThan/DateLessThan conditions | |
| 		// - Time manipulation capabilities for testing different time windows | |
| 		// - Test policy enforcement during allowed vs restricted time periods | |
| 		t.Skip("Time-based policy testing requires time manipulation capabilities") | |
| 	}) | |
| } | |
| 
 | |
| // Helper function to create test content of specific size | |
| func createTestContent(size int) *bytes.Reader { | |
| 	content := make([]byte, size) | |
| 	for i := range content { | |
| 		content[i] = byte(i % 256) | |
| 	} | |
| 	return bytes.NewReader(content) | |
| } | |
| 
 | |
| // TestS3IAMPresignedURLIntegration tests presigned URL generation with IAM | |
| func TestS3IAMPresignedURLIntegration(t *testing.T) { | |
| 	framework := NewS3IAMTestFramework(t) | |
| 	defer framework.Cleanup() | |
| 
 | |
| 	// Setup test bucket with admin client | |
| 	adminClient, err := framework.CreateS3ClientWithJWT("admin-user", "TestAdminRole") | |
| 	require.NoError(t, err) | |
| 
 | |
| 	// Use static bucket name but with cleanup to handle conflicts | |
| 	err = framework.CreateBucketWithCleanup(adminClient, testBucketPrefix) | |
| 	require.NoError(t, err) | |
| 
 | |
| 	// Put test object | |
| 	_, err = adminClient.PutObject(&s3.PutObjectInput{ | |
| 		Bucket: aws.String(testBucketPrefix), | |
| 		Key:    aws.String(testObjectKey), | |
| 		Body:   strings.NewReader(testObjectData), | |
| 	}) | |
| 	require.NoError(t, err) | |
| 
 | |
| 	t.Run("presigned_url_generation_and_usage", func(t *testing.T) { | |
| 		// ARCHITECTURAL NOTE: AWS SDK presigned URLs are incompatible with JWT Bearer authentication | |
| 		// | |
| 		// AWS SDK presigned URLs use AWS Signature Version 4 (SigV4) which requires: | |
| 		// - Access Key ID and Secret Access Key for signing | |
| 		// - Query parameter-based authentication in the URL | |
| 		// | |
| 		// SeaweedFS JWT authentication uses: | |
| 		// - Bearer tokens in the Authorization header | |
| 		// - Stateless JWT validation without AWS-style signing | |
| 		// | |
| 		// RECOMMENDATION: For JWT-authenticated applications, use direct API calls | |
| 		// with Bearer tokens rather than presigned URLs. | |
|  | |
| 		// Test direct object access with JWT Bearer token (recommended approach) | |
| 		_, err := adminClient.GetObject(&s3.GetObjectInput{ | |
| 			Bucket: aws.String(testBucketPrefix), | |
| 			Key:    aws.String(testObjectKey), | |
| 		}) | |
| 		require.NoError(t, err, "Direct object access with JWT Bearer token works correctly") | |
| 
 | |
| 		t.Log("✅ JWT Bearer token authentication confirmed working for direct S3 API calls") | |
| 		t.Log("ℹ️  Note: Presigned URLs are not supported with JWT Bearer authentication by design") | |
| 	}) | |
| 
 | |
| 	// Cleanup | |
| 	_, err = adminClient.DeleteObject(&s3.DeleteObjectInput{ | |
| 		Bucket: aws.String(testBucket), | |
| 		Key:    aws.String(testObjectKey), | |
| 	}) | |
| 	require.NoError(t, err) | |
| 
 | |
| 	_, err = adminClient.DeleteBucket(&s3.DeleteBucketInput{ | |
| 		Bucket: aws.String(testBucket), | |
| 	}) | |
| 	require.NoError(t, err) | |
| }
 |