From f5e319ae8ec50bdad60cc6f7688032c6ce103720 Mon Sep 17 00:00:00 2001 From: Jay Hemnani <193022578+jayhemnani9910@users.noreply.github.com> Date: Sat, 24 Oct 2026 05:38:49 +0530 Subject: [PATCH] iam: GetUserPolicy returns NoSuchEntity for a policy name the user does not have (#11944) * iam: GetUserPolicy returns NoSuchEntity for a policy name the user does not have When the user has stored inline policies, an unknown name fell through to the policy rebuilt from the user's actions, so a deleted policy still read back. The standalone IAM server already returns NoSuchEntity here. * iam: return service failure when listing inline policies fails A ListUserInlinePolicies error in GetUserPolicy fell through to action-based reconstruction, masking a store failure as a policy document. Propagate it as ServiceFailureException instead, matching the standalone iam server and the other callers in this file. * iam: return service failure when the inline policy store read fails --------- Co-authored-by: chrislusf Co-authored-by: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- weed/s3api/s3api_embedded_iam.go | 12 +++++-- weed/s3api/s3api_embedded_iam_test.go | 50 +++++++++++++++++++++++++++ 2 files changed, 60 insertions(+), 2 deletions(-) diff --git a/weed/s3api/s3api_embedded_iam.go b/weed/s3api/s3api_embedded_iam.go index 4b837117f4..1c6aa1bec0 100644 --- a/weed/s3api/s3api_embedded_iam.go +++ b/weed/s3api/s3api_embedded_iam.go @@ -1291,9 +1291,9 @@ func (e *EmbeddedIamApi) GetUserPolicy(s3cfg *iam_pb.S3ApiConfiguration, values ctx := context.Background() storedDoc, storeErr := e.credentialManager.GetUserInlinePolicy(ctx, userName, policyName) if storeErr != nil { - glog.Warningf("GetUserPolicy: failed to read stored inline policy %q for user %s: %v; falling back to reconstruction", policyName, userName, storeErr) + return resp, &iamError{Code: iam.ErrCodeServiceFailureException, Error: fmt.Errorf("get inline policy %s for %s: %w", policyName, userName, storeErr)} } - if storeErr == nil && storedDoc != nil { + if storedDoc != nil { policyDocumentJSON, err := json.Marshal(storedDoc) if err != nil { return resp, &iamError{Code: iam.ErrCodeServiceFailureException, Error: err} @@ -1301,6 +1301,14 @@ func (e *EmbeddedIamApi) GetUserPolicy(s3cfg *iam_pb.S3ApiConfiguration, values resp.GetUserPolicyResult.PolicyDocument = string(policyDocumentJSON) return resp, nil } + // The user has stored inline policies, but none by this name + policyNames, err := e.credentialManager.ListUserInlinePolicies(ctx, userName) + if err != nil { + return resp, &iamError{Code: iam.ErrCodeServiceFailureException, Error: fmt.Errorf("list inline policies for %s: %w", userName, err)} + } + if len(policyNames) > 0 { + return resp, &iamError{Code: iam.ErrCodeNoSuchEntityException, Error: fmt.Errorf("the user policy with name %s cannot be found", policyName)} + } } // Fallback: reconstruct from ident.Actions (lossy - fine-grained actions diff --git a/weed/s3api/s3api_embedded_iam_test.go b/weed/s3api/s3api_embedded_iam_test.go index 2e7f9206af..4269958de9 100644 --- a/weed/s3api/s3api_embedded_iam_test.go +++ b/weed/s3api/s3api_embedded_iam_test.go @@ -1979,6 +1979,56 @@ func TestEmbeddedIamGetUserPolicyFallback(t *testing.T) { assert.ElementsMatch(t, []string{"arn:aws:s3:::b-le*", "arn:aws:s3:::b-le*/*"}, stmt.Resource.Strings()) } +// TestEmbeddedIamGetUserPolicyMissingName checks that a policy name the user does not have +// is NoSuchEntity, not a policy rebuilt from the user's other inline policies. +func TestEmbeddedIamGetUserPolicyMissingName(t *testing.T) { + api := NewEmbeddedIamApiForTest() + s3cfg := &iam_pb.S3ApiConfiguration{ + Identities: []*iam_pb.Identity{{Name: "steward"}}, + } + api.mockConfig = s3cfg + + _, iamErr := api.PutUserPolicy(s3cfg, url.Values{ + "UserName": []string{"steward"}, + "PolicyName": []string{"read"}, + "PolicyDocument": []string{`{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Action":"s3:GetObject","Resource":"arn:aws:s3:::b/*"}]}`}, + }) + require.Nil(t, iamErr) + + _, iamErr = api.GetUserPolicy(s3cfg, url.Values{ + "UserName": []string{"steward"}, + "PolicyName": []string{"write"}, + }) + require.NotNil(t, iamErr) + assert.Equal(t, iam.ErrCodeNoSuchEntityException, iamErr.Code) +} + +// TestEmbeddedIamGetUserPolicyStoreError checks that a store failure surfaces as +// ServiceFailure instead of rebuilding a policy from the identity's actions. +func TestEmbeddedIamGetUserPolicyStoreError(t *testing.T) { + api := NewEmbeddedIamApiForTest() + s3cfg := &iam_pb.S3ApiConfiguration{ + Identities: []*iam_pb.Identity{{Name: "steward"}}, + } + api.mockConfig = s3cfg + + _, iamErr := api.PutUserPolicy(s3cfg, url.Values{ + "UserName": []string{"steward"}, + "PolicyName": []string{"read"}, + "PolicyDocument": []string{`{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Action":"s3:GetObject","Resource":"arn:aws:s3:::b/*"}]}`}, + }) + require.Nil(t, iamErr) + + api.credentialManager = &credential.CredentialManager{Store: &memory.MemoryStore{}} + + _, iamErr = api.GetUserPolicy(s3cfg, url.Values{ + "UserName": []string{"steward"}, + "PolicyName": []string{"read"}, + }) + require.NotNil(t, iamErr) + assert.Equal(t, iam.ErrCodeServiceFailureException, iamErr.Code) +} + // TestEmbeddedIamGetUserPolicyUserNotFound tests GetUserPolicy with non-existent user func TestEmbeddedIamGetUserPolicyUserNotFound(t *testing.T) { api := NewEmbeddedIamApiForTest()