AWSWAFでブロックログのアラートをフィルタリングしつつSlackへ飛ばす

## 構成
- WAFで攻撃を検知したらSlackへ飛ばす

WAF → KinesisFirehose → Lambda → Slack
　　　　　　　↓
　　　　　　　→ S3バケット 

## WAF

### ルール設定
- ルールは汎用的なものを設定
 - AWSManagedRulesCommonRuleSet
 - AWS-AWSManagedRulesLinuxRuleSet
- DefaultActionはAllow

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/293481/8b52d13d-94b0-caca-5866-0071e96a7a2f.png)

### 接続リソース設定
- 紐づけたいALBを指定

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/293481/46f77d23-78cb-84b6-6674-f56ea02a5a69.png)


### Logging and metrics設定

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/293481/4e3fc237-189f-7247-9c4f-06f126630a49.png)

- KinesisiFirehoseにログ出力するようにする
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/293481/925a7449-40cf-09c1-7e2c-b0986bce8b11.png)

## KinesisFirehose

### Transform設定
- kinesisFirehoseのJson出力をLambdaへ渡す
    - Lambda側でのトリガーでKinesisFirehoseを指定することはできない

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/293481/8cdc410d-ae54-f5bb-0cba-18f98051201c.png)

## Lambda設定
- トリガー、送信先は指定なし

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/293481/f1c61ebf-e301-338f-da46-02f6282dffce.png)

### ソースコード
- ランタイムはpython3.8
- AWSManagedRulesCommonRuleSetの中で`NoUserAgent_HEADER`と`UserAgent_BadBots_HEADER`がやたらでるので、Slackへは飛ばしたくない
- それ以外のルールはSlackへ飛ばす

```python
import base64
import json
import os
import urllib.request

def lambda_handler(event, context):
    output = []

    for record in event['records']:
        output_record = {
            'recordId': record['recordId'],
            'result': 'Ok',
            'data': record['data']
        }
        output.append(output_record)

        a = base64.b64decode(record['data'])
        b = json.loads(a)
        # if b['action'] != 'ALLOW':
        #     print("action not ALLOW")
        if b["ruleGroupList"][0]["terminatingRule"]["ruleId"] != 'NoUserAgent_HEADER' and b["ruleGroupList"][0]["terminatingRule"]["ruleId"] != 'UserAgent_BadBots_HEADER':
            print("terminatingRule is not NoUserAgent_HEADER")
            response = post_slack(b)
            print(output_record)

    return {'records': output}

def post_slack(msg):
    send_data = {
        "username": "notify_slack",
        "icon_emoji": ":vampire:",
        "color":"#D00000",
        "text": "WAF攻撃検知",
        "attachments":[
            {
                "fallback":"fallback Test",
                "color":"#D00000",
                "fields":[
                    {
                        "title":"詳細内容",
                        "value":str(msg)
                    }
                ]
            }
        ]
    }


    send_text = "payload=" + json.dumps(send_data)
    # send_text = "payload=" + json.dumps(jsondata)

    method = 'POST'
    headers = {'Content-Type': 'application/json'}
    WEB_HOOK_URL = os.environ['WEBHOOK_URL']

    request = urllib.request.Request(
        WEB_HOOK_URL,
        data=send_text.encode('utf-8'),
        method=method
    )
    with urllib.request.urlopen(request) as response:
        response_body = response.read().decode('utf-8')
        print("send ok")
```

### 環境変数
- SlackのWebhookURLを指定する

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/293481/05faf0fe-1146-1ac7-6936-ca756b58f62e.png)

### 動作確認用のテストデータ
- kinesisFirehoseから送られるであろうデータ形式
- dataはkinesisFirehoseからs3バケットに出力されているWAFのログをbase64エンコードしたもの
- lambda関数では、data部分をデコードし、terminatingRuleを判定するようにしている

