CloudFront導入とCognitoで認証した話

株式会社クラシコム テクノロジーグループの冨田です。

北欧、暮らしの道具店とクラシコムのコーポレートサイトにCloudFrontを導入しました。以前から画像についてはCloudFrontやimgixを利用し配信していました。今回はサイト自体を配信するようにしました。

# 背景

2022年8月に東証グロース市場へ上場しました。

- メディアへの露出が増えたため、突発的なアクセスがきても耐えられるようにしたい。
- 既に導入していた[CloudFront Security Savings Bundle](https://docs.aws.amazon.com/ja_jp/AmazonCloudFront/latest/DeveloperGuide/savings-bundle.html)でコストを抑えたい。

という動機からCloudFrontを導入しました。

# やったこと

## Cognitoによる認証

クラシコムの検証環境（ステージング環境）はALBでCognitoによる認証をかけており、社員のみアクセス可能にしていました。今回ALBの前段にCloudFrontを導入しましたが、そのままの構成でCloudFrontを前段に置いてしまうと社員以外もキャッシュされた内容がCloudFrontで閲覧可能になってしまいます。

検証環境（ステージング環境）と本番環境でなるべくインフラの構成を合わせておきたいのでLambda@Edgeで[cognito-at-edge](https://github.com/awslabs/cognito-at-edge)をデプロイし、Cognitoでの認証をかけました。

Lambda@Edgeの制約で下記の点ではまってしまいました。

- Lambda環境変数が利用できないのでCognito `userPoolAppSecret` をAWS System Manager パラメータで管理するようにしました。
- LambdaのNode.jsは[SDK for JavaScript 2.1055.0が標準](https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/lambda-runtimes.html) バージョン3を組み込もうとしたが[1MBの関数および組み込みライブラリの最大圧縮サイズ](https://docs.aws.amazon.com/ja_jp/AmazonCloudFront/latest/DeveloperGuide/cloudfront-limits.html#limits-lambda-at-edge)にひっかかってしまいました。

https://github.com/awslabs/cognito-at-edge

## ALBへのリクエストの制限

CloudFrontを用意したため、ALBに直接アクセスされてしまうと用意した意味がありません。

CloudFrontからALBへのアクセスはリクエストにカスタムHTTPヘッダを付与し、カスタムヘッダが付与されているものだけに応答するようにしてあります。2022年2月からCloudFront マネージドプレフィックスリストが用意されたため、ALBのセキュリティグループで、CloudFrontからのインバウンドHTTPSアクセスのみを許可するようにしました。また、万が一に備えてステージング環境においてはALBでもCognitoによる認証は引き続きかけています。

https://docs.aws.amazon.com/ja_jp/AmazonCloudFront/latest/DeveloperGuide/restrict-access-to-load-balancer.html

# はまったこと

## クライアントのIPアドレス、スキーマの取り扱い

ECS Fargate上でNGINXとphp-fpm、Laravelを利用しています。

NGINXのアクセスログでクライアントIPアドレスやリクエストスキーマを記録しています。また、Laravel上でもクライアントIPアドレスやリクエストスキーマを利用しています。

クライアントのIPアドレスはNGINXで `real_ip_header X-Forwarded-For;` リクエストヘッダ、 `set_real_ip_from 192.168.1.0/24;` で信頼できるアドレス、 `real_ip_recursive on;` で信頼されない最後のアドレスを取得するようにしていました。

CloudFrontのIPアドレスは[JSON形式で公開されています](https://docs.aws.amazon.com/ja_jp/AmazonCloudFront/latest/DeveloperGuide/LocationsOfEdgeServers.html)が不定期に変更されるため、`set_real_ip_from`を自動的に更新するのは少し面倒です。

クラシコムではインフラのアドバイザーとして[カヤックの藤原さん](https://www.kayac.com/team/fujiwara-shunichiro)にアドバイス、レビューをしていただいています。`CloudFront-Viewer-Address`や`CloudFront-Forwarded-Proto` リクエストヘッダを利用すれば良いとアドバイスをいただきました。

`CloudFront-Viewer-Address`はIPアドレスとポート番号が含まれているため、IPアドレスのみを利用したい場合には分割する必要があります。

NGINXのLua moduleを利用し分割しました。

https://qiita.com/2357gi/items/af37d03d623fcf700674

NGINXのオフィシャルのDockerイメージそのままではLua moduleは利用できないため、オフィシャルの[ドキュメント](https://github.com/nginxinc/docker-nginx/blob/master/modules/README.md)をもとにビルドしました。

運用は無事回っているもののデプロイ時にLua moduleのビルド時間がのびてしまいました。今後の課題としてはビルド方法の見直しを行うか、[OpenResty](https://github.com/openresty/docker-openresty)のイメージを利用するか、[CloudFront Functions](https://docs.aws.amazon.com/ja_jp/AmazonCloudFront/latest/DeveloperGuide/example-function-add-true-client-ip-header.html)側に任せようかなと思います。

https://docs.aws.amazon.com/ja_jp/AmazonCloudFront/latest/DeveloperGuide/using-cloudfront-headers.html

## カナリアリリースやメンテナンス

カナリアリリースやメンテナンス時にALBのリスナールール 送信元IPアドレス に 作業者のIPアドレスを設定して動作確認をしていました。CloudFrontを経由することになり送信元IPアドレスがCloudFrontになってしまいました。

ALBのリスナールール の HTTPヘッダで ヘッダ名は `CloudFront-Viewer-Address` を指定し、比較文字列には 作業者のIPアドレスを指定しました、ポート番号はワイルドカード文字列を利用しました。

設定の例

```
203.0.113.1:*
```

https://docs.aws.amazon.com/ja_jp/elasticloadbalancing/latest/application/listener-update-rules.html


## 並行運用

日中に旧ALBからCloudFrontへDNSの切り替えを行い、DNSのキャッシュが切れるまで並行運用を行いました。

DNSを変更して3分後には大半のトラフィックはCloudFront経由に切り替わったのですが、CATV系のプロバイダやBotは1週間経過しても旧ALBへのアクセスが続きました。

ALBも運用中にIPアドレスが変わることもあるということで旧ALBは退役させました。

![スクリーンショット 2022-08-19 18.26.00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/45760/4b41fc26-6794-ee9f-cce5-0a53e780514d.png)

# まとめ

CloudFrontの導入で7/12の[テレビ東京系列のWBS（ワールドビジネスサテライト）](https://kurashi.com/news/12593)でとりあげられたタイミングや、8/5の[上場当日](https://kurashi.com/news/12679)も無事乗り切ることができほっとしました。

一点悔やまれるのはALB側のWAFにレートリミットを設定してしまい、一部のリクエストをブロックしてしまいました。今後も改善を重ね「サイトに快適・安全にアクセスできて当たり前」な状態にしていきたいと思っています。

![スクリーンショット 2022-08-12 15.48.50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/45760/9952d8df-d0a9-862f-d63f-697274663e6a.png)