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, 告警管理, 日志审计, 自动化运维, 自定义请求头, 请求拦截, 运维监控