maiko/zabbix-discord-ops
GitHub: maiko/zabbix-discord-ops
一个具备租户感知能力的 Zabbix-Discord 事件运维集成服务,支持将告警路由到指定频道并允许授权用户在 Discord 中交互式管理告警生命周期。
Stars: 0 | Forks: 0
# Zabbix Discord Ops
Zabbix Discord Ops 为 Discord 增加了具备租户感知能力的交互式事件操作功能,且不会将 Discord 作为唯一事实来源。
它将 Zabbix 的问题发送到正确的 Discord 频道,并允许授权操作者进行确认、评论、抑制、更改严重程度、取消抑制或手动关闭问题。每次交互都会重新读取实时的 Zabbix 问题,并在更改任何内容之前,重新验证 Discord 公会、频道、用户或角色、Zabbix 事件标签以及 Zabbix 主机组。
## 安全模型
该服务被特意划分为两个信任边界:
- `POST /discord/interactions` 是唯一的公共 endpoint。Discord 请求需要有效的 Ed25519 签名、新的时间戳以及防重放保护。
- `POST /v1/zabbix/events`、`GET /healthz` 和 `GET /metrics` 保留在 loopback 上。Zabbix 事件摄取还需要一个独立的 bearer token。
按钮携带经过 HMAC 认证的紧凑事件、路由、操作和过期时间。它们并非授权许可。每次点击和模态框提交时,都会根据实时的 Discord 和 Zabbix 状态重新计算授权。
该服务没有数据库,不接受 Discord 消息内容,不需要特权 intents,并且在其 JSON 配置中不存储任何凭据。它确实会在一个包含原子 JSON 记录的小目录中持久化事件、路由、频道、消息和 pending-at 元数据,以便在没有数据库的情况下,通知关联能够挺过进程重启。
重放条目保留在进程内存中,直到 Discord 的五分钟签名窗口关闭。进程重启会清除该缓存,因此如果 Discord 在重启期间重放完全相同的交互,重复的 Zabbix 评论仍可能被提交。签名时间戳窗口、实时授权检查和 Zabbix 审计历史记录仍然有效;操作者在 bot 重启后重试交互之前,应检查 Zabbix。
## 支持的操作
- 告警生命周期:创建、更新和解决原始的 Discord 消息。
- 交互式操作:确认、评论、抑制、取消抑制、更改严重程度和手动关闭。
- Slash 命令:
- `/zabbix status`
- `/zabbix problems`
- 在 loopback `/metrics` endpoint 上的 Prometheus 文本指标。
手动关闭仍然取决于底层的 Zabbix trigger 是否允许手动关闭。如果 trigger 策略禁止,Zabbix 将拒绝该操作。
## 路由
每个路由都是 fail-closed 的,并绑定了以下所有内容:
- 一个 Discord 公会;
- 一个 Discord 频道;
- 至少一个允许的 Discord 角色或用户;
- 必需的 Zabbix 事件标签;
- 至少一个允许的 Zabbix 主机组 ID。
事件还必须包含配置的路由标签。典型的路由使用 `notification_route=digityser`、`tenant=digityser` 和 `impact=prod`,以及 `Tenants/Digityser` 的数字 ID。
如果路由标签、配置的必需标签或 Discord 关联标签具有多个不同的值,摄取和实时交互授权将拒绝该事件,而不是依赖于 Zabbix API 的顺序。其他 Zabbix 标签仍然可以是多值的。
请参阅 [`config.example.json`](config.example.json)。
## 构建与测试
该项目需要 Go 1.26.5 或更新版本。最低补丁版本要求是刻意的:早期的 1.26 工具链包含可利用的标准库漏洞。
```
go test -race ./...
go vet ./...
go build ./cmd/zabbix-discord-ops
go build ./cmd/register-commands
```
该容器使用固定版本的 Go 1.26.5 / Alpine 3.24 构建器以及 `scratch` 运行时。它以 UID/GID 65532 运行,仅包含二进制文件和 CA 包,并具有原生的健康检查。
```
docker build \
--build-arg VERSION=dev \
--build-arg GIT_SHA="$(git rev-parse HEAD)" \
--build-arg CREATED="$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
-t zabbix-discord-ops:dev .
```
## Discord 应用程序
使用以下 scope 创建一个 Discord 应用程序和 bot:
- `bot`
- `applications.commands`
bot 在其目标频道中仅需要 `View Channels`、`Read Message History`、`Send Messages` 和 `Embed Links` 权限。读取历史记录仅用于在进程记录消息 ID 之前停止时恢复已被 Discord 接受的消息。不要授予 Administrator 或特权 gateway intents。
将 `discord_bot_user_id` 设置为该 bot 用户的确切数字 ID。对账操作仅接受由该确切 bot 撰写的消息,因为 Discord 的 nonce 唯一性范围仅限于作者。
将应用程序的交互 endpoint 设置为:
```
https://YOUR_PUBLIC_HOST/discord/interactions
```
[`deploy/nginx/discord-interactions.location.conf`](deploy/nginx/discord-interactions.location.conf) 中的 Nginx location 仅发布该 endpoint。如果服务位于 Coolify 之后,请通过 Traefik 暴露相同的单个路径。
首先针对测试公会注册命令,更改会立即生效:
```
go run ./cmd/register-commands \
-application-id 000000000000000001 \
-guild-id 000000000000000002 \
-token-file /secure/path/discord-bot-token
```
token 文件必须是常规文件,且不具有组或其他用户的访问权限。只有在金丝雀验证之后才能省略 `-guild-id` 以注册全局命令。新命令默认没有成员权限;需明确授予在每个路由中配置的相同 Discord 角色。
## Zabbix 设置
### API 身份
创建一个专用的 Zabbix 用户和 API token。不要重用管理员或 Rudder 同步身份。
仅授予其用户组对路由所引用的主机组的读写权限。Zabbix 需要对 trigger 的读写权限才能更改严重程度或手动关闭事件。这不会授予配置 API:该角色的 API 允许列表仍然仅限于:
- `problem.get`
- `trigger.get`
- `host.get`
- `event.acknowledge`
根据 Zabbix 7.4 的要求,调用 `apiinfo.version` 时不带授权。
该角色仍然需要与确认、评论、严重程度更改、抑制和手动关闭相对应的 UI 操作权限。保持前端访问禁用状态。Zabbix 7.4 要求 User 角色至少保留一个 UI 条目,因此仅启用 `monitoring.problems`;前端禁用的用户组仍然会阻止登录。禁用所有其他角色操作和 UI 条目、所有模块以及所有服务访问。在将确切的主机组同时添加到 bot 配置和该专用用户组的权限中之前,不得上线新路由。
### Media type
导入 [`deploy/zabbix/media_zabbix_discord_ops.yaml`](deploy/zabbix/media_zabbix_discord_ops.yaml)。
它将以禁用状态导入。
定义:
- `{$ZABBIX_DISCORD_OPS_URL}` 为 `http://127.0.0.1:9088/v1/zabbix/events`;
- `{$ZABBIX_DISCORD_OPS_INGEST_TOKEN}` 为一个包含服务加载的相同摄取凭据的 secret-text macro;
- `{$ZABBIX.URL}` 为规范的 HTTPS 前端 URL。
该 webhook 拒绝非 trigger 事件、非 loopback 目标和 HTTP 代理。它将 Discord 消息关联 ID 作为 Zabbix 事件标签返回,因此恢复和更新操作会编辑原始的 Discord 消息。
在创建问题之前,服务会持久化记录一个 pending 事件。然后,它使用数字 Zabbix 事件 ID 作为 Discord nonce,并启用 `enforce_nonce`,随后原子性地存储返回的消息 ID。如果进程在这个短暂的时间窗口内停止,下一次尝试将扫描自该 pending 记录以来创建的 Discord 消息,并在决定是否仍需要发送消息之前恢复匹配的 nonce。已完成的关联会在重试和重启期间被重用。同一事件的工作由支持上下文的键控锁串行化;不相关的事件保持并发,且排队的请求不会超过其摄取截止时间。
导入的 media type 明确使用三次尝试,间隔为十秒。摄取处理程序对读取请求主体和所有上游工作应用八秒的截止时间,低于 media type 的十秒超时。
在告警处于活动状态时,请勿删除关联状态。如果丢失,则只剩下 Discord 短暂的 nonce 唯一性窗口,延迟过长的重试可能会创建重复项。
`state_review_after_days`(默认为 90 天,可在 30 到 3650 天之间配置)定义了已完成的记录何时被视为陈旧。启动和每日检查会验证每条记录,记录陈旧数量,并将其发布为 `zabbix_discord_ops_stale_completed_correlations`;它们从不删除状态。pending 和已完成记录将保持可用,直到操作者检查确切目标并执行显式的、已备份的清理。每个状态目录仅运行一个活动服务实例。
对账操作从最新到最旧扫描消息,一直回溯到 pending 时间戳之前的五分钟,并保持在八秒的摄取预算之内。在经历长时间的 Discord 故障或处于异常大流量的频道中,该扫描可能会在到达边界之前超时。然后服务会 fail closed 并保持 pending 记录原封不动;它不会创建推测性的替代记录。在重试之前,请恢复 Discord 访问权限或调查该 pending 记录。
使用此 media type 创建一个专用的通知用户。请勿将摄取 token 放在媒体的“Send to”字段中。
### Action 设计
为 media type 使用一个 trigger action,并启用 problem、recovery 和 update 操作。避免对同一事件从多个 action 调用此 webhook,因为 Zabbix webhook 关联标签的作用域仅限于事件。
将严重程度、租户、影响和时间窗口的决策保留在 Zabbix action 中:
- 默认路由:仅在其本地操作窗口内通知;
- 生产路由:7x24 小时通知;
- Discord:Warning 及以上级别;
- Pushover:根据单独的升级策略,High 和 Disaster 级别。
bot 负责执行目标和操作者授权。它不会重新实现 Zabbix 升级引擎。
## systemd 部署
推荐的部署方案是位于 [`deploy/systemd/zabbix-discord-ops.service`](deploy/systemd/zabbix-discord-ops.service) 中的强化版 systemd 服务。
它使用 `DynamicUser`、只读文件系统、空 capability set 和加密的 systemd 凭据。
安装非机密配置,如下所示:
```
/etc/zabbix-discord-ops/config.json
```
将 `state_path` 设置为 `/var/lib/zabbix-discord-ops/correlations`。该单元会为其动态用户创建私有的、可写的父状态目录,并且服务会创建模式为 `0700` 的 correlations 子目录。这些记录不包含任何凭据,但它们是操作状态:在升级时保留它们,并将该目录包含在主机备份中。
准备以下 systemd 凭据名称:
- `discord-bot-token`
- `discord-public-key`
- `zabbix-api-token`
- `ingest-token`
- `component-signing-key`
Discord 公钥是一个 32 字节的十六进制字符串。为摄取和组件签名凭据生成独立的 32 字节十六进制值。切勿将 Discord 或 Zabbix token 用于这两种用途。
使用 `systemd-creds encrypt --name=NAME - DESTINATION` 将每个密钥直接写入 `/etc/credstore.encrypted` 下的相应文件中;通过标准输入从密码管理器或未记录日志的提示符中输入该值。服务单元将这些文件映射到 `$CREDENTIALS_DIRECTORY`。
在启用之前:
```
systemd-analyze verify /etc/systemd/system/zabbix-discord-ops.service
nginx -t
systemctl daemon-reload
```
从单个非生产路由和合成的 Zabbix 事件开始。在扩大覆盖范围之前,验证问题创建、确认、评论、抑制、严重程度更改、恢复、路由拒绝以及未经授权的 Discord 角色。
## 容器部署
该进程有意仅绑定到 IP loopback 地址。在 Linux Docker 主机上,请使用主机网络,以便主机 Nginx 和 Zabbix 可以访问该 loopback 套接字。保持根文件系统只读,删除所有 capabilities,设置 `no-new-privileges`,挂载只读配置和只读凭据目录,并在 `/var/lib/zabbix-discord-ops` 挂载专用的可写目录用于存储关联数据。有意不支持使用 `0.0.0.0` 的桥接容器。
## 版本验证
标记的发布会发布:
- Linux `amd64` 和 `arm64` 二进制文件;
- SHA-256 校验和;
- 一个 SPDX JSON SBOM;
- 签名的 GitHub 构建来源;
- 带有 BuildKit SBOM 和来源证明的多架构 GHCR 镜像。
验证已下载的二进制文件:
```
sha256sum --check checksums.txt
gh attestation verify zabbix-discord-ops_linux_amd64 \
--repo maiko/zabbix-discord-ops
```
验证镜像:
```
gh attestation verify \
oci://ghcr.io/maiko/zabbix-discord-ops:VERSION \
--repo maiko/zabbix-discord-ops
```
## 许可证
Apache License 2.0。请参阅 [`LICENSE`](LICENSE)。
标签:Discord机器人, EVTX分析, Zabbix, 告警管理, 日志审计, 自动化运维, 自定义请求头, 请求拦截, 运维监控