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 EC2AssociateIamInstanceProfileAPI validates the profile name against a regional endpoint — if the profile was never created (Terraform sometimes skipsaws_iam_instance_profileresource) or you referenced the role directly, you getInstanceProfileNotFound. - How to fix it: Create a dedicated
aws_iam_instance_profileresource wrapping your role, then passName(notArnof 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:
- Engineers under pressure manually attach overly-permissive roles to unblock the outage — often
AdministratorAccess— and forget to revert. - 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.
- 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-1Terraform 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 (missingaws_iam_instance_profileresource 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_profileresource actually exist in your Terraform state (separate fromaws_iam_role)? - Are you using the profile
name(string) vsarnfield 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.