上級 35分 Lesson 23

EC2セキュリティ — IMDSv2・Nitro Enclaves・Image Builder

IMDSv2によるSSRF対策、Nitro Enclaves、EC2 Instance Connect、AMI暗号化を徹底解説

AWS EC2 SCS-C03 IMDS Nitro Security

はじめに

EC2セキュリティはAWS SCS-C03試験の要であり、特にインスタンスメタデータサービス(IMDS)、キーペア管理、Nitro Enclaves、AMI暗号化の4領域が重要です。

本講では、IMDSv1の脆弱性からIMDSv2への移行、SSRF攻撃対策、Nitro Enclavesの暗号化処理、そしてEC2 Image Builderによるセキュアなイメージ構築まで、試験を突破し本番運用に耐えるセキュリティ実装を解説します。

IMDSv1 vs v2 SSRF 攻撃対策

Loading diagram...

EC2 Image Builder パイプライン(セキュアなAMI構築)

Loading diagram...


1. インスタンスメタデータサービス(IMDS)

1.1 IMDSとは

EC2インスタンスは起動時に自動的にメタデータサーバー(169.254.169.254:80)にアクセスでき、以下を取得できます:

  • インスタンスID・タイプ・AZ
  • IAMロールの一時的認証情報(AccessKey、SecretKey、Token)
  • ネットワークインターフェース情報
  • ユーザーデータ
  • SSH公開鍵

メタデータへのアクセス経路:

# コマンドラインからのアクセス例
curl http://169.254.169.254/latest/meta-data/

# IAMロールの認証情報取得(危険)
curl http://169.254.169.254/latest/meta-data/iam/security-credentials/MyRole

# レスポンス例
{
  "Code" : "Success",
  "LastUpdated" : "2026-04-27T10:30:00Z",
  "Type" : "AWS-HMAC",
  "AccessKeyId" : "AKIA...",
  "SecretAccessKey" : "...",
  "Token" : "...",
  "Expiration" : "2026-04-27T11:30:00Z"
}

1.2 IMDSv1の脆弱性(SSRF攻撃ベクトル)

IMDSv1はHTTP GETリクエストを無条件に受け入れ、認証なしにメタデータを返すという設計上の欠陥があります。

SSRF(Server-Side Request Forgery)攻撃パターン:

# インスタンス内の脆弱なWebアプリが任意のURLにリクエストを送信可能な場合
# 攻撃者は以下をリクエストさせることで認証情報を盗むことができる
curl http://169.254.169.254/latest/meta-data/iam/security-credentials/MyRole

# また、インスタンスのローカルプロセスがメタデータに直接アクセスして
# 意図せず認証情報を漏洩することもある

攻撃シナリオ:

  1. SSRF脆弱性を持つアプリを入れてしまった → 攻撃者が「任意のURLをfetch」する処理をトリガー → メタデータからIAM認証情報を盗用 → AWS APIへの不正アクセス

  2. コンテナブレイクアウト → コンテナ内の攻撃者がホストのメタデータにアクセス → インスタンスロールの認証情報を奪取

1.3 IMDSv2の仕組み(トークン必須化)

IMDSv2では2段階認証が必須になり、SSRF耐性が大幅に向上します。

項目IMDSv1IMDSv2
認証方式認証なし(オープン)トークン方式(PUT→GET)
リクエスト方法直接GETPUTでトークン取得→GETで使用
デフォルトv1のみ有効v2推奨・v1廃止予定
TTL(トークン有効期間)N/A21秒(設定可能1-21秒)
ホップリミットN/A1(デフォルト・設定可能)
SSRF対策✗ なし✓ あり(トークン取得に手順)

IMDSv2の フロー:

# ステップ 1: トークン取得(PUT)
TOKEN=$(curl -X PUT "http://169.254.169.254/latest/api/token" \
  -H "X-aws-ec2-metadata-token-ttl-seconds: 21")

# ステップ 2: トークンを使ってメタデータ取得(GET)
curl -H "X-aws-ec2-metadata-token: $TOKEN" \
  http://169.254.169.254/latest/meta-data/iam/security-credentials/MyRole

# IMDSv1の方式(v2対応前の古いコード)
curl http://169.254.169.254/latest/meta-data/iam/security-credentials/MyRole

SSRF攻撃時の動作の違い:

# IMDSv1: 直接リクエスト1行で認証情報が返される
GET http://169.254.169.254/latest/meta-data/iam/security-credentials/MyRole

# IMDSv2: トークン取得にはPUTメソッドが必要
# → SSRF脆弱性がGETのみしか許さない場合、トークンが取得できない
# → メタデータへのアクセスが失敗する

1.4 IMDSv2の強制(HttpTokens: required)

ベストプラクティスは全インスタンスでIMDSv2を強制することです。

インスタンスレベルでの設定:

# AWS CLIで既存インスタンスを修正
aws ec2 modify-instance-metadata-options \
  --instance-id i-1234567890abcdef0 \
  --http-tokens required \
  --http-put-response-hop-limit 1 \
  --region us-east-1

# レスポンス確認
aws ec2 describe-instances \
  --instance-ids i-1234567890abcdef0 \
  --query 'Reservations[0].Instances[0].MetadataOptions'

# 出力例
{
  "State": "pending",
  "HttpTokens": "required",
  "HttpPutResponseHopLimit": 1,
  "HttpEndpoint": "enabled"
}

CloudFormationでの定義:

