CloudFrontのアクセスログを使って特定IPからの不正リクエストをAWS WAFでブロックする - 初期設定編

# はじめに
WAFのルールで **Rate-based rule** がありますが、これだと**「5分間に同じIPから設定した回数を超えたリクエスト」**でしかブロックができません。
これで十分なら良いのですが、ちゃんと設定値を考えないと場合によったら正常なアクセスに対してもブロックしてしまうかもしれないです。
そこで、**「同じIPからのリクエストかつ、指定のエラーコードが設定した回数を超えた場合にブロック」**をしたい場合の設定を行います。

内容としてはAWS公式ドキュメントにある[「チュートリアル: 不正なリクエストを送信する IP アドレスのブロック」](https://docs.aws.amazon.com/ja_jp/waf/latest/developerguide/tutorials-4xx-blocking.html)になります。
これを自分なりに噛み砕いて解説しました。あとLambdaのプログラムで一部おかしいと思われるところがあったのでそこを修正しています。


# 概要
![](https://docs.aws.amazon.com/ja_jp/waf/latest/developerguide/images/tutorial-4xx-blacklist.png)

システムの流れとしてはまず、
1. CloudFrontのアクセスログをS3バケットへ保存します。
2. S3イベントからバケットにログが出力されたのをトリガーにLambdaが動きます。
3. Lambda処理で1分間に指定したレスポンスコードが閾値を超えたらWAFのIPブロックリストへ登録します。
4. LambdaのログをCloudWatchへ出力します。
また、「3.」では一定時間経過した場合IPブロックリストから除外する処理も行われています。デフォルト4時間(14400sec)です。

[注意点]*
チェック自体は1分間に対するレスポンスコード数ですが、CloudFrontのログは１分ごとに出力されるわけではありません。
[「ログファイル配信のタイミング」](https://docs.aws.amazon.com/ja_jp/AmazonCloudFront/latest/DeveloperGuide/AccessLogs.html#access-logs-timing)によると `「1 時間に最大で数回配信」を記載があります。また、「最大で 24 時間遅れることもあります」` とも記載されています。
そこまで気にする必要はないと思いますが、頭に入れて置いた方が良いかもです。

# スタックの作成
CloudFormationのテンプレートが用意されているのでそれを元に作成します。
今回は東京リージョンで行いますので[「アジアパシフィック (東京) でスタックを作成する」](https://console.aws.amazon.com/cloudformation/home?region=ap-northeast-1#cstack=sn%7eBadBehavingIP%7cturl%7ehttps:%2f%2fs3.amazonaws.com/awswaf.ap-northeast-1/block-bad-behaving-ips/block-bad-behaving-ips_template.json)をクリックしてスタックを作成してください。
ドキュメントには他に「[バージニア](https://console.aws.amazon.com/cloudformation/home?region=us-east-1#cstack=sn%7eBadBehavingIP%7cturl%7ehttps:%2f%2fs3.amazonaws.com/awswaf.us-east-1/block-bad-behaving-ips/block-bad-behaving-ips_template.json)」「[オレゴン](https://console.aws.amazon.com/cloudformation/home?region=us-west-2#cstack=sn%7eBadBehavingIP%7cturl%7ehttps:%2f%2fs3.amazonaws.com/awswaf.us-west-2/block-bad-behaving-ips/block-bad-behaving-ips_template.json)」「[アイルランド](https://console.aws.amazon.com/cloudformation/home?region=eu-west-1#cstack=sn%7eBadBehavingIP%7cturl%7ehttps:%2f%2fs3.amazonaws.com/awswaf.eu-west-1/block-bad-behaving-ips/block-bad-behaving-ips_template.json)」のテンプレートも用意されています。

またテンプレートで作成されるのは、

- WAF(ウェブACL、IPセット、ルール)
- S3バケット
- Lambda関数
- IAMロール

になります。

## テンプレートの選択
するとCloudFormationのスタック作成画面へ遷移します。
そこで表示される画面ではテンプレート選択がすでにされていることを確認してください。
![スクリーンショット 2019-03-05 12.27.36.png](https://qiita-image-store.s3.amazonaws.com/0/82090/c489419e-9bf0-4f11-c4b4-4882fb2f26f2.png)
もし、何も入力されていない場合やリンクからではなく管理コンソールからCloudFormationを新規に作成した場合は以下のURLを入力すればOKです。
`https://s3.amazonaws.com/awswaf.ap-northeast-1/block-bad-behaving-ips/block-bad-behaving-ips_template.json`

## 詳細設定
次に、詳細設定になります。ここではCloudFrontアクセスログ出力用のバケットを新規に作成します。
既存のバケットを利用したい場合もまずはここで新規に作成する必要があります。
![スクリーンショット 2019-03-05 12.37.57.png](https://qiita-image-store.s3.amazonaws.com/0/82090/f7f3128e-f585-a69b-4a91-58750e23f632.png)

## オプション
ここは何も設定せず全てデフォルトの状態で次へ進みます。

## 確認
IAMリソースが作成される場合があることを承諾し、作成をクリックします。
![スクリーンショット 2019-03-05 12.34.42.png](https://qiita-image-store.s3.amazonaws.com/0/82090/84a0fd8e-5da7-4d43-95ec-c03af9a7214d.png)

とりあえず *CREATE_IN_PROGRESS* となっている間は色々作成中です。
![スクリーンショット 2019-03-05 12.38.37.png](https://qiita-image-store.s3.amazonaws.com/0/82090/62507dcb-69fc-66e0-b70a-ccadc08af40d.png)

*CREATE_COMPOLETE* まで数分かかりますが、完了したらテンプレートでいくつか情報は出力されるようになっているので、出力タブで情報を確認することができます。
![スクリーンショット 2019-03-05 12.50.26.png](https://qiita-image-store.s3.amazonaws.com/0/82090/977c2d8f-db68-4e16-3b06-fffb66378e90.png)

>RequestThreshold：閾値
CloudFrontAccessLogBucket：CloudFrontアクセスログバケット
AutoBlockIPSetID：Lambdaから追加されるIPセットのID
ManualBlockIPSetID：手動で追加したい場合のIPセットのID
WAFBlockPeriod：ブロックし続ける時間


# ウェブACLをCloudFrontへ関連づける
今回のスタックで作成されたウェブACLの名前は **Malicious Requesters** になります。
CloudFormationのスタックではウェブACLは表示されませんがIPセットから追うとわかります。

このウェブACLを関連付けるには「Rules」タブを選択し、下部にある**Add association** から行うことができます。
![スクリーンショット 2019-03-05 15.25.09.png](https://qiita-image-store.s3.amazonaws.com/0/82090/78c7a09d-a645-55c6-4031-6f8758c1b347.png)
Resource選択のポップアップが表示されるので関連付けたいリソースを選択して**Add**をクリックすれば完了です。
![スクリーンショット 2019-03-05 15.35.57.png](https://qiita-image-store.s3.amazonaws.com/0/82090/d9059c0a-8be5-f327-6396-71ef92314a30.png)

またはCloudFrontから選択することも可能です。
![スクリーンショット 2019-03-05 15.51.36.png](https://qiita-image-store.s3.amazonaws.com/0/82090/1818b879-6e05-6cb4-aae8-8974580f73b2.png)


# CloudFrontログ用バケットの設定
CloudFrontで出力されるログを先ほどスタックで作成したS3バケットへ保存されるように設定を行います。
![スクリーンショット 2019-03-05 15.57.12.png](https://qiita-image-store.s3.amazonaws.com/0/82090/772b0fb8-7058-04aa-8123-00165892ce55.png)

以上で設定は完了です。


# Lambdaプログラム修正
テストしたところ、プログラムの一部におかしな所があったのでそこを修正します。
137〜139行目あたりにあるif文の処理になります。

```python:修正前
                    if total_diff_sec > (BLACKLIST_BLOCK_PERIOD):
                        print "[merge_current_blocked_requesters] \t\tExpired BLOCK %s rule"%k
                        outstanding_requesters['block'][k] = v
```

```python:修正後
                    if total_diff_sec > (BLACKLIST_BLOCK_PERIOD):
                        print "[merge_current_blocked_requesters] \t\tExpired BLOCK %s rule"%k
               ココ→ else:
                        outstanding_requesters['block'][k] = v
```


# テスト
以上で設定は完了したので実際にテストしてみます。
サンプルとなる[アクセスログファイル](https://s3.amazonaws.com/awswaf.us-east-1/block-bad-behaving-ips/access_log_sample.gz)をAWS公式ドキュメントからダウンロードし、先ほど指定したS3バケットへアップロードします。


# 確認
「AWS WAF > IP address > Auto Block Set」を確認します。
サンプルをそのままアップした場合、以下のようにリストに追加されました。
![スクリーンショット 2019-03-15 13.32.20.png](https://qiita-image-store.s3.amazonaws.com/0/82090/8d39085a-8358-06ed-6c43-a92479a7c97e.png)

また、S3バケットのルートに *current_outstanding_requesters.json* というファイルが作成されています。
このファイルでは上の画像のようにIPブロックリストに追加されたIPごとのリクエスト数とリストに追加した日時を確認することができます。

```json:current_outstanding_requesters.json
{"block": {"203.0.113.0": {"max_req_per_min": 70, "updated_at": "2019-03-15 04:22:22"}, "198.51.100.0": {"max_req_per_min": 66, "updated_at": "2019-03-15 04:22:22"}, "192.0.2.0": {"max_req_per_min": 53, "updated_at": "2019-03-15 04:22:22"}}}
```


# おわりに
チュートリアルを行なっていて感じるのが、CFnのテンプレートを利用するのはとても便利なのですが、ちゃんと理解しなくても「なんとなく」で出来てしまうのが気をつけないといけないなという点です。
今回、Lambda関数のプログラムに不具合があったおかげで、より理解を深めることができて良かったです。

エラーコードや対象ログバケットなどの変更を行いたい場合についてはこちらをご参照ください。
[CloudFrontのアクセスログを使って特定IPからの不正リクエストをAWS WAFでブロックする - 初期設定編](https://qiita.com/kooohei/items/1821bae6600e1244c1c1)