AmplifyにIP制限を設ける

Amplifyへファイヤーウォール機能の実装をするのに戸惑ったのでメモ

## やりたかったこと

Amplifyでデプロイしたwebページを、特定のIPからしか閲覧出来ないようにする
(ホワイトリスト形式でIP制限をかける)

## 試したこと

- Amplifyコンソールのアクセスコントロール
    - ベーシック認証のみ

- AmplifyをWAFのリソースで選択出来ない...?
    - 自動デプロイで生成されるコンテンツやDistributionは、コンソールから見ることも触ることも出来ない...

- CloudFrontでDistributionを作成し、Amplifyのドメインを設定してみる

　　[こちらのissue](https://github.com/aws-amplify/amplify-console/issues/8) によると、Amplifyの環境変数に謎の呪文を追加すると出来るようになるとかならないとか
　　hosting機能でやってくれることを自分でやってるだけな気がしたので却下

## 結論　hosting + WAF

`amplify add hosting` で生成されるDistributionに対してWAFを設定するのが一番シンプル

### hostingを作成する

Amplifyコンソールから作成出来る「Deploy」とは別のもの
1環境1つで、生成されたS3バケットとDistributionは各コンソールからいじることが出来る

```
$ amplify add hosting

? Select the environment setup:
  > PROD (S3 with CloudFront using HTTPS)  ※WAFを設定するためのCFが必要なのでPRODにする
? hosting bucket name
  > 任意のバケット名
? index doc for the website
  > index.html
? error doc for the website
  > error.html

$ amplify publish
```

### S3
- 作成されたS3バケットの「静的ウェブサイトホスティング」が無効化されていることを確認

### CloudFront(CF)
- デフォルトの`*.cloudfront.com`ドメインで表示出来ていることを確認
- 「Alternate Domain Names」にドメイン名を入力(例：hoge.sample.com)
- 「CustomSSLCertificate」で証明書を選択
    - ない場合は「Request or Import a Certificate with ACM」から取得可能

![スクリーンショット 2019-10-15 10.59.04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252165/f656151b-4c4d-d1ac-ebae-3609bface046.png)

### Route53
- 作成したいホストゾーンを選択
- CNAMEでドメイン名を追加
- エイリアスで選択またはCFのDomainNameをコピペする
- 作成
- 設定したサブドメインで表示されることを確認

### WAF
- WebACLs作成
- 「Select a resource」でdistributionを選択
![スクリーンショット 2019-10-15 13.18.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252165/748fd9b7-ee02-f096-58ec-827a8a3e33dd.png)

- conditionを追加する
    - 作成済のconditionがある場合は次のルール作成で選択出来る
    - ない場合は「Create condition」から作成。許可するIPを設定する。
    - 今回は作成済の「ip-match-condition」を利用する
![スクリーンショット 2019-10-16 10.32.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252165/e52080a7-9179-503a-5734-6e0a5c842bcb.png)

- ルールを作成する
    - 任意の名前をつける
    - Add conditions
        - 「does」
        - 「originate from an IP address in」
        - 先程作成したconditionまたは既存のconditionを選択する
    - 作成

![スクリーンショット 2019-10-16 10.49.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252165/585c2124-7401-cdb5-f73d-b59eadccf6af.png)

- ルールを選択する
    - 先程作成したルールを選択し「Add another rule」で追加する
    - 今回はホワイトリスト形式としたいため、conditionにマッチするものはAllow
    - 「DefaultAction」は「Block all requests ...」

![スクリーンショット 2019-10-16 10.34.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252165/3404821e-dc1e-39ce-76fd-e1e3599ecb3a.png)


## 困ったこと

vue-routerのhistoryモードが利用できない

> AWS WAF は、指定した条件に基づいてウェブリクエストをブロックすると、CloudFront に HTTP ステータスコード 403 (Forbidden) を返します。

> CloudFront は、オリジンによって返された HTTP ステータスコード 403 と、リクエストがブロックされたときに AWS WAF によって返された HTTP ステータスコード 403 とを区別できません。つまり、HTTP ステータスコード 403 のさまざまな原因に基づいて、異なるカスタムエラーページを返すことはできません。

[公式ドキュメント](https://docs.aws.amazon.com/ja_jp/waf/latest/developerguide/cloudfront-features.html#cloudfront-features-custom-error-pages)

CloudFrontがリソースを見つけられなかった場合、 `404` ではなく `403` が返ってくるが、
WAFによるアクセスブロックの `403` と区別されない仕様らしい

historyモードでルート以外のパスを叩くと403が返ってくるため、CFの設定で403の時は `index.html` を返すようにしていた
hashモードであればルーティング先はすべてアンカーとして渡されるため上記のエラーは起きない

ちょっと悔しいですが今回は仕方なくhashモードに切り替えました
なにか良い方法ご存知の方いらっしゃいましたら教えて下さい

## 参考
[Amplify-CLI + Reactで超絶簡単ホスティング](https://qiita.com/makishy/items/499961fa64aefae2e430)
[CloudFront + S3 での IP アドレスベースのアクセス制限設定をする](https://ceblog.mediba.jp/post/156084994102/cloudfront-s3-%E3%81%A7%E3%81%AE-ip-%E3%82%A2%E3%83%89%E3%83%AC%E3%82%B9%E3%83%99%E3%83%BC%E3%82%B9%E3%81%AE%E3%82%A2%E3%82%AF%E3%82%BB%E3%82%B9%E5%88%B6%E9%99%90%E8%A8%AD%E5%AE%9A%E3%82%92%E3%81%99%E3%82%8B)