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

![waf_icon.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/85375/ed24fc83-892e-2e89-6257-b853da7fb0bf.png)


## 発端、ALBの4xxエラー
[Wano株式会社](https://wano.co.jp/)の fushimi  です。
関わっているサービスの一つでREST APIを提供していましたが、ALBの4xxアラートが閾値を越えることが多くなってきました。  

Athenaに格納してあるログを確認すると単純に404エラー。 ``` /admin.php ``` やら  ```admin/login``` やらの不正侵入を試みるbotアクセスです。
IPでのブロックもホワイトリスト方式でなんやかんやするのもいまいちうまくない要件だったため、今回は**愚直に特定URIパターンやUAを弾く対応をAWS WAFで行ってみました。** 

##  terraform構成の概観
インフラ構築にterraformを使用しているので、今回はterraformの例です。
WAFはGUI上でサクッとやるならあまり学習しなくとも割と直感的に１０分くらいで設定できてしまいます。 
 cloudformationやterraform含むIaCでやるとGUIと微妙に階層や用語が食い違ったりして、なかなか大変だったので、備忘録がわりに共有します。  

該当サービスではWorkspace機能を使わず、以下のような構成になっています。
環境違いの構成は ``` prod/ ``` や ``` stage/ ```  にまとめ、共通設定として ``` common/ ``` 以下のmoduleを呼び出す形です。

```shell
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を提供するモジュール
 

```hcl: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は以下です。

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


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

```hcl: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は以下です。  

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

## モジュール使用側

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

```hcl: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は大変。