Слушатель вебхуков Microsoft Graph
Платформа шлюза msgraph_webhook — это входящий слушатель событий. Через неё Hermes принимает уведомления об изменениях из Microsoft Graph: «встреча в Teams завершилась», «в этом чате появилось новое сообщение», «событие в календаре обновили». От платформы teams (чат-бот, которому пишут пользователи) она отличается принципиально: здесь M365 сам сообщает Hermes, что что-то произошло, а не человек что-то печатает.
Сейчас главный потребитель этих событий — конвейер сводок по встречам Teams. Graph присылает уведомление, как только для встречи готов транскрипт, конвейер забирает его, и Hermes публикует сводку обратно в Teams. Другие ресурсы Graph (/chats/.../messages, /users/.../events) ходят через тот же слушатель — потребители для них в конвейере приедут отдельными PR.
Предварительные требования
- Учётные данные приложения Microsoft Graph — Регистрация приложения Microsoft Graph
- Публичный HTTPS-URL, до которого Microsoft Graph сможет дотянуться (Graph не звонит на приватные эндпоинты). Для тестов сгодится dev-туннель; для прода нужен настоящий домен с действующим сертификатом.
- Стойкий общий секрет для значения
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
Учтите: хост для привязки берётся из extra.host в config.yaml (см. пример выше); переменной MSGRAPH_WEBHOOK_HOST для переопределения нет.
Запустите шлюз: hermes gateway run. Слушатель отдаёт следующие эндпоинты:
POST /msgraph/webhook— уведомления об изменениях от GraphGET /msgraph/webhook?validationToken=...— рукопожатие для валидации подписки GraphGET /health— проба готовности со счётчиками принятых и дублей
Откройте слушатель наружу (через обратный прокси, dev-туннель, ingress). URL для уведомлений в подписках Graph — это ваш публичный HTTPS-origin плюс /msgraph/webhook:
https://ops.example.com/msgraph/webhook
Конфигурация
Все настройки лежат под platforms.msgraph_webhook.extra:
| Настройка | По умолчанию | Описание |
|---|---|---|
host | 0.0.0.0 | Адрес привязки HTTP-слушателя. Для привязки не к loopback нужен allowed_source_cidrs; loopback (127.0.0.1 / ::1) — самый простой вариант для dev-туннеля или обратного прокси. |
port | 8646 | Порт привязки. |
webhook_path | /msgraph/webhook | URL-путь, на который Graph отправляет POST. |
health_path | /health | Эндпоинт готовности. |
client_state | — | Общий секрет, который Graph возвращает в каждом уведомлении. Сравнивается через hmac.compare_digest — генерируйте командой openssl rand -hex 32. |
accepted_resources | [] (принимать все) | Список разрешённых путей и шаблонов ресурсов Graph. Завершающая * работает как совпадение по префиксу. Ведущий / допускается. Пример: ["communications/onlineMeetings", "chats/*/messages"]. |
max_seen_receipts | 5000 | Размер кэша дедупликации для идентификаторов уведомлений. При достижении лимита старые записи вытесняются. |
allowed_source_cidrs | [] | Обязателен при привязке не к loopback. Оставляйте пустым, только когда слушатель привязан к loopback и спрятан за локальным туннелем или обратным прокси. |
У большинства настроек есть эквивалентная переменная окружения (MSGRAPH_WEBHOOK_*), которая подмешивается в конфиг при запуске шлюза (исключение — host: только конфиг, см. примечание выше) — см. справочник по переменным окружения.
Усиление безопасности
clientState — основная проверка подлинности
В каждое уведомление Graph кладёт строку clientState, с которой регистрировалась ваша подписка. Слушатель отклоняет любое уведомление с несовпадающим clientState, сравнивая значения в режиме, устойчивом к атакам по времени. Так это описано в документации Microsoft — относитесь к значению как к стойкому общему секрету.
Если client_state не задан, слушатель попросту не стартует.
Список разрешённых IP-адресов источника (для прод-развёртываний)
В проде ограничьте слушатель опубликованными Microsoft диапазонами IP-источников для вебхуков Graph. Microsoft публикует диапазоны исходящего трафика в веб-сервисе IP-адресов и URL Office 365. Задайте их так:
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"
# ...добавьте актуальные диапазоны исходящего трафика категорий Microsoft 365 «Common» + «Teams»
Или через переменную окружения:
MSGRAPH_WEBHOOK_ALLOWED_SOURCE_CIDRS="52.96.0.0/14,52.104.0.0/14"
Привязка к хосту вне loopback — 0.0.0.0, :: или IP в локальной сети — без allowed_source_cidrs отклоняется уже на старте. Если у вас dev-туннель или обратный прокси на той же машине, привяжите Hermes к 127.0.0.1 или ::1 и оставьте список разрешений там пустым. Неверные строки CIDR пишутся в лог предупреждением и игнорируются. Сверяйтесь со списком IP от Microsoft раз в квартал — он меняется.
Терминация HTTPS
Слушатель говорит по обычному HTTP. Завершайте TLS на обратном прокси (Caddy, Nginx, Cloudflare Tunnel, AWS ALB) и проксируйте до слушателя по локальной сети. Graph отказывается доставлять на эндпоинты без HTTPS, так что незашифрованному трафику из самого Graph до вас попросту нет дороги.
Гигиена ответов
При успехе слушатель возвращает 202 Accepted с пустым телом — внутренние счётчики в ответ по проводу не утекают. Подсмотреть значения счётчиков оператор может через /health, который защищён теми же правилами по IP-источнику, что и путь вебхука.
Таблица кодов статуса:
| Исход | Статус |
|---|---|
| Уведомление(я) приняты или отброшены как дубль | 202 |
Рукопожатие валидации (GET с validationToken) | 200 (возвращает токен) |
| По всем элементам пакета не сошёлся clientState | 403 |
Битый JSON / нет массива value / неизвестный ресурс | 400 |
| IP источника не в списке разрешений | 403 |
Голый GET без validationToken | 400 |
Диагностика
| Проблема | Что проверить |
|---|---|
| Валидация подписки Graph не проходит | Публичный URL достижим, путь /msgraph/webhook совпадает, GET с validationToken возвращает токен дословно как text/plain в течение 10 секунд. |
| POST с уведомлениями приходят, но ничего не загружается | client_state совпадает с тем, под которым регистрировалась подписка. Если значение разъехалось, перегенерируйте его через openssl rand -hex 32 и создайте новую подписку. Проверьте, что accepted_resources включает путь ресурса, который шлёт Graph. |
| Каждое уведомление получает 403 | Несовпадение clientState (подделка или подписка зарегистрирована с другим значением). Пересоздайте подписку командой hermes teams-pipeline subscribe --client-state "$MSGRAPH_WEBHOOK_CLIENT_STATE" ... (приедет вместе с PR на рантайм конвейера). |
Слушатель не запускается на 0.0.0.0 | Задайте allowed_source_cidrs с актуальными диапазонами исходящего трафика вебхуков Microsoft либо привяжите Hermes к 127.0.0.1 / ::1 за туннелем или обратным прокси. |
Слушатель запустился, но curl http://localhost:8646/health зависает | Конфликт привязки порта. Проверьте ss -tlnp | grep 8646 и при необходимости смените port:. |
| Настоящие запросы Graph от Microsoft ловят 403 | Список разрешённых IP-источников слишком узкий. Расширьте его, добавив актуальные диапазоны исходящего трафика Microsoft. Если вы ещё отлаживаете путь через туннель, привяжите Hermes к loopback и доверьте публичный доступ туннелю. |
Связанные документы
- Регистрация приложения Microsoft Graph — обязательная регистрация приложения в Azure
- Переменные окружения → Microsoft Graph — полный список переменных окружения
- Настройка бота Microsoft Teams — другая платформа, через которую пользователи переписываются с Hermes в Teams