CloudFront+API Gateway構成ではHostヘッダに気をつけよう

## TL;DR
CloudFrontのオリジンにAPI Gatewayを追加したい時には、
Origin Request Policyにて、`AllViewer`の設定で全てのヘッダをバイパスするように設定するとエラーとなる。
原因はCloudFront向きの`Host`ヘッダの情報をAPI Gatewayにそのままバイパスしてしまうため。

## はじめに
よく知られていることですが、API Gatewayは内部的にCloudFrontの仕組みを利用しているため、
レスポンスデータのキャッシュやスケーラビリティの確保については、CloudFrontをユーザー側で用意しなくても、
CloudFrontのメリットを受けられるようになっています。
しかしながら、例えば

- WAFによるアクセス制限の仕組みを利用したい
- キャッシュ設定をより細かく設定したい
(`Maximum TTL`, `Minimum TTL`、`Invalidation`等)

などのユースケースにて、意図的にAPI Gatewayの前段にCloudFrontを置きたくなることがあります。

今回自分もキャッシュ設定のメリットを受けるためにこの構成を採用しましたが、表題の通りヘッダ設定周りでハマったので残しておこうと思います。

## 今回実施した構成

今回実施した構成は以下のようなイメージです。

![構成.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/272559/bfb53f1b-23e9-993f-3838-e389325c194a.png)


ユーザーはAPIにアクセスする際はCloudFrontを経由します。
CloudFrontにキャッシュデータがある場合はキャッシュを返し、キャッシュがない場合のみAPI Gateway経由で各Lambdaに配置されたAPI関数を実行します。
また、CloudFrontとAPI Gatewayにはそれぞれドメインを用意します。ドメインはRoute53でAレコード、ACMによってSSL証明書とCNAMEレコードを発行します。
API Gatewayにもカスタムドメインを用意するのは、自動で着くステージ名のパスが要求仕様的に不要で、カスタムドメインによって内包するためです。


## 最初に行った設定
早速CloudFrontのDistributionを作成します。
最初は、おおよそ以下のような設定で作成を行いました。(主要部分の抜粋)

### Origin Settings
- `Origin Domain Name` : ＜API Gatewayのカスタムドメイン＞
- `Origin Pass` : *

### Cache Policy
- Cache Key Content
  - `Headers`: `whitelist`
    - `Authorization` 
    (↑BASIC認証の成功有無でキャッシュするか決定するため)
  - `QueryString`: `All` (←全てのパターンで識別するため)
  - 圧縮は行わない


### Origin Request Policy
- Origin Request contents
  - **Headers : AllViewer** 
  - `QueryString`: `All`
    (↑元々のリクエスト同等のリクエスト内容となるように)

さて、上記のような設定でDistributionを作成し、早速アクセスをしてみました。すると、以下のようなHTMLページがレスポンスで帰ってきました。

![エラー画面.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/272559/400e2cfe-6c70-05b7-1477-dcb16b7dc11e.png)


うーん困った...
APIは全てjsonを返すものなので、HTMLが帰ってきていると言うことはCloudFrontとAPI Gateway部分でのエラーだろうと考え設定周りを調べました。


## 原因
調査を行ったところ、原因は設定内でも太字にしていた、Origin Request PolicyにてHeadersを`AllViewer`として、リクエストの全てのヘッダをオリジンへバイパスしていることが原因でした。

今回のキーとなったのは、リクエストにて自動的に付与される、`Host`ヘッダの内容です。
`Host`ヘッダとは、リクエストが送信される先のサーバーのホスト名とポート番号を指定するためのヘッダ情報です。HTTP/1.1だと唯一の必須ヘッダになっています。
(参考：https://developer.mozilla.org/ja/docs/Web/HTTP/Headers/Host)

API Gatewayに直接アクセスしようとしていた時は(当たり前ですが)`Host`ヘッダの宛先はAPI Gatewayのドメインになっています。

![apigw直接.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/272559/e7988b29-3f52-51c3-12c7-6ab29e35b198.png)


しかし今回、CloudFrontからAPI Gatewayにアクセスする際、`AllViewer`の設定によって、ユーザーがCloudFrontへリクエストを投げる際に付与されていた`Host`ヘッダもそのままバイパスしてAPI Gatewayにリクエストするようになっています。
ここからは確認できていませんが恐らく、

- `Host`ヘッダにCloudFrontのドメインを設定したままAPI Gatewayのドメインにアクセスしようとした
- CloudFrontアクセス時の`Host`ヘッダに加え、API Gatewayアクセス時の`Host`ヘッダが作成され、2つの`Host`ヘッダがある不正なリクエストとなってしまった

かのいずれかでアクセスエラーとなっていたのでしょう。その結果、CloudFrontがエラーを検知して403のHTMLを返していた、ということです。

![CF経由不明Host.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/272559/9ed4d4a8-69b0-5eb0-89e0-410f1a398aa2.png)



## 対応方法

原因がわかったので対策として、Headersの設定を`whitelist`にし、必要なヘッダのみをバイパスするように設定を変更しました。今回は`Accept`と`Accept-Language`を追加しています。
なお、`Authorization`ヘッダについてはCache Policyにて識別対象に設定しているため、こちらで設定を行わなくともオリジンへのリクエスト時にはバイパスされています。

![対策.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/272559/5ad12c3f-6e2e-ef27-c885-124d2e676a6e.png)


これによって、CloudFrontからオリジンとしてAPI Gatewayにアクセスする際に適切な`Host`ヘッダが再設定されるようになり、正常にアクセスができるようになりました。

## まとめ
原因の調査には時間がかかりましたが、おかげで普段あまり意識しきれていなかったHTTP/1.1の仕様部分まで理解を深められて良かったです。
低レベルなコンピューターサイエンスの知識はやはり重要です。