EC2インスタンスIDをメタデータから取得する方法 — IMDSv2が安全な理由

サーバー上で動作するスクリプトから自分のインスタンスIDを取得したい場面は多い。デプロイスクリプト、ログ集約、Auto Scalingグループのインベントリ管理など、用途は様々だ。ただし、EC2インスタンスメタデータサービス(IMDS)には2つのバージョンが存在し、IMDSv1とIMDSv2の選択はセキュリティ上の重大な差異を生む。本番環境でIMDSv1を使い続けることは、SSRF(Server-Side Request Forgery)攻撃の踏み台になるリスクを放置することと同義だ。

TL;DR — IMDSv1 vs IMDSv2 早見表

観点IMDSv1IMDSv2
認証方式なし(直接GETのみ)セッショントークン必須(PUT → GET)
SSRF耐性なしあり(PUT不可なSSRFはトークン取得不可)
TTL制御なしX-aws-ec2-metadata-token-ttl-secondsで指定
デフォルト状態新規インスタンスでも有効(設定次第)IMDSv2-onlyに変更可能
推奨度非推奨AWS公式推奨

IMDSの仕組み — なぜインスタンス内からだけ到達できるのか

EC2インスタンスメタデータサービスは、リンクローカルアドレス 169.254.169.254 で提供される。このアドレスはRFC 3927で定義されたリンクローカル範囲であり、インスタンスのネットワークインターフェース外には到達できない。つまり、インターネット経由や他のインスタンスから直接アクセスすることは原理的に不可能だ。

ただし、SSRF脆弱性があるアプリケーションが動作している場合は話が変わる。攻撃者がアプリケーションを経由して http://169.254.169.254/latest/meta-data/iam/security-credentials/ へリクエストを誘導できれば、インスタンスにアタッチされたIAMロールの一時クレデンシャルを盗める。IMDSv1はこのシナリオに対して無防備だ。

graph TD subgraph v1["IMDSv1 — 認証なし"] A1["攻撃者 / SSRFペイロード"] -->|"GET /latest/meta-data/instance-id"| B1["IMDS 169.254.169.254"] B1 -->|"200 OK: i-0abc..."| A1 end subgraph v2["IMDSv2 — トークン必須"] A2["正規プロセス"] -->|"PUT /latest/api/token
TTL: 21600"| B2["IMDS 169.254.169.254"] B2 -->|"200 OK: TOKEN"| A2 A2 -->|"GET /latest/meta-data/instance-id
Header: X-aws-ec2-metadata-token: TOKEN"| B2 B2 -->|"200 OK: i-0abc..."| A2 A3["攻撃者 / SSRFペイロード"] -->|"GET only — PUTが使えない"| B2 B2 -->|"401 Unauthorized"| A3 end
  1. IMDSv1(上段): 攻撃者がSSRF経由でGETリクエストを送るだけでメタデータを取得できる。認証ステップが存在しない。
  2. IMDSv2(下段): まずPUTリクエストでセッショントークンを取得する必要がある。一般的なSSRFはGETリクエストしか誘導できないため、PUTが必要なトークン取得ステップで攻撃が止まる。
  3. TTL=1: X-aws-ec2-metadata-token-ttl-seconds: 1 を指定すると、取得したトークンは1秒で失効する。プロキシ経由の攻撃に対してさらに有効だ。

IMDSv2でインスタンスIDを取得する — 基本コマンド

IMDSv2によるインスタンスID取得は2ステップで構成される。まずセッショントークンを取得し、そのトークンを使ってメタデータエンドポイントにアクセスする。

# ステップ1: セッショントークンを取得(TTL=21600秒 = 6時間)
TOKEN=$(curl -s -X PUT \
  'http://169.254.169.254/latest/api/token' \
  -H 'X-aws-ec2-metadata-token-ttl-seconds: 21600')

# ステップ2: トークンを使ってインスタンスIDを取得
INSTANCE_ID=$(curl -s \
  'http://169.254.169.254/latest/meta-data/instance-id' \
  -H "X-aws-ec2-metadata-token: $TOKEN")

