こんにちは。 株式会社エビリーの millvi 開発チームでエンジニアをしております。 井上と申します。
今回は MediaStore に対して CloudFront でキャッシュする構成において、 Lambda@Edge でアクセス元の IP アドレスによるフィルタリングを実現する手順を記事に残したいと思います。
よろしければおつきあいくださいませ。
注意
本記事は 2020年8月14日 時点の情報です。 ご覧になられた時点で 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 等で保持する方法について、本記事では扱わない。
トリガーとするイベント
- 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 に出力される。
手順
リージョンに バージニア北部 を選択する
Lambda > 関数 より「関数の作成」をクリック
「一から作成」で「基本的な情報」を埋めていく
- 関数名: 実行する処理に応じた関数名を入力
- ランタイム: デフォルトのまま(今回は
Node.js 12.x
) - アクセス権限
- 「実行ロールの選択または作成」で「基本的な Lambda アクセス権限で新しいロールを作成」を選択
- 「関数の作成」をクリック
Lambda 関数の設定画面が表示される(これで Lambda 関数そのものは作成できた)
一旦完了
IAM ロールの設定
CloudFront へのアクセスに対して Lambda@Edge を適用する場合、Lambda@Edge 用の IAM ロール が必要なので、作成したロールに設定を追加する。
アクセス権限の設定
IAM > ロール から「Lambda 関数を作成する」で作成したロールを選択
表示された概要の「アクセス権限」タブからポリシーを選択し「ポリシーの編集」をクリック
編集画面が表示されるので「JSON」タブをクリック
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" } ] }
▼JSON編集後の画面
- 設定した項目が追加されていることを確認し、「信頼ポリシーの更新」をクリック
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 と紐付けを行う。
「アクション」から「新しいバージョンを発行」を選択
モーダル上で「発行」をクリック(バージョンの説明はなくてもOK)
「トリガーを追加」をクリック
編集画面で「CloudFront」を選択
このとき Lmabda関数を バージニア北部 のリージョンで作成していないと CloudFront が選択肢に出てこないので注意。
「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 のクォータ
その他
注意書き
本記事は Qiita でも公開 しております。