Help us understand the problem. What is going on with this article?

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

はじめに

こちらの記事 の 「まとめにかえて」で触れたとおり、 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/MediaPackage のプロダクトを作りたい。
で、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 関数での IP 制限

Lambda 関数を作成する

注意事項

CloudFront のリージョンは現状だとグローバルしか選択できない。それに関係してか、Lambda@Edge 用の関数は バージニア北部 のリージョンで作成する必要がある。(そうしないとトリガーに CloudFront を指定できない )

手順

  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 を指定できない )

  • CloudFront のイベントには ビューワーリクエスト を指定する

参考

公式

その他

ksh-fthr
フロントエンドは Angular, バックエンドは Express / axios / Sequelize 等を使ってました。今は AWS で奮闘中。あと Docker 使って環境構築なんかにも手をだしてます。 モノをつくって共有、というよりはその過程で得たものを共有できればいいなぁと思ってます。 どうぞよろしくお願いします。
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした