Signed Event Webhook RequestsをPythonで試す

こんにちは!SendGridサポートチームの吉田です。前回のブログ記事でも紹介しましたが、送信リクエスト受理をはじめとするイベント情報を任意のURLにPOSTできる「Event Webhook」に関して、近ごろセキュリティを強化する機能が追加されました。その一つが「Signed Event Webhook Requests」です。これを用いると、POSTされるデータが確かにSendGridから送られたということ(なりすまされていないこと)と、内容が途中で書き換えられていないこと(改竄されていないこと)の証明になります。本記事では、私がPythonを用いて簡単に実装してみた例をご紹介します。

仕組み

Signed Event Webhook Requestsで用いているのは公開鍵暗号方式(ECDSA)による電子署名の技術です。簡単な流れを下図に示しました。

仕組み

SendGridは、イベント情報とタイムスタンプを合わせたものをハッシュ化し、秘密鍵でデジタル署名(2つの値r, sのペア)を生成します。このデジタル署名はリクエストヘッダに格納されてPOSTされます。ユーザは、POSTされたデータを用いて決められた演算を行い、それをデジタル署名と比較して検証します。公式ライブラリが用意されているので、演算や検証の中身がわからなくても実装することが可能です。

Pythonでのお試し実装例

ここでは簡単な例として、PythonのWebフレームワークであるFlaskを用いて、Signed Event Webhook Requestsで検証を行い、受け取ったJSONを表示させるスクリプトを書いてみました。検証を行わない単純なEvent Webhookの実装はこちらの記事で紹介しましたが、今回はこれに検証機能を付け加える形です。

1. Signed Event Webhook Requestsを有効化し、公開鍵を取得する

スクリプトを書く前に、設定を有効化して公開鍵を入手しましょう。ダッシュボードからなら、Mail Settings > Event Settingsの中に「Signed Event Webhook Requests」があります。これを選択し、「Generate Verification Key」ボタンをクリックすると公開鍵を取得することができます。ここではPUBLIC_KEYという名前で環境変数に設定することにします。

$ export PUBLIC_KEY=<<your_verification_key>>

公開鍵の入手

なお、設定の有効・無効化および公開鍵の取得はWeb API経由でも可能です。詳細はAPI ReferenceのEVENT TRACKING > WEBHOOKSを参照してください。

2. スクリプトを書く

Pythonでの実装例は以下のようになります。

import os
from flask import Flask, request
from json import dumps
from sendgrid.helpers.eventwebhook import EventWebhook

public_key = os.environ['PUBLIC_KEY']

app = Flask(__name__)


@app.route('/', methods=['POST'])
def Display_json():
    signature = request.headers.get('X-Twilio-Email-Event-Webhook-Signature')
    timestamp = request.headers.get('X-Twilio-Email-Event-Webhook-Timestamp')
    payload = request.get_data(as_text=True)
    ew = EventWebhook(public_key=public_key)
    verified = ew.verify_signature(payload=payload, 
                                   signature=signature,
                                   timestamp=timestamp)
    if verified:
        json_list = request.get_json()
        for data in json_list:
            dumped = dumps(data)
            print(dumped)
    else:
        print('Verification Failed.')
    return ""


if __name__ == '__main__':
    app.run()

13行目〜15行目
デジタル署名とタイムスタンプはHTTPヘッダのX-Twilio-Email-Event-Webhook-SignatureとX-Twilio-Email-Event-Webhook-Timestampにそれぞれ格納されます。ここではそれらを取得しています。また、HTTPリクエストボディ全体がダイジェストの生成に必要なので、”payload”として取ってきました。

16行目〜19行目
SendGridの各言語の公式ライブラリに、最小限の手間で検証を行えるAPIが用意されています。各ライブラリへのリンクはこちらをご覧ください。今回はこのPython公式ライブラリを利用しています。環境変数として定義した公開鍵(6行目: “public_key”)と、上で説明したデジタル署名(”signature”)、タイムスタンプ(”timestamp”)、リクエストボディ(”payload”)を与えてやることで、2行で検証を行うことができました。”verified”に検証結果がbooleanで返ってきます。

20行目〜
検証が通った場合に、イベント情報を表示させています。この部分は以前の記事とほぼ同じです。

3. ローカルホストでテストする

ローカルホストでテストする

ngrokなどのサービスを用いてEvent WebhookのPOSTをローカルにポートフォワードし、スクリプトのテストを行ってみましょう。ここでは詳細は省きますが、ngrokの使い方についてはこちらの記事、Event Webhookのテスト方法については前回のEvent Webhookの記事をご覧ください。

おわりに

結果的に、思ったより少ない改良で電子署名の機能を追加することができました(実際に試した際はリクエストボディの取得の仕方に迷い、思ったより時間がかかってしまいましたが……)。これによりセキュリティを向上させることができるので、皆さんもお好みの言語で試してはいかがでしょうか。以下のブログ記事と公式ドキュメント(英語)も参考にしてみてください。

メールを成功の原動力に

開発者にもマーケターにも信頼されているメールサービスを利用して、
時間の節約、スケーラビリティ、メール配信に関する専門知識を手に入れましょう。