Skip to content

EC2 instances should not use key pairs in running state

Key pairs create a persistent, decentralized authentication channel to EC2 instances. Anyone holding the private key file can SSH in without passing through IAM policies, CloudTrail-auditable session logging, or network-level controls enforced by Systems Manager Session Manager. Leaked PEM files are a common finding in incident response, and rotating key pairs across a fleet is operationally painful.

Removing key pairs forces teams toward Session Manager or EC2 Instance Connect, both of which tie access to IAM identities and produce CloudTrail events for every session. For any team that needs traceable, revocable access to compute, that's a real improvement.

Retrofit consideration

Removing a key pair from an existing instance is not an in-place operation: key_name cannot be changed on a running instance, so Terraform will destroy and recreate it. Before making that change, confirm SSM Agent is installed, the instance profile includes AmazonSSMManagedInstanceCore (or equivalent), and Session Manager or EC2 Instance Connect is actually reachable. Cutting SSH access before verifying an alternative path locks you out.

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  = "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"]
}

What this control checks

The aws_instance resource accepts an optional key_name argument. Omit it entirely or set it to null to pass. Any non-empty value fails. The same applies to aws_launch_template: if key_name is populated, every instance an Auto Scaling group launches inherits the key pair. Ensure instances carry an IAM instance profile with AmazonSSMManagedInstanceCore (or equivalent) so Session Manager provides a usable access path once key pairs are removed.

Common pitfalls

  • key_name cannot be removed in place

    Changing key_name on an aws_instance forces resource replacement. Terraform will destroy and recreate the instance. Know your lifecycle { prevent_destroy = true } settings before applying, and schedule replacements during a maintenance window.

  • Launch templates propagate key pairs silently

    An aws_launch_template with key_name set stamps every instance an Auto Scaling group launches. Auditing only running instances misses this. Check launch templates and launch configurations too, not just the instances already running.

  • SSM Agent not installed or reachable

    Removing key pairs without confirming SSM Agent connectivity locks you out. Verify the instance can reach the ssm, ssmmessages, and ec2messages VPC endpoints (or has internet access) before dropping key_name.

  • AMIs with baked-in authorized_keys

    Custom AMIs may contain hardcoded SSH public keys in /home/ec2-user/.ssh/authorized_keys even when no key pair is attached at the EC2 metadata level. This control checks the key pair association, not the OS-level SSH configuration. Audit your AMI build process separately.

Audit evidence

Expect AWS Config rule evaluation results showing all running EC2 instances pass the no-key-pair check. Supporting evidence: aws ec2 describe-instances output filtered to KeyName fields, with null or absent values across the fleet. CloudTrail logs for ssm:StartSession events confirm administrative access is running through Session Manager rather than direct SSH. For any documented exceptions, a risk acceptance with compensating controls (such as a security group restricting port 22 to a bastion requiring MFA) should be on file.

Framework-specific interpretation

PCI DSS v4.0: Requirement 8 calls for strong authentication and individual accountability across all system components. Using IAM-authenticated access paths like Session Manager, rather than shared key pairs, can support objectives including unique identification (8.2.1), MFA for administrative access (8.4.1), and authentication factor management (8.3). This control is supportive evidence; it doesn't satisfy all Requirement 8 obligations on its own.

Tool mappings

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

  • Compliance.tf Control: ec2_instance_no_amazon_key_pair

  • AWS Config Managed Rule: EC2_NO_AMAZON_KEY_PAIR

  • Powerpipe Control: aws_compliance.control.ec2_instance_no_amazon_key_pair

Last reviewed: 2026-03-09