Amazon Web Services ブログ

CloudFront Functions を使用して Amazon S3 の Amazon CloudFront オリジンでデフォルトディレクトリインデックスを実装

Amazon CloudFront Functions により、以前は AWS Lambda@Edge でしかできなかったことを、より高性能な方法で行うことができるようになりました。たとえば、Amazon CloudFront のオリジンアクセスアイデンティティ (OAI) を使用してオリジンを保護する場合に必要不可欠である URI パスを操作できます。

2017年に私が書いた投稿 (Implementing Default Directory Indexes in Amazon S3-backed Amazon CloudFront Origins Using Lambda@Edge) で、それには CloudFront ディストリビューションとオリジン ( Amazon S3 など) の間でリクエストを書き換えて、http://example.com/about/ などの URL がデフォルトドキュメントインデックス (<bucketname>/about/index.html など) に解決され、S3 が「noSuchKey」エラーを返さないようにする方法を示しました。これは CloudFront がサイト訪問者にエラーを返すことを減らします。

この投稿では、CloudFront Functions を使用して同じ結果を達成する方法を紹介します。2017年の投稿を書いたとき、CloudFront Functions は存在しませんでした。現在、CloudFront Functions は、URI パスなどのアイテムを軽量に操作するために推奨されているサービスです。CloudFront Functions に関する情報や CloudFront Functions と Lambda@Edge の違いについては、この投稿を参照してください。

図 1 : アーキテクチャー図

この例では、デフォルトインデックスファイル (この例では index.html ) を追加する、またはパスに .html などのファイル名拡張子がない場合、リクエスト URI のパスを変更するために CloudFront ディストリビューションに関連付けられた CloudFront Functions で JavaScript コードを使用します。オリジンアクセスアイデンティティ (OAI) を使用してプライベートに保たれる S3 バケットで CloudFront を使用する場合、CloudFront は CloudFront ディストリビューションに関連付けられたアクセス権限を使用して、S3 REST API にて S3 にアクセスします。これにより、CloudFront のみがバケット (ウェブサイトのオリジンとして機能している) へのアクセスを許可されていることを S3 が検証できるようになり、バケットがプライベートにしかアクセスできない状態に保たれます。

CloudFront ではサイトルートに対してデフォルトドキュメントインデックスを指定できますが、サブディレクトリに対して同様の機能はありません。したがって、http://example.com/about/ などのページに対するリクエストはそのまま S3 に渡されますが、'about/' は S3 のキー (ファイルとも呼ばれる) にマップされません。これにより、“ KeyNotFound ”エラーメッセージが表示され、S3 から CloudFront に返されます。顧客の S3 バケットに関する情報が意図せずに漏洩するのを防ぐため、CloudFront は代わりに、より一般的な“ AccessDenied ”エラーメッセージをウェブビューアに返します。このソリューションを実装すると、ビューワーが http://example.com/abouthttp://example.com/about/ などの URI をリクエストしたときに、CloudFront はリクエストの一部としてデフォルトのファイル名 ( index.html ) を S3 に追加します。これは、特にシングルページアプリケーション (SPA) で一般的です。

ウォークスルー

以下の手順は、CloudFront ディストリビューションが既にデプロイされていることを前提としています。ここでは、S3 は CloudFront ディストリビューションのオリジンとして例示されていますが、オリジンからリソースをリクエストする前にオリジンリクエストを書き換える必要が発生する、すべての CloudFront ディストリビューションにも同じ手順が適用できます。

前提条件

