From 229ad84e2d20c84d465bd94e36f5dc17b057f4fc Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Thu, 29 Oct 2026 13:38:53 -0700 Subject: [PATCH] s3tables: reject s3vectors resources in every ARN partition (#12215) * s3tables: reject s3vectors resources in bucket policies An arn:aws:s3vectors: Resource is as dead as an s3vectors action: the evaluator compares against the generated s3tables ARN, so the rule the writer believes enforced never matches. * s3tables: reject s3vectors resources in every ARN partition * s3tables: test wildcard-partition and mixed-list s3vectors resources --- weed/s3api/s3tables/permissions.go | 13 ++++++++++++- weed/s3api/s3tables/permissions_test.go | 12 ++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/weed/s3api/s3tables/permissions.go b/weed/s3api/s3tables/permissions.go index 2991bcfe18..d14eecad5d 100644 --- a/weed/s3api/s3tables/permissions.go +++ b/weed/s3api/s3tables/permissions.go @@ -157,7 +157,7 @@ func validatePolicyDocument(raw string) error { } } for _, resource := range statementActions(stmt.Resource) { - if strings.HasPrefix(resource, "arn:aws:s3vectors:") { + if arnService(resource) == "s3vectors" { return fmt.Errorf("resource %q is not enforceable in a resource policy", resource) } } @@ -165,6 +165,17 @@ func validatePolicyDocument(raw string) error { return nil } +// arnService returns the service field of an ARN in any partition, so a +// resource naming s3vectors is caught for aws-cn, aws-us-gov, and friends — +// not just the commercial partition. +func arnService(resource string) string { + parts := strings.SplitN(resource, ":", 4) + if len(parts) == 4 && parts[0] == "arn" { + return parts[2] + } + return "" +} + func statementActions(spec interface{}) []string { switch a := spec.(type) { case string: diff --git a/weed/s3api/s3tables/permissions_test.go b/weed/s3api/s3tables/permissions_test.go index bdab3c7f73..84752afdf7 100644 --- a/weed/s3api/s3tables/permissions_test.go +++ b/weed/s3api/s3tables/permissions_test.go @@ -476,6 +476,18 @@ func TestValidatePolicyDocument(t *testing.T) { if err := validatePolicyDocument(deadResource); err == nil { t.Fatal("s3vectors resource should be rejected") } + deadPartition := `{"Version":"2012-10-17","Statement":[{"Effect":"Deny","Principal":"*","Action":"s3tables:*","Resource":"arn:aws-cn:s3vectors:cn-1:a:bucket/b"}]}` + if err := validatePolicyDocument(deadPartition); err == nil { + t.Fatal("s3vectors resource in another partition should be rejected") + } + deadWildcard := `{"Version":"2012-10-17","Statement":[{"Effect":"Deny","Principal":"*","Action":"s3tables:*","Resource":"arn:*:s3vectors:r:a:bucket/b"}]}` + if err := validatePolicyDocument(deadWildcard); err == nil { + t.Fatal("s3vectors resource in a wildcard partition should be rejected") + } + deadMixed := `{"Version":"2012-10-17","Statement":[{"Effect":"Deny","Principal":"*","Action":"s3tables:*","Resource":["arn:aws:s3tables:r:a:bucket/b","arn:aws:s3vectors:r:a:bucket/b"]}]}` + if err := validatePolicyDocument(deadMixed); err == nil { + t.Fatal("resource list containing s3vectors should be rejected") + } } // An ARN-scoped statement reaches a caller recorded under the ARN's trailing