Initializing Enclave...

Fixing EC2 'InstanceProfileNotFound' Error When Attaching IAM Role Across Regions

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

TL;DR

  • What broke: You passed an IAM Role ARN or a wrong identifier to --iam-instance-profile. EC2 requires the instance profile name, not the role name or ARN. Instance profiles are IAM global constructs but the EC2 AssociateIamInstanceProfile API validates the profile name against a regional endpoint — if the profile was never created (Terraform sometimes skips aws_iam_instance_profile resource) or you referenced the role directly, you get InstanceProfileNotFound.
  • How to fix it: Create a dedicated aws_iam_instance_profile resource wrapping your role, then pass Name (not Arn of the role) to the EC2 association call.
  • Call to action: Use our Client-Side Sandbox above to paste your failing Terraform block or CLI command — it auto-refactors the instance profile attachment locally without sending your ARNs anywhere.

The Incident (What Does the Error Mean?)

Raw error output:

An error occurred (InvalidParameterValue) when calling the AssociateIamInstanceProfile operation:
Value (arn:aws:iam::123456789012:role/MyAppRole) for parameter iamInstanceProfile.arn is invalid.
-- or --
An error occurred (InstanceProfileNotFound) when calling the AssociateIamInstanceProfile operation:
Instance profile MyAppRole cannot be found.

Immediate consequence: The EC2 instance launches with no IAM identity. Any application code calling AWS SDKs (S3, Secrets Manager, DynamoDB) gets NoCredentialProviders at runtime. In autoscaling fleets, every new instance in the group is born broken. If this is a launch template, the entire ASG is dead on arrival.


The Attack Vector / Blast Radius

This is not just an availability issue. The failure mode creates a dangerous operational pressure:

  1. Engineers under pressure manually attach overly-permissive roles to unblock the outage — often AdministratorAccess — and forget to revert.
  2. Instance metadata endpoint (IMDSv1) on a misconfigured instance with an overly-permissive role attached post-hoc is a direct SSRF-to-credential-theft path. Any SSRF vulnerability in your app becomes full account compromise.
  3. In multi-region deployments, teams assume IAM is "replicated" — it is not regional, but the EC2 API validation is. A profile created via us-east-1 Terraform apply is globally available by name, but if your automation script hardcodes a region-specific ARN format incorrectly, or the profile simply was never created (missing aws_iam_instance_profile resource block), every region's fleet fails silently until health checks cascade.

Blast radius: Full application layer outage + credential hygiene risk if engineers apply emergency manual fixes.


How to Fix It (The Solution)

Root Cause Checklist

  • Are you passing the Role ARN instead of the Instance Profile Name?
  • Does an aws_iam_instance_profile resource actually exist in your Terraform state (separate from aws_iam_role)?
  • Are you using the profile name (string) vs arn field correctly in the EC2 API call?

Basic Fix — AWS CLI

# WRONG: Passing role ARN directly
- aws ec2 associate-iam-instance-profile \
-   --instance-id i-0abcd1234efgh5678 \
-   --iam-instance-profile Arn=arn:aws:iam::123456789012:role/MyAppRole

# CORRECT: Pass instance profile NAME (not role name, not ARN)
+ aws ec2 associate-iam-instance-profile \
+   --instance-id i-0abcd1234efgh5678 \
+   --iam-instance-profile Name=MyAppRole-InstanceProfile

Verify the profile exists first:

aws iam get-instance-profile --instance-profile-name MyAppRole-InstanceProfile

Enterprise Best Practice — Terraform

The most common root cause in IaC: declaring aws_iam_role but never declaring aws_iam_instance_profile. They are separate resources.

 resource "aws_iam_role" "app_role" {
   name               = "MyAppRole"
   assume_role_policy = data.aws_iam_policy_document.ec2_assume.json
 }

+resource "aws_iam_instance_profile" "app_profile" {
+  name = "MyAppRole-InstanceProfile"
+  role = aws_iam_role.app_role.name
+}

 resource "aws_launch_template" "app" {
   name_prefix   = "app-"
   image_id      = var.ami_id
   instance_type = "t3.medium"

-  # WRONG: referencing role ARN directly
-  iam_instance_profile {
-    arn = aws_iam_role.app_role.arn
-  }

+  # CORRECT: reference the instance profile resource
+  iam_instance_profile {
+    name = aws_iam_instance_profile.app_profile.name
+  }
 }

Additionally, enforce IMDSv2 to eliminate SSRF-to-credential-theft risk:

 resource "aws_launch_template" "app" {
+  metadata_options {
+    http_endpoint               = "enabled"
+    http_tokens                 = "required"  # Forces IMDSv2
+    http_put_response_hop_limit = 1
+  }
 }

💡 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

Checkov — Catch Missing Instance Profile

Add to your pipeline. Checkov rule CKV_AWS_28 flags EC2 instances without IAM profiles, but you need a custom check for the role-vs-profile confusion:

# .checkov.yml
checks:
  - id: CKV_AWS_28
  - id: CKV_AWS_79  # Enforces IMDSv2

OPA/Conftest Policy

# policy/ec2_instance_profile.rego
package terraform

deny[msg] {
  resource := input.resource.aws_launch_template[name]
  profile := resource.iam_instance_profile[_]
  profile.arn  # ARN field used instead of name
  not profile.name
  msg := sprintf("Launch template '%v': use iam_instance_profile.name, not .arn — ensure aws_iam_instance_profile resource exists.", [name])
}

Terraform Sentinel (HashiCorp Cloud Platform)

# Enforce instance profile resource exists for every role used in launch templates
precondition {
  condition     = length(aws_iam_instance_profile.app_profile) > 0
  error_message = "An aws_iam_instance_profile resource must be declared for every IAM role attached to EC2."
}

GitHub Actions Gate

- name: Validate IAM Instance Profiles
  run: |
    # Fail if any launch_template references iam_instance_profile.arn instead of .name
    grep -r 'iam_instance_profile' ./terraform --include='*.tf' | grep '\barn\b' && \
      echo 'ERROR: Use .name not .arn in iam_instance_profile block' && exit 1 || echo 'OK'

This gates every PR. The 30-second pipeline check eliminates the 3am production outage.

Related Diagnostics

"Part of the Security Utility Matrix."

View all 140 Security Tools →