@shinyaz

VPC Encryption Controls Hands-On — From Instant Monitor Mode to the Pitfalls of Enforce Migration

Table of Contents

Introduction

On November 21, 2025, AWS announced VPC Encryption Controls — a security feature that lets you audit (monitor) and enforce encryption-in-transit for all traffic within and across VPCs. As of March 1, 2026, the feature transitioned to paid pricing at $0.15/h per non-empty VPC in us-east-1.

VPC Encryption Controls has near-zero cost to enable in monitor mode, but migrating to enforce mode is an asynchronous process that takes time and can fail. The size of this gap varies by workload, making monitor mode the most rational first step — this is the hypothesis we test in this article.

This article verifies both monitor and enforce modes hands-on, showing concrete commands, outputs, and timing at each step. By the end, you'll be able to assess your own environment's encryption posture and decide whether enforce migration is worth the effort. See the official documentation at Enforce VPC encryption in transit.

Prerequisites:

  • AWS CLI configured (EC2, VPC, IAM, CloudWatch Logs permissions)
  • Test region: us-east-1

Skip to Verification 1 if you only want the results.

VPC Encryption Controls Overview

VPC Encryption Controls leverages both AWS Nitro System hardware-level encryption and application-layer encryption (TLS) to control encryption within VPCs. The Nitro System is AWS's custom hardware and software combination that automatically encrypts traffic between supported instances using AES-256-GCM.

VPC Encryption Controls operates in two modes.

ModeBehaviorApplying to existing VPCs
MonitorAdds encryption-status field to Flow Logs for visibilityCan be enabled directly
EnforceBlocks unencrypted traffic and prevents non-compliant resource creationOnly via Monitor mode

encryption-status values:

ValueMeaning
0Not encrypted
1Nitro hardware encryption
2Application-layer encryption (TLS via PrivateLink endpoints)
3Both Nitro and application-layer
-Unknown or Encryption Controls disabled

Pricing is per-VPC hourly. Details in Monitor vs Enforce: Migration Decision Points.

Test Environment Setup

VPC, subnet, security group, and EC2 instance creation steps

The test environment consists of:

  • VPC: 10.100.0.0/16
  • Private subnet: 10.100.2.0/24 (us-east-1a)
  • Security group: HTTP(80), HTTPS(443), SSH(22), ICMP restricted to VPC CIDR
  • SSM VPC endpoints: ssm, ssmmessages, ec2messages
  • Internet Gateway (used in Enforce verification)

VPC, Subnet, and Internet Gateway

Terminal
# VPC
VPC_ID=$(aws ec2 create-vpc \
  --cidr-block 10.100.0.0/16 \
  --tag-specifications 'ResourceType=vpc,Tags=[{Key=Name,Value=vpc-enc-verify}]' \
  --query 'Vpc.VpcId' --output text)
 
# DNS settings (required for VPC endpoint Private DNS)
aws ec2 modify-vpc-attribute --vpc-id $VPC_ID --enable-dns-hostnames '{"Value":true}'
aws ec2 modify-vpc-attribute --vpc-id $VPC_ID --enable-dns-support '{"Value":true}'
 
# Private subnet
PRIVATE_SUBNET_ID=$(aws ec2 create-subnet \
  --vpc-id $VPC_ID --cidr-block 10.100.2.0/24 \
  --availability-zone us-east-1a \
  --tag-specifications 'ResourceType=subnet,Tags=[{Key=Name,Value=enc-verify-private}]' \
  --query 'Subnet.SubnetId' --output text)
 
# Internet Gateway (used in Enforce verification)
IGW_ID=$(aws ec2 create-internet-gateway \
  --tag-specifications 'ResourceType=internet-gateway,Tags=[{Key=Name,Value=enc-verify-igw}]' \
  --query 'InternetGateway.InternetGatewayId' --output text)
aws ec2 attach-internet-gateway --internet-gateway-id $IGW_ID --vpc-id $VPC_ID

Security Group

