If you’ve granted an IAM role an explicit Allow for an action, verified the policy is attached, and then the actual API call still fails, you’ve likely hit a Permission Boundary issue. I’ve debugged dozens of these cases, and they’re deceptively hard to diagnose because the error message never mentions the permission boundary. In this post, I’ll walk through exactly what permission boundaries do and how to identify when one is silently blocking your access.
The Problem
You have an IAM role with this policy:
{
"Effect": "Allow",
"Action": "ec2:*",
"Resource": "*"
}
CloudTrail logs show the API call was denied. The policy is clearly attached to the role. Yet the call fails. The culprit: a Permission Boundary.
| Error Type | Error Message |
|---|---|
| AccessDenied | User: arn:aws:iam::123456789012:role/MyRole is not authorized to perform: ec2:TerminateInstances on resource: arn:aws:instance/* |
A permission boundary acts as an invisible ceiling that you don’t see in your policy list.
Why Does This Happen?
- Permission boundary is too restrictive: The boundary policy doesn’t include the action you’re trying to perform, so even though your identity policy allows it, the boundary blocks it
- Permission boundary inherited from automation: Landing Zone, Control Tower, or other provisioning tools attach permission boundaries automatically, often without notifying developers
- Developer doesn’t know boundary exists: The boundary was applied long ago and is forgotten about, leading to hours of debugging
- Boundary is an AND gate, not an OR: The effective permission = (identity policy) AND (permission boundary). Both must allow the action
- Boundary doesn’t show up in the IAM console policy summary: Policies tab shows identity policies, but Permission Boundary is in a separate section
The Fix
Step 1: Check if a Permission Boundary is Attached
Get the role details and look for the PermissionsBoundary field:
aws iam get-role \
--role-name MyRole \
--query 'Role.[AssumeRolePolicyDocument, PermissionsBoundary]' \
--output text
If PermissionsBoundary returns a value (an ARN), the role has a boundary. It might look like:
arn:aws:iam::123456789012:policy/BoundaryPolicy
or
arn:aws:iam::aws:policy/PowerUserAccess
If it returns None or empty, no boundary is attached, so that’s not your problem.
Step 2: Get the Boundary Policy Content
If a boundary is attached, retrieve its policy document:
# For a customer-managed boundary
aws iam get-policy-version \
--policy-arn arn:aws:iam::123456789012:policy/BoundaryPolicy \
--version-id v1 \
--query 'PolicyVersion.Document' \
--output text | jq .
# For an AWS managed boundary
aws iam get-policy-version \
--policy-arn arn:aws:iam::aws:policy/PowerUserAccess \
--version-id v1 \
--query 'PolicyVersion.Document' \
--output text | jq .
Look at what actions and resources the boundary allows. The boundary policy might look like:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"ec2:*",
"s3:*",
"rds:*"
],
"Resource": "*"
},
{
"Effect": "Deny",
"Action": [
"iam:*",
"organizations:*"
],
"Resource": "*"
}
]
}
This boundary allows EC2, S3, and RDS actions but explicitly denies IAM and Organizations actions.
Step 3: Understand the Effective Permission
The key principle: Effective Permission = (Identity Policy) AND (Permission Boundary)
Both must allow the action. Here’s an example:
| Action | Identity Policy | Boundary Policy | Effective |
|---|---|---|---|
| ec2:TerminateInstances | Allow | Allow | Allow |
| ec2:TerminateInstances | Allow | Deny | Deny |
| ec2:TerminateInstances | Deny | Allow | Deny |
| iam:AttachUserPolicy | Deny | Allow | Deny |
If your identity policy allows ec2:* but the boundary doesn’t include ec2:TerminateInstances, the call fails.
Step 4: Fix the Boundary
You have two options: update the boundary or remove it.
Option 1: Update the boundary to include the needed action
# Get the current boundary policy
aws iam get-policy-version \
--policy-arn arn:aws:iam::123456789012:policy/BoundaryPolicy \
--version-id v1 \
--query 'PolicyVersion.Document' \
--output text > boundary.json
# Edit boundary.json to add the needed action (e.g., ec2:TerminateInstances)
# Create a new version with the updated policy
aws iam create-policy-version \
--policy-arn arn:aws:iam::123456789012:policy/BoundaryPolicy \
--policy-document file://boundary.json \
--set-as-default
Option 2: Remove the boundary entirely (if it’s no longer needed)
aws iam delete-role-permissions-boundary \
--role-name MyRole
Step 5: Verify the Fix
Test that the action now works:
# Use AWS CLI to test the action
aws ec2 describe-instances \
--output text
If this works, the permission boundary issue is resolved. If it still fails, check your identity policy — that’s likely the next culprit.
Understanding Permission Boundaries in Context
Permission boundaries are typically applied in three scenarios:
- Delegated administration: A manager controls a team but can only grant permissions up to a boundary level
- Compliance requirements: Organizations enforce that certain services/actions are never accessible
- Landing Zone/Control Tower: Automated provisioning tools apply boundaries to enforce organizational standards
The problem is scenario #3 — developers often don’t realize a boundary was applied automatically.
Is This Safe?
Checking for permission boundaries is completely safe — it’s a read-only operation. Modifying or removing boundaries should follow your organization’s change management process, since they’re often applied to enforce compliance standards.
Key Takeaway
Permission boundaries are the silent permission killers — they block access without any indication in error messages or policy summaries. Always run get-role --query 'Role.PermissionsBoundary' when you hit an unexplained access denied error. The effective permission is the intersection of identity policy AND permission boundary — both must allow the action. In my experience, permission boundaries cause 10-15% of mysterious access denied errors, and they’re almost never documented to the teams affected by them.
Have questions or ran into a different IAM issue? Connect with me on LinkedIn or X.