In-toto:给软件供应链上把密码锁

In-toto:给软件供应链上把密码锁

安全供应链CNCF开源in-toto

数据源:HN + Lobsters

2024年3月29日,微软工程师Andres Freund在排查SSH性能问题时,发现了一个让他后背发凉的事实:广泛使用的压缩库XZ Utils中,藏着一个精心设计的后门。这个后门通过篡改RSA签名验证逻辑,让攻击者可以绕过OpenSSH的认证机制,远程执行任意代码。

更可怕的是,它不是一个简单的「恶意commit」。攻击者化名「Jia Tan」,用两年时间逐步赢得了原维护者的信任,成为项目的共同维护者,然后将恶意代码隐藏在看似正常的测试文件中,通过构建脚本在编译阶段激活。整条供应链上的每一个环节——代码审查、CI测试、打包分发——都没有发现异常。

一个维护者,两年潜伏,整个Linux生态摇摇欲坠。

这就是软件供应链攻击的可怕之处:你不只是信任自己写的代码,你还在信任每一个依赖、每一个构建工具、每一个CI服务器、每一个分发渠道。而攻击者只需要攻破其中一环。

从SolarWinds到xz:供应链攻击的升级路线

供应链攻击不是新鲜事,但最近几年的烈度和复杂程度在陡峭上升:

  • SolarWinds(2020):攻击者入侵了SolarWinds的构建系统,在Orion平台更新包中植入后门(SUNBURST),18,000多家客户——包括美国政府机构——在不知情的情况下安装了被篡改的软件。FBI、国土安全部、财政部无一幸免。

  • CodeCov(2021):攻击者利用CodeCov的Docker镜像构建错误,获取其Bash Uploader脚本的修改权限,在数百家客户的CI环境中窃取凭证和密钥。

  • 3CX(2023):攻击者篡改了金融软件Trading Technologies的安装包,通过3CX分发渠道将后门传播到全球数千家企业。

  • xz Utils(2024):社会工程+技术渗透,攻击者在一个关键开源组件中植入了几乎成功的后门,被偶然发现才避免了灾难。

这些事件的共同模式是:攻击发生在软件被用户使用之前。当你拿到一个安装包、一个Docker镜像、一个npm包时,你怎么确定它从源代码到最终产品的每一个步骤都没有被篡改过?

传统的安全手段——代码签名、哈希校验、TLS传输加密——都在回答「最后一步」的问题:这个包是不是由它所声称的人发布的?传输过程中有没有被修改?但SolarWinds和xz告诉我们一个残酷的事实:如果构建系统本身被攻破,签名和哈希只会忠实地证明一个已经被污染的产品。

那问题来了,签了commit就够了吗?

in-toto的核心设计:Layout的哲学

in-toto对这个问题的回答是:不够。你需要的是端到端的密码学验证链

in-toto由纽约大学Secure Systems Lab开发,2025年4月从CNCF毕业(最高成熟度级别)。它的核心思想可以用一句话概括:用密码学手段记录供应链上每一步「谁做了什么、产生了什么、用的是什么输入」,然后验证这一切是否符合预设的规则。

这个规则叫做Layout。Layout是一个由项目负责人签名的文件,定义了三条核心信息:

  1. Steps(步骤):供应链上必须发生哪些操作——比如「代码必须经过review」、「测试必须通过」、「构建必须在CI中完成」。
  2. Functionaries(执行者):谁有权执行每一步——通过公钥绑定身份。构建步骤只能由CI服务器执行,代码review只能由指定的reviewer完成。
  3. Inspections(检查):验证时执行的独立检查命令——不依赖于任何人提供的元数据,而是验证者自己运行。

每当供应链上的一个步骤被执行,就会生成一条Link元数据——记录了这一步的命令、输入材料(materials)的哈希、产出物(products)的哈希、执行环境的信息。Link由执行者签名。

验证时,in-toto收集所有Link文件,对照Layout逐一检查:步骤是否齐全?执行者是否是授权的那位?每个步骤的输入是否正好是上一步的输出?材料和产物的哈希是否匹配?