Terminal
SG_ID=$(aws ec2 create-security-group \
  --group-name enc-verify-sg \
  --description "Allow HTTP/HTTPS/SSH for encryption verification" \
  --vpc-id $VPC_ID \
  --query 'GroupId' --output text)
 
aws ec2 authorize-security-group-ingress \
  --group-id $SG_ID \
  --ip-permissions \
    '[{"IpProtocol":"tcp","FromPort":80,"ToPort":80,"IpRanges":[{"CidrIp":"10.100.0.0/16"}]},
      {"IpProtocol":"tcp","FromPort":443,"ToPort":443,"IpRanges":[{"CidrIp":"10.100.0.0/16"}]},
      {"IpProtocol":"tcp","FromPort":22,"ToPort":22,"IpRanges":[{"CidrIp":"10.100.0.0/16"}]},
      {"IpProtocol":"icmp","FromPort":-1,"ToPort":-1,"IpRanges":[{"CidrIp":"10.100.0.0/16"}]}]'

IAM Roles (SSM + Flow Logs)

Terminal (SSM)
aws iam create-role --role-name enc-verify-ssm-role \
  --assume-role-policy-document '{
    "Version":"2012-10-17",
    "Statement":[{"Effect":"Allow","Principal":{"Service":"ec2.amazonaws.com"},"Action":"sts:AssumeRole"}]
  }'
aws iam attach-role-policy --role-name enc-verify-ssm-role \
  --policy-arn arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
 
aws iam create-instance-profile --instance-profile-name enc-verify-ssm-profile
aws iam add-role-to-instance-profile \
  --instance-profile-name enc-verify-ssm-profile --role-name enc-verify-ssm-role
Terminal (Flow Logs)
aws iam create-role --role-name enc-verify-flow-logs-role \
  --assume-role-policy-document '{
    "Version":"2012-10-17",
    "Statement":[{"Effect":"Allow","Principal":{"Service":"vpc-flow-logs.amazonaws.com"},"Action":"sts:AssumeRole"}]
  }'
aws iam put-role-policy --role-name enc-verify-flow-logs-role \
  --policy-name flow-logs-publish \
  --policy-document '{
    "Version":"2012-10-17",
    "Statement":[{"Effect":"Allow","Action":["logs:CreateLogGroup","logs:CreateLogStream","logs:PutLogEvents","logs:DescribeLogGroups","logs:DescribeLogStreams"],"Resource":"*"}]
  }'

SSM VPC Endpoints

Required for SSM access from private subnet without internet.

Terminal
for SVC in ssm ssmmessages ec2messages; do
  aws ec2 create-vpc-endpoint \
    --vpc-id $VPC_ID \
    --vpc-endpoint-type Interface \
    --service-name com.amazonaws.us-east-1.$SVC \
    --subnet-ids $PRIVATE_SUBNET_ID \
    --security-group-ids $SG_ID \
    --private-dns-enabled
done

EC2 Instance Launch

Terminal (AMI lookup)
ARM_AMI=$(aws ec2 describe-images --owners amazon \
  --filters 'Name=name,Values=al2023-ami-2023.*-arm64' 'Name=state,Values=available' \
  --query 'sort_by(Images,&CreationDate)[-1].ImageId' --output text)
 
X86_AMI=$(aws ec2 describe-images --owners amazon \
  --filters 'Name=name,Values=al2023-ami-2023.*-x86_64' 'Name=state,Values=available' \
  --query 'sort_by(Images,&CreationDate)[-1].ImageId' --output text)
Terminal (launch instances)
# Web server (Nitro, m7g.medium)
aws ec2 run-instances --image-id $ARM_AMI --instance-type m7g.medium \
  --subnet-id $PRIVATE_SUBNET_ID --security-group-ids $SG_ID \
  --iam-instance-profile Name=enc-verify-ssm-profile \
  --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=enc-verify-server-nitro}]'
 
