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:
| Service | iam:PassedToService |
|---|---|
| EC2 | ec2.amazonaws.com |
| Lambda | lambda.amazonaws.com |
| ECS tasks | ecs-tasks.amazonaws.com |
| ECS service scheduler | ecs.amazonaws.com |
| CodeBuild | codebuild.amazonaws.com |
| EKS | eks.amazonaws.com |
PassRole is not enough on its own
Two separate checks have to pass:
- You need
iam:PassRoleon the role. - 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
- Read both ARNs: who is calling, and which role they want to pass.
- Grant
iam:PassRolein the caller's policy, on the role's ARN. - Never
Resource: "*". It is a privilege escalation path. - Add an
iam:PassedToServicecondition for the intended service. - Check the role's trust policy allows that service principal.
- EC2 → confirm an instance profile exists for the role.
- ECS → grant PassRole on both the task role and the execution role.
- 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.