AWS

not authorized to perform: iam:PassRole

You can create the resource but not hand it the role. Why PassRole exists as a separate permission, and how to scope it without granting it on every role.

medium fix5 min read

the aws error
An error occurred (AccessDenied) when calling the RunInstances operation: User: arn:aws:iam::123456789012:user/dev is not authorized to perform: iam:PassRole on resource: arn:aws:iam::123456789012:role/app-instance-role

User: arn:aws:sts::123456789012:assumed-role/deploy/session is not authorized to perform: iam:PassRole on resource: arn:aws:iam::123456789012:role/ecsTaskExecutionRole because no identity-based policy allows the iam:PassRole action

Do this first3 steps

Run these in order. Each one tells you what its output means before you change anything.

  1. 1

    Read which role you are being refused permission to pass

    aws sts get-caller-identity

    The error names two ARNs: the identity making the call, and the role it wants to pass. PassRole must be granted on the second, in the identity policy of the first.

  2. 2

    Grant PassRole scoped to that specific role

    aws iam get-role --role-name app-instance-role --query 'Role.Arn' --output text

    Put that exact ARN in a Resource list rather than using a wildcard. PassRole on * lets the holder attach any role in the account to a resource they control, which is a full privilege escalation path.

  3. 3

    Confirm the target role trusts the service you are passing it to

    aws iam get-role --role-name app-instance-role --query 'Role.AssumeRolePolicyDocument'

    PassRole permits handing the role over; the role's own trust policy decides whether the service may assume it. A role that does not trust ec2.amazonaws.com cannot be used as an instance profile even with PassRole granted.

All 9 sections

iam:PassRole is not an action against IAM. It is a permission check AWS runs when you hand a role to a service: launching an EC2 instance with an instance profile, creating a Lambda with an execution role, registering an ECS task definition with a task role.

It exists because otherwise anyone who could create a resource could grant it any permissions in the account. Create a Lambda with an admin role attached, invoke it, and you are admin. PassRole is the check that stops that.

So the permission to create the resource and the permission to pass the role are deliberately separate.

Read the two ARNs

User: arn:aws:iam::123456789012:user/dev
is not authorized to perform: iam:PassRole
on resource: arn:aws:iam::123456789012:role/app-instance-role

The user needs iam:PassRole on the role. That grant goes in the user's own identity policy, not in the role.

aws sts get-caller-identity

Grant it, scoped

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "ec2:RunInstances",
      "Resource": "*"
    },
    {
      "Effect": "Allow",
      "Action": "iam:PassRole",
      "Resource": "arn:aws:iam::123456789012:role/app-instance-role"
    }
  ]
}

Never "Resource": "*" on PassRole. That is a full escalation path: the holder can attach any role in the account, including an administrator role, to a resource they control and then use it.

For several roles, list them, or use a path or naming convention:

"Resource": "arn:aws:iam::123456789012:role/app/*"

Creating your roles under a path such as /app/ makes this pattern clean and auditable.

Constrain which service it can be passed to

iam:PassedToService limits the grant to one service:

{
  "Effect": "Allow",
  "Action": "iam:PassRole",
  "Resource": "arn:aws:iam::123456789012:role/ecsTaskExecutionRole",
  "Condition": {
    "StringEquals": { "iam:PassedToService": "ecs-tasks.amazonaws.com" }
  }
}

Now the role can be passed to ECS tasks and not to EC2 or Lambda. This is worth adding: it means a role intended for one service cannot be repurposed onto another where its permissions might combine differently.

Service principals you will use most:

Serviceiam:PassedToService
EC2ec2.amazonaws.com
Lambdalambda.amazonaws.com
ECS tasksecs-tasks.amazonaws.com
ECS service schedulerecs.amazonaws.com
CodeBuildcodebuild.amazonaws.com
EKSeks.amazonaws.com

PassRole is not enough on its own

Two separate checks have to pass:

  1. You need iam:PassRole on the role.
  2. The role must trust the service, in its own trust policy.
aws iam get-role --role-name app-instance-role \
  --query 'Role.AssumeRolePolicyDocument'
{
  "Statement": [{
    "Effect": "Allow",
    "Principal": { "Service": "ec2.amazonaws.com" },
    "Action": "sts:AssumeRole"
  }]
}