# Client A (Nitro, m7g.medium)
aws ec2 run-instances --image-id $ARM_AMI --instance-type m7g.medium \
  --subnet-id $PRIVATE_SUBNET_ID --security-group-ids $SG_ID \
  --iam-instance-profile Name=enc-verify-ssm-profile \
  --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=enc-verify-client-nitro}]'
 
# Client B (non-Nitro, t2.micro)
aws ec2 run-instances --image-id $X86_AMI --instance-type t2.micro \
  --subnet-id $PRIVATE_SUBNET_ID --security-group-ids $SG_ID \
  --iam-instance-profile Name=enc-verify-ssm-profile \
  --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=enc-verify-client-non-nitro}]'

Wait 2-3 minutes for instances to reach running state and register with SSM. Verify with aws ssm describe-instance-information.

Starting the Web Server

Start HTTP/HTTPS server on the server instance via SSM.

Terminal
SERVER_ID=i-XXXXXXXXX  # Replace with your server instance ID
 
aws ssm send-command --instance-ids $SERVER_ID \
  --document-name AWS-RunShellScript \
  --parameters 'commands=[
    "openssl req -x509 -nodes -days 1 -newkey rsa:2048 -keyout /tmp/server.key -out /tmp/server.crt -subj /CN=enc-verify 2>/dev/null",
    "cat > /tmp/https_server.py << PYEOF\nimport http.server, ssl, threading\ndef run_http():\n    s = http.server.HTTPServer((\"0.0.0.0\", 80), http.server.SimpleHTTPRequestHandler)\n    s.serve_forever()\ndef run_https():\n    s = http.server.HTTPServer((\"0.0.0.0\", 443), http.server.SimpleHTTPRequestHandler)\n    ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)\n    ctx.load_cert_chain(\"/tmp/server.crt\", \"/tmp/server.key\")\n    s.socket = ctx.wrap_socket(s.socket, server_side=True)\n    s.serve_forever()\nthreading.Thread(target=run_http, daemon=True).start()\nrun_https()\nPYEOF",
    "nohup python3 /tmp/https_server.py > /tmp/server.log 2>&1 &",
    "sleep 2 && curl -s -o /dev/null -w %{http_code} http://localhost && curl -sk -o /dev/null -w %{http_code} https://localhost"
  ]'

Output of 200200 confirms both HTTP and HTTPS are working.

Verification 1: Visualizing Encryption Status with Monitor Mode

Enabling Encryption Controls

Terminal
aws ec2 create-vpc-encryption-control \
  --vpc-id vpc-09456d794ff62c982 \
  --tag-specifications 'ResourceType=vpc-encryption-control,Tags=[{Key=Name,Value=enc-verify-control}]'
Output
{
    "VpcEncryptionControl": {
        "VpcEncryptionControlId": "vpcec-070c7cd71ca25ecc8",
        "Mode": "monitor",
        "State": "creating"
    }
}

State transitioned to available in about 1 minute. No impact on existing traffic.

Creating Flow Logs

Create a CloudWatch Logs log group first, then create Flow Logs with a custom format including the encryption-status field.

Terminal
aws logs create-log-group --log-group-name /vpc/enc-verify-flow-logs
 
aws ec2 create-flow-logs \
  --resource-type VPC \
  --resource-ids vpc-09456d794ff62c982 \
  --traffic-type ALL \
  --log-destination-type cloud-watch-logs \
  --log-group-name /vpc/enc-verify-flow-logs \
  --deliver-logs-permission-arn arn:aws:iam::381492023699:role/enc-verify-flow-logs-role \
  --log-format '${flow-direction} ${traffic-path} ${srcaddr} ${dstaddr} ${srcport} ${dstport} ${protocol} ${encryption-status} ${reject-reason}'

Traffic Generation and Results

Four traffic patterns were generated:

PatternClientProtocolExpected encryption-status
ANitro (m7g)HTTP (80)1 (Nitro encryption)
BNitro (m7g)HTTPS (443)3 (Nitro + TLS)
CNon-Nitro (t2)HTTP (80)0 (not encrypted)
DNon-Nitro (t2)HTTPS (443)2 (TLS only)

