RAPID INCIDENT TRIAGE & ACTION PLAN•Syntax
01. Error Signature
Terraform IAM Role Import Conflict Resolver
02. Root Cause Vector

Terraform refuses to create an aws_iam_role because the role already exists in AWS, causing a state desync that blocks all downstream res...

03. Rapid Prescription

Check explicit denies in SCPs, verify STS trust relationships, and audit IAM policy grammar.

Zero-Trust Client IDE/ Syntax

Terraform IAM Role Import Conflict Resolver

Terraform refuses to create an aws_iam_role because the role already exists in AWS, causing a state desync that blocks all downstream resource provisioning.

SYSTEM STATUS
CREDITS: 0 / 3
source.yaml(65L)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# TODO: Refactor this entire module after PCI audit. -JSmith 2022-11-30
# This is the legacy IAM role config for the pci-secure-zone batch processor.
# WARNING: The new infra-as-code pipeline also creates roles. Check for conflicts.

provider "aws" {
  region = "us-east-1"
  # VULNERABILITY: Hardcoded creds for the legacy service account. Rotate quarterly.
  access_key = "AKIAIOSFODNN7EXAMPLE"
  secret_key = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
}

# Old module import - can't delete because of compliance reasons.
module "legacy_pci_role" {
  source = "git::https://my-registry.internal/terraform-aws-iam.git?ref=v1.2.1"

  role_name = "pci-batch-processor-legacy"
  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [{
      Effect = "Allow"
      Principal = { Service = "ec2.amazonaws.com" }
      Action = "sts:AssumeRole"
    }]
  })
  # TODO: fix this workaround for KMS permissions. The new pipeline uses a different policy.
  # managed_policy_arns = ["arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess"]
}

# New "modernized" resource definition in the same state file.
# VULNERABILITY: This will cause 'Error: Resource already exists' on terraform import because the module above creates the same role via a different resource address, but the names are identical.
resource "aws_iam_role" "pci_batch_processor" {
  name = "pci-batch-processor-legacy" # Same name as the module's output!
  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [{
      Effect = "Allow"
      Principal = { Service = "ec2.amazonaws.com" }
      Action = "sts:AssumeRole"
    }]
  })
  # Embedded dummy secret for the legacy database connection string.
  tags = {
    Environment = "pci-secure-zone"
    # TODO: Move this to Secrets Manager after migration.
    DbConnection = "postgresql://batchuser:SuperSecret123!@legacy-db.internal.pci:5432/transactions"
    ApiKey = "sk_live_51H8s9KEXAMPLE12345678abcdef"
  }
}

# Redundant output that references the same role, creating confusion.
output "role_arn" {
  value = aws_iam_role.pci_batch_processor.arn
  # Also outputs from module, commented out but still in file.
  # value = module.legacy_pci_role.role_arn
}

# Dummy multi-line RSA key injected into a local for a mock bootstrap script (never used).
locals {
  legacy_ssh_private_key = <<-EOT
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAz7X8X8X8X8X8X8X8X8X8X8X8X8X8X8X8X8X8X8X8X8X8X8X8X
8X8X8X8X8X8X8X8X8X8X8X8X8X8X8X8X8X8X8X8X8X8X8X8X8X8X8X8X8X8X8X8X8
-----END RSA PRIVATE KEY-----
EOT
}
AI Audit Console
> Waiting for pre-flight audit...
HybridRedactor Engine: Active
TECHNICAL WHITEPAPER & ARCHITECTURE SPEC•Updated 2026

How to Fix Terraform 'Error: Resource Already Exists' When Importing an aws_iam_role

Threat/Impact Level: HIGH | Exploitability/Downtime Risk: HIGH | Time to Fix: 10 mins

TL;DR

  • What broke: Terraform is trying to CREATE an IAM role that already physically exists in AWS, but has no entry in terraform.tfstate. The plan fails hard.
  • How to fix it: Run terraform import aws_iam_role.<resource_label> <existing-role-name> to pull the live resource into state, then reconcile your HCL to match the live role's attributes.
  • Shortcut: Use our Client-Side Sandbox above to auto-refactor this — paste your failing .tf block and get the corrected import command and HCL diff instantly.

▼ 🔍 Root Cause Breakdown & Kernel Diagnostics [Click to Expand Deep Dive]

The Incident (What Does the Error Mean?)

Raw error output from a terraform apply:

