构建许可与安全
构建许可(Grant) 是把公开的构建服务器绑到私有构建机器的密码学原语。 没有许可,构建机器必须绝对信任构建服务器;有许可,构建机器在本地验证 每个进入的会话,可以拒绝任何与自己嵌入的公钥不匹配的东西。
许可里有什么
每个许可携带以下字段:
| 字段 | 类型 / 取值 | 说明 |
|---|---|---|
user_id | 用户标识 | 颁发给哪个用户。 |
job_id | 任务标识 | 这个许可绑定的构建任务。 |
issuer_id | 签发方标识 | 哪个控制面签发的(便于密钥轮换识别)。 |
issued_at | unix 时间戳 | 签发时刻。 |
ttl_secs | 整数,5–30 秒(常规许可) | 许可 token 的存活时间。 |
execution_params | 资源约束 | CPU、内存等执行参数。 |
webrtc.allowed_channels | 字符串列表,如 ["espctl", "pty", "firmware"] | 允许打开的数据通道名。 |
webrtc.max_bandwidth_kbps | 整数 | 滑动窗口带宽上限。 |
webrtc.max_message_rate | 整数 / 秒 | 每秒消息数上限。 |
webrtc.ice_servers | STUN / TURN 列表 | 用于建立 WebRTC 连接。 |
webrtc.peer_fingerprint | 请求方证书的 SHA-256 指纹 | 用于绑定到具体客户端。 |
整张许可被签名,构建机器在编译期嵌入对应公钥,在执行任何会话之前 在本地验证签名。
生命周期
1. 浏览器/MCP 客户端计算自己证书的指纹
(DER 形式证书的 sha-256)。
2. 客户端 POST /grant/request 带上指纹和需要的通道。
3. 构建服务器:
- 鉴权请求方(session/JWT/MCP_AUTH_SECRET)。
- 检查速率限制和配额。
- 挑 ICE 服务(STUN、带轮换凭据的 TURN)。
- 构造一份许可记录,TTL <= 30s。
- 用签名密钥签名。
4. 客户端收到签名许可 + ICE 服务 + ephemeral job_id。
5. 客户端用 ICE 服务打开自己的 WebRTC peer 连接。
6. 构建机器(在定期查看 /agents/jobs)看到新任务和许可。
7. 构建机器验证:
- 许可签名(对照嵌入的公钥)。
- issued_at + ttl_secs > now。
- peer 证书指纹匹配许可中的 peer_fingerprint
(这发生在连接协商完成后,加密层交出证书时)。
8. 构建机器只执行许可中 allowed_channels 列出的通道。
9. 构建机器按通道执行 max_bandwidth_kbps 和 max_message_rate。
10. 构建结束(或许可过期),构建机器拆除连接。
安全模型假设什么 —— 以及不假设什么
假设:
- 构建机器嵌入的公钥没有被一个已经在构建机器上拿到 root 的攻击者 换掉(如果他真拿到了,游戏已经结束了)。
- 构建服务器的私钥保存在构建服务器主机上没泄露。如果它泄露了,攻击者 可以为任何人铸造许可,但他们仍然不能让构建机器跑超出沙箱允许范围 的任意代码。
- 浏览器的证书指纹对每个会话是唯一的;在每次 peer 连接上重新计算。
不假设:
- 构建服务器被信任来读或修改构建内容。它不行 —— 数据通道在与构建机器 之间端到端加密。
- 客户端和构建机器之间的网络是可信的。WebRTC over TURN 加密整个 payload;中间人只看到 TURN 包裹的密文。
- CORS 或 CSP 单独是足够的浏览器侧保护。指纹绑定让被偷的许可对 任何证书不匹配的人都没用。
通道白名单强制执行
构建机器提供的最具体的安全属性是通道白名单:许可精确列出允许
哪些 WebRTC 数据通道名(例如 ["espctl", "pty", "firmware"]),
构建机器拒绝任何用其他名字打开的通道。
这在构建机器的 on_data_channel handler 中强制执行 —— 在读任何消息
之前,如果 channel label 不在白名单里就关闭它。没有服务端的 opt-out,
也没有按消息的覆盖。
如果未来构建添加新数据通道(例如 metrics 通道),每个需要它的客户端
都需要在 required_channels 中请求新名字。构建服务器会拒绝为不在
配置允许列表中的未知通道名颁发许可。
带宽和速率限制
每个许可携带 max_bandwidth_kbps 和 max_message_rate 数值。
构建机器用滑动窗口强制执行两者:
- 带宽: 拖尾 1 秒窗口的字节计数器。当滚动总和超过上限,写阻塞 (发送变慢),直到窗口前移。
- 消息率: 拖尾 1 秒窗口的消息计数器。同样的强制执行模型。
它们是按通道的,不是按会话的。firmware 通道通常比 espctl 通道
得到大得多的带宽预算。
许可过期 —— 故意短
默认许可有效期是 5-30 秒。这是有意的:
- 不知怎么偷到许可的攻击者只有几秒时间用它。
- 构建服务器密钥泄露的影响范围有限 —— 攻击者能向前铸造许可,但他 不能重放昨天的许可。
- 长时间运行的构建会话(例如交互式串口监控)有单独的、生命周期更长
的“会话许可“,有效期上限由服务侧策略决定(通常几分钟)。用户通过许可
请求中的
timeout_secs字段申请更长的会话。
对交互式 PTY 会话,典型模式是周期性地通过重新颁发新许可来刷新,
不丢失底层 peer 连接。客户端和构建机器在 espctl 通道上交接新许可。
用户不需要做任何事 —— 客户端自动处理刷新。
另见
- 构建服务器与连接建立 —— 许可怎么颁发。
- WebRTC 构建机器与数据通道 —— 许可怎么强制执行。