LoginSignup
8
5

More than 3 years have passed since last update.

terraformでALBにWAF Rules を適用する(UAでのブロック/URIでのブロック)

Last updated at Posted at 2019-11-11

waf_icon.png

発端、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を提供するモジュール

infra/aws/modules/common/waf_rules/block_ua/main.tf

// 最終的に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は以下です。

infra/aws/modules/common/waf_rules/block_ua/output.tf
output "rule_id" {
  value = aws_wafregional_rule.block_ua_rule.id
}

特定URIをブロックするモジュール

infra/aws/modules/common/waf_rules/block_uri/main.tf

// 最終的に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は以下です。

infra/aws/modules/common/waf_rules/block_uri/output.tf
output "rule_id" {
  value = aws_wafregional_rule.block_uri_rule.id
}

モジュール使用側

最終的には上記で作ったルール群をpriority付きで適用します。
これで完成です。

infra/aws/stage/routing/main.tf
...
// 定義したモジュールの読み込み
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は大変。

8
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
5