CloudTrailでWAF IP setの操作を見える化し、期限付き自動解除も実装した話
目次
こんにちは、L小川です。
皆さんはWAFを使ってますか?
Webアプリケーションを運用するうえで、WAFはセキュリティ対策の要のひとつです。
WAFには不正アクセスを自動でブロックする機能もありますが、過去の記事でご紹介したように、弊社ではWAFの検知情報をもとに手動でIPアドレスをブロックする運用も行っています。
手動でブロックする場合、誰が・いつ・どのIPをブロック(解除)したか、把握できていますか?
この記事では、AWS CloudTrailをトリガーにAWS WAFのIPブロック・解除操作を記録・通知する仕組みを紹介します。
システム構成:初期バージョン
今回構築した仕組みは、WAFのIP Setへの操作をCloudTrailで検知し、「誰が・いつ・どのIPを・ブロック/解除したか」をDynamoDBに記録し、Slackへ通知するものです。
使用するAWSサービスは以下のとおりです。
- AWS WAF :IPブロック・解除の操作対象
- AWS CloudTrail :WAF操作イベントの検知・証跡のS3保存
- Amazon S3 :CloudTrail証跡ログの保存先
- Amazon EventBridge :Lambdaのトリガー
- AWS Lambda :記録・通知の処理
- Amazon DynamoDB :操作履歴の保存
全体の構成は以下のとおりです。

CloudTrail
「証跡」を作成し、「書き込み」のAPIアクティビティのみを記録するようにします。

証跡の作成にあたり、証跡ログを保存するS3バケットの指定が必要になります。今回はWAFのIP set変更を検知するための証跡ログであり、長期間保存する必要はないのでS3のライフサイクルフックで1日のみ保存するようにしました。
EventBridge
以下のルールをEventBridgeに作成し、IP setへの変更を検知します。
ターゲットにLambdaを指定し、DynamoDBへの登録とSlack通知を行います。
|
1 2 3 4 5 6 7 8 9 10 11 |
{ "source": ["aws.wafv2"], "detail-type": ["AWS API Call via CloudTrail"], "detail": { "eventSource": ["wafv2.amazonaws.com"], "eventName": ["UpdateIPSet"], "requestParameters": { "name": ["{IP set名}"] } } } |
Lambda
CloudTrailからのイベントは以下の形式で通知されます。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
{ "version": "0", "id": "aaaabbbb-1111-2222-3333-ccccddddeeee", "detail-type": "AWS API Call via CloudTrail", "source": "aws.wafv2", "account": "123456789012", "time": "2026-06-09T21:00:26Z", "region": "ap-northeast-1", "resources": [], "detail": { "eventVersion": "1.11", "userIdentity": { "type": "IAMUser", "principalId": "AIDAEXAMPLEXXXXXXXX", "arn": "arn:aws:iam::123456789012:user/{AWSユーザー名}", "accountId": "123456789012", "accessKeyId": "ASIAEXAMPLEXXXXXXXX", "userName": "{AWSユーザー名}", "sessionContext": { "attributes": { "creationDate": "2026-06-09T21:00:06Z", "mfaAuthenticated": "true" } } }, "eventTime": "2026-06-09T21:00:26Z", "eventSource": "wafv2.amazonaws.com", "eventName": "UpdateIPSet", "awsRegion": "ap-northeast-1", "sourceIPAddress": "{操作者のIPアドレス}", "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36", "requestParameters": { "name": "{IP set名}", "scope": "REGIONAL", "id": "aaaabbbb-2222-3333-4444-ccccddddeeee", "addresses": [ "{IPアドレス}", "{IPアドレス}", "{IPアドレス}" ], "lockToken": "aaaabbbb-3333-4444-5555-ccccddddeeee" }, "responseElements": { "nextLockToken": "aaaabbbb-4444-5555-6666-ccccddddeeee" }, "requestID": "aaaabbbb-5555-6666-7777-ccccddddeeee", "eventID": "aaaabbbb-6666-7777-8888-ccccddddeeee", "readOnly": false, "eventType": "AwsApiCall", "apiVersion": "2019-04-23", "managementEvent": true, "recipientAccountId": "123456789012", "eventCategory": "Management", "tlsDetails": { "tlsVersion": "TLSv1.3", "cipherSuite": "TLS_AES_128_GCM_SHA256", "clientProvidedHostHeader": "wafv2.ap-northeast-1.amazonaws.com" }, "sessionCredentialFromConsole": "true" } } |
注意が必要な点として、addressesにはIP set変更後のすべてのIPアドレスが格納されています。追加/削除されたIPアドレスを取得するために、変更前のIPアドレスをDynamoDBに格納しておき、変更前後のデータ差分から変更されたIPアドレスを取得しています。
DynamoDB
DynamoDBに以下のようなテーブルを作成し、Lambdaで受け取ったイベント内容からレコードを作成します。
| キー | 型 | 説明 |
|---|---|---|
| ip_cidr | string | WAFに登録されたCIDR (Primary Key) |
| action | string | “block” または “unblock” |
| actor_name | string | 作業者名 |
| requested_at | string (ISO8601) | ブロック/解除を要求した時刻 |
| removed_at | string (ISO8601, nullable) | 実際に解除された時刻 |
これらの内容をLambdaからSlackに通知することで、誰が・いつ・どのIPを・ブロック/解除したのかがわかるようになります。

