AWS WAFでメンテナンスモード(アクセス制限)

<div align="center">
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50310/201d9f38-1b2b-e4f8-af83-31b142903de1.jpeg" width="250" alt="AWS WAF">
</div>

・CloudFront + Origins(S3 や ALB) という構成のWebサイトにて、
メンテナンスモードへ切り替えたいという要望をAWS WAFにて対処した際の備忘録になります。

# AWS WAFとは
・AWSにて、Webサイトを構築した際の入り口となることが多いCloudFront, ALB, API Gatewayの前段に配置することが出来、連携もあるようです。また、Managed rulesという基本的な防御内容を抑えたルール群が用意されており、セキュリティ対策としての導入を易しくしている印象を受けました。

詳しくは[公式Document](https://docs.aws.amazon.com/waf/latest/developerguide/what-is-aws-waf.html)を参照ください


# 1. はじめに
## 1-1. 目的
・一般的に用いられるWebサーバ(apache等)で確立されているメンテナンスモード切り替え。
それと同等のことをAWS WAFにて実装出来るか試してみました。


■ 一般的なWebサーバでのメンテナンスモードの挙動の例
・大まかには下記機能があるかと思います
```text
・HTTP Requestの評価(IP判定など)
・HTTP Requestの書き換え(Rewrite、Redirect など)
・任意のResponseを返す
・設定の有効・無効を時間指定出来る 
```

## 1-2. これまで
![20220417_WAF(以前の構成).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50310/d4b76b08-cf6f-7248-8247-8c2f15d2d17a.png)
・CloufFront(CF) + Originsという構成のWebサイトを運用しており、メンテナンス時はS3 Originに配置したメンテナンス画面へCFのdefault behavior(*)を向ける。という手法にてメンテナンス画面に切り替えていました。
しかし、この手順の致命的な欠点は「**作業者も動作確認が出来ない**」こと。
またその他に、下記のようなデメリットがありました。
  - 既存の設定(CF Behavior)を変更しなければいけない (※1)
  - 反映に時間が掛かる (※2)
  - 設定作業に時間的制約が生じる (※3)
:::note info
  (※1) 戻す手間 や 作業ミスのリスク
  (※2) Invalidationを実施しても、クライアント側のキャッシュやEdge Locationによっては反映までに差が生じる
  (※3) 構成管理ツールなどにより自動設定出来るかと思いますが
:::

## 1-3. 備考
・公式も謳っている CF + Lambda@EdgeによるBasic認証も実装および運用していますが、メンテナンス時にBasic認証の画面を出すのはスマートでないと考え、他の方法を調査・実装しました。


# 2. 構成
## 2-1. 構成図
・今回適用した構成では、Webサーバ(SSR)およびAPIサーバがALBの背後(バックエンド)に位置しています。
本構成にて、AWS WAFをCFに関連付けてみました。

![20220417_WAF利用例(cloudfront).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50310/fe7082cd-9363-9a3f-1af8-aece1fc8830c.png)

## 2-2. 構成に関する要点
- AWS WAFはRequestを評価(※1)しBlockとなった場合、CFに403エラーを返す
- CFのError Pagesの設定にて403エラーの向き先をメンテナンスページ(普段はsorryページ)に設定している(※2)

(※1)今回適用したのはIP制限になります
(※2)これらの設定が別途済ませてある前提で話を進ませて頂きます


# 3. WAFの設定
## 3-1. 概要
- 許可した送信元(作業者などのIP)からのアクセスのみ通す
- それ以外はBlock(403エラー)とする

## 3-2. 設定内容
AWSコンソールの[AWS WAF](https://us-east-1.console.aws.amazon.com/wafv2/homev2/start?region=global)より設定した際の手順を記載します

### ① IP setを作成
```text
[IP sets] - [Create IP set]を選択
  [Region] - [Global(CloudFront)]を選択 (※)
  [IP addresses] - 許可するアクセス元のIPアドレスを設定していく。その際、下記注意書きのとおりに設定する
  > Enter one IP address per line in CIDR format.
```

■ 備考
・Web ACL作成時にIP setをアサインする必要があるため、先に作成しています。
(※) Regionは、Web ACLアサイン先のリソース(CloudFront)に合わせています。


### ② Web ACLを作成
**②-1. Web ACL details**
```console
[Resource type] - [CloudFront distributions]を選択
[Associated AWS resources] - 関連付けるCF Distributionを指定
```

**②-2. Ruleを追加**
```console
  [Add rules] - [Add my own rules and rule groups]を選択
  [Rule type] - [IP set]を選択

  <IP set>
    [IP set] - ドロップダウンリストより先に作成したIP setを選択する
    [IP address to use as the originating address] - [Source IP address]を選択 (※1)
    [Action] - [Allow]を選択
    [custome response] - '未設定' (※2)
```
■ 備考
(※1) LBやProxyなどの配下にWAFを設定する場合は、X-Forwarded-Forを評価する必要があるため[IP address in header]をチェックし更に詳細を設定します。今回は、CF向けのHTTP Requestを評価するため、表記のとおりの設定としています。
(※2) Block時に返されるdefault値(403)などResponseの内容を変更等できる模様。

**②-3. Default web ACL action for requests that don't match any rules**
```console
[Default action] - [Block]を選択
→ 先に設定したルールに一致しなかったRequestは全てBlock
```
**②-4. その他**
・CloudWatch metrics, Request Sampling などは、default設定値(有効)のままとしました。
(本件とはあまり関連がないため)

**②-5. Review**
・設定内容に間違いがないか確認した後、[Create web ACL]を押下する


これにて設定は完了になります。


# 4. 挙動
## 4-1. 実装後の挙動
・当該設定(WAFとCFの連携)により、許可された送信元以外はメンテナンスページにリダイレクトされるようになりました。

:::note warn
構成図にあるとおり、APIサーバへのRequestはWAFを経由しません。
しかし、クライアントは通常APIサーバのURLを知りません。
(本ケースではWebサーバが生成したHTMLを受信して初めて知ることになります)

また、別の方法でアクセスを制限することが出来るため、本件ではそちらを採用しています。
(CFが発行した独自ヘッダーやAPI KeyをALB Listener Ruleにてチェックなど)
:::

## 4-2. 課題の対処状況
・以前のメンテナンスモードでの課題、本実装により下記のようになりました。

**① 作業者による動作確認が出来ない**
**解決**
→ 許可したユーザがメンテナンス中もアクセス出来るようになりました。

**② 既存の設定(CF Behavior)を変更しなければならない**
**解決**
→ WAF側の設定を変えるだけでよくなりました。
(WAFの待機設定としては、普段defaultもAllowにしておき、メンテナンス時はBlockにすることが考えられます)

**③ 反映に時間が掛かる**
**解決**
→ WAF設定後、即時反映されます。

**④ 設定作業に時間的制約が生じる**
**未解決**
→ 設定の有効・無効の時間指定は現状ない模様。
`構成管理ツール` や `EventBridge + Lambda`など他サービスを利用するなど、工夫すればスケジューリングも出来ると思います。


# 5. 運用コスト
・気になる費用面も確認してみました。

## 5-1. 本WAFの月額利用料
・AWS pricing Calculatorにて算出してみました。
[AWS Pricing Calculator / AWS WAF](https://calculator.aws/#/createCalculator/WAF)

:::note info
Regionを選択。Cloudfrontと連携させるためにWebACLを定義したRegion「Global(Cloudfront)」は選択肢になかったが、公式Documentに「Region間での料金に差異はない」との表記がありました。
:::

| 項目 | 単位 |
|:-----------|------------:|
WebACL数:        | 1 / mon
WebACLのRule数:  | 1 / mon
RuleGroup数:     | 1 / mon
Rule数:          | 1 / mon
Managed Rule数:  | 0 / mon
受信するRequest数: | 200万 / mon

| Total Cost |
|:-----------------------:|
| **AWS WAFのコスト: 9.20 USD/mon** |

```
(内訳)
WebACL:   5.00 USD/mon = 5.00USD/WebACL * 1
Rule:     3.00 USD/mon = 1.00 USD/Rule * 3 (※)
Request:  1.20 USD/mon = 0.6 USD/100万Req * 200万Req

(※) 上記Rule、Rule Group で 合わせて3グループとカウントされている模様
```

## 5-2. CloudFront Functionsの料金と比較
・後述するWAFの代替として利用できそうな当該サービスと料金比較を行ってみました。
(詳細は後述します)

[CloudFrontの料金説明](https://aws.amazon.com/jp/cloudfront/pricing/)に下記記載がありました。

```
無料利用枠
1 TB のデータ転送 (アウト)
1,000 万件の HTTP または HTTPS リクエスト
2,000,000 件の CloudFront Function 呼び出し
```
・CloudFront Functionで同じことをやろうとした場合、無料で利用できるということが分かりました。
(Request数が 200万Req/month であること前提)

上記より差額は `9.20 USD/mon`となりました。


## 5-3. 気になったこと

**Q: 固定費である WebACLおよびRule。
　EC2、RDSなどと同様に、停止や無効化など利用していない際、費用を掛けない手段は？**

**A: 出来ない模様** (理由は下記)

- FAQに該当の記載がない
- 該当リソースが存在するだけで、固定月額費を時間毎に比例配分方式で請求されると公式にある
- [AWS CLIのコマンドReference](https://awscli.amazonaws.com/v2/documentation/api/latest/reference/wafv2/index.html)にも、無効化といったような機能の記載が見当たらない


・このことより、いざという時のためにWebACL/Ruleを用意しておいただけで料金が発生するということになります。また、すべてAllowとして、Requestを全スルーする設定にしておいても、そのリクエストも請求対象となると思われます。
本稿のような利用用途であれば、該当リソース(WebACL/Rule)を削除することが対処方法として考えられますが、セキュリティ対策として運用する場合は、WAF導入のメリットとお財布事情を検討する必要がありそうです。


# 6. 所感
・アクセス制限およびメンテナンスモード実装という目的の場合、後述するサービスの方が適正があるように思えます。

## ① CloudFront Function
・昨年リリースされた新機能。Lambdaに比べ簡易なjavascriptしか書けませんが、その分低コスト、高速処理が売り。そして、スクリプトを利用することで柔軟な対応が可能。
本件ですとRequest Headerより送信元IPを判別し、任意の宛先へリダイレクトさせることも出来るかと思います。
(apacheのように時間設定も出来そうです。こちら近い内に試してみたく思います)

## ② ALB Listener Rule
・こちらでも送信元IPを判別可能。また条件が一致した際にリダイレクトや任意のレスポンスコードを返すことが出来ます。CFを利用しない、ALB + Webサーバ(+バックエンド)というシンプルな構成の場合は、この方法が適切なように思いました。


# 最後に
・CFを利用したWebサイトにて、WAFを利用してメンテナンスモードを実装する方法を記載しました。
まだまだ触っていない機能があるため、AWS WAFについて、これからも学習していきたく思います。


# (おまけ) WAFをALBにアサインした場合

![20220417_WAF利用例(ALB).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50310/15444123-fe59-e11a-33eb-865074b6e51f.png)

・同様のWebサイト構成でAWS WAFをALBにアサインした場合は、このような構成となりました。
この場合、Requestをどのように捌くかを簡単にご説明すると

① 構成上、ALBの前段にあるCF Behaviorへのアクセスを制限することが出来ません。
② ALBへの直接RequestをWAFでも評価することが出来るようになりました。
③ Block時、アクセス元のCFに403をエラーを返すことで、CFのError Pages設定を利用することが出来ました。そのため、本構成でもCF+S3のメンテンナンスページを利用出来ました。

■ 備考
・本件の採用の判断基準は、①が許容できるか否かになりそうです。
個人的には、CF+Origins構成には不向き。また、アクセス制限ということだけであれば上述したように同様のことがALB Listener Rulesで出来、その方がコストも抑えられるため、本用途には不向きかと思います。
セキュリティ対策ということであれば、検討の余地がありそうだとも。

以上になります。