構成
- WAFで攻撃を検知したらSlackへ飛ばす
WAF → KinesisFirehose → Lambda → Slack
↓
→ S3バケット
WAF
ルール設定
- ルールは汎用的なものを設定
- AWSManagedRulesCommonRuleSet
- AWS-AWSManagedRulesLinuxRuleSet
- DefaultActionはAllow
接続リソース設定
- 紐づけたいALBを指定
Logging and metrics設定
KinesisiFirehoseにログ出力するようにする
KinesisFirehose
Transform設定
kinesisFirehoseのJson出力をLambdaへ渡す
- Lambda側でのトリガーでKinesisFirehoseを指定することはできない
Lambda設定
- トリガー、送信先は指定なし
ソースコード
- ランタイムはpython3.8
- AWSManagedRulesCommonRuleSetの中で
NoUserAgent_HEADER
とUserAgent_BadBots_HEADER
がやたらでるので、Slackへは飛ばしたくない - それ以外のルールはSlackへ飛ばす
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 62 63 |
<span class="kn">import</span> <span class="nn">base64</span> <span class="kn">import</span> <span class="nn">json</span> <span class="kn">import</span> <span class="nn">os</span> <span class="kn">import</span> <span class="nn">urllib.request</span> <span class="k">def</span> <span class="nf">lambda_handler</span><span class="p">(</span><span class="n">event</span><span class="p">,</span> <span class="n">context</span><span class="p">):</span> <span class="n">output</span> <span class="o">=</span> <span class="p">[]</span> <span class="k">for</span> <span class="n">record</span> <span class="ow">in</span> <span class="n">event</span><span class="p">[</span><span class="s">'records'</span><span class="p">]:</span> <span class="n">output_record</span> <span class="o">=</span> <span class="p">{</span> <span class="s">'recordId'</span><span class="p">:</span> <span class="n">record</span><span class="p">[</span><span class="s">'recordId'</span><span class="p">],</span> <span class="s">'result'</span><span class="p">:</span> <span class="s">'Ok'</span><span class="p">,</span> <span class="s">'data'</span><span class="p">:</span> <span class="n">record</span><span class="p">[</span><span class="s">'data'</span><span class="p">]</span> <span class="p">}</span> <span class="n">output</span><span class="p">.</span><span class="n">append</span><span class="p">(</span><span class="n">output_record</span><span class="p">)</span> <span class="n">a</span> <span class="o">=</span> <span class="n">base64</span><span class="p">.</span><span class="n">b64decode</span><span class="p">(</span><span class="n">record</span><span class="p">[</span><span class="s">'data'</span><span class="p">])</span> <span class="n">b</span> <span class="o">=</span> <span class="n">json</span><span class="p">.</span><span class="n">loads</span><span class="p">(</span><span class="n">a</span><span class="p">)</span> <span class="c1"># if b['action'] != 'ALLOW': </span> <span class="c1"># print("action not ALLOW") </span> <span class="k">if</span> <span class="n">b</span><span class="p">[</span><span class="s">"ruleGroupList"</span><span class="p">][</span><span class="mi">0</span><span class="p">][</span><span class="s">"terminatingRule"</span><span class="p">][</span><span class="s">"ruleId"</span><span class="p">]</span> <span class="o">!=</span> <span class="s">'NoUserAgent_HEADER'</span> <span class="ow">and</span> <span class="n">b</span><span class="p">[</span><span class="s">"ruleGroupList"</span><span class="p">][</span><span class="mi">0</span><span class="p">][</span><span class="s">"terminatingRule"</span><span class="p">][</span><span class="s">"ruleId"</span><span class="p">]</span> <span class="o">!=</span> <span class="s">'UserAgent_BadBots_HEADER'</span><span class="p">:</span> <span class="k">print</span><span class="p">(</span><span class="s">"terminatingRule is not NoUserAgent_HEADER"</span><span class="p">)</span> <span class="n">response</span> <span class="o">=</span> <span class="n">post_slack</span><span class="p">(</span><span class="n">b</span><span class="p">)</span> <span class="k">print</span><span class="p">(</span><span class="n">output_record</span><span class="p">)</span> <span class="k">return</span> <span class="p">{</span><span class="s">'records'</span><span class="p">:</span> <span class="n">output</span><span class="p">}</span> <span class="k">def</span> <span class="nf">post_slack</span><span class="p">(</span><span class="n">msg</span><span class="p">):</span> <span class="n">send_data</span> <span class="o">=</span> <span class="p">{</span> <span class="s">"username"</span><span class="p">:</span> <span class="s">"notify_slack"</span><span class="p">,</span> <span class="s">"icon_emoji"</span><span class="p">:</span> <span class="s">":vampire:"</span><span class="p">,</span> <span class="s">"color"</span><span class="p">:</span><span class="s">"#D00000"</span><span class="p">,</span> <span class="s">"text"</span><span class="p">:</span> <span class="s">"WAF攻撃検知"</span><span class="p">,</span> <span class="s">"attachments"</span><span class="p">:[</span> <span class="p">{</span> <span class="s">"fallback"</span><span class="p">:</span><span class="s">"fallback Test"</span><span class="p">,</span> <span class="s">"color"</span><span class="p">:</span><span class="s">"#D00000"</span><span class="p">,</span> <span class="s">"fields"</span><span class="p">:[</span> <span class="p">{</span> <span class="s">"title"</span><span class="p">:</span><span class="s">"詳細内容"</span><span class="p">,</span> <span class="s">"value"</span><span class="p">:</span><span class="nb">str</span><span class="p">(</span><span class="n">msg</span><span class="p">)</span> <span class="p">}</span> <span class="p">]</span> <span class="p">}</span> <span class="p">]</span> <span class="p">}</span> <span class="n">send_text</span> <span class="o">=</span> <span class="s">"payload="</span> <span class="o">+</span> <span class="n">json</span><span class="p">.</span><span class="n">dumps</span><span class="p">(</span><span class="n">send_data</span><span class="p">)</span> <span class="c1"># send_text = "payload=" + json.dumps(jsondata) </span> <span class="n">method</span> <span class="o">=</span> <span class="s">'POST'</span> <span class="n">headers</span> <span class="o">=</span> <span class="p">{</span><span class="s">'Content-Type'</span><span class="p">:</span> <span class="s">'application/json'</span><span class="p">}</span> <span class="n">WEB_HOOK_URL</span> <span class="o">=</span> <span class="n">os</span><span class="p">.</span><span class="n">environ</span><span class="p">[</span><span class="s">'WEBHOOK_URL'</span><span class="p">]</span> <span class="n">request</span> <span class="o">=</span> <span class="n">urllib</span><span class="p">.</span><span class="n">request</span><span class="p">.</span><span class="n">Request</span><span class="p">(</span> <span class="n">WEB_HOOK_URL</span><span class="p">,</span> <span class="n">data</span><span class="o">=</span><span class="n">send_text</span><span class="p">.</span><span class="n">encode</span><span class="p">(</span><span class="s">'utf-8'</span><span class="p">),</span> <span class="n">method</span><span class="o">=</span><span class="n">method</span> <span class="p">)</span> <span class="k">with</span> <span class="n">urllib</span><span class="p">.</span><span class="n">request</span><span class="p">.</span><span class="n">urlopen</span><span class="p">(</span><span class="n">request</span><span class="p">)</span> <span class="k">as</span> <span class="n">response</span><span class="p">:</span> <span class="n">response_body</span> <span class="o">=</span> <span class="n">response</span><span class="p">.</span><span class="n">read</span><span class="p">().</span><span class="n">decode</span><span class="p">(</span><span class="s">'utf-8'</span><span class="p">)</span> <span class="k">print</span><span class="p">(</span><span class="s">"send ok"</span><span class="p">)</span> |
環境変数
- SlackのWebhookURLを指定する
動作確認用のテストデータ
- kinesisFirehoseから送られるであろうデータ形式
- dataはkinesisFirehoseからs3バケットに出力されているWAFのログをbase64エンコードしたもの
- lambda関数では、data部分をデコードし、terminatingRuleを判定するようにしている
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
<span class="p">{</span> <span class="nl">"invocationId"</span><span class="p">:</span> <span class="s2">"invocationIdExample"</span><span class="p">,</span> <span class="nl">"deliverySteamArn"</span><span class="p">:</span> <span class="s2">"arn:aws:kinesis:EXAMPLE"</span><span class="p">,</span> <span class="nl">"region"</span><span class="p">:</span> <span class="s2">"ap-northeast-1"</span><span class="p">,</span> <span class="nl">"records"</span><span class="p">:</span> <span class="p">[</span> <span class="p">{</span> <span class="nl">"recordId"</span><span class="p">:</span> <span class="s2">"49546986683135544286507457936321625675700192471156785154"</span><span class="p">,</span> <span class="nl">"approximateArrivalTimestamp"</span><span class="p">:</span> <span class="mi">1495072949453</span><span class="p">,</span> <span class="nl">"kinesisRecordMetadata"</span><span class="p">:</span> <span class="p">{</span> <span class="nl">"sequenceNumber"</span><span class="p">:</span> <span class="s2">"49545115243490985018280067714973144582180062593244200961"</span><span class="p">,</span> <span class="nl">"subsequenceNumber"</span><span class="p">:</span> <span class="s2">"123456"</span><span class="p">,</span> <span class="nl">"partitionKey"</span><span class="p">:</span> <span class="s2">"partitionKey-03"</span><span class="p">,</span> <span class="nl">"shardId"</span><span class="p">:</span> <span class="s2">"shardId-000000000000"</span><span class="p">,</span> <span class="nl">"approximateArrivalTimestamp"</span><span class="p">:</span> <span class="mi">1495072949453</span> <span class="p">},</span> <span class="nl">"data"</span><span class="p">:</span> <span class="s2">"ew0KCSJ0aW1lc3RhbXAiOiAxNjA0Nzc0MzM1MTEyLA0KCSJmb3JtYXRWZXJzaW9uIjogMSwNCgkid2ViYWNsSWQiOiAiYXJuOmF3czp3YWZ2MjphcC1ub3J0aGVhc3QtMToxMTExMTExMTExMTpyZWdpb25hbC93ZWJhY2wvdmFtZGVtaWMtd2FmdjIveHh4eHh4eHgtY2UwYi00ZWYwLTkxZGUtYjQ0OTg1NmFmNDUxIiwNCgkidGVybWluYXRpbmdSdWxlSWQiOiAiQVdTTWFuYWdlZFJ1bGVzQ29tbW9uUnVsZVNldCIsDQoJInRlcm1pbmF0aW5nUnVsZVR5cGUiOiAiTUFOQUdFRF9SVUxFX0dST1VQIiwNCgkiYWN0aW9uIjogIkJMT0NLIiwNCgkidGVybWluYXRpbmdSdWxlTWF0Y2hEZXRhaWxzIjogW10sDQoJImh0dHBTb3VyY2VOYW1lIjogIkFMQiIsDQoJImh0dHBTb3VyY2VJZCI6ICIxMTExMTExMTExMS1hcHAvdmFtZGVtaWMtZGV2LWJ1c2luZXNzLWRldjItYWxiLzI4YWU3NjcyYzExMTExMWRjIiwNCgkicnVsZUdyb3VwTGlzdCI6IFsNCgkJew0KCQkJInJ1bGVHcm91cElkIjogIkFXUyNBV1NNYW5hZ2VkUnVsZXNDb21tb25SdWxlU2V0IiwNCgkJCSJ0ZXJtaW5hdGluZ1J1bGUiOiB7DQoJCQkJInJ1bGVJZCI6ICJOb1VzZXJBZ2VudF9IRUFERVIiLA0KCQkJCSJhY3Rpb24iOiAiQkxPQ0siLA0KCQkJCSJydWxlTWF0Y2hEZXRhaWxzIjogbnVsbA0KCQkJfSwNCgkJCSJub25UZXJtaW5hdGluZ01hdGNoaW5nUnVsZXMiOiBbXSwNCgkJCSJleGNsdWRlZFJ1bGVzIjogbnVsbA0KCQl9DQoJXSwNCgkicmF0ZUJhc2VkUnVsZUxpc3QiOiBbXSwNCgkibm9uVGVybWluYXRpbmdNYXRjaGluZ1J1bGVzIjogW10sDQoJImh0dHBSZXF1ZXN0Ijogew0KCQkiY2xpZW50SXAiOiAiMTI2LjIwOS4yMjEuNCIsDQoJCSJjb3VudHJ5IjogIkpQIiwNCgkJImhlYWRlcnMiOiBbDQoJCQl7DQoJCQkJIm5hbWUiOiAiSG9zdCIsDQoJCQkJInZhbHVlIjogImRldjIudmFtZGVtaWMuanAiDQoJCQl9LA0KCQkJew0KCQkJCSJuYW1lIjogIkFjY2VwdCIsDQoJCQkJInZhbHVlIjogIiovKiINCgkJCX0NCgkJXSwNCgkJInVyaSI6ICIvIiwNCgkJImFyZ3MiOiAiIiwNCgkJImh0dHBWZXJzaW9uIjogIkhUVFAvMS4xIiwNCgkJImh0dHBNZXRob2QiOiAiR0VUIiwNCgkJInJlcXVlc3RJZCI6ICIxLTVmYTZlOWJmLTIyMmU3NmNkNWJkNDQyYTEyYjg5YmM5YSINCgl9DQp9"</span> <span class="p">}</span> <span class="p">]</span> <span class="p">}</span> |
テスト
こちらの2パターンのテストを行い、挙動を見る
参考
https://dev.classmethod.jp/articles/aws-waf-block-log-pipeline/