How to Fix AWS AccessDeniedException: iam:CreateRole and iam:PassRole Permission Errors
Threat/Impact Level: HIGH | Exploitability/Downtime Risk: HIGH | Time to Fix: 5–15 mins
TL;DR
- What broke: The IAM principal (user, role, or instance profile) executing your deployment lacks
iam:CreateRoleon the target role ARN and/oriam:PassRoleto hand that role off to an AWS service. - How to fix it: Attach an inline or managed policy granting
iam:CreateRolescoped to the specific role ARN pattern, andiam:PassRolewith aiam:PassedToServicecondition locking it to the intended service. - Fast path: Use our Client-Side Sandbox above to auto-refactor this — paste your failing policy or Terraform
aws_iam_roleblock and get a corrected, least-privilege policy output instantly.
The Incident (What Does the Error Mean?)
You hit this wall:
An error occurred (AccessDeniedException) when calling the CreateRole operation:
User: arn:aws:iam::123456789012:user/ci-deployer is not authorized to perform:
iam:CreateRole on resource: arn:aws:iam::123456789012:role/my-app-execution-role
with an explicit deny / no identity-based policy allows this action.
Sometimes the error surfaces as a missing iam:PassRole when the calling principal tries to attach the newly created role to a Lambda, ECS task, EC2 instance profile, or CodePipeline action:
User: arn:aws:iam::123456789012:role/deployer-role is not authorized to perform:
iam:PassRole on resource: arn:aws:iam::123456789012:role/my-app-execution-role
Immediate consequence: Your Terraform apply, CloudFormation stack create/update, CDK deploy, or SDK call halts completely. Any downstream resource depending on that role (Lambda function, ECS task definition, CodeBuild project) also fails to provision. CI/CD pipeline is dead.
The Attack Vector / Blast Radius
iam:PassRole is not a bureaucratic checkbox — it is a privilege escalation gate. Here is why AWS enforces it hard:
If a principal can call iam:CreateRole but not iam:PassRole, they can create a role with an AdministratorAccess managed policy attached, but they cannot hand it to any service. The PassRole check is the enforcement point that prevents a low-privilege CI user from creating a backdoor admin role and then attaching it to a Lambda or EC2 instance they control.
The exploit chain without PassRole enforcement:
- Attacker compromises a CI/CD service account with broad
iam:CreateRolebut no PassRole restriction. - Attacker creates
arn:aws:iam::account:role/backdoorwithAdministratorAccess. - Attacker calls
lambda:UpdateFunctionConfigurationto swap the execution role on an existing Lambda to the backdoor role. - Attacker invokes the Lambda — now executing as admin.
Conversely, if your legitimate deployer is blocked, every environment promotion, hotfix deploy, and infrastructure rollout stalls. In a microservices org with 40 services, one missing permission on the deploy role cascades into 40 broken pipelines simultaneously.
How to Fix It (The Solution)
Basic Fix
The minimum viable policy addition for the calling principal:
- // No iam:CreateRole or iam:PassRole granted — deployer policy before fix
- {
- "Version": "2012-10-17",
- "Statement": [
- {
- "Effect": "Allow",
- "Action": [
- "lambda:*",
- "ecs:*"
- ],
- "Resource": "*"
- }
- ]
- }
+ // Least-privilege fix: scoped CreateRole + PassRole with service condition
+ {
+ "Version": "2012-10-17",
+ "Statement": [
+ {
+ "Sid": "AllowScopedRoleCreation",
+ "Effect": "Allow",
+ "Action": "iam:CreateRole",
+ "Resource": "arn:aws:iam::123456789012:role/my-app-*"
+ },
+ {
+ "Sid": "AllowPassRoleToServicesOnly",
+ "Effect": "Allow",
+ "Action": "iam:PassRole",
+ "Resource": "arn:aws:iam::123456789012:role/my-app-*",
+ "Condition": {
+ "StringEquals": {
+ "iam:PassedToService": [
+ "lambda.amazonaws.com",
+ "ecs-tasks.amazonaws.com"
+ ]
+ }
+ }
+ }
+ ]
+ }
Critical: Scope Resource to a role name prefix or path (/app-roles/*), never "Resource": "*" on PassRole — that is the privilege escalation hole.
Enterprise Best Practice (Permission Boundary + Path Scoping)
In multi-team AWS orgs, pair iam:CreateRole with a mandatory Permission Boundary condition. This prevents the deployer from creating roles that exceed its own privilege ceiling, even if it has iam:CreateRole.
- {
- "Sid": "AllowScopedRoleCreation",
- "Effect": "Allow",
- "Action": "iam:CreateRole",
- "Resource": "arn:aws:iam::123456789012:role/my-app-*"
- }
+ {
+ "Sid": "AllowRoleCreationWithBoundaryEnforced",
+ "Effect": "Allow",
+ "Action": [
+ "iam:CreateRole",
+ "iam:AttachRolePolicy",
+ "iam:PutRolePolicy"
+ ],
+ "Resource": "arn:aws:iam::123456789012:role/app-roles/*",
+ "Condition": {
+ "StringEquals": {
+ "iam:PermissionsBoundary": "arn:aws:iam::123456789012:policy/AppRolePermissionBoundary"
+ }
+ }
+ },
+ {
+ "Sid": "AllowPassRoleScopedWithBoundary",
+ "Effect": "Allow",
+ "Action": "iam:PassRole",
+ "Resource": "arn:aws:iam::123456789012:role/app-roles/*",
+ "Condition": {
+ "StringEquals": {
+ "iam:PassedToService": "lambda.amazonaws.com"
+ }
+ }
+ }
Also enforce IAM role path isolation (/app-roles/, /infra-roles/) and use SCPs at the AWS Organizations level to hard-deny iam:CreateRole without the boundary condition across all non-admin accounts.
💡 Tired of pasting proprietary configs into ChatGPT? Generic AI tools log your company's ARNs, DB strings, and private keys. StackEngine is a zero-backend, pure Client-Side WASM utility. Drop your failing config into the sandbox above. We redact your secrets locally in the browser and auto-generate the refactored code using your own API key.
Prevention in CI/CD
Stop this from reaching production in the first place.
1. Checkov (Terraform / CloudFormation static analysis)
Add to your pipeline pre-plan stage:
checkov -d . --check CKV_AWS_274 # Disallows IAM role creation without permission boundary
checkov -d . --check CKV2_AWS_56 # Checks PassRole is not granted on wildcard resource
2. OPA / Conftest Policy (blocks wildcard PassRole in Terraform plans)
package terraform.iam
deny[msg] {
resource := input.resource_changes[_]
resource.type == "aws_iam_policy"
statement := resource.change.after.policy.Statement[_]
statement.Action[_] == "iam:PassRole"
statement.Resource == "*"
msg := sprintf("DENY: iam:PassRole on wildcard resource in %v. Scope to specific role ARN.", [resource.address])
}
Run in CI:
terraform show -json tfplan.binary | conftest test -p policies/ -
3. AWS IAM Access Analyzer
Enable IAM Access Analyzer with a policy validation check in your pipeline:
aws accessanalyzer validate-policy \
--policy-document file://deployer-policy.json \
--policy-type IDENTITY_POLICY
This catches overly broad PassRole grants and missing iam:PassedToService conditions before the policy is ever attached.
4. SCPs at Org Level (Non-negotiable for production accounts)
{
"Sid": "DenyPassRoleWithoutServiceCondition",
"Effect": "Deny",
"Action": "iam:PassRole",
"Resource": "*",
"Condition": {
"Null": {
"iam:PassedToService": "true"
}
}
}
This SCP hard-denies any PassRole call that does not specify a target service — even if a human admin tries it interactively.