CloudFrontに複数オリジン（API GatewayオリジンとS3オリジン）の設定

# 概要
API Gatewayを使うとき手が届かないむずかゆいところがたくさんあります（HTTPSのみしか受け付けないとか、前段にWAFおけないとか）。
そのときに使われるのがCloudFrontになりますが（もちろん、API Gateway内部にCloudFrontが暗黙的に使われており、多段CloudFrontになるため遅延が大きいというのは承知）、API　Gatewayの前段用にCloudFront使うのもなんなのでS3オリジンの配信もやっちゃいたい、という内容です。

* CloudFront + API Gateway + S3
* S3はOAI(Object Access Identity)でアクセス制限
* API Gatewayはカスタムオリジンヘッダによるアクセス制限はしない
  * めんどいので
* API Gatewayはキャッシュしない
* Route53で取得したドメインを利用
* ACMで取得した証明書をHTTPS通信に利用

# アーキテクチャ構成
![arch.png](https://qiita-image-store.s3.amazonaws.com/0/123578/0a4481b9-e92f-6948-d72c-3f43408c6a3e.png)

# 手順
## Route53の設定（その１）
CloudFrontのエンドポイントを覚えやすいドメイン名にするため、Route53でドメイン名を取得して設定する必要があります。

1. Domain Resigtration
    * 独自ドメインをRoute53から取得します。[Domain Registration]->[Resigter Domain]を選択し、ドメイン（xxxxxx.comなど）を取得してください。
    * メインを取得すると、ダッシュボードが下記になります
  ![register.png](https://qiita-image-store.s3.amazonaws.com/0/123578/ff8d8c15-7647-0127-2968-8670a1488019.png)


## ACM(Amazon Certificate Manager)で証明書作成
独自ドメインに対してhttpsアクセスするために証明書を作成します。

  1. ACM画面
  ![top.png](https://qiita-image-store.s3.amazonaws.com/0/123578/fd1eb156-a607-f8f5-aff3-8c73fb8b142f.png)　

  2. 登録画面　
  Route53で取得したドメインを登録します。
  ![regist.png](https://qiita-image-store.s3.amazonaws.com/0/123578/d314c690-b213-8838-73cc-e1326a6350e9.png)　

  3. 確定画面　
  設定を確認し、確定します。確定するとドメインに関連付けられているメールアドレスにValidationメールが送付されます。
![confirm.png](https://qiita-image-store.s3.amazonaws.com/0/123578/61950c2f-5c02-504c-82f8-5fb81a79ce8c.png)　

  4. 仮登録完了画面
![pre_done.png](https://qiita-image-store.s3.amazonaws.com/0/123578/17396a6d-a907-c398-9812-556f78eccf0c.png)　　

  5. バリデーション画面
  送付されてきたメールリンクを押下すると下記画面となります。承認を押して確定してください。
![validation.png](https://qiita-image-store.s3.amazonaws.com/0/123578/4410b161-66a0-eac0-57de-f2a8d5f2a149.png)　

  6. 完了画面
![success.png](https://qiita-image-store.s3.amazonaws.com/0/123578/482ba63f-fafe-1c21-d272-ca3a7fee33bd.png)　

  7. ダッシュボード
  完了までするとステータスが発行済みとなり、使用中でないことを確認してください。以後、これを使ってHTTPS通信における証明をします。
![result.png](https://qiita-image-store.s3.amazonaws.com/0/123578/65f50364-b7db-8bc7-b8fe-0fc29ba10072.png)　

## API Gatewayの設定
詳細は割愛しますが、下記手順で実施します。完了後にAPI Gatewayのデプロイ完了画面が表示されるはずです。下記画像の通り、各リソースごと（メソッドごと）にURLエンドポイントが発行されています。

  1. API作成
  2. リソース作成
  3. メソッドの作成
  4. APIをステージへデプロイ
  
![apiurl.png](https://qiita-image-store.s3.amazonaws.com/0/123578/ee6633cc-9d30-5efa-7eea-ba58980156cd.png)

ここで使うのはURLの、**「https://XXXXXXX.execute-api.ap-northeast-1.amazonaws.com/prod」**
までの部分です。

## CloudFrontの設定（その１）
CloudFrontの設定に入ります。その１としているのは後でS3の設定が入るからです。
注意として、CloudFrontのデプロイにはめちゃくちゃ時間がかかる（20分くらい）ので、あせらず実施してください。時間のあるときにやってください。

1. CloudFrontを開くと下記画面が表示されるので、Distributionを作成します。
![dashboard.png](https://qiita-image-store.s3.amazonaws.com/0/123578/2bdea6c7-f819-ec47-0090-66b5b3b955a8.png)　

1. webを選択します。
![web.png](https://qiita-image-store.s3.amazonaws.com/0/123578/d6833a75-c243-1e92-675f-364c5ee18ad0.png)　

2. Origin Settingsの設定
![origin.png](https://qiita-image-store.s3.amazonaws.com/0/123578/8861a9e3-ca95-6315-1e84-89c3eb0ff030.png)　

3. Behavior Settingsの設定

  * 注意点としては、HOSTヘッダーはForwardさせてはいけない。なぜかというとAPI Gateway自体がSNI前提のバーチャルホスト構成なのでHOSTが切り替えられなくなってしまうため。
  * 見切れている下記オプションについて
    * Smooth Streaming：動画配信でないため「No」
    * Restrict Viewer Access：今回は「No」
    * Compress Objects Automatically：Gzip圧縮しないので「No」
    * Lambda Function Associations：S3オリジンにファイルが入ってInvalidationAPIたたいたりする設定。今回は「設定しない」　
![behavior.png](https://qiita-image-store.s3.amazonaws.com/0/123578/d7fa37e9-3787-5cb3-1b0a-f5fe381d9d2c.png)　

1. Distribution Settingsの設定
見切れているオプションは特に変更せずデフォルトのままにする。
![distribution.png](https://qiita-image-store.s3.amazonaws.com/0/123578/e95189c8-730e-42cb-39ba-2520dc7c4deb.png)　


2. 作成！
![progress.png](https://qiita-image-store.s3.amazonaws.com/0/123578/0de6c142-eeb0-f011-f751-8cef0420e1ff.png)　

  * ダッシュボードに戻り、Statusが「In Progress」になります。ここから20分ほど、ぐつぐつ煮込んでください）
  * StateがEnabledになったら無事完了です。

## Route53の設定（その２）
CloudFrontの代替名を独自ドメインにしたのでRoute53の設定が必要です。

- 上記で取得したドメインをCloudFrontへルーティングするためにDNSリソースレコードを追加する必要があります。[DNS management]->[Domain Name]を選ぶと作成したDNSリソースが記載された画面に移動します。
- [Create Record Set]を選択
  * Record setを下記の通り設定します。
    * Name：ZoneApexを指定のため何も入力しない
    * Type：Aレコード
    * Alias：Yes
    * Alias Target：CloudFrontDistribution名（xxxx.cloudfront.net.)
    * Routing policy: Simple
    * Evaluate Target Health: No
* リソースレコードの登録が完了です。
![dns.png](https://qiita-image-store.s3.amazonaws.com/0/123578/e4b16db0-6204-e97d-d866-06d3f9ca660d.png)

## 中間CP
いったん、ここまでの設定が動作確認してみます。

* API Gatewayは単純にSuccessと返すだけのMockエンドポイントを用意し、prodステージにデプロイしました。
  * リソース名：success
* ブラウザから動作確認
  * アドレスバーに下記を入力して実行
    * https://xxxxxx.com/success
     ![cp.png](https://qiita-image-store.s3.amazonaws.com/0/123578/e37c087b-8049-d4e9-cf64-848667007d63.png)　

  * 補足）CloudFrontのDistribution設定でACMで取得した証明書を設定していない場合は下記のエラーになります。
![sslerror.png](https://qiita-image-store.s3.amazonaws.com/0/123578/1336ced4-918c-4f32-07d8-7786c5318b45.png)　


## CloudFrontの設定（その２）
現在の設定ではAPI Gatewayに対してのパスしかないので、S3オリジンとパスを追加します。

1. オリジンを追加するために、[Create Origin]を押下
![s3_add_origin.png](https://qiita-image-store.s3.amazonaws.com/0/123578/42da8922-7f4d-9bdc-a1d7-64df65c5f930.png)　

2. S3オリジンの設定
![s3_origin.png](https://qiita-image-store.s3.amazonaws.com/0/123578/aea6a670-94fb-7879-bc3f-a9ad7117135b.png)　

3. S3オリジン設定完了
![s3_origin_result.png](https://qiita-image-store.s3.amazonaws.com/0/123578/805e3b37-a5dc-8f99-2f7b-38a299ebc9a0.png)　

4. S3オリジンへのリクエストを受けるためのレスポンスを追加するために[Create Behavior]を押下
![s3_add_behavior.png](https://qiita-image-store.s3.amazonaws.com/0/123578/e1f1b3c6-1c4c-af8c-00ee-58542cef62d1.png)　

5. S3 Behaviorの設定
見切れているオプションについてはデフォルトのままで問題ありません。
![s3_behavior.png](https://qiita-image-store.s3.amazonaws.com/0/123578/d2bcb638-c56d-dab4-806f-4452f09a35d2.png)　

6. S3 Behaviorの設定完了
![s3_behavior._result.png](https://qiita-image-store.s3.amazonaws.com/0/123578/6c9a983d-f20d-bebf-1c75-5ba6b376fd15.png)　

## S3のバケットプロパティの確認
CloudFrontからコンテンツ配信するために下記設定を確認してください。

  * Static website hosting：Enabledとなっていること
  * バケットポリシーはOAIのみAllowとなっていること
    * 例えば以前にS3からStatic website hostingしていた場合、下記の設定が残っていることがあります。

    ```json:bucket.json
        {
          "Version": "2008-10-17",
          "Id": "test",
          "Statement": [
              {
                  "Sid": "1",
                  "Effect": "Allow",
                  "Principal": {
                      "AWS": "*"
                  },
                  "Action": "s3:GetObject",
                  "Resource": "arn:aws:s3:::bucket-name/*"
              }
          ]
        }

    ```

    * 上記の場合、CloudFrontからのアクセス＋S3への直接アクセスも許可していることになり、OAIで絞り込んでいる意味がないため削除し、プリンシパルがCloudFrontのみのアクセスだけ残してください。　
    
    ```json:bucket.json
        {
            "Version": "2008-10-17",
            "Id": "PolicyForCloudFrontPrivateContent",
            "Statement": [
                {
                    "Sid": "1",
                    "Effect": "Allow",
                    "Principal": {
                        "AWS": "arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity XXXXXXXXXXXXXX"
                    },
                    "Action": "s3:GetObject",
                    "Resource": "arn:aws:s3:::bucket-name/*"
                }
            ]
        }
    ```

# 結果
長かったですが、ここまでの設定内容を確認したら完了です。

  1. Route53で取得したドメイン名でAPIコールが出来ること
  2.  Route53で取得したドメイン名でS3コンテンツを取得できること
  3. S3のドメイン名でS3コンテンツを取得できないこと

# まとめ

* CloudFront経由でAPI GatewayとS3を配信する構成を作成できました。
* CloudFrontの設定が多く結構手こずりました。
  * Blackbeltの資料など読んで各設定が何を意味しているか頭にいれておくとよいです。