AWS IAM Policy Simulator: Testing and Debugging Policies Like a Pro
Deploying an IAM policy change to production without testing it is like pushing application code without running tests. The IAM Policy Simulator lets you verify that your policies grant the intended permissions and nothing more, all without making a single actual API call. This guide covers how to use the simulator effectively, from interactive debugging to automated CI/CD integration.
Simulating API Calls
The Policy Simulator evaluates IAM policies against simulated API requests. You provide a principal (user or role), a set of actions, and optionally a set of resource ARNs, and the simulator tells you whether each action would be allowed or denied.
Using the CLI
# Test whether a role can perform specific S3 operations
aws iam simulate-principal-policy \
--policy-source-arn arn:aws:iam::123456789012:role/DataProcessingRole \
--action-names \
s3:GetObject \
s3:PutObject \
s3:DeleteObject \
s3:ListBucket \
--resource-arns \
arn:aws:s3:::production-data-bucket \
arn:aws:s3:::production-data-bucket/* \
--query 'EvaluationResults[].{
Action: EvalActionName,
Decision: EvalDecision,
MatchedStatements: MatchedStatements[].SourcePolicyId
}' \
--output table
The output tells you not just whether access is allowed, but which specific policy statement produced the decision. This is invaluable when debugging complex permission chains where multiple policies interact.
Understanding Evaluation Logic
The simulator follows the same evaluation logic as the real IAM engine: explicit deny wins over everything, then it checks for explicit allow, and the default is implicit deny. When you see an "implicitDeny" result, it means no policy granted the permission. When you see an "explicitDeny," a policy actively blocked it.
Pay attention to the MatchedStatements field. It identifies which policy and which statement within that policy produced the decision. When a role has five attached policies and two inline policies, knowing exactly which statement is responsible saves hours of manual policy reading.
Testing Cross-Account Access
Cross-account access involves two policy evaluations: the trust policy on the target role and the permissions policies in the target account. The simulator can test both sides.
import boto3
import json
def test_cross_account_access(source_role_arn, target_role_arn, actions, resources):
"""Test whether a source role can assume a target role and perform actions."""
iam = boto3.client('iam')
results = {'assume_role': None, 'actions': []}
# Step 1: Can the source role assume the target role?
assume_result = iam.simulate_principal_policy(
PolicySourceArn=source_role_arn,
ActionNames=['sts:AssumeRole'],
ResourceArns=[target_role_arn]
)
assume_decision = assume_result['EvaluationResults'][0]['EvalDecision']
results['assume_role'] = assume_decision
if assume_decision != 'allowed':
print(f"BLOCKED: {source_role_arn} cannot assume {target_role_arn}")
return results
# Step 2: Once assumed, can the target role perform the required actions?
action_results = iam.simulate_principal_policy(
PolicySourceArn=target_role_arn,
ActionNames=actions,
ResourceArns=resources
)
for eval_result in action_results['EvaluationResults']:
result = {
'action': eval_result['EvalActionName'],
'decision': eval_result['EvalDecision'],
'resource': eval_result.get('EvalResourceName', '*')
}
results['actions'].append(result)
status = 'ALLOWED' if result['decision'] == 'allowed' else 'DENIED'
print(f" {status}: {result['action']} on {result['resource']}")
return results
# Test a cross-account scanning role
test_cross_account_access(
source_role_arn='arn:aws:iam::123456789012:role/ScannerRole',
target_role_arn='arn:aws:iam::987654321098:role/AccessLensReadOnlyRole',
actions=[
'iam:ListRoles',
'iam:ListPolicies',
'iam:GetPolicy',
'iam:GetRolePolicy',
's3:ListAllMyBuckets',
's3:GetBucketPolicy'
],
resources=['*']
)
Note that the simulator in your source account can only evaluate the source-side permissions. It cannot evaluate the trust policy on the target role in another account. For a complete cross-account test, you need to run simulations in both accounts or use IAM Access Analyzer's policy validation for the trust policy.
Comparing Effective Permissions
When managing multiple roles that should have similar permissions, such as regional deployment roles or team-specific roles, permission drift is common. Use batch simulations to compare effective permissions across principals.
def compare_role_permissions(role_arns, actions, resources):
"""Compare effective permissions across multiple roles."""
iam = boto3.client('iam')
comparison = {}
for role_arn in role_arns:
role_name = role_arn.split('/')[-1]
comparison[role_name] = {}
results = iam.simulate_principal_policy(
PolicySourceArn=role_arn,
ActionNames=actions,
ResourceArns=resources
)
for eval_result in results['EvaluationResults']:
action = eval_result['EvalActionName']
comparison[role_name][action] = eval_result['EvalDecision']
# Find discrepancies
discrepancies = []
for action in actions:
decisions = {role: comparison[role].get(action, 'unknown') for role in comparison}
unique_decisions = set(decisions.values())
if len(unique_decisions) > 1:
discrepancies.append({
'action': action,
'decisions': decisions
})
if discrepancies:
print(f"\nFound {len(discrepancies)} permission discrepancies:")
for d in discrepancies:
print(f"\n Action: {d['action']}")
for role, decision in d['decisions'].items():
print(f" {role}: {decision}")
else:
print("\nAll roles have identical permissions for the tested actions.")
return comparison, discrepancies
# Compare deployment roles across regions
compare_role_permissions(
role_arns=[
'arn:aws:iam::123456789012:role/DeployRole-us-east-1',
'arn:aws:iam::123456789012:role/DeployRole-us-west-2',
'arn:aws:iam::123456789012:role/DeployRole-eu-west-1'
],
actions=[
'ecs:UpdateService', 'ecs:DescribeServices',
'ecr:GetAuthorizationToken', 'ecr:BatchGetImage',
's3:GetObject', 's3:PutObject',
'lambda:UpdateFunctionCode', 'lambda:UpdateFunctionConfiguration'
],
resources=['*']
)
Integrating Policy Testing into CI/CD
The most impactful use of the Policy Simulator is automated testing in your deployment pipeline. Every pull request that modifies an IAM policy should trigger simulation tests that verify the policy grants the intended permissions and does not grant unintended ones.
Pipeline Integration
#!/bin/bash
# ci/test-iam-policies.sh
# Run as a CI step after CDK synth but before deploy
set -euo pipefail
ROLE_ARN="arn:aws:iam::123456789012:role/ApplicationRole"
FAILED=0
echo "=== IAM Policy Simulation Tests ==="
# Test 1: Role SHOULD be able to read from the application bucket
RESULT=$(aws iam simulate-principal-policy \
--policy-source-arn "$ROLE_ARN" \
--action-names s3:GetObject \
--resource-arns "arn:aws:s3:::app-config-bucket/*" \
--query 'EvaluationResults[0].EvalDecision' \
--output text)
if [ "$RESULT" != "allowed" ]; then
echo "FAIL: Role cannot read from app-config-bucket (got: $RESULT)"
FAILED=1
else
echo "PASS: Role can read from app-config-bucket"
fi
# Test 2: Role SHOULD NOT be able to delete from the config bucket
RESULT=$(aws iam simulate-principal-policy \
--policy-source-arn "$ROLE_ARN" \
--action-names s3:DeleteObject \
--resource-arns "arn:aws:s3:::app-config-bucket/*" \
--query 'EvaluationResults[0].EvalDecision' \
--output text)
if [ "$RESULT" == "allowed" ]; then
echo "FAIL: Role can delete from app-config-bucket (should be denied)"
FAILED=1
else
echo "PASS: Role cannot delete from app-config-bucket"
fi
# Test 3: Role SHOULD NOT be able to create IAM users
RESULT=$(aws iam simulate-principal-policy \
--policy-source-arn "$ROLE_ARN" \
--action-names iam:CreateUser iam:AttachUserPolicy \
--resource-arns "*" \
--query 'EvaluationResults[?EvalDecision==`allowed`].EvalActionName' \
--output text)
if [ -n "$RESULT" ]; then
echo "FAIL: Role has IAM write permissions: $RESULT"
FAILED=1
else
echo "PASS: Role has no IAM write permissions"
fi
# Test 4: Role SHOULD be able to write to DynamoDB application tables
RESULT=$(aws iam simulate-principal-policy \
--policy-source-arn "$ROLE_ARN" \
--action-names dynamodb:PutItem dynamodb:GetItem dynamodb:Query \
--resource-arns "arn:aws:dynamodb:us-east-1:123456789012:table/AppData" \
--query 'EvaluationResults[?EvalDecision!=`allowed`].EvalActionName' \
--output text)
if [ -n "$RESULT" ]; then
echo "FAIL: Role cannot perform DynamoDB operations: $RESULT"
FAILED=1
else
echo "PASS: Role has required DynamoDB permissions"
fi
echo ""
if [ $FAILED -eq 1 ]; then
echo "IAM policy tests FAILED. Fix policies before deploying."
exit 1
else
echo "All IAM policy tests passed."
fi
Add this script as a step in your CI/CD pipeline that runs after infrastructure synthesis but before deployment. This catches permission regressions before they affect production. Define both positive tests (actions that should be allowed) and negative tests (actions that must be denied) to ensure least privilege is maintained.
Simulator Limitations
The Policy Simulator has some limitations to be aware of. It does not evaluate resource-based policies (like S3 bucket policies or KMS key policies), permission boundaries in certain complex scenarios, or session policies applied during AssumeRole. It also cannot evaluate policies across account boundaries. For comprehensive policy analysis, combine the simulator with IAM Access Analyzer and manual review of resource-based policies.
Securing IAM Policies with AccessLens
The Policy Simulator is excellent for targeted testing of known scenarios, but it requires you to define the test cases yourself. You have to know which actions to test and which resources to check. The permissions you forget to test are the ones that become security vulnerabilities.
AccessLens complements the Policy Simulator by providing:
- Comprehensive permission analysis that evaluates every action a principal can perform, not just the ones you think to test
- Cross-account trust relationship mapping that reveals the full chain of access, including the trust policies the simulator cannot evaluate
- Risk scoring that prioritizes overpermissive policies by exploitability so you know which ones to fix first
- Continuous monitoring that detects when policy changes introduce new permissions, even if your CI/CD tests do not cover them
- Effective permission visualization that shows the combined effect of managed policies, inline policies, and permission boundaries in a single view
The Policy Simulator tells you if a specific action is allowed. AccessLens tells you everything that is allowed and whether it should be.
Get complete IAM policy visibility with AccessLens and move beyond manual policy testing to continuous, automated permission analysis.