业务故事站
P3 Apollo

Bundle 签名与验签:供应链安全

制品不是"一个压缩包传过去"就完了:bundle 自带逐文件 SM3 清单、不可变 digest、SM2 签名与签名者白名单信任锚。看完这 3 个故事,你就明白"验签通过"和"可信"为什么是两回事。

安全合规 平台运维 SM3 SM2 信任锚 共 3 个故事

能 / 不能速览

✅ 这个主题能做
  • 上传文件构建签名 bundle:manifest + 逐文件 SM3 清单 + 不可变 digest + SM2 签名
  • 全链路验签:files_ok / digest_ok / trusted / signature_ok 四项诊断
  • 签名者白名单信任锚:指纹不在白名单(或已禁用)即拒绝应用(403 VERIFY_SIGNER_NOT_TRUSTED)
  • 下载 zip 归档(manifest.json + digest.sig + files/),可离线读回再验
  • 构建时自动把演示签名者占位指纹升级为真实 SM2 公钥指纹并注册白名单
⛔ 这个主题做不了
  • verify 是诊断接口;真正应用时的拒绝逻辑在 Spoke 侧白名单预检(本版验签与白名单在控制面)
  • 演示签名者私钥仅存服务器内存,重启后重新生成密钥,指纹会变化(需重新构建 / 更新白名单)
  • SBOM / 漏洞扫描 / 策略门禁已实现(自研内置模拟,非真实 Trivy/Syft,OPA 未实现),但属于"安全合规"主题,bundle 签名层只管来源与完整性
  • 签名者白名单要求密钥分发生命周期管理,当前只有手动 add / enable / disable

适用角色

  • 应用开发工程师:把制品文件打包成 bundle,填 app / version / bundle_version 并上传文件。
  • 安全合规工程师:验签、维护签名者白名单、评估供应链风险——本主题核心使用者。
  • 平台运维 / SRE:构建与下载制品,配合安全团队完成信任锚演练。

能力速览(能做什么)

构建签名 bundle

POST /bundles multipart 上传文件,生成 manifest(文件清单 + 组件清单)+ 不可变 digest + SM2 签名。

全链路验签

POST /bundles/:id/verify:逐文件重算 SM3、重算 digest、白名单查指纹、SM2 验签,四项结果全绿才可信。

签名者白名单

POST /signers 维护信任锚(name + 公钥指纹 + enabled),指纹不在白名单或已禁用即拒绝应用。

不可变 digest

digest = 内容哈希,是 bundle 的唯一连接键(内容变则 digest 变);version 仅作展示语义。

下载与离线校验

GET /bundles/:id/download 返回 zip(manifest.json + digest.sig + files/),落盘后可读回再校验。

占位指纹自动升级

seed 的 demo-signer 占位指纹在首次构建 bundle 时自动替换为真实 SM3(公钥) 指纹,白名单自洽。

调整指南(怎么调整)

  • 换签名者:构建时表单字段 signer 指定签名者(默认 demo-signer);组件 kind 按文件路径自动识别(config / eval_set / ontology_yaml 等)。
  • 改信任锚:新供应商加入走 POST /signers(name + 公钥指纹);下线走 disable,同名 / 同指纹重复注册会 409 SIGNER_CONFLICT。
  • 改校验粒度:验签四项逐项诊断,哪项红了就定位哪类问题(文件被改 / digest 对不上 / 白名单缺失 / 签名无效)。
  • 改版本语义:bundle_version 建议按发布计划单调递增;bundle digest 变更即新制品,历史 bundle 仍可下载验签。
  • 改分发方式:制品目录(temp/apollo_bundles/<digest>)可整体拷贝到离线环境,配合 zip 下载做二次分发。

做得好的场景

Bundle 签名最擅长"让制品来源与完整性可被证明":
  • 供应链可视:bundle 从"一个包"变成"清单 + 签名 + 指纹",谁打的、含什么、有没有被改,全可查。
  • 防篡改:逐文件 SM3 + digest 双重校验,中间人改一行配置都会被 files_ok / digest_ok 拦住。
  • 防"任意有效签名":白名单信任锚让"验签通过"不等于"可信",从根上堵住 Sigstore 指出的漏洞。
  • 国密合规:SM3 摘要 + SM2 签名,契合信创与国产化要求,离线可验。

限制与不足

