発端、ALBの4xxエラー
Wano株式会社の fushimi です。
関わっているサービスの一つでREST APIを提供していましたが、ALBの4xxアラートが閾値を越えることが多くなってきました。
Athenaに格納してあるログを確認すると単純に404エラー。 /admin.php
やら admin/login
やらの不正侵入を試みるbotアクセスです。
IPでのブロックもホワイトリスト方式でなんやかんやするのもいまいちうまくない要件だったため、今回は愚直に特定URIパターンやUAを弾く対応をAWS WAFで行ってみました。
terraform構成の概観
インフラ構築にterraformを使用しているので、今回はterraformの例です。
WAFはGUI上でサクッとやるならあまり学習しなくとも割と直感的に10分くらいで設定できてしまいます。
cloudformationやterraform含むIaCでやるとGUIと微妙に階層や用語が食い違ったりして、なかなか大変だったので、備忘録がわりに共有します。
該当サービスではWorkspace機能を使わず、以下のような構成になっています。
環境違いの構成は prod/
や stage/
にまとめ、共通設定として common/
以下のmoduleを呼び出す形です。
infra/aws/
├── Readme.md
├── admin-ec2
├── modules
│ └── common
│ ├── acm
│ ├── alb
│ ├── codebuild
....
│ └── waf_rules
│ ├── block_ua
│ │ ├── main.tf
│ │ ├── output.tf
│ │ └── variables.tf
│ └── block_uri
│ ├── main.tf
│ ├── output.tf
│ └── variables.tf
├── prod
│ ├── routing
│ │ ├── main.tf
│ │ ├── outputs.tf
│ │ └── terragrunt.hcl
...
└── stage
├── routing
│ ├── main.tf
│ ├── outputs.tf
│ └── terragrunt.hcl
...
具体的には今回は、 infra/aws/stage/routing/main.tf
や infra/aws/prod/routing/main.tf
から infra/aws/modules/common/waf_rules
以下のwafrule群を呼び出すスタイルになっています。
UAをブロックするRuleを提供するモジュール
// 最終的にoutputするresource
resource "aws_wafregional_rule" "block_ua_rule" {
//depends_on = ["aws_waf_regex_match_set.ipset"]
name = "${var.rule_name}BlockBotRule"
metric_name = "${var.rule_name}BlockBotRuleMetric"
predicate {
data_id = aws_wafregional_regex_match_set.block_bot_ua.id
negated = false
type = "RegexMatch"
}
}
resource "aws_wafregional_regex_match_set" "block_bot_ua" {
name = "${var.rule_name}_block_bot_ua"
regex_match_tuple {
field_to_match {
data = "User-Agent"
type = "HEADER"
}
regex_pattern_set_id = aws_wafregional_regex_pattern_set.ua_for_block.id
text_transformation = "NONE"
}
}
resource "aws_wafregional_regex_pattern_set" "ua_for_block" {
name = "${var.rule_name}_block-bot"
regex_pattern_strings = ["AAAAAA", "HogeBot" , "Test Certificate Info" , ...]
}
ALBに当てるルールなので aws_wafregional_xxx
系を使います。
ここでは、aws_wafregional_regex_match_set
で request headerから User-Agent
部にマッチさせています。
regex_pattern_stringsは正規表現としてPCREが使えるようです。
outputsは以下です。
output "rule_id" {
value = aws_wafregional_rule.block_ua_rule.id
}
特定URIをブロックするモジュール
// 最終的にoutputするresource
resource "aws_wafregional_rule" "block_uri_rule" {
name = "${var.rule_name}BlockUriRule"
metric_name = "${var.rule_name}BlockUriRuleMetric"
predicate {
data_id = aws_wafregional_regex_match_set.block_request_uri.id
negated = false
type = "RegexMatch"
}
}
resource "aws_wafregional_regex_match_set" "block_request_uri" {
name = "${var.rule_name}_request_url"
regex_match_tuple {
field_to_match {
data = null
type = "URI"
}
regex_pattern_set_id = aws_wafregional_regex_pattern_set.uri_block.id
text_transformation = "NONE"
}
}
resource "aws_wafregional_regex_pattern_set" "uri_block" {
name = "${var.rule_name}_block_uri"
regex_pattern_strings = ["php$", "html$", "cert$", "admin", "favicon.ico$", ...]
}
aws_wafregional_regex_match_set
のtypeだけがUserAgentの時と違います。
ここではdataは null
になります。
GUIで立てるといい感じにラップされている部分ですが、この辺はCloudFormationやaws-cliのドキュメントが参考になりました。
outputは以下です。
output "rule_id" {
value = aws_wafregional_rule.block_uri_rule.id
}
モジュール使用側
最終的には上記で作ったルール群をpriority付きで適用します。
これで完成です。
...
// 定義したモジュールの読み込み
module "app_alb_waf_block_ua_rule" {
source = "../../modules/common/waf_rules/block_ua"
rule_name = "stagealb"
}
module "app_alb_waf_block_uri_rule" {
source = "../../modules/common/waf_rules/block_uri"
rule_name = "stagealb"
}
/* ルールをまとめて適用 */
resource "aws_wafregional_web_acl" "stage_alb_waf_acl" {
metric_name = "stageAlbWafAclMetric"
name = "stageAlbWafAcl"
default_action {
type = "ALLOW"
}
rule {
priority = 1
action {
type = "BLOCK"
}
rule_id = module.app_alb_waf_block_ua_rule.rule_id
type = "REGULAR"
}
rule {
priority = 2
action {
type = "BLOCK"
}
rule_id = module.app_alb_waf_block_uri_rule.rule_id
type = "REGULAR"
}
// GeoIPのルール
...
// DOS対策のルール
...
}
/* ALBとACLをつなぐ */
resource "aws_wafregional_web_acl_association" "waf_attach_alb" {
resource_arn = module.app_alb.arn //ALBのArn
web_acl_id = aws_wafregional_web_acl.stage_alb_waf_acl.id
}
....
これから
今後の展望ですが、
- AWS WAFには「よくあるアクセスパターン」をマネージしてくれるものもあるようなので、そちらを試してみるといたちごっこにならずに良いのかも
- アラートがうざいだけならALBの4xx閾値とかでなく、ログ集計後のアクセスで適切にルーティングするのが筋かも
あたりかなあ...と考えています。
そしてIaCは大変。