EC2 上のコンテナが IAM Instance Profile Role 経由で必要な権限を取得できない
EC2 上のコンテナが IAM Instance Profile Role 経由で必要な権限を取得できない. Practical notes and implementation steps.
EC2 上で AWS リソースを操作する場合、
ベストプラクティスは IAM Instance Profile Role をアタッチし、最小権限で運用することです。
実際には EC2 側でメタデータサービス経由の認証情報が提供され、
次のコマンドで取得できます。
1
curl http://169.254.169.254/latest/meta-data/iam/security-credentials/
正常なら次のように表示されます。
1
<IAM Profile Name>
ただし、EC2 の機能である IMDSv2 を有効化すると、
EC2 内のコンテナが認証情報を取得できなくなることがあります。
EC2 インスタンス詳細 -> Actions -> Instance settings -> Modify instance metadata options
従来 IMDSv2 は Optional で運用されることが多かったですが、
現在は AWS が Required を推奨しています。
この変更は、アプリケーションの脆弱性悪用による
インスタンス認証情報やメタデータの窃取を防ぐためです。
EC2 インスタンス内では、以下のローカル URL から多くの情報を取得できます。
1 http://169.254.169.254/latest/meta-data/例えば次の情報です。
- instance ID
- IAM role credentials
- security groups
- AMI ID
- hostname
- region これらの情報は、AWS SDK などが IAM の一時認証情報を自動取得する際にも利用されます。
初期の IMDSv1 は、単純なリクエストだけでメタデータを取得できました。
IMDSv2 ではセッショントークン方式が追加され、
先にトークン取得、その後メタデータ取得、という流れになります。
では、推奨どおり Required にした場合、
Optional に戻す以外に手段はないのでしょうか?
実際には、EC2 上で Docker を動かす場合に他の要因もあります。
IMDSv2 はトークン必須だが、利用ツールが IMDSv1 しか対応していない
IMDSv2 の流れ: 1) PUT で token を取得 2) token を使って metadata を GET 一部の古いツール(または古い SDK)は IMDSv1 のみ対応です。例:
- 古い AWS CLI
- 古い SDK
- 一部の CI イメージ この場合、token が無いため
401 Unauthorizedになります。
hop limit の問題
EC2 metadata には次の設定があります:
1
HttpPutResponseHopLimit
デフォルトは通常:
1
1
ただし Docker コンテナから metadata までには、実際には 1 hop 余分に発生します:
1 2 3 4 5 6 7
container ↓ docker bridge ↓ EC2 instance network ↓ 169.254.169.254そのため hop=1 だと token 応答がコンテナに戻れません。
結果として、次のようなエラーになります:
1
IMDSv2 token request timeout
これは CI Runner で非常によくある原因です。
対策は hop limit を上げることです。
例えば:
1 2 3
aws ec2 modify-instance-metadata-options \ --instance-id i-xxxx \ --http-put-response-hop-limit 2
Docker network policy / iptables
この方法についてはまだ十分に把握できていません。
今後実際に遭遇したら追記します。
私がこの問題に遭遇したのは、
EC2 上に GitLab CI Runner を構築したときでした。
Pipeline 内のコンテナ通信経路は次の通りです:
1
2
3
4
5
6
7
container
↓
docker bridge (docker0)
↓
host network
↓
IMDS
token 応答の戻り経路は:
1
2
3
4
5
6
7
IMDS
↓
host network
↓
docker bridge
↓
container
実際の hop 数は次のようになります:
1
2 hops
もし:
1
HttpPutResponseHopLimit = 1
ならパケットが破棄されます。
その結果、コンテナ側では通常次のように見えます:
1
IMDS token request timeout
または
1
no credentials found
解決策は 1 つではありません:
- IMDSv2 を Optional に戻す(非推奨)
- hop limit を増やす
- IAM User Credentials を Environment Variable として注入し、GitLab CI Runner 実行時に渡す(妥協案)