Results appeared in Flow Logs after about 3 minutes.

Terminal (generate traffic via SSM)
# Nitro client
aws ssm send-command --instance-ids $NITRO_CLIENT_ID \
  --document-name AWS-RunShellScript \
  --parameters 'commands=[
    "for i in $(seq 1 30); do curl -s -o /dev/null http://10.100.2.110; done",
    "for i in $(seq 1 30); do curl -sk -o /dev/null https://10.100.2.110; done"
  ]'
 
# Non-Nitro client
aws ssm send-command --instance-ids $NON_NITRO_CLIENT_ID \
  --document-name AWS-RunShellScript \
  --parameters 'commands=[
    "for i in $(seq 1 30); do curl -s -o /dev/null http://10.100.2.110; done",
    "for i in $(seq 1 30); do curl -sk -o /dev/null https://10.100.2.110; done"
  ]'
Terminal (query Flow Logs)
aws logs filter-log-events \
  --log-group-name /vpc/enc-verify-flow-logs \
  --filter-pattern "10.100.2.110" \
  --limit 50 \
  --query 'events[*].message' --output text
Output (Nitro client → server, HTTP port 80)
egress 1 10.100.2.173 10.100.2.110 56738 80 6 1 -
egress 1 10.100.2.173 10.100.2.110 56750 80 6 1 -

Each field corresponds to the --log-format order: flow-direction / traffic-path / srcaddr / dstaddr / srcport / dstport / protocol (6=TCP) / encryption-status / reject-reason. The second-to-last 1 is the encryption-status, indicating Nitro encryption. The final - is reject-reason (no rejection).

Output (Non-Nitro client → server, HTTP port 80)
ingress - 10.100.2.139 10.100.2.110 45186 80 6 1 -
ingress - 10.100.2.139 10.100.2.110 45210 80 6 1 -

HTTPS (port 443) traffic also showed encryption-status of 1 for both Nitro and non-Nitro clients.

Unexpected Result: All Traffic Shows encryption-status 1

All four patterns returned encryption-status of 1 (Nitro encryption). The expected values of 0, 2, and 3 never appeared.

PatternExpectedActual
A: Nitro (m7g) → HTTP11
B: Nitro (m7g) → HTTPS31
C: Non-Nitro (t2) → HTTP01
D: Non-Nitro (t2) → HTTPS21

The t2.micro returning 1 instead of 0 was particularly unexpected. The AWS blog used t4g.medium and recorded 0. To investigate, we ran an additional test under the exact same conditions as the blog (m7g.medium + t4g.medium, HTTP only).

Additional Test: Blog-Identical Conditions (t4g.medium)

A separate VPC was created with the same instance type combination as the blog. The setup follows the same steps as Verification 1 (VPC + subnet + SSM endpoints + Encryption Controls), with only the instance types changed.

Additional test setup steps

Create VPC, subnet, SG, and SSM endpoints using the same steps as the main setup, then launch two instances:

Terminal
# Server (Nitro v4, m7g.medium)
aws ec2 run-instances --image-id $ARM_AMI --instance-type m7g.medium \
  --subnet-id $PRIVATE_SUBNET_ID --security-group-ids $SG_ID \
  --iam-instance-profile Name=enc-verify-ssm-profile \
  --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=blog-server-m7g}]'
 
# Client (Nitro v2, t4g.medium)
aws ec2 run-instances --image-id $ARM_AMI --instance-type t4g.medium \
  --subnet-id $PRIVATE_SUBNET_ID --security-group-ids $SG_ID \
  --iam-instance-profile Name=enc-verify-ssm-profile \
  --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=blog-client-t4g}]'

Start HTTP server and generate traffic via SSM:

Terminal
# Start HTTP server on server instance
aws ssm send-command --instance-ids $SERVER_ID \
  --document-name AWS-RunShellScript \
  --parameters 'commands=["nohup python3 -m http.server 80 > /tmp/http.log 2>&1 &"]'
 