A role that does not trust ec2.amazonaws.com cannot be an instance profile even when PassRole is granted, and the error you get is different and less clear.

EC2 also needs the instance profile

For EC2 specifically there is a third thing. An instance profile is a container around the role, and older tooling requires both permissions:

{
  "Effect": "Allow",
  "Action": ["iam:PassRole", "ec2:AssociateIamInstanceProfile", "ec2:ReplaceIamInstanceProfileAssociation"],
  "Resource": "*"
}
aws iam list-instance-profiles-for-role --role-name app-instance-role

A role created in the console gets an instance profile of the same name automatically. One created with the CLI or Terraform does not; you create aws_iam_instance_profile separately, and forgetting it produces a confusing failure at launch.

ECS needs it twice

An ECS task definition can reference two roles, and both are passed:

{
  "Effect": "Allow",
  "Action": "iam:PassRole",
  "Resource": [
    "arn:aws:iam::123456789012:role/ecsTaskExecutionRole",
    "arn:aws:iam::123456789012:role/app-task-role"
  ],
  "Condition": {
    "StringEquals": { "iam:PassedToService": "ecs-tasks.amazonaws.com" }
  }
}

Granting PassRole on only the execution role is a common half-fix: RegisterTaskDefinition then fails on the task role instead, with an almost identical message.

In Terraform

The identity running terraform apply needs PassRole for every role any resource attaches:

resource "aws_instance" "app" {
  iam_instance_profile = aws_iam_instance_profile.app.name   # needs PassRole
}

resource "aws_lambda_function" "api" {
  role = aws_iam_role.lambda.arn                              # needs PassRole
}

A pipeline role that can create everything but cannot pass roles fails at exactly these lines, which is why deployment roles usually need a PassRole statement scoped to a path covering the roles the stack creates.

A checklist

  1. Read both ARNs: who is calling, and which role they want to pass.
  2. Grant iam:PassRole in the caller's policy, on the role's ARN.
  3. Never Resource: "*". It is a privilege escalation path.
  4. Add an iam:PassedToService condition for the intended service.
  5. Check the role's trust policy allows that service principal.
  6. EC2 → confirm an instance profile exists for the role.
  7. ECS → grant PassRole on both the task role and the execution role.
  8. Terraform → the pipeline role needs PassRole for every role the stack attaches.

Frequently Asked Questions

Why is PassRole a separate permission from creating the resource?

Because handing a role to a service grants that service the role's permissions, so without a separate check anyone who could create a Lambda or an EC2 instance could grant themselves whatever that role holds. Attaching an administrator role to a function you control and invoking it is a complete escalation. Splitting the two means the ability to create resources does not imply the ability to choose what permissions those resources run with.

Why should I never use Resource: "*" on PassRole?

Because it lets the holder attach any role in the account to a resource they control. Combined with permission to create a Lambda function or launch an EC2 instance, that is equivalent to granting every permission in the account, via a route that does not look like an admin grant in a policy review. Always list specific role ARNs, or use a path prefix such as arn:aws:iam::123456789012:role/app/* so the grant is bounded and auditable.

What does the iam:PassedToService condition do?

It restricts which AWS service the role may be passed to. A grant conditioned on ecs-tasks.amazonaws.com allows the role to be attached to an ECS task and not to an EC2 instance or a Lambda function. This matters because a role's permissions can be far more dangerous in one execution context than another, and it prevents a role intended for one narrow purpose being repurposed somewhere its permissions combine differently.

I granted PassRole and it still fails. What else is required?

Two other things. The role's own trust policy must allow the service principal to assume it, so a role without ec2.amazonaws.com in its trust policy cannot be an instance profile no matter what you grant. And for EC2, an instance profile object must exist wrapping the role: the console creates one automatically, while the CLI and Terraform do not, so you must create aws_iam_instance_profile explicitly.

Why does my ECS task definition still fail after I added PassRole?

Probably because a task definition references two roles and you granted PassRole on only one. The execution role is used by the ECS agent to pull images and read secrets, and the task role is assumed by your application. RegisterTaskDefinition checks PassRole for both, so granting it on the execution role alone produces a near-identical error naming the task role instead. List both ARNs in the Resource array.

Reference and practice

Learn the underlying concept

Other AWS errors