资讯详情

资讯详情

行业资讯

软件供应链安全落地:SBOM、SCA与SLSA在CI/CD中的协同实践

软件供应链安全这件事2026 年已经不再是要不要做的问题而是做的这些东西到底有没有用的问题。SBOM、SCA、SLSA 这三个词在过去两年被反复提及几乎每一份安全建设规划里都会出现但我见过太多团队的真实状态是流水线里加了一个扫描步骤产出了一堆 JSON 文件堆在某个存储桶里没人看也没人知道该怎么用构建平台号称支持生成 provenance但从来没人在部署前验证过它SCA 一周扫出四千条高危安全团队发邮件研发团队加白名单最后大家一起把这个环节当成噪音源关掉。这不是工具的问题是从一开始就没搞清楚这三个东西各自解决什么问题、边界在哪里、在流水线的哪个位置该扮演什么角色。下面我按概念澄清—单项落地—串联成线—踩坑复盘的顺序把 SBOM软件物料清单、SCA软件成分分析、SLSA软件制品供应链等级这三件事从原理到实操讲透重点放在怎么让它们真正在 CI/CD 里跑起来并且持续产生价值而不是又变成一份应付检查的文档。适合正在做供应链安全落地、被扫描告警淹没、或者准备给构建流水线加卡点的同学参考基础薄弱的也能看懂因为我会把每个概念先翻译成人话。1. 三个词经常被并列提起但它们回答的根本不是同一个问题1.1 先把供应链安全拆成三个可回答的问题我在内部做分享时从来不从定义讲起而是先问三个问题第一我这个制品里到底装了什么第二这些东西里有没有已知的坏东西第三我怎么证明这个制品确实是我用我以为的那份代码、在那台我以为的机器上构建出来的这三个问题分别对应 SBOM、SCA、SLSA。它们之间有依赖关系但绝对不是替代关系。很多人以为上了 SCA 就等于有了 SBOM或者以为有了 SBOM 就自动获得了供应链安全能力这是最常见的认知错位。SCA 是检查动作SBOM 是检查的依据和产物SLSA 是检查结论的可信度担保。换个角度理解如果没有一份准确、完整、机器可读的组件清单SCA 扫描出来的结果就只是基于它自己那套解析逻辑的推测你不知道它漏了什么而如果这份清单本身可以被随意篡改或者你根本不知道它是从哪个版本、哪次构建产生的那么基于它做出的任何安全判断都建立在流沙上。这就是为什么这三个东西必须一起谈单独拎出任何一个都会形成一个看似完整实则漏风的闭环。顺带说一句这三个概念的成熟度差异很大。SBOM 有明确的格式标准落地门槛最低SCA 是商业化最成熟的一块工具最多但效果参差SLSA 是最重的涉及构建基础设施改造推进速度最慢但一旦建成对整条链路的可信度提升也最明显。团队排优先级时不能按哪个词更火来要按我当前最缺哪一环来。1.2 SBOM 的真实定位一份会过期的成分表SBOM 这个词被翻译成软件物料清单我觉得物料清单这个译法反而容易让人误解成一份静态的资产台账。它更像食品包装背面的成分表——但它有一个致命差异食品的成分表在保质期内是稳定的软件的成分表可能在你构建完成的第二天就过时了因为某个你依赖的传递依赖被披露了新漏洞。所以 SBOM 的正确心智模型是某一次构建产物的快照而不是我们公司用了哪些开源软件的清单。这两者的差别决定了你的落地方式快照必须跟着产物走产物在哪儿 SBOM 就在哪儿而台账是集中维护的一旦集中维护就必然和实际产物脱节。我在评审过的一些实现里见过一个典型错误有人建了一个独立的仓库把所有项目的 SBOM 都提交进去每天定时更新。这个做法看起来整齐实际上把 SBOM 和产物之间的绑定关系彻底切断了——你没法回答线上正在跑的这个镜像它的 SBOM 是哪一份。正确的做法是 SBOM 作为构建产物的一部分和二进制、镜像一起进入制品库通过镜像 digest 或者产物哈希建立索引关系。1.3 SCA 的真实定位拿成分表去对风险库SCA 做的事情本质上是一次连接查询左边是组件清单理想情况下来自 SBOM实际上很多工具是自己现场解析出来的右边是漏洞库、许可证库、维护状态库中间靠包标识符匹配。它的输出不是你的软件有漏洞而是你依赖的某个组件版本在某个漏洞库的记录里被标记为受影响。这个区别非常重要因为它直接解释了为什么 SCA 的误报率会那么高。漏洞库的记录是基于版本区间和包标识做的它不知道你到底调用了这个组件里的哪个函数也不知道你用的是不是受影响的那个编译选项。所以 SCA 的结果本质上是一个嫌疑人名单不是定罪书。把嫌疑人名单当定罪书用就会得到阻断一切的荒谬结论最终逼迫团队绕过门禁。我个人的经验是SCA 的价值 80% 取决于你怎么处理它的输出20% 才取决于工具本身的检出能力。市面上主流的几款工具在核心匹配能力上差距没有宣传的那么大真正拉开差距的是去重、优先级排序、修复建议的可用性以及和工单系统的集成顺畅度。1.4 SLSA 的真实定位证明这张成分表没被掉包SLSA 解决的是一个更底层的问题你怎么相信前面那份 SBOM 和那次 SCA 的结果是真的如果你的构建环境里有人能改依赖、能改构建脚本、能往产物里塞东西那么 SBOM 上写的和产物里实际有的可能完全是两回事。SLSA 通过构建溯源provenance也就是一份描述这个产物是怎么来的的签名声明来回答这个问题。它记录的是用了哪个仓库的哪个 commit、构建在哪个平台、用什么构建配置、输入是什么、输出是什么。这份声明由构建平台签名下游可以验证签名并检查内容是否符合预期。这里要特别强调一点也是我在很多场合反复纠正的SLSA 是构建过程的等级不是软件的安全等级。一个通过 SLSA L3 的产物可能是功能上漏洞百出的也可能是代码质量很差的它只保证这个产物确实是由这份源码在这个构建平台上产出的中间没有被人为篡改。把 SLSA 当成安全认证去宣传是典型的误用。1.5 用一张表把三者的边界钉死维度SBOMSCASLSA回答的问题里面有什么这些东西有没有已知风险这个制品是否按声明的方式构建产出物组件清单文件JSON/XML漏洞与许可证报告签名后的溯源声明时效性单次构建快照依赖漏洞库实时变化单次构建快照永久有效主要消费者下游用户、应急响应、审计研发团队、安全团队部署方、制品消费方落地难度低半天能跑通中难在降噪和流程高涉及构建平台改造失败模式生成了但没人保存、没人关联告警过多导致整体被忽略签了但没人验等于没签这张表我建议打印出来贴在工位上。每次有人问我们是不是做了 SBOM 就不用做 SCA 了指着第三行看就行——SBOM 是静态快照SCA 是对着动态漏洞库做的比对两者的时间维度完全不同缺一个都不成立。2. SBOM 落地格式选型、生成时机和那几个必填字段2.1 SPDX 和 CycloneDX 该怎么选目前主流就这两个格式SPDX 出身于许可证合规场景历史更久已经被纳入国际标准体系CycloneDX 出身于安全场景结构更紧凑对漏洞声明、服务依赖、运行时信息的表达能力更强。两者的信息模型有重叠也各有对方没有的字段。我的选型建议非常直接如果你们的主要诉求是给客户交付、应付合规审计、做许可证合规选 SPDX因为它的受众认知度更高法务和安全审计方更容易接受如果主要诉求是内部安全运营、要和 SCA 结果联动、要做 VEX 声明选 CycloneDX因为它原生就有漏洞和 VEX 相关的结构联动起来少走一层转换。但现实情况往往是两边都要。这时候不要试图自己写转换器去双向无损转换——我试过字段语义的差异会导致大量信息丢失尤其是依赖关系图和许可证表达式的复杂情况。比较务实的做法是选定一个作为主格式用于内部流转和存储另一个作为交付格式在需要时按需生成并且明确接受交付格式是有损的。还有一个经常被忽略的点格式版本。两个标准都在持续演进不同版本的 schema 差异不小字段增减、枚举值变化都会影响下游解析。落地时必须在文档里明确锁定版本号并且在生成工具升级时做回归测试否则某天工具默认版本一变下游的解析器直接报错排查起来能耗掉一整天。2.2 生成时机的分歧源码期生成还是构建后生成这个分歧在实际项目里吵得最凶。源码期生成指的是在代码仓库层面跑扫描基于锁文件、包管理器的元数据来推断依赖树构建后生成指的是在构建完成的产物镜像文件系统、二进制、打包产物上做实际探测。源码期生成的优点是快、早、能在 PR 阶段就给出反馈缺点是它推断的是应该装了哪些东西而不是实际装了哪些东西。构建过程中如果有动态安装、有构建脚本临时拉取、有多阶段构建的产物层合并源码期的推断就会漏。构建后生成的优点是准确、反映真实情况缺点是慢而且发现问题时已经构建完了反馈链路长。我的做法是两个都要但用途严格区分PR 阶段跑源码期的轻量扫描目的是快速拦住明显的高危新增依赖这是防增量构建完成后跑基于产物的完整生成产出的 SBOM 才是随制品归档、对外交付的那一份这是保准确。这两份 SBOM 不要混用也不要用源码期的那份去替代产物那份否则应急响应时你会对着错误的组件清单做判断。另外提醒一个实操细节多阶段构建的镜像中间阶段装的编译工具链如果不清理会被完整扫进 SBOM 里导致清单里出现一堆运行时根本不存在的组件既增加体积也制造噪音。要么在最终阶段做彻底清理要么在生成工具里配置排除路径和阶段过滤。2.3 一份能用的 SBOM 至少要有哪些字段我见过很多 SBOM 只有组件名和版本号这种清单在应急响应时几乎没法用。一份能真正支撑安全运营的 SBOM我要求至少包含以下几类信息包的唯一标识符。这是最关键的字段没有它就无法和漏洞库做精确匹配。通用包 URL 这种标识方式比单纯的名字加版本可靠得多因为它包含了生态类型、命名空间、名称、版本能避免跨生态的同名包混淆。内容的哈希值。用于校验完整性也是判断这个组件是不是被替换过的依据。供应方信息。出问题时你得知道该找谁虽然很多开源组件的供应方字段是空的但至少要把能从包元数据里拿到的都填上。依赖关系图。只列平铺的组件清单价值有限有了依赖关系才能回答这个漏洞是通过哪条路径进来的也才能做后续的可达性分析。许可证信息。这块经常被安全团队忽略但对交付型业务来说是硬需求。生成元信息。谁生成的、什么时间、用什么工具、什么版本、对应的产物哈希是什么。缺了这块这份 SBOM 就无法自证来源。覆盖率方面我一般会用一个简单指标来衡量抽取若干个已知的、明确在依赖树里的组件检查 SBOM 里是否存在。如果连这些都找不到说明生成工具的生态支持不全需要补充其他工具做兜底而不是假装清单是完整的。2.4 让 SBOM 保鲜与制品 digest 绑定、随产物归档这一节是我认为整个 SBOM 落地里最重要、也最容易被跳过的一环。清单生成了然后呢正确的链路是这样的构建完成 → 计算产物哈希 → 生成 SBOM 并把产物哈希写进 SBOM 的元信息 → SBOM 随产物一起推送到制品库用产物摘要作为关联键 → 下游拉取产物时可以按摘要反查对应的 SBOM。这样一来任何时候你拿到一个线上运行的镜像都能通过它的摘要立刻定位到当初构建时生成的那份清单。发生新漏洞披露时你的应急响应流程就变成了一次索引查询拿漏洞影响的组件标识去查哪些产物摘要关联的 SBOM 里包含它而不是让每个团队回去翻自己的依赖文件。还有一个动作必须有SBOM 的留存策略要跟着产物的生命周期走。产物在线上跑三年SBOM 就得留三年产物下线了SBOM 可以按合规要求再留一段时间后清理。我见过有团队把 SBOM 存在 CI 的临时工作目录里构建完容器一销毁清单跟着没了等到半年后要做漏洞回溯发现什么都没有。至于保鲜的另一层含义——组件本身在变化但 SBOM 是快照不会自动更新——这个不是靠改 SBOM 解决的而是靠 SCA 持续对着漏洞库重新比对。SBOM 不变漏洞库在变重新比对产生新的结论这才是正确的工作模式。3. SCA 落地怎么从几千条告警里捞出真正要修的那几十条3.1 依赖解析这一关没过后面全是白干SCA 工具的能力上限首先被依赖解析的准确性卡死。解析不准后面的匹配再精确也是错的。我在实际项目里遇到过的问题基本集中在以下几类第一类是锁文件缺失或不一致。很多项目仓库里提交了锁文件但 CI 构建时用的是另一套解析逻辑导致构建实际用的版本和锁文件记录的不一致。这种情况下 SCA 扫的是锁文件实际跑的是别的版本结论自然对不上。解决办法很土但有效在 CI 里加一步校验确认构建时解析出的依赖树和锁文件一致不一致就失败。第二类是私有源和镜像源。企业内部往往有自己的包代理同一个包从不同源拉取元数据可能不同SCA 工具如果配置里没加这些源解析会失败或者解析出错误的依赖。第三类是 vendored 代码也就是把第三方代码直接复制进仓库的做法。这种情况依赖管理器完全看不到SCA 默认也不会扫但代码确实在产物里。需要单独配置目录扫描并且手动维护版本信息。第四类是开发依赖和生产依赖的区分。测试框架、构建工具这些只在开发期用的依赖扫出来的漏洞优先级应该低很多但如果工具不做区分它们会和运行时依赖混在一起直接把告警量翻倍。我在做工具评估时有个必测项准备一个包含多语言、有 vendored 目录、有私有源依赖的样例项目看工具能不能把依赖树完整正确地还原出来。这一关过不了的后面讲的所有降噪能力都是空中楼阁。3.2 漏洞匹配为什么会误报CPE、PURL 与版本区间理解了匹配原理就能理解误报从哪来。早期的 SCA 主要靠 CPE通用平台枚举做匹配这个标识体系的粒度很粗同一个组件在不同发行版、不同打包方式下可能对应同一个 CPE导致把不相关的漏洞关联上来。现在更主流的是用包 URL 这类精确标识配合版本区间判断。但即便是精确标识误报依然存在主要来自三个地方一是版本区间的语义问题。不同生态的版本号规则不一样语义化版本、日期版本、带后缀的发行版版本比较逻辑各有各的坑。有些工具对预发布版本、修订号的处理不够严谨会把不受影响的版本判成受影响。二是同名前缀问题。有些项目会把不同产品线放在同一个命名空间下版本号混排导致版本区间判断出错。三是漏洞库本身的滞后和错误。漏洞库的记录依赖人工和自动化流程维护错误关联、版本区间写错的情况并不罕见。所以我一直强调SCA 结果必须保留人工判定这一环不能全自动阻断。实操上我建议的做法是把工具报出的每一条高危都要求确认受影响的具体函数或调用路径。确认不了的先标记为待定通过 VEX 声明记录下来而不是直接当成必须修复的缺陷。这个动作看起来很繁琐但它能把告警量压到一个团队愿意认真处理的数量级。3.3 三步降噪可达性、VEX、优先级打分降噪是 SCA 落地的核心工作我一般按三步走。第一步是可达性分析。核心问题是这个有漏洞的组件它的受影响代码到底有没有被你的程序实际调用到。Java 生态可以通过字节码调用图分析JavaScript 生态可以通过模块引用图分析其他生态的支持程度参差不齐。可达性分析的结论不是绝对的它会有假阴性和假阳性但把不可达的那部分降级能把告警量砍掉一大半。需要注意的是动态调用、反射、配置驱动的加载这些场景静态分析看不出来所以不可达的结论要留有余地不能作为完全不处理的理由。第二步是 VEX 声明。VEX 是一份机器可读的声明用来表达这个组件确实有漏洞但对我的产品不构成影响理由是某某。常见的理由包括不受影响的代码路径、漏洞所依赖的编译选项未启用、漏洞所依赖的功能未开启、以及有外部缓解措施。把人工判定结果沉淀成 VEX最大的好处是可以自动化地抑制后续重复告警避免每次扫描都要重新人工确认一遍。这是我认为投入产出比最高的一个动作。第三步是优先级打分。不要只看 CVSS 基础分那个分数假设的是最坏情况。实际排序我会综合几个因素漏洞是否已有公开利用代码、是否有修复版本可用、组件是否在运行时依赖里、是否暴露在外部可访问的路径上。把这几个维度做成一个简单的加权打分输出一个本周该修的前二十条比给团队一个四千条的列表有用一百倍。降噪手段主要作用局限性建议落地顺序可达性分析砍掉未调用代码路径的告警动态调用场景不适用第二VEX 声明沉淀人工判定抑制重复告警需要人工投入维护第三优先级打分输出可执行的修复排序依赖准确的运行时信息第一依赖范围过滤排除开发依赖噪音需准确识别依赖类型第一3.4 门禁策略哪些必须阻断哪些只记录门禁设计是个组织问题不是技术问题。我见过最失败的模式是全量阻断只要有高危就卡住合并。结果是一周之内所有人都在找绕过的方法最后门禁形同虚设。我推荐的策略是分层必须阻断新增依赖引入了已知有公开利用代码的高危漏洞且有可用修复版本或者引入的组件许可证与公司政策冲突。这两个条件都满足时才阻断因为这是改一下版本号就能解决的事成本低收益高。告警并要求限期处理存量依赖中的高危漏洞不可达性未确认的进工单系统给一个处理窗口。不要阻断但要可见、可统计。只记录不告警开发依赖中的漏洞、已通过 VEX 声明排除的、无修复版本的。这些进报表不进工单。自动放行低危、无利用条件、不在运行时路径上的。关键原则是阻断条件必须满足可执行、可修复、成本可控三条。做不到这三条的阻断最终一定会被绕过。另外强烈建议给门禁加一个临时例外通道带审批、带过期时间、带记录让团队有正规途径处理特殊情况而不是逼他们去改流水线配置。4. SLSA 落地从我信任这个构建机到我能验证这个产物4.1 SLSA 各级到底在约束什么SLSA 的构建等级我习惯用一句话概括每一级在解决什么L1 是有记录。构建过程有自动化脚本描述产出了溯源声明但这份声明没有任何防篡改能力谁都能改。它的价值是让你开始有意识地记录构建输入是后面所有工作的基础。L2 是有签名且构建在托管环境里。溯源声明由构建平台签名声明里包含源码仓库、commit、构建配置等信息。这一级拦住了最常见的一类攻击在构建完成后替换产物或者在构建过程中插入未经记录的步骤。因为签名由平台持有攻击者要在不破坏签名的前提下改产物难度陡然上升。L3 是构建环境加固溯源不可伪造。这一级的关键在于构建平台的设计不同构建任务之间强隔离构建过程的参数由平台在运行时确定而不允许构建脚本自行修改签名密钥对构建过程不可见。到这一级即使攻击者能控制你的源代码仓库他也很难伪造出一份看起来正常的溯源声明。再往上的等级双人评审、封闭构建等对绝大多数团队来说短期内不现实我不建议一开始就奔着最高级去。我的建议是先用三个月把 L2 做扎实这是投入产出比最高的一档L3 作为一到两年的目标因为它涉及构建平台的选型和改造属于基础设施投资。4.2 一个团队从零到 L3 的现实路径我参与过的一个项目从完全没有溯源到稳定运行 L3花了大约十个月。路径大致是这样的我觉得有参考价值第一阶段第一个月盘点现状。梳理清楚有多少个构建入口、分别跑在哪些环境上、哪些是手工触发的、哪些构建产物会对外发布。这个盘点结果往往会让人吃惊——很多团队以为自己只有一条流水线实际上一查发现有七八个不同的构建方式包括某个人本地打的包直接上传的。第二阶段第二到四个月把所有对外发布的构建统一到托管构建平台上禁用本地构建上传。这一步是组织阻力最大的因为总有人有紧急情况需要本地构建的理由。我的做法是保留一个例外通道走审批、留记录、例外产物打特殊标记并且不能进入正式发布渠道。第三阶段第五到七个月打开平台自动生成溯源声明并且在发布流程里加验证步骤。这一步的坑在于很多团队生成了声明但没有任何地方消费它等于白做。必须在部署环节加一道校验不通过就不允许部署。第四阶段第八到十个月推动构建平台升级到支持更强隔离的形态同时清理构建脚本里那些会动态修改依赖、动态下载资源的部分。这些是通往 L3 的主要障碍。整个路径里技术工作量大概占四成剩下六成是推动组织统一流程、说服各方放弃绕过手段。这个比例要有心理准备。4.3 溯源声明的生成与验证两头都得做生成这一端相对简单主流的托管构建平台基本都能自动生成你需要做的是确认几件事声明里包含的源码信息是否指向正确仓库和 commit、构建参数是否被完整记录、声明是否被正确签名、声明的存储位置是否和产物绑定。验证这一端才是真正的难点也是绝大多数团队的缺口。验证要做的事情是在部署或使用制品之前检查随附的溯源声明确认签名有效、签发者身份符合预期、源码仓库和分支符合预期、构建平台符合预期、声明中的产物哈希和实际部署的产物哈希一致。任何一项不满足拒绝使用。我特别要强调最后那个哈希比对因为这是最容易被跳过的一步。声明是签名了的产物也是从同一个地方下来的看起来天然对应——但中间有太多环节可能出错比如缓存污染、比如同名不同 tag 的镜像被覆盖。必须显式做一次哈希比对这是整条链路上唯一能真正证明声明的就是这个产物的动作。验证策略要写成代码放进部署流水线里而不是写成文档让人去检查。人对文档的执行率在赶进度的时候是零。4.4 自签自证为什么等于没签这是我在评审中最常发现的问题流水线里自己生成一份声明自己用存在 CI 变量里的密钥签名部署时用同一把密钥验证。这一套流程走下来形式上完整了但安全性提升是零——因为任何能修改流水线配置的人都能同时修改生成逻辑和验证逻辑或者直接换掉密钥。要让签名有意义必须满足签名主体和验证主体分离的原则。具体来说签名密钥不能对构建脚本可见由构建平台在受控环境中完成签名验证方要独立于生成方验证的是这个声明确实由那个受信任的构建平台签发而不是这个声明确实由我们自己的流水线签发。如果做不到平台级托管签名退而求其次的做法是用短期凭据通过工作负载身份换取一次性的签名证书证书有效期只有几十分钟绑定具体的构建任务用完即失效并且把签发记录独立存储。这种做法比长期密钥好得多因为长期密钥一旦泄露历史所有声明的可信度全部归零。5. 串进流水线卡点位置、策略代码和一个能跑通的骨架5.1 五个卡点以及每个卡点该拦什么把三件事放进 CI/CD我一般划分成五个卡点每个卡点只做一件事不要混。卡点一提交与合并请求阶段。运行轻量级的依赖扫描只检查新增或变更的依赖不扫全量。拦什么新增的高危且有修复版本的依赖、许可证冲突的新增依赖。这一关必须快超过三分钟就会被抱怨。卡点二构建阶段。生成完整的 SBOM 和溯源声明。这一关不拦截只产出。但要保证产出的东西被正确保存和关联。这里失败通常是因为工具报错被忽略所以要把SBOM 生成成功作为构建成功的前置条件生成失败就构建失败。卡点三制品入库阶段。对产物签名把签名、SBOM、溯源声明一起推送到制品库并建立索引。拦什么签名失败、SBOM 缺失、溯源声明缺失的制品不允许入库。卡点四部署阶段。验证溯源声明、比对产物哈希、检查 SBOM 中是否存在被策略禁止的组件。拦什么声明验证不通过、产物哈希不匹配、存在明确禁止的组件。卡点五运行时阶段。持续对已部署产物的 SBOM 做重新比对新披露的漏洞进来时触发告警。这一关不阻断只做发现和联动工单。这五个卡点的顺序很重要从早到晚越早发现成本越低。但也不要试图把所有检查都塞到最早的那一关那会让开发者体验崩掉。我的分配原则是能在三分钟内做完的放最早需要完整构建产物的放中间需要外部信息的放最后。5.2 一份流水线骨架各家流水线平台的语法差异不小下面这份是结构化示意把关键的步骤和判断条件标出来实际迁移时按平台语法调整即可。stages: - name: precheck steps: - name: dependency-diff-scan # 仅扫描本次变更引入的依赖 run: sca-scan --diff --base main --block-on high --require-fix-available - name: license-gate run: license-check --policy allowlist.yaml --fail-on deny - name: build steps: - name: compile run: build-script - name: generate-sbom # 基于构建产物而非源码生成确保准确性 run: sbom-gen --target ./dist --format cyclonedx --include-hashes # 生成失败即构建失败不允许跳过 continue-on-error: false - name: attach-artifact-hash run: sbom-enrich --sbom sbom.json --artifact-hash $(sha256sum ./dist/app) - name: publish steps: - name: sign-artifact # 使用工作负载身份换取短期证书不使用长期密钥 run: signer sign --artifact ./dist/app --identity workload - name: sign-sbom run: signer sign --artifact sbom.json --identity workload - name: push-with-attestation run: registry push --artifact ./dist/app --sbom sbom.json --attestation provenance.json - name: deploy-verify steps: - name: verify-provenance run: verifier verify-attestation --artifact $IMAGE --policy deploy-policy.yaml - name: verify-hash-match run: verifier assert-hash --attestation provenance.json --artifact $IMAGE - name: policy-check run: policy-engine eval --input sbom.json --rules deploy-rules.yaml # 存在策略禁止组件时失败几个关键点解释一下。generate-sbom的continue-on-error: false是刻意写的很多团队在这里默认容忍失败结果就是某些分支的构建产物长期没有 SBOM事后完全查不出来。attach-artifact-hash这一步是把产物哈希写回 SBOM这是实现产物到清单反查的关键。部署阶段的verify-hash-match是我认为最不能省的一步前面说过原因。5.3 签名与密钥短期凭据是硬要求这块单独拿出来说因为它是整个方案里最容易做错的地方。原则很简单任何长期有效的签名密钥只要它存在于 CI 的变量配置里就应当被视为已经泄露。正确的做法是用工作负载身份流水线运行时向身份提供方证明我是某个仓库的某条流水线换取一个短期有效的签名证书证书里绑定仓库、分支、commit 这些信息有效期通常几十分钟。签名完成后证书自动失效即使被截获也无法用于其他构建。这么做还有个额外好处验证方可以从证书里读出身身份信息判断这个声明是不是由期望的那条流水线签发的。如果用长期密钥所有构建共用一个密钥签名里就丢失了身份信息验证方只能判断签名有效无法判断是谁签的这个判断的价值就大打折扣。密钥轮换也要提前规划。即便是短期证书签发机构的根证书也需要轮换轮换期间新旧根证书要有一段时间的并存期否则会出现旧产物验证不通过的问题。这个坑我在一个项目里踩过升级签发机构配置的当天所有历史产物的验证全部失败部署流水线全线告警。5.4 上线之后怎么衡量它有没有用没有度量的安全建设半年后一定会被质疑价值。我一般会盯几个指标指标计算方式期望方向SBOM 覆盖率有 SBOM 的发布产物数 / 总发布产物数接近 100%溯源验证覆盖率部署时执行了验证的次数 / 总部署次数接近 100%高危漏洞平均修复时长从首次检出到修复上线的中位数持续下降门禁误报率被人工判定为误报的拦截数 / 总拦截数低于 15%例外通道使用率走例外流程的构建数 / 总构建数低于 5%其中我最看重的是门禁误报率。这个指标一旦超过两成团队对门禁的信任就会崩塌接着就是各种绕过手段。所以定期抽样复查拦截记录、根据反馈调整规则比增加新规则更重要。例外通道使用率是另一个健康度指标。用得太少可能是流程太严导致大家不敢用然后去偷偷改配置用得太多说明规则设计有问题。维持在 3% 到 5% 之间我认为是健康的。6. 几个我踩过并反复被问到的坑6.1 清单生成了然后躺在临时目录里这个坑我说过一遍还要再说一遍因为它太普遍了。CI 环境是临时的构建完机器就回收任何没被显式上传的东西都会消失。偏偏很多 SBOM 生成工具的默认行为是输出到当前工作目录而流水线脚本里又没写上传步骤。排查方法很简单随便找一个三个月前的发布产物试着反查它的 SBOM找不到就说明有这个坑。修复方式是在流水线里把 SBOM 上传做成强制步骤并且加一个定期巡检任务抽查最近的发布产物有没有对应的 SBOM缺了就告警。这个巡检比任何文档都管用。6.2 一上来就全量阻断两周后被彻底关掉我见过不止一次这样的剧情安全团队上线门禁第一周全公司范围内阻断了两百多次合并第二周开始有人在群里问怎么绕过第三周流水线配置里多了一行跳过检查的参数第四周这个门禁就名存实亡了。根本原因是把存量问题和增量问题混在一起处理。存量漏洞可能有几千个这些是历史包袱不可能在短期内解决用它们去阻断当前的开发工作只会制造对立。正确的做法是增量严格把关存量单独立项、定计划、给资源、按季度推进。这两条线必须分开用同一套门禁去管一定失败。我推荐的具体做法是设置一个基线把当前所有存量问题打入基线文件门禁只对基线之外的新增问题生效同时给存量问题设定一个逐季度缩减的目标。这样既不会阻塞开发又能持续改善。6.3 多语言项目的依赖识别总是缺斤少两一个仓库里同时有后端服务、前端应用、几个脚本工具语言横跨四五种这是很常见的形态。这种情况下任何单一工具都很难把所有生态的依赖都识别全。表现是 SBOM 里只覆盖了主语言其他语言的组件全部缺失。我的处理方式是组合使用选一个覆盖主流生态最全的工具做主生成器然后针对它支持不好的生态用对应生态专用的工具补充生成最后做清单合并。合并时要按包标识符去重并且保留各自的依赖关系子图。这里有个细节合并后的清单必须重新绑定产物哈希不能沿用合并前的元信息否则反查关系会错乱。另外合并脚本要纳入版本管理并做测试不要写成一次性的临时脚本因为生态在变工具在升级这个合并逻辑是需要长期维护的。6.4 把 SLSA 当成一次性认证项目最后一个坑是最隐蔽的把 SLSA 等级当成一个可以拿到的证书投入资源冲到一个等级然后就不再管了。SLSA 描述的是构建过程的持续性属性不是一次性成果。你的构建平台升级、流水线配置变更、签名密钥轮换、甚至换了代码仓库都可能让实际达到的等级下降。如果验证环节没有持续运行你根本不知道自己什么时候掉回去了。务实的做法是把验证做成常态化的自动化检查定期对最近发布的产物做一次完整验证包括签名有效性、签发者身份、哈希匹配。验证失败就告警而不是等到某次审计时才发现问题。同时把构建平台配置变更纳入变更管理流程任何影响构建环境的改动都要重新评估等级影响。我自己在做这类项目时有个习惯每个季度做一次断网演练式的自查——假设现在有个新披露的高危漏洞我能不能在三十分钟内查出受影响的产物范围、能不能拿出对应的构建溯源、能不能确认这些产物没有被替换过。如果任何一个环节卡住那就是下一季度要补的地方。这个自查比任何指标看板都更能反映真实水平。最后分享一个小技巧不要试图一次性把三个东西都做到完美。先花两周把 SBOM 的生成和归档做扎实这是地基再用一个月把 SCA 的降噪和工单流程跑顺让团队感受到它是帮手不是负担最后再动 SLSA因为它的推进周期最长、组织协调成本最高。顺序反了前面两件事会因为迟迟看不到效果而被放弃后面那件也会因为缺少基础数据而落不了地。

准备好开启您的数字化征程了吗?

专业团队为您量身定制,让您的企业网站脱颖而出

立即咨询