in-toto元数据流程示意图

这就像是给软件供应链建立了一条密码学「物证链」(chain of custody)。在法庭上,证物的每一次转手都必须记录在案,任何断链都会导致证据无效。in-toto的逻辑完全相同——如果Layout要求「构建之前必须通过代码审查」,但Link文件中找不到对应审查记录,或者审查者的签名不匹配,整个验证就失败了。

in-toto还提供了一套Artifact Rules来控制文件的行为:

  • CREATE <pattern>:这一步必须创建某个文件
  • DELETE <pattern>:这一步必须删除某个文件
  • MODIFY <pattern>:这一步必须修改某个文件
  • ALLOW / DISALLOW <pattern>:允许或禁止操作
  • MATCH ... FROM <step>:将这一步的材料与上一步的产物绑定——这是串联步骤的关键

以xz后门为例,如果xz项目使用了in-toto,Layout可以要求:

  • 源代码的所有改动必须由至少两名reviewer签署Link
  • CI构建产物(编译后的二进制)的哈希必须和测试环境中产物完全一致
  • 构建步骤必须使用指定的Docker镜像,Match测试阶段的产物

攻击者在测试文件中隐藏恶意代码的trick会在这里撞墙:测试阶段的文件操作会被Link记录,构建阶段引用的测试文件哈希会与review阶段的不一致——链条断了,验证失败。

从in-toto到Attestation:赢在标准,而非工具

上面描述的Layout/Link模型是in-toto的「经典模式」。但在实际生态中,in-toto以另一种形式获得了更广泛的采用:in-toto Attestation Framework

这是一套更轻量的「Statement + Predicate」格式:Statement描述「什么软件产品」(Subject),Predicate描述「关于这个产品的什么声明」(比如来源、构建方式、测试结果)。一个Statement由生成者签名,可以独立验证。

SLSA(Supply-chain Levels for Software Artifacts)的Provenance格式,本质上就是一个in-toto Predicate。当GitHub Actions生成「Artifact Attestation」时,它输出的就是in-toto格式的签名Statement。当Kubernetes的准入控制器验证镜像来源时,它也在消费in-toto格式的元数据。

in-toto赢了标准战,但输掉了工具战。 它的元数据格式是供应链安全生态的「lingua franca」——被SLSA、Sigstore、GitHub、OpenVEX共同采用。但完整的Layout/Link端到端验证仍然只有少数组织在使用。

这是个微妙的局面:你已经在用in-toto了,只是你不知道。

CNCF安全三件套:in-toto + TUF + Sigstore

理解in-toto的最佳方式,是把它放到CNCF的供应链安全全景图中:

  • in-toto:负责完整性验证。回答「这个软件是按正确的方式生产出来的吗?」通过Layout定义期望的流程,通过Link记录实际发生的事,通过验证比对两者。

  • TUF(The Update Framework):负责更新安全。回答「我下载的这个更新包是合法的吗?」通过多角色密钥管理、签名阈值、防回滚机制保护软件更新系统。TUF和in-toto出自同一个NYU实验室(Justin Cappos),也是CNCF第一个毕业的安全项目(2019年)。

  • Sigstore:负责签名基础设施。回答「谁签的这个东西,什么时候签的?」通过Fulcio(免费证书颁发)、Rekor(透明日志)、Cosign(镜像签名工具)解决了密钥管理的门槛问题。Sigstore让「人人可签名」成为现实。

这三个项目各司其职,组合起来形成了一条完整的防线:

  1. Sigstore为每个步骤提供无摩擦的签名能力(不用管理私钥了)
  2. in-toto定义签名应该证明什么——每个签名对应一个具体的供应链步骤:「我在XX步骤中使用了YY输入产生了ZZ输出」
  3. TUF确保用户下载这些签名和元数据本身不会被篡改

SLSA框架中的in-toto:从Build到Source的扩展

