Lambda@EdgeでCloudFrontへのアクセスをいい感じに振り分ける

f:id:vasilyjp:20180711150943j:plain(Icon Credit *1

こんにちは。PB開発部インフラチームの@inductorです。最近はすっかりインフラ勉強会というオンライン勉強会の運営が趣味になっています。

今回はLambda@EdgeというAWSのサービスを使って、CloudFrontへのアクセスを「細かいルール」を設定して振り分けてみたいと思います。

Lambda@Edgeについてもう詳しく知っているよ! という方は、次のセクションはスキップしてもらって構いません。もしよく知らないという方は、一緒に勉強してみましょう!

AWS Lambda@Edgeとは

Lambda@Edgeは以下の2つのサービスから成り立ちます。

  • AWS Lambda
  • Amazon CloudFront

CloudFrontのエッジロケーションにおいて、Lambdaで定義した任意のコードを実行できるというのがLambda@Edgeで、具体的には以下のような恩恵を得ることができます。

  • エッジロケーション(地域)に応じて表示させるコンテンツを変えたい
  • User-Agentなどに応じて取得するコンテンツを変えたい
  • アクセス元のIPアドレスによって表示させるコンテンツを変えたい
    • 開発環境に、アクセス元IPが自社でない場合のみBasic認証を導入したい
  • 実際に叩かれるURLと、S3などから実際に取得する資源のURIを変更したい

この他にも、CloudFrontから取得できるデータに応じて様々な対応を柔軟に行えるのが特徴です。

AWS Lambdaのおさらい

AWS Lambdaは、任意のプログラムをサーバを用意することなく実行できるFaaS(Function as a Service)のサービスです。主に、バッチ処理やログの処理、サーバレスアーキテクチャにおけるバックエンドの処理などで利用されます。 現在Lambdaで提供されている実行環境には、Node.js、Java、C#、Go、Pythonがあり、それぞれ好きな言語を選んで実行させることができます。

詳しくは、AWSの公式ページや、クラスメソッドさんなどの記事をご覧いただければと思います。

Amazon CloudFrontのおさらい

Amazon CloudFrontは、AWSのCDNサービスです。世界中に存在する「エッジロケーション」と呼ばれるAmazonの各拠点に対して、S3に保存した静的コンテンツ(HTML、CSS、JSなど)をキャッシュさせておくことができます。

キャッシュのルールを細かく設定できる他、SSL証明書をACMなどから投入しつつHTTP/2の有効化やTLSのバージョン指定などのエンドポイントとしても機能的に利用できるなど、開発者にとって面倒な設定も簡単にできるという利点もあります。

改めてLambda@Edgeとは

CloudFrontのエッジロケーションにおいて、Lambdaを使って細かいルールを自由に設定できるサービスです。

CloudFrontでは大きく分けて以下の4つのデータの流れがあり、それぞれ、1つのリクエスト(レスポンス)に対して一意のLambdaのコードを割り当てることができます(割り当てなくても動作はします)。

f:id:vasilyjp:20180709150636p:plain ※引用元スライド: https://www.slideshare.net/AmazonWebServicesJapan/aws-blackbelt-online-seminar-2017-amazon-cloudfront-aws-lambdaedge

Lambda@Edge導入前の注意事項と事前準備

注意事項

  • Lambda@Edgeにて2018年7月現在で対応しているのはNode.js(6.10と8.10)のみです
  • Lambda関数はバージニアリージョンで作成する必要があります
  • Lambda関数が実行されたときに吐き出されるログが保管されるリージョンは、ユーザーから最も近いエッジロケーションになります
    • 例えばバージニアリージョンでコードを上げていても、ベトナムのユーザーがアクセスした場合、LambdaのCloudWatchログはムンバイなどのリージョンに表示されます
  • CloudFrontの仕様上、特定のHTTPヘッダについて書き換えられないなどの制限があります

必要な事前準備

Lambda@Edgeを適用するためには以下の準備が必要となります。

これらについては今回は省略します。それぞれ、該当リンクなどを参考に準備をしてみてください。

まず、IPアドレスに応じて必要な振り分け先を変えるという対応をしてみましょう。

サンプルとして、以下のようなコードを作成し、Lambda@Edgeの設定をしていきます(IPアドレスはダミーのため、各自試したいものに変更しましょう)。

'use strict'

const permitIp = [
  '172.0.0.1', //IPアドレスは各自設定すること!
  '172.0.0.2',
];

exports.handler = (event, context, callback) => {
  const request = event.Records[0].cf.request;
  const httpVersion = request.httpVersion;
  const clientIp = request.clientIp;
  const isPermittedIp = permitIp.includes(clientIp);

  if (isPermittedIp) {
    // 許可されているIPであればそのまま次の処理へ
    callback(null, request);
  } else {
    // 許可されていないIPに対しては許可されていない旨のメッセージを返す
    const body =
      '<!DOCTYPE html>\n' +
      '<html>\n' +
      '<head><title>Hello From Lambda@Edge</title></head>\n' +
      '<body>\n' +
      'Your IP address is not permitted to access!\n' +
      '</body>\n' +
      '</html>'

    /* Generate HTTP response */
    const customResponse = {
      status: '200',
      statusDescription: 'HTTP OK',
      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'
        }],
      },
    };
    callback(null, customResponse);
  }
}

