s3: embedded IAM inline policy honors prefix-scoped resources (#10192)

* s3: embedded IAM inline policy honors prefix-scoped resources

getActions stripped the trailing wildcard from a resource like
arn:aws:s3:::bucket/prefix/*, producing a non-wildcard action
(Write:bucket/prefix) that CanDo only ever matched at bucket level, so
PutObject under the prefix was denied. Preserve the object path with its
wildcard (Write:bucket/prefix/*) to match objects under the prefix,
matching the standalone iamapi behavior.

* s3: prune bucket-confined wildcard actions on bucket delete

actionScopedToBucket treated any wildcard as multi-bucket, so a
prefix-scoped action like Write:bucket/prefix/* survived deletion of its
own bucket and could re-grant access if the bucket was recreated. Scope
the wildcard check to the bucket segment only: a wildcard in the object
path stays scoped to its bucket, while one in the bucket segment does
not.
This commit is contained in:
Chris Lu authored and GitHub committed 2026-07-02 09:14:54 -07:00
1 parent cc4043c9d2
commit c46526822b
4 files changed
+75 -13

No files matched your search

+37
View File
@@ -2095,6 +2095,43 @@ func TestEmbeddedIamGetActionsFromPolicy(t *testing.T) {
assert.Contains(t, actions, "Write:mybucket")
}
// TestEmbeddedIamGetActionsPrefixScopedResource verifies that a prefix-scoped
// resource keeps its trailing wildcard so CanDo authorizes objects under the
// prefix. Stripping the wildcard produced a non-wildcard action that only
// matched at bucket level, denying every PutObject under the prefix.
func TestEmbeddedIamGetActionsPrefixScopedResource(t *testing.T) {
api := NewEmbeddedIamApiForTest()
policyDoc := `{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Action": ["s3:Get*", "s3:Put*", "s3:DeleteObject"],
"Resource": ["arn:aws:s3:::mcp-upload/mcp/TMS/api_keys/example/*"]
}]
}`
policy, err := api.GetPolicyDocument(&policyDoc)
require.NoError(t, err)
actions, err := api.getActions(&policy)
require.NoError(t, err)
assert.Contains(t, actions, "Read:mcp-upload/mcp/TMS/api_keys/example/*")
assert.Contains(t, actions, "Write:mcp-upload/mcp/TMS/api_keys/example/*")
identity := &Identity{Name: "prefix-user"}
for _, a := range actions {
identity.Actions = append(identity.Actions, Action(a))
}
assert.True(t, identity.CanDo(ACTION_WRITE, "mcp-upload", "mcp/TMS/api_keys/example/logo.png"),
"PutObject under the allowed prefix should be authorized")
assert.True(t, identity.CanDo(ACTION_READ, "mcp-upload", "mcp/TMS/api_keys/example/logo.png"),
"GetObject under the allowed prefix should be authorized")
assert.False(t, identity.CanDo(ACTION_WRITE, "mcp-upload", "other/logo.png"),
"PutObject outside the allowed prefix should be denied")
}
// TestEmbeddedIamPutUserPolicyAllResourceWildcard reproduces issue #9209:
// an AWS-style policy using "Action":"s3:*" with a bare "Resource":"*" is
// valid AWS IAM syntax (meaning "any resource") but was rejected with