AWSTemplateFormatVersion: "2010-09-09"
Resources:
  MyEC2Instance:
    Type: AWS::EC2::Instance
    Properties:
      ImageId: ami-0c55b159cbfafe1f0
      InstanceType: t3.micro
      MetadataOptions:
        HttpTokens: required          # IMDSv2 強制
        HttpPutResponseHopLimit: 1    # ホップリミット1に設定
        HttpEndpoint: enabled         # メタデータエンドポイント有効
      IamInstanceProfile: MyInstanceProfile

Terraformでの定義:

resource "aws_instance" "secure" {
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = "t3.micro"
  
  metadata_options {
    http_endpoint               = "enabled"
    http_tokens                 = "required"  # IMDSv2強制
    http_put_response_hop_limit = 1
  }
  
  iam_instance_profile = aws_iam_instance_profile.ec2_profile.name
}

アカウントレベルでの一括設定:

# AWS Account 全体のデフォルトをIMDSv2に設定
aws ec2 modify-instance-metadata-defaults \
  --http-tokens required \
  --http-put-response-hop-limit 1 \
  --region us-east-1

# 設定確認
aws ec2 get-instance-metadata-defaults --region us-east-1

1.5 SSRF攻撃対策としてのIMDSv2の重要性

なぜトークン方式でSSRF対策になるのか:

  1. トークン取得(PUT)とメタデータ取得(GET)が分離される

    • 単純なGET SSRFではメタデータにアクセスできない
  2. ホップリミット(HTTP_PUT_RESPONSE_HOP_LIMIT)で防御層を追加

    • デフォルト1 → インスタンスローカルのプロセスからのみアクセス可能
    • コンテナやVM内の攻撃者がホストメタデータにアクセスする難度が上がる

実装例:IAM認証情報取得の安全化

# IMDSv2対応SDK(AWS SDK v3)は自動的にv2を使用
import boto3
from botocore.compat import requests

# 新しいコード(推奨)
session = boto3.Session()
sts = session.client('sts')
caller = sts.get_caller_identity()
print(f"Account: {caller['Account']}")  # メタデータから自動取得

# 古いコード(v1依存)- こちらは避ける
# 直接http://169.254.169.254にアクセスする実装は危険

2. キーペア管理と認証方式の選択肢

2.1 従来のSSHキーペアの問題点

従来のEC2キーペア(.pem/.ppk)には以下の問題があります:

  • キーローテーションが複雑

    • 新キーペアに置き換える際、全インスタンスの authorized_keys を手動更新
    • ダウンタイムが発生する可能性
  • キーの紛失・漏洩リスク

    • ローカルに保存されたキーが盗まれると、無限にアクセス可能
    • アクセス権限を細かく制御できない
  • 監査ログが不十分

    • 誰がいつアクセスしたか、AWSのネイティブログで追跡しにくい

2.2 EC2 Instance Connect(短命キー方式)

EC2 Instance Connect は IAM権限をベースに一時的なSSH公開鍵を自動生成する方式です。

特徴:

項目説明
認証ベースIAM権限(EC2InstanceConnect)
キーの有効期限60秒(固定・変更不可)
トランスポートSSH over HTTPS(443番)
ホストキー検証EC2InstanceConnect APIがホスト鍵を検証
監査ログCloudTrail(API呼び出し)+syslog(接続ログ)
対応OSEC2 Instances(AL2、Ubuntu 16.04+)

使用方法:

# 前提条件1: インスタンスにIAMロール付与
# ロールには "ec2-instance-connect:SendSSHPublicKey" が必要

# 前提条件2: Security Group で HTTPS (443) と SSH (22) を許可
aws ec2 authorize-security-group-ingress \
  --group-id sg-xxxxxxxxx \
  --protocol tcp \
  --port 22 \
  --cidr 0.0.0.0/0

# EC2 Instance Connect で接続
aws ec2-instance-connect send-ssh-public-key \
  --instance-id i-1234567890abcdef0 \
  --instance-os-user ec2-user \
  --ssh-public-key file://~/.ssh/my-key.pub \
  --availability-zone us-east-1a

# その後、通常のSSH接続
ssh -i ~/.ssh/my-key ec2-user@<instance-public-ip>

CloudFormationでの権限設定:

MyEC2ConnectRole:
  Type: AWS::IAM::Role
  Properties:
    AssumeRolePolicyDocument:
      Statement:
        - Effect: Allow
          Principal:
            Service: ec2.amazonaws.com
          Action: sts:AssumeRole
    ManagedPolicyArns:
      - arn:aws:iam::aws:policy/AmazonEC2InstanceConnectRoleForEC2Instance

MyInstanceProfile:
  Type: AWS::IAM::InstanceProfile
  Properties:
    Roles:
      - !Ref MyEC2ConnectRole

MyInstance:
  Type: AWS::EC2::Instance
  Properties:
    ImageId: ami-0885b1f6bd170450c  # Ubuntu or AL2
    InstanceType: t3.micro
    IamInstanceProfile: !Ref MyInstanceProfile
    SecurityGroupIds:
      - !Ref SSHSecurityGroup

SSHSecurityGroup:
  Type: AWS::EC2::SecurityGroup
  Properties:
    GroupDescription: Allow SSH
    SecurityGroupIngress:
      - IpProtocol: tcp
        FromPort: 22
        ToPort: 22
        CidrIp: 0.0.0.0/0

セキュリティ上の利点:

# CloudTrailログで接続を追跡
import boto3
import json

logs = boto3.client('logs')