# Generate traffic from each client
aws ssm send-command --instance-ids $CLIENT_ID \
  --document-name AWS-RunShellScript \
  --parameters 'commands=["for i in $(seq 1 30); do curl -s -o /dev/null http://SERVER_IP; done"]'
ClientInstance TypeNitro Versionencryption-status
m7g.mediumNitro v4 (encryption supported)1
t4g.mediumNitro v2 (encryption not supported)0
Output (t4g.medium → m7g.medium server, HTTP port 80)
egress 1 10.0.128.91 10.0.128.31 55906 80 6 0 -
egress 1 10.0.128.91 10.0.128.31 55772 80 6 0 -
Output (m7g.medium → m7g.medium server, HTTP port 80)
egress 1 10.0.128.22 10.0.128.31 58172 80 6 1 -
egress 1 10.0.128.22 10.0.128.31 58148 80 6 1 -

The blog's results were reproduced. t4g.medium (Nitro v2) traffic shows 0, m7g.medium (Nitro v4) traffic shows 1.

Findings

These two rounds of testing yielded the following findings:

Finding 1: encryption-status depends on Nitro version. Nitro v3 and later (e.g. m7g) support encryption in transit and record 1. Nitro v2 (e.g. t4g) does not support encryption and records 0. This matches the documentation stating that Nitro v3 and later support Encryption in transit.

Finding 2: Why t2.micro (Xen-based, non-Nitro) returned 1 is unknown. While t4g (Nitro v2) returns 0 as expected, t2 (non-Nitro) returned 1 — counterintuitive. get-vpc-resources-blocking-encryption-enforcement also did not flag t2.micro as a blocking resource. The exact cause is unidentified, but internal infrastructure migration after monitor mode activation may be a factor.

Finding 3: encryption-status 2 (application-layer encryption) does not apply to regular HTTPS. Re-reading the documentation confirms that 2 is limited to "TCP port 443 for interface endpoint to AWS service" and "TCP port 443 for gateway endpoint." Self-signed HTTPS between EC2 instances does not register as 2. AWS determines encryption status by endpoint type and port, not by inspecting packet contents.

Verification 2: Enforce Mode Migration and Blocking Behavior

With encryption status now visible via monitor mode, the next step is attempting to enforce encryption by switching to enforce mode.

Identifying Blocking Resources

Before switching to enforce mode, you need to identify resources that block encryption enforcement.

Terminal
aws ec2 get-vpc-resources-blocking-encryption-enforcement \
  --vpc-id vpc-09456d794ff62c982
Output
{
    "NonCompliantResources": [
        {
            "Id": "igw-03f9ce2d3fb1b1155",
            "Type": "internet-gateway",
            "IsExcludable": true
        }
    ]
}

Only the Internet Gateway was flagged. The t2.micro (non-Nitro) was not detected as a blocking resource. Combined with the encryption-status 1 observed in Verification 1, AWS may be internally treating it as encryption-compliant, though the exact reason is unknown.

Switching to Enforce Mode

Switch to enforce mode with an IGW exclusion. Internet Gateways carry traffic outside the AWS network where Nitro encryption cannot apply, so they require an exclusion in enforce mode. Only 8 resource types can be excluded: Internet Gateway, NAT Gateway, Egress-only IGW, VPC Peering, Virtual Private Gateway, Lambda, VPC Lattice, and EFS.

Terminal
aws ec2 modify-vpc-encryption-control \
  --vpc-encryption-control-id vpcec-070c7cd71ca25ecc8 \
  --mode enforce \
  --internet-gateway-exclusion enable

The command returns immediately with State: enforce-in-progress. Enforce mode transition is asynchronous.

enforce-failed After 15 Minutes

Poll the status every 60 seconds:

