Broker 管理和投递配置
运行中的 PSSP Broker 独立完成客户端认证、主题/投递授权和 SQLite 管理。WebService 与其他开通服务只能调用加密的管理监听器。
此实现明确不支持 Broker 集群、复制、仲裁、分区所有权或跨节点协调。运行一个权威的低延迟 Broker,并让多个远程 worker/客户端实例连接它。可以部署多个 WebService worker,但不能部署多个 Broker 副本。
安全与存储边界
| 组件 | 知道什么 | 禁止做什么 |
|---|---|---|
| PSSP Broker | SQLite 路径、Argon2id 哈希、ACL、订阅投递授权、管理服务白名单、审计调用方。 | 调用 WebService 或解析业务负载。 |
| WebService | 管理主机/端口、自身 service ID 和 Ed25519 私钥、固定的 Broker 管理公钥。 | 打开 SQLite、获取 SQLite 路径、启动 Broker 二进制文件或验证 PSSP 客户端密码。 |
| PSSP 客户端/worker | 一次性的 client ID、用户名、密码、普通 PSSP 地址和数据面 Broker 公钥。 | 自行提升为未授权的投递模式/worker 组,或创建其他主体。 |
Broker 运行期间会独占锁定 <sqlite_path>.lock。此时离线管理命令会失败。setup 是唯一引导例外,必须在 serve 之前执行。
管理 TOML 示例
普通 PSSP 数据面和管理面必须使用不同的 X25519 私钥。完整样例位于 broker/config/pssp.aes-only.example.toml。
[management]
enabled = true
bind = "127.0.0.1:6690"
encryption_enabled = true
static_private_key = "替换为管理面_X25519_私钥"
max_request_bytes = 65536
authentication_timeout_seconds = 10
request_timeout_seconds = 15
[[management.allowed_services]]
service_id = "oxrecorder-webservice"
enabled = true
ed25519_public_key = "替换为32字节Ed25519原始公钥的base64url"
permissions = ["principal.provision", "principal.delete"]
principal_kinds = ["device"]
client_id_patterns = ["ox-device-*"]
username_patterns = ["device-*"]
allowed_delivery_modes = ["broadcast"]
allowed_share_groups = []
publish_topic_patterns = [
"/api/devices/*/heartbeat",
"/api/devices/*/audio/chunks",
]
subscribe_topic_patterns = ["/api/devices/*/commands"]
字段含义
| 字段 | 含义 |
|---|---|
bind | 独立管理监听地址。建议仅绑定回环或受防火墙保护的管理网络。 |
encryption_enabled | 必须为 true。管理面使用 X25519/HKDF 和 AES-256-GCM,不依赖 TLS。 |
static_private_key | 32 字节 X25519 私钥的无填充 base64url;必须与数据面私钥不同。 |
service_id | 稳定的应用身份,包含在签名挑战和凭据审计 actor 中。 |
ed25519_public_key | 服务 Ed25519 原始 32 字节公钥。对应私钥只保存在该服务中。 |
permissions | 允许 principal.provision 和/或 principal.delete。 |
principal_kinds | 服务可创建的主体类型。管理面永远禁止创建 administrator。 |
client_id_patterns/username_patterns | 简单 glob 白名单,* 匹配零个或多个字符。 |
allowed_delivery_modes | 允许 broadcast、shared 或两者。 |
allowed_share_groups | 服务可以分配的准确 worker 组名称。 |
| 主题 pattern | 单独的 * 匹配一个具体主题段。授权 worker 的通配过滤器时使用字面量 +。禁止扩大为 #。 |
密钥生成
# 分别生成数据面和管理面 X25519 私钥
pssp-broker generate-aes-only-private-key
pssp-broker generate-aes-only-private-key
# 从受保护的私钥文件派生管理公钥,供调用方固定
pssp-broker aes-only-public-key \
--private-key-file /run/secrets/pssp-management-x25519
# 为 WebService 生成 Ed25519 身份;命令写入 0600 PKCS#8 PEM,
# 并直接打印 TOML 所需的原始公钥 base64url
pssp-broker generate-management-service-key \
--private-key-file /run/secrets/pssp-webservice-ed25519.pem
# 以后可安全地再次输出公钥
pssp-broker management-service-public-key \
--private-key-file /run/secrets/pssp-webservice-ed25519.pem
服务私钥不能写入 Broker TOML;Broker 私钥不能写入 WebService 环境变量。只能通过可信部署渠道分发公钥 pin。
管理认证流程
- Broker 发送随机 challenge 和管理 X25519 公钥。
- 服务检查公钥 pin,生成临时 X25519 密钥和 salt,并使用 Ed25519 签署 service ID、challenge、时间戳、临时公钥和 salt。
- Broker 查找准确且启用的服务,验证签名和 ±30 秒时钟偏差,然后通过 X25519/HKDF-SHA256 派生会话密钥。
- 后续帧使用带序列号的 AES-256-GCM。重放、乱序、修改、错误签名、未知或禁用服务都会被拒绝。
- 每个请求还会检查 permission、主体类型、身份 pattern、主题、投递模式和 worker 组,然后才进入 Broker 内部事务。
开通成功时,Broker 原子地创建/轮换主体、替换 ACL 与投递授权,只保存 Argon2id 密码哈希,在审计中记录已认证的 service_id,并且只返回一次明文密码。
旧管理客户端如果省略 deliveryGrants,Broker 会为订阅 ACL 创建广播授权;共享 worker 必须明确发送投递授权。普通 PSSP 客户端若省略 shareGroup,且该过滤器只有一个授权,Broker 会自动使用该授权,客户端无法覆盖它。
workerID、共享 worker 与广播客户端
clientId 用于认证与授权;workerID 是 SUBSCRIBE 中持久 ACK/恢复身份;connection ID 只代表当前 TCP 会话;shareGroup 代表竞争 worker 组,但 Broker 只有在数据库存在准确投递授权时才接受。
| 连接 | workerID | 授权 | 每条消息 |
|---|---|---|---|
| A | audio-worker-a | shared / audio-workers | 三个共享 worker 之一。 |
| B | audio-worker-b | shared / audio-workers | 三个共享 worker 之一。 |
| C | audio-worker-c | shared / audio-workers | 三个共享 worker 之一。 |
| D | viewer-d | broadcast | 总是收到独立副本。 |
对于 A/B/C/D,Broker 通过本地轮询只把消息发给 A、B、C 中一个,同时也给 D 一份。A/B/C 共享一个组游标;选中的 worker 在 MSGACK 前断开时,Broker 会把未确认 QoS 1 消息重新投递给另一个在线组成员。D 的游标由自己的 workerID 标识,可使用 from: "resume" 重连。
多个广播 workerID 各自收到一份;不同共享组各收到一份。worker 不能去掉配置组来获得广播权限,viewer 也不能自行加入 worker 组。投递是至少一次,因此 worker 必须实现幂等处理。慢客户端不会阻塞 publisher ACK 或其他订阅者。
Broker 暂时接受旧的 subscriberId 作为输入别名。更新后的客户端发送 workerID,新集成必须使用 workerID。
WebService 环境配置
PSSP_DEVICE_PROVISIONING_ENABLED=true
PSSP_MANAGEMENT_HOST=127.0.0.1
PSSP_MANAGEMENT_PORT=6690
PSSP_MANAGEMENT_SERVICE_ID=oxrecorder-webservice
PSSP_MANAGEMENT_PRIVATE_KEY_FILE=/run/secrets/pssp-webservice-ed25519.pem
PSSP_MANAGEMENT_BROKER_PUBLIC_KEY=固定的管理X25519公钥
PSSP_MANAGEMENT_TIMEOUT_MS=15000
POST /api/me/devices 完成注册后,WebService 请求 Broker 创建一个设备主体,并向设备返回 pssp_client_id、pssp_username 和一次性的 pssp_password。WebService 不保存明文密码。删除设备时通过同一管理监听器发送 deletePrincipal。
旧的 PSSP_BROKER_BINARY、PSSP_BROKER_CONFIG 和 PSSP_BROKER_WORKING_DIRECTORY 已移除,不应恢复。
初始化与迁移
- 备份 SQLite 并停止旧 Broker。
- 在受保护 TOML 中加入管理配置与明确服务白名单,分别安装各类私钥。
- 新数据库先运行
setup,然后启动serve。 - Schema v1 启动时迁移到 v2,并为已有订阅 ACL 创建广播投递授权。
- 更新并重启 WebService,确认它没有 Broker 二进制、TOML 或 SQLite 路径。
- 通过另一个获授权的部署控制器开通 worker;每个实例使用唯一
workerID,同类实例使用相同已授权shareGroup。
故障排查
| 现象 | 检查项 |
|---|---|
| 管理连接立即拒绝 | service ID 是否存在/启用;Ed25519 密钥是否匹配;系统时钟偏差是否小于 30 秒。 |
| Broker 公钥 pin 不匹配 | WebService pin 必须从 management.static_private_key 派生,不能绕过检查。 |
management_not_authorized | permission、主体类型、身份 pattern、主题、投递模式或组超出服务范围。 |
subscription_delivery_not_authorized | 主体没有“过滤器 + 广播/共享组”的准确结构化授权。 |
| 离线 CLI 提示 Broker 占用 SQLite | 这是预期保护;使用管理 API,或仅在批准维护时停止 Broker。 |
| A/B/C 只有一个收到消息 | 同一共享组的预期行为。D 需要独立广播授权。 |
| 故障后出现重复处理 | 至少一次语义的预期结果;按主题序号或业务消息 ID 去重。 |