# ログクエリ例:EC2InstanceConnect使用履歴
response = logs.start_query(
    logGroupName='/aws/cloudtrail/my-trail',
    startTime=int((datetime.now() - timedelta(days=1)).timestamp()),
    endTime=int(datetime.now().timestamp()),
    queryString='''
      fields @timestamp, userIdentity.principalId, eventName, requestParameters
      | filter eventName = "SendSSHPublicKey"
      | stats count() by userIdentity.principalId
    '''
)

2.3 Systems Manager Session Manager

Session Manager はSSHキーを完全に排除し、IAMのみで接続を制御します。

特徴:

項目説明
認証ベースIAM権限(ssm:StartSession)
キーレスSSH秘密鍵が一切不要
トランスポートHTTPS(Systems Manager Endpoint経由)
監査ログCloudTrail + Session Manager ログ(S3)
リバースシェルインスタンスがAWS APIに接続(インバウンド不要)
ポートフォワーディングRDS・ElastiCacheへの直接接続も可能

セットアップ:

# ステップ1: インスタンスロールにポリシー追加
aws iam attach-role-policy \
  --role-name MyEC2Role \
  --policy-arn arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore

# ステップ2: Systems Manager Agent がインスタンスで実行中か確認
# (最新のAL2/UbuntuはデフォルトでSSM Agent が動作)

# ステップ3: インスタンスのネットワーク設定確認
# - VPC Endpoint for Systems Manager が必要(プライベートインスタンス)
# またはNAT Gateway 経由でインターネット接続

# ステップ4: 接続テスト
aws ssm start-session --target i-1234567890abcdef0

CloudFormationでのセットアップ:

MyEC2Role:
  Type: AWS::IAM::Role
  Properties:
    AssumeRolePolicyDocument:
      Statement:
        - Effect: Allow
          Principal:
            Service: ec2.amazonaws.com
          Action: sts:AssumeRole
    ManagedPolicyArns:
      - arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore

MyInstanceProfile:
  Type: AWS::IAM::InstanceProfile
  Properties:
    Roles:
      - !Ref MyEC2Role

MyInstance:
  Type: AWS::EC2::Instance
  Properties:
    ImageId: ami-0885b1f6bd170450c
    InstanceType: t3.micro
    IamInstanceProfile: !Ref MyInstanceProfile
    # セキュリティグループ: インバウンドルール不要
    SecurityGroupIds:
      - !Ref NoInboundSecurityGroup

NoInboundSecurityGroup:
  Type: AWS::EC2::SecurityGroup
  Properties:
    GroupDescription: No inbound rules (Session Manager only)
    SecurityGroupEgress:
      - IpProtocol: -1
        CidrIp: 0.0.0.0/0

Session Manager ログの記録:

# S3へのセッションログ記録を有効化
aws ssm update-document-default-version \
  --name "AWS-StartInteractiveCommand" \
  --document-version "\$LATEST"

# セッション設定ファイル (prefs.json)
{
  "s3BucketName": "my-session-logs-bucket",
  "s3KeyPrefix": "session-logs/",
  "s3EncryptionEnabled": true,
  "cloudWatchLogGroupName": "/aws/ssm/session-logs",
  "cloudWatchEncryptionEnabled": true,
  "idleSessionTimeout": "20",
  "maxSessionDuration": "60",
  "kmsKeyId": "arn:aws:kms:us-east-1:123456789012:key/12345678-1234-1234-1234-123456789012",
  "runAsEnabled": false,
  "runAsDefaultUser": ""
}

2.4 EC2 Instance Connect Endpoint

パブリックIPを持たないプライベートインスタンスから安全にアクセスするための仲介役です。

図解:

┌─────────────────────────────────────────────────────────┐
│ インターネット側                                        │
│ ┌─────────────┐                                         │
│ │ クライアント │                                        │
│ │(ローカルPC)  │                                       │
│ └──────┬──────┘                                         │
└───────┼────────────────────────────────────────────────┘
        │ SSH/HTTPS (443)

┌───────┼────────────────────────────────────────────────┐
│ AWS VPC                                                 │
│ ┌───────▼───────────────────────────────────────────┐ │
│ │ EC2 Instance Connect Endpoint                   │ │
│ │ (パブリックサブネット)                        │ │
│ └──────────┬──────────────────────────────────────┘ │
│            │ SSH (22) / RDP (3389)                  │
│ ┌──────────▼──────────────────────────────────────┐ │
│ │ Private EC2 インスタンス                        │ │
│ │ (プライベートサブネット)                      │ │
│ └───────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘

セットアップ:

# ステップ1: EC2 Instance Connect Endpoint を作成
aws ec2 create-instance-connect-endpoint \
  --subnet-id subnet-0123456789abcdef0 \
  --security-group-ids sg-xxxxxxxxx \
  --tag-specifications 'ResourceType=instance-connect-endpoint,Tags=[{Key=Name,Value=MyEndpoint}]'

# ステップ2: Endpoint IDを取得
ENDPOINT_ID=$(aws ec2 describe-instance-connect-endpoints \
  --filters Name=tag:Name,Values=MyEndpoint \
  --query 'InstanceConnectEndpoints[0].InstanceConnectEndpointId' \
  --output text)

# ステップ3: プライベートインスタンスに接続
aws ec2-instance-connect open-tunnel \
  --instance-connect-endpoint-id $ENDPOINT_ID \
  --instance-id i-0987654321fedcba0 \
  --local-port 8888

# ステップ4: 別ターミナルでSSH接続
ssh -i my-key.pem -p 8888 ec2-user@localhost