Terminal (status polling)
while true; do
  STATUS=$(aws ec2 describe-vpc-encryption-controls \
    --vpc-ids $VPC_ID \
    --query 'VpcEncryptionControls[0].[Mode,State]' --output text)
  echo "$(date +%H:%M:%S) - $STATUS"
  echo "$STATUS" | grep -q "available\|failed" && break
  sleep 60
done

The transition failed after approximately 15 minutes:

Output (polling results)
19:44:33 - monitor  enforce-in-progress
19:45:35 - monitor  enforce-in-progress
...
19:58:55 - monitor  enforce-failed

The error message says "Failed due to one or more non-compliant resources," but get-vpc-resources-blocking-encryption-enforcement only shows the IGW — which we already excluded.

Still Fails Without IGW

After detaching the IGW and confirming zero blocking resources, enforce was retried — and failed again after 15 minutes.

Terminal
aws ec2 detach-internet-gateway \
  --internet-gateway-id igw-03f9ce2d3fb1b1155 \
  --vpc-id vpc-09456d794ff62c982
 
aws ec2 get-vpc-resources-blocking-encryption-enforcement \
  --vpc-id vpc-09456d794ff62c982
# → NonCompliantResources: []
 
aws ec2 modify-vpc-encryption-control \
  --vpc-encryption-control-id vpcec-070c7cd71ca25ecc8 \
  --mode enforce
# → enforce-failed again after ~15 minutes

The blocking resources API returns empty, yet enforce fails. The remaining resources in the VPC were EC2 instances (m7g + t2) and 3 SSM VPC endpoint ENIs.

Root Cause Confirmed: VPC Endpoint ENIs

To isolate the cause, a new VPC was created with m7g.medium server + t4g.medium client + 3 SSM VPC endpoints. Enforce failed after ~15 minutes in this environment as well. Next, the SSM VPC endpoints (ssm, ssmmessages, ec2messages) were deleted and enforce was retried.

Terminal
# Delete VPC endpoints
aws ec2 delete-vpc-endpoints \
  --vpc-endpoint-ids $SSM_VPCE_ID $SSMMSG_VPCE_ID $EC2MSG_VPCE_ID
 
# Wait for ENIs to be released (~2 minutes)
# Retry enforce
aws ec2 modify-vpc-encryption-control \
  --vpc-encryption-control-id $VPCEC_ID \
  --mode enforce

It succeeded after approximately 15 minutes.

Output (polling after VPC endpoint deletion)
21:40:32 - monitor  enforce-in-progress
...
21:54:55 - enforce  available

VPC endpoint ENIs were blocking the enforce transition. The documentation states that NLB, ALB, Fargate, and EKS automatically migrate when monitor mode is enabled, but says nothing about VPC endpoint ENI migration. In this test, enforce continued to fail for about 1 hour after monitor mode activation while VPC endpoints were present. It's possible that migration simply takes longer, but get-vpc-resources-blocking-encryption-enforcement does not detect them, making it difficult to determine whether to wait or delete.

Enforce Mode Blocking Behavior

With enforce mode active, launching an encryption-incompatible instance type (t4g.medium) was attempted.

Terminal
aws ec2 run-instances --image-id $ARM_AMI --instance-type t4g.medium \
  --subnet-id $PRIVATE_SUBNET_ID --security-group-ids $SG_ID \
  --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=enforce-test-t4g}]'
Output
An error occurred (DisallowedByVpcEncryptionControl) when calling the RunInstances operation:
Instance type does not support required VPC encryption

t4g.medium (Nitro v2, no encryption support) was blocked with DisallowedByVpcEncryptionControl. Changing only --instance-type m7g.medium in the same command launched successfully.

For production environments using VPC endpoints, enforce migration requires temporarily deleting and recreating VPC endpoints, or waiting for ENI migration to complete — though no API exists to check migration status.

Monitor vs Enforce: Migration Decision Points

Comparison