※特定の条件下で何もせず次の処理に渡したい場合は、上記コードのようにcallbackでrequestをそのまま返してあげると良いです。

Lambdaの設定

Lambdaに対して上記コードを仕込んでいきます。 まずLambdaのコンソールにアクセスし、関数を新しく作成します。 ※このとき、かならずバージニアリージョンを選びましょう!

続いて、以下スクリーンショットのように関数名に適当な名前を付け、ロールに作成済みのLambda@Edge用ロールを紐づけます。

f:id:vasilyjp:20180709150652p:plain

関数が作成されますので、「関数コード」の欄に該当コードを貼り付け、画面上部、右上の「保存」ボタンをクリックします。

f:id:vasilyjp:20180709150632p:plain

「アクション」から新しいバージョンを発行するを選択し、「発行」をクリックします。

バージョンが1になったことを確認したら、「Designer」よりトリガーの追加→CloudFrontを選択します。

f:id:vasilyjp:20180709150657p:plain

「トリガーの設定」から、ディストリビューションにCloudFrontのDistribution IDを入力し、イベントトリガーにはビューアーリクエストを追加します。

f:id:vasilyjp:20180709150659p:plain

上記が確認できたら「追加」をクリックし、再度、画面右上の保存ボタンをクリックします。

f:id:vasilyjp:20180709150650p:plain

CloudFrontのコンソール画面からBehaviorsタブを選択し、「Path Pattern」にて「Default(*)」または自分の選択したいパスを選択し、「Edit」をクリックします。

f:id:vasilyjp:20180709150642p:plain

最下部のLambda Function Associationsにバージョン番号含めLambda関数が紐付いていることを確認します。

最後に、該当のCloudFrontのURLをブラウザで確認します!

該当しないIPアドレスからのアクセスに対して、以下のようなレスポンスが返ってくれば問題ないです。

f:id:vasilyjp:20180709150654p:plain

以上、長くなってしまいましたが、Lambda@Edgeを使ったIPアドレスフィルタの実装手順でした。

さいごに

Lambda@Edgeでは、CloudFrontだけでは今までできなかった細かいルールの振り分けが実現でき、より柔軟な使い方が可能になっています!本ブログでも、役に立った便利な使い方を定期的に更新していこうと思います。

みなさまも、この機会に是非導入を検討してみてはいかがでしょうか!

スタートトゥディテクノロジーズでは、一緒にサービスを作り上げてくれるエンジニアを大募集中です。
ご興味のある方は、以下のリンクからぜひご応募ください!

https://www.starttoday-tech.com/recruit/