The Permission That Is Not in the Log: How Far an Agent Reaches Through iam:PassRole
A role that is not an administrator can become one, and no rule has to be broken on the way. This post is the prose version of our ninety-second film on how, which is also on YouTube. The subject is AWS’s iam:PassRole; Google Cloud and Azure have the same shape under other names, covered further down.
The small role
Give an agent a role that may do three things, each on "Resource": "*":
lambda:CreateFunction, so it can deploy code;lambda:InvokeFunction, so it can run that code;iam:PassRole, because a function needs a role to run as, and whoever creates it has to hand it one.
Read line by line, nothing here is alarming: no iam:*, no administrator policy. The line that matters is the third, because of what * means there: every role in the account.
The move
The agent creates a function, hands it the strongest role it can, and invokes it. The function runs as that role, and the agent wrote what the function does. Its reach is no longer its own role. It is the role it passed.
There is a real precondition. In AWS’s own example, one of the three things you need is “A trust policy for the role that allows the service to assume the role” (AWS IAM). So the agent cannot hand Lambda just any role, only one that trusts Lambda. Its reach is every role in the account that trusts a service the agent can create things in. Whether one of those is powerful is a question about your account, and AWS’s own guidance names the risk: “When setting the PassRole permission, you should make sure that a user doesn’t pass a role where the role has more permissions than you want the user to have.”
Nothing is bypassed. Every call is allowed. That is the point: the policy grants all of this, it just does not say so in words.
The search
Afterwards someone searches CloudTrail for PassRole and finds nothing. AWS says why: “PassRole is not an API call. PassRole is a permission, meaning no CloudTrail logs are generated for IAM PassRole.”
The evidence is elsewhere, and the same page says where: “To review what roles are passed to which AWS services in CloudTrail, you must review the CloudTrail log that created or modified the AWS resource receiving the role.” Here, “The log for the CreateFunction action shows a record of role that was passed to the function.” The pass is recorded, but under the name of what it was used for. Search for the permission and you find nothing; read the calls that created or changed resources and you find the role.
The fix
Two changes, both from the same AWS page. First, name the roles: “To limit the user to passing only approved roles, you can filter the iam:PassRole permission with the Resources element of the IAM policy statement.” Second, name the service: “The iam:PassedToService condition key can be used to specify the service principal of the service to which a role can be passed.”
{
"Effect": "Allow",
"Action": "iam:PassRole",
"Resource": "arn:aws:iam::<ACCOUNT_ID>:role/example-agent-function-role",
"Condition": {"StringEquals": {"iam:PassedToService": "lambda.amazonaws.com"}}
}
Do not reach for role tags instead. AWS is blunt: “Do not try to control who can pass a role by tagging the role and then using the ResourceTag condition key in a policy with the iam:PassRole action. This approach does not have reliable results.”
The same shape elsewhere
On Google Cloud, attaching a service account to a resource requires the iam.serviceAccounts.actAs permission, which the Service Account User role includes, and code on that resource then runs as the service account (Google Cloud). Google’s caution reads like AWS’s: “In scenarios where a service account has been granted permissions to perform highly-privileged operations, be cautious when granting the Service Account User role or its included permissions to a user on that service account.”
On Azure, the Managed Identity Operator role is described as “Read and Assign User Assigned Identity” and grants Microsoft.ManagedIdentity/userAssignedIdentities/*/assign/action (Microsoft Learn). Different names, one question: which identities can this principal attach to something it runs?
What it does not cover
Naming a role does not make that role small. If the one role the agent may pass can delete every bucket, the agent can still delete every bucket; scope the passed role as carefully as the agent’s. A new policy also changes only future passes. Roles already attached to running functions keep what they have until someone changes those functions. And least privilege bounds how far an agent can reach. It cannot read what the agent meant to do.
Five minutes
The free Agent Readiness Check now reports an agent role whose inline policy allows iam:PassRole on every role with no iam:PassedToService condition. It reads inline policies only, not attached ones, and it prints that limit in the finding itself. For a rough look at everything, two read-only commands from the film:
aws iam get-account-authorization-details --filter Role > roles.json
grep -c '"iam:PassRole"' roles.json
The number is a count of lines that mention the permission, not a verdict. Read each one for two things: which roles, and to which service.
