Skip to content

EC2 instances should use IMDSv2

The EC2 Instance Metadata Service exposes IAM role credentials at http://169.254.169.254. IMDSv1 serves these credentials to any process that sends a simple HTTP GET, which means a server-side request forgery (SSRF) vulnerability in any application on the instance can exfiltrate temporary IAM credentials in a single hop. The Capital One breach in 2019 exploited exactly this pattern.

IMDSv2 adds a PUT-based session token handshake with a TTL-limited token and a default hop limit of 1, which blocks most SSRF exploitation paths because the attacker cannot inject the required custom header through typical SSRF vectors. Enforcing IMDSv2 across all instances is one of the highest-value, lowest-effort security improvements available for EC2 workloads.

Retrofit consideration

Applications or scripts that call the metadata endpoint without IMDSv2 token negotiation (older AWS SDK versions, custom curl calls to http://169.254.169.254) will break when IMDSv1 is disabled. Audit running instances with aws ec2 describe-instances and check the CloudWatch metric MetadataNoToken before enforcing.

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  = "fedrampmoderate.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  = "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  = "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  = "nydfs23.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"
}

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  = "fedramplow.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"
}

module "ec2_instance" {
  source  = "nist80053rev4.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"

  metadata_options = {
    http_tokens = "required"
  }
}

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

  metadata_options {
    http_tokens = "required"
  }
}

What this control checks

The control checks that every aws_instance and aws_launch_template resource includes a metadata_options block with http_tokens set to "required". With http_tokens = "required", the instance only responds to metadata requests that carry a valid session token obtained via PUT request. Omitting metadata_options entirely, or setting http_tokens to "optional", fails the control since IMDSv1 GET requests remain accepted in either case. The http_endpoint argument must also stay "enabled" (the default) for metadata to function at all. http_put_response_hop_limit defaults to 1, preventing token forwarding through extra network hops; container workloads may need this set to 2. For Auto Scaling Groups, the enforcement point is the aws_launch_template, not the ASG resource itself.

Common pitfalls

  • Omitting metadata_options defaults to IMDSv1 allowed

    Declaring aws_instance without a metadata_options block leaves http_tokens at "optional", the AWS API default. Terraform shows no drift because the provider treats the omission as accepting that default. You must explicitly set http_tokens = "required" in every instance and launch template resource.

  • Launch templates can be overridden at launch time

    Instance-level metadata_options blocks override the launch template at launch time. If an aws_instance or Auto Scaling Group referencing the template supplies its own metadata_options, that wins. Check the effective configuration on the instance, not just the template definition.

  • Older SDKs and user-data scripts will break on enforcement

    Enforce IMDSv2 before auditing your SDK versions and user-data scripts and things will break in ways that aren't always obvious. AWS SDK versions before mid-2019 (boto3 < 1.13.0, Java SDK < 1.11.x with IMDS support) don't negotiate IMDSv2 tokens; curl http://169.254.169.254/latest/meta-data/ calls without the PUT token exchange return HTTP 401. Check the CloudWatch MetadataNoToken metric on each instance before flipping enforcement.

  • Container workloads may need hop limit of 2

    Set http_put_response_hop_limit = 2 for any instance running Docker, ECS on EC2, or other container workloads. Docker adds a network hop between the container and the metadata endpoint; with the default hop limit of 1, containers can't reach IMDS at all.

Audit evidence

Auditors expect the AWS Config rule ec2-imdsv2-check showing all evaluated resources as COMPLIANT, or equivalent output from a CSPM tool. The CLI query aws ec2 describe-instances --query 'Reservations[*].Instances[*].{Id:InstanceId,HttpTokens:MetadataOptions.HttpTokens}' should return "required" for every running instance. For accounts using account-level IMDS defaults, aws ec2 get-instance-metadata-defaults should show HttpTokens: required.

CloudTrail records for RunInstances and ModifyInstanceMetadataOptions document when IMDSv2 enforcement was applied and whether it was ever reverted. If the organization enforces IMDSv2 via SCP or IAM condition key (ec2:MetadataHttpTokens), the auditor will typically also request the SCP policy document as a preventive control artifact.

Framework-specific interpretation

PCI DSS v4.0: PCI DSS v4.0 Requirement 6.3 covers protection against known attack techniques, and SSRF-based metadata credential theft is one of the documented ones. Enforcing IMDSv2 is one way to satisfy the secure configuration intent here, though it maps more cleanly to Requirement 7 (restrict access to system components) than to the software inventory side of Requirement 6.

HIPAA Omnibus Rule 2013: Any EC2 workload with access to ePHI is a problem if SSRF can drain the instance's IAM credentials. The HIPAA Security Rule's technical safeguards under 45 CFR 164.312(a)(1) require limiting credential access to authorized processes, and IMDSv2 is one technical mechanism that tightens that boundary. It doesn't satisfy the access control requirement on its own, but it closes a well-documented gap.

NIST SP 800-53 Rev 5: AC-3 (Access Enforcement) and SI-10 (Information Input Validation) both apply. AC-3 because IMDS exposes IAM credentials that must be access-controlled; SI-10 because IMDSv2 validates that metadata requests carry a legitimate session token rather than accepting arbitrary unauthenticated GETs. SC-7 is also relevant where an SSRF path traverses a network boundary to reach the metadata endpoint.

FedRAMP Moderate Baseline Rev 4: At the Moderate baseline, AC-3 and SC-7 both apply here. Federal workloads on EC2 must prevent lateral credential exposure, and the SSRF-to-metadata path is well-documented in federal cloud guidance. IMDSv2 enforcement limits the blast radius of any SSRF vulnerability on a government system and is what agency reviewers ask to see.

Tool mappings

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

  • Compliance.tf Control: ec2_instance_uses_imdsv2

  • AWS Config Managed Rule: EC2_IMDSV2_CHECK

  • Checkov Check: CKV_AWS_79

  • Powerpipe Control: aws_compliance.control.ec2_instance_uses_imdsv2

  • Prowler Checks: ec2_instance_account_imdsv2_enabled, ec2_instance_imdsv2_enabled

  • AWS Security Hub Control: EC2.8

  • KICS Query: c306ac53-ee5b-41d3-86a9-0fd2722b4e67

  • Trivy Checks: AWS-0028, AWS-0130

Last reviewed: 2026-03-09