```json
{
  "invocationId": "invocationIdExample",
  "deliverySteamArn": "arn:aws:kinesis:EXAMPLE",
  "region": "ap-northeast-1",
  "records": [
    {
      "recordId": "49546986683135544286507457936321625675700192471156785154",
      "approximateArrivalTimestamp": 1495072949453,
      "kinesisRecordMetadata": {
        "sequenceNumber": "49545115243490985018280067714973144582180062593244200961",
        "subsequenceNumber": "123456",
        "partitionKey": "partitionKey-03",
        "shardId": "shardId-000000000000",
        "approximateArrivalTimestamp": 1495072949453
      },
      "data": "ew0KCSJ0aW1lc3RhbXAiOiAxNjA0Nzc0MzM1MTEyLA0KCSJmb3JtYXRWZXJzaW9uIjogMSwNCgkid2ViYWNsSWQiOiAiYXJuOmF3czp3YWZ2MjphcC1ub3J0aGVhc3QtMToxMTExMTExMTExMTpyZWdpb25hbC93ZWJhY2wvdmFtZGVtaWMtd2FmdjIveHh4eHh4eHgtY2UwYi00ZWYwLTkxZGUtYjQ0OTg1NmFmNDUxIiwNCgkidGVybWluYXRpbmdSdWxlSWQiOiAiQVdTTWFuYWdlZFJ1bGVzQ29tbW9uUnVsZVNldCIsDQoJInRlcm1pbmF0aW5nUnVsZVR5cGUiOiAiTUFOQUdFRF9SVUxFX0dST1VQIiwNCgkiYWN0aW9uIjogIkJMT0NLIiwNCgkidGVybWluYXRpbmdSdWxlTWF0Y2hEZXRhaWxzIjogW10sDQoJImh0dHBTb3VyY2VOYW1lIjogIkFMQiIsDQoJImh0dHBTb3VyY2VJZCI6ICIxMTExMTExMTExMS1hcHAvdmFtZGVtaWMtZGV2LWJ1c2luZXNzLWRldjItYWxiLzI4YWU3NjcyYzExMTExMWRjIiwNCgkicnVsZUdyb3VwTGlzdCI6IFsNCgkJew0KCQkJInJ1bGVHcm91cElkIjogIkFXUyNBV1NNYW5hZ2VkUnVsZXNDb21tb25SdWxlU2V0IiwNCgkJCSJ0ZXJtaW5hdGluZ1J1bGUiOiB7DQoJCQkJInJ1bGVJZCI6ICJOb1VzZXJBZ2VudF9IRUFERVIiLA0KCQkJCSJhY3Rpb24iOiAiQkxPQ0siLA0KCQkJCSJydWxlTWF0Y2hEZXRhaWxzIjogbnVsbA0KCQkJfSwNCgkJCSJub25UZXJtaW5hdGluZ01hdGNoaW5nUnVsZXMiOiBbXSwNCgkJCSJleGNsdWRlZFJ1bGVzIjogbnVsbA0KCQl9DQoJXSwNCgkicmF0ZUJhc2VkUnVsZUxpc3QiOiBbXSwNCgkibm9uVGVybWluYXRpbmdNYXRjaGluZ1J1bGVzIjogW10sDQoJImh0dHBSZXF1ZXN0Ijogew0KCQkiY2xpZW50SXAiOiAiMTI2LjIwOS4yMjEuNCIsDQoJCSJjb3VudHJ5IjogIkpQIiwNCgkJImhlYWRlcnMiOiBbDQoJCQl7DQoJCQkJIm5hbWUiOiAiSG9zdCIsDQoJCQkJInZhbHVlIjogImRldjIudmFtZGVtaWMuanAiDQoJCQl9LA0KCQkJew0KCQkJCSJuYW1lIjogIkFjY2VwdCIsDQoJCQkJInZhbHVlIjogIiovKiINCgkJCX0NCgkJXSwNCgkJInVyaSI6ICIvIiwNCgkJImFyZ3MiOiAiIiwNCgkJImh0dHBWZXJzaW9uIjogIkhUVFAvMS4xIiwNCgkJImh0dHBNZXRob2QiOiAiR0VUIiwNCgkJInJlcXVlc3RJZCI6ICIxLTVmYTZlOWJmLTIyMmU3NmNkNWJkNDQyYTEyYjg5YmM5YSINCgl9DQp9"
    }
  ]
}
```

## テスト
- こちらの２パターンのテストを行い、挙動を見る
    - https://qiita.com/yuta_vamdemic/items/65a9d04edb38a9087c51

## 参考
https://dev.classmethod.jp/articles/aws-waf-block-log-pipeline/