Amazon Web Services ブログ

Amazon CloudFront と AWS Lambda@Edge を用いたプライベートコンテンツの提供

このブログ投稿では、カスタムオリジンからプライベートコンテンツを提供するために、Lambda@Edge を用いて Amazon CloudFront ディストリビューションを設定する方法について取り上げます。

Amazon CloudFront はグローバルなコンテンツ配信ネットワーク(CDN) サービスであり、世界中のビューワー(クライアント) へ ウェブサイト、アプリケーション、ビデオ、大容量ファイル、および API を高い安全性を確保しつつ高速に配信します。CloudFront はコンテンツを提供する様々な種類のオリジンを組み合わせて設定することができます。例えば、AWSのサービス(e.g. Amazon S3、Amazon EC2、Elastic Load Balancing、Amazon API Gateway…) やパブリックな HTTP(S) エンドポイントをオリジンに設定することができます。一方で AWS のサービスをオリジンとして設定する際は、次のようなメリットがあります。

  • コスト最適化: AWS オリジンから CloudFront エッジロケーションへのデータ転送が無料です。
  • 高性能化: AWS上の別のリソースへのアクセスは、Amazon が可用性とレイテンシーを監視している高品質なネットワーク上で行われます。この監視により、低いエラー率を維持しつつ、ウインドウサイズを大きく保つことができています。

Lambda@Edge はコンテンツをカスタマイズする AWS Lambda 関数をビューワーに近い CloudFront ロケーションで実行します。これによりCloudFront をプログラミング可能なコンテンツ配信ネットワークにしています。Lambda@Edge は Node.js コードをサーバーのプロビジョニングや管理の必要なく実行できます。ビューワーのコンテンツリクエストや CloudFront からオリジンサーバーへのリクエストのような CloudFront イベントに応答してあなたのコードを実行できます。

インターネットを利用してコンテンツを配信する多くの企業では、ドキュメント、ビジネスデータ、メディアストリーミング、もしくは特定のユーザ(例えば課金済みのユーザ) のみに配信するコンテンツについてアクセス制限をかけたいと考えています。CloudFront を使って、これらプライベートコンテンツを安全に配信するためには下記のことを実施する必要があります。

  • ユーザーがプライベートコンテンツにアクセスする際に、CloudFront の 署名付き URL または署名付き Cookie の使用をユーザに要求します。
  • オリジンへのアクセスを CloudFront からのみできるように制限します。

このブログでは以降、CloudFront と Lambda@Edge を用いてどのようにオリジンへのアクセスを制限するか、という 2 つ目のポイントに焦点をあてていきます。一般的には、いくつかの技術を利用してオリジンへのアクセス制限をすることができます。

  • Amazon S3 では、コンテンツへのアクセスを制限するオリジンアクセスアイデンティティを設定します。
  • カスタムオリジンのファイアウォールに Amazon CloudFront IPs のIPアドレスをホワイトリストとして登録します。カスタムオリジンは HTTP(S) のエンドポイントであり、例えば EC2 上で稼働する HTTP サーバーやプライベート環境で管理する HTTP サーバーなどです。VPC 内のオリジンを設定した場合、AWS Lambda を利用することでセキュリティグループを自動でアップデートすることができます。
  • リクエストをカスタムオリジンへ転送するときに、カスタムヘッダーを含むように CloudFront ディストリビューションを設定します。その際はヘッダー名と値を定めておく必要があります。例えば、オリジンとして API Gateway を利用するときは、カスタムヘッダーとして API Key を値とした x-api-key ヘッダーを設定することができます。

しかしながら、カスタムオリジンにさらなるアクセスコントロールが必要な場合では、次の 3 ステップにより Lambda@Edge を利用することができます。この例では、Secure Link Module を用いた NGINX HTTP サーバーでカスタムオリジンを設定しています。このモジュールでは、サーバー上のプライベートコンテンツにアクセスするために署名付き URL が必要であり、そうでない場合はアクセスが禁止されます。

