diff --git a/weed/iam/integration/iam_manager_trust.go b/weed/iam/integration/iam_manager_trust.go index e97ed62f6..462ec14a7 100644 --- a/weed/iam/integration/iam_manager_trust.go +++ b/weed/iam/integration/iam_manager_trust.go @@ -27,11 +27,18 @@ func (m *IAMManager) ValidateTrustPolicyForPrincipal(ctx context.Context, roleAr return fmt.Errorf("role has no trust policy") } - // Create evaluation context + // Create evaluation context with RequestContext populated so that + // principal matching works for specific (non-wildcard) principals. + // Without this, evaluatePrincipalValue cannot look up "aws:PrincipalArn" + // and always returns false for non-wildcard trust policy principals. evalCtx := &policy.EvaluationContext{ Principal: principalArn, Action: "sts:AssumeRole", Resource: roleArn, + RequestContext: map[string]interface{}{ + "principal": principalArn, + "aws:PrincipalArn": principalArn, + }, } // Evaluate the trust policy diff --git a/weed/iam/integration/trust_policy_principal_test.go b/weed/iam/integration/trust_policy_principal_test.go new file mode 100644 index 000000000..6e4cb0906 --- /dev/null +++ b/weed/iam/integration/trust_policy_principal_test.go @@ -0,0 +1,248 @@ +package integration + +import ( + "context" + "testing" + + "github.com/seaweedfs/seaweedfs/weed/iam/policy" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestTrustPolicyAWSUserPrincipal tests that trust policies with specific AWS user +// principals work correctly with ValidateTrustPolicyForPrincipal. +// This is a regression test for the case where setting a specific user principal like: +// +// "Principal": {"AWS": "arn:aws:iam::000000000000:user/backend"} +// +// would fail with "trust policy denies access to principal" even though the +// caller's principal ARN matched. +func TestTrustPolicyAWSUserPrincipal(t *testing.T) { + iamManager := setupIntegratedIAMSystem(t) + ctx := context.Background() + + const ( + accountID = "000000000000" + backendUser = "backend" + backendArn = "arn:aws:iam::" + accountID + ":user/" + backendUser + otherUser = "other" + otherArn = "arn:aws:iam::" + accountID + ":user/" + otherUser + clientRoleN = "ClientRole" + clientRoleA = "arn:aws:iam::role/" + clientRoleN + ) + + // Create role with trust policy restricted to a specific AWS user principal + // (the exact scenario from discussion #8588) + err := iamManager.CreateRole(ctx, "", clientRoleN, &RoleDefinition{ + RoleName: clientRoleN, + TrustPolicy: &policy.PolicyDocument{ + Version: "2012-10-17", + Statement: []policy.Statement{ + { + Effect: "Allow", + Principal: map[string]interface{}{ + "AWS": backendArn, + }, + Action: []string{"sts:AssumeRole"}, + }, + }, + }, + AttachedPolicies: []string{"S3ReadOnlyPolicy"}, + }) + require.NoError(t, err) + + tests := []struct { + name string + principalArn string + expectErr bool + description string + }{ + { + name: "matching user principal should be allowed", + principalArn: backendArn, + expectErr: false, + description: "Backend user ARN matches the trust policy principal exactly", + }, + { + name: "non-matching user principal should be denied", + principalArn: otherArn, + expectErr: true, + description: "Other user ARN does not match the trust policy principal", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := iamManager.ValidateTrustPolicyForPrincipal(ctx, clientRoleA, tt.principalArn) + if tt.expectErr { + assert.Error(t, err, tt.description) + assert.Contains(t, err.Error(), "trust policy denies access") + } else { + assert.NoError(t, err, tt.description) + } + }) + } +} + +// TestTrustPolicyAWSWildcardPrincipal tests that wildcard AWS principals +// continue to work correctly (this already works, serves as a regression guard). +func TestTrustPolicyAWSWildcardPrincipal(t *testing.T) { + iamManager := setupIntegratedIAMSystem(t) + ctx := context.Background() + + const roleName = "WildcardAWSRole" + const roleArn = "arn:aws:iam::role/" + roleName + + err := iamManager.CreateRole(ctx, "", roleName, &RoleDefinition{ + RoleName: roleName, + TrustPolicy: &policy.PolicyDocument{ + Version: "2012-10-17", + Statement: []policy.Statement{ + { + Effect: "Allow", + Principal: map[string]interface{}{ + "AWS": "*", + }, + Action: []string{"sts:AssumeRole"}, + }, + }, + }, + AttachedPolicies: []string{"S3ReadOnlyPolicy"}, + }) + require.NoError(t, err) + + // Any user should be able to assume a role with wildcard AWS principal + err = iamManager.ValidateTrustPolicyForPrincipal(ctx, roleArn, "arn:aws:iam::000000000000:user/anyuser") + assert.NoError(t, err, "Wildcard AWS principal should allow any user") +} + +// TestTrustPolicyAWSUserArrayPrincipal tests trust policies with an array of +// AWS user principals. +func TestTrustPolicyAWSUserArrayPrincipal(t *testing.T) { + iamManager := setupIntegratedIAMSystem(t) + ctx := context.Background() + + const ( + accountID = "000000000000" + roleName = "MultiUserRole" + roleArn = "arn:aws:iam::role/" + roleName + user1Arn = "arn:aws:iam::" + accountID + ":user/user1" + user2Arn = "arn:aws:iam::" + accountID + ":user/user2" + user3Arn = "arn:aws:iam::" + accountID + ":user/user3" + ) + + // Create role with multiple allowed user principals + err := iamManager.CreateRole(ctx, "", roleName, &RoleDefinition{ + RoleName: roleName, + TrustPolicy: &policy.PolicyDocument{ + Version: "2012-10-17", + Statement: []policy.Statement{ + { + Effect: "Allow", + Principal: map[string]interface{}{ + "AWS": []interface{}{user1Arn, user2Arn}, + }, + Action: []string{"sts:AssumeRole"}, + }, + }, + }, + AttachedPolicies: []string{"S3ReadOnlyPolicy"}, + }) + require.NoError(t, err) + + tests := []struct { + name string + principalArn string + expectErr bool + }{ + { + name: "first listed user is allowed", + principalArn: user1Arn, + expectErr: false, + }, + { + name: "second listed user is allowed", + principalArn: user2Arn, + expectErr: false, + }, + { + name: "unlisted user is denied", + principalArn: user3Arn, + expectErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := iamManager.ValidateTrustPolicyForPrincipal(ctx, roleArn, tt.principalArn) + if tt.expectErr { + if assert.Error(t, err) { + assert.Contains(t, err.Error(), "trust policy denies access") + } + } else { + assert.NoError(t, err) + } + }) + } +} + +// TestTrustPolicyPlainStringPrincipal tests trust policies with a plain string +// principal (not wrapped in {"AWS": ...}). +func TestTrustPolicyPlainStringPrincipal(t *testing.T) { + iamManager := setupIntegratedIAMSystem(t) + ctx := context.Background() + + const ( + roleName = "PlainStringPrincipalRole" + roleArn = "arn:aws:iam::role/" + roleName + userArn = "arn:aws:iam::000000000000:user/backend" + ) + + // Plain string wildcard - should work + err := iamManager.CreateRole(ctx, "", roleName, &RoleDefinition{ + RoleName: roleName, + TrustPolicy: &policy.PolicyDocument{ + Version: "2012-10-17", + Statement: []policy.Statement{ + { + Effect: "Allow", + Principal: "*", + Action: []string{"sts:AssumeRole"}, + }, + }, + }, + AttachedPolicies: []string{"S3ReadOnlyPolicy"}, + }) + require.NoError(t, err) + + err = iamManager.ValidateTrustPolicyForPrincipal(ctx, roleArn, userArn) + assert.NoError(t, err, "Plain wildcard principal should allow any user") + + // Plain string with specific ARN + const roleName2 = "PlainStringSpecificRole" + const roleArn2 = "arn:aws:iam::role/" + roleName2 + + err = iamManager.CreateRole(ctx, "", roleName2, &RoleDefinition{ + RoleName: roleName2, + TrustPolicy: &policy.PolicyDocument{ + Version: "2012-10-17", + Statement: []policy.Statement{ + { + Effect: "Allow", + Principal: userArn, + Action: []string{"sts:AssumeRole"}, + }, + }, + }, + AttachedPolicies: []string{"S3ReadOnlyPolicy"}, + }) + require.NoError(t, err) + + err = iamManager.ValidateTrustPolicyForPrincipal(ctx, roleArn2, userArn) + assert.NoError(t, err, "Matching plain string principal should allow user") + + err = iamManager.ValidateTrustPolicyForPrincipal(ctx, roleArn2, "arn:aws:iam::000000000000:user/other") + if assert.Error(t, err, "Non-matching plain string principal should deny user") { + assert.Contains(t, err.Error(), "trust policy denies access") + } +}