From 88c2f3c34d4d5c3d0df31e72e0c8e6680afdcf09 Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Thu, 23 Apr 2026 22:14:41 -0700 Subject: [PATCH] fix(iam): accept bare "*" resource in PutUserPolicy (#9209) (#9210) AWS IAM treats a bare "*" in a statement's Resource as "any resource", but the embedded IAM resource parser required a 6-segment S3 ARN and silently skipped anything else. With a policy like {Action: "s3:*", Resource: "*"}, every resource was dropped and the statement produced no actions, so PutUserPolicy rejected the document with "no valid actions found in policy document". Short-circuit Resource == "*" to the same full-wildcard path that "arn:aws:s3:::*" already takes. --- weed/s3api/s3api_embedded_iam.go | 16 ++++++++++++---- weed/s3api/s3api_embedded_iam_test.go | 27 +++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 4 deletions(-) diff --git a/weed/s3api/s3api_embedded_iam.go b/weed/s3api/s3api_embedded_iam.go index 1e581d545..7e02f39b8 100644 --- a/weed/s3api/s3api_embedded_iam.go +++ b/weed/s3api/s3api_embedded_iam.go @@ -819,9 +819,18 @@ func (e *EmbeddedIamApi) getActions(policy *policy_engine.PolicyDocument) ([]str return nil, fmt.Errorf("not a valid effect: '%s'. Only 'Allow' is possible", statement.Effect) } for _, resource := range statement.Resource.Strings() { - res := strings.Split(resource, ":") - if len(res) != 6 || res[0] != "arn" || res[1] != "aws" || res[2] != "s3" { - continue + // AWS IAM treats a bare "*" as "any resource". Normalize it to the + // full-wildcard S3 ARN so it flows through the same path as + // "arn:aws:s3:::*" instead of being dropped as malformed. + var resourcePath string + if resource == "*" { + resourcePath = "*" + } else { + res := strings.Split(resource, ":") + if len(res) != 6 || res[0] != "arn" || res[1] != "aws" || res[2] != "s3" { + continue + } + resourcePath = res[5] } for _, action := range statement.Action.Strings() { act := strings.Split(action, ":") @@ -833,7 +842,6 @@ func (e *EmbeddedIamApi) getActions(policy *policy_engine.PolicyDocument) ([]str return nil, fmt.Errorf("not a valid action: '%s'", act[1]) } - resourcePath := res[5] if resourcePath == "*" { // Wildcard - applies to all buckets actions = append(actions, statementAction) diff --git a/weed/s3api/s3api_embedded_iam_test.go b/weed/s3api/s3api_embedded_iam_test.go index 124831eb9..4d2412dee 100644 --- a/weed/s3api/s3api_embedded_iam_test.go +++ b/weed/s3api/s3api_embedded_iam_test.go @@ -1814,6 +1814,33 @@ func TestEmbeddedIamGetActionsFromPolicy(t *testing.T) { assert.Contains(t, actions, "Write:mybucket") } +// 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 +// "no valid actions found in policy document" because the resource parser +// required a full ARN. +func TestEmbeddedIamPutUserPolicyAllResourceWildcard(t *testing.T) { + api := NewEmbeddedIamApiForTest() + + // From issue #9209: bare "*" resource and bare "s3:*" action. + policyDoc := `{ + "Version": "2012-10-17", + "Statement": [{ + "Sid": "AllowS3Admin", + "Effect": "Allow", + "Action": "s3:*", + "Resource": "*" + }] + }` + + policy, err := api.GetPolicyDocument(&policyDoc) + require.NoError(t, err) + + actions, err := api.getActions(&policy) + require.NoError(t, err, "getActions must accept bare \"*\" resource") + assert.Contains(t, actions, ACTION_ADMIN, "s3:* should map to admin action") +} + // TestEmbeddedIamSetUserStatus tests enabling/disabling a user func TestEmbeddedIamSetUserStatus(t *testing.T) { api := NewEmbeddedIamApiForTest()