LoginSignup
13
13

More than 1 year has passed since last update.

[AWS] CloudFront + Lambda@Edge で IP 制限をかける

Last updated at Posted at 2020-06-21

はじめに

こちらの記事 の 「まとめにかえて」で触れたとおり、 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 アドレスによるフィルタリングも行いたい。

というのをイメージしたのが以下の図。

Lambda-01.png

フィルタリング

ホワイトリスト方式を採用する。指定した IP アドレスのみ許可し、リストにない IP アドレスからのアクセスはエラーとする。

制限事項

ホワイトリストの持ち方

本記事ではホワイトリストは Lambda 関数で保持する方法を示す。
ホワイトリストを DB や S3 等で保持する方法について、本記事では扱わない。
( 手前味噌ではありますが、こちら の記事で S3DynamoDB でホワイトリストを持つ方法について触れております。ご興味有ればご参照ください )

トリガーとするイベント

ビューワーリクエスト

開発ガイド から抜粋。

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@Edge 関数の作成と使用の開始

手順

  1. リージョンに バージニア北部 を選択する
    スクリーンショット 2020-06-21 9.01.40.png

  2. Lambda > 関数 より「関数の作成」をクリック
    スクリーンショット 2020-05-25 11.29.25.png

  3. 「一から作成」で「基本的な情報」を埋めていく

    1. 関数名: 実行する処理に応じた関数名を入力
    2. ランタイム: デフォルトのまま(今回は Node.js 12.x)
    3. アクセス権限
      • 「実行ロールの選択または作成」で「基本的な Lambda アクセス権限で新しいロールを作成」を選択
    4. 「関数の作成」をクリック スクリーンショット 2020-05-25 12.07.36.png スクリーンショット 2020-05-25 12.07.47.png
  4. Lambda 関数の設定画面が表示される(これで Lambda 関数そのものは作成できた)
    スクリーンショット 2020-05-25 15.42.09.png

  5. 一旦完了

IAM ロールの設定

CloudFront へのアクセスに対して Lambda@Edge を適用する場合、Lambda@Edge 用の IAM ロール が必要なので、作成したロールに設定を追加する。

アクセス権限の設定

  1. IAM > ロール から「Lambda 関数を作成する」で作成したロールを選択
    スクリーンショット 2020-05-25 12.27.16.png

  2. 表示された概要の「アクセス権限」タブからポリシーを選択し「ポリシーの編集」をクリック
    スクリーンショット 2020-05-25 12.30.28.png
    スクリーンショット 2020-05-25 12.32.44.png

  3. 編集画面が表示されるので「JSON」タブをクリック
    スクリーンショット 2020-05-25 12.33.19.png

  4. 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": "*"
           }
       ]
   }
  1. 「ポリシーの確認」をクリック
    スクリーンショット 2020-05-25 15.54.09.png

  2. 確認画面で設定した項目が追加されていることを確認し、「変更の保存」をクリック
    スクリーンショット 2020-05-25 12.39.22.png

信頼関係の設定

  1. 再度、IAM > ロール から「Lambda 関数を作成する」で作成したロールを選択

  2. 「信頼関係」のタブから「信頼関係の編集」をクリック
    スクリーンショット 2020-05-25 14.21.34.png

  3. 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編集後の画面
スクリーンショット 2020-05-25 14.30.21.png

  1. 設定した項目が追加されていることを確認し、「信頼ポリシーの更新」をクリック スクリーンショット 2020-05-25 14.31.07.png

Lambda に戻って

IAM ロールの設定が完了したので、もう一度 Lambda に戻って Lambda 関数の実装を行う。

Lambda 関数の処理を実装する

  1. 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));
     }
   }
  1. 「保存」をクリックして編集内容を保存する

Lambda関数を Lambda@Edge として CloudFront に紐付ける

Lambda 関数の実装が終わったら、次は下記の手順で Lambda@Edge として CloudFront と紐付けを行う。

  1. 「アクション」から「新しいバージョンを発行」を選択
    スクリーンショット 2020-05-25 14.03.52.png

  2. モーダル上で「発行」をクリック(バージョンの説明はなくてもOK)
    スクリーンショット 2020-05-25 14.04.03.png

  3. 「トリガーを追加」をクリック
    スクリーンショット 2020-05-25 14.04.25.png

  4. 編集画面で「CloudFront」を選択

このとき Lmabda関数を バージニア北部 のリージョンで作成していないと CloudFront が選択肢に出てこないので注意。

スクリーンショット 2020-05-25 14.04.40.png

  1. 「CloudFront トリガーの設定」を編集

    1. 「ディストリビューション」に紐付ける CloudFront のディストリビューションを設定
    2. 「キャッシュ動作」はいじらない
    3. 「CloudFront イベント」には「ビューアーリクエスト」を選択
    4. 「ボディを含める」にチェック
    5. 「関数のこのバージョンが〜」にチェック

    スクリーンショット 2020-05-25 14.05.18.png

  2. 「追加」をクリック
    スクリーンショット 2020-05-25 14.05.28.png

  3. トリガーに CloudFront が追加されていることを確認
    スクリーンショット 2020-05-25 15.04.27.png

動作確認

ホワイトリスに登録されていないIPアドレスからのアクセス

Lambda 関数で設定したエラーメッセージが表示されていることが確認できる。
スクリーンショット 2020-06-21 10.11.25.png

まとめ

  • 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 のイベントには ビューワーリクエスト を指定する

参考

公式

その他

13
13
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
13