echo "Instance ID: $INSTANCE_ID"

出力例:

Instance ID: i-0abcdef1234567890

スクリプト内でインスタンスIDを使う場面では、上記の2ステップをそのまま組み込める。トークンのTTLはユースケースに合わせて調整する。短命なスクリプトなら60秒程度で十分だ。

IMDSv1との比較 — なぜv1は危険なのか

IMDSv1では認証ステップが存在しないため、単純なGETリクエストだけでメタデータを取得できる。

# IMDSv1(非推奨) — 認証なしで直接取得できてしまう
curl -s http://169.254.169.254/latest/meta-data/instance-id

このシンプルさが問題の本質だ。Webアプリケーション内にSSRF脆弱性があれば、攻撃者は外部から上記と同等のリクエストをアプリ経由で発行できる。IMDSv2のPUTベースのトークン取得は、多くのSSRFシナリオで攻撃者がPUTメソッドを使えないという特性を利用した防御層だ。

IMDSv2のセキュリティモデルは『PUTができる = 正規のインスタンス内プロセス』という前提に基づいている。SSRFの多くはGETリダイレクトしか制御できないため、PUTが必要なトークン取得ステップが自然なフィルターになる。

IMDSv2-onlyモードへの移行 — インスタンス設定の変更

既存インスタンスをIMDSv2-onlyに変更するには、modify-instance-metadata-options を使う。これによりIMDSv1へのアクセスが完全に無効化される。

aws ec2 modify-instance-metadata-options \
  --instance-id i-0abcdef1234567890 \
  --http-tokens required \
  --http-endpoint enabled \
  --region us-east-1

--http-tokens required がIMDSv2-onlyを意味する。optional に設定するとv1とv2の両方が使える状態になる。本番環境では required を推奨する。

新規インスタンス起動時にIMDSv2-onlyを強制するには、起動テンプレートで設定する。

aws ec2 create-launch-template \
  --launch-template-name imdsv2-enforced-template \
  --version-description 'IMDSv2 only' \
  --launch-template-data '{
    "MetadataOptions": {
      "HttpTokens": "required",
      "HttpEndpoint": "enabled"
    }
  }' \
  --region us-east-1

現在の設定を確認する

既存インスタンスのIMDS設定状況を確認するには以下を実行する。HttpTokensrequired になっていればIMDSv2-onlyだ。

aws ec2 describe-instances \
  --instance-ids i-0abcdef1234567890 \
  --query 'Reservations[*].Instances[*].MetadataOptions' \
  --output table \
  --region us-east-1

リージョン内の全インスタンスでIMDSv1が有効なものを洗い出したい場合:

aws ec2 describe-instances \
  --filters 'Name=metadata-options.http-tokens,Values=optional' \
  --query 'Reservations[*].Instances[*].[InstanceId,MetadataOptions.HttpTokens]' \
  --output table \
  --region us-east-1

このコマンドで出力されたインスタンスは、IMDSv1アクセスが可能な状態にある。優先的に required へ移行すべき対象だ。

実際の障害パターン — IMDSv2移行後にスクリプトが壊れるケース

IMDSv2-onlyへ切り替えた直後、ユーザーデータスクリプトやcronジョブが突然失敗し始めた、という報告は珍しくない。症状はシンプルで、curl がHTTP 401を返すか、空のレスポンスを返す。

最初の誤診断は「IMDSエンドポイントが落ちている」だ。ping 169.254.169.254 が通るのでネットワーク層は問題ない、と判断してしまう。実際の原因はスクリプトがIMDSv1の直接GETを使っていたことで、--http-tokens required 設定後はそのリクエストが拒否されるようになった、というものだ。

確認すべきログ:

# ユーザーデータスクリプトのログを確認
sudo cat /var/log/cloud-init-output.log | grep -i 'metadata\|curl\|401'

修正は単純で、スクリプト内のIMDSアクセスをすべてIMDSv2の2ステップパターンに書き換えるだけだ。移行前に grep -r '169.254.169.254' /etc/grep -r '169.254.169.254' /usr/local/bin/ でIMDSv1パターンを使っているスクリプトを洗い出しておくと、移行後の障害を防げる。

