EC2インスタンスが突然消えた — CloudTrail Event Historyで削除者を特定する方法
月曜の朝、本番EC2インスタンスが消えていた。Slackに通知が飛んで、誰もやっていないと言う。こういう状況で最初に開くべきツールがCloudTrail Event Historyだ。TerminateInstancesアクションは必ずCloudTrailに記録されており、どのIAMユーザーまたはロールが実行したかを数分で特定できる。
TL;DR — CloudTrailでEC2削除者を特定する
| ステップ | 操作 | 確認ポイント |
|---|---|---|
| 1 | Event Historyを開く | リージョンが正しいか確認 |
| 2 | イベント名でTerminateInstancesをフィルタ | 削除時刻の前後30分を絞り込む |
| 3 | イベント詳細を展開 | userIdentityフィールドを確認 |
| 4 | CLIで同じクエリを実行 | インスタンスIDで絞り込み精度を上げる |
| 5 | 再発防止策を実施 | Termination ProtectionまたはSCP適用 |
CloudTrail Event Historyの仕組みを理解する
CloudTrailはAWSアカウント内のAPI呼び出しを記録するサービスだ。EC2のTerminateInstancesも例外ではなく、マネジメントコンソール、CLI、SDK、Auto Scalingなど、どの経路で実行されても同じイベントとして記録される。
Event Historyは追加設定なしで有効になっており、過去90日間のマネジメントイベントを保持する。S3バケットへの長期保存にはTrailの設定が必要だが、90日以内の調査であればEvent Historyだけで完結する。
- API呼び出し元 — コンソール、CLI、SDK、Auto Scalingなど複数の経路が存在する
- CloudTrail — すべての経路からのAPI呼び出しを記録し、イベントとして保存する
- Event History — 90日間のマネジメントイベントをコンソールまたはCLIで検索可能
- S3 / CloudWatch Logs — Trailを設定した場合の長期保存先。90日超の調査に必要
重要な点として、CloudTrailのイベントにはリージョン情報が含まれる。EC2インスタンスが存在していたリージョンのEvent Historyを確認しなければ、該当イベントは表示されない。これで見つからないと焦るケースが実際に多い。
ステップ1 — Event Historyでイベントを検索する(コンソール)
まず、削除されたEC2インスタンスが存在していたリージョンに切り替える。リージョンを間違えると何も見つからない。
- AWSマネジメントコンソールでCloudTrailを開く
- 左メニューから「イベント履歴 (Event History)」を選択する
- フィルターのドロップダウンで「イベント名 (Event name)」を選択し、
TerminateInstancesと入力する - 時間範囲を、インスタンスが最後に確認できた時刻から現在までに絞り込む
- イベントの行をクリックして詳細を展開し、イベントレコード (Event record)のJSONを確認する
JSONの中で最初に確認すべきフィールドはuserIdentityだ。ここに実行者の情報がすべて含まれている。
TerminateInstancesイベントのJSONを読む
イベントレコードのJSONは一見複雑に見えるが、削除者の特定に必要なフィールドは限られている。
🔽 イベントレコードJSONのサンプル(クリックして展開)
{
"eventVersion": "1.08",
"userIdentity": {
"type": "IAMUser",
"principalId": "AIDAEXAMPLEID",
"arn": "arn:aws:iam::123456789012:user/taro.yamada",
"accountId": "123456789012",
"userName": "taro.yamada"
},
"eventTime": "2024-03-15T02:34:56Z",
"eventSource": "ec2.amazonaws.com",
"eventName": "TerminateInstances",
"awsRegion": "ap-northeast-1",
"sourceIPAddress": "203.0.113.10",
"userAgent": "aws-cli/2.x Python/3.x",
"requestParameters": {
"instancesSet": {
"items": [
{ "instanceId": "i-0abc123def456789" }
]
}
},
"responseElements": {
"instancesSet": {
"items": [
{
"instanceId": "i-0abc123def456789",
"currentState": { "code": 32, "name": "shutting-down" },
"previousState": { "code": 16, "name": "running" }
}
]
}
}
}
確認すべきフィールドの意味を整理する。
| フィールド | 意味 | 注目ポイント |
|---|---|---|
userIdentity.type | 実行者の種別 | IAMUser、AssumedRole、AWSServiceなど |
userIdentity.arn | 実行者のARN | ユーザー名またはロール名が含まれる |
userIdentity.userName | IAMユーザー名 | typeがIAMUserの場合のみ存在 |
sourceIPAddress | 呼び出し元IPアドレス | コンソール操作の場合はAWSのIPになることがある |
userAgent | 使用したクライアント | CLIかコンソールかSDKかを判別できる |
requestParameters.instancesSet | 対象インスタンスID | 複数インスタンスの一括削除も記録される |
userIdentity.typeがAssumedRoleの場合、sessionContext.sessionIssuerフィールドにロールのARNが記録される。EC2インスタンスプロファイルやLambdaからの実行はこのパターンになる。
IAMロールの一時認証情報で実行された操作は、userIdentity.typeがAssumedRoleになる。この場合、誰がそのロールをAssumeしたかはsessionContextを辿る必要がある。ロール名だけ見て「サービスが消した」と結論づけると、実際には人間がそのロールを使って実行していたケースを見逃す。
ステップ2 — CLIでTerminateInstancesイベントを検索する
コンソールのEvent Historyはフィルタリングが限定的で、大量のイベントがある環境では目的のレコードを見つけにくい。CLIを使えばインスタンスIDで直接絞り込める。これがコンソールで見つからないときの次の手だ。
aws cloudtrail lookup-events \
--lookup-attributes AttributeKey=EventName,AttributeValue=TerminateInstances \
--start-time "2024-03-14T00:00:00Z" \
--end-time "2024-03-16T00:00:00Z" \
--region ap-northeast-1 \
--output json
特定のインスタンスIDに絞り込む場合は、ResourceName属性を使う。
aws cloudtrail lookup-events \
--lookup-attributes AttributeKey=ResourceName,AttributeValue=i-0abc123def456789 \
--region ap-northeast-1 \
--output json
lookup-eventsコマンドは1回のリクエストで最大50件を返す。--max-resultsと--next-tokenでページネーションを制御できる。また、このコマンドはEvent Historyと同じ90日制限が適用される。
出力から実行者情報だけを抽出したい場合は、jqを組み合わせると効率的だ。
aws cloudtrail lookup-events \
--lookup-attributes AttributeKey=EventName,AttributeValue=TerminateInstances \
--region ap-northeast-1 \
--output json | jq '.Events[].CloudTrailEvent | fromjson | {eventTime, userIdentity, sourceIPAddress}'
ステップ3 — AssumedRoleの場合に実行者を特定する
Auto ScalingやECSなどのAWSサービスがインスタンスを終了させた場合、または誰かがIAMロールをAssumeして実行した場合、userIdentity.typeはAssumedRoleになる。このケースで実行者を特定するにはsessionContextを確認する。
- typeの確認 —
IAMUserであればuserNameフィールドで即座に特定できる - AssumedRoleの場合 —
sessionContext.sessionIssuerにロールのARNが記録されている - AWSServiceの場合 — Auto ScalingやEC2 Fleet等のサービスが自動的に実行した可能性がある。
invokedByフィールドにサービス名が記録される - ロール名の確認 — ロール名から用途を判断し、そのロールを誰がAssumeしたかをさらに調査する
Auto Scalingによる終了の場合、userIdentity.invokedByにautoscaling.amazonaws.comが記録される。これは意図した動作である可能性が高いが、スケールインポリシーの設定ミスで本番インスタンスが消えるケースも実際にある。
実際の障害パターン — 「誰も消していない」が嘘ではなかった件
症状: 深夜2時にEC2インスタンスが1台消えた。チームメンバー全員が「操作していない」と言う。CloudTrailを確認するとTerminateInstancesのイベントが存在し、userIdentity.typeはAssumedRole、ARNにはrole/developer-roleが含まれていた。
最初の誤診: 誰かがdeveloper-roleを使って手動で削除したと判断し、チームメンバーへの聞き込みを続けた。
実際の原因: sessionContext.sessionIssuerをさらに確認すると、invokedByフィールドにspotfleet.amazonaws.comが記録されていた。Spot Fleetがスポットインスタンスの中断に伴い自動的に終了処理を実行していたのだ。developer-roleはSpot Fleetのサービスリンクロールに付与されていた権限の一部だった。
修正: Spot Fleetの設定を確認し、中断通知の処理とインスタンス置き換えポリシーを見直した。CloudTrailのイベントを最後まで読まずにuserIdentity.arnだけで判断したのが失敗だった。イベントレコードは必ずinvokedByまで確認する。
ステップ4 — 90日を超えた調査にはS3のTrailログを使う
Event Historyの保持期間は90日だ。それ以前の削除を調査する場合、またはより詳細なデータイベントが必要な場合は、S3に保存されたTrailログを直接検索する必要がある。
TrailがS3に保存するログはGzip圧縮されたJSONファイルだ。aws s3 cpでダウンロードしてjqで検索するか、Amazon Athenaを使ってSQLクエリで検索できる。
aws s3 ls s3://your-cloudtrail-bucket/AWSLogs/123456789012/CloudTrail/ap-northeast-1/2024/03/15/ \
--region ap-northeast-1
Athenaを使う場合は、AWSが提供するCloudTrail用のテーブル定義を使ってデータベースを作成し、以下のようなクエリで検索できる。
SELECT
eventtime,
useridentity.arn,
useridentity.type,
sourceipaddress,
useragent,
requestparameters
FROM cloudtrail_logs
WHERE eventname = 'TerminateInstances'
AND awsregion = 'ap-northeast-1'
AND eventtime BETWEEN '2024-03-14' AND '2024-03-16'
ORDER BY eventtime DESC;
再発防止 — Termination Protectionと予防的コントロール
削除者を特定した後は、同じ事故が起きない仕組みを入れる。調査で終わらせてはいけない。
EC2 Termination Protectionを有効にする
aws ec2 modify-instance-attribute \
--instance-id i-0abc123def456789 \
--disable-api-termination \
--region ap-northeast-1
Termination Protectionが有効かどうかを確認する
aws ec2 describe-instance-attribute \
--instance-id i-0abc123def456789 \
--attribute disableApiTermination \
--region ap-northeast-1
Termination Protectionは有効な防御だが、ec2:ModifyInstanceAttribute権限を持つユーザーが無効化してから削除できる点に注意が必要だ。より強固な制御が必要な場合は、AWS OrganizationsのSCP(Service Control Policy)でec2:TerminateInstancesを特定のロールのみに制限するアプローチが有効だ。
また、CloudTrailのイベントをCloudWatch Logsに転送し、TerminateInstancesイベントに対してメトリクスフィルターとアラームを設定することで、次回からはリアルタイムで通知を受け取れる。
CloudTrail Event Historyで削除者を特定するまとめ
EC2インスタンスのCloudTrail Event History調査は、正しいリージョンでTerminateInstancesイベントを検索し、userIdentityフィールドを読むだけで完結する。ただし、AssumedRoleの場合はsessionContextとinvokedByまで確認しないと実行者を誤認するリスクがある。90日以内であればEvent Historyで十分だが、長期保存にはS3へのTrail設定が必要だ。
調査後は必ずTermination Protectionの有効化と、必要に応じたSCPによる予防的コントロールを実施する。インシデント対応は特定で終わりではなく、再発防止まで含めて完了だ。
用語集
| 用語 | 説明 |
|---|---|
| Event History | CloudTrailが提供する過去90日間のマネジメントイベント検索機能。追加設定不要で利用できる。 |
| userIdentity | CloudTrailイベントレコード内のフィールド。API呼び出しを実行したIAMエンティティの情報を含む。 |
| AssumedRole | IAMロールの一時認証情報を使用してAPI呼び出しが行われた場合のuserIdentity.type値。 |
| Termination Protection | EC2インスタンスに設定できる保護機能。有効にするとAPIまたはコンソールからの終了操作をブロックする。 |
| Trail | CloudTrailのイベントをS3バケットやCloudWatch Logsに継続的に配信する設定。90日超の保存に必要。 |
コメント
コメントを投稿