WAFログをCloudWatch Logsと連携して海外からの攻撃を自動検知して通知する
Posted On 2025-02-16
ALBにWAFを設定し、日本以外からのアクセスをブロックしてCloudWatch Logsへ送信、メトリックフィルターとSNSを使ったメール通知を実現。結果としてロシアからの大規模アクセスなどBot攻撃を可視化・アラートでき、運用監視の有用性が確認できた。
目次
はじめに
- DOPの問題に出てきた、WAFのログをCloudWatch Logsに入れて、メトリックフィルターからCloudWatch Alarmを作って、SNSでメール通知というのをやってみた
- WAFをCloudWatchに吐き出るところの名前の制約のみハマるが、割とハマらずにできて使い所は多め
アーキテクチャー
- ALBにWAFをつけるだけ。ALBがEC2など付けずにリスナールールに固定のレスポンスを返す単純なもの
- WAFはルールが悩ましいが、「日本以外からのアクセスをブロックする」でやってみた。最初はBotのアクセスをブロックだったが、このアーキテクチャーだと結構貫通するので、海外アクセスのブロックとした
- 10分間に10回以上海外からのアクセスがあったらメールで通知というもの
コード
そこまで長くないので全体のTerraformのコードを示す。ポイントは以下の通り
- WAFの定義は「JPからのアクセスでないものをブロックする」という書き方。ここはIAMポリシーと同じように「Block ~ not condition ◯◯」でOK。
- WAFのログをCloudWatch Logsに転送するときに名前の制約がある。aws-waf-logs-で始める必要がある。参考
- CloudWatch Log→メトリクスはメトリックフィルターで実現する。
"{ $.terminatingRuleId = \"BlockNonJapanRule\" && $.action = \"BLOCK\" }"
tぽいパターンで、特定のパターン(このケースではBlockNonJapanRule
)がブロックしたケースを絞り込むことができる - メトリクスからアラームはよくある書き方(Auto Scalingとかと同じ)
########################################
# ALB 作成用セキュリティグループ
########################################
resource "aws_security_group" "alb_sg" {
name = "alb-sg"
description = "Security group for ALB"
vpc_id = var.vpc_id
ingress {
description = "Allow all HTTP(80) inbounds"
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
egress {
description = "Allow all outbands"
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
########################################
# ALB の作成とリスナー設定(固定応答:HTML)
########################################
resource "aws_lb" "alb" {
name = "alb"
internal = false
load_balancer_type = "application"
security_groups = [aws_security_group.alb_sg.id]
subnets = var.public_subnet_ids
}
resource "aws_lb_listener" "http_listener" {
load_balancer_arn = aws_lb.alb.arn
port = "80"
protocol = "HTTP"
default_action {
type = "fixed-response"
fixed_response {
content_type = "text/html"
# 可能なら HTML を返す(以下はシンプルな HTML サンプル)
message_body = "<html><body><h1>Welcome</h1><p>This is the default HTML response.</p></body></html>"
status_code = "200"
}
}
}
########################################
# WAFv2 Web ACL の作成(日本以外からのアクセスをブロック)
########################################
resource "aws_wafv2_web_acl" "waf" {
name = "alb-waf"
description = "WAF to allow only JP traffic and block others"
scope = "REGIONAL"
# デフォルトでは ALLOW
default_action {
allow {}
}
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "albWaf"
sampled_requests_enabled = true
}
rule {
name = "BlockNonJapanRule"
priority = 1
# このルールにマッチしたらブロック
action {
block {}
}
# not_statement を使うことで、"country_codes = [\"JP\"]" に合致しない国をブロック
statement {
not_statement {
statement {
geo_match_statement {
country_codes = ["JP"]
}
}
}
}
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "BlockNonJapanMetric"
sampled_requests_enabled = true
}
}
}
# ALB に対して WAF を紐付ける
resource "aws_wafv2_web_acl_association" "waf_association" {
resource_arn = aws_lb.alb.arn
web_acl_arn = aws_wafv2_web_acl.waf.arn
}
########################################
# WAF ログを CloudWatch Logs に出力
########################################
# WAF 用ロググループ(任意の保持日数を設定) (aws-waf-logsから始まる必要があり)
# https://dev.classmethod.jp/articles/aws-waf-log-support-s3-and-cloudwatch-logs/
resource "aws_cloudwatch_log_group" "waf_logs" {
name = "aws-waf-logs-alb"
retention_in_days = 7
}
# WAF ログ設定
resource "aws_wafv2_web_acl_logging_configuration" "waf_logging" {
resource_arn = aws_wafv2_web_acl.waf.arn
log_destination_configs = [aws_cloudwatch_log_group.waf_logs.arn]
}
########################################
# CloudWatch メトリクスフィルターとアラーム設定
# (日本以外のアクセスがブロックされた回数を集計し、10分で10回以上なら通知)
########################################
resource "aws_cloudwatch_log_metric_filter" "non_jp_block_filter" {
name = "WAFNonJapanBlockMetric"
log_group_name = aws_cloudwatch_log_group.waf_logs.name
# BlockNonJapanRule で BLOCK されたログのみカウント
pattern = "{ $.terminatingRuleId = \"BlockNonJapanRule\" && $.action = \"BLOCK\" }"
metric_transformation {
name = "NonJapanBlockCount"
namespace = "WAF"
value = "1"
}
}
# SNS トピック作成(アラーム通知用)
resource "aws_sns_topic" "waf_alarm_topic" {
name = "waf-nonjp-alarm-topic"
}
# SNS トピックにメールアドレスをサブスクライブ
resource "aws_sns_topic_subscription" "email_subscription" {
topic_arn = aws_sns_topic.waf_alarm_topic.arn
protocol = "email"
endpoint = var.notification_email
}
# CloudWatch アラーム作成
resource "aws_cloudwatch_metric_alarm" "non_jp_block_alarm" {
alarm_name = "WAFNonJapanBlockAlarm"
alarm_description = "10分間に日本以外からのアクセスで10回以上ブロックされた場合に通知"
metric_name = aws_cloudwatch_log_metric_filter.non_jp_block_filter.metric_transformation[0].name
namespace = aws_cloudwatch_log_metric_filter.non_jp_block_filter.metric_transformation[0].namespace
statistic = "Sum"
period = 600 # 10分(600秒)
evaluation_periods = 1
threshold = 10
comparison_operator = "GreaterThanOrEqualToThreshold"
alarm_actions = [aws_sns_topic.waf_alarm_topic.arn]
treat_missing_data = "notBreaching"
}
########################################
# 出力:ALB の DNS 名
########################################
output "alb_dns_name" {
value = aws_lb.alb.dns_name
}
結果
ALBの画面
日本からアクセスしているので以下のように正常に表示される。ALBのURLのプレフィックスにはALBの名前がつくが、なるべくBotに狙われやすいように汎用的な名前(alb
)にしている。
Botから攻撃されるのを待つ
この条件かなり当たりを狙うのが難しくて、10分間に5アクセス程度ならちょくちょくくるのだが、WAFに弾かれたときにBotが諦めてしまってなかなか連続アクセスしてくれない。
CloudWatchアラームのグラフだが、10がなかなかヒットしない。
メールがきた! お客さんは…
しばらく待ってると通知のメールがようやくきた。以下のような内容だった。
ワクワクしてWAFの画面を見ると……
ブロック1000件近く!? だいぶ派手にやったな。しかも30分ぐらい続けて、ねちっこい攻撃してる。
ロシアからのお客さんでした笑! いろんな国から攻撃されているけど、ロシアからの攻撃は桁違いに派手でした。
このグラフは草生える
大当たりを引けたのでこの検証は終了。
所感
- WAFだけでなくいろんなところで汎用的に使える運用向けのシステムだと思う
- WAFでやると結構面白い。普段は嫌われるBotを積極的に待つような検証になるので
- まあ前も調べたけどPublic ALBは結構攻撃されるね。CloudFront使うならVPCオリジンにしてInternal ALBを使いなさいということですね。
Shikoan's ML Blogの中の人が運営しているサークル「じゅ~しぃ~すくりぷと」の本のご案内
技術書コーナー
北海道の駅巡りコーナー