aboutsummaryrefslogtreecommitdiff
path: root/weed/s3api/s3_policy_templates_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'weed/s3api/s3_policy_templates_test.go')
-rw-r--r--weed/s3api/s3_policy_templates_test.go504
1 files changed, 504 insertions, 0 deletions
diff --git a/weed/s3api/s3_policy_templates_test.go b/weed/s3api/s3_policy_templates_test.go
new file mode 100644
index 000000000..9c1f6c7d3
--- /dev/null
+++ b/weed/s3api/s3_policy_templates_test.go
@@ -0,0 +1,504 @@
+package s3api
+
+import (
+ "fmt"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+func TestS3PolicyTemplates(t *testing.T) {
+ templates := NewS3PolicyTemplates()
+
+ t.Run("S3ReadOnlyPolicy", func(t *testing.T) {
+ policy := templates.GetS3ReadOnlyPolicy()
+
+ require.NotNil(t, policy)
+ assert.Equal(t, "2012-10-17", policy.Version)
+ assert.Len(t, policy.Statement, 1)
+
+ stmt := policy.Statement[0]
+ assert.Equal(t, "Allow", stmt.Effect)
+ assert.Equal(t, "S3ReadOnlyAccess", stmt.Sid)
+ assert.Contains(t, stmt.Action, "s3:GetObject")
+ assert.Contains(t, stmt.Action, "s3:ListBucket")
+ assert.NotContains(t, stmt.Action, "s3:PutObject")
+ assert.NotContains(t, stmt.Action, "s3:DeleteObject")
+
+ assert.Contains(t, stmt.Resource, "arn:seaweed:s3:::*")
+ assert.Contains(t, stmt.Resource, "arn:seaweed:s3:::*/*")
+ })
+
+ t.Run("S3WriteOnlyPolicy", func(t *testing.T) {
+ policy := templates.GetS3WriteOnlyPolicy()
+
+ require.NotNil(t, policy)
+ assert.Equal(t, "2012-10-17", policy.Version)
+ assert.Len(t, policy.Statement, 1)
+
+ stmt := policy.Statement[0]
+ assert.Equal(t, "Allow", stmt.Effect)
+ assert.Equal(t, "S3WriteOnlyAccess", stmt.Sid)
+ assert.Contains(t, stmt.Action, "s3:PutObject")
+ assert.Contains(t, stmt.Action, "s3:CreateMultipartUpload")
+ assert.NotContains(t, stmt.Action, "s3:GetObject")
+ assert.NotContains(t, stmt.Action, "s3:DeleteObject")
+
+ assert.Contains(t, stmt.Resource, "arn:seaweed:s3:::*")
+ assert.Contains(t, stmt.Resource, "arn:seaweed:s3:::*/*")
+ })
+
+ t.Run("S3AdminPolicy", func(t *testing.T) {
+ policy := templates.GetS3AdminPolicy()
+
+ require.NotNil(t, policy)
+ assert.Equal(t, "2012-10-17", policy.Version)
+ assert.Len(t, policy.Statement, 1)
+
+ stmt := policy.Statement[0]
+ assert.Equal(t, "Allow", stmt.Effect)
+ assert.Equal(t, "S3FullAccess", stmt.Sid)
+ assert.Contains(t, stmt.Action, "s3:*")
+
+ assert.Contains(t, stmt.Resource, "arn:seaweed:s3:::*")
+ assert.Contains(t, stmt.Resource, "arn:seaweed:s3:::*/*")
+ })
+}
+
+func TestBucketSpecificPolicies(t *testing.T) {
+ templates := NewS3PolicyTemplates()
+ bucketName := "test-bucket"
+
+ t.Run("BucketSpecificReadPolicy", func(t *testing.T) {
+ policy := templates.GetBucketSpecificReadPolicy(bucketName)
+
+ require.NotNil(t, policy)
+ assert.Equal(t, "2012-10-17", policy.Version)
+ assert.Len(t, policy.Statement, 1)
+
+ stmt := policy.Statement[0]
+ assert.Equal(t, "Allow", stmt.Effect)
+ assert.Equal(t, "BucketSpecificReadAccess", stmt.Sid)
+ assert.Contains(t, stmt.Action, "s3:GetObject")
+ assert.Contains(t, stmt.Action, "s3:ListBucket")
+ assert.NotContains(t, stmt.Action, "s3:PutObject")
+
+ expectedBucketArn := "arn:seaweed:s3:::" + bucketName
+ expectedObjectArn := "arn:seaweed:s3:::" + bucketName + "/*"
+ assert.Contains(t, stmt.Resource, expectedBucketArn)
+ assert.Contains(t, stmt.Resource, expectedObjectArn)
+ })
+
+ t.Run("BucketSpecificWritePolicy", func(t *testing.T) {
+ policy := templates.GetBucketSpecificWritePolicy(bucketName)
+
+ require.NotNil(t, policy)
+ assert.Equal(t, "2012-10-17", policy.Version)
+ assert.Len(t, policy.Statement, 1)
+
+ stmt := policy.Statement[0]
+ assert.Equal(t, "Allow", stmt.Effect)
+ assert.Equal(t, "BucketSpecificWriteAccess", stmt.Sid)
+ assert.Contains(t, stmt.Action, "s3:PutObject")
+ assert.Contains(t, stmt.Action, "s3:CreateMultipartUpload")
+ assert.NotContains(t, stmt.Action, "s3:GetObject")
+
+ expectedBucketArn := "arn:seaweed:s3:::" + bucketName
+ expectedObjectArn := "arn:seaweed:s3:::" + bucketName + "/*"
+ assert.Contains(t, stmt.Resource, expectedBucketArn)
+ assert.Contains(t, stmt.Resource, expectedObjectArn)
+ })
+}
+
+func TestPathBasedAccessPolicy(t *testing.T) {
+ templates := NewS3PolicyTemplates()
+ bucketName := "shared-bucket"
+ pathPrefix := "user123/documents"
+
+ policy := templates.GetPathBasedAccessPolicy(bucketName, pathPrefix)
+
+ require.NotNil(t, policy)
+ assert.Equal(t, "2012-10-17", policy.Version)
+ assert.Len(t, policy.Statement, 2)
+
+ // First statement: List bucket with prefix condition
+ listStmt := policy.Statement[0]
+ assert.Equal(t, "Allow", listStmt.Effect)
+ assert.Equal(t, "ListBucketPermission", listStmt.Sid)
+ assert.Contains(t, listStmt.Action, "s3:ListBucket")
+ assert.Contains(t, listStmt.Resource, "arn:seaweed:s3:::"+bucketName)
+ assert.NotNil(t, listStmt.Condition)
+
+ // Second statement: Object operations on path
+ objectStmt := policy.Statement[1]
+ assert.Equal(t, "Allow", objectStmt.Effect)
+ assert.Equal(t, "PathBasedObjectAccess", objectStmt.Sid)
+ assert.Contains(t, objectStmt.Action, "s3:GetObject")
+ assert.Contains(t, objectStmt.Action, "s3:PutObject")
+ assert.Contains(t, objectStmt.Action, "s3:DeleteObject")
+
+ expectedObjectArn := "arn:seaweed:s3:::" + bucketName + "/" + pathPrefix + "/*"
+ assert.Contains(t, objectStmt.Resource, expectedObjectArn)
+}
+
+func TestIPRestrictedPolicy(t *testing.T) {
+ templates := NewS3PolicyTemplates()
+ allowedCIDRs := []string{"192.168.1.0/24", "10.0.0.0/8"}
+
+ policy := templates.GetIPRestrictedPolicy(allowedCIDRs)
+
+ require.NotNil(t, policy)
+ assert.Equal(t, "2012-10-17", policy.Version)
+ assert.Len(t, policy.Statement, 1)
+
+ stmt := policy.Statement[0]
+ assert.Equal(t, "Allow", stmt.Effect)
+ assert.Equal(t, "IPRestrictedS3Access", stmt.Sid)
+ assert.Contains(t, stmt.Action, "s3:*")
+ assert.NotNil(t, stmt.Condition)
+
+ // Check IP condition structure
+ condition := stmt.Condition
+ ipAddress, exists := condition["IpAddress"]
+ assert.True(t, exists)
+
+ sourceIp, exists := ipAddress["aws:SourceIp"]
+ assert.True(t, exists)
+ assert.Equal(t, allowedCIDRs, sourceIp)
+}
+
+func TestTimeBasedAccessPolicy(t *testing.T) {
+ templates := NewS3PolicyTemplates()
+ startHour := 9 // 9 AM
+ endHour := 17 // 5 PM
+
+ policy := templates.GetTimeBasedAccessPolicy(startHour, endHour)
+
+ require.NotNil(t, policy)
+ assert.Equal(t, "2012-10-17", policy.Version)
+ assert.Len(t, policy.Statement, 1)
+
+ stmt := policy.Statement[0]
+ assert.Equal(t, "Allow", stmt.Effect)
+ assert.Equal(t, "TimeBasedS3Access", stmt.Sid)
+ assert.Contains(t, stmt.Action, "s3:GetObject")
+ assert.Contains(t, stmt.Action, "s3:PutObject")
+ assert.Contains(t, stmt.Action, "s3:ListBucket")
+ assert.NotNil(t, stmt.Condition)
+
+ // Check time condition structure
+ condition := stmt.Condition
+ _, hasGreater := condition["DateGreaterThan"]
+ _, hasLess := condition["DateLessThan"]
+ assert.True(t, hasGreater)
+ assert.True(t, hasLess)
+}
+
+func TestMultipartUploadPolicyTemplate(t *testing.T) {
+ templates := NewS3PolicyTemplates()
+ bucketName := "large-files"
+
+ policy := templates.GetMultipartUploadPolicy(bucketName)
+
+ require.NotNil(t, policy)
+ assert.Equal(t, "2012-10-17", policy.Version)
+ assert.Len(t, policy.Statement, 2)
+
+ // First statement: Multipart operations
+ multipartStmt := policy.Statement[0]
+ assert.Equal(t, "Allow", multipartStmt.Effect)
+ assert.Equal(t, "MultipartUploadOperations", multipartStmt.Sid)
+ assert.Contains(t, multipartStmt.Action, "s3:CreateMultipartUpload")
+ assert.Contains(t, multipartStmt.Action, "s3:UploadPart")
+ assert.Contains(t, multipartStmt.Action, "s3:CompleteMultipartUpload")
+ assert.Contains(t, multipartStmt.Action, "s3:AbortMultipartUpload")
+ assert.Contains(t, multipartStmt.Action, "s3:ListMultipartUploads")
+ assert.Contains(t, multipartStmt.Action, "s3:ListParts")
+
+ expectedObjectArn := "arn:seaweed:s3:::" + bucketName + "/*"
+ assert.Contains(t, multipartStmt.Resource, expectedObjectArn)
+
+ // Second statement: List bucket
+ listStmt := policy.Statement[1]
+ assert.Equal(t, "Allow", listStmt.Effect)
+ assert.Equal(t, "ListBucketForMultipart", listStmt.Sid)
+ assert.Contains(t, listStmt.Action, "s3:ListBucket")
+
+ expectedBucketArn := "arn:seaweed:s3:::" + bucketName
+ assert.Contains(t, listStmt.Resource, expectedBucketArn)
+}
+
+func TestPresignedURLPolicy(t *testing.T) {
+ templates := NewS3PolicyTemplates()
+ bucketName := "shared-files"
+
+ policy := templates.GetPresignedURLPolicy(bucketName)
+
+ require.NotNil(t, policy)
+ assert.Equal(t, "2012-10-17", policy.Version)
+ assert.Len(t, policy.Statement, 1)
+
+ stmt := policy.Statement[0]
+ assert.Equal(t, "Allow", stmt.Effect)
+ assert.Equal(t, "PresignedURLAccess", stmt.Sid)
+ assert.Contains(t, stmt.Action, "s3:GetObject")
+ assert.Contains(t, stmt.Action, "s3:PutObject")
+ assert.NotNil(t, stmt.Condition)
+
+ expectedObjectArn := "arn:seaweed:s3:::" + bucketName + "/*"
+ assert.Contains(t, stmt.Resource, expectedObjectArn)
+
+ // Check signature version condition
+ condition := stmt.Condition
+ stringEquals, exists := condition["StringEquals"]
+ assert.True(t, exists)
+
+ signatureVersion, exists := stringEquals["s3:x-amz-signature-version"]
+ assert.True(t, exists)
+ assert.Equal(t, "AWS4-HMAC-SHA256", signatureVersion)
+}
+
+func TestTemporaryAccessPolicy(t *testing.T) {
+ templates := NewS3PolicyTemplates()
+ bucketName := "temp-bucket"
+ expirationHours := 24
+
+ policy := templates.GetTemporaryAccessPolicy(bucketName, expirationHours)
+
+ require.NotNil(t, policy)
+ assert.Equal(t, "2012-10-17", policy.Version)
+ assert.Len(t, policy.Statement, 1)
+
+ stmt := policy.Statement[0]
+ assert.Equal(t, "Allow", stmt.Effect)
+ assert.Equal(t, "TemporaryS3Access", stmt.Sid)
+ assert.Contains(t, stmt.Action, "s3:GetObject")
+ assert.Contains(t, stmt.Action, "s3:PutObject")
+ assert.Contains(t, stmt.Action, "s3:ListBucket")
+ assert.NotNil(t, stmt.Condition)
+
+ // Check expiration condition
+ condition := stmt.Condition
+ dateLessThan, exists := condition["DateLessThan"]
+ assert.True(t, exists)
+
+ currentTime, exists := dateLessThan["aws:CurrentTime"]
+ assert.True(t, exists)
+ assert.IsType(t, "", currentTime) // Should be a string timestamp
+}
+
+func TestContentTypeRestrictedPolicy(t *testing.T) {
+ templates := NewS3PolicyTemplates()
+ bucketName := "media-bucket"
+ allowedTypes := []string{"image/jpeg", "image/png", "video/mp4"}
+
+ policy := templates.GetContentTypeRestrictedPolicy(bucketName, allowedTypes)
+
+ require.NotNil(t, policy)
+ assert.Equal(t, "2012-10-17", policy.Version)
+ assert.Len(t, policy.Statement, 2)
+
+ // First statement: Upload with content type restriction
+ uploadStmt := policy.Statement[0]
+ assert.Equal(t, "Allow", uploadStmt.Effect)
+ assert.Equal(t, "ContentTypeRestrictedUpload", uploadStmt.Sid)
+ assert.Contains(t, uploadStmt.Action, "s3:PutObject")
+ assert.Contains(t, uploadStmt.Action, "s3:CreateMultipartUpload")
+ assert.NotNil(t, uploadStmt.Condition)
+
+ // Check content type condition
+ condition := uploadStmt.Condition
+ stringEquals, exists := condition["StringEquals"]
+ assert.True(t, exists)
+
+ contentType, exists := stringEquals["s3:content-type"]
+ assert.True(t, exists)
+ assert.Equal(t, allowedTypes, contentType)
+
+ // Second statement: Read access without restrictions
+ readStmt := policy.Statement[1]
+ assert.Equal(t, "Allow", readStmt.Effect)
+ assert.Equal(t, "ReadAccess", readStmt.Sid)
+ assert.Contains(t, readStmt.Action, "s3:GetObject")
+ assert.Contains(t, readStmt.Action, "s3:ListBucket")
+ assert.Nil(t, readStmt.Condition) // No conditions for read access
+}
+
+func TestDenyDeletePolicy(t *testing.T) {
+ templates := NewS3PolicyTemplates()
+
+ policy := templates.GetDenyDeletePolicy()
+
+ require.NotNil(t, policy)
+ assert.Equal(t, "2012-10-17", policy.Version)
+ assert.Len(t, policy.Statement, 2)
+
+ // First statement: Allow everything except delete
+ allowStmt := policy.Statement[0]
+ assert.Equal(t, "Allow", allowStmt.Effect)
+ assert.Equal(t, "AllowAllExceptDelete", allowStmt.Sid)
+ assert.Contains(t, allowStmt.Action, "s3:GetObject")
+ assert.Contains(t, allowStmt.Action, "s3:PutObject")
+ assert.Contains(t, allowStmt.Action, "s3:ListBucket")
+ assert.NotContains(t, allowStmt.Action, "s3:DeleteObject")
+ assert.NotContains(t, allowStmt.Action, "s3:DeleteBucket")
+
+ // Second statement: Explicitly deny delete operations
+ denyStmt := policy.Statement[1]
+ assert.Equal(t, "Deny", denyStmt.Effect)
+ assert.Equal(t, "DenyDeleteOperations", denyStmt.Sid)
+ assert.Contains(t, denyStmt.Action, "s3:DeleteObject")
+ assert.Contains(t, denyStmt.Action, "s3:DeleteObjectVersion")
+ assert.Contains(t, denyStmt.Action, "s3:DeleteBucket")
+}
+
+func TestPolicyTemplateMetadata(t *testing.T) {
+ templates := NewS3PolicyTemplates()
+
+ t.Run("GetAllPolicyTemplates", func(t *testing.T) {
+ allTemplates := templates.GetAllPolicyTemplates()
+
+ assert.Greater(t, len(allTemplates), 10) // Should have many templates
+
+ // Check that each template has required fields
+ for _, template := range allTemplates {
+ assert.NotEmpty(t, template.Name)
+ assert.NotEmpty(t, template.Description)
+ assert.NotEmpty(t, template.Category)
+ assert.NotEmpty(t, template.UseCase)
+ assert.NotNil(t, template.Policy)
+ assert.Equal(t, "2012-10-17", template.Policy.Version)
+ }
+ })
+
+ t.Run("GetPolicyTemplateByName", func(t *testing.T) {
+ // Test existing template
+ template := templates.GetPolicyTemplateByName("S3ReadOnlyAccess")
+ require.NotNil(t, template)
+ assert.Equal(t, "S3ReadOnlyAccess", template.Name)
+ assert.Equal(t, "Basic Access", template.Category)
+
+ // Test non-existing template
+ nonExistent := templates.GetPolicyTemplateByName("NonExistentTemplate")
+ assert.Nil(t, nonExistent)
+ })
+
+ t.Run("GetPolicyTemplatesByCategory", func(t *testing.T) {
+ basicAccessTemplates := templates.GetPolicyTemplatesByCategory("Basic Access")
+ assert.GreaterOrEqual(t, len(basicAccessTemplates), 2)
+
+ for _, template := range basicAccessTemplates {
+ assert.Equal(t, "Basic Access", template.Category)
+ }
+
+ // Test non-existing category
+ emptyCategory := templates.GetPolicyTemplatesByCategory("NonExistentCategory")
+ assert.Empty(t, emptyCategory)
+ })
+
+ t.Run("PolicyTemplateParameters", func(t *testing.T) {
+ allTemplates := templates.GetAllPolicyTemplates()
+
+ // Find a template with parameters (like BucketSpecificRead)
+ var templateWithParams *PolicyTemplateDefinition
+ for _, template := range allTemplates {
+ if template.Name == "BucketSpecificRead" {
+ templateWithParams = &template
+ break
+ }
+ }
+
+ require.NotNil(t, templateWithParams)
+ assert.Greater(t, len(templateWithParams.Parameters), 0)
+
+ param := templateWithParams.Parameters[0]
+ assert.Equal(t, "bucketName", param.Name)
+ assert.Equal(t, "string", param.Type)
+ assert.True(t, param.Required)
+ assert.NotEmpty(t, param.Description)
+ assert.NotEmpty(t, param.Example)
+ })
+}
+
+func TestFormatHourHelper(t *testing.T) {
+ tests := []struct {
+ hour int
+ expected string
+ }{
+ {0, "00"},
+ {5, "05"},
+ {9, "09"},
+ {10, "10"},
+ {15, "15"},
+ {23, "23"},
+ }
+
+ for _, tt := range tests {
+ t.Run(fmt.Sprintf("Hour_%d", tt.hour), func(t *testing.T) {
+ result := formatHour(tt.hour)
+ assert.Equal(t, tt.expected, result)
+ })
+ }
+}
+
+func TestPolicyTemplateCategories(t *testing.T) {
+ templates := NewS3PolicyTemplates()
+ allTemplates := templates.GetAllPolicyTemplates()
+
+ // Extract all categories
+ categoryMap := make(map[string]int)
+ for _, template := range allTemplates {
+ categoryMap[template.Category]++
+ }
+
+ // Expected categories
+ expectedCategories := []string{
+ "Basic Access",
+ "Administrative",
+ "Bucket-Specific",
+ "Path-Restricted",
+ "Security",
+ "Upload-Specific",
+ "Presigned URLs",
+ "Content Control",
+ "Data Protection",
+ }
+
+ for _, expectedCategory := range expectedCategories {
+ count, exists := categoryMap[expectedCategory]
+ assert.True(t, exists, "Category %s should exist", expectedCategory)
+ assert.Greater(t, count, 0, "Category %s should have at least one template", expectedCategory)
+ }
+}
+
+func TestPolicyValidation(t *testing.T) {
+ templates := NewS3PolicyTemplates()
+ allTemplates := templates.GetAllPolicyTemplates()
+
+ // Test that all policies have valid structure
+ for _, template := range allTemplates {
+ t.Run("Policy_"+template.Name, func(t *testing.T) {
+ policy := template.Policy
+
+ // Basic validation
+ assert.Equal(t, "2012-10-17", policy.Version)
+ assert.Greater(t, len(policy.Statement), 0)
+
+ // Validate each statement
+ for i, stmt := range policy.Statement {
+ assert.NotEmpty(t, stmt.Effect, "Statement %d should have effect", i)
+ assert.Contains(t, []string{"Allow", "Deny"}, stmt.Effect, "Statement %d effect should be Allow or Deny", i)
+ assert.Greater(t, len(stmt.Action), 0, "Statement %d should have actions", i)
+ assert.Greater(t, len(stmt.Resource), 0, "Statement %d should have resources", i)
+
+ // Check resource format
+ for _, resource := range stmt.Resource {
+ if resource != "*" {
+ assert.Contains(t, resource, "arn:seaweed:s3:::", "Resource should be valid SeaweedFS S3 ARN: %s", resource)
+ }
+ }
+ }
+ })
+ }
+}