また、ブロックした理由などの情報をSlackのスレッドに追記しておけば、時間が経った後でもどういった意図で作業したのかを確認しやすくなります。
IP setにIPアドレスを溜めっぱなしにしていませんか?
ところで、一度ブロックしたIPアドレスをそのままにしていませんか?
IP setに登録できる件数の上限は10,000件です。登録件数が多いことによるパフォーマンスへの影響については公式ドキュメントでは確認できませんでしたが、別のリスクも考えられます。ISP(インターネットサービスプロバイダー)によるIPアドレスの譲渡や再割り当てにより、過去にブロックしたIPアドレスが別のユーザーに割り当てられる可能性がゼロではないためです。ブロックしたまま放置していると、無関係なユーザーのアクセスを誤ってブロックしてしまうかもしれません。
そこで上記の仕組みを拡張し、一定期間が経過したIPアドレスを自動で解除する仕組みも実装しました。
システム構成:自動解除追加バージョン

図の右上の「日次処理(ブロック解除)」が新しく追加した部分です。
IP setへの登録後、一定期間が経過したIPアドレスをIP setから解除するLambdaを作成しました。EventBridgeのスケジュールからこのLambdaを日次起動します。
これまでの観測から、ブロック後3ヶ月ほどで同じIPから攻撃が来ることもあったため、余裕を持たせて自動解除までの日数は180日としました。
DynamoDB
DynamoDBのテーブルは最終的に以下のようになりました。
| キー | 型 | 説明 |
|---|---|---|
| ip_cidr | string | WAFに登録されたCIDR (Primary Key) |
| action | string | “block” または “unblock” |
| actor_name | string | 作業者名 |
| applied_at | string (ISO8601) | WAFに反映した時刻 |
| expires_at | string (ISO8601) | 自動解除予定時刻 |
| ipset_id | string | WAF IP set ID |
| ipset_name | string | 対象のIP set名 |
| region | string | WAFリージョン |
| requested_at | string (ISO8601) | ブロック/解除を要求した時刻 |
| removed_at | string (ISO8601, nullable) | 実際に解除された時刻 |
| source | string | “cloudtrail” (手動解除) または “auto-unblocker”(自動解除Lambda) |
| ttl | number (epoch) | DynamoDB TTL(1年後に物理削除) |
この仕組みにより、ブロックから180日が経過したIPアドレスは自動的にIP setから解除されます。また、DynamoDBのTTLを1年に設定しているため、操作履歴は1年間保持されたのち自動で削除されます。
これにより、IP setへの登録件数が際限なく増え続けることを防ぎつつ、一定期間の操作履歴はいつでも確認できる状態を維持できます。
まとめ
今回の仕組みを導入したことで、「誰が・いつ・どのIPを・ブロック/解除したか」をSlack通知でリアルタイムに把握できるようになりました。また、DynamoDBに操作履歴が蓄積されるため、過去の操作を遡って確認したい場合にも役立ちます。
「とりあえずブロックして放置」という運用から一歩進んで、IPブロックの管理を仕組みとして整備できたことは、小さいながらも確実な改善だと感じています。
WAFのIP set運用に課題を感じている方の参考になれば幸いです。






