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:

  1. Delegated administration: A manager controls a team but can only grant permissions up to a boundary level
  2. Compliance requirements: Organizations enforce that certain services/actions are never accessible
  3. 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.