以下是明确的边界,使用前先知道:
  • 验签与应用分离:verify 只做诊断;"应用前拒绝"的白名单预检在 Spoke 侧,本版 Spoke 演示为拉取 / 上报。
  • 密钥生命周期:演示签名者私钥仅存内存、重启重建;真实环境需自行管理密钥分发与轮换。
  • SBOM / 漏洞扫描 / 策略门禁在"安全合规"主题:已实现的是自研内置模拟(非真实 Trivy/Syft,OPA 未实现),bundle 签名层不携带漏洞信息。
  • 离线导入回传是骨架:三区双重校验 + 导入报告签名回传(G9)当前为机制骨架,完整流程属 P1 后续。

场景故事

故事 1 第一次构建签名 bundle:占位指纹自动升级
背景
阿强要把 demo-app 的制品打包成 bundle:web.bin + db.bin 两个文件。demo-signer 是 seed 里的占位签名者(占位指纹 demo-fingerprint-placeholder),第一次构建 bundle 时平台会自动把它升级为真实 SM2 密钥指纹并注册进白名单。
传统做法对比
以前制品就是一个压缩包传来传去,没人能证明"这个包到底是谁打的、有没有被改过",线上跑出问题只能回滚瞎猜;现在 bundle 自带文件清单(SM3)+ 不可变 digest + SM2 签名,来源与完整性一目了然。
角色
阿强(应用开发工程师,打包制品)+ 刘经理(安全合规,随后验收信任锚)。
操作步骤
  1. 进入"制品"页(/apollo/bundles),点击"构建 bundle"
  2. 填 app=demo-app、version=1.0.0、bundle_version=1
  3. 上传 web.bin、db.bin(表单字段名即 bundle 内路径)
  4. 提交,观察记录生成
系统响应
构建成功返回 bundle 记录:
POST /api/v1/bundles
{ "code": 0, "data": { "id": 1,
  "name": "demo-app-1.0.0-b1",
  "digest": "sm3:...", "signer_id": "demo-signer",
  "signer_fingerprint": "..." } }
trusted_signers 里 demo-signer 的占位指纹被替换为真实 SM3(公钥) 指纹。
结果洞察
bundle 不可变 digest 是唯一连接键(内容变则 digest 变);manifest 含逐文件 SM3 校验和与组件清单(组件名取文件基名)。签名者指纹自动注册白名单,保证后续验签信任锚自洽。
调整建议
bundle_version 按发布计划填并保持单调递增;文件路径名会决定组件 kind(含 config / eval_set / ontology_yaml 的路径自动归类),跨产品分发时可复用。
动手试一试
登录:admin / admin1,端口 18082。页面路径:制品 → 构建 bundle。输入内容:app=demo-app、version=1.0.0、上传两个小文件。预期结果:返回 id 与 digest;到"签名者"列表看 demo-signer 指纹已从占位变为真实指纹。
限制提示
文件源不能为空(400 BUNDLE_INVALID);文件路径不允许重复 / 绝对路径 / 目录穿越;构建用服务器内存中的演示签名者私钥,重启后密钥重建,指纹变化需重新构建或更新白名单。
故事 2 验签:SM3 清单 + digest + SM2 全链路体检
背景
bundle 构建完,刘经理要对它做一次完整"体检":逐文件 SM3 校验、digest 一致性、白名单信任、SM2 验签,确认这个包可以放心分发到各环境。
传统做法对比
以前校验制品只能"解压、跑起来看看",无法预防中间人篡改——改一行配置文件就神不知鬼不觉;现在一键验签,四项结果全绿才说明包从签名到现在没被动过。
角色
刘经理(安全合规工程师,主导验签与放行决策)。
操作步骤
  1. 在制品列表找到刚构建的 bundle,点击"验签"
  2. 等待校验链执行
  3. 查看 VerifyResult 四项结果
系统响应
验签返回结构示例:
POST /api/v1/bundles/1/verify
{ "code": 0, "data": { "files_ok": true,
  "digest_ok": true, "signature_ok": true,
  "trusted": true, "signer_name": "demo-signer" } }
