跳到主要内容

Microsoft Graph Webhook 监听器

msgraph_webhook 网关平台是一个入站事件监听器。Hermes 通过它接收来自 Microsoft Graph 的变更通知——例如“Teams 会议已结束”、“此聊天中收到新消息”、“此日历事件已更新”。这与 teams 平台(用户向其发送消息的聊天机器人)不同——后者是 M365 告知 Hermes 发生了某事,而非由人发起。

目前的主要消费者是 Teams 会议摘要流水线:当会议生成转录文本时,Graph 会发出通知,流水线获取该文本,然后 Hermes 将摘要发布回 Teams。其他 Graph 资源(/chats/.../messages/users/.../events)也使用相同的监听器——流水线消费者会通过各自的 PR 接入。

前提条件

  • Microsoft Graph 应用程序凭据 — 注册 Microsoft Graph 应用程序
  • 一个 Microsoft Graph 可以访问的公共 HTTPS URL(Graph 不会调用私有端点)。测试时使用开发隧道即可;生产环境需要具有有效证书的真实域名。
  • 一个强共享密钥,用作 clientState 值。使用 openssl rand -hex 32 生成,并将其放入 ~/.hermes/.env 中,设置为 MSGRAPH_WEBHOOK_CLIENT_STATE

快速开始

最小的 ~/.hermes/config.yaml

platforms:
msgraph_webhook:
enabled: true
extra:
host: 127.0.0.1
port: 8646
client_state: "replace-with-a-strong-secret"
accepted_resources:
- "communications/onlineMeetings"

或者通过 ~/.hermes/.env 中的环境变量(启动时自动合并):

MSGRAPH_WEBHOOK_ENABLED=true
MSGRAPH_WEBHOOK_PORT=8646
MSGRAPH_WEBHOOK_CLIENT_STATE=<generate-with-openssl-rand-hex-32>
MSGRAPH_WEBHOOK_ACCEPTED_RESOURCES=communications/onlineMeetings

注意:绑定主机从 config.yaml 中的 extra.host 读取(参见上面的示例);没有 MSGRAPH_WEBHOOK_HOST 环境变量覆盖。

启动网关:hermes gateway run。监听器暴露以下端点:

  • POST /msgraph/webhook — 来自 Graph 的变更通知
  • GET /msgraph/webhook?validationToken=... — Graph 订阅验证握手
  • GET /health — 就绪探针,包含已接受/重复计数器

公开暴露监听器(通过反向代理、开发隧道或入口控制器)。用于 Graph 订阅的通知 URL 是你的公共 HTTPS 源地址后跟 /msgraph/webhook

https://ops.example.com/msgraph/webhook

配置

所有设置均位于 platforms.msgraph_webhook.extra 下:

设置默认值描述
host0.0.0.0HTTP 监听器的绑定地址。非环回绑定需要 allowed_source_cidrs;环回(127.0.0.1 / ::1)是最简单的开发隧道/反向代理设置。
port8646绑定端口。
webhook_path/msgraph/webhookGraph POST 请求的 URL 路径。
health_path/health就绪端点。
client_state共享密钥,Graph 会在每个通知中回显该值。使用 hmac.compare_digest 进行比较 — 使用 openssl rand -hex 32 生成。
accepted_resources[](接受所有) Graph 资源路径/模式的允许列表。尾随 * 作为前缀匹配。前导 / 是被容忍的。示例:["communications/onlineMeetings", "chats/*/messages"]
max_seen_receipts5000通知 ID 的去重缓存大小。达到上限时驱逐最旧的条目。
allowed_source_cidrs[]非环回绑定必需。仅当监听器绑定到环回地址并由本地隧道/反向代理前置时,才留空。

大多数设置也有等效的环境变量(MSGRAPH_WEBHOOK_*),它们在网关启动时合并到配置中(例外是 host,仅限配置—见上文说明)— 请参阅环境变量参考

安全加固

clientState 是主要的身份验证检查

每个 Graph 通知都包含你注册订阅时提供的 clientState 字符串。如果通知的 clientState 不匹配,监听器将使用计时安全比较予以拒绝。这是微软记录的机制—请将该值视为强共享秘密。

如果未设置 client_state,监听器将拒绝启动。

源 IP 允许列表(生产部署)

对于生产环境,将监听器限制为微软发布的 Graph webhook 源 IP 范围。微软在 Office 365 IP 地址和 URL Web 服务 下记录了出站范围。配置如下:

platforms:
msgraph_webhook:
enabled: true
extra:
host: 0.0.0.0
client_state: "..."
allowed_source_cidrs:
- "52.96.0.0/14"
- "52.104.0.0/14"
# ...add the current Microsoft 365 "Common" + "Teams" category egress ranges

或者作为环境变量:

MSGRAPH_WEBHOOK_ALLOWED_SOURCE_CIDRS="52.96.0.0/14,52.104.0.0/14"

如果在没有 allowed_source_cidrs 的情况下绑定非环回主机(如 0.0.0.0:: 或 LAN IP),启动时将被拒绝。如果你在同一台机器上使用开发隧道或反向代理,请将 Hermes 绑定到 127.0.0.1::1,并将允许列表留空。无效的 CIDR 字符串会记录警告并被忽略。每季度审查微软 IP 列表—它会发生变化。

HTTPS 终止

监听器使用纯 HTTP 通信。在反向代理(Caddy、Nginx、Cloudflare Tunnel、AWS ALB)处终止 TLS,并通过本地网络代理到监听器。Graph 拒绝交付到非 HTTPS 端点,因此来自 Graph 本身的未加密流量无法到达你。

响应卫生

成功时,监听器返回 202 Accepted 且响应体为空——内部计数器不会出现在网络响应中。操作员可以通过 /health 端点观察计数,该端点受与 webhook 路径相同的源 IP 规则保护。

状态码表:

结果状态码
通知已接受或去重202
验证握手(带有 validationToken 的 GET 请求)200(回显令牌)
批次中的每个项目均因 clientState 失败403
JSON 格式错误 / 缺少 value 数组 / 未知资源400
源 IP 不在允许列表中403
不带 validationToken 的普通 GET 请求400

故障排除

问题检查事项
Graph 订阅验证失败公共 URL 可访问,/msgraph/webhook 路径匹配,带有 validationToken 的 GET 请求在 10 秒内以 text/plain 原样回显令牌。
通知已 POST 但未被摄入client_state 与你注册订阅时使用的值匹配。如果值发生漂移,请重新运行 openssl rand -hex 32 并创建新订阅。检查 accepted_resources 是否包含 Graph 发送的资源路径。
每个通知都返回 403clientState 不匹配(被伪造,或订阅注册时使用了不同的值)。使用 hermes teams-pipeline subscribe --client-state "$MSGRAPH_WEBHOOK_CLIENT_STATE" ... 重新创建订阅(随管道运行时 PR 提供)。
监听器拒绝在 0.0.0.0 上启动allowed_source_cidrs 设置为 Microsoft 当前的 webhook 出口范围,或者将 Hermes 绑定到隧道或反向代理背后的 127.0.0.1 / ::1
监听器已启动,但 curl http://localhost:8646/health 挂起端口绑定冲突。检查 ss -tlnp | grep 8646 并在必要时更改 port:
来自 Microsoft 的真实 Graph 请求被返回 403源 IP 允许列表过窄。扩大列表以包含当前的 Microsoft 出口范围。如果你仍在验证隧道路径,请将 Hermes 绑定到环回接口,让隧道处理公共暴露。