3. Nitro Enclaves(機密コンピューティング環境)

3.1 Nitro Enclaveとは

Nitro Enclave は EC2インスタンス内で分離されたコンピューティング環境を作り、メモリ・CPU・ストレージが暗号化・隔離される仕組みです。

ユースケース:

  • PII(個人識別情報)処理 → 顧客の機密データを安全に処理

  • 暗号化キー管理 → KMSキーをEnclave内でのみ使用、外に漏らさない

  • マルチパーティ計算 → 複数企業のデータを結合分析、各企業のデータは秘密のまま

  • 決済処理 → クレジットカード情報の復号・処理をEnclave内で完結

アーキテクチャ:

Nitro Enclaveの隔離構造

3.2 Attestation(証明書検証)

Attestation は「このEnclave が正しい構成で動作している」ことを証明するメカニズムです。

流れ:

  1. Enclave内のアプリケーションが起動時にAttestation Documentを生成
  2. KMS or カスタムサービスがドキュメントを検証
  3. 検証成功時のみ、Enclaveに対してデータ/キーを提供

CloudFormationでのEnclave起動:

MyEnclaveInstance:
  Type: AWS::EC2::Instance
  Properties:
    ImageId: ami-0c55b159cbfafe1f0  # Nitro対応イメージ
    InstanceType: m5.large
    EnclaveOptions:
      Enabled: true  # Nitro Enclave 有効化
    BlockDeviceMappings:
      - DeviceName: /dev/xvda
        Ebs:
          VolumeSize: 30
          VolumeType: gp3
          Encrypted: true
          KmsKeyId: !GetAtt MyKMSKey.Arn

Attestation Documentの検証例(Python):

import json
import base64
import requests
from cryptography import x509
from cryptography.hazmat.backends import default_backend

# Enclave内のアプリケーション側: Attestation Document 生成
def get_attestation_document():
    """
    Nitro Enclave内のprocfs経由でAttestation Documentを取得
    """
    with open('/dev/attestation/attestation.crt', 'rb') as f:
        attestation_doc = f.read()
    return attestation_doc

# Parent インスタンス or 外部サービス側: 検証
def verify_attestation(attestation_doc, pcr_values):
    """
    Attestation Document を検証
    
    Args:
        attestation_doc: Enclave が生成した証明書
        pcr_values: 期待される PCR (Platform Configuration Register) 値
    
    Returns:
        bool: 検証成功か否か
    """
    # 証明書をデコード
    cert = x509.load_pem_x509_certificate(
        attestation_doc,
        default_backend()
    )
    
    # ドキュメントの署名を検証(AWS Nitro親鍵で検証)
    # PCR値(ファームウェア・Enclaveイメージのハッシュ)が一致するか確認
    doc_data = json.loads(cert.public_bytes(encoding=x509.Encoding.PEM))
    
    for i, expected_pcr in enumerate(pcr_values):
        actual_pcr = doc_data['document']['pcr'][i]
        if actual_pcr != expected_pcr:
            return False
    
    return True

3.3 KMS 連携(Enclave内でのみ復号)

KMS と Enclave を組み合わせることで、Enclave内でのみデータを復号可能にできます。

シナリオ:

1. ユーザーがクレジットカード情報を提出
2. 親インスタンスがKMSで暗号化して保存
3. Enclaveがユーザー認証を実施
4. Enclave がKMSにリクエスト「このEnclaveが正当か検証してくれ」
5. KMS が Attestation を確認し、特別なポリシー付きキーをEnclave内でのみ復号可能な形で返す
6. Enclave がカード情報を復号・処理
7. 処理後、親インスタンスには復号済みデータが戻らない

KMS キーポリシーの例:

{
  "Sid": "AllowNitroEnclaveOnly",
  "Effect": "Allow",
  "Principal": {
    "AWS": "arn:aws:iam::123456789012:role/MyEC2Role"
  },
  "Action": "kms:Decrypt",
  "Resource": "*",
  "Condition": {
    "StringEquals": {
      "kms:RecipientAttestation:ImageSha384": "aaaabbbbccccdddd..."
    }
  }
}

KMS DecryptWithEnclave呼び出し(Enclave内):

import boto3
import json

kms = boto3.client('kms')

def decrypt_in_enclave(encrypted_data, pcr_claim):
    """
    Enclave 内での復号(Attestation検証付き)
    """
    response = kms.decrypt(
        CiphertextBlob=encrypted_data,
        EncryptionContext={
            'pcr': pcr_claim  # Enclave の PCR値を証明
        }
    )
    return response['Plaintext']

3.4 Nitro Enclave の実装例:機密データ処理

# Parent インスタンス側 (privileged=true)
import socket
import struct
import json

class EnclaveClient:
    def __init__(self, enclave_cid, enclave_port):
        self.enclave_cid = enclave_cid
        self.enclave_port = enclave_port
    
    def send_request(self, data):
        """
        Enclave へリクエスト送信(vsock通信)
        """
        sock = socket.socket(socket.AF_VSOCK, socket.SOCK_STREAM)
        sock.connect((self.enclave_cid, self.enclave_port))
        
        message = json.dumps(data).encode()
        sock.sendall(struct.pack('<I', len(message)) + message)
        
        # レスポンス受信
        response_len_data = sock.recv(4)
        response_len = struct.unpack('<I', response_len_data)[0]
        response_data = sock.recv(response_len)
        
        sock.close()
        return json.loads(response_data.decode())

# Enclave側(Enclave内で実行)
import socket
import json
import struct
from cryptography.fernet import Fernet