Error: creating IAM Role (my-app-execution-role): EntityAlreadyExists:
  Role with name my-app-execution-role already exists.
│   with aws_iam_role.app_execution_role,
│   on iam.tf line 4, in resource "aws_iam_role" "app_execution_role":
│    4: resource "aws_iam_role" "app_execution_role" {

Immediate consequence: Terraform's AWS provider attempted a CreateRole API call. IAM rejected it with EntityAlreadyExists. Your entire terraform apply is now aborted — every resource depending on this role (ECS task definitions, Lambda functions, EC2 instance profiles) did not get created or updated. Your pipeline is dead.

This happens in three common scenarios:

  1. The role was created manually in the AWS Console before IaC was adopted.
  2. A previous Terraform run created the role but the state file was lost, corrupted, or the workspace was switched.
  3. Another Terraform root module or stack owns this role, and you have a duplicate resource block.

▼ 🔍 Detailed Impact & Blast Radius Analysis [Click to Expand Deep Dive]

The Attack Vector / Blast Radius

This is not just a nuisance error. The blast radius is significant:

  • Pipeline deadlock: Every CI/CD run will fail at this step. No infrastructure changes can be applied until resolved.
  • State drift accumulation: The longer the role exists outside of state, the more its live configuration (inline policies, tags, trust relationships) will drift from your HCL. Importing a heavily drifted role and running apply immediately can overwrite production trust policies, breaking service authentication silently.
  • Duplicate ownership risk: If two Terraform stacks both define this role without remote state locking, a concurrent apply from either stack can delete and recreate the role, instantly invalidating all attached instance profiles and assumed-role sessions. Active workloads using sts:AssumeRole will receive AccessDenied with no warning.
  • Audit trail gap: A role living outside of IaC state has no change history. You cannot answer "who modified this role's trust policy and when" from your Terraform logs.

How to Fix It (The Solution)

Step 1: Verify the Role Exists in AWS

aws iam get-role --role-name my-app-execution-role --query 'Role.{Name:RoleName,Arn:Arn,Created:CreateDate}'

Confirm the exact role name. IAM role names are case-sensitive.

Step 2: Basic Fix — Import the Existing Role into State

terraform import aws_iam_role.app_execution_role my-app-execution-role

After import, immediately run terraform plan before any apply. The plan output will show every attribute delta between your HCL and the live role. You must reconcile these before applying.

Step 3: Reconcile HCL to Match Live Resource

The most common drift is in the assume_role_policy JSON (whitespace, key ordering, added/removed principals) and tags. Use terraform plan output to identify deltas.

 resource "aws_iam_role" "app_execution_role" {
   name = "my-app-execution-role"
+  path = "/"
+  description = "ECS task execution role - managed by Terraform"

   assume_role_policy = jsonencode({
     Version = "2012-10-17"
     Statement = [{
       Effect    = "Allow"
-      Principal = { Service = "ecs.amazonaws.com" }
+      Principal = { Service = "ecs-tasks.amazonaws.com" }
       Action    = "sts:AssumeRole"
     }]
   })

+  tags = {
+    ManagedBy   = "terraform"
+    Environment = var.environment
+  }
 }

Enterprise Best Practice — Prevent Orphaned Roles with Remote State and Lifecycle Guards

For roles shared across multiple stacks, use a data source reference instead of a duplicate resource block:

- resource "aws_iam_role" "app_execution_role" {
-   name               = "my-app-execution-role"
-   assume_role_policy = data.aws_iam_policy_document.ecs_trust.json
- }

+ # Role is owned by the platform stack. Reference it here as a data source.
+ data "aws_iam_role" "app_execution_role" {
+   name = "my-app-execution-role"
+ }

For roles that must be owned by this stack, add a lifecycle block to prevent accidental destruction:

 resource "aws_iam_role" "app_execution_role" {
   name               = "my-app-execution-role"
   assume_role_policy = data.aws_iam_policy_document.ecs_trust.json
+
+  lifecycle {
+    prevent_destroy = true
+    # Ignore tag drift from manual emergency changes
+    ignore_changes  = [tags["LastEmergencyModifiedBy"]]
+  }
 }

💡 Tired of pasting proprietary infrastructure configs into public LLMs? Generic AI interfaces log your company's ARNs, connection strings, and private keys. StackEngine enforces client-side zero-trust boundaries—the sandbox above executes 100% locally in your browser. Drop your broken manifest above to redact secrets locally and synthesize deterministic patch drafts with your own API key.

(For CI/CD pipeline automation, install via npm i -g stackengine).


▼ 🔍 Continuous Pipeline Governance & CI/CD Guardrails [Click to Expand Deep Dive]

Prevention in CI/CD

1. Enforce State Existence Before Apply with Checkov

Add Checkov to your pipeline to catch unmanaged IAM resources:

checkov -d . --check CKV_AWS_274 --compact

2. OPA Policy — Block Plans with IAM Role Creation if Role Name Already Exists

package terraform.iam

deny[msg] {
  resource := input.resource_changes[_]
  resource.type == "aws_iam_role"
  resource.change.actions[_] == "create"
  # Integrate with aws_iam_role data source pre-check in your pipeline
  msg := sprintf("IAM role '%v' is being created by Terraform. Verify it does not already exist in AWS before applying.", [resource.change.after.name])
}

3. Pre-Apply Shell Guard in CI/CD

Insert this before terraform apply in your pipeline script:

ROLE_NAME=$(terraform show -json | jq -r '.planned_values.root_module.resources[] | select(.type=="aws_iam_role") | .values.name')
if aws iam get-role --role-name "$ROLE_NAME" > /dev/null 2>&1; then
  echo "[WARN] IAM role $ROLE_NAME exists in AWS but may not be in state. Run terraform import before apply."
  exit 1
fi

4. Terragrunt + S3 Remote State Locking

If you are not already using remote state with DynamoDB locking, this class of error will recur. Shared IAM roles must live in a single authoritative state file, referenced by all consumer stacks via terraform_remote_state data source or SSM Parameter Store ARN lookup.

# In consumer stacks, never re-declare shared platform roles
data "terraform_remote_state" "platform_iam" {
  backend = "s3"
  config = {
    bucket = "my-org-terraform-state"
    key    = "platform/iam/terraform.tfstate"
    region = "us-east-1"
  }
}

resource "aws_ecs_task_definition" "app" {
  # ...
  execution_role_arn = data.terraform_remote_state.platform_iam.outputs.app_execution_role_arn
}

Official Grounding & Normative Specifications

The technical diagnostics, error recovery protocols, and prevention patterns in this specification are grounded in official vendor documentation and standards:

Diagnostic & Architectural FAQ

Q1.Can I just delete the IAM role in AWS and re-run terraform apply?
The root cause is a state desync: your Terraform state lacks the resource, but AWS's live environment holds it, often due to manual changes or a previous import. Deleting the live role is a cascade failure trigger. It immediately invalidates all active STS sessions and instance profiles, causing workloads like ECS tasks or Lambda functions to fail mid-execution as their API calls are denied. This is not a fix; it's an outage. To build authority, you must first import the live resource to reconcile state, then manage drift—manually, a tedious and high-risk process. For a precise, automated resolution, drop the broken manifest into the StackEngine Local Sandbox above to compile a zero-trust fix that prevents the deletion trap entirely.
Q2.After running terraform import, my terraform plan still shows changes. Is it safe to apply?
Applying this plan is a direct mutation of your live AWS environment. The changes represent configuration drift between your HCL and the imported role. The highest-risk deltas are in the `assume_role_policy` (trust relationships) and attached managed policies—a single misapplied change can over-permission a service account or break trust for an entire application. Manual reconciliation is error-prone; a typo in a principal ARN or a misplaced policy attachment creates immediate security vulnerabilities and operational blast radius. Do not apply manually. Instead, drop the broken manifest into the StackEngine Local Sandbox above to automatically compile a zero-trust fix that validates and applies only the necessary, safe changes.
Q3.How do I find which Terraform workspace or stack originally created this IAM role?
Manually tracing ownership via tags or S3 state file searches is a compliance nightmare at scale. In large mono-repos or multi-team environments, an untagged, orphaned IAM role represents an unaccountable security risk—it could be a shadow IT resource or a compliance violation waiting for an audit. Doing this investigation manually for dozens of roles is unsustainable and error-prone, leaving gaps in your security posture. For enterprise-scale, automated governance, upgrade to the StackEngine CI/CD Automator (Team Edition). It provides on-prem, automated pull request generation with Maker-Checker review, enabling you to bulk-import, reconcile, and tag orphaned resources across your entire estate while generating compliance-ready audit trails.