Step 1: CloudFront ディストリビューションの作成

マネジメントコンソールで、新しくウェブディストリビューションを作成します。

Origin Domain Name を入力し、Origin Protocol Policy を HTTPS のみとし、Origin Response Timeout と Origin Keep-alive Timeout を設定します。簡単にするために、その他の設定はデフォルトのままとします。

Step 2: Lambda@Edge関数の作成

マネジメントコンソールで、AWS Lambda サービスのコンソールへアクセスし、us-east-1(バージニア北部) リージョンで新しく Lambda 関数を作成します。

次に、NGINX 上の Secure Link Module を使って生成した秘密鍵をコピー&ペーストしてCredentials.json ファイルを作成します。

最後に下記の Node.js コードをメインの index.js ファイルで利用します。


'use strict';

const crypto = require('crypto');
const credentials = require('credentials.json');
const secret = credentials.secret;

exports.handler = (event, context, callback) => { 
    const request = event.Records[0].cf.request;
    
    const expires = generateExpires();
    const signature = "md5="+generateSecurePathHash(expires, request.origin.custom.path + request.uri)+"&expires="+expires;
    
    if (request.querystring) {
        request.querystring = request.querystring + "&" + signature;
    } else {
        request.querystring = signature;
    }
    
    callback(null, request);
}

function generateExpires() {
    // Set expiration time to 1 hour from now
    const date = new Date();
    date.setHours(date.getHours()+1);
    return Math.floor(date/1000);
}

function generateSecurePathHash(expires, URL) {
    // construct string to sign
    const unsignedString = expires + URL  + ' ' + secret; 
    
    // compute signature
    const binaryHash = crypto.createHash("md5").update(unsignedString).digest();
    const base64Value = new Buffer(binaryHash).toString('base64');
    return base64Value.replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_');
}

上記のコードでは Lambda@Edge 関数で次のことを実行します。

  • 署名文字列を作成します。
  • MD5 シグネチャを計算し、Base64 でエンコードをし、いくつかの特殊文字列を置き換えます。
  • クエリ文字列を利用してオリジンへのリクエストに有効期限と署名を追加します。
  • オリジンへリクエストを転送します。

Step3: Lambda@Edge 関数を CloudFront ディストリビューションに関連づける

Lambda@Edge 関数でイベントをトリガーできるのは以下の4つです。

  • ビューワーリクエスト: 全てのリクエストに対して CloudFront のキャッシュを確認する前に実行
  • ビューワーレスポンス: 全てのリクエストに対してオリジンかキャッシュからレスポンスが応答された後に実行
  • オリジンリクエスト: キャッシュミスの時にオリジンへリクエストを送る前に実行
  • オリジンレスポンス: キャッシュミスの時にオリジンからレスポンスが応答された後に実行

このユースケースではオリジンへのリクエストをトリガーとしてLambda@Edge 関数を実行します。この場合 Amazon CloudFront がキャッシュミスでオリジンからオブジェクトを取得する必要がある時に実行されます。

最初に、Lambda コンソールで関数の新しいバージョンを発行します。

その際に、オリジンリクエストをトリガーとした CloudFront ディストリビューションの設定を有効にします。

コンソールで CloudFront ディストリビューションのステータスが ‘Deployed’ となるまで待ち、CloudFront ドメインを使ってアクセスして、プライベートコンテンツが取得できるか試します。

まとめ

このブログ記事ではカスタムオリジンでプライベートコンテンツを提供するための署名つきリクエストを生成する Lambda@Edge を使って CloudFront を設定しました。このソリューションはLambda@Edge の高いカスタマイズ性を活用できる様々なユースケースのうちの1つの例を示しています。

この例で活用したサービスについて更に学ぶためには、Getting Started with Amazon CloudFront と Getting Started with AWS Lambda@Edge のドキュメントページへアクセスしてください。

翻訳はソリューションアーキテクトの三上が行いました。原文は Serving Private Content Using Amazon CloudFront & AWS Lambda@Edge です。