エッジでJavaScriptを実行するCloudFront Functionsのユースケースまとめ

エッジで軽量なJavaScriptを実行するAmazon CloudFront Functionsのユースケースをまとめました
2021.05.10

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

AWSはエッジ環境で軽量なJavaScriptによる処理を実行可能な新サービス「Amazon CloudFront Functions」を発表しました。

以前から存在するLambda@Edgeは機能が豊富な一方で、処理が重く、利用費も高めだったのに対して、 CloudFront Functionsは機能を絞り込んでいる一方で、高速に動作し、利用費も安くすみます。

CloudFront Functions の

  • 1 msの起動時間制限
  • ネットワーク・ファイルシステムアクセスできない
  • Request Viewer(キャッシュ前のリクエスト)とRequest Response(キャッシュのレスポンス)にのみ割り込める

といった制約から、HTTPリクエスト/レスポンスに対する文字列操作のような実行時間の短いシンプル処理に向いており、公式ドキュメントでは以下の用途が列挙されています。

  • キャッシュキーの正規化(HTTPヘッダー、クエリーストリング、Cookie、URLパス)
  • HTTPヘッダーの変更
  • URLのリダイレクト・リライト
  • リクエストの認証

公式ドキュメントで、実践的なユースケース別にサンプルコード付随しているため、順に紹介します。

Example code for CloudFront Functions - Amazon CloudFront

各サンプルはGitHub レポジトリへのリンクも含まれており、各関数の Readme.md ファイルには詳細な解説も含まれています。一読をおすすめします。

HTTP レスポンスに Cache-Control ヘッダーを追加

サンプル

Webサービスは様々なコンポーネントでコンテンツをキャッシュできます。

CloudFront のような CDN はオリジンのレスポンスをエッジでキャッシュし、 ブラウザはサーバーのレスポンスをクライアントサイドでキャッシュします。

後者のブラウザ向けキャッシュ設定をコントロールするのが、レスポンスヘッダーの Cache-Control です。

例えば

Cache-Control: public, max-age=86400

のようにすると、ブラウザは1日間(= 86400秒)ローカルキャッシュを利用します。

CloudFrontへのリクエストを減らすためにブラウザキャッシュを利用したいけれども、オリジンが Cache-Control ヘッダーを返していない場合、Viewer ResponseでCloudFront Functionを走らせてCache-Controlヘッダーを付与します。

オリジン間リソース共有(CORS)設定

オリジンをまたぐ CSRF 攻撃などを防ぐために、ブラウザーにはSame-Origin Policyが実装されています。 一方で、オリジンをまたいでリクエストしたい時もあり、そのようなリクエストを実現する仕組みが CORS です。

オリジンをまたいでリクエストする際に、どのオリジンからリクエストしているのか名乗のり(Origin リクエストヘッダー)、サーバーはどのオリジンからアクセス可能か返信します(Access-Control-Allow-Origin レスポンスヘッダー)。

例えば、本ブログ(ドメイン名はdev.classmethod.jp)ではアクセス解析Google Analyticsが導入されています。 トラッキングのために、ブログとは異なるドメインの www.google-analytics.com へ POST しています。

  • リクエストヘッダーの Origin
  • レスポンスヘッダーの Access-Control-Allow-Origin

どちらが欠落していても、CORS を伴うリクエストは失敗します。

これらヘッダーは CloudFront Functions で付与することができます。

参考

セキュリティヘッダー

サンプル

HTTPレスポンスヘッダーには、ウェブアプリケーションのセキュリティを高めるための以下のようなヘッダーが存在します。

  • HTTP Strict-Transport-Security (HSTS) : ウェブサイトがブラウザーに HTTP の代わりに HTTPS を用いて通信を行うよう指示するためのものです。
  • Content-Security-Policy(CSP) : クロスサイトスクリプティング (XSS) やデータインジェクション攻撃などのような、特定の種類の攻撃を検知し、影響を軽減するために追加できるセキュリティレイヤーです。これらの攻撃はデータの窃取からサイトの改ざん、マルウェアの拡散に至るまで、様々な目的に用いられます。
  • X-Content-Type-Options : Content-Type ヘッダーで示された MIME タイプを変更せずに従うべきであることを示すために、サーバーによって使用されるマーカーです。これにより、MIME タイプのスニッフィングを抑止することができます。

※ 説明は MDN Web Docs から。

Viewer ResponseでCloudFront Functions を利用すると、オリジンを改修すること無く、ビューアーへのレスポンスにこれらセキュアヘッダーを含めることができます。

