Fix Terraform Error - AssumeRole AccessDenied
Fix the Terraform AssumeRole AccessDenied error for cross-account deployments. Covers trust policies, STS permissions, MFA, and external ID configuration.
Troubleshooting
Fix the Terraform IAM EntityAlreadyExists error for roles, users, and policies. Covers import, unique naming, cross-workspace coordination, and cleanup.
An IAM role, user, or policy with the same name already exists in your AWS account. Import it into Terraform state, use unique names per environment, or delete the duplicate if it's orphaned.
When running terraform apply, you encounter:
Error: creating IAM Role (my-lambda-role):
EntityAlreadyExists: Role with name my-lambda-role already exists.Error: creating IAM User (deploy-user):
EntityAlreadyExists: User with name deploy-user already exists.Error: creating IAM Policy (s3-read-policy):
EntityAlreadyExists: A policy called s3-read-policy already exists.
Duplicate names are not allowed.Error: creating IAM Instance Profile (web-profile):
EntityAlreadyExists: Instance Profile web-profile already exists.Someone created the role/user/policy through the AWS Console or CLI, and now Terraform tries to create the same one.
A different Terraform workspace or project already manages an IAM entity with the same name. IAM names are global within an AWS account.
A previous terraform apply created the IAM entity in AWS but crashed before recording it in state.
The state file was deleted or corrupted, but the IAM entities still exist in AWS.
Using the same name across dev/staging/production in the same account.
# Import IAM Role
terraform import aws_iam_role.lambda my-lambda-role
# Import IAM User
terraform import aws_iam_user.deploy deploy-user
# Import IAM Policy (needs ARN)
terraform import aws_iam_policy.s3_read arn:aws:iam::123456789012:policy/s3-read-policy
# Import IAM Instance Profile
terraform import aws_iam_instance_profile.web web-profile
# Import IAM Role Policy Attachment
terraform import aws_iam_role_policy_attachment.lambda_basic \
my-lambda-role/arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRoleAfter importing, run plan to check for drift:
terraform plan
# Update your .tf files to match the actual IAM configurationimport {
to = aws_iam_role.lambda
id = "my-lambda-role"
}
resource "aws_iam_role" "lambda" {
name = "my-lambda-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect = "Allow"
Principal = { Service = "lambda.amazonaws.com" }
Action = "sts:AssumeRole"
}]
})
}variable "environment" {
type = string
}
variable "project" {
type = string
default = "myapp"
}
resource "aws_iam_role" "lambda" {
name = "${var.project}-${var.environment}-lambda-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect = "Allow"
Principal = { Service = "lambda.amazonaws.com" }
Action = "sts:AssumeRole"
}]
})
tags = {
Project = var.project
Environment = var.environment
ManagedBy = "terraform"
}
}Let AWS generate a unique suffix:
resource "aws_iam_role" "lambda" {
name_prefix = "lambda-role-"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect = "Allow"
Principal = { Service = "lambda.amazonaws.com" }
Action = "sts:AssumeRole"
}]
})
}
# Creates: lambda-role-20251212abc123name_prefix works for: aws_iam_role, aws_iam_policy, aws_iam_instance_profile, aws_iam_group.
If the IAM entity shouldn't exist:
# Check if the role exists and who created it
aws iam get-role --role-name my-lambda-role
aws cloudtrail lookup-events \
--lookup-attributes AttributeKey=ResourceName,AttributeValue=my-lambda-role \
--max-results 5
# Delete if orphaned (detach policies first)
aws iam list-attached-role-policies --role-name my-lambda-role
aws iam detach-role-policy --role-name my-lambda-role \
--policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
aws iam delete-role --role-name my-lambda-role
# Then apply normally
terraform apply# Check if it's in another Terraform state
# Look for the "ManagedBy" tag
aws iam list-role-tags --role-name my-lambda-role
# Search your S3 state buckets
aws s3 ls s3://terraform-state/ --recursive | grep tfstate
# Download and search each state for the role name| IAM Entity | Name Scope | Max Length |
|---|---|---|
| IAM User | Global per account | 64 chars |
| IAM Role | Global per account | 64 chars |
| IAM Policy | Global per account | 128 chars |
| IAM Group | Global per account | 128 chars |
| Instance Profile | Global per account | 128 chars |
| OIDC Provider | Global per account | 2048 chars (URL) |
All IAM names are global within an AWS account — you can't have two roles with the same name even in different regions.
locals {
name_prefix = "${var.project}-${var.environment}"
}
resource "aws_iam_role" "lambda" {
name = "${local.name_prefix}-lambda-execution"
# e.g., "myapp-prod-lambda-execution"
}
resource "aws_iam_role" "ecs_task" {
name = "${local.name_prefix}-ecs-task"
# e.g., "myapp-prod-ecs-task"
}
resource "aws_iam_policy" "s3_access" {
name = "${local.name_prefix}-s3-access"
# e.g., "myapp-prod-s3-access"
}aws iam get-role --role-name NAME)name_prefix prevent future collisions?prod-lambda-role, not lambda-rolename_prefix when the exact name doesn't matterManagedBy = "terraform" — makes orphan detection easyIAM EntityAlreadyExists means the name is taken within your AWS account. Import the entity if Terraform should manage it, delete it if it's orphaned, or adopt a naming convention that includes project and environment to prevent collisions. For maximum isolation, use separate AWS accounts per environment.
Fix the Terraform AssumeRole AccessDenied error for cross-account deployments. Covers trust policies, STS permissions, MFA, and external ID configuration.
Fix the Terraform InvalidAMIID.NotFound error. Covers region-specific AMIs, data source lookups, deregistered images, and cross-account AMI sharing.
How to manage AWS IAM roles, policies, and permissions with Terraform following security best practices. Step-by-step guide with code examples and best pract...
Fix the Terraform 'Backend configuration changed' error. Migrate state between backends (local to S3, S3 to S3), resolve lock conflicts