Help us understand the problem. What is going on with this article?

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

More than 1 year has passed since last update.

やりたいこと

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

設定

1. Lambdaの作成

以下の要件で作成しました。

  • ランタイム:python3.6
    • boto3, requests, dnspython3 モジュールが必要
  • IAM Role:AWSWAFReadOnlyAccessCloudWatchLogsFullAccessポリシーが必要
  • 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>

通知結果

通知結果のサンプルです。
スクリーンショット 2018-06-01 17.06.34.png

運用してみて

まず安いです。この料金でWAFを導入できるのであれば御の字だと思います。
料金表は、こちら

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/

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away