AspectMonitorEnforce
Activation time~1 minute15+ minutes (async, may fail)
Impact on existing trafficNoneBlocks unencrypted traffic
PrerequisitesNoneIdentify/migrate blocking resources, configure exclusions
Flow Logs encryption-status✓ Available✓ Available
Non-Nitro instance launchAllowedBlocked (DisallowedByVpcEncryptionControl error)
VPC endpoint impactNoneENIs block enforce transition (deletion required)

Pricing

VPC Encryption Controls charges per non-empty VPC per hour.

RegionHourly rateMonthly (730h)
us-east-1$0.15$109.50
eu-west-1 (Ireland)$0.16$116.80
sa-east-1 (São Paulo)$0.31$226.30

For an environment with 10 VPCs, that's $1,095/month. Enabling Transit Gateway encryption support charges all attached VPCs regardless of their encryption control mode.

Whether this is justified depends on compliance requirements. For HIPAA or PCI DSS environments requiring encryption audit trails, compare against the cost of manual encryption audits. Monitor mode alone provides encryption status in Flow Logs, which may be sufficient for audit evidence.

Summary

  • Monitor mode can be deployed immediately — Activation takes about 1 minute with zero impact on existing traffic. Safe for production environments since it only adds encryption-status to Flow Logs
  • encryption-status depends on Nitro version — Nitro v3+ (e.g. m7g) records 1, Nitro v2 (e.g. t4g) records 0. However, t2.micro (non-Nitro) was observed returning 1, so per-instance-type behavior should be verified in your own environment
  • encryption-status 2 is PrivateLink only — Regular HTTPS traffic between EC2 instances does not register as 2. AWS determines status by endpoint type and port, not packet inspection
  • Enforce mode migration is blocked by VPC endpoint ENIs — Enforce failed repeatedly with VPC endpoints present. After deleting VPC endpoints, enforce succeeded in about 15 minutes. get-vpc-resources-blocking-encryption-enforcement does not detect VPC endpoint ENIs, making root cause identification difficult. Environments using VPC endpoints need a migration plan that includes temporary deletion and recreation

Cleanup

Test resource deletion steps
Terminal
# Delete Encryption Controls
aws ec2 delete-vpc-encryption-control \
  --vpc-encryption-control-id vpcec-070c7cd71ca25ecc8
 
# Terminate EC2 instances
aws ec2 terminate-instances \
  --instance-ids i-0ae5ecc423253d10a i-0a103bc51557301f1 i-05598f623192f9fed
 
# Delete VPC endpoints
aws ec2 delete-vpc-endpoints \
  --vpc-endpoint-ids vpce-0af9e55053d66697c vpce-09b65419d5598c9a9 vpce-0e76869f0adbce959
 
# Delete Flow Logs
aws ec2 delete-flow-logs --flow-log-ids fl-020b63347a112dc03
aws logs delete-log-group --log-group-name /vpc/enc-verify-flow-logs
 
# Delete networking resources
aws ec2 delete-security-group --group-id sg-087c5ea932b375258
aws ec2 delete-subnet --subnet-id subnet-0fbfc4d4b0df3b641
aws ec2 delete-internet-gateway --internet-gateway-id igw-03f9ce2d3fb1b1155
aws ec2 delete-vpc --vpc-id vpc-09456d794ff62c982
 
# Delete IAM resources
aws iam delete-role-policy --role-name enc-verify-flow-logs-role --policy-name flow-logs-publish
aws iam delete-role --role-name enc-verify-flow-logs-role
aws iam detach-role-policy --role-name enc-verify-ssm-role --policy-arn arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
aws iam remove-role-from-instance-profile --instance-profile-name enc-verify-ssm-profile --role-name enc-verify-ssm-role
aws iam delete-instance-profile --instance-profile-name enc-verify-ssm-profile
aws iam delete-role --role-name enc-verify-ssm-role

Share this post

Shinya Tahara

Shinya Tahara

Solutions Architect @ AWS

I'm a Solutions Architect at AWS, providing technical guidance primarily to financial industry customers. I share learnings about cloud architecture and AI/ML on this site.The views and opinions expressed on this site are my own and do not represent the official positions of my employer.

Related Posts