TerraformでAWS WAFを基礎から学ぶ(基本のルール設定編)

# はじめに
AWS WAF はフルマネージドで便利なファイアウォールであり、インターネットに面したゲートウェイ機能を有するシステムをお手軽に構築するなら、ほぼ必須で使うことになるサービスだろう(自前で手塩にかけて育てたWAFを持っているのであれば話は別だろうけど)。

ということで、お手軽に使える AWS WAF をお手軽に Terraform で構築しつつ基礎を理解していく。
インテグレーションは API Gateway を使う。
今回作る「IPアドレス制限」だけであれば、[API Gateway のリソースポリシー](https://qiita.com/neruneruo/items/c466b22f2b2973a4cd7c)を使った方が手っ取り早い。あくまでも練習のためにやっていると考えていただきたい。

# IPアドレス制限する AWS WAF の基本セット
今回の構成では、ブラックリスト形式で実施する。
ルール設定に必要になる Terraform のリソースは以下の2種類。

- aws_wafv2_web_acl
- aws_wafv2_ip_set

今回の設定は練習であり、マネージメントコンソールにも注意書きがあるように、ブラックリストとしては実質的に意味がないということは留意しておくこと。

> When a request comes through a CDN or other proxy network, the source IP address identifies the proxy and the original IP address is sent in a header. Use caution with the option, IP address in header, because headers can be handled inconsistently by proxies and they can be modified to bypass inspection.

##  Web ACL 本体とルールの設定
Web ACL は以下のように定義する。

```HCL
resource "aws_wafv2_web_acl" "iprestriction" {
  name        = local.webacl_name
  description = "TEST IP Adress Restriction"
  scope       = "REGIONAL"

  default_action {
    allow {}
  }

  rule {
    name     = "iprestriction"
    priority = 1

    action {
      block {}
    }

    statement {
      ip_set_reference_statement {
        arn = aws_wafv2_ip_set.test.arn
      }
    }

    visibility_config {
      cloudwatch_metrics_enabled = false
      metric_name                = local.webacl_iprestriction_metric_name
      sampled_requests_enabled   = false
    }
  }

  visibility_config {
    cloudwatch_metrics_enabled = false
    metric_name                = local.webacl_metric_name
    sampled_requests_enabled   = false
  }
}
```

```visibility_config``` は今の段階では使わないものの、必須パラメータなので設定しておく。
```default_action``` も必須パラメータで、ここでブラックリストにするかホワイトリストにするかを指定するようなイメージだ。デフォルトが ```allow``` であればブラックリストといった感じだ。

##  IPアドレスリストの設定
```aws_wafv2_web_acl``` 側でIPアドレス制限を直接書くのではなく、IPアドレスセットのリソースを準備し、それと比較するようなかたちになっている。おそらく、複数のルール間でお手軽に共通化ができるように、という思想なのだろうと推測。

```HCL
resource "aws_wafv2_ip_set" "test" {
  name               = "test"
  description        = "TEST IP set"
  scope              = "REGIONAL"
  ip_address_version = "IPV4"

  addresses = [
    "xxx.xxx.xxx.xxx/32",
  ]
}
```

アドレスはCIDRで指定する。今回はIPアドレスを1つだけ block したいので、```/32``` を指定する。

# API Gateway とインテグレーションする
さて、せっかくルールを作ってもインテグレーションしなければ意味がない。
インテグレーションには以下の Terraform リソースを使う。

- aws_wafv2_web_acl_association

使い方は簡単で、API Gateway と AWS WAF の ARN を繋ぐだけで良い。
API Gateway は REST API そのものではなくてステージと連動するという点だけ注意だ。

```HCL
resource "aws_wafv2_web_acl_association" "iprestriction" {
  resource_arn = aws_api_gateway_stage.prod.arn
  web_acl_arn  = aws_wafv2_web_acl.iprestriction.arn
}
```

# 動作確認する
さて、本当にこれでIPアドレス制限されるのかを試してみよう。

IPアドレスリストに設定していないIPアドレスでアクセスした場合、

```JSON
{
  "data": {
    "hoge": "hige"
  },
  "errorMessage": ""
}
```

と正しく取得され、リストに設定したIPアドレスでアクセスすると

```JSON
{"message":"Forbidden"}
```

と表示される。その他、```curl -i``` で取得されるヘッダ情報としては以下。
HTTPステータスコードは 403 として返却される。

```
HTTP/2 403 
content-type: application/json
content-length: 23
date: Sun, 06 Dec 2020 02:57:37 GMT
x-amzn-requestid: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
x-amzn-errortype: ForbiddenException
x-amz-apigw-id: xxxxxxxxxxxxxxxx
x-cache: Error from cloudfront
via: 1.1 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.cloudfront.net (CloudFront)
x-amz-cf-pop: NRT12-C4
x-amz-cf-id: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
```

## API Gateway の HTTPステータスコードやワーディングの変更
API Gateway とインテグレーションする場合は、HTTP ステータスコードや応答の JSON は[ゲートウェイのレスポンス](https://qiita.com/neruneruo/items/a568cd3f0542cde8c967#%E3%82%B2%E3%83%BC%E3%83%88%E3%82%A6%E3%82%A7%E3%82%A4%E3%81%AE%E3%83%AC%E3%82%B9%E3%83%9D%E3%83%B3%E3%82%B9%E3%82%92%E8%A8%AD%E5%AE%9A%E3%81%99%E3%82%8B)で変更可能だ。```api_gateway_gateway_response``` リソースの ```response_type = "WAF_FILTERED"``` を使えば Terraform でもできる(詳細は記事を参照)。

## API Gateway のログ出力
API Gateway とインテグレーションする場合は、AWS WAF とのやり取りをアクセスログに出力することができる。アクセスログの設定に関する詳細は、手前味噌だが[この記事](https://qiita.com/neruneruo/items/b1eb81e3a1ec5b354f4c)が分かりやすいと思う。

AWS WAF に関連する項目の出力例は以下の通り。

```JSON
  "waf_response_code": "$context.wafResponseCode",
  "webacl_arn": "$context.webaclArn",
  "waf_error": "$context.waf.error",
  "waf_latency": "$context.waf.latency",
  "waf_status": "$context.waf.status",
```

これで実際に出力されたアクセスログを確認すると、以下のように出力される。

```JSON:allow&nbsp;した場合
    "waf_response_code": "WAF_ALLOW",
    "webacl_arn": "arn:aws:wafv2:ap-northeast-1:xxxxxxxxxxxx:regional/webacl/waf-iprestrict-test-webacl/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
    "waf_error": "-",
    "waf_latency": "7",
    "waf_status": "200"
```

```JSON:block&nbsp;した場合
    "waf_response_code": "WAF_BLOCK",
    "webacl_arn": "arn:aws:wafv2:ap-northeast-1:xxxxxxxxxxxx:regional/webacl/waf-iprestrict-test-webacl/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
    "waf_error": "-",
    "waf_latency": "7",
    "waf_status": "403"
```

# AWS マネージメントコンソールでの確認
上記で作ったリソースをマネージメントコンソールで確認すると以下のようになる。

![キャプチャ1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/111314/b47e3ec7-a18a-007c-bf79-481cf8a67cbf.png)

```iprestriction``` のルールを選択して、右上の ```Edit``` ボタンを押すと、以下のようにルール編集画面で設定内容を確認できる。

![キャプチャ2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/111314/d88eb28e-f895-98af-3632-3cc62d678c5b.png)
![キャプチャ3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/111314/24420a7e-1635-03b3-6080-5d5a164ba079.png)
![キャプチャ4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/111314/b8d197cd-06f2-3996-5a0e-b9df5cb1b42b.png)

API Gateway とのインテグレーションに関する設定は以下。

![キャプチャ5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/111314/ddcb9712-36ac-eaa6-9053-21d51859b8b7.png)

IPアドレスリストに関する設定は以下。

![キャプチャ6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/111314/d7ff1387-c255-7d0c-7605-35b80c834bb6.png)
![キャプチャ7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/111314/684821f7-b61e-d21f-ce77-c144e9207369.png)

基本のルール設定編はここまで。