适用读者:要对"我们依赖的东西是否可信、出事能否追溯"负责的人。
前置:S01-J07(依赖治理)、S01-J08(制品发布)。
版本基线:Nexus Repository 3(示例)、CycloneDX 1.5+、SLSA 1.0 框架。
覆盖旧文:A-1399(制品库治理视角,duplicate_ref 至 S01-J08)。
新增扩展:E-S01-029(SBOM / 依赖扫描 / 许可证)、E-S01-030(制品晋升与不可变版本)。
上一篇:S01-A01 · 团队工具链标准化与新人 Onboarding
下一篇:S01-A03 · 文档与知识库平台架构。
1. 业务目标:三个必须能回答的问题
| 问题 | 出现场景 | 必须多久答出 |
|---|---|---|
| "这个 CVE 影响我们哪些服务?" | 漏洞公告刷屏 | ≤ 2 小时 |
| "线上这个 jar 是哪个 commit 打的?" | 线上事故复盘 | ≤ 30 分钟 |
| "这个依赖的许可证能不能商用?" | 法务尽调 / 客户审计 | ≤ 1 天 |
这三个问题的答案,全部来自"制品元数据",而不是代码本身。 这就是制品仓库治理存在的理由。
2. 制品流向:一张图定边界
开发者本地 公司私服(Nexus/Artifactory) 生产
┌──────────┐ mvn deploy ┌───────────────────────────┐ pull ┌──────────┐
│ ~/.m2 │ ────────────────► │ hosted-release(内部库) │ ───────► │ 容器/主机 │
│ 本地仓库 │ │ hosted-snapshot(开发库) │ │ │
└──────────┘ │ proxy-central(公网代理) │ └──────────┘
▲ │ group(聚合入口) │
│ pull └───────────────────────────┘
└───────────────────────────────┘
| 边界 | 规则 | 为什么 |
|---|---|---|
| 本地 → 私服 | 只有 CI 能发 release;人只能发 snapshot | 防止"某人的机器"产出生产制品 |
| 私服 → 生产 | 生产只从私服拉,且只拉固定版本 | 断网可控、可审计 |
| 公网 → 私服 | 只允许通过 proxy 仓库 | 统一出口,可缓存、可阻断 |
| 私服 → 公网 | 默认禁止(no egress) | 防止内部构件泄露 |
红线:生产构建不能直连公网仓库。生产环境拉不到依赖是"可用性问题",拉到了不可信的依赖是"安全问题"——前者可以修,后者可能修不了。
3. 私服拓扑:三类仓库各管什么
| 仓库类型 | 作用 | 关键配置 |
|---|---|---|
| proxy | 代理公网仓库(central、spring、阿里云) | 缓存 TTL、失败重试、白名单 |
| hosted | 存内部构件 | release 与 snapshot 分开;release 禁覆盖 |
| group | 对外聚合入口(proxy + hosted) | 顺序:hosted 优先(内部构件不被公网同名覆盖) |
mirrorOf 的正确写法(与 S01-B03 §7.4 的坑呼应):
<mirror>
<id>corp-group</id>
<url>https://nexus.example.com/repository/maven-group/</url>
<mirrorOf>*,!internal-snapshots</mirrorOf>
</mirror>
写成 mirrorOf=* 会把内部快照仓库也劫持到 group,导致 snapshot 拉不到。
4. 供应链安全三件套
4.1 SBOM(软件物料清单)
| 项 | 说明 |
|---|---|
| 标准 | CycloneDX(JSON/XML)或 SPDX |
| 粒度 | 组件 + 版本 + 许可证 + 哈希(purl) |
| 生成 | cyclonedx-maven-plugin(构建期生成,随制品一起归档) |
<plugin>
<groupId>org.cyclonedx</groupId>
<artifactId>cyclonedx-maven-plugin</artifactId>
<version>2.8.1</version>
<executions><execution><phase>verify</phase>
<goals><goal>makeAggregateBom</goal></goals>
</execution></executions>
</plugin>
判据:SBOM 必须与制品一起存(同一次构建产出)。事后补生成的 SBOM 不可信——它反映的是"现在的依赖树",不是"当时的"。
4.2 漏洞扫描:在哪个环节卡
| 环节 | 工具类型 | 卡点策略 |
|---|---|---|
| 开发本地 | IDE 插件 / dependency-check |
只提示,不阻断(否则没人写代码) |
| CI(合并) | OWASP Dependency-Check / Trivy | CVE 严重度 ≥ 高 → 阻断 |
| 制品入库 | 私服扫描(Nexus IQ / Artifactory Xray) | 入库即扫描,结果挂到制品上 |
| 运行期 | 镜像扫描 | 上线前最后一道 |
只做 CI 不做入库扫描是不够的:CI 扫描的是"这一次构建",入库扫描才能回答"仓库里躺着的 5000 个制品哪些有问题"。
4.3 许可证合规
| 许可证 | 商用 | 传染性 | 处置 |
|---|---|---|---|
| Apache-2.0 / MIT / BSD | ✅ | 无 | 放行 |
| LGPL | ✅(动态链接) | 弱 | 记录,法务确认 |
| GPL / AGPL | ⚠️ | 强(AGPL 含网络服务) | 默认阻断,需评审 |
| 商业许可 | 视合同 | — | 登记授权数量 |
实践:把许可证清单纳入 SBOM,CI 里配一份"禁止清单"(license-maven-plugin 可做)。AGPL 在服务端软件里尤其危险——你提供网络服务,就触发开源义务。
5. "某个 CVE 影响我们哪些服务"——可执行路径
① SBOM 库(所有制品 × 组件 × 版本)
▼ 查受影响的 groupId:artifactId:version 范围
② 制品 → 服务映射(部署清单 / 镜像 label / 服务目录)
▼
③ 受影响服务清单 + 负责人 + 当前版本
▼
④ 修复路径:升级到哪个版本 / 是否有替代库 / 能否临时隔离
这套路径能不能在 2 小时内跑完,取决于两件事:
| 前提 | 没有会怎样 |
|---|---|
| SBOM 与制品同存 | 只能靠 mvn dependency:tree 逐个服务跑(几十个服务 = 一天) |
| 制品↔服务映射 | 知道"哪个 jar 有洞",但不知道"哪个线上服务在用" |
这两件事都属于"平时没人做、出事时最想要"的投入。
6. 制品晋升与不可变版本(E-S01-030)
6.1 晋升(promotion)而不是重建
| 做法 | 风险 |
|---|---|
| 重建(每个环境各打一次包) | 每次构建都可能引入差异(依赖漂移、时间戳、插件版本)→ "测试通过的包"和"上线的包"不是同一个 |
| 晋升(同一个制品从 test 仓库晋级到 prod 仓库) | 无差异;可追溯 |
原则:一次构建,多处晋升。同一份 sha256 从 dev → test → prod。
6.2 不可变版本
| 规则 | 实现 |
|---|---|
| release 制品禁覆盖 | Nexus:hosted-release 关闭 redeploy |
| 制品带哈希校验 | 私服客户端校验 sha256 |
| 签名链 | GPG 签名(S01-J08)+ 构建证明(SLSA) |
SLSA 分级(简版):
| 级别 | 要求 | 现实起点 |
|---|---|---|
| L1 | 有构建脚本、有来源说明 | 大部分团队在这里 |
| L2 | 托管 CI + 生成来源证明 | 主流目标 |
| L3 | 加固构建环境(隔离、不可篡改) | 大型组织 |
建议:先做到 L2(CI 构建 + 生成 provenance),不要一上来追 L3。
7. 故障域与可观测
| 故障 | 影响 | 缓解 |
|---|---|---|
| 私服宕机 | 全员/全线构建失败 | 本地仓库预热、group 兜底、只读副本 |
| proxy 缓存污染(拉到被篡改的包) | 供应链攻击 | 哈希校验 + 白名单 + 定期校验 |
| 证书过期(私服 HTTPS) | 突然全线失败 | 到期监控 + 双证书轮换 |
| 磁盘写满(制品堆积) | 私服不可写 | 保留策略(每库保留 N 个版本)、监控 |
| 权限过宽(人人可发 release) | 未审制品入生产 | RBAC + CI 专用账号 |
观测项:私服探活(mvn dependency:get 定时)、制品入库量、扫描阻断率、许可证阻断清单变化。
8. 迁移与回滚
| 阶段 | 动作 | 回滚 |
|---|---|---|
| 现状盘点 | 列出所有依赖来源(含直连公网的漏网之鱼) | — |
| 收口 | 强制 settings.xml 走 group 仓库 |
保留直连配置 1 个月 |
| 扫描接入 | 先"只报告不阻断" | 关掉阻断开关即可 |
| 阻断 | 高危 CVE 与禁止许可证阻断构建 | 降级为警告(需评审) |
| 晋升制 | 一次构建多处晋升 | 回退到"每环境重建" |
回滚原则:每一层都要有"开关"(开关放在配置里,不要写死在 CI 脚本里)。
9. 验收条件
| # | 验收项 | 判据 |
|---|---|---|
| 1 | 生产构建无公网出站 | 网络策略证明 + 一次断网构建通过 |
| 2 | SBOM 与制品同存 | 随机抽 10 个制品,SBOM 齐全且版本一致 |
| 3 | CVE 追溯 | 给定一个 CVE,2 小时内产出受影响服务清单 |
| 4 | release 不可覆盖 | 尝试重发同版本 → 被私服拒绝 |
| 5 | 许可证阻断 | 引入 AGPL 依赖 → 构建失败并给出说明 |
10. 自测(架构师版)
- 为什么"生产构建直连公网仓库"是安全问题而不是可用性问题?
- SBOM 为什么必须与制品同时生成、同时存放?
- "重建"与"晋升"的差别,会在什么场景下暴露成线上事故?
- CI 扫描与私服入库扫描,各解决什么问题?只做一个会漏什么?
- 团队现在处于 SLSA 的哪一级?升到 L2 的最小动作是什么?
11. 下一篇
S01-A03 · 文档与知识库平台架构
依赖可信之后,下一个"长期资产"是文档。下一篇基于旧文 A-1867(docsify + Git + 码云 Pages + 自建服务器)的真实迁移史,比较 docsify / VitePress / Docusaurus / Astro 四种方案在搜索、权限、成本、构建时长上的取舍,并给出 URL 迁移与 SEO 回滚方案。
本篇属于系列《S01 · 开发环境与工具链》,架构师层第 2 篇。旧文关联:A-1399(duplicate_ref);扩展点 E-S01-029/030。