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
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.
This control is enforced automatically with Compliance.tf modules. Start free trial
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:
1. An aws_iam_role with assume_role_policy allowing sts:AssumeRole from ec2.amazonaws.com.
2. An aws_iam_instance_profile with its role argument set to that IAM role's name.
3. An aws_instance with iam_instance_profile set 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_profile to a hardcoded string instead of referencing aws_iam_instance_profile.example.name breaks 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 the iam_instance_profile block using a name or arn argument. If an Auto Scaling group references the template and that block is missing, every launched instance runs without a role, even if a standalone aws_instance in 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_profile on a running instance doesn't require a stop/start. The aws ec2 associate-iam-instance-profile API attaches a new profile when none exists, and replace-iam-instance-profile-association handles 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
PCI DSS v4.0: Requirement 7.2 restricts access to system components and cardholder data by job function; Requirement 8.6 covers management of application and system accounts. Instance roles address both: permissions are scoped per workload, static shared keys are eliminated, and API calls are attributable per instance in CloudTrail. Examiners will ask to confirm that no EC2 instances in the cardholder data environment rely on IAM user credentials.
ISO/IEC 27001:2022: A.8.2 covers privileged access rights; A.8.5 covers secure authentication for automated processes. Manually provisioned access keys tend to fail both: they are often over-permissioned and rarely rotated. Instance roles replace them with STS-issued temporary credentials, giving you controlled issuance, automatic expiry, and an audit trail in CloudTrail.
NIS2 Directive (EU 2022/2555): Article 21 calls for appropriate technical measures covering access control and identity management. For EC2 workloads, that means machine-to-service authentication should use short-lived, auditable credentials. Static access keys can be exfiltrated and reused indefinitely; instance role credentials expire and are tied to a specific instance identity.
NIST SP 800-53 Rev 5: AC-2 and AC-6 both apply. AC-2 expects managed credential lifecycles for machine identities, not just user accounts. AC-6 limits what those identities can do. Instance roles satisfy both: STS-issued credentials rotate automatically, and per-role policies scope permissions to the specific workload rather than granting broad access to every instance sharing the same key.
Tool mappings
Use these identifiers to cross-reference this control across tools, reports, and evidence.
- Compliance.tf Control:
ec2_instance_using_iam_instance_role - AWS Config Managed Rule:
EC2_INSTANCE_PROFILE_ATTACHED - Checkov Check:
CKV2_AWS_41 - Powerpipe Control:
aws_compliance.control.ec2_instance_using_iam_instance_role - Prowler Check:
ec2_instance_profile_attached
Last reviewed: 2026-03-09