Fix trust policy validation for specific AWS user principals (#8597)
* Add tests for AWS user principal in AssumeRole trust policies Add test cases that verify trust policy validation when using specific AWS user principals (e.g., "arn:aws:iam::000000000000:user/backend") in the Principal field of trust policies for AssumeRole. Covers single user, multiple users (array), wildcard, and plain string principal formats. These tests demonstrate the bug reported in #8588 where specific user principals always fail validation. * Populate RequestContext in ValidateTrustPolicyForPrincipal ValidateTrustPolicyForPrincipal was creating an EvaluationContext with a nil RequestContext. The policy engine's principal matching logic looks up "aws:PrincipalArn" in RequestContext for non-wildcard principals, so specific user ARNs like "arn:aws:iam::000000000000:user/backend" always failed to match, while wildcard "*" worked because it short-circuits before the lookup. Populate RequestContext with both "principal" and "aws:PrincipalArn" keys, consistent with how IsActionAllowed already does it. Fixes #8588 * Remove GitHub discussion URL from source code comments * Add specific error message assertions in trust policy tests
This commit is contained in:
@@ -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
|
||||
|
||||
248
weed/iam/integration/trust_policy_principal_test.go
Normal file
248
weed/iam/integration/trust_policy_principal_test.go
Normal file
@@ -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")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user