AWS WAFのRate LimitがブロックしたIPアドレスをSlackに通知する

# やりたいこと
AWS WAFには、Rate Basedルールという機能があり、設定済みのしきい値を超える速度でリクエスト数を送信したIPアドレスをブラックリストに追加することができます。
課題としてはGoogleのクローラー等、アクセスをブロックしたくないリクエストもあるため、
WAFがブロックしたIPアドレスを把握しておく必要がありました。

調査したところ、WAFがブロックしたIPアドレスは、
 `get-rate-based-rule-managed-keys` コマンドで取得可能です。

```
$ aws waf-regional get-rate-based-rule-managed-keys --rule-id <WAFのRuleid>
{
    "ManagedKeys": [
        "***.***.***.***/32"
    ]
}
```

しかし、当該コマンドを実行した時点でブロックされているIPアドレスは取得可能ですが、
**それ以前にブロックされていたものの現在はブロックが解除されているIPアドレスを取得することはできません。**

今回は、Cloudwatch Alerm、SNSを活用してSlack通知するLambdaを実装しました。


# 構成
WAFを導入する前は、ALB -> EC2(Webサーバ)というオーソドックスな構成です。

# WAF導入後の構成イメージ
![AWS Networking.png](https://qiita-image-store.s3.amazonaws.com/0/95324/bc73165e-bded-1d94-9147-24337a29f65e.png)



# 設定
##1. Lambdaの作成
以下の要件で作成しました。

* ランタイム：python3.6
    * boto3, requests, dnspython3 モジュールが必要
* IAM Role：`AWSWAFReadOnlyAccess`と`CloudWatchLogsFullAccess`ポリシーが必要
* Lambdaの環境変数は以下の通り

|環境変数  |値  |補足  |
|---|---|---|
|RULEID|WAFのルールIDを設定|`aws waf-regional list-rate-based-rules`コマンドで確認可能|
|SLACK_WEBHOOK_URL|Slack Incoming Webhooksで取得したWebhook URLを設定||


* コードは以下の通り

```lambda_function.py
import boto3
import json
import os
import requests
import re
from dns import reversename,resolver

def reverse_dns(ip):
    result_ip = re.sub('\/.*',"",ip)
    result_addr = reversename.from_address(result_ip)
    result_record = resolver.query(result_addr, "PTR")[0]
    return result_record

def notification_slack(ip, result_record):
    url = os.environ.get('SLACK_WEBHOOK_URL')
    title = '本番ALBでDOS攻撃を検知しました。'
    text = "遮断したIPアドレスは、 ```%s``` です。\n遮断したドメインは ```%s``` です。" % (ip, result_record)
    color = 'danger'

    payload = {
        'channel': '<通知先のSlackのチャンネル名>',
        'username': '<表示させるユーザ名>',
        'icon_emoji': '<表示させるicon>',
        'attachments': [
            {
                'fallback': title + ' - ' + text,
                'color': color,
                'title': title,
                'text': text
            }
        ]
    }

    headers = {'content-type': 'application/json'}
    requests.post(url, data=json.dumps(payload), headers=headers)

def lambda_handler(event, context):
    ruleid = os.environ.get('RULEID')
    client = boto3.client('waf-regional')
    response = client.get_rate_based_rule_managed_keys(
       RuleId=ruleid
    )
    dict_ip = response.get("ManagedKeys")
    for ip in dict_ip:
        print(ip)
        result_record = reverse_dns(ip)
        notification_slack(ip, result_record)
```

　`get_rate_based_rule_managed_keys`でブロックしているIPアドレスを取得し、
　`dnspython3`モジュールで逆引きして名前解決しています。

##2. SNS
新たにSNSトピックを任意の名前で作成し、`サブスクリプション`にさきほど作成したLambdaを設定します。
この辺りはさくっとできると思うので割愛。

##3. WAF
###3-1. ルールの作成
####3-1-1. token取得
まずはtokenを取得します。　※一定時間が経過するとtokenが切れるので注意。tokenが切れたら再発行してください。

```
$ aws waf-regional get-change-token
{
    "ChangeToken": "************************************"
}
```

####3-1-2. ルールの作成
次にWAFに適用するRuleを作成します。
5分間に同じIPから2000回(最低値が2000です)アクセスがあればブロックするルールです。

```
$ aws waf-regional create-rate-based-rule \
--name <任意> \
--metric-name <任意(英数字のみ)> \
--rate-key IP \
--rate-limit 2000 \
--change-token <取得したChangeToken >
```

###3-2. ACLの作成
####3-2-1. ACLの作成
次にAclを作成します。

```
$ aws waf-regional create-web-acl \
--name <任意> \
--metric-name <任意(英数字のみ)> \
--default-action Type="ALLOW" \
--change-token <取得したChangeToken >
```

####3-2-1. ACLにルールを追加
まず、ACL ID、Rule IDを確認します。

```
$ aws waf-regional list-web-acls
$ aws waf-regional list-rate-based-rules
```
次にACLに作成したルールを追加します。

```
$ aws waf-regional update-web-acl \
--web-acl-id <作成したACL ID> \
--updates 'Action=INSERT,ActivatedRule={Priority=1,RuleId='<作成したRule ID>',Action={Type=ALLOW},Type='RATE_BASED'}' \
--change-token <取得したChangeToken >
```

####3-2-1. ACLとリソース(今回はALB）を紐づけ
作成したACLを適用するリソース、今回はALBをのARNを指定します。

```
$ aws waf-regional associate-web-acl \
--web-acl-id <作成したACL ID> \
--resource-arn <ALBのArn>
```

参考
https://docs.aws.amazon.com/cli/latest/reference/waf-regional/create-rate-based-rule.html
https://dev.classmethod.jp/cloud/aws/aws-waf-with-awscli/

##4.  Cloudwatch
アラームを設定します。
1分間隔で`BlockedRequest`メトリックを監視し、`BlockedRequest`メトリックの合計が1回でも超えればアラートを発報するようにしました。
以下にAWS CLIのコマンドを用いた設定例を記載します。

```
$ aws cloudwatch put-metric-alarm \
--alarm-name "alb-waf-event" \
--namespace "WAF" \
--period "60" \
--evaluation-periods "1" \
--threshold "1" \
--comparison-operator "GreaterThanOrEqualToThreshold" \
--metric-name "BlockedRequest" \
--statistic Sum \
--treat-missing-data notBreaching \
--alarm-actions <2.SNSで作成したSNS ARN>
```

# 通知結果
通知結果のサンプルです。
<img width="657" alt="スクリーンショット 2018-06-01 17.06.34.png" src="https://qiita-image-store.s3.amazonaws.com/0/95324/c3aef378-4203-e61e-cd38-ba5cfa44f528.png">


# 運用してみて
まず安いです。この料金でWAFを導入できるのであれば御の字だと思います。
料金表は、[こちら](https://aws.amazon.com/jp/waf/pricing/)

`Rate limit`に関しては、最初閾値をゆるめに設定して、徐々に絞っていくような運用が良いかなと思います。
この他にも細かな設定も可能なのです。便利！

# 参考記事
https://dev.classmethod.jp/cloud/aws/aws-waf-rate-based-rules/
https://blog.manabusakai.com/2017/07/aws-waf-rate-based-rule/
https://dev.classmethod.jp/cloud/aws/aws-waf-with-awscli/