3.5 Permission Boundaries: Capping Maximum Effective Permissions
Right, so you’ve finally decided to build a safety net that isn’t made of wishful thinking and prayer. Good. You’ve learned about IAM policies and roles, but you’ve also heard the horror stories: a runaway Lambda function with AdministratorAccess, a dev role that accidentally nuked a production database. Permission Boundaries are how you tell an IAM entity (a user or role), “You can have all the permissions you want, but you will never have more than this.” It’s the absolute ceiling for their power, and it’s arguably one of the most important safety tools in your AWS kit.
Think of it this way: an identity-based policy attached to a role is like the gas pedal—it grants permissions. A permission boundary is the governor on the engine—it doesn’t add power, it just says “you shall not go past 110 mph” no matter how hard you press the pedal. The actual effective permissions for a request are the intersection of the identity-based policies and the permission boundary. No boundary permission? No go. It’s a logical AND operation.
Why You Absolutely Need This
Because trust is not a security model. Let’s say you grant a developer an IAM role that can pass roles to EC2 instances. Their job is to deploy applications. But if that role they can pass is AdministratorAccess, you’ve just handed them the keys to the kingdom on a silver platter. They could launch an instance, pass the admin role to it, SSH in, and do whatever they want. A permission boundary on the developer’s role that explicitly denies iam:PassRole on any role without a specific boundary, or denies iam:PassRole on any policy more powerful than a specific one, stops this dead in its tracks. It caps their maximum privilege, making privilege escalation vastly more difficult.
Crafting and Applying a Boundary
A permission boundary is just a managed policy. But it’s a special one. You don’t attach it as a permission grant; you attach it as the boundary. You can use the AWS-managed AdministratorAccess policy as a boundary, but that’s… well, pointless. The power is in creating a tight, custom one.
Here’s a realistic example. Let’s create a boundary that allows full access to S3 and DynamoDB, but nothing else. This is perfect for a data processing role.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:*",
"dynamodb:*"
],
"Resource": "*"
}
]
}
Now, let’s create a role and attach this policy as its boundary. Notice we use the --permissions-boundary parameter. This is crucial.
# Create the permission boundary policy (do this first)
aws iam create-policy --policy-name DataProcessorBoundary \
--policy-document file://data_processor_boundary.json
# Create the role itself, immediately capping its power
aws iam create-role --role-name DataProcessorRole \
--assume-role-policy-document file://trust_policy.json \
--permissions-boundary arn:aws:iam::123456789012:policy/DataProcessorBoundary
Now, even if you attach AdministratorAccess to the DataProcessorRole, it will still only be able to perform S3 and DynamoDB actions. The admin policy says “yes you can,” but the boundary says “nope, not on my watch.” The boundary wins.
The Critical Nuances and “Gotchas”
This is where the rubber meets the road, and where most people get tripped up.
The Boundary MUST Be Explicitly Allowed: The boundary uses a whitelist model. If an action isn’t explicitly allowed in the boundary, it is implicitly denied, even if it’s explicitly allowed in the identity policy. Your boundary isn’t a blacklist; it’s a very strict whitelist. This is the most common mistake. You must include all the actions you ever want the role to perform in the boundary’s
Allowstatement.It Affects the IAM API Itself: This is a big one. A role cannot alter or remove its own permission boundary. Why? Because that would be a privilege escalation path. Think about it: if a role could remove its own governor, the governor is useless. The
iam:DeleteRolePermissionsBoundarypermission must be granted in the boundary itself for the role to be able to do it. This is brilliantly designed and a key reason boundaries are so robust.Not All Services Support Them (Yet): While most major services do, there are edge cases. For example, as of my last trench dive, AWS CloudFormation StackSets’ service-managed permissions don’t fully honor boundaries in certain scenarios. Always test your specific use case. The official docs have the updated list, but assume it works for EC2, Lambda, S3, IAM, etc.
The Confusing Dance with SCPs: This is a classic “who wins?” scenario. Remember the order of evaluation:
- SCP: Is the account itself allowed to do this? (Explicit Deny -> Deny)
- Permission Boundary: Is the action within the role’s capped limit? (Not Allowed -> Deny)
- Identity-based Policies: Does the role’s policy grant the action? (Explicit Allow -> Allow)
An SCP can shut down everything. A boundary can shut down an identity policy. But an identity policy can never override a boundary or an SCP. SCPs are the organization’s guardrails, boundaries are the role’s guardrails.
Best Practice: The Boundary-of-Last-Resort Pattern
One incredibly powerful pattern is to create a strict, organization-wide permission boundary that denies certain “nuclear” actions—like leaving your AWS organization, turning off CloudTrail, or deleting critical resources. You then attach this boundary to every new IAM user and role by default, perhaps via an SCP that mandates its use. This ensures that no matter what permissions a role is granted, it can never perform those catastrophic actions. It’s the ultimate safety catch. You’re not just building guardrails for your roles; you’re building a foundation that makes it impossible for anyone to accidentally (or intentionally) drive the entire company off a cliff. And that, my friend, is how you sleep soundly at night.