S3: Enforce bucket policy (#7471)

* evaluate policies during authorization

* cache bucket policy

* refactor

* matching with regex special characters

* Case Sensitivity, pattern cache, Dead Code Removal

* Fixed Typo, Restored []string Case, Added Cache Size Limit

* hook up with policy engine

* remove old implementation

* action mapping

* validate

* if not specified, fall through to IAM checks

* fmt

* Fail-close on policy evaluation errors

* Explicit `Allow` bypasses IAM checks

* fix error message

* arn:seaweed => arn:aws

* remove legacy support

* fix tests

* Clean up bucket policy after this test

* fix for tests

* address comments

* security fixes

* fix tests

* temp comment out
This commit is contained in:
Chris Lu
2025-11-12 22:14:50 -08:00
committed by GitHub
parent 50f067bcfd
commit 508d06d9a5
47 changed files with 1104 additions and 749 deletions

View File

@@ -26,8 +26,8 @@ func TestS3PolicyTemplates(t *testing.T) {
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:::*/*")
assert.Contains(t, stmt.Resource, "arn:aws:s3:::*")
assert.Contains(t, stmt.Resource, "arn:aws:s3:::*/*")
})
t.Run("S3WriteOnlyPolicy", func(t *testing.T) {
@@ -45,8 +45,8 @@ func TestS3PolicyTemplates(t *testing.T) {
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:::*/*")
assert.Contains(t, stmt.Resource, "arn:aws:s3:::*")
assert.Contains(t, stmt.Resource, "arn:aws:s3:::*/*")
})
t.Run("S3AdminPolicy", func(t *testing.T) {
@@ -61,8 +61,8 @@ func TestS3PolicyTemplates(t *testing.T) {
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:::*/*")
assert.Contains(t, stmt.Resource, "arn:aws:s3:::*")
assert.Contains(t, stmt.Resource, "arn:aws:s3:::*/*")
})
}
@@ -84,8 +84,8 @@ func TestBucketSpecificPolicies(t *testing.T) {
assert.Contains(t, stmt.Action, "s3:ListBucket")
assert.NotContains(t, stmt.Action, "s3:PutObject")
expectedBucketArn := "arn:seaweed:s3:::" + bucketName
expectedObjectArn := "arn:seaweed:s3:::" + bucketName + "/*"
expectedBucketArn := "arn:aws:s3:::" + bucketName
expectedObjectArn := "arn:aws:s3:::" + bucketName + "/*"
assert.Contains(t, stmt.Resource, expectedBucketArn)
assert.Contains(t, stmt.Resource, expectedObjectArn)
})
@@ -104,8 +104,8 @@ func TestBucketSpecificPolicies(t *testing.T) {
assert.Contains(t, stmt.Action, "s3:CreateMultipartUpload")
assert.NotContains(t, stmt.Action, "s3:GetObject")
expectedBucketArn := "arn:seaweed:s3:::" + bucketName
expectedObjectArn := "arn:seaweed:s3:::" + bucketName + "/*"
expectedBucketArn := "arn:aws:s3:::" + bucketName
expectedObjectArn := "arn:aws:s3:::" + bucketName + "/*"
assert.Contains(t, stmt.Resource, expectedBucketArn)
assert.Contains(t, stmt.Resource, expectedObjectArn)
})
@@ -127,7 +127,7 @@ func TestPathBasedAccessPolicy(t *testing.T) {
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.Contains(t, listStmt.Resource, "arn:aws:s3:::"+bucketName)
assert.NotNil(t, listStmt.Condition)
// Second statement: Object operations on path
@@ -138,7 +138,7 @@ func TestPathBasedAccessPolicy(t *testing.T) {
assert.Contains(t, objectStmt.Action, "s3:PutObject")
assert.Contains(t, objectStmt.Action, "s3:DeleteObject")
expectedObjectArn := "arn:seaweed:s3:::" + bucketName + "/" + pathPrefix + "/*"
expectedObjectArn := "arn:aws:s3:::" + bucketName + "/" + pathPrefix + "/*"
assert.Contains(t, objectStmt.Resource, expectedObjectArn)
}
@@ -216,7 +216,7 @@ func TestMultipartUploadPolicyTemplate(t *testing.T) {
assert.Contains(t, multipartStmt.Action, "s3:ListMultipartUploads")
assert.Contains(t, multipartStmt.Action, "s3:ListParts")
expectedObjectArn := "arn:seaweed:s3:::" + bucketName + "/*"
expectedObjectArn := "arn:aws:s3:::" + bucketName + "/*"
assert.Contains(t, multipartStmt.Resource, expectedObjectArn)
// Second statement: List bucket
@@ -225,7 +225,7 @@ func TestMultipartUploadPolicyTemplate(t *testing.T) {
assert.Equal(t, "ListBucketForMultipart", listStmt.Sid)
assert.Contains(t, listStmt.Action, "s3:ListBucket")
expectedBucketArn := "arn:seaweed:s3:::" + bucketName
expectedBucketArn := "arn:aws:s3:::" + bucketName
assert.Contains(t, listStmt.Resource, expectedBucketArn)
}
@@ -246,7 +246,7 @@ func TestPresignedURLPolicy(t *testing.T) {
assert.Contains(t, stmt.Action, "s3:PutObject")
assert.NotNil(t, stmt.Condition)
expectedObjectArn := "arn:seaweed:s3:::" + bucketName + "/*"
expectedObjectArn := "arn:aws:s3:::" + bucketName + "/*"
assert.Contains(t, stmt.Resource, expectedObjectArn)
// Check signature version condition
@@ -495,7 +495,7 @@ func TestPolicyValidation(t *testing.T) {
// 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)
assert.Contains(t, resource, "arn:aws:s3:::", "Resource should be valid AWS S3 ARN: %s", resource)
}
}
}