🗂

Azure Bot Service を Teams のチャットボットで使う場合の通信経路と認証の整理

2024/10/03に公開

Teams のチャットボットを展開する際の Azure Bot Service と Bot、Teams の通信経路を整理してみました。

Azure Bot Service と Bot の通信

Teams と Azure Bot Service、Bot の通信経路はざっくりと以下のようになります。
1~4は Teams から Bot へのメッセージ送信、5~7は Bot から Teams へのメッセージ送信です。

  1. ユーザーが Teams クライアントのチャネルでメッセージを送信する
  2. Teams は Azure Bot Service に設定されたメッセージング エンドポイントを取得する
  3. Teams はメッセージング エンドポイント(Bot)にメッセージを送信する
  4. Bot は Teams のメッセージのヘッダーに含まれるトークンを検証するためにメタデータの取得を行い、メタデータの情報を元に検証する
  5. Bot は Entra ID からトークンを取得する
    (マネージド ID を使っている場合は Azure のサービス内のエンドポイントから取得する)
  6. Bot は Teams にメッセージを送信する
  7. Teams は Teams クライアントにメッセージを表示する

以下にポイントを整理してみます。

1. Teams から Bot へメッセージを送信するフロー

Azure Bot Service というリソースを Azure に展開しエンドポイントを設定するため、Azure Bot Service が Teams と Bot の間に入り仲介するようなイメージを持ってしまいますが、Teams は Bot に直接メッセージを送信します。
Azure Bot Service に設定したエンドポイントはあくまでも Teams が送信する宛先のエンドポイントを取得するためのものであり、Azure Bot Service はメッセージのやり取りには関与しません。

これはつまり、Bot は Teams から直接メッセージを受信できるようにアクセス制御を設定する必要があるということです。ただ、Teams の IP アドレスは固定されていることが保証されているわけではなく、ネットワークのレイヤーでアクセス制御することは事実上出来ません。

ちなみに、Teams の IP アドレスは以下のドキュメントに記載されています。
https://learn.microsoft.com/ja-jp/microsoft-365/enterprise/urls-and-ip-address-ranges?view=o365-worldwide#microsoft-teams

以下は Teams のチャットから送ったメッセージを Bot 側でダンプしたリクエストです。

POST /api/messages HTTP/1.1
Connection: Keep-Alive
Content-Type: application/json; charset=utf-8
Ms-Cv: EJTXcHRHYEW9uT9wkAAmEw.1.1.1.1984583658.1.1
X-Forwarded-For: 52.114.15.98
Content-Length: 1084
X-Forwarded-Host: possible-goat-proud.ngrok-free.app
X-Ms-Tenant-Id: e75b1cfe-2e3c-43e6-b59e-e95648cae091
X-Forwarded-Proto: https
X-Ms-Conversation-Id: a:1g_nXFE5GST73490rhfmRARxg0DhBB0lQIdcPSFXtirvDfRO7_0mOFQfTjUyoxknxMnO5r7G5RrDSEHxM4C3WlKLgIcLIVS8lWWR9TIdyNIeFwFK-16_HERvxIWSivDkV
Authorization: Bearer <トークン>
Host: localhost:3978
User-Agent: Microsoft-SkypeBotApi (Microsoft-BotFramework/3.0)

{"text":"dddddddddddddddddddddd","textFormat":"plain","attachments":[{"contentType":"text/html","content":"<p>dddddddddddddddddddddd</p>"}],"type":"message","timestamp":"2024-08-27T08:57:33.6578996Z","localTimestamp":"2024-08-27T17:57:33.6578996+09:00","id":"1724749053639","channelId":"msteams","serviceUrl":"https://smba.trafficmanager.net/apac/","from":{"id":"29:1mcLiu9uB17FOOC36KFUgYQ9uYm6SNWHrcthCO7M-LICAADZGhlEQ6E2bJ_niwS5YbOIbSCMIm7QYiMSrJSoHDQ","name":"Tsubasa Nomura","aadObjectId":"c9ed6edd-e66a-47de-a98d-4f41770308c6"},"conversation":{"conversationType":"personal","tenantId":"e75b1cfe-2e3c-43e6-b59e-e95648cae091","id":"a:1g_nXFE5GST73490rhfmRARxg0DhBB0lQIdcPSFXtirvDfRO7_0mOFQfTjUyoxknxMnO5r7G5RrDSEHxM4C3WlKLgIcLIVS8lWWR9TIdyNIeFwFK-16_HERvxIWSivDkV"},"recipient":{"id":"28:4539e79f-34e2-4965-b7d3-02e59abe6f32","name":"tsunomurbottestNoMI"},"entities":[{"locale":"ja-JP","country":"JP","platform":"Web","timezone":"Asia/Tokyo","type":"clientInfo"}],"channelData":{"tenant":{"id":"e75b1cfe-2e3c-43e6-b59e-e95648cae091"}},"locale":"ja-JP","localTimezone":"Asia/Tokyo"}

送信元のクライアント IP アドレスは、X-Forwarded-For と同じ 52.114.15.98 でした。この IP アドレスは、上記のドキュメントに記載されている Teams の IP アドレスの範囲と一致しています。

2. Bot から Teams にメッセージを送信するフロー

Bot から Teams にメッセージを送信する際も、Bot は Teams にアウトバウンド方向に直接メッセージを送信します。
つまり、全ユーザー共通の Teams のエンドポイントに対してメッセージを送信するため、FQDN や IP アドレスでアクセス制御はできません。アプリケーションのレイヤーで認証・認可された上でメッセージが受信されます。

