Microsoft Graph Webhook Listener
msgraph_webhook 게이트웨이 플랫폼은 인바운드 이벤트 리스너. Hermes가 Microsoft Graph로부터 변경 알림 받는 방식 — "Teams 세션 종료됨", "이 채팅에 새 메시지 도착", "이 캘린더 이벤트 업데이트됨". teams 플랫폼(사용자가 입력하는 채팅 봇)과 다름 — 이건 사람이 아니라 M365가 Hermes에게 뭔가 발생했다고 알리는 것.
현재 주요 컨슈머는 Teams 세션 요약 파이프라인: 세션가 transcript 생성하면 Graph 알림, 파이프라인이 가져옴, Hermes가 요약을 Teams로 다시 게시. 다른 Graph 리소스(/chats/.../messages, /users/.../events)도 같은 리스너 사용 — 파이프라인 컨슈머는 각자 PR로 도착.
사전 요구사항
- Microsoft Graph 애플리케이션 자격 증명 — Register a Microsoft Graph Application
- Microsoft Graph가 도달 가능한 공개 HTTPS URL (Graph는 비공개 엔드포인트 호출 안 함). 테스트는 dev tunnel로 충분; 프로덕션은 유효한 인증서 있는 실제 도메인 필요.
clientState값으로 쓸 강력한 공유 시크릿.openssl rand -hex 32로 생성하고~/.hermes/.env에MSGRAPH_WEBHOOK_CLIENT_STATE로 저장.
빠른 시작
최소 ~/.hermes/config.yaml:
platforms:
msgraph_webhook:
enabled: true
extra:
port: 8646
client_state: "replace-with-a-strong-secret"
accepted_resources:
- "communications/onlineMeetings"
또는 ~/.hermes/.env의 env vars로 (시작 시 자동 병합):
MSGRAPH_WEBHOOK_ENABLED=true
MSGRAPH_WEBHOOK_PORT=8646
MSGRAPH_WEBHOOK_CLIENT_STATE=<generate-with-openssl-rand-hex-32>
MSGRAPH_WEBHOOK_ACCEPTED_RESOURCES=communications/onlineMeetings
게이트웨이 시작: hermes gateway run. 리스너 노출 항목:
POST /msgraph/webhook— Graph 변경 알림GET /msgraph/webhook?validationToken=...— Graph subscription 검증 핸드셰이크GET /health— accepted/duplicate 카운터 포함 readiness probe
리스너 공개 노출 (reverse proxy, dev tunnel, ingress). Graph subscription용 알림 URL은 공개 HTTPS origin 뒤에 /msgraph/webhook:
https://ops.example.com/msgraph/webhook
Configuration
모든 설정은 platforms.msgraph_webhook.extra 하위:
| 설정 | 기본값 | 설명 |
|---|---|---|
host | 0.0.0.0 | HTTP 리스너 bind 주소. |
port | 8646 | Bind 포트. |
webhook_path | /msgraph/webhook | Graph가 POST하는 URL 경로. |
health_path | /health | Readiness 엔드포인트. |
client_state | — | Graph가 모든 알림에 echo하는 공유 시크릿. hmac.compare_digest와 비교 — openssl rand -hex 32로 생성. |
accepted_resources | `` (모두 허용) | Graph 리소스 경로/패턴 allowlist. 끝의 *은 prefix match. 앞의 /은 허용. 예: ["communications/onlineMeetings", "chats/*/messages"]. |
max_seen_receipts | 5000 | 알림 ID dedupe 캐시 크기. 한도 도달 시 가장 오래된 항목 evict. |
allowed_source_cidrs | `` (모두 허용) | 선택적 source-IP allowlist. 아래 참조. |
각 설정은 게이트웨이 시작 시 config에 병합되는 동등한 env var(MSGRAPH_WEBHOOK_*) 보유 — environment variables reference 참조.
보안 강화
clientState가 주요 인증 체크
모든 Graph 알림은 subscription 등록 시 사용한 clientState 문자열 포함. 리스너는 clientState 불일치하는 알림은 timing-safe 비교로 거부. Microsoft 공식 메커니즘 — 값을 강력한 공유 시크릿으로 취급.
client_state가 unset이면 리스너는 형식만 맞으면 모든 POST 수락. 프로덕션에서 절대 이 상태로 실행 금지.
Source-IP allowlisting (프로덕션 배포)
프로덕션에서는 Microsoft 공식 Graph webhook source IP 범위로 리스너 제한. Microsoft가 Office 365 IP Address and URL Web service에서 egress 범위 문서화. 설정 방법:
platforms:
msgraph_webhook:
enabled: true
extra:
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
또는 env var로:
MSGRAPH_WEBHOOK_ALLOWED_SOURCE_CIDRS="52.96.0.0/14,52.104.0.0/14"
빈 allowlist = 어디서든 수락 (기본값; dev-tunnel 워크플로 유지). 잘못된 CIDR 문자열은 경고 로그 남기고 무시. Microsoft IP 목록은 분기별 검토 — 변경됨.
HTTPS 종료
리스너는 평문 HTTP 사용. TLS는 reverse proxy(Caddy, Nginx, Cloudflare Tunnel, AWS ALB)에서 종료 후 로컬 네트워크로 리스너에 프록시. Graph는 비HTTPS 엔드포인트로 전달 거부 — Graph 자체에서 암호화되지 않은 트래픽이 도달할 경로 없음.
응답 위생
성공 시 리스너는 빈 body로 202 Accepted 반환 — 내부 카운터는 wire 응답에 노출 안 됨. 운영자는 /health로 카운트 관찰 가능.
상태 코드 표:
| 결과 | 상태 |
|---|---|
| 알림 수락 또는 dedupe됨 | 202 |
검증 핸드셰이크 (validationToken 포함 GET) | 200 (토큰 echo) |
| 배치의 모든 항목이 clientState 실패 | 403 |
잘못된 JSON / value 배열 누락 / 알 수 없는 리소스 | 400 |
| Source IP가 allowlist에 없음 | 403 |
validationToken 없는 단순 GET | 400 |
트러블슈팅
| 문제 | 확인 항목 |
|---|---|
| Graph subscription 검증 실패 | 공개 URL 도달 가능, /msgraph/webhook 경로 일치, validationToken 포함 GET이 10초 내에 토큰을 text/plain로 그대로 echo. |
| 알림 POST는 오는데 ingest 안 됨 | client_state이 subscription 등록값과 일치. 값이 변경됐으면 openssl rand -hex 32 재실행하고 새 subscription 생성. accepted_resources에 Graph가 보내는 리소스 경로 포함 확인. |
| 모든 알림 403 | clientState 불일치 (위조, 또는 subscription이 다른 값으로 등록됨). hermes teams-pipeline subscribe --client-state "$MSGRAPH_WEBHOOK_CLIENT_STATE"...로 subscription 재생성 (파이프라인 런타임 PR에 포함). |
리스너 시작되는데 curl http://localhost:8646/health 행 | 포트 바인딩 충돌. 필요 시 ss -tlnp | grep 8646 and change port: 확인. |
| Microsoft에서 오는 실제 Graph 요청이 403 | Source IP allowlist가 너무 좁음. allowed_source_cidrs 임시 제거하고 트래픽 흐름 확인 후 현재 Microsoft egress 범위 포함하도록 목록 확장. |
관련 문서
- Register a Microsoft Graph Application — Azure 앱 등록 사전 요구사항
- Environment Variables → Microsoft Graph — 전체 env var 목록
- Microsoft Teams bot setup — Teams에서 사용자가 Hermes와 채팅할 수 있는 별개 플랫폼