封闭构建是什么
封闭构建要求构建过程不访问外部网络、不依赖未在描述中声明的文件与隐式环境,全部输入在构建前显式声明并冻结。Google 内部的大规模构建系统(Bazel 的前身 Blaze)即以此为核心设计:只有输入完整可列举,缓存键才能覆盖全部输入,构建结果才既稳定又可精确复用;同时它收窄了供应链攻击面——构建期动态拉取依赖是最常见的投毒通道。
基本信息
| 适用平台 | Bazel / Nix 等原生支持;其他生态用容器与预取近似 |
|---|---|
| 约束 | 构建期间不访问网络,不读取未声明的宿主文件或环境 |
| 收益 | 缓存精确、结果稳定可复现、攻击面收窄 |
| 降级路径 | 准封闭:先预取依赖(允许联网),再断网离线构建 |
典型使用场景
- 可审计发布:所有输入有据可查,产物可被第三方重建
- 大型单仓:多团队共享构建缓存,输入不封闭则缓存污染风险高
- 隔离网交付:金融、政企生产网不直连公网源,构建必须离线完成
- 定位「换台机器就失败」:把隐式的系统依赖逼出来显式声明
- 供应链治理:阻断构建期直连公网拉包,改走受控镜像源
实践方向
实践方向
1. 依赖以内容摘要引用(而非浮动版本)
2. 构建环境与外部网络隔离
3. 缓存键覆盖全部输入(含工具链版本)
4. 构建日志保留输入清单预取与离线构建分离
预取与离线构建分离
pip download -r requirements.txt -d vendor/ # 预取阶段:允许联网
pip install --no-index --find-links=vendor/ -r requirements.txt # 构建阶段:完全离线
npm ci --offline # 依赖来自本地缓存,构建期不出网
bazel build --nofetch //app:image # 缺失输入直接报错,而不是联网拉取输入类别与封闭要求
| 输入类别 | 封闭要求 | 常见违例 |
|---|---|---|
| 依赖包 | 摘要固定 + 预取到本地 | 构建中直连镜像源下载 |
| 工具链 | 版本与哈希固定 | 依赖 runner 预装的 apt 包,版本随镜像漂移 |
| 环境变量 | 清单显式声明 | 隐式继承 shell 或宿主环境 |
| 宿主文件 | 显式声明为输入 | 读取 HOME 下的缓存或配置 |
从准封闭入手:先把依赖下载与构建分离(预取允许联网、构建阶段断网),大多数团队在这一步就能拿到大部分收益。