Amazon Web Services ブログ
Lambda@Edge を利用した外部サーバによる認可の実装
はじめに
このブログでは、 Lambda@Edge 利用し、リクエストに含まれるデータを外部の認証サーバーへ転送することによって、Amazon CloudFront でリクエストを認可する方法を説明します。ここでは、このようなワークフローでのリクエストの順序、 Node.js のサンプルコードによる実装手順、ヘッダーベースの認可の動作テストのために利用するシンプルな外部認証サーバー用の CloudFormation テンプレートの概要を説明します。また、このブログでは追加で最適化や考慮事項についても案内します。
ゲーム、ソフトウェア、ドキュメント、その他の大容量ファイルのダウンロードなど、コンテンツを CloudFront で安全に配信するには、特にアクセスが特定の視聴者に制限されている場合や、コンテンツが課金の対象になっている場合は、リクエストの承認が必要になることがよくあります。リクエストを承認する一般的な方法には、署名付き URL、署名付き Cookie、JSON Web トークン (JWT)、OpenID Connect (OIDC) がありますが、場合によっては、ヘッダーベースの認証など、他の既存の認証の方法でユーザーを認可する必要があります。
このソリューションが役立つような一般的な事例はいくつもあります。一つは、オンプレミス環境から AWS クラウドへウェブサーバーやストレージをマイグレーションする必要があるが、認可の仕組みを他のシステムやワークフローと共有しているため、既存の認可の仕組みは維持したいといったものです。別の例では、ダウンロードのワークフローとして追加またはバックアップの CDN として CloudFront を入れる際に、既存の CDN の実装で既にリモート認証のサーバーを利用して、リクエストを認可しているというものです。また、組み込みソフトウェアを利用したデバイスのソリューションでは、ソフトウェアアップデートによって、認可の仕組みを変更することは難しい、または不可能な場合があります。
外部サーバーによる認可フロー例の概要
次のダイアグラムは、Lambda@Edge を使用して既存の認可メカニズムを維持したまま、ダウンロードとストレージのワークフローを CloudFront と S3 に移行する例を示しています。
マイグレーション前 :
図1 : マイグレーション前
オンプレミス上の既存の認可を維持したまま、 CloudFront と S3 にマイグレーションした後 :
図2 : マイグレーション後
- エンドユーザーが Amazon CloudFront へ認証ヘッダー付きの HTTP GET リクエストを送信する
- CloudFront はビューワーリクエストで Lambda@Edge function をトリガーする
- Lambda@Edge function は認証ヘッダーを解析して、認証ヘッダー付きの HTTP リクエストを外部の認証サーバーへ送信する
- 認証情報が正当であれば外部の認証サーバーは、200 のステータスコードを返信するが、もし認証情報が正当でなければ403 のステータスコードを返信する
- Lambda@Edge function が外部の認証サーバーから 200 のステータスコードを受け取って、ファイルがキャッシュに存在する場合、CloudFront はエンドユーザーに 200 OK のステータスと一緒にコンテンツを返す。もし、ファイルがキャッシュに存在しない場合、次のように動作する :
- a. CloudFront はコンテンツを取得するために S3 オリジンにリクエストを送信する
- b. S3 オリジンは CloudFront にコンテンツを返す
- c. CloudFront はエンドユーザーに 200 OK のステータスと一緒にコンテンツを返す
- Lambda@Edge function が外部の認証サーバーから 403 のステータスコードを受け取った場合、Lambda@Edge function はエンドユーザーに 403 のステータスのレスポンスを返す
Lambda@Edge のサンプルコード
次のコードは、Lambda@Edge で外部サーバーによる認可を実装する方法を示しています。これは Node.js HTTP モジュールを利用してHTTP コールを行います。
CloudFront ディストリビューションの作成
最初に、S3 をオリジンとする CloudFront ディストリビューションを作成します。オリジンリクエストポリシーで認可用に必要なヘッダー名を追加する必要があることに注意してください。ヘッダーを追加することで、Lambda@Edge がエンドユーザーからのリクエストから認証用のヘッダーを取得することができます。認証用のヘッダーは、キャッシュキーに含まれるべきではないので、キャッシュポリシーでヘッダーを追加する必要はありません。
もし、認可プロセスが特定のコンテンツだけで必要ならば、特定のコンテンツ用のパスだけをもつ CloudFront ビヘイビアに Lambda@Edge 関数を関連付けできます。ディストリビューションの作成について詳しくは、こちらをご覧ください。
図3 : オリジンリクエストの設定
Lambda@Edge のコードを作成して構成する
Lambda コンソール上で上記のコードをデプロイして、その関数を作成済みの CloudFront ビヘイビアに関連付けます。( Lambda@Edge 関数を CloudFront に関連づけるために、バージニアリージョンを選択する必要があります。) この関数はビューワーリクエストで動作するので、Cloudfront イベントのトリガーに「ビューワーリクエスト」を選択します。Lambda@Edge 関数のデプロイについて詳しくは、こちらをご覧ください。
図4 : Lambda@Edge へのデプロイ
シンプルな外部認証サーバーを用いたテスト
上記のワークフローでは、検証が成功すると外部認証サーバーは「200 OK」を返し、失敗の場合は「403」を返します。以下は、クイックなテスト目的で利用できる外部認証サーバー用のシンプルな PHP コードです。
<?php
$headers = getallheaders();
$authheader = $headers['authentication'];
if ($authheader){
if ($authheader == "xxxxxxx") {
header("HTTP/1.1 200 OK");
header("Status: 200");
header("Content-Type: text/plain");
}else{
header("HTTP/1.1 403 ");
header("Status: 403");
header("Content-Type: text/plain");
}
}else{
header("HTTP/1.1 403 ");
header("Status: 403");
header("Content-Type: text/plain");
}
?>
例えば、curl コマンドを利用して以下のようなテストを実行できます。
200 OK :
$ curl -i https://xxxxxxxxxxxxx.cloudfront.net/ -H "authentication: xxxxxxx"
HTTP/2 200
content-type: text/html; charset=UTF-8
content-length: 14
date: Wed, 26 May 2021 13:53:39 GMT
server: Apache
last-modified: Wed, 14 Apr 2021 07:29:51 GMT
etag: "e-5bfe9b659bc1c"
accept-ranges: bytes
403 Forbidden :
$ curl -i https://xxxxxxxxxxxxx.cloudfront.net/ -H "authentication: <Invalid Value>"
HTTP/2 403
content-length: 179
server: CloudFront
date: Wed, 26 May 2021 13:54:40 GMT
x-cache: LambdaGeneratedResponse from cloudfront
CloudFormation を利用してテスト用の外部認証サーバーをデプロイする
このセクションでは、サンプルとなる CloudFormation テンプレートを用いて、PHP コードを利用したテスト用の外部認証サーバーをデプロイする方法を説明します。各手順を完了するためには、AWS account や Amazon Virtual Private Cloud ( Amazon VPC ) 、Amazon EC2 といった AWS リソースにアクセスできる IAM user を必要とします。この CloudFormation テンプレートは、SSL 証明書のデプロイを含んでいません。上記の Lambda@Edge のサンプルコードは、HTTPS 443 ポート経由で外部認証サーバーに接続するので、このテンプレートでテストする時は、サンプルコード上で外部認証サーバーへの接続を http:80 に変更するか、SSL 証明書を外部認証サーバーに追加してください。
- CloudFormation をデプロイするために をクリックしてください。
- デフォルトでは、CloudFormation コンソール内の「スタックのクイック作成」ページへ誘導され、CloudFormation テンプレートが自動で入力されます。
図5 : スタックの作成
- スタック名や必須のパラメーターはデフォルト値が入力されています。私たちのデモ環境では、「ExternalAuthTestServer」というスタック名になっています。もし、テストで異なる値を利用したい場合、認証ヘッダーの「AuthHeaderName」や「AuthHeaderValue」の値やパラメーターを変更できます。「スタックの作成」をクリックして続行します。
テンプレートをデプロイするとスタックの出力タブで、ヘッダー名( ExternalAuthHeaderName )、ヘッダーの値( ExternalAuthHeaderValue ) や認可用の URL を確認できます。
図6 : スタックの出力タブ
補足 : 最適化と考慮事項
- HTTP メソッド : 外部認証サーバーへのアクセスでは GET メソッドの代わりに、POST メソッドを利用できます。
- パーシステントコネクション : トラフィックに応じて、パーシステントコネクションの max socket を構成できます。
- エラーハンドリング( フェールオープンとフェールクローズの検討 ) : Lambda@Edge が外部サーバーからレスポンスを得られない場合に、フェールオープンまたはフェールクローズの実施について検討する必要があります。
- スケーリングの制限 : Lambda@Edge は秒間あたりのリクエスト数や同時実行数にはクォーターがあります。ビューワーリクエストで大量のリクエストが見込まれる場合、上限の引き上げが可能です。Lanbda@Edge のクォーター
- 追加ヘッダーの検証 : 強力なセキュリティの追加を検討する場合、外部サーバーへヘッダーを送付する前に、共有シークレットによるハッシュキーを利用してリクエストヘッダーの検証を追加できます。Lambda@Edge で取得される共有シークレットを保存するために AWS Secrets Manager を利用できます。詳細はこのブログを参照してください。
- コスト : このソリューションのコストは、予想されるビューワーリクエストの数を見積り、またテストにて関数の実行時間を確認してください。関数のプロセスに外部ネットワークがあるので、関数の実行時間は外部サーバーの処理やネットワークのレイテンシーに依存します。加えて、CloudWatch Logs のコストがあり、アクセス数からログのボリュームや関数からログの出力量を見積もります。Lambda@Edge のコストを見積もるベストプラクティスを確認してください。
クリーンアップ
CloudFormation テンプレートを使用してサンプルの外部認証サーバーをデプロイした場合、CloudFormation テンプレートでローンチしたリソースに関するコスト増を避けるため、テスト後に CloudFormation スタックを削除してください。
おわりに
このブログでは、ビューワーリクエストをトリガーした Lambda@Edge を通じて外部認証サーバーを利用した認可の実装方法について説明しました。これは、コンテンツデリバリーである CloudFront プラットフォームのパフォーマンス、可用性やセキュリティの恩恵を受けながら、既存の認可方法を活用したり高度にカスタマイズされたエンドユーザーの認可ソリューションを開発、構築する柔軟性があります。
他の Lambda@Edge CDN ワークフローのカスマイズについてさらに学ぶために、Lambda@Edge でのカスタマイズに関するドキュメントを参照してください。
本ブログは、External Server Authorization with Lambda@Edge を翻訳したものです。翻訳は、PSSA 小林が担当しました。