From 8c0c7248b3c8c29c319d5626d43de391f6b66bf3 Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Wed, 25 Feb 2026 10:30:05 -0800 Subject: [PATCH] Refresh IAM config after policy attachments (#8439) * Refresh IAM cache after policy attachments * error handling --- weed/s3api/s3api_embedded_iam.go | 23 +++++++++++++++ weed/s3api/s3api_embedded_iam_test.go | 42 +++++++++++++++++++++++++++ 2 files changed, 65 insertions(+) diff --git a/weed/s3api/s3api_embedded_iam.go b/weed/s3api/s3api_embedded_iam.go index f18114ad1..9085b2b2e 100644 --- a/weed/s3api/s3api_embedded_iam.go +++ b/weed/s3api/s3api_embedded_iam.go @@ -51,6 +51,19 @@ func NewEmbeddedIamApi(credentialManager *credential.CredentialManager, iam *Ide } } +func (e *EmbeddedIamApi) refreshIAMConfiguration() error { + if e.reloadConfigurationFunc != nil { + return e.reloadConfigurationFunc() + } + if e.iam == nil { + return nil + } + if err := e.iam.LoadS3ApiConfigurationFromCredentialManager(); err != nil { + return fmt.Errorf("failed to refresh IAM configuration: %w", err) + } + return nil +} + // Constants for service account identifiers const ( ServiceAccountIDLength = 12 // Length of the service account ID @@ -903,6 +916,11 @@ func (e *EmbeddedIamApi) AttachUserPolicy(ctx context.Context, values url.Values return resp, &iamError{Code: iam.ErrCodeServiceFailureException, Error: err} } + // Best-effort refresh: log any failures but don't fail the API call since the mutation succeeded + if err := e.refreshIAMConfiguration(); err != nil { + glog.Warningf("Failed to refresh IAM configuration after attaching policy %s to user %s: %v", policyName, userName, err) + } + return resp, nil } @@ -943,6 +961,11 @@ func (e *EmbeddedIamApi) DetachUserPolicy(ctx context.Context, values url.Values return resp, &iamError{Code: iam.ErrCodeServiceFailureException, Error: err} } + // Best-effort refresh: log any failures but don't fail the API call since the mutation succeeded + if err := e.refreshIAMConfiguration(); err != nil { + glog.Warningf("Failed to refresh IAM configuration after detaching policy %s from user %s: %v", policyName, userName, err) + } + return resp, nil } diff --git a/weed/s3api/s3api_embedded_iam_test.go b/weed/s3api/s3api_embedded_iam_test.go index 906895f6d..1c0d351f8 100644 --- a/weed/s3api/s3api_embedded_iam_test.go +++ b/weed/s3api/s3api_embedded_iam_test.go @@ -20,9 +20,11 @@ import ( "github.com/seaweedfs/seaweedfs/weed/credential" "github.com/seaweedfs/seaweedfs/weed/credential/memory" "github.com/seaweedfs/seaweedfs/weed/pb/iam_pb" + "github.com/seaweedfs/seaweedfs/weed/s3api/policy_engine" . "github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants" "github.com/seaweedfs/seaweedfs/weed/s3api/s3err" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "google.golang.org/protobuf/proto" ) @@ -468,6 +470,46 @@ func TestEmbeddedIamAttachUserPolicy(t *testing.T) { assert.Equal(t, []string{"TestManagedPolicy"}, api.mockConfig.Identities[0].PolicyNames) } +func TestEmbeddedIamAttachUserPolicyRefreshesIAM(t *testing.T) { + api := NewEmbeddedIamApiForTest() + ctx := context.Background() + cm := api.credentialManager + user := &iam_pb.Identity{ + Name: "policyRefreshUser", + Credentials: []*iam_pb.Credential{ + {AccessKey: "REFRESHACCESS", SecretKey: "REFRESHSECRET"}, + }, + } + require.NoError(t, cm.CreateUser(ctx, user)) + policy := policy_engine.PolicyDocument{ + Version: policy_engine.PolicyVersion2012_10_17, + Statement: []policy_engine.PolicyStatement{ + { + Effect: policy_engine.PolicyEffectAllow, + Action: policy_engine.NewStringOrStringSlice("s3:GetObject"), + Resource: policy_engine.NewStringOrStringSlice("arn:aws:s3:::bucket/*"), + }, + }, + } + require.NoError(t, cm.PutPolicy(ctx, "RefreshPolicy", policy)) + require.NoError(t, api.iam.LoadS3ApiConfigurationFromCredentialManager()) + + identity := api.iam.lookupByIdentityName("policyRefreshUser") + require.NotNil(t, identity) + assert.Empty(t, identity.PolicyNames) + + values := url.Values{} + values.Set("UserName", "policyRefreshUser") + values.Set("PolicyArn", "arn:aws:iam:::policy/RefreshPolicy") + + _, iamErr := api.AttachUserPolicy(ctx, values) + require.Nil(t, iamErr) + + identity = api.iam.lookupByIdentityName("policyRefreshUser") + require.NotNil(t, identity) + assert.Equal(t, []string{"RefreshPolicy"}, identity.PolicyNames) +} + // TestEmbeddedIamAttachUserPolicyNoSuchPolicy tests attach failure when managed policy does not exist. func TestEmbeddedIamAttachUserPolicyNoSuchPolicy(t *testing.T) { api := NewEmbeddedIamApiForTest()