このウォークスルーでは、次の前提条件が必要です。

  • AWSアカウント
  • CloudFront ディストリビューションがデプロイされ、S3 オリジンに関連付けられていること
    • CloudFront ディストリビューションが S3 オリジンでデプロイおよび設定されていることを確認する
    • オリジンにコンテンツが入力されていることを確認する。ルート ( http://example.com/index.html ) とサブフォルダ (http://example.com/about/index.html) の両方で ‘index.html' をデフォルトのIndexドキュメントとして使用する
    • オプション : CloudFront で使用するドメイン名がある場合 ( CloudFront が提供する汎用ドメインではなく)、このドキュメントに従ってディストリビューションに関連付けをする
  • AWS コンソールを使用して CloudFront Functions を作成して関連付ける適切な IAM 権限を割り当てる
  • CloudFront Functions に使用する GitHub のサンプル JavaScript コードにアクセスする

CloudFront Functions を作成してディストリビューションに関連付ける

CloudFrontのドキュメントでは、CloudFront Functions、関数コード (プログラミングモデル) の記述方法、関数の作成、ディストリビューションへの関連付けについて詳しく説明しています。この例では、ユースケースのためにすでに準備されているサンプルコードを使用します。このコードを使用して関数を作成し、それをディストリビューションに関連付けて、動作を検証します。

関数をテストできることを確認する

デフォルトでは、CloudFront Functions はメトリックス (関数の呼び出し回数、エラーなど) を追加費用なしで CloudWatch にパブリッシュします。開発時には、関数をテストするためにロギングステートメントを関数に追加すると便利です。これらのロギングメッセージは、関数が LIVE ステージにパブリッシュされたときにのみ CloudWatch Logs にパブリッシュされます。CloudFront Functions を LIVE ステージにデプロイするときは、ロギングステートメントを削除することをお勧めします。この関数はビューワーリクエストに関連付けられ、CloudFront へのリクエストごとに実行されるためです。ログのストレージコストが時間の経過とともにチェックされずに増大するのを防ぐために、CloudFront Functions のロググループに保管設定を作成することをお勧めします。これは、CloudWatch Logs は、提供されている無料利用枠を超えるとにコストがかかるためです。サンプルコードにはロギングステートメントが含まれていないため、関数が意図したとおりに動作することを実証できるように、S3 バケット (Origin) を次のように構成しました。

図 2 : S3 バケットのルートを示すスクリーンショット

上記には、CloudFront ディストリビューション ( http://example.com ) のルートに対応する index.html ファイルがあります。また、http://example.com/about/ に対応する別の index.html ファイル (ここには表示されていません) が入っているサブフォルダ ( about/ ) もあります。さらに、各ファイルに記述的で一意の HTML コンテンツを入れて、要求された URI パスとブラウザでの出力に基づいて、期待されるコンテンツが提供されていることをブラウザで確認できるようにしました。さらに、各ファイルに説明的で一意な HTML コンテンツを入れて、要求された URI パスとブラウザでの出力に基づいて、期待されるコンテンツが提供されていることをブラウザで確認できるようにしました。

index.html contents:

<!doctype html>
<html class="no-js" lang="">

<head>
  <meta charset="utf-8">
  <title>Main Page</title>
</head>

<body>

  <p>Hello. You're in the root directory of the website.</p>

</body>

</html>

about/index.html contents:

<!doctype html>
<html class="no-js" lang="">

<head>
  <meta charset="utf-8">
  <title>About</title>
</head>

<body>

  <p>You're in the About Me directory of the website.</p>

</body>

</html>

この状態では、CloudFront ディストリビューションの http://example.com/about/ パスに移動しようとすると、次のエラーが表示されます。

<Error>
<Code>AccessDenied</Code>
<Message>Access Denied</Message>
<RequestId>…</RequestId>
<HostId>…</HostId>
</Error>

これは、CloudFront と OAI を使用して S3 オリジンにアクセスする場合に予想される動作です。http://example.com/about も試しても同じエラーが発生します。次に、CloudFront Functions を実装して、各リクエストの最後にファイル拡張子がない場合は常に、リクエストされた URI を書き換えるようにします。

CloudFront Functions の実装

まず、CloudFront Functions を作成する必要があります。コンソールで、左側のメニューバーの [ Functions ] セクションを選択します。

図 3 : CloudFront Functions にアクセスする場所を示す CloudFront コンソール

次に、新しい関数を作成します。関数に rewriteDefaultIndexRequest などの名前を付けます。

図 4 : Create function を示す CloudFront コンソール

ここで、サンプルコードaws-samples/amazon-cloudfront-functions の GitHub リポジトリからコピーします (このリポジトリは index.js として url-rewrite-single page-apps フォルダーにあります)。サンプルコードは次の 2 つのことを行います。

  1. リクエストされた URI が ‘ / ‘ (example.com/about/ など) で終わる場合、デフォルトのインデックスファイルとして ‘index.html‘ をリクエストに追加する
  2. リクエストされた URI にファイル拡張子 ( example.com/about など) がない場合、リクエストに ‘/index.html’ が追加される

index.html 以外のファイルをデフォルトのドキュメントインデックスとして使用している場合は、ここで置き換えることができます。

図 5 : Function code を示すスクリーンショット

新しい CloudFront Functions への変更を保存した後、上の [ Publish ] タブを選択して関数をパブリッシュし、関数を公開します。完了したら、関数をパブリッシュした後に次の画像に表示される [ Add association ] ボタンを選択して、ディストリビューションに関連付けます。

図 6 : 関数の Publish を示すスクリーンショット

次の画面で、適切な CloudFront ディストリビューションを選択します。これをビューワーリクエストにすると、受信するクライアントリクエストが、S3 Origin のキーとして存在すると期待されるものに確実に書き換えられます。たとえば、‘about’、‘about/’、‘about/index.html’ に対するクライアントのリクエストはすべて S3 の同じオブジェクトを参照しており、受信リクエストの ‘about’ と ‘about/’ を書き直します。CloudFront はまず、オブジェクトがすでにキャッシュに存在するかどうかを確認し、必要に応じてオリジンからフェッチします。

私の CloudFront ディストリビューションには 1 つのキャッシュ動作 (1つのオリジン) しかないので、ここではそれを選択しました。複数の S3 オリジンが同じディストリビューションに異なる動作として関連付けられている場合は、この書き換え機能を必要とするすべてのオリジンに関数を関連付ける必要があります。

図 7 : 関数の関連付けを示すスクリーンショット

CloudFront Functions をディストリビューションに関連付けるには、CloudFront がデプロイを実行する必要があります。これには数分かかる場合があります。デプロイのステータスを監視して、テストの前に完了したことを確認します。

図 8 : ディストリビューションのデプロイの進捗を示す CloudFront コンソール

テスト

デプロイが完了したら (上記のステータスが“デプロイ”から新しい“最終変更日”に変わります)、以前に失敗したパス http://example.com/about/ を要求して、関数が URL を書き換えていることを確認します。成功した場合、ディストリビューションは S3 オリジンの about/ サブフォルダーにある index.html ファイルを返すはずです。Linux の cURL ユーティリティまたはブラウザを使用して以下をテストできます。

図 9 : CloudFront ディストリビューションのルートを示すブラウザ

図 10 : ‘about/’ パスを示すブラウザ

クリーンアップ

CloudFront の無料利用枠には現在、毎月 2,000,000 回の CloudFront Functions 呼び出しが無料で含まれています。この例の追加コストを回避するには、CloudFront Functions とオリジンの動作との関連付けを削除してください。無料利用枠を超えても課金されません。関数自体の削除はオプションです。機能コードは無料です。オプションで、CloudFront ディストリビューションとオリジンを作成して不要になった場合は、それらを削除できます。

図 11 : ディストリビューションから関数の関連付けを削除する方法を示すスクリーンショット

おわりに

書き換えが行われると、Amazon CloudFrontAmazon S3 にアクセスするときにデフォルトのドキュメント ( index.html ) をリクエストします。これは、CloudFront オリジンアクセスアイデンティティを使用して S3 オリジンへのアクセスを保護する場合に特に役立ちます。これにより、example.com/about/ などのサブディレクトリにアクセスしようとしたときに、S3 が CloudFront にエラーを返すのを防ぎます。これは、リクエストをそのまま渡すのではなく、デフォルトのインデックスをリクエストするためです。

参考

この記事では、CloudFront Functions を使用してリクエスト URI を書き換える方法のみに焦点を当てました。CloudFront ディストリビューションと S3 オリジンのパフォーマンスとセキュリティをさらに最適化することに興味がある場合は、次のリソースを確認してください。

Restricting access to Amazon S3 content by using an origin access identity (OAI)

Blocking public access to an S3 bucket

CloudFront Cache and Origin Request Policies

著者について

Ronnie Eichler

Ronnie Eichler は、米国テキサス州ダラスのシニアグローバルソリューションアーキテクトです。テクノロジーの問題解決に忙しくないとき、Ronnie は旅行、写真、フィットネス(現在はキックボクシング、サイクリング、ヨガ)を楽しんでいます。

翻訳は Partner Success SA 小林が担当しました。原文はこちらをご覧ください。