Using IAM Policy Simulation to Verify Least-Privilege Permissions in AWS
Learn how to use IAM policy simulation to verify least‑privilege permissions before granting them, including requirements, a minimal design, trust boundaries, operational checks, failure modes, and a concrete example.
19 Nov 2025, 17:31 UTC

Requirements
Before granting a role or user broad permissions, you need confidence that the policy allows only the actions required for the workload. The verification must be repeatable, suitable for inclusion in a CI/CD pipeline, and should not modify any live resources.
Smallest Suitable Design
The minimal approach is to invoke the IAM policy simulation API (aws iam simulate-principal-policy) with:
- Policy source – the ARN of the managed or inline policy you want to test (or a JSON policy string supplied via
--policy-input-file). - Principal ARN – the role or user whose effective permissions you are evaluating.
- Action list – the specific API actions you intend to allow (e.g.,
s3:GetObject,ec2:DescribeInstances). - Resource ARNs – the target resources (e.g., a specific S3 bucket or object).
The simulation returns a decision (allowed or denied) for each action/resource pair, together with the statements that influenced the result.
Trust/Data Boundaries
The simulation runs entirely within your AWS account and is read‑only; it does not create, modify, or delete any resources. It evaluates policies against a hypothetical request, so no data leaves the trust boundary of the simulation call itself. However, the simulation does not consider:
- Service control policies (SCPs) from AWS Organizations.
- Permission boundaries attached to the principal.
- Resource‑based policies (e.g., S3 bucket policies) unless you explicitly include them as additional policy inputs.
- Temporary credentials or session policies derived from STS assume‑role calls.
These omissions define the trust boundary of the simulation: it validates the identity‑based policy in isolation.
Operational Checks
- Add a simulation step to your pipeline that runs after a policy change but before the role is attached to any compute resource.
- Parse the JSON output and fail the build if any
Decisionfield isAllowedfor an action that is not in your approved list. - Review the
MatchedStatementsarray to confirm that the allow comes from the expected statement (e.g., a statement with aStringEqualscondition on a specific tag). - Record the simulation output as an artifact for audit purposes.
Failure Modes
- False sense of security – if you rely solely on simulation and forget SCPs, the effective permission may be more restrictive than simulated, or less restrictive if a resource‑based policy grants additional access.
- Missing actions – some service‑specific actions are not fully modeled in the simulation engine; they may return
Unknownor be omitted. - Stale policy version – if you simulate an older version of a managed policy, the result will not reflect the latest attached version.
Conditions That Would Change the Design
- When you need to validate cross‑account access, you must include the external account’s role as a principal and possibly test the resource policy of the target account.
- If permission boundaries are enforced, add the boundary policy as an additional input to the simulation.
- When you want to test conditional logic that depends on session tags, supply those tags via the
--context-entriesparameter. - For services where the simulation coverage is known to be spotty (e.g., certain Lambda API actions), complement simulation with a dry‑run using temporary credentials in a sandbox account.
Example Simulation
Suppose you have a role arn:aws:iam::123456789012:role/MyAppRole and you want to verify that it can only read objects with the tag Project=Alpha from bucket my‑app‑data.
aws iam simulate-principal-policy \
--policy-source-arn arn:aws:iam::123456789012:policy/MyAppS3ReadPolicy \
--principal-arn arn:aws:iam::123456789012:role/MyAppRole \
--action-names s3:GetObject s3:ListBucket \
--resource-arns arn:aws:s3:::my-app-data/* arn:aws:s3:::my-app-data \
--context-entries ContextKeyName='s3:ExistingObjectTag/Project',ContextKeyValues='Alpha',ContextKeyType='StringEquals'
The output (truncated for clarity) might contain entries such as:
{
"EvaluationResults": [
{
"EvalActionName": "s3:GetObject",
"EvalResourceName": "arn:aws:s3:::my-app-data/project/file.txt",
"EvalDecision": "allowed",
"MatchedStatements": ["MyAppS3ReadPolicy-AllowGetObjectWithTag"]
},
{
"EvalActionName": "s3:ListBucket",
"EvalResourceName": "arn:aws:s3:::my-app-data",
"EvalDecision": "denied",
"MatchedStatements": []
}
]
}
Interpretation:
- The
GetObjectaction is allowed only when the object carries theProject=Alphatag, as indicated by the matched statement. - The
ListBucketaction is denied because the policy does not grant permission to list the bucket (which matches the least‑privilege goal).
Limitations and Practical Verification
To increase confidence, follow the simulation with a limited live test in a sandbox account:
- Assume the role using short‑term credentials:
aws sts assume-role --role-arn arn:aws:iam::123456789012:role/MyAppRole --role-session-name test-sim. - Attempt the allowed action (e.g.,
aws s3api get-object --bucket my-app-data --key project/file.txt ./file.txt) and verify success. - Attempt a disallowed action (e.g.,
aws s3api list-objects --bucket my-app-data) and expect anAccessDeniederror. - Clean up the temporary credentials after the test.
If the live test matches the simulation decision, you have validated that the identity‑based policy behaves as expected within the boundaries you tested.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.