diff --git a/test/s3/iam/iam_config.json b/test/s3/iam/iam_config.json index 4befe50f3..4c689cf11 100644 --- a/test/s3/iam/iam_config.json +++ b/test/s3/iam/iam_config.json @@ -10,7 +10,8 @@ "name": "test-oidc", "type": "mock", "config": { - "issuer": "test-oidc-issuer" + "issuer": "test-oidc-issuer", + "clientId": "test-oidc-client" } }, { @@ -18,11 +19,11 @@ "type": "oidc", "enabled": true, "config": { - "issuer": "http://localhost:8090/realms/seaweedfs-test", + "issuer": "http://localhost:8080/realms/seaweedfs-test", "clientId": "seaweedfs-s3", "clientSecret": "seaweedfs-s3-secret", - "jwksUri": "http://localhost:8090/realms/seaweedfs-test/protocol/openid-connect/certs", - "userInfoUri": "http://localhost:8090/realms/seaweedfs-test/protocol/openid-connect/userinfo", + "jwksUri": "http://localhost:8080/realms/seaweedfs-test/protocol/openid-connect/certs", + "userInfoUri": "http://localhost:8080/realms/seaweedfs-test/protocol/openid-connect/userinfo", "scopes": ["openid", "profile", "email"], "claimsMapping": { "username": "preferred_username", diff --git a/test/s3/iam/s3_iam_framework.go b/test/s3/iam/s3_iam_framework.go index f8d1d2b69..69c9b3e16 100644 --- a/test/s3/iam/s3_iam_framework.go +++ b/test/s3/iam/s3_iam_framework.go @@ -8,6 +8,7 @@ import ( "encoding/json" "fmt" "io" + "math/rand" "net/http" "net/http/httptest" "net/url" @@ -17,6 +18,7 @@ import ( "time" "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/aws/credentials" "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/s3" @@ -612,6 +614,19 @@ func (f *S3IAMTestFramework) ExpireSessionForTesting(sessionToken string) error return nil } +// GenerateUniqueBucketName generates a unique bucket name for testing +func (f *S3IAMTestFramework) GenerateUniqueBucketName(prefix string) string { + // Use test name and timestamp to ensure uniqueness + testName := strings.ToLower(f.t.Name()) + testName = strings.ReplaceAll(testName, "/", "-") + testName = strings.ReplaceAll(testName, "_", "-") + + // Add random suffix to handle parallel tests + randomSuffix := rand.Intn(10000) + + return fmt.Sprintf("%s-%s-%d", prefix, testName, randomSuffix) +} + // CreateBucket creates a bucket and tracks it for cleanup func (f *S3IAMTestFramework) CreateBucket(s3Client *s3.S3, bucketName string) error { _, err := s3Client.CreateBucket(&s3.CreateBucketInput{ @@ -626,6 +641,51 @@ func (f *S3IAMTestFramework) CreateBucket(s3Client *s3.S3, bucketName string) er return nil } +// CreateBucketWithCleanup creates a bucket, cleaning up any existing bucket first +func (f *S3IAMTestFramework) CreateBucketWithCleanup(s3Client *s3.S3, bucketName string) error { + // First try to create the bucket normally + _, err := s3Client.CreateBucket(&s3.CreateBucketInput{ + Bucket: aws.String(bucketName), + }) + + if err != nil { + // If bucket already exists, clean it up first + if awsErr, ok := err.(awserr.Error); ok && awsErr.Code() == "BucketAlreadyExists" { + f.t.Logf("Bucket %s already exists, cleaning up first", bucketName) + + // Empty the existing bucket + f.emptyBucket(s3Client, bucketName) + + // Don't need to recreate - bucket already exists and is now empty + } else { + return err + } + } + + // Track bucket for cleanup + f.createdBuckets = append(f.createdBuckets, bucketName) + return nil +} + +// emptyBucket removes all objects from a bucket +func (f *S3IAMTestFramework) emptyBucket(s3Client *s3.S3, bucketName string) { + // Delete all objects + listResult, err := s3Client.ListObjects(&s3.ListObjectsInput{ + Bucket: aws.String(bucketName), + }) + if err == nil { + for _, obj := range listResult.Contents { + _, err := s3Client.DeleteObject(&s3.DeleteObjectInput{ + Bucket: aws.String(bucketName), + Key: obj.Key, + }) + if err != nil { + f.t.Logf("Warning: Failed to delete object %s/%s: %v", bucketName, *obj.Key, err) + } + } + } +} + // Cleanup cleans up test resources func (f *S3IAMTestFramework) Cleanup() { // Clean up buckets (best effort) diff --git a/test/s3/iam/s3_iam_integration_test.go b/test/s3/iam/s3_iam_integration_test.go index 480c21bde..24081b5b2 100644 --- a/test/s3/iam/s3_iam_integration_test.go +++ b/test/s3/iam/s3_iam_integration_test.go @@ -15,11 +15,11 @@ import ( ) const ( - testEndpoint = "http://localhost:8333" - testRegion = "us-west-2" - testBucket = "test-iam-bucket" - testObjectKey = "test-object.txt" - testObjectData = "Hello, SeaweedFS IAM Integration!" + testEndpoint = "http://localhost:8333" + testRegion = "us-west-2" + testBucketPrefix = "test-iam-bucket" + testObjectKey = "test-object.txt" + testObjectData = "Hello, SeaweedFS IAM Integration!" ) // TestS3IAMAuthentication tests S3 API authentication with IAM JWT tokens @@ -176,33 +176,22 @@ func TestS3IAMPolicyEnforcement(t *testing.T) { require.NoError(t, err) // Should NOT be able to read objects - // TODO: Fix IAM policy evaluation system - explicit deny statements are not being enforced - // This is a known issue where the policy engine allows read operations despite explicit deny _, err = writeOnlyClient.GetObject(&s3.GetObjectInput{ Bucket: aws.String(testBucket), Key: aws.String(testObjectKey), }) - if err == nil { - t.Skip("KNOWN ISSUE: IAM policy evaluation system does not properly enforce explicit deny statements for write-only roles") - } else { - // If the error is properly returned, verify it's AccessDenied - if awsErr, ok := err.(awserr.Error); ok { - assert.Equal(t, "AccessDenied", awsErr.Code()) - } + require.Error(t, err) + if awsErr, ok := err.(awserr.Error); ok { + assert.Equal(t, "AccessDenied", awsErr.Code()) } // Should NOT be able to list objects - // TODO: Same IAM policy evaluation issue as above _, err = writeOnlyClient.ListObjects(&s3.ListObjectsInput{ Bucket: aws.String(testBucket), }) - if err == nil { - t.Skip("KNOWN ISSUE: IAM policy evaluation system does not properly enforce explicit deny statements for list operations") - } else { - // If the error is properly returned, verify it's AccessDenied - if awsErr, ok := err.(awserr.Error); ok { - assert.Equal(t, "AccessDenied", awsErr.Code()) - } + require.Error(t, err) + if awsErr, ok := err.(awserr.Error); ok { + assert.Equal(t, "AccessDenied", awsErr.Code()) } }) @@ -547,12 +536,13 @@ func TestS3IAMPresignedURLIntegration(t *testing.T) { adminClient, err := framework.CreateS3ClientWithJWT("admin-user", "TestAdminRole") require.NoError(t, err) - err = framework.CreateBucket(adminClient, testBucket) + // 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(testBucket), + Bucket: aws.String(testBucketPrefix), Key: aws.String(testObjectKey), Body: strings.NewReader(testObjectData), }) @@ -565,7 +555,7 @@ func TestS3IAMPresignedURLIntegration(t *testing.T) { // Test direct object access with JWT token (which is what JWT authentication supports) _, err := adminClient.GetObject(&s3.GetObjectInput{ - Bucket: aws.String(testBucket), + Bucket: aws.String(testBucketPrefix), Key: aws.String(testObjectKey), }) require.NoError(t, err, "Direct object access with JWT should work")