Skip to content

EC2 instances should have IAM profile attached

EC2 instances without an IAM instance profile cannot use temporary credentials from STS. This pushes developers toward embedding long-lived access keys in application config, environment variables, or user data scripts. Leaked keys mean emergency rotation, and rotating hardcoded credentials across distributed config files is its own operational incident.

Instance profiles grant short-lived credentials that rotate automatically and are scoped by an IAM role's policy. AWS Systems Manager Agent, CloudWatch Agent, and CodeDeploy Agent all depend on instance profile credentials to function. Missing profiles also block SSM Session Manager access, forcing teams to open SSH over port 22.

Retrofit consideration

Existing instances do not require a stop/start or reboot to attach an instance profile via the AWS API. Terraform can associate a profile on a running instance, but the EC2 metadata service may need a short propagation window before new credentials appear. After migration, audit each instance for hardcoded credentials and remove them.

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  = "pcidss.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  = "hipaa.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  = "nist80053.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  = "cisv80ig1.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  = "nist800171.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  = "ffiec.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  = "acscessentialeight.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  = "cfrpart11.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  = "rbiitfnbfc.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  = "nistcsfv11.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 aws_instance resource must include the iam_instance_profile argument pointing to a valid aws_iam_instance_profile resource. A configuration passes when iam_instance_profile is set to a non-empty value (name or ARN). It fails when the argument is omitted or unset. The referenced aws_iam_instance_profile must itself reference an aws_iam_role via the role argument. For launch templates, aws_launch_template should include an iam_instance_profile block with either name or arn specified. For Auto Scaling groups using launch templates, the profile must be defined in the template rather than on individual instances.

Common pitfalls

  • Instance profile vs. IAM role confusion

    An IAM role alone does not satisfy this control. You must create an aws_iam_instance_profile resource that wraps the role, then reference the profile in aws_instance.iam_instance_profile. Having a role defined without the instance profile wrapper is one of the most common Terraform mistakes on this control.

  • Empty or placeholder profiles

    Attaching an instance profile with a role that has no policies technically passes this control but provides no actual permissions. Some teams create stub profiles to clear compliance checks without granting the permissions applications need. The result is runtime failures that are easy to misattribute until someone checks the role.

  • Launch template override gaps

    When an Auto Scaling group uses mixed instance types with launch template overrides, the iam_instance_profile block must be defined in the base aws_launch_template, not in an override. Overrides do not support changing the instance profile, so if it's missing from the base template, launched instances have no profile attached.

  • Spot and on-demand fleet instances

    Instances launched via aws_spot_instance_request or EC2 Fleet require the same iam_instance_profile argument. These resource types get overlooked when teams apply this control to aws_instance resources and assume coverage is complete.

Audit evidence

Auditors look for AWS Config rule evaluation results from the managed rule ec2-instance-profile-attached showing all EC2 instances as compliant. A supplementary report listing all running instances alongside their attached instance profile ARN (from aws ec2 describe-instances filtered on IamInstanceProfile) is direct supporting evidence. CloudTrail events for AssociateIamInstanceProfile and RunInstances confirm when profiles were attached and whether new launches include them.

For continuous compliance, AWS Security Hub findings mapped to this control, or external CSPM scan results showing zero non-compliant EC2 instances, work as ongoing evidence.

Framework-specific interpretation

PCI DSS v4.0: Requirements 7 and 8 together cover what access should exist and how it's authenticated. Instance profiles satisfy both: each instance gets a distinct identity tied to a scoped role, eliminating shared long-lived credentials and producing a per-instance authentication record that examiners can trace.

HIPAA Omnibus Rule 2013: The Security Rule at 164.312(a)(1) requires access controls that restrict system access to authorized users and processes. For EC2 workloads touching ePHI in other AWS services, an instance profile replaces shared static credentials with temporary, role-scoped keys, meeting the minimum necessary standard for that machine-level access.

NIST SP 800-53 Rev 5: Maps to AC-2 (Account Management), AC-3 (Access Enforcement), and IA-2 (Identification and Authentication). Instance profiles give each EC2 instance a managed identity within IAM, with credentials that rotate automatically and a role policy enforcing the access boundaries AC-3 requires. Under AC-2, there's no static key lifecycle to manage.

Tool mappings

Use these identifiers to cross-reference this control across tools, reports, and evidence.

  • Compliance.tf Control: ec2_instance_iam_profile_attached

  • AWS Config Managed Rule: EC2_INSTANCE_PROFILE_ATTACHED

  • Checkov Check: CKV2_AWS_41

  • Powerpipe Controls: aws_compliance.control.ec2_instance_iam_profile_attached, aws_compliance.control.ec2_instance_publicly_accessible_iam_profile_attached

  • Prowler Check: ec2_instance_profile_attached

Last reviewed: 2026-03-09