Lambda@Edge 版実装は、過去に公式ブログになっています。

参考

クライアントのIPアドレスをリクエストヘッダーに含める

サンプル

リクエストヘッダー X-Forwarded-For には、中継するミドルボックスの IP アドレスが追記されます。 例えば、CloudFront - ALB - EC2 という構成の場合、EC2 上のウェブサーバーへの X-Forwarded-For リクエストヘッダーは、

X-Forwarded-For: クライアントのIPアドレス, CloudFrontエッジのIPアドレス

となります。

クライアントのIPアドレスだけをオリジンサーバーに渡したい場合、この CloudFront Functions を利用します。

サンプルコードでは True-Client-IP というリクエストヘッダーにクライアントのIPアドレスを保存しています。 このヘッダーをオリジンへも転送するよう、CloudFrontを設定してください。

居住地によってリダイレクト

サンプル

CloudFront はビューアーの国、緯度経度といったエリア情報を取得することができます。

クライアントの国を判別する CloudFront-Viewer-Country ヘッダーを利用し、国に対応する URL パスへリダイレクトします。

準備として、CloudFront-Viewer-Country ヘッダーをCloudFront のキャッシュキーに含め、Request Viewerでこのヘッダーを取得できるようにしてください。

同様のアプローチで、CloudFront-Is-Desktop-Viewer のようなデバイスを判定するヘッダーを利用し、デバイスごとにリダイレクトすることも可能です。 この場合も、判定に利用するヘッダーをキャッシュキーに含めてください。

URLパスに index.html を追加

サンプル

S3にある静的ファイルを CloudFront から配信しているとします。

S3 上でHTMLファイルのキーは /index.html のため、 https://HOST/foo/ のような URL でアクセスすると、S3オブジェクトが見つからず 4xx 系エラーが発生します。

Viewer Request で CloudFront Functions を実行することで

  • /foo/ を /foo/index.html
  • /foo を /foo/index.html

というようにS3 フレンドリーに URL を書き換えることができます。

Viewer Request は CloudFront のキャッシュ前のため、URL の正規化、ひいては、キャッシュヒット率の向上にも寄与します。

なお、S3 の静的ウェブサイトホスティングを有効にし、index ドキュメントを設定している場合は、本対応は不要です。

静的ウェブサイトホスティングが無効な場合、 default root object はルートディレクトリでしか /index.html が補完されないため、本対応が有効です。

Lambda@Edge 版実装は、過去に公式ブログになっています。

プライベートコンテンツのトークンの検証

サンプル

CloudFront からプライベートコンテンツを配信するために、署名付きの URL・Cookie を発行する仕組みが備わっています。

これとは別に、 サーバーサイドでJSON Web Token(JWT)を独自に生成してプライベートコンテンツのクエリーストリングに含め、Viewer Requestでトークンの署名を検証するのが本サンプルプログラムです。

READMEドキュメントには、CloudFront標準機能を使った署名に対する優位性について、次の記載があります。

creating a signed URL creates long and complex URLs and is more computationally costly to produce. If you need a simple and lightweight way to validate time bound URLs, this function can be easier than using CloudFront signed URLs.

個人的には、 CloudFront Functions の JavaScript ランタイムに組み込まれている Crypto モジュールのサンプルとして存在していると思われ、特殊な要件がない限りは、独自に署名を発行・検証せず、CloudFront の標準機能を利用したほうが運用が楽と思います。

サンプルには、サーバーサイド向けに JWT を生成するプログラムも含まれています。

最後に

AWSが発表した新たなエッジ向けの軽量なJavaScript実行環境 CloudFront Functions のユースケースを紹介しました。

機能が豊富な Lambda@Edge に比べ

  • 実行時間は 1ms 以内
  • Viewer RequestとViewer Responseでしか動作しない
  • ネットワーク・ファイルシステムは使用できない

といった制約から、HTTPリクエスト・レスポンスの文字列操作に完結するような軽量な処理に向いています。

CloudFront Functionsの大きなメリットの一つは安さです。

実行時間に対する課金はなく、リクエスト辺りの利用費はLambda@Edgeの1/6です。

Viewer RequestとViewer Response は CloudFrontへのすべてのリクエスト・レスポンスで呼び出されます。 アクセス数が多いサイトに Lambda@Edge を導入すると、利用費が望外に高くついたり、高額な利用費のために導入そのものを見送ることもありました。

Lambda@EdgeをCloudFront Functionsに置き換えることで、利用費は大幅に安くなるでしょう。

それでは。