AWS S3 + CloudFrontでReactアプリをHTTPS公開するための正しい構成

# はじめに
Reactのようなフロントエンドアプリの配置先としては、AWS S3のようなフルマネージドなWebホスティング環境が最適です。ただし、S3のWebホスティング機能は単体ではSSL(TSL)通信が不可のため、クローズドなS3の前にCloudFrontを置いてCloudFront経由でSSL(TSL)通信を実現します。その際、S3への直接アクセスやCloudFrontへの非暗号HTTP通信は拒否し、必ずSSL(TSL)で暗号化されたHTTPS通信となるようにします。
ポイントは、S3はパブリックアクセスを禁止して構成し、CloudFrontからのOAI（Origin Access Identity）を使ったアクセスのみ許可します。OAIの構成はCloudFrontの構成の中で自動で設定するようにできます。

より簡単に環境を作るには、別記事にて紹介するAWS Amplifyが使えます（[AWS Amplify Consoleを使ってReactアプリのCICD環境を10分で作る](https://qiita.com/NorihitoYamamoto/items/a866b3692c472f38320d)）。以下の手順をすべてAmplifyが自動で行ってくれて同じような環境を自動で作ってくれるうえに、別記事で紹介するCICD環境も合わせてAmplifyが自動で用意してくれます。ただしAmplifyにはいくつか制約もあります。そちらも参考にしてください。

# 作成するもの
次のような環境をAWS上で構築します。
![React-Deploy-Environment.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/603113/b0e9b23c-8376-d935-28ea-70e979b5cd94.png)

# 事前準備
* 公開するURL（ここでは、cityrank.test.comとします）を決める
* お名前.comなどでドメイン名を登録しておく（AWSのRoute53からも取得できます）
* AWS CLIを導入しておく（参照：[AWS CLI v2をMacにインストールする](https://qiita.com/NorihitoYamamoto/items/badd32785078bc944089)）

# 手順
初めにRoute53でDNSの準備と、公開したいURL用のサーバ証明書をACMで準備します。その後、Reactアプリの置き場としてS3バケットを作成し、公開用のCloudFrontを構成していきます。

## DNSとサーバ証明書の準備

### Route53登録
1. AWS管理コンソールにログインし、Route53の画面に移動
2. 「ホストゾーンの作成」ボタンを押す
3. 「ドメイン名」に公開するFQDN（cityrank.test.com）のドメイン名部分（test.com）を入れ、タイプはパブリックホストゾーンのまま「作成」ボタンを押す
4. 作成されたホストゾーンのNSレコード（タイプがNSとなっている行）の値にある4つのネームサーバ（ns-xx.awsdns-xx.com.といった値）を、レジストラ（ここではお名前.com）側の管理画面を開き、対象ドメイン名のネームサーバの箇所に4つすべて転記する

### ACM(AWS Certificate Manager)でサーバ証明書作成
1. AWS管理コンソールにログインし、Certificate Managerの画面に移動
2. 右上のリージョンを東京からバージニア北部（us-east-1）に変更する(*1)
2. 「証明書のプロビジョニング」の「今すぐ始める」ボタンを押す（もしくは証明書管理画面が出る場合は「証明書のリクエスト」ボタンを押す）
3. 「パブリック証明書のリクエスト」のまま「証明書のリクエスト」ボタンを押す
4. 「ドメイン名の追加」で「ドメイン名」にドメイン共通で使えるようにするためアスタリスクを使ったドメイン名を記入し（例：*.test.com）、「次へ」ボタンを押す
5. 「DNSの検証」にチェックを入れたたま「次へ」ボタンを押す
6. 「タグを追加」では、タグ名にName、値に識別する名称を入れて（ここではcityrank）「確認」ボタンを押す（タグの追加は省略してもOK）
7. 「確定とリクエスト」ボタンを押す
8. 「検証状態」が「検証保留中」となる
9. そのまま「続行」ボタンを押す
10. 次のページで、対象の証明書の左の三角（▶）をクリックして開き、そのなかの「ドメイン名」の左の三角（▶）を再度クリックして開く
11. 「Route53でのレコードの作成」ボタンを押す（Route53側の設定が自動でアップデートされる）（*2）
12. 「状況」が「発行済み」となるまで待つ（数分から30分以上）

*1 このあとでCloudFrontから証明書を使えるようにする際に、証明書がバージニア北部リージョンにないとCloudFrontから見えません。CloudFrontがリージョンに依存しないグローバル・サービスのためです。必ず東京ではなくバージニア北部で証明書を作成します。

*2 手動でのRoute53設定は以下の通り（「Route53でのレコードの作成」ボタンは同じことをしています）
<details><summary>Route53設定手順</summary><div>
1. 「ドメイン名」の左の三角（▶）をクリックして開き、そこにあるCNAMEレコードをRoute53に登録していく
2. Route53の管理画面を開き、test.comホストゾーンを開く
3. 「レコードセットの作成」ボタンを押し、「名前」にACMのCNAMEレコードの名前のランダムな文字列の部分だけを入れる
4. 「タイプ」をCNAMEに変更する
5. 「値」にACMのCNAMEレコードの値を入力する（[ランダムな文字列].acm-validations.aws.）
6. 「作成」ボタンを押す
</div></details>

## アプリ公開環境（S3+CloudFront）の構築
### Reactアプリ配置先S3バケットの作成
今回はCloudFront経由で公開するために、S3の静的ウェブサイトホスティング機能は使いません。そのため、バケット名もFQDN気にせず自由に付けます。後のCloudFrontの設定でS3バケットポリシーも設定されるため、ここでは「ブロックパブリックアクセスのバケット設定」もオンのままで、パブリックアクセスできないメッセージが出ても問題ありません。

1. AWS管理コンソールにログインし、S3の画面に移動
2. 「バケットを作成」ボタンを押す
3. 「一般的な設定」で、「バケット名」に任意の名前を付け、「リージョン」は東京（ap-northeast-1）を選ぶ
4. 「ブロックパブリックアクセスのバケット設定」では「パブリックアクセスをすべてブロック」にチェックを入れたまま（デフォルト）
5. 「バケットを作成」ボタンを押す
6. 作成されたバケットの名前のリンクをクリックしてバケットを開き、「アップロード」ボタンでテスト用のindex.htmlをアップロード

### CloudFrontログ保管先S3バケットの作成
上手順と同様にバケットを作成し、ログ保管先とするフォルダ（ここではcityrank）を作成します。

### CloudFrontの構成
CloudFrontからのHTTPS通信のみ許可します。そのため、OAI（Origin Access Identity）を使い、OAIをもつCloudFrontからのみS3へのアクセスを受け付けるようS3バケットポリシーを構成します。そのバケットポリシーの更新は「Yes, Update Bucket Policy」にチェックを入れることでCloudFrontの構成の中で自動的に行われます。

1. AWS管理コンソールにログインし、CloudFrontの画面に移動
2. 「Create Distribution」ボタンを押す
3. 「Web」の「Get Started」ボタンを押す
4. 「Origin Settings」で「Origin Domain Name」でリストされる中から、上で作ったバケットを選択する
5. 「Restrict Bucket Access」をYesにチェックを入れ、S3バケットへは必ずCloudFront経由のアクセスとする
6. 「Origin Access Identity」で「Create a New Identity」にチェックを入れる
7. 「Grant Read Permissions on Bucket」で「Yes, Update Bucket Policy」にチェックを入れ、S3バケットポリシーを自動でOrigin Access Identity対応に更新する設定にする
8. 「Default Cache Behavior Settings」で「Viewer Protocol Policy」は「Redirect HTTP to HTTPS」にチェックを入れる
9. 「Allowed HTTP Methods」は「GET, HEAD」のまま
10. 「Distribution Settings」で「Alternate Domain Names(CNAMEs)」に公開用のURL（cityrank.test.com）を入れる
11. 「SSL Certificate」で「Custom SSL Certificate (example.com)」にチェックを入れ、上のACMで作った証明書が入力ボックスに表示されるのを確認する（表示されていなければリストから対象証明書を選ぶ）
12. 「Default Root Object」にindex.htmlを入れる（ルートパスにアクセスしてもReactコンテンツ=index.htmlを表示させるため）
12. LoggingをOnにチェックボックスを入れ、ログ保管先のS3バケットとPrefix（フォルダ名＝cityrank）を指定する
13. それ以外はデフォルトのまま「Create Distribution」ボタンを押す
14. 左ペインのDistributionをクリックし、しばらく待つとStatusがIn ProgressからDeployedに変わる
15. 対象DistributionのIDのリンクをクリックして開き、「Domain Name」のところに記述されているURLにアクセスできるかを確認する（xxxxx.cloudfront.net）。

### Route53へ独自ドメイン名の設定
独自ドメイン名を使用するために、Route53でCloudFrontへのエイリアスの設定を行います。

1. AWS管理コンソールのRoute53の画面に移動
2. ホストゾーンの対象ドメイン名を開く
3. 「レコードセットの作成」ボタンを押し、名前に公開したいURLのサブドメイン名（cityrank）を入れ、タイプはAのまま、「エイリアス」をはいにチェックを入れ、「エイリアス先」のリストから上で設定したCloudFrontを選ぶ
4. 「作成」ボタンを押す
5. 公開したいURLでアクセスできるかを確認する（cityrank.test.com）。

## Reactアプリのアップロード
### AWS CLIを使ったアップロード
1. Reactプロジェクトのフォルダに移動し、ビルドする `$ npm run build`
2. buildフォルダに移動し、buildフォルダ配下の内容を、公開用S3バケットへ、AWS CLIでアップロードする `$ aws s3 cp ./* s3://[バケット名]/`
3. 公開したいURLでアクセスし、Reactアプリが表示されるか確認する（cityrank.test.com）。