结果洞察
校验链四步:①逐文件重算 SM3 对照清单(files_ok)②重算 digest 对照 manifest(digest_ok)③签名者指纹在白名单且 enabled(trusted)④用白名单公钥做 SM2 验签(signature_ok)。任何一步失败即拒绝应用。
调整建议
校验结果作为审计凭据留存;下载 zip(manifest.json + digest.sig + files/)后可离线读回再验,适合给内网 / 离线环境做二次确认。
动手试一试
操作:对刚构建的 bundle 点击"验签"。预期结果:files_ok / digest_ok / signature_ok / trusted 全部 true,signer_name=demo-signer。
限制提示
verify 是诊断接口;真正部署时的"应用前拒绝"逻辑在 Spoke 侧白名单预检;trusted=false 时接口返回 403 VERIFY_SIGNER_NOT_TRUSTED(即使 SM2 验签通过也不放行)。
故事 3 签名者白名单信任锚:未知签名者的包被拒
背景
供应链安全演练:假设攻击者 / 外部供应商用"自己的密钥"签名了一个 bundle,SM2 验签技术上能通过,但它的指纹不在 trusted_signers 白名单里——平台必须拒绝。演示方法:把 demo-signer 禁用,再验签之前的 bundle,观察被拒;最后重新启用恢复。
传统做法对比
只验签不查身份的系统里,"任何持有有效密钥的人都能发布",等于把信任全交给密钥分发环节,这正是 Sigstore 指出的"任意有效签名"漏洞;白名单信任锚 = 除了验签,还要求签名者指纹在预置白名单(enabled=true),从根上杜绝。
角色
刘经理(安全合规,演练主导)+ 陈工(平台运维,操作白名单启用 / 禁用)。
操作步骤
  1. 先构建一个 bundle 并验签成功(demo-signer 在白名单)
  2. 到签名者页把 demo-signer 禁用(POST /signers/:id/disable)
  3. 再次对同一 bundle 验签
  4. 观察被拒,再启用(enable)恢复
系统响应
禁用后验签返回:
HTTP 403  VERIFY_SIGNER_NOT_TRUSTED
"签名者指纹 ... 不在可信白名单(enabled=true)中,拒绝应用"
VerifyResult: { "files_ok": true, "digest_ok": true,
  "signature_ok": true, "trusted": false }
重新启用后验签恢复四项全绿。
结果洞察
信任锚语义:SM2 验签通过 ≠ 可信。白名单(signer_id + 公钥指纹)是信任的根,指纹不在白名单或已禁用,一律拒绝应用。这比"验签通过即放行"更贴近真实供应链安全,也是对 Sigstore 结论的落地。
调整建议
白名单模型已支持环境作用域(trusted_signers.environment_id,验签按环境级优先、回退全局);正式环境建议把签发流程与白名单变更纳入审批审计;同名 / 同指纹重复注册返回 409 SIGNER_CONFLICT。
动手试一试
操作:构建 bundle → 验签全绿 → 禁用 demo-signer → 再验签被 403 拒绝(trusted=false)→ 重新启用 → 验签恢复全绿。预期结果:完整走通"信任锚拒绝 / 恢复"闭环。
限制提示
白名单要求密钥分发生命周期管理(换钥要同步更新指纹);演示签名者私钥仅存内存,重启服务器会重新生成新密钥(指纹变化,需重新构建);当前仅 HTTP / 轮询,gRPC / mTLS 属 V2。

常见问题

bundle 和普通压缩包有什么区别?

普通压缩包只有内容;bundle 是"内容 + 清单 + 签名":manifest 记录逐文件 SM3 校验和与组件清单,digest 是内容哈希(唯一连接键),SM2 签名证明来源,白名单证明签名者可信任。任何一项对不上就拒绝。

为什么"验签通过"还不等于"可信"?

验签只证明"这个包确实是持密钥者签的",但持密钥者是不是可信的签发方是另一回事。白名单信任锚把信任收敛到"签名者指纹在 trusted_signers(enabled=true)",堵住"任意有效签名"漏洞。

demo-signer 的占位指纹是怎么回事?

seed 创建的 demo-signer 初始指纹是占位符(demo-fingerprint-placeholder)。首次构建 bundle 时,平台生成真实 SM2 密钥对,用 SM3(公钥) 计算指纹并替换占位值,保证"构建方"与"白名单"自洽。

换了密钥怎么办?

重新构建 bundle 会得到新指纹;若签名者记录已存在,需要更新 trusted_signers 的指纹与公钥(PUT /signers/:id),或删除重建。注意同名 / 同指纹冲突返回 409,白名单变更建议纳入审批。

验签失败可能是什么原因?

按四项对号入座:files_ok=false 文件被改(SM3 对不上);digest_ok=false 内容与 manifest 不一致;trusted=false 签名者不在白名单或已禁用(403);signature_ok=false SM2 验签失败(签名无效 / 公钥不匹配)。

主题小结

一句话:bundle 供应链安全 = 逐文件 SM3 清单 + 不可变 digest + SM2 签名 + 签名者白名单信任锚。核心认知:验签通过 ≠ 可信,白名单才是信任的根。边界:verify 是诊断接口、演示密钥仅存内存重启重建;SBOM / 漏洞扫描 / 策略门禁已实现(自研内置模拟)但属于 安全合规主题