class EnclaveServer:
    def __init__(self, port=5000):
        self.port = port
        self.encryption_key = Fernet.generate_key()
        self.cipher = Fernet(self.encryption_key)
    
    def start(self):
        """
        vsock 上でサーバー起動
        """
        sock = socket.socket(socket.AF_VSOCK, socket.SOCK_STREAM)
        sock.bind((socket.VMADDR_CID_ANY, self.port))
        sock.listen(1)
        
        print(f"Enclave listening on port {self.port}")
        
        while True:
            conn, addr = sock.accept()
            self.handle_request(conn)
    
    def handle_request(self, conn):
        """
        Enclave 内での処理(外部からは見えない)
        """
        # リクエスト受信
        msg_len_data = conn.recv(4)
        msg_len = struct.unpack('<I', msg_len_data)[0]
        msg_data = conn.recv(msg_len)
        request = json.loads(msg_data.decode())
        
        # 例: クレジットカード情報を復号・検証
        if request['action'] == 'decrypt_card':
            encrypted_card = request['encrypted_card'].encode()
            decrypted = self.cipher.decrypt(encrypted_card).decode()
            
            # カード番号の検証(Luhnアルゴリズム等)
            is_valid = self.validate_card(decrypted)
            
            response = {
                'status': 'success' if is_valid else 'invalid',
                'card_last_four': decrypted[-4:] if is_valid else None
            }
        else:
            response = {'status': 'error', 'message': 'Unknown action'}
        
        # レスポンス送信
        response_data = json.dumps(response).encode()
        conn.sendall(struct.pack('<I', len(response_data)) + response_data)
        conn.close()
    
    @staticmethod
    def validate_card(card_number):
        """Luhnアルゴリズムでカード番号検証"""
        digits = [int(d) for d in card_number if d.isdigit()]
        checksum = 0
        for i, digit in enumerate(reversed(digits)):
            if i % 2 == 1:
                digit *= 2
                if digit > 9:
                    digit -= 9
            checksum += digit
        return checksum % 10 == 0

# 実行例
if __name__ == '__main__':
    # Enclave側
    server = EnclaveServer()
    server.start()  # Enclave内で起動
    
    # Parent側
    client = EnclaveClient(enclave_cid=16, enclave_port=5000)
    response = client.send_request({
        'action': 'decrypt_card',
        'encrypted_card': 'gAAAAABl...'  # 暗号化されたカード情報
    })
    print(response)

4. セキュリティグループ・ネットワーク・ストレージ暗号化

4.1 インスタンスプロファイル(IAMロール付与)

EC2 インスタンスが AWS APIを呼び出す際には、インスタンスプロファイル経由で IAMロールを付与します。

構造:

┌─────────────────────────────────────┐
│ EC2 インスタンス                     │
│                                     │
│ ┌──────────────────────────────┐  │
│ │ Instance Metadata Service    │  │
│ │ - ロール名                   │  │
│ │ - 一時認証情報(AccessKey)  │  │
│ │ - Token (STS)               │  │
│ └──────────────────────────────┘  │
└─────────────────────────────────────┘

     ┌───────────────────────┐
     │ Instance Profile      │
     │ (ロールのコンテナ)    │
     └───────────────────────┘

     ┌───────────────────────┐
     │ IAM ロール            │
     │ (権限の集合)          │
     └───────────────────────┘

CloudFormationでの定義:

