Enforcing IMDSv2 Across AWS Infrastructure

Implement IMDSv2 enforcement to protect EC2 instances from SSRF attacks and credential theft with mandatory session tokens.

The Instance Metadata Service v2 (IMDSv2) protects EC2 instances from SSRF attacks by requiring session-oriented requests. This guide covers enforcement strategies at scale.

Understanding IMDSv1 Risks

SSRF Attack Vector

IMDSv1 vulnerabilities allow attackers to steal credentials:

# Attacker exploiting SSRF to get credentials (IMDSv1)
curl http://169.254.169.254/latest/meta-data/iam/security-credentials/EC2Role

IMDSv2 Protection

IMDSv2 requires a session token:

# IMDSv2 requires token-based requests
TOKEN=$(curl -X PUT "http://169.254.169.254/latest/api/token" \
  -H "X-aws-ec2-metadata-token-ttl-seconds: 21600")

curl -H "X-aws-ec2-metadata-token: $TOKEN" \
  http://169.254.169.254/latest/meta-data/iam/security-credentials/EC2Role

Enforcing IMDSv2 on Launch

Launch Template Configuration

SecureLaunchTemplate:
  Type: AWS::EC2::LaunchTemplate
  Properties:
    LaunchTemplateName: IMDSv2Required
    LaunchTemplateData:
      MetadataOptions:
        HttpEndpoint: enabled
        HttpTokens: required  # Enforce IMDSv2
        HttpPutResponseHopLimit: 1
        InstanceMetadataTags: enabled

Terraform Configuration

resource "aws_launch_template" "secure" {
  name = "imdsv2-required"
  
  metadata_options {
    http_endpoint               = "enabled"
    http_tokens                 = "required"
    http_put_response_hop_limit = 1
    instance_metadata_tags      = "enabled"
  }
  
  # Other configuration...
}

Organization-Wide Enforcement

Service Control Policy

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "RequireIMDSv2",
      "Effect": "Deny",
      "Action": "ec2:RunInstances",
      "Resource": "arn:aws:ec2:*:*:instance/*",
      "Condition": {
        "StringNotEquals": {
          "ec2:MetadataHttpTokens": "required"
        }
      }
    },
    {
      "Sid": "PreventIMDSv2Disable",
      "Effect": "Deny",
      "Action": "ec2:ModifyInstanceMetadataOptions",
      "Resource": "*",
      "Condition": {
        "StringEquals": {
          "ec2:MetadataHttpTokens": "optional"
        }
      }
    }
  ]
}

AWS Config Rule

IMDSv2ComplianceRule:
  Type: AWS::Config::ConfigRule
  Properties:
    ConfigRuleName: ec2-imdsv2-check
    Description: Checks whether IMDSv2 is required
    Source:
      Owner: AWS
      SourceIdentifier: EC2_IMDSV2_CHECK
    Scope:
      ComplianceResourceTypes:
        - AWS::EC2::Instance

Updating Existing Instances

Bulk Update Script

import boto3

ec2 = boto3.client('ec2')

def enforce_imdsv2_all_instances():
    paginator = ec2.get_paginator('describe_instances')
    
    for page in paginator.paginate():
        for reservation in page['Reservations']:
            for instance in reservation['Instances']:
                instance_id = instance['InstanceId']
                
                metadata = instance.get('MetadataOptions', {})
                
                if metadata.get('HttpTokens') != 'required':
                    print(f"Updating {instance_id} to require IMDSv2")
                    
                    try:
                        ec2.modify_instance_metadata_options(
                            InstanceId=instance_id,
                            HttpTokens='required',
                            HttpPutResponseHopLimit=1,
                            HttpEndpoint='enabled'
                        )
                        print(f"Successfully updated {instance_id}")
                    except Exception as e:
                        print(f"Failed to update {instance_id}: {e}")

def get_imdsv1_instances():
    """Find instances still using IMDSv1"""
    imdsv1_instances = []
    
    paginator = ec2.get_paginator('describe_instances')
    
    for page in paginator.paginate():
        for reservation in page['Reservations']:
            for instance in reservation['Instances']:
                metadata = instance.get('MetadataOptions', {})
                
                if metadata.get('HttpTokens') == 'optional':
                    imdsv1_instances.append({
                        'instance_id': instance['InstanceId'],
                        'name': get_instance_name(instance),
                        'state': instance['State']['Name']
                    })
    
    return imdsv1_instances

Application Compatibility

SDK Updates

Ensure applications use IMDSv2-compatible SDK versions:

# Python - boto3 automatically uses IMDSv2 in recent versions
import boto3
from botocore.config import Config

# Configure retry for metadata service
config = Config(
    retries = {
        'max_attempts': 3,
        'mode': 'adaptive'
    }
)

session = boto3.Session()
credentials = session.get_credentials()

Container Configuration

# ECS Task Definition with IMDSv2
{
  "containerDefinitions": [
    {
      "name": "app",
      "image": "my-app:latest",
      "environment": [
        {
          "name": "AWS_EC2_METADATA_SERVICE_ENDPOINT_MODE",
          "value": "IPv4"
        }
      ]
    }
  ],
  "requiresCompatibilities": ["EC2"]
}

Monitoring IMDSv1 Usage

# CloudWatch metric for IMDSv1 usage
aws cloudwatch get-metric-statistics \
  --namespace AWS/EC2 \
  --metric-name MetadataNoToken \
  --dimensions Name=InstanceId,Value=i-1234567890abcdef0 \
  --start-time 2024-01-01T00:00:00Z \
  --end-time 2024-01-02T00:00:00Z \
  --period 3600 \
  --statistics Sum

Working with Warqline

We are a cloud engineering consultancy and an official AWS and Google Cloud partner. If you are running this in production and want a second pair of eyes, we scope work in a free 45-minute technical call: you describe what you are running and what worries you, and we tell you what we would look at first.

Talk to an engineer

Conclusion

IMDSv2 enforcement is critical for EC2 security. Use SCPs for prevention, Config rules for compliance, and monitoring to track adoption across your infrastructure.