Lambda@EdgeによるIP制限をPythonで書いてみた

Lambda@EdgeでPython使ってIP制限してみた記事です。コンソール上の作業手順も以前より簡単になってますよ!
2020.10.21

Lambda@Edgeで本番公開前のCloudFrontに対してIP制限を行う機会がありました。せっかくなので紹介させていただこうと思います。

Lambda@Edgeは今の所、Node.jsとPythonが対応しており、ググればそのまま流用できそうなスクリプトが見つかりましたが、いずれもNode.jsだったのでPythonで作成してみました。

前提

トリガーとなるCloudFrontディストリビューションは既に作成済みとします。今回はS3をオリジンとしています。
それでは手順を見ていきましょう。

関数の作成

今回はコンソールから作成します。適当な名前をつけて、ランタイムは「Python 3.8」を選びます。

01-base-info

ロールの指定は、「AWSポリシーテンプレートから新しいロールを作成」を選択します。ロール名は適当に入力してください。(今回はlambda-edge-restriction-roleという名前にしています)

02-set-role

「ポリシーテンプレート」のプルダウンから「基本的なLambda@Edgeのアクセス権限」を選択してください。関数を作成する時にIAMの信頼関係もLambda@Edgeに合った形で作成してくれます。

03-select-le-access

設定できたら「関数の作成」をクリックします。

04-make-function

次の画面で「関数コード」セクションにコードを書きます。入力できたら「Deploy」をクリックします。
(今回は単純で短いので、コンソールから書いてます)

06-write-code

コードの全体は下記です。
IP_WHITE_LISTにアクセスを許可するIPをリストで記載しています。コードに記載しているIPはサンプルです。

import json

CONTENT = """
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>403 Forbidden</title>
  </head>
  <body style="background-color:lightpink;">
    <h1>This is an Error Page</h1>
    <p>Your IP is not allowed to access this site!</p>
  </body>
</html>
"""

def lambda_handler(event, context):
    IP_WHITE_LIST = ['203.0.113.78', '198.51.100.29', '192.0.2.221']
    request = event['Records'][0]['cf']['request']
    client_ip = event['Records'][0]['cf']['request']['clientIp']
    print("Client IP: ", client_ip)
    
    if client_ip in IP_WHITE_LIST:
        return request

    # return 403 response when clientIP doesn't exist in the whitelist
    response = {
        'status': '403',
        'statusDescription': 'Forbidden',
        'headers': {
            'cache-control': [
                {
                    'key': 'Cache-Control',
                    'value': 'max-age=100'
                }
            ],
            "content-type": [
                {
                    'key': 'Content-Type',
                    'value': 'text/html'
                }
            ],
            'content-encoding': [
                {
                    'key': 'Content-Encoding',
                    'value': 'UTF-8'
                }
            ]
        },
        'body': CONTENT
    }
    return response

Lambda関数のテスト

さて、ここでLambda関数が正しく動くか確認してみます。コンソールの「テスト」ボタンをクリックしてください。

20-lambda-test

テストイベントを設定します。イベント内容には下記のドキュメントにある「ビューワーリクエストの例」をそのまま利用しました。

21-sample-request

下記はテストを実行した結果です。上記のイベント例に書かれている「203.0.113.178」は許可IPではないので、403エラーを返していることが確認できます。

22-test-result

トリガーの設定とデプロイ

スクリプト自体の動作が確認できたので、次にトリガーを設定していきます。

07-add-trigger

プルダウンからCloudFrontを選択します。

08-select-cloudfront

「トリガーの設定」で「Lambda@Edgeへのデプロイ」をクリックします。

09-deploy-le

次の画面では、対象のディストリビューションやビヘイビア、イベントの指定などを行います。今回はIP制限なので、「ビューワーリクエスト」を選択します。

設定が完了したら「デプロイ」をクリックして実行します。

(対象とするディストリビューション、ビヘイビアが複数ある場合は、この作業を繰り返します)

10-deploy-le

デプロイを実行すると、自動的に「バージョン1」が作成されて、指定したCloudFrontのビヘイビアに関連付けられます。

11-integrated-cloudfront

CloudFront側でステータスが変わっていることが確認できます。

12-cloudfront-status-in-progress

ステータスが「Deployed」になれば完了です。

13-cloudfront-deployed

動作確認

先程のコードではサンプルIPしか許可していなかったので、まだどこからもアクセスできない状態になっています。ブラウザでアクセスして確認してみましょう。

下記の通り、Lambda@Edgeで「403エラーコード」が返されてコンテンツにアクセスできていないことが分かります。

14-before-whitelist-modify-403-error

では次に、許可したいIPを追加してみます。(画像ではxx.xx.xx.xxですが、実際には許可したいIPをシングルクォートで括って記載してください)

IP_WHITE_LIST = ['203.0.113.78', '198.51.100.29', '192.0.2.221','追加するIP']

15-add-allow-ip

IPを追記できたらコンソールの上部にあるメニューより「Lambda@Edgeへのデプロイ」をクリックします。

16-deploy-la

既存の関数を更新してデプロイする時は、オプションで「この関数で既存のCloudFrontトリガーを使用」を選択すると、以前指定した設定をそのまま引き継いで使用できるので、デプロイ作業が楽になります。
前回同様に対象が複数ある場合は、プルダウンから対象のトリガーを選択し直して同じ作業を繰り返します。

17-select-cloudfront

CloudFront側の更新が終わって、改めて確認するとS3に置いたコンテンツを見ることができました。

18-after-whitelist-modify-200-ok

最後に

Pythonのスクリプトだけ紹介して終わろうかと思いましたが、それだけでは寂しいので、コンソールを利用した更新方法も踏まえてご紹介してみました。

以上です。