Cloudfront→Nginx→Railsアプリ構成での考慮点について

## 背景
* 私がAWS上で運営している個人サービスはサーバ証明書をAWS Certificate managerから発行しているのですが、このサーバ証明書は、一般的にはロードバランサー（LB）やCloudFrontといったAWSのマネージドサービスと併用する必要があります。[※出所](https://repost.aws/ja/knowledge-center/configure-acm-certificates-ec2)

* LBは個人サービスとしては少しコストが高いため、前段にCloudfrontを置き、オリジンサーバとしてEC2を設定する構成にしています。

本記事は、その構築にあたり考慮すべき点を備忘録として記録するものです。

### End To EndでのHTTP(s)通信の流れまとめ

`ブラウザ (https) → CloudFront:443 → (http) → EC2{ Nginx:80 → (http) → Railsアプリ:3000}`

## 考慮点①：オリジンサーバ(EC)のパブリックIPが判るとCloudfrontを経由せず通信できてしまう問題

* 問題の詳細は[クラスメソッドさんのこちらの記事](https://dev.classmethod.jp/articles/amazon-cloudfront-managed-prefix-list/)に詳細があります。

* この件についてはEC2に紐づくセキュリティグループにインバウンド通信の許可送信元としてCloudfrontのマネージドプレフィックスリストを指定することで解消することができます。[※出所](https://aws.amazon.com/jp/blogs/news/limit-access-to-your-origins-using-the-aws-managed-prefix-list-for-amazon-cloudfront/)

## 考慮点②：CloudfrontがX-Forwarded-Protoをバックエンドに連携しない問題

これがなぜ問題かというと、

* X-Forwarded-Protoヘッダーは、プロキシやLBがクライアントからのリクエストがHTTPであったかHTTPSであったかをバックエンドサーバーに伝えるために使用されます。
 
* 今回だとRailsアプリは通信こそhttpで受け取るものの、X-Forwarded-for-protoにhttpsという元の通信プロトコルがバインドされていれば、それを踏まえた処理（レスポンスに設定するURLスキームをhttpではなくhttpsにするなど）を行います。

ゆえに、X-Forwarded-for-Protoに元の通信がhttpsであった旨がないと、http通信が届いたと認識してアプリケーションが正常に動作しない可能性があります。

### 発生した問題①


```text:エラーログ抜粋
HTTP Origin header (https://hogehoge.net) didn't match request.base_url (http://hogehoge.net) 
```

上記自体はconfig/environments/production.rbなどに以下の設定をすることでセキュリティは低下するものの、回避は可能だったのですが、後段の問題②が発生してしまいました。（CRSF対策はCRSFトークンで代替できるため）

`config.action_controller.forgery_protection_origin_check = false`

### 発生した問題②

すべてのユースケースではないものの、redirect_toでページをリダイレクトさせる処理で、最初のリクエストが HTTPS で保護されているが、Web ページを表示するために HTTPS コンテンツ​ と HTTP コンテンツが読み込まれることにより、ブラウザがセキュリティ上の問題として判断する`(block:Mixed Content)`が発生。

## 解消策① Nginxのバックエンド連携でX-Forwarded-Protoがhttpsとなるよう設定

NginxでX-Forwarded-Protoをhttpsに設定する。

```/etc/nginx/sites-enabled/hogehoge.net

server {
    listen 80;
    server_name hogehoge.net;
    root /hogehoge/app/public;

    location / {
        proxy_pass http://localhost:3000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto https;  # https決め打ちで設定
    }

    error_page 500 502 503 504 /500.html;
    client_max_body_size 10M;
    keepalive_timeout 10;
}
```

## 解消策②：Cloudfront-Forwarded-Protoの値をX-Forwarded-Protoに引き継ぐ

Cloudfrontは`X-Forwarded-for-Proto`の代わりに`CloudFront-Forwarded-Proto`に同様の情報をバインドする仕様のようです。[出所](https://docs.aws.amazon.com/ja_jp/AmazonCloudFront/latest/DeveloperGuide/adding-cloudfront-headers.html)

こちらはmapブロックが発生するためnginx.confへの設定も必要になりますが、Cloudfrontの仕様に即した対応になります。

```/etc/nginx/nginx.conf
～略～
map $http_cloudfront_forwarded_proto $custom_proto {
        default $http_cloudfront_forwarded_proto;
    }
～略～
```

```/etc/nginx/sites-enabled/hogehoge.net
server {
    listen 80;
    server_name hogehoge.net;
    root /hogehoge/app/public;

    location / {
        proxy_pass http://localhost:3000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $custom_proto;  # 引継ぎ
    }

    error_page 500 502 503 504 /500.html;
    client_max_body_size 10M;
    keepalive_timeout 10;
}

```