技術的な話

Microsoft Teamsの送信WebhookをAzure Functions(Python)で受けてみる

Pythonで受けるコードがあんまり落ちていなかったのでここに記載しておきます。
Azure FunctionsではC#が優遇され、次点でNode.jsなんでしょうか。

Slackは純粋なエンジニア会社やスタートアップ企業で使われ、それ以外のエンジニアもいるけどといった会社はTeamsを入れているイメージ(偏見)。

概要

  • Microsoft Teamsから送信Webhookにメンションしてメッセージを送る
  • Azure Functions(Python)でメッセージを受信
  • 受信したメッセージによって何かしらの処理をする

環境・条件

  • Python 3.9.1

やってみる

Microsoft Teamsで送信Webhookを作成する

公式を参照しましょう。
チャンネルの管理者権限ではなくても登録は出来た気がします。(会社によって違う…かも?)
送信Webhookに最初登録しておくURLは適当でも良いです。(後から変更可能)
ただし、https://じゃないと登録できないです。
登録されたらその際に表示されるsecretはメモ帳等に保管しておいてください。

https://docs.microsoft.com/ja-jp/microsoftteams/platform/webhooks-and-connectors/how-to/add-outgoing-webhook

Azure Functionsでの処理

以下に処理の流れを記載しておきます。
今回は戻りメッセージを返すだけにしています。

  1. Teamsから発行されたsecretを読み込む
  2. Request HeaderからHMAC署名を抜き出す
  3. Request Bodyとsecretからハッシュ文字列(SHA256 HMAC)を生成する
  4. 2と3の結果が一致しているかを確認する
  5. 一致しているならOKメッセージ、それ以外はNGメッセージを返す

正直上記の処理は書かなくても送信Webhookとの連動は実装出来てしまいますが、セキュリティの観点で検証するようにしています。検証しなかった場合、Azure FunctionsのURLが漏れてしまうと誰でもアクセスできるようになってしまうので。

色々なAPIにもこの技術が使用されているので覚えておいて損は無いです。

https://techbureau-api-document.readthedocs.io/ja/latest/trade/3_how_to_implement/1_python.html

import logging
import azure.functions as func
import json
import hmac
import hashlib
import base64
import os

# secret
secret = os.environ["SECRET"]  # // e.g. "+ZaRRMC8+mpnfGaGsBOmkIFt98bttL5YQRq3p2tXgcE="

def main(req: func.HttpRequest) -> func.HttpResponse:
   logging.info('Python HTTP trigger function processed a request.')

   # authorization hmac message
   logging.info(f'Secret : {base64.b64decode(secret)}')
   auth = req.headers.get('Authorization')

   logging.info(f'Request header authorization : {auth}')

   signature = hmac.new(base64.b64decode(secret), req.get_body(), hashlib.sha256)
   compare_sig = f'HMAC {base64.b64encode(signature.digest()).decode("utf-8")}'
   logging.info(f'Request body signature : {compare_sig} / Request header authorization: {auth}')

   params = {}
   message = ''

   if compare_sig == auth:
       params = {
           'type': 'message',
           'text': 'OKです'
       }
   else:
       params = {
           'type': 'message',
           'text': 'NGです'
       }

   json_str = json.dumps(params, ensure_ascii=False, indent=2)

   logging.info(f'return response : {json_str}')

   return func.HttpResponse(json_str)

Request Bodyとsecretからハッシュ文字列(SHA256 HMAC)を生成する部分が結構躓きました。

signature = hmac.new(base64.b64decode(secret), req.get_body(), hashlib.sha256)

secretは元々base64文字列なので一度decodeを使用してbyteに変換します。ここが分からず色々試行錯誤してしまいました。
普通にbyte変換するだけだと一致しなかった記憶があります。

ハッシュの比較は適当に「==」で比較していますが、本当はcompare_digestを使用した方がいいかと思います。

if compare_sig == auth:
 ~
else:
  ~

こんな感じに書くのが正解かも。

if hmac.compare_digest(compare_sig, auth):
 ~
else:
  ~

そして色々サイトを漁って最終的に解消したのは公式ドキュメント。
やっぱ公式って偉大です。

https://docs.python.org/ja/3.9/library/hmac.html

まとめ

正直Pythonじゃなくても別の言語でFunctionsは書けたのですが、その案件がPython中心で書かれていたので言語を合わせる為にPythonにしました。

HMAC認証分からない、面倒だから実装しないではなく、キチンと実装することをお勧めします。

クローズなシステムならまだしもパブリックなシステムなら尚更。

-技術的な話
-, , ,