MyEC2Role:
  Type: AWS::IAM::Role
  Properties:
    AssumeRolePolicyDocument:
      Version: "2012-10-17"
      Statement:
        - Effect: Allow
          Principal:
            Service: ec2.amazonaws.com
          Action: sts:AssumeRole
    Policies:
      - PolicyName: S3Access
        PolicyDocument:
          Version: "2012-10-17"
          Statement:
            - Effect: Allow
              Action:
                - s3:GetObject
                - s3:PutObject
              Resource: arn:aws:s3:::my-bucket/*
            - Effect: Allow
              Action:
                - kms:Decrypt
                - kms:GenerateDataKey
              Resource: !GetAtt MyKMSKey.Arn

MyInstanceProfile:
  Type: AWS::IAM::InstanceProfile
  Properties:
    Roles:
      - !Ref MyEC2Role

MyInstance:
  Type: AWS::EC2::Instance
  Properties:
    ImageId: ami-0c55b159cbfafe1f0
    InstanceType: t3.micro
    IamInstanceProfile: !Ref MyInstanceProfile

4.2 EBS ボリューム暗号化

EC2 のブロックストレージ(EBS)はデフォルトで暗号化設定されるべきです。

項目説明
デフォルト暗号化リージョンレベルで有効化可能
KMS キーAWS管理キー or カスタマー管理キー
スナップショット暗号化ボリュームのスナップショットも暗号化
スナップショット共有カスタマー管理キーで共有時、ターゲットアカウントにキーアクセス許可が必須

デフォルト暗号化の有効化:

# リージョンレベルでデフォルト暗号化を有効化
aws ec2 enable-ebs-encryption-by-default \
  --region us-east-1

# 確認
aws ec2 get-ebs-encryption-by-default --region us-east-1

CloudFormationでのEBS定義:

MyInstance:
  Type: AWS::EC2::Instance
  Properties:
    ImageId: ami-0c55b159cbfafe1f0
    InstanceType: t3.micro
    BlockDeviceMappings:
      - DeviceName: /dev/xvda
        Ebs:
          VolumeSize: 30
          VolumeType: gp3
          Iops: 3000
          Throughput: 125
          Encrypted: true  # 暗号化を明示的に指定
          KmsKeyId: !GetAtt MyKMSKey.Arn  # カスタマー管理キーを指定
          DeleteOnTermination: true

MyKMSKey:
  Type: AWS::KMS::Key
  Properties:
    Description: "KMS key for EBS encryption"
    KeyPolicy:
      Version: "2012-10-17"
      Statement:
        - Sid: Enable IAM policies
          Effect: Allow
          Principal:
            AWS: !Sub "arn:aws:iam::${AWS::AccountId}:root"
          Action: "kms:*"
          Resource: "*"
        - Sid: Allow EBS service
          Effect: Allow
          Principal:
            Service: ec2.amazonaws.com
          Action:
            - kms:Decrypt
            - kms:GenerateDataKey
            - kms:CreateGrant
          Resource: "*"

4.3 AMI の暗号化とクロスアカウント共有

AMIを別アカウントと共有する場合、暗号化キーのアクセス許可が必須です。

シナリオ:

Account A (AMI所有)
  ├─ AMI (encrypted with KMS key A)
  └─ KMS Key A

         ↓ AMI共有リクエスト

Account B (受け取り側)
  ├─ スナップショットコピー
  └─ KMS Key B でコピーを再暗号化

セットアップ:

# Account A: AMI作成・確認
aws ec2 create-image \
  --instance-id i-1234567890abcdef0 \
  --name "MySecureAMI" \
  --description "Encrypted AMI for cross-account sharing" \
  --region us-east-1

IMAGE_ID=$(aws ec2 describe-images \
  --filters Name=name,Values=MySecureAMI \
  --query 'Images[0].ImageId' \
  --output text)

# Account A: KMS キーポリシー修正(Account B に許可)
# キーポリシーに以下を追加
{
  "Sid": "AllowAccountBDecrypt",
  "Effect": "Allow",
  "Principal": {
    "AWS": "arn:aws:iam::ACCOUNT-B-ID:root"
  },
  "Action": [
    "kms:Decrypt",
    "kms:DescribeKey",
    "kms:GenerateDataKey"
  ],
  "Resource": "*"
}

# Account A: AMI を Account B と共有
aws ec2 modify-image-attribute \
  --image-id $IMAGE_ID \
  --launch-permission Add={UserId=ACCOUNT-B-ID} \
  --region us-east-1

# Account B: 共有 AMI を確認・コピー
aws ec2 describe-images \
  --owners ACCOUNT-A-ID \
  --region us-east-1

# Account B: AMI をコピー(自アカウントの KMS キーで再暗号化)
aws ec2 copy-image \
  --source-image-id $IMAGE_ID \
  --source-region us-east-1 \
  --name "MyAMI-AccountB" \
  --encrypted \
  --kms-key-id arn:aws:kms:us-east-1:ACCOUNT-B-ID:key/KEY-ID \
  --region us-east-1

4.4 Dedicated Hosts / Dedicated Instances

コンプライアンス要件(物理サーバーの占有、ライセンス管理)により、Dedicated Host / Dedicated Instance が必要な場合があります。

項目SharedDedicated InstanceDedicated Host
物理ホスト複数顧客共有単一顧客専有単一顧客専有
ライセンス持ち込み(BYOL)
物理属性制御
コスト最安中程度高額(ホスト単位)
スナップショット制限なし制限なしホスト内のみ

CloudFormationでの定義:

# Dedicated Instance
MyDedicatedInstance:
  Type: AWS::EC2::Instance
  Properties:
    ImageId: ami-0c55b159cbfafe1f0
    InstanceType: m5.large
    Tenancy: dedicated  # Dedicated Instance に設定
    SubnetId: !Ref MySubnet

# Dedicated Host
MyDedicatedHost:
  Type: AWS::EC2::Host
  Properties:
    AvailabilityZone: us-east-1a
    InstanceType: m5.large
    Quantity: 1

MyHostInstance:
  Type: AWS::EC2::Instance
  Properties:
    ImageId: ami-0c55b159cbfafe1f0
    InstanceType: m5.large
    Tenancy: host
    HostId: !Ref MyDedicatedHost

5. EC2 Image Builder(セキュアなAMI構築)

5.1 EC2 Image Builder の概要

EC2 Image Builder は、セキュリティパッチ・ハードニング・コンプライアンステストを自動化してAMIを構築するサービスです。

パイプライン:

┌───────────────────────────────────┐
│ 1. Base Image (Amazon Linux 2)   │
└───────────────┬───────────────────┘

┌───────────────────────────────────┐
│ 2. Components (Build Steps)       │
│  - Package Update                 │
│  - Security Hardening             │
│  - Custom Software Install        │
└───────────────┬───────────────────┘

┌───────────────────────────────────┐
│ 3. Test Phase                     │
│  - Inspector (Vulnerability Scan) │
│  - Custom Tests                   │
└───────────────┬───────────────────┘

┌───────────────────────────────────┐
│ 4. Output AMI (Encrypted)         │
│  - Versioning                     │
│  - Distribution (S3/ECR)          │
└───────────────────────────────────┘

5.2 CloudFormation による Image Builder 構築

AWSTemplateFormatVersion: "2010-09-09"
Description: "Secure AMI Pipeline with EC2 Image Builder"

Resources:
  # IAM Role for Image Builder
  ImageBuilderRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service: ec2.amazonaws.com
            Action: sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
        - arn:aws:iam::aws:policy/EC2InstanceProfileForImageBuilder
      Policies:
        - PolicyName: S3Access
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: Allow
                Action:
                  - s3:GetObject
                  - s3:PutObject
                Resource: !Sub "arn:aws:s3:::${ArtifactBucket}/*"

  ImageBuilderInstanceProfile:
    Type: AWS::IAM::InstanceProfile
    Properties:
      Roles:
        - !Ref ImageBuilderRole

  # Artifact Bucket
  ArtifactBucket:
    Type: AWS::S3::Bucket
    Properties:
      VersioningConfiguration:
        Status: Enabled
      ServerSideEncryptionConfiguration:
        - ServerSideEncryptionByDefault:
            SSEAlgorithm: AES256

  # Image Builder Component (Security Hardening)
  HardeningComponent:
    Type: AWS::ImageBuilder::Component
    Properties:
      Name: SecurityHardening
      Platform: Linux
      Version: "1.0.0"
      Data: |
        name: Security Hardening Component
        description: CIS Benchmarks and security updates
        schemaVersion: 1.0
        phases:
          - name: build
            steps:
              - name: UpdatePackages
                action: ExecuteBash
                inputs:
                  commands:
                    - yum update -y
                    - yum install -y aide
              - name: ApplySecuritySettings
                action: ExecuteBash
                inputs:
                  commands:
                    - echo "net.ipv4.ip_forward = 0" >> /etc/sysctl.conf
                    - echo "net.ipv4.conf.all.send_redirects = 0" >> /etc/sysctl.conf
                    - sysctl -p
              - name: DisableUnnecessaryServices
                action: ExecuteBash
                inputs:
                  commands:
                    - systemctl disable avahi-daemon
                    - systemctl disable cups
              - name: ConfigureSSH
                action: ExecuteBash
                inputs:
                  commands:
                    - sed -i 's/PermitRootLogin yes/PermitRootLogin no/' /etc/ssh/sshd_config
                    - sed -i 's/#PasswordAuthentication yes/PasswordAuthentication no/' /etc/ssh/sshd_config

  # Image Recipe
  MyImageRecipe:
    Type: AWS::ImageBuilder::ImageRecipe
    Properties:
      Name: MySecureAMI
      Version: "1.0.0"
      ParentImage: !Sub "arn:aws:imagebuilder:${AWS::Region}:aws:image/amazonlinux-2-x86_64/2024.3.0"
      Components:
        - ComponentArn: !Sub "arn:imagebuilder:${AWS::Region}:aws:component/update-linux/1.0.2/1"
        - ComponentArn: !GetAtt HardeningComponent.Arn
      BlockDeviceMappings:
        - DeviceName: /dev/xvda
          Ebs:
            DeleteOnTermination: true
            VolumeSize: 30
            VolumeType: gp3
            Encrypted: true

  # Infrastructure Configuration
  InfrastructureConfig:
    Type: AWS::ImageBuilder::InfrastructureConfiguration
    Properties:
      Name: MyInfrastructure
      InstanceProfileName: !Ref ImageBuilderInstanceProfile
      InstanceTypes:
        - t3.medium
      Logging:
        S3Logs:
          S3BucketName: !Ref ArtifactBucket
          S3KeyPrefix: imagebuilder-logs/
      TerminateInstanceOnFailure: true

  # Distribution Configuration
  DistributionConfig:
    Type: AWS::ImageBuilder::DistributionConfiguration
    Properties:
      Name: MyDistribution
      Distributions:
        - Region: !Ref AWS::Region
          AmiDistributionConfiguration:
            Name: MySecureAMI-{{ imagebuilder:buildDate }}
            Description: "Hardened AMI with security patches"
            AmiTags:
              Environment: production
              BuildDate: "{{ imagebuilder:buildDate }}"

  # Image Pipeline
  ImagePipeline:
    Type: AWS::ImageBuilder::ImagePipeline
    Properties:
      Name: MySecureAMIPipeline
      InfrastructureConfigurationArn: !GetAtt InfrastructureConfig.Arn
      ImageRecipeArn: !GetAtt MyImageRecipe.Arn
      DistributionConfigurationArn: !GetAtt DistributionConfig.Arn
      EnhancedImageMetadataEnabled: true
      Schedule:
        ScheduleExpression: "cron(0 0 * * MON *)"  # 毎週月曜に実行

Outputs:
  ImagePipelineArn:
    Value: !GetAtt ImagePipeline.Arn
  ArtifactBucketName:
    Value: !Ref ArtifactBucket

5.3 CIS Benchmark 準拠ハードニング

# CIS Benchmark コンポーネント例
CISHardeningComponent:
  Type: AWS::ImageBuilder::Component
  Properties:
    Name: CISBenchmarkHardening
    Platform: Linux
    Version: "2.0.0"
    Data: |
      name: CIS Benchmark Hardening
      description: Implements CIS Amazon Linux 2 Benchmark recommendations
      schemaVersion: 1.0
      phases:
        - name: build
          steps:
            # 1.1.1.1 CIS 1.1.1.1
            - name: DisableUnnecessedFilesystems
              action: ExecuteBash
              inputs:
                commands:
                  - echo "install cramfs /bin/true" >> /etc/modprobe.d/disable.conf
                  - echo "install freevxfs /bin/true" >> /etc/modprobe.d/disable.conf
                  - echo "install jffs2 /bin/true" >> /etc/modprobe.d/disable.conf
                  - echo "install hfs /bin/true" >> /etc/modprobe.d/disable.conf
                  - echo "install hfsplus /bin/true" >> /etc/modprobe.d/disable.conf

            # 1.1.2 CIS 1.1.2-1.1.4
            - name: MountOptionsPartitions
              action: ExecuteBash
              inputs:
                commands:
                  - mount -o remount,nodev /tmp
                  - mount -o remount,nosuid /tmp
                  - mount -o remount,noexec /tmp

            # 3.1.1 IP Forwarding の無効化
            - name: DisableIPForwarding
              action: ExecuteBash
              inputs:
                commands:
                  - sysctl -w net.ipv4.ip_forward=0
                  - echo "net.ipv4.ip_forward = 0" >> /etc/sysctl.d/99-hardening.conf

            # 4.1.1 Auditing の設定
            - name: EnableAuditing
              action: ExecuteBash
              inputs:
                commands:
                  - yum install -y audit
                  - systemctl enable auditd
                  - auditctl -w /etc/passwd -p wa -k identity
                  - auditctl -w /etc/shadow -p wa -k identity

            # SSH ハードニング
            - name: HardenSSHConfiguration
              action: ExecuteBash
              inputs:
                commands:
                  - sed -i 's/^#PermitRootLogin.*/PermitRootLogin no/' /etc/ssh/sshd_config
                  - sed -i 's/^#HostbasedAuthentication.*/HostbasedAuthentication no/' /etc/ssh/sshd_config
                  - sed -i 's/^#PermitEmptyPasswords.*/PermitEmptyPasswords no/' /etc/ssh/sshd_config
                  - echo "Protocol 2" >> /etc/ssh/sshd_config
                  - systemctl restart sshd