Teams へのメッセージの送信時に Authorization ヘッダーにベアラートークンがセットされます。トークンは Entra ID から取得します。
Entra IDからのトークンの取得に使うアプリは、ユーザーが自身でアプリ登録をする方法と、マネージド IDを使う方法があります。もちろんマネージド IDを使う方法がセキュアです。

マネージド ID を使わない場合、Bot 内から Entra ID のエンドポイントへトークンを取得するためのリクエストを送信します。
以下は Entra ID からトークンを取得するリクエストの例です。

POST https://login.microsoftonline.com/botframework.com/oauth2/v2.0/token
Host: login.microsoftonline.com
Content-Type: application/x-www-form-urlencoded

grant_type=client_credentials&client_id=<Client id>&client_secret=<Client secret>&scope=https%3A%2F%2Fapi.botframework.com%2F.default

以下の様なレスポンスが返ってきます。

HTTP/1.1 200 OK
Cache-Control: no-store, no-cache
Pragma: no-cache
Content-Type: application/json; charset=utf-8
Expires: -1
Strict-Transport-Security: max-age=31536000; includeSubDomains
X-Content-Type-Options: nosniff
P3P: CP="DSP CUR OTPi IND OTRi ONL FIN"
x-ms-request-id: fddb943d-9285-48f2-bad8-5a7d18b82a00
x-ms-ests-server: 2.1.19005.9 - EUS ProdSlices
x-ms-srs: 1.P
X-XSS-Protection: 0
Set-Cookie: fpc=AqPNWmzrgoJKv1OMbappOU4aY5d-AQAAAOmQjd4OAAAA; expires=Thu, 31-Oct-2024 06:42:51 GMT; path=/; secure; HttpOnly; SameSite=None,x-ms-gateway-slice=estsfd; path=/; secure; samesite=none; httponly
Date: Tue, 01 Oct 2024 06:42:50 GMT
Content-Length: 1247

{
  "token_type": "Bearer",
  "expires_in": 86399,
  "ext_expires_in": 86399,
  "access_token": "eyJ0e...."
}

レスポンスの access_token のトークンを使って Teams にメッセージを送信します。以下は Teams にメッセージを送信するリクエストの例です。

POST https://smba.trafficmanager.net/teams/v3/conversations/a:1WGDp1pnNxijALsKQwYTkz_yAJ5oYv055yNA61JeK4MjVNN3Rw3gKlE8z_2mOMuOMGbLPcNr6Cc-raU6M_ak8thFZaQlxzl8hUCEkXdmY8nWyFYu-M-hD5Y4NGCMkYqa3/activities
Authorization: Bearer eyJ0e....

{
  "type": "message",
  "from": {
    "id": "HOGEHOGE",
    "name": "name"
  },
  "text": "Hello World!"
}

POST 先のhttps://smba.trafficmanager.netは、Teams が Bot にメッセージを送信したときの serviceUrl に紐づきます。

マネージドID使ってる場合は、トークンは Azure のサービス内のエンドポイントから取得するため Entra ID のエンドポイントへのアクセスはありません。
https://learn.microsoft.com/ja-jp/azure/bot-service/rest-api/bot-framework-rest-connector-authentication?view=azure-bot-service-4.0&tabs=userassigned#step-1-request-an-access-token-from-the-microsoft-entra-id-account-login-service

App Service と Azure Functions は、トークン取得のために、内部でアクセス可能なRES Tエンドポイントを提供します。 MSI/token エンドポイントからのアクセス トークンを要求するには、エンドポイントに対して GET 要求を行います。

FAQ

Bot は閉域化できるのか?

いわゆるネットワークのレイヤーでの閉域化は出来ません。

Teams から Bot へのメッセージ送信は Teams のプラットフォームからの送信を許可する必要がありますが、Teams の IP アドレスは固定されているわけではないため、ネットワークのレイヤーでアクセス制御することは出来ません。

Bot から Teams へのメッセージ送信は、login.botframework.comsmba.trafficmanager.netを許可する必要があります。Azure 上に Bot をホストする場合は Azure Firewall を使うことで特定の通信のみを許可できます。ネットワークルールで上記の FQDN を許可することで、Bot から Teams へのメッセージ送信のみに限定できます。
App Service で構成する場合、以下のドキュメントが参考になります。
https://learn.microsoft.com/ja-jp/azure/architecture/example-scenario/teams/securing-bot-teams-channel

ちなみに Bot から Teams へのメッセージ送信時に Azure Firewall を挟むと確かに上記 FQDN にアクセスしていたログが記録されていました。

Teams のテナントを区別する方法はどのようにすればよいか?

ネットワーク的な閉域化は出来ないものの可能な限りメッセージの送信元を特定したい、という要望はあると思います。
その場合、メッセージに含まれるテナント ID を使って区別し、特定のテナントではないメッセージは無視する、という方法があります。
その方法が以下の FAQ に記載されています。
https://learn.microsoft.com/en-us/azure/bot-service/bot-service-resources-faq-security?view=azure-bot-service-4.0#how-do-i-restrict-the-use-of-my-bot-to-users-belonging-to-my-tenant-only

具体的には 1. のメッセージを見てもらうと、Channel Data にテナント ID が含まれています。このテナント ID を使ってメッセージをフィルタリング出来ます。

参考ドキュメント

https://jpdsi.github.io/blog/azure-bot-service/ip-limitation/
https://learn.microsoft.com/ja-jp/azure/bot-service/bot-builder-concept-authentication-types?view=azure-bot-service-4.0
https://learn.microsoft.com/ja-jp/azure/bot-service/bot-builder-authentication?view=azure-bot-service-4.0&tabs=userassigned%2Caadv2%2Ccsharp

Microsoft (有志)

Discussion