SLSA定义了软件供应链安全的四个等级,从L1(基本的构建元数据)到L4(双人review + 隔离构建 + 可重现)。SLSA v1.0聚焦在构建环节——证明一个二进制确实来自某个源代码、在某个构建平台上、通过某个构建流程生产的。

但构建只是供应链的一环。in-toto的Layout模型覆盖的范围更广:

  • Source Track(正在开发中):源代码管理层面的控制——谁可以push?push前是否需要review?review记录是否被签名?
  • Dependency Track:依赖管理——依赖是如何解析的?SBOM是否准确?已知漏洞是否被记录?
  • Deployment Track:部署环节——镜像进入生产环境前是否经过准入控制?

in-toto社区孵化的项目gittuf正在将这种思维延伸到Git层面:对分支的每一次push、merge都生成签名的reference-state log,确保Git仓库本身的操作历史不可篡改。gittuf已进入OpenSSF孵化阶段。

真实世界的落地:谁在用,怎么用

in-toto的CNCF毕业公告中,提到了几个具有代表性的生产采用案例:

**Datadog(2019年起)**通过in-toto保护其Agent组件的构建和分发管道。作为一个需要部署到客户基础设施中的监控代理,Agent的供应链完整性直接影响客户信任。Datadog使用in-toto记录构建过程中的每一步,并在客户安装时进行验证。

SolarWinds——这是最有戏剧性的案例。经历了2020年的SUNBURST事件后,SolarWinds重新设计了其构建管道,将in-toto纳入核心安全架构。用CNCF毕业公告的原话说:SolarWinds「围绕in-toto重新设计了构建管道」。被蛇咬过的人最怕井绳,SolarWinds的采用本身就是对in-toto最有力的背书。

Autodesk也在生产环境中使用in-toto保护其软件交付流程。

除了直接使用in-toto的组织,更广泛的采用是通过GitHub Artifact Attestations:GitHub在2024-2026年间逐步将其作为公共仓库的默认功能,生成的证明文件完全符合in-toto Statement格式,可以直接达到SLSA Build L2级别。

in-toto社区提供的Witness(证明收集器,自动从CI步骤中抓取证言)和Archivista(证明存储库)大幅降低了部署门槛——不需要手写Layout文件,不需要人工追踪Link元数据。

现实困境:in-toto到底能不能阻止下一个xz?

诚实地说:不能——至少在当前形态下不能。

in-toto解决的是一个「可见性」和「可验证性」的问题。它告诉验证者:供应链上发生了什么。但它不主动阻止任何事情发生。如果Layout本身被恶意修改(项目负责人被钓鱼、密钥被盗),或者某个步骤的记录被伪造(执行者密钥泄露),in-toto的验证仍然会「通过」。

还有一些现实障碍:

复杂度门槛。 定义一份完整的Layout需要理解供应链中的每一个步骤,并用in-toto的规则语言精确描述。大多数中小型开源项目没有资源做这件事。即便是CNCF毕业公告中,也提到Witness和Archivista「大幅降低了开发者摩擦」——这是在承认,裸用in-toto门槛太高。

格式级采用,框架级未采用。 如前所述,生态中的大部分采用是在Statement/Predicate层面(SLSA Provenance、GitHub Attestations),而非Layout/Link端到端验证。而后者的安全保证强度远高于前者。

元数据本身的安全。 Layout和Link文件需要安全分发。如果攻击者可以替换这些文件,验证就毫无意义。这也是为什么in-toto + TUF的组合如此重要——TUF保护元数据的传输安全。

人的问题。 供应链安全本质上是人和流程的问题。工具可以降低验证成本,但不能替代安全实践。xz事件的根源是:一个关键开源项目只有一个精疲力竭的维护者。

本文的素材来自公开信息和社区讨论。如果你对这个话题有更深入的一手经验,欢迎指出文中的不足。

参考链接:

  • in-toto 官方项目与 CNCF 毕业公告
  • SLSA 软件供应链安全框架文档
  • xz Utils 后门事件(Andres Freund 披露)