5.4 Image Builder と Inspector の統合

# Image Builder 出力 AMI を Inspector でスキャン
aws inspector start-assessment-run \
  --assessment-template-arn arn:aws:inspector:us-east-1:123456789012:template/0-XXXXXXXX \
  --region us-east-1

# スキャン結果を CloudWatch Logs に送信
aws logs put-metric-alarm \
  --alarm-name ImageBuilderVulnerabilities \
  --alarm-description "Alert on critical vulnerabilities in Image Builder AMI" \
  --metric-name VulnerabilityCount \
  --namespace AWS/Inspector \
  --statistic Sum \
  --period 3600 \
  --threshold 1 \
  --comparison-operator GreaterThanOrEqualToThreshold \
  --alarm-actions arn:aws:sns:us-east-1:123456789012:AlertTopic

試験で狙われるポイント

⭐ IMDSv2(最重要)

  • IMDSv1 vs v2の違いを正確に説明できるか

    • v1: 認証なし、GET直接アクセス → SSRF脆弱性
    • v2: トークン方式、PUT→GET、ホップリミット → SSRF耐性
  • HttpTokens: required の設定方法(CLI / CloudFormation / Terraform)

  • IMDSv2がSSRF対策になる理由

    • GETだけではアクセス不可、PUT(トークン取得)が必須
    • ホップリミット1でホスト内部からのみアクセス可能