Pythonスクリプトでの実装例

Bashだけでなく、Pythonスクリプトから取得する場合のパターンも示す。

🔽 Python実装例(クリックで展開)
import urllib.request
import urllib.error

def get_instance_id():
    # ステップ1: セッショントークンを取得
    token_url = 'http://169.254.169.254/latest/api/token'
    token_req = urllib.request.Request(
        token_url,
        method='PUT',
        headers={'X-aws-ec2-metadata-token-ttl-seconds': '21600'}
    )
    with urllib.request.urlopen(token_req, timeout=2) as resp:
        token = resp.read().decode('utf-8')

    # ステップ2: インスタンスIDを取得
    instance_id_url = 'http://169.254.169.254/latest/meta-data/instance-id'
    id_req = urllib.request.Request(
        instance_id_url,
        headers={'X-aws-ec2-metadata-token': token}
    )
    with urllib.request.urlopen(id_req, timeout=2) as resp:
        return resp.read().decode('utf-8')

if __name__ == '__main__':
    instance_id = get_instance_id()
    print(f'Instance ID: {instance_id}')

タイムアウトを短く設定(2秒程度)しておくことを推奨する。EC2インスタンス外でこのスクリプトが誤って実行された場合、IMDSエンドポイントへの接続が長時間ハングするのを防ぐためだ。

IAMポリシーとIMDSの関係

IMDSへのアクセス自体にIAMポリシーは不要だ。ただし、ec2:ModifyInstanceMetadataOptions アクションはIAM権限が必要であり、インスタンスのIMDS設定を変更する操作者には以下のポリシーが必要になる。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "ec2:ModifyInstanceMetadataOptions",
        "ec2:DescribeInstances"
      ],
      "Resource": "*"
    }
  ]
}

ec2:ModifyInstanceMetadataOptions はリソースレベルの制限をサポートしているが、ec2:DescribeInstancesResource: * が必要だ。Service Authorization Referenceで最新の対応状況を確認すること。

EC2インスタンスIDの取得とIMDSv2 — まとめと次のステップ

インスタンスメタデータサービスを使ったEC2インスタンスID取得は、IMDSv2の2ステップパターン(PUT → GET)を使うことが現在のベストプラクティスだ。IMDSv1はSSRF攻撃に対して無防備であり、新規・既存を問わずIMDSv2-onlyへの移行を優先すべきだ。

  • 既存インスタンスの設定確認: describe-instancesHttpTokens の状態を確認する
  • IMDSv1を使っているスクリプトの洗い出し: grep -r '169.254.169.254' でコードベースを検索する
  • 新規インスタンスへの適用: 起動テンプレートで HttpTokens: required をデフォルト化する
  • AWS SCPによる組織全体への強制: Organizations SCPで ec2:ModifyInstanceMetadataOptions を制限し、optional への変更を禁止する方法も検討する

公式ドキュメント: インスタンスメタデータサービスの設定 — AWS公式

用語集

用語説明
IMDSInstance Metadata Service。EC2インスタンスが自身の情報を取得するための内部エンドポイント(169.254.169.254)
IMDSv2セッション指向のIMDS。PUTリクエストで取得したトークンを使ってメタデータにアクセスする方式
SSRFServer-Side Request Forgery。攻撃者がサーバーを経由して内部リソースへリクエストを送らせる攻撃手法
リンクローカルアドレス169.254.0.0/16の範囲。同一リンク(ネットワークセグメント)内でのみ有効なIPアドレス
HttpTokens: requiredIMDSv2-onlyモードを示す設定値。この設定ではIMDSv1のリクエストが拒否される

Related Posts

コメント

このブログの人気の投稿

EC2 SSH接続タイムアウトの原因と修正方法 — セキュリティグループのインバウンドルール完全ガイド

S3パブリックアクセス拒否の原因と解決策:バケットレベルの「Block Public Access」が優先される仕組み