VPC Encryption Controls を実機検証 — Monitor モードの即日導入から Enforce 移行の落とし穴まで
目次
はじめに
2025年11月21日、AWS は VPC Encryption Controls を発表した。VPC 内およびVPC 間のトラフィック暗号化状況を監査(monitor)し、暗号化を強制(enforce)できるセキュリティ機能である。詳細は AWS ブログの紹介記事を参照。2026年3月1日からは有料化され、非空 VPC あたり $0.15/h(us-east-1)の課金が発生する。
VPC Encryption Controls は monitor モードの導入コストがほぼゼロである一方、enforce モードへの移行は非同期処理で時間がかかり、失敗する場合もある。このギャップの大きさはワークロードごとに異なるため、まず monitor で現状を把握することが最も合理的な第一歩になる——これが本記事の仮説である。
本記事では monitor/enforce の両モードを実機検証し、各ステップの具体的なコマンド・出力・所要時間を示す。読者は自環境の暗号化状況を棚卸しし、enforce 移行の要否と工数を判断できるようになる。公式ドキュメントは Enforce VPC encryption in transit を参照。
前提条件:
- AWS CLI 設定済み(EC2, VPC, IAM, CloudWatch Logs の権限)
- 検証リージョン: us-east-1
セットアップだけ見たい場合は検証環境のセットアップ、結果だけ見たい場合は検証 1に進んでほしい。
VPC Encryption Controls の概要
VPC Encryption Controls は AWS Nitro System のハードウェアレベル暗号化と、アプリケーション層の暗号化(TLS)の両方を活用して、VPC 内の暗号化を制御する。Nitro System は AWS が設計したハードウェアとソフトウェアの組み合わせで、対応インスタンス間のトラフィックを AES-256-GCM で自動的に暗号化する。
VPC Encryption Controls は 2 つのモードで動作する。
| モード | 動作 | 既存 VPC への適用 |
|---|---|---|
| Monitor | Flow Logs に encryption-status フィールドを追加し、暗号化状況を可視化 | 直接有効化可能 |
| Enforce | 非暗号化トラフィックをブロックし、非対応リソースの作成を防止 | Monitor 経由でのみ移行可能 |
encryption-status は以下の値を取る:
| 値 | 意味 |
|---|---|
| 0 | 非暗号化 |
| 1 | Nitro ハードウェア暗号化 |
| 2 | アプリケーション層暗号化(PrivateLink エンドポイント経由の TLS) |
| 3 | Nitro + アプリケーション層の両方 |
| - | 不明または Encryption Controls 無効 |
料金は VPC あたりの時間課金で、リージョンにより異なる。詳細は Monitor vs Enforce: 移行判断のポイント で考察する。
検証環境のセットアップ
VPC・サブネット・セキュリティグループ・EC2 インスタンスの作成手順
検証環境は以下の構成で構築した。
- VPC:
10.100.0.0/16 - プライベートサブネット:
10.100.2.0/24(us-east-1a) - セキュリティグループ: HTTP(80), HTTPS(443), SSH(22), ICMP を VPC CIDR に限定
- SSM 用 VPC エンドポイント: ssm, ssmmessages, ec2messages
- Internet Gateway(Enforce 検証で使用)
VPC・サブネット・Internet Gateway
# 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 設定(VPC エンドポイントの 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_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(Enforce 検証で使用)
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セキュリティグループ
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 ロール(SSM + Flow Logs)
# 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-roleaws 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 エンドポイント
プライベートサブネットからインターネットアクセスなしで SSM を使うために必要。
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
doneEC2 インスタンスの起動
# ARM (m7g 用)
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 (t2 用)
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)# Web サーバー (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}]'
# クライアント 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}]'
# クライアント B (非 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}]'インスタンスが running になり SSM に登録されるまで約2〜3分待つ。aws ssm describe-instance-information で PingStatus: Online を確認する。
Web サーバーの起動
SSM 経由でサーバーに HTTP/HTTPS サーバーを起動する。
SERVER_ID=i-XXXXXXXXX # サーバーのインスタンス 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"
]'出力が 200200 であれば HTTP/HTTPS 両方が動作している。
検証 1: Monitor モードで暗号化ステータスを可視化する
Encryption Controls の有効化
aws ec2 create-vpc-encryption-control \
--vpc-id vpc-09456d794ff62c982 \
--tag-specifications 'ResourceType=vpc-encryption-control,Tags=[{Key=Name,Value=enc-verify-control}]'{
"VpcEncryptionControl": {
"VpcId": "vpc-09456d794ff62c982",
"VpcEncryptionControlId": "vpcec-070c7cd71ca25ecc8",
"Mode": "monitor",
"State": "creating",
"StateMessage": "creating"
}
}約1分後に State が available に遷移した。既存のトラフィックへの影響はなく、即座に有効化できる。
Flow Logs の作成
encryption-status フィールドを含むカスタムフォーマットで Flow Logs を作成する。CloudWatch Logs のロググループを先に作成しておく。
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}'トラフィック生成と結果
4 パターンのトラフィックを生成した。
| パターン | クライアント | プロトコル | 期待する encryption-status |
|---|---|---|---|
| A | Nitro (m7g) | HTTP (80) | 1 (Nitro 暗号化) |
| B | Nitro (m7g) | HTTPS (443) | 3 (Nitro + TLS) |
| C | 非 Nitro (t2) | HTTP (80) | 0 (非暗号化) |
| D | 非 Nitro (t2) | HTTPS (443) | 2 (TLS のみ) |
SSM 経由で各クライアントから HTTP(port 80)と HTTPS(port 443)のリクエストを送信した。
# Nitro クライアント
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"
]'
# 非 Nitro クライアント
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"
]'約3分後に 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 textegress 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 -
egress 1 10.100.2.173 10.100.2.110 56684 80 6 1 -各フィールドは --log-format で指定した順に対応する: flow-direction / traffic-path / srcaddr / dstaddr / srcport / dstport / protocol(6=TCP)/ encryption-status / reject-reason。末尾から2番目の 1 が encryption-status で、Nitro 暗号化を示す。最後の - は reject-reason(拒否なし)。
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 -
ingress - 10.100.2.139 10.100.2.110 45280 80 6 1 -HTTPS(port 443)のトラフィックも、Nitro・非 Nitro ともに全て encryption-status が 1 だった。
予想外の結果: 全トラフィックが encryption-status 1
4 パターン全てで encryption-status が 1(Nitro 暗号化)だった。期待していた 0、2、3 は一切出現しなかった。
| パターン | 期待値 | 実測値 |
|---|---|---|
| A: Nitro (m7g) → HTTP | 1 | 1 ✓ |
| B: Nitro (m7g) → HTTPS | 3 | 1 ✗ |
| C: 非 Nitro (t2) → HTTP | 0 | 1 ✗ |
| D: 非 Nitro (t2) → HTTPS | 2 | 1 ✗ |
t2.micro からのトラフィックが 0 ではなく 1 だった点が特に予想外だった。AWS ブログでは t4g.medium を使って 0 が記録されている。そこで、ブログと同条件(m7g.medium + t4g.medium、HTTP のみ)で追加検証を行った。
追加検証: ブログと同条件(t4g.medium)での再テスト
別の VPC を作成し、ブログと同じインスタンスタイプの組み合わせで検証した。セットアップ手順は検証 1 と同様(VPC + サブネット + SSM エンドポイント + Encryption Controls)で、インスタンスタイプのみ変更している。
追加検証のセットアップ手順
検証 1 のセットアップと同じ手順で VPC・サブネット・SG・SSM エンドポイントを作成し、以下の2台を起動する。
# サーバー (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}]'
# クライアント (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}]'サーバーに HTTP サーバーを起動し、両クライアントから HTTP リクエストを送信する。
# サーバーで HTTP サーバー起動
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 &"]'
# 各クライアントからトラフィック生成
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"]'| クライアント | インスタンスタイプ | Nitro バージョン | encryption-status |
|---|---|---|---|
| m7g.medium | Nitro v4(暗号化対応) | 1 | |
| t4g.medium | Nitro v2(暗号化非対応) | 0 |
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 -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 -ブログの結果が再現された。t4g.medium(Nitro v2)からのトラフィックは 0、m7g.medium(Nitro v4)からのトラフィックは 1 である。
知見の整理
この 2 回の検証から、以下の知見が得られた。
知見 1: encryption-status は Nitro バージョンに依存する。 Nitro v3 以降(m7g 等)は暗号化対応で 1 が記録される。Nitro v2(t4g 等)は暗号化非対応で 0 が記録される。これはドキュメントの「Nitro v3 以降が Encryption in transit をサポート」という記載と一致する。
知見 2: t2.micro(Xen ベース、非 Nitro)が 1 を返した理由は不明。 t4g(Nitro v2)が 0 を返す一方で、t2(非 Nitro)が 1 を返すのは直感に反する。get-vpc-resources-blocking-encryption-enforcement でも t2.micro はブロッキングリソースとして検出されなかった。正確な原因は特定できていないが、monitor モード有効化後の内部的なインフラマイグレーションが影響している可能性がある。
知見 3: encryption-status 2(アプリケーション層暗号化)は一般的な HTTPS では記録されない。 ドキュメントを再確認すると、2 は「TCP port 443 for interface endpoint to AWS service」と「TCP port 443 for gateway endpoint」に限定されている。つまり、PrivateLink 経由の AWS サービスアクセスのみが 2 として記録され、EC2 インスタンス間の自己署名証明書による HTTPS は 2 にならない。AWS はパケットの中身を検査せず、ポートとエンドポイントの種類で判定している。
検証 2: Enforce モードへの移行とブロック挙動の確認
Monitor モードで暗号化状況を可視化できた。次のステップとして、暗号化を強制する enforce モードへの移行を試みる。
ブロッキングリソースの特定
Enforce モードに移行する前に、暗号化をブロックしているリソースを特定する必要がある。
aws ec2 get-vpc-resources-blocking-encryption-enforcement \
--vpc-id vpc-09456d794ff62c982{
"NonCompliantResources": [
{
"Id": "igw-03f9ce2d3fb1b1155",
"Type": "internet-gateway",
"IsExcludable": true
}
]
}ブロッキングリソースは Internet Gateway のみだった。t2.micro(非 Nitro)はブロッキングリソースとして検出されなかった。検証 1 で t2.micro のトラフィックが encryption-status 1 で記録されていた点と合わせると、AWS が内部的に暗号化対応として扱っている可能性があるが、正確な理由は不明である。
Enforce モードへの切り替え
IGW の除外を設定しつつ enforce モードに切り替える。Internet Gateway はトラフィックが AWS ネットワーク外に出るため Nitro 暗号化の対象外であり、enforce モードでは除外設定(Exclusion)が必要になる。除外可能なリソースは IGW、NAT Gateway、Egress-only IGW、VPC Peering、Virtual Private Gateway、Lambda、VPC Lattice、EFS の 8 種類に限定されている。
aws ec2 modify-vpc-encryption-control \
--vpc-encryption-control-id vpcec-070c7cd71ca25ecc8 \
--mode enforce \
--internet-gateway-exclusion enable{
"VpcEncryptionControl": {
"Mode": "monitor",
"State": "enforce-in-progress",
"StateMessage": "modifying",
"ResourceExclusions": {
"InternetGateway": {
"State": "enabling",
"StateMessage": "in-progress"
}
}
}
}注目すべきは、コマンド実行直後の Mode がまだ monitor で、State が enforce-in-progress である点だ。enforce への切り替えは非同期処理であり、即座には完了しない。
15分後に enforce-failed
以下のコマンドで60秒ごとにステータスをポーリングした。
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約15分後に enforce-failed となった。
19:44:33 - monitor enforce-in-progress
19:45:35 - monitor enforce-in-progress
...(中略)...
19:58:55 - monitor enforce-failedFailed due to one or more non-compliant resources.
Use GetVpcResourcesBlockingEncryptionEnforcement to discover the blocking resources.しかし、get-vpc-resources-blocking-encryption-enforcement を再実行しても IGW のみが表示される。IGW の除外は指定済みなのに失敗している。
IGW をデタッチしても失敗
IGW をデタッチしてブロッキングリソースをゼロにした状態で再試行したが、再び約15分後に enforce-failed となった。
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
# → 再び約15分後に enforce-failedブロッキングリソース API が空を返すにもかかわらず enforce が失敗する。VPC 内に残っているリソースは EC2 インスタンス(m7g + t2)と SSM 用 VPC エンドポイント 3 つの ENI だった。
原因の特定: VPC エンドポイントの ENI
原因を切り分けるため、新しい VPC を作成し、m7g.medium サーバー + t4g.medium クライアント + SSM 用 VPC エンドポイント 3 つの構成で enforce を試みた。同様に約15分で失敗した。次に、SSM 用の VPC エンドポイント(ssm, ssmmessages, ec2messages)を削除した状態で enforce を再試行した。
# VPC エンドポイントの削除
aws ec2 delete-vpc-endpoints \
--vpc-endpoint-ids $SSM_VPCE_ID $SSMMSG_VPCE_ID $EC2MSG_VPCE_ID
# ENI が解放されるまで待機(約2分)
# VPC エンドポイント削除後に enforce を再試行
aws ec2 modify-vpc-encryption-control \
--vpc-encryption-control-id $VPCEC_ID \
--mode enforce約15分後に enforce / available に遷移し、enforce モードへの移行が成功した。
21:40:32 - monitor enforce-in-progress
...(中略)...
21:54:55 - enforce availableつまり、VPC エンドポイントの ENI が enforce 移行をブロックしていた。ドキュメントには「monitor モードを有効化すると、NLB、ALB、Fargate、EKS が自動的に暗号化対応ハードウェアにマイグレーションされる」と記載されているが、VPC エンドポイントの ENI についてはマイグレーションに関する記載がない。本検証では monitor モード有効化から約1時間後でも VPC エンドポイントが存在する状態では enforce が失敗し続けた。マイグレーションにさらに時間がかかる可能性もあるが、get-vpc-resources-blocking-encryption-enforcement でも検出されないため、待つべきか削除すべきかの判断が困難である。
Enforce モードでのブロック挙動
Enforce モードが有効な VPC で、暗号化非対応のインスタンスタイプ(t4g.medium)の起動を試みた。
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}]'An error occurred (DisallowedByVpcEncryptionControl) when calling the RunInstances operation:
Instance type does not support required VPC encryptiont4g.medium(Nitro v2、暗号化非対応)の起動は DisallowedByVpcEncryptionControl エラーでブロックされた。一方、m7g.medium(Nitro v4、暗号化対応)は同じコマンドで --instance-type m7g.medium に変更するだけで正常に起動できた。
これは実運用で重要な知見である。VPC エンドポイントを使用している環境で enforce モードに移行するには、一時的に VPC エンドポイントを削除するか、VPC エンドポイントの ENI マイグレーションが完了するまで待つ必要がある。ただし、マイグレーション完了のタイミングを確認する API は提供されていない。
Monitor vs Enforce: 移行判断のポイント
比較テーブル
| 観点 | Monitor | Enforce |
|---|---|---|
| 有効化の所要時間 | 約1分 | 15分以上(非同期、失敗の可能性あり) |
| 既存トラフィックへの影響 | なし | 非暗号化トラフィックがブロックされる |
| 必要な事前準備 | なし | ブロッキングリソースの特定・移行・除外設定 |
| Flow Logs の encryption-status | ✓ 利用可能 | ✓ 利用可能 |
| 非 Nitro インスタンスの起動 | 可能 | ブロックされる(DisallowedByVpcEncryptionControl エラー) |
| VPC エンドポイントの影響 | なし | ENI が enforce 移行をブロックする(削除が必要) |
料金の考察
VPC Encryption Controls の料金は非空 VPC あたりの時間課金である。
| リージョン | 時間単価 | 月額(730h) |
|---|---|---|
| us-east-1 | $0.15 | $109.50 |
| ap-northeast-1 (東京) | 料金表に未掲載 | — |
| eu-west-1 (アイルランド) | $0.16 | $116.80 |
VPC が 10 個ある環境では月額 $1,095 になる。Transit Gateway の暗号化サポートを有効化すると、TGW に接続された全 VPC に課金が発生する点にも注意が必要である。
この料金が妥当かどうかは、コンプライアンス要件の厳しさによる。HIPAA や PCI DSS で暗号化証跡が求められる環境では、手動での暗号化監査コスト(人件費 + ツール費)と比較して判断すべきである。monitor モードだけでも Flow Logs に暗号化ステータスが記録されるため、監査証跡としての価値は十分にある。
まとめ
- Monitor モードは即日導入できる — 有効化は約1分、既存トラフィックへの影響ゼロ。Flow Logs に
encryption-statusが追加されるだけなので、本番環境でも安全に有効化できる - encryption-status は Nitro バージョンに依存する — Nitro v3 以降(m7g 等)は
1、Nitro v2(t4g 等)は0が記録される。ただし t2.micro(非 Nitro)が1を返すケースも確認されており、インスタンスタイプごとの挙動は自環境で実測すべきである - encryption-status 2 は PrivateLink 経由のみ — 一般的な HTTPS トラフィックは
2にならない。AWS はパケットの中身ではなく、エンドポイントの種類とポートで判定している - Enforce モードへの移行は VPC エンドポイントの ENI にブロックされる — VPC エンドポイントが存在する状態では enforce が失敗し続けた。VPC エンドポイントを削除した状態では約15分で enforce が成功した。
get-vpc-resources-blocking-encryption-enforcementでは VPC エンドポイントの ENI は検出されないため、原因の特定が困難である。VPC エンドポイントを使用する環境では、一時的な削除・再作成を含む移行計画が必要になる
クリーンアップ
検証リソースの削除手順
# Encryption Controls の削除
aws ec2 delete-vpc-encryption-control \
--vpc-encryption-control-id vpcec-070c7cd71ca25ecc8
# EC2 インスタンスの終了
aws ec2 terminate-instances \
--instance-ids i-0ae5ecc423253d10a i-0a103bc51557301f1 i-05598f623192f9fed
# VPC エンドポイントの削除
aws ec2 delete-vpc-endpoints \
--vpc-endpoint-ids vpce-0af9e55053d66697c vpce-09b65419d5598c9a9 vpce-0e76869f0adbce959
# 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
# セキュリティグループ・サブネット・VPC の削除
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
# IAM リソースの削除
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