ADR-0005:边缘遥测走 MQTT,Agent 侧不持有 Broker 凭证
- 状态:已采纳
- 日期:2026-09-15
- 决策者:毕设作者
- 相关文档:
docs/07-STM32边缘终端设计.md、docs/04-MCP安全网关设计.md、docs/adr/0002-mcp-gateway-as-sole-egress.md
背景
边缘终端需要把温度与振动数据送到服务端。可选方案包括 MQTT、HTTP 上报、CoAP、自定义 TCP 协议、甚至串口转接。同时必须回答一个安全问题:Agent 能不能自己订阅 MQTT 主题拿数据?
从工程便利角度看,让 Agent 直接订阅 iot/+/telemetry 是最省事的——一个 mqtt.connect 就拿到全部设备数据。但这会直接推翻 ADR-0001 与 ADR-0002 建立的边界:
- Agent 一旦持有 Broker 凭证,就能订阅全部主题,包括其他设备、其他产线的数据;
- Broker 侧无法做"按调用上下文裁剪字段";
- Agent 的取数行为不再经过审计落库,审计出现盲区;
- 提示注入可以诱导 Agent 把订阅范围扩大到与任务无关的设备。
决策
分工明确,两条通道物理分离:
| 通道 | 两端 | 协议 | 承载内容 |
|---|---|---|---|
| 上行数据通道 | 边缘终端 ↔ MQTT Broker ↔ 网关 | MQTT | 温度、振动遥测;终端状态;指令 ACK |
| 工具调用通道 | Agent ↔ 网关 | MCP | mqtt.read_sensor 等工具调用 |
Agent 不持有 MQTT Broker 的任何凭证,不部署 MQTT 客户端库,环境变量中不存在 Broker 地址与账号。Agent 取遥测数据的唯一方式是调用网关的 mqtt.read_sensor 工具,由网关:
- 从 Broker 订阅/查询得到原始数据(或直接读
business-db.sensor_data的已落库数据); - 按参数校验设备 ID 是否在 Agent 的授权设备范围内;
- 按字段白名单裁剪为
{temp, vibration, time}; - 落审计;
- 返回。
理由
- 订阅是"全量能力",工具是"按次授权"。MQTT 主题通配符
+/#天然是"一次订阅、长期全量",与最小权限原则冲突;MCP 工具天然是"一次调用、一次授权、一次审计"。 - Broker 无法表达业务语义。Broker 不知道"这个 Agent 当前在处理 3 号产线的故障",网关知道。裁剪与授权必须在有业务上下文的地方做。
- 终端的信任对象收敛为网关一个。终端只需配置一套 Broker 凭证(或一机一密),不需要为 Agent 单独开权限,固件侧的安全设计大幅简化。
- 审计完整性。所有 Agent 取数行为在
audit_log中有且只有一条来源,答辩时可以用一条查询证明"Agent 从未绕过网关取过数"。
备选方案与拒绝理由
| 方案 | 为什么没选 |
|---|---|
| Agent 直接订阅 MQTT 主题 | 违反 ADR-0002 唯一出口不变量;无法字段裁剪;审计盲区;主题通配符导致越权范围不可控。 |
Agent 直连 business-db.sensor_data | 违反 ADR-0001;Agent 会顺带获得同库其他表的访问能力。 |
| 终端 HTTP 直连业务接口服务 | 工业现场网络抖动与功耗场景下,HTTP 长轮询/短连接开销高于 MQTT;且失去 Broker 的 QoS 与遗嘱消息能力。 |
| 网关把 MQTT 数据实时推给 Agent(订阅式) | 原型阶段会引入推流背压与上下文膨胀问题;且"推"绕过了"一次调用一次审计"。落地期若需要,可作为 mqtt.watch_sensor 工具另行设计并保留审计语义。 |
后果
正面
- 终端侧只需信任一个网关,固件安全设计简化为一机一密 + TLS。
- Agent 的取数范围可被逐次限制(设备 ID、时间窗口、采样点数)。
- 审计链条完整:终端 → Broker → 网关 → business-db 每一跳都有日志。
负面 / 代价
- 网关成为 MQTT 的唯一订阅方,需要承担主题订阅管理、连接保活与消息积压处理。
- 实时性要求高的场景(如高频振动波形分析)经网关转发会有延迟;原型阶段用"网关先落库、Agent 按窗口读取"的方式规避,落地期需评估。
- 遥测数据存在"落库一份 + 网关缓存一份"的双份问题,需要明确以
business-db为权威来源。
原型期的具体取值
- Broker:Mosquitto,仅监听内网/本机,启用
password_file与 ACL。 - 主题:
iot/{deviceId}/telemetry、iot/{deviceId}/status、iot/{deviceId}/cmd。 - QoS:遥测用 QoS 1(至少一次,容忍重复,靠
seq去重);指令用 QoS 1 + ACK。 - 网关订阅
iot/+/telemetry,以服务账号身份;该账号不出现在 Agent 侧任何配置中。