⭐ キーペア管理の選択肢

  • EC2 Instance Connect

    • 短命キー(60秒)、IAM権限ベース
    • CloudTrailで監査可能
  • Systems Manager Session Manager

    • キーレス、IAM権限のみ
    • プライベートインスタンスにも接続可能
  • 従来のSSHキーペア

    • ローテーション困難、無期限アクセス
    • 新規構築では避けるべき

⭐ Nitro Enclave

  • Attestation Document の役割

    • Enclave が正当か検証
    • KMS キーの条件付きアクセスに使用
  • ユースケース (PII処理、暗号化キー管理)

  • vsock 通信 の理解

    • Parent ↔ Enclave の通信メカニズム

⭐ EBS・AMI 暗号化

  • デフォルト暗号化の有効化(リージョンレベル)

  • クロスアカウント AMI 共有時の KMS キーポリシー

    • ターゲットアカウントに復号権限が必須
  • スナップショット共有 も同様にキーポリシー修正が必要

⭐ EC2 Image Builder

  • パイプライン (Component → Recipe → Infrastructure → Distribution)

  • CIS Benchmark 準拠 ハードニングの実装

  • Inspector 統合 でスキャン自動化

試験頻出問題パターン

  1. 「IMDSv1 で認証情報が盗まれている。どう対策するか?」 → IMDSv2 に強制、HttpTokens: required

  2. 「プライベートインスタンスに接続したいが、SSH キーペアを配布したくない」 → Session Manager (キーレス)、EC2 Instance Connect Endpoint

  3. 「Enclave 内でのみ復号させたい」 → KMS キーポリシーで PCR 条件付け、Attestation で検証

  4. 「AMI をセキュアに構築し、毎週更新したい」 → Image Builder パイプライン、スケジュール実行、Inspector テスト

  5. 「EBS が暗号化されていない。全インスタンスを強制したい」 → リージョンレベルデフォルト暗号化、新規構築時は CloudFormation で Encrypted: true


まとめ

領域重要な技術試験での出題頻度
IMDSIMDSv2、HttpTokens⭐⭐⭐⭐⭐
キーペアEC2IC、Session Manager⭐⭐⭐⭐
NitroAttestation、KMS連携⭐⭐⭐
暗号化EBS/AMI、デフォルト化⭐⭐⭐⭐
Image Builderパイプライン、CIS⭐⭐⭐

EC2 セキュリティは SSRF 対策(IMDSv2)キーペア管理(キーレス化) の2本柱が最優先です。本番運用では「認証情報の盗難」「不正アクセス」を徹底的に防ぐ設計が求められます。


参考資料