Permissions boundaries and delegation
Using permissions boundaries to let teams create their own roles without escalating privilege, controlling iam:PassRole, and how service-linked roles fit in.
Exam tasks: 4.2 (Skills 4.2.1 authorization controls for human, application and system access, 4.2.3 least-privilege policies with permission boundaries)
The decision: how do you let a team create and attach IAM roles for their own workloads without letting them create a role more powerful than themselves?
What a boundary is
- A managed policy set as the permissions boundary of a user or role. One boundary per entity.
- Effective permissions are the intersection of the identity policies and the boundary. The boundary never grants anything by itself.
- It doesn't limit resource-based policies that name the user or role session directly (same account), and it doesn't apply to groups.
- An explicit deny in the boundary works like any other explicit deny.
| Permissions boundary | SCP | Session policy | |
|---|---|---|---|
| Scope | One user or role | Every principal in the member accounts below | One session |
| Set by | Whoever creates the entity (you can require it) | Org management or delegated admin | Whoever calls STS |
| Persists | Until removed | Until detached | Until the session expires |
| Classic use | Safe delegation of IAM | Org guardrails | Narrow a broad role for one job |
Delegating role creation safely
The pattern: developers in account 7788 may create roles for their Lambda functions, but every role they create
must carry the dev-workload-boundary policy, and they can't remove or weaken it.
Write the boundary (dev-workload-boundary): the most any workload role may ever do, for example the app's
data services in two Regions, and nothing in IAM, Organizations or the security tooling.
Require it on create. Allow iam:CreateRole and iam:PutRolePermissionsBoundary only when
iam:PermissionsBoundary equals the boundary's ARN.
Protect it. Deny iam:DeleteRolePermissionsBoundary, and deny iam:CreatePolicyVersion,
iam:SetDefaultPolicyVersion and iam:DeletePolicy on the boundary policy itself.
Limit the names. Restrict role and policy actions to a path or prefix such as role/app/*, so developers
can't edit the platform's own roles.
Apply the boundary to the developers too, so they can't simply attach AdministratorAccess to themselves.
{
"Sid": "CreateRolesOnlyWithBoundary",
"Effect": "Allow",
"Action": ["iam:CreateRole", "iam:PutRolePermissionsBoundary", "iam:AttachRolePolicy", "iam:PutRolePolicy"],
"Resource": "arn:aws:iam::778899001122:role/app/*",
"Condition": {
"StringEquals": {
"iam:PermissionsBoundary": "arn:aws:iam::778899001122:policy/dev-workload-boundary"
}
}
}Exam signal
"Allow developers to create IAM roles, but prevent privilege escalation" is the permissions boundary question.
The answer always includes the iam:PermissionsBoundary condition on role creation plus a deny on removing or
editing the boundary.
Boundary without protection
A boundary that developers can detach (DeleteRolePermissionsBoundary) or rewrite (CreatePolicyVersion on the
boundary policy) is decoration. Answers that set the boundary but don't lock those actions miss the point.
IAM paths
- Paths such as
/app/or/platform/security/are part of the ARN, so policies can scope actions toarn:aws:iam::*:role/app/*. - They're organisational only: a path grants nothing and can't be changed on an existing role. Use them with boundaries to separate what teams can touch.
iam:PassRole
A service such as EC2, Lambda, ECS or Glue acts as a role you hand it. Handing over the role needs
iam:PassRole on that role's ARN.
- Without a scope,
iam:PassRoleon*lets anyone who can launch an instance attach any role, including an admin one, then use it from the instance. It's a classic privilege-escalation path. - Scope the resource to a path (
role/app/*) and addiam:PassedToServiceso the role can only go to the service you intend. iam:AssociatedResourceArnnarrows which resource the role can be attached to, for services that support it.- PassRole is a permission, not an API call, so there's no
PassRoleevent in CloudTrail. You see it inside the service call that used it, such asRunInstancesorCreateFunction.
{
"Sid": "PassAppRolesToLambdaOnly",
"Effect": "Allow",
"Resource": "arn:aws:iam::778899001122:role/app/*",
"Action": "iam:PassRole",
"Condition": { "StringEquals": { "iam:PassedToService": "lambda.amazonaws.com" } }
}The role trust policy isn't enough
A role whose trust policy allows ec2.amazonaws.com still can't be attached by a user who lacks iam:PassRole on
it. And the reverse: PassRole doesn't help if the trust policy doesn't trust the service. Both must line up.
Service-linked roles
- Created and owned by an AWS service (
AWSServiceRoleFor...), with a trust policy and permissions you can't edit. Some are created automatically when you first use a feature. - SCPs and RCPs don't restrict them. That's by design, so services keep working, and it means you can't use an SCP to stop, say, a service writing to its own resources with its linked role.
- Creating one needs
iam:CreateServiceLinkedRole. You can deny that in an SCP to block a service being set up, which is an indirect control. - Deleting one usually requires cleaning up the service's resources first.
| Service role | Service-linked role | |
|---|---|---|
| Who defines permissions | You | The service |
| Can you edit it | Yes | Only the description |
| Needs PassRole | Yes, when you attach it | No |
| Limited by SCP/RCP | Yes | No |
Scenarios
Requiring the boundary at creation caps any new role, and locking the boundary stops teams removing or rewriting it. Denying CreateRole outright breaks the requirement that teams keep creating roles. MFA proves who the caller is, not what they may grant. Policy generation helps trim roles later but doesn't prevent an over-privileged one.
The risk is passing any role, including admin roles, to an instance. Limiting PassRole to the app path and to EC2 removes that. Instance profiles still need PassRole to attach, so removing it breaks launches. Boundaries on instance roles don't stop the developer passing a role that has no boundary, and the developer isn't calling AssumeRole at all, the EC2 service is.
Service-linked roles are exempt from SCPs, so services can keep doing the work you enabled. The management account detail is irrelevant because this role is in a member account, SCPs do apply to roles, and there's no such aws:SourceRegion key for this purpose. To stop it, disable the service or its multi-Region recording.
Further reading
Policy evaluation logic
How AWS combines identity, resource, boundary, session, SCP and RCP policies into one allow or deny, how cross-account requests differ, and the condition keys and operators the exam leans on.
Organization policies and data perimeters
SCPs, RCPs, declarative policies and AI services opt-out policies in AWS Organizations, what each can and can't restrict, and how they combine into identity, resource and network perimeters.