Asterrr's Handbook

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 boundarySCPSession policy
ScopeOne user or roleEvery principal in the member accounts belowOne session
Set byWhoever creates the entity (you can require it)Org management or delegated adminWhoever calls STS
PersistsUntil removedUntil detachedUntil the session expires
Classic useSafe delegation of IAMOrg guardrailsNarrow 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 to arn: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:PassRole on * 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 add iam:PassedToService so the role can only go to the service you intend.
  • iam:AssociatedResourceArn narrows 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 PassRole event in CloudTrail. You see it inside the service call that used it, such as RunInstances or CreateFunction.
{
  "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 roleService-linked role
Who defines permissionsYouThe service
Can you edit itYesOnly the description
Needs PassRoleYes, when you attach itNo
Limited by SCP/RCPYesNo

Scenarios

Scenario · choose 2
Platform engineers at a logistics company let application teams create IAM roles for their ECS tasks. An audit finds that one team created a role with AdministratorAccess and used it from a task. The company wants teams to keep creating their own roles. Which combination prevents this? (Choose TWO.)
Scenario
A developer has ec2:RunInstances and iam:PassRole on arn:aws:iam::*:role/*. A security review flags this as a privilege-escalation risk. What is the most precise fix?
Scenario
A company attaches an SCP denying all actions outside two approved Regions. A few weeks later, CloudTrail shows actions in a third Region performed by a role named AWSServiceRoleForConfig. Why wasn't it blocked?

Further reading

On this page