EC2 instances should use IAM instance roles for AWS resource access
Hardcoded AWS access keys on EC2 instances are a persistent source of credential leaks. They show up in environment variables, configuration files, AMI snapshots, and version control. When an instance uses an IAM instance role, the EC2 metadata service delivers temporary credentials that rotate automatically, removing the need to distribute or manage static secrets.
Instances without an attached role also lack a clear identity boundary. You can't scope AWS permissions to a workload, trace API calls back to a specific instance in CloudTrail, or revoke access by detaching a role. Every instance should have an instance profile, even if its role policy grants zero permissions, because retrofitting one later often requires application restarts and deployment pipeline changes.
Retrofit consideration
Attaching or swapping an instance profile on a running instance doesn't require a stop/start; the EC2 association APIs handle it in place. The harder part is applications that rely on hardcoded credentials: those need code changes to use the SDK default credential chain.
Implementation
Choose the approach that matches how you manage Terraform.
Use the compliance.tf module to enforce this control by default. See get started with compliance.tf.
module "ec2_instance" {
source = "cis.compliance.tf/terraform-aws-modules/ec2-instance/aws"
version = ">=6.0.0"
ami_ssm_parameter = "/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-arm64"
instance_type = "t4g.nano"
subnet_id = "subnet-abc123"
}
module "ec2_instance" {
source = "cisv500.compliance.tf/terraform-aws-modules/ec2-instance/aws"
version = ">=6.0.0"
ami_ssm_parameter = "/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-arm64"
instance_type = "t4g.nano"
subnet_id = "subnet-abc123"
}
If you use terraform-aws-modules/ec2-instance/aws, set the right module inputs for this control. You can later migrate to the compliance.tf module with minimal changes because it is compatible by design.
module "ec2_instance" {
source = "terraform-aws-modules/ec2-instance/aws"
version = ">=6.0.0"
ami_ssm_parameter = "/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-arm64"
instance_type = "t4g.nano"
subnet_id = "subnet-abc123"
}
Use AWS provider resources directly. See docs for the resources involved: aws_instance.
resource "aws_instance" "this" {
ami = "ami-abc12345"
instance_type = "t4g.nano"
subnet_id = element(["subnet-abc123", "subnet-def456"], 0)
vpc_security_group_ids = ["sg-abc12345"]
iam_instance_profile = "example-instance-profile"
}
What this control checks
The control checks that each aws_instance has iam_instance_profile set to a non-empty value. The instance profile is a separate aws_iam_instance_profile resource that references an aws_iam_role via the role argument. That role must have an assume role policy trusting the ec2.amazonaws.com service principal.
A passing configuration requires:
- An
aws_iam_rolewithassume_role_policyallowingsts:AssumeRolefromec2.amazonaws.com. - An
aws_iam_instance_profilewith itsroleargument set to that IAM role's name. - An
aws_instancewithiam_instance_profileset to the instance profile's name or ARN.
It fails when iam_instance_profile is omitted or set to an empty string. The control doesn't evaluate what permissions the attached role grants, only that a profile is present.
Common pitfalls
Inline iam_instance_profile name vs. resource reference
Setting
iam_instance_profileto a hardcoded string instead of referencingaws_iam_instance_profile.example.namebreaks the Terraform dependency graph. If the profile is deleted or renamed outside Terraform, the instance resource won't detect the drift.Launch templates override instance-level settings
With
aws_launch_template, the IAM instance profile goes inside theiam_instance_profileblock using anameorarnargument. If an Auto Scaling group references the template and that block is missing, every launched instance runs without a role, even if a standaloneaws_instancein the same module has one configured.Instance profile without attached policies
An instance profile whose role has no policies satisfies this control but will cause application failures if the workload expects AWS API access. Teams sometimes skip the profile entirely because permissions haven't been decided yet. Attach the profile with a minimal policy and expand it later; leaving it off entirely creates a different problem.
Replacing instance profile requires instance restart
This is a common misconception: changing
iam_instance_profileon a running instance doesn't require a stop/start. Theaws ec2 associate-iam-instance-profileAPI attaches a new profile when none exists, andreplace-iam-instance-profile-associationhandles swaps. Terraform uses these APIs automatically. What does require changes is any application code that reads static credentials directly rather than using the SDK default credential chain.
Audit evidence
AWS Config rule results for ec2-instance-profile-attached showing all instances as COMPLIANT are the primary evidence artifact. Prowler or Steampipe scan output works as an equivalent, confirming all instances have a profile attached. Console evidence from the EC2 Instances page with the "IAM Role" column visible, showing no blank entries, satisfies point-in-time checks.
CloudTrail logs for RunInstances events should show iamInstanceProfile populated in the request parameters. AWS Config configuration timeline snapshots for each instance should include a non-null iamInstanceProfile.arn in the recorded configuration item, useful for demonstrating coverage over historical audit periods.
Framework-specific interpretation
Tool mappings
Use these identifiers to cross-reference this control across tools, reports, and evidence.
Compliance.tf Control:
ec2_instance_using_iam_instance_roleAWS Config Managed Rule:
EC2_INSTANCE_PROFILE_ATTACHEDCheckov Check:
CKV2_AWS_41Powerpipe Control:
aws_compliance.control.ec2_instance_using_iam_instance_roleProwler Check:
ec2_instance_profile_attached
Last reviewed: 2026-03-09