はじめに
こちらの記事 の 「まとめにかえて」で触れたとおり、 CloudFront へのアクセスに対して IP アドレスで制限をかける方法について触れる。
実現にあたり、本記事では CloudFront と Lambda@Edge を利用する。
注意
本記事は 2020年6月21日 時点の情報です。
ご覧になられた時点で UI が変更されている可能性がありますので、その点ご注意ください。
前提
- こちらの記事 の構築が完了していること
環境
サービス | 概要 |
---|---|
macOS | 10.15.x |
Elemental MediaLive | あらゆるデバイスへのブロードキャストおよびストリーミング向けにライブ動画をエンコードする |
Elemental MediaStore | ライブストリーミングによるメディアワークフロー向けにビデオアセットを保存、配信する |
CloudFront | 高速で安全性が高くプログラム可能なコンテンツ配信ネットワーク (CDN、content delivery network) |
Lambda | サーバーについて検討することなくコードを実行できる |
Lambda@Edge | ユーザーに近いロケーションでコードを実行 |
OBS | AWS Lambda の使用開始ビデオ録画と生放送用の無料でオープンソースのソフトウェア。 |
想定する構成
MediaLive + MediaStore のプロダクトを作りたい。
で、CloudFront でキャッシュし、かつ Lambda@Edge でアクセス元の IP アドレスによるフィルタリングも行いたい。
というのをイメージしたのが以下の図。
フィルタリング
ホワイトリスト方式を採用する。指定した IP アドレスのみ許可し、リストにない IP アドレスからのアクセスはエラーとする。
制限事項
ホワイトリストの持ち方
本記事ではホワイトリストは Lambda 関数で保持する方法を示す。
ホワイトリストを DB や S3 等で保持する方法について、本記事では扱わない。
( 手前味噌ではありますが、こちら の記事で S3 や DynamoDB でホワイトリストを持つ方法について触れております。ご興味有ればご参照ください )
トリガーとするイベント
-
Lambda 関数をトリガーできる CloudFront イベント には下記の4つがあり、本記事では ビューワーリクエスト をトリガーとする。
- ビューワーリクエスト
- オリジンリクエスト
- オリジンレスポンス
- ビューワーレスポンス
ビューワーリクエスト
開発ガイド から抜粋。
CloudFront がビューワーからのリクエストを受け取ると、リクエストされたオブジェクトが CloudFront キャッシュ内にあるかどうかを確認する前に、関数が実行されます。
注意事項
こちらも 開発ガイド から抜粋。
CloudFront イベントによって Lambda 関数の実行がトリガーされると、その関数が終了するまで CloudFront は続行できません。たとえば、CloudFront ビューワーリクエストイベントによって Lambda 関数がトリガーされた場合、Lambda 関数の実行が終了するまでは、CloudFront はビューワーにレスポンスを返したり、オリジンにリクエストを転送したりしません。つまり、Lambda 関数をトリガーするリクエストごとにリクエストのレイテンシーが長くなるため、関数をできるだけ速く実行する必要があります。
Lambda@Edge の制限
公式のこちら から転載。
これによると Lambda@Edge では通常の Lambda に比べて設定項目に対する制限が厳しくなっているとのこと。
イベントタイプによって異なるクォータ | ||
---|---|---|
エンティティ | オリジンのリクエストおよびレスポンスイベントのクォータ | ビューワーのリクエストおよびレスポンスイベントのクォータ |
関数のメモリサイズ | Lambda のクォータと同じ | 128 MB |
関数タイムアウト。関数は AWS リージョンの Amazon S3 バケット、DynamoDB テーブル、Amazon EC2 インスタンスなどのリソースに対してネットワーク呼び出しを実行できます。 | 30 秒 | 5 秒 |
ヘッダーと本文を含む、Lambda 関数によって生成されたレスポンスのサイズ | 1 MB | 40 KB |
Lambda 関数および組み込みライブラリの最大圧縮サイズ | 50 MB | 1 MB |
Lambda@Edge 関数での IP 制限
Lambda 関数を作成する
注意事項
Lambda 関数を作成するリージョン
CloudFront のリージョンは 現状だと グローバルしか選択できない。それに関係してか、Lambda@Edge 用の関数は バージニア北部 のリージョンで作成する必要がある。(そうしないとトリガーに CloudFront を指定できない )
実行時のリージョンと出力されるログ
作成した関数は 世界中のAWSローケーション( リージョン )にレプリケートされる。
で、Lambda@Edge が実行されるとき、実際に実行されるのはクライアントから最寄りのリージョンにレプリケートされた関数 となる。
実行時のログは 実行されたリージョンの CloudWatch Logs に出力される。
手順
-
「一から作成」で「基本的な情報」を埋めていく
-
一旦完了
IAM ロールの設定
CloudFront へのアクセスに対して Lambda@Edge を適用する場合、Lambda@Edge 用の IAM ロール が必要なので、作成したロールに設定を追加する。
アクセス権限の設定
-
IAM > ロール から「Lambda 関数を作成する」で作成したロールを選択
-
JSONを編集
次の内容を設定する。
{ "Version": "2012-10-17", "Statement": [ { "Sid": "VisualEditor0", "Effect": "Allow", "Action": [ "logs:CreateLogStream", "iam:CreateServiceLinkedRole", "lambda:GetFunction", "cloudfront:UpdateDistribution", "cloudfront:CreateDistribution", "logs:PutLogEvents", "lambda:EnableReplication*" ], "Resource": "*" }, { "Sid": "VisualEditor1", "Effect": "Allow", "Action": "logs:CreateLogGroup", "Resource": "*" } ] }
信頼関係の設定
-
再度、IAM > ロール から「Lambda 関数を作成する」で作成したロールを選択
-
JSONの編集画面になるので、
Service
プロパティに以下を追記edgelambda.amazonaws.com
Service
プロパティの部分が配列ではない場合は配列に変更する。変更後の JSON が下記。{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Service": [ "lambda.amazonaws.com", "edgelambda.amazonaws.com" ] }, "Action": "sts:AssumeRole" } ] }
Lambda に戻って
IAM ロールの設定が完了したので、もう一度 Lambda に戻って Lambda 関数の実装を行う。
Lambda 関数の処理を実装する
-
「Lambda 関数を作成する」で作成した関数を選択〜表示し「関数コード」を編集する
IPアドレスのホワイトリスト、並びにフィルタリング処理の例を以下に示す。'use strict' // IPアドレスのホワイトリスト const IP_WHITE_LIST = [ 'xxx.xxx.xxx.xxx', //IPアドレスは各自設定すること! ]; const errorResponse = (httpVersion) => { const body = '<!DOCTYPE html>\n' + '<html>\n' + '<head><title>Lambda@Edge からのエラー</title></head>\n' + '<body>\n' + '許可されていない IP アドレスです\n' + '</body>\n' + '</html>' return { status: '403', statusDescription: 'Forbidden', httpVersion: httpVersion, body: body, headers: { 'cache-control': [{ key: 'Cache-Control', value: 'max-age=100' }], 'content-type': [{ key: 'Content-Type', value: 'text/html; charset=utf-8' }], }, }; }; exports.handler = (event, context, callback) => { const request = event.Records[0].cf.request; const httpVersion = request.httpVersion; const clientIp = request.clientIp; // リクエスト情報からアクセス元のIPアドレスを取得できる const isPermittedIp = IP_WHITE_LIST.includes(clientIp); if (isPermittedIp) { // 許可されているIPアドレスなので何もしない( 通常処理へ流れる ) callback(null, request); } else { // 許可されていない IP アドレスなのでエラーを返す callback(null, errorResponse(httpVersion)); } }
-
「保存」をクリックして編集内容を保存する
Lambda関数を Lambda@Edge として CloudFront に紐付ける
Lambda 関数の実装が終わったら、次は下記の手順で Lambda@Edge として CloudFront と紐付けを行う。
-
編集画面で「CloudFront」を選択
このとき Lmabda関数を バージニア北部 のリージョンで作成していないと CloudFront が選択肢に出てこないので注意。
-
「CloudFront トリガーの設定」を編集
- 「ディストリビューション」に紐付ける CloudFront のディストリビューションを設定
- 「キャッシュ動作」はいじらない
- 「CloudFront イベント」には「ビューアーリクエスト」を選択
- 「ボディを含める」にチェック
- 「関数のこのバージョンが〜」にチェック
動作確認
ホワイトリスに登録されていないIPアドレスからのアクセス
Lambda 関数で設定したエラーメッセージが表示されていることが確認できる。
まとめ
-
IPアドレスのフィルタリングはホワイトリスト方式とする
-
Lambda 関数での視聴者の IP アドレス取得方法
exports.handler = (event, context, callback) => { // 省略 const clientIp = request.clientIp; // リクエスト情報からアクセス元のIPアドレスを取得できる // 省略 }
-
Lambda@Edge 用の IAM ロールが必要
-
Lambda@Edge 用の関数は バージニア北部 のリージョンで作成する必要がある( そうしないとトリガーに CloudFront を指定できない )
-
Lambda@Edge で作成した関数は 世界中のAWSローケーション( リージョン )にレプリケートされる
-
実際に実行されるのはクライアントから最寄りのリージョンにレプリケートされた関数
-
実行時のログは 実行されたリージョンの CloudWatch Logs に出力される。
-
CloudFront のイベントには ビューワーリクエスト を指定する
参考
公式
- 開発ガイド-Lambda@Edge 関数の例
- Lambda 関数の要件と制限
- Lambda@Edge デザインベストプラクティス
- Lambda コンソールで Lambda@Edge 関数を作成する
- Lambda@Edge 用の Lambda 関数の編集
- Amazon CloudFront と AWS Lambda@Edge を用いたプライベートコンテンツの提供
- Lambda@Edge 関数の作成と使用の開始
- Lambda@Edge のクォータ