软件供应链攻击已经成为安全领域最受关注的话题之一。攻击者不再直接攻击最终部署的系统,而是在软件生产的某个中间环节下手,比如篡改构建脚本、替换依赖库或者在打包阶段植入恶意代码。由于传统安全防护只关注最终制品,中间环节一旦被污染,最终产物即使通过了杀毒扫描也依然是有毒的。in-toto正是为了解决这个问题而生,它通过密码学手段对软件生产链路上的每一步进行记录和验证,让任何环节的篡改行为都无所遁形。

in-toto的核心设计思想
in-toto的设计灵感来自一个朴素的问题:我们如何知道手中的软件制品,确实经过了我们期望的那些环节,而没有被任何人在中间动手脚?它的答案是全程留痕加签名验证。整个框架由两部分元数据构成:layout和link。
layout文件是供应链的蓝图,由项目负责人制定,用DSL语言描述整条供应链应该包含哪些步骤、每个步骤由谁负责执行、步骤之间的顺序关系如何,以及最终的制品检查规则。可以把它理解成一份带签名的流程规范。
link元数据则由实际执行者生成。每当供应链中的一个步骤完成,比如代码审查、单元测试、构建打包,in-toto的工具就会记录下这个步骤的名称、执行者身份、使用的命令、操作涉及的文件材料以及产出的制品哈希值,并附上执行者的数字签名。这些记录就像流水线上的质检单,一环扣一环。
验证阶段,in-toto会做三件核心检查:第一,layout本身的签名是否可信;第二,收集到的link记录是否覆盖了layout中规定的所有步骤,签名是否有效,且执行步骤的人是否被授权;第三,把所有步骤的材料和制品哈希串联起来验证,如果某个环节的文件被篡改,其哈希值就会与下一步骤记录的输入哈希对不上,篡改立刻暴露。
角色体系与密钥管理
in-toto定义了几类角色,权限分离是它安全模型的基石。项目负责人拥有供应链的定义权,负责创建并签名layout;步骤负责人执行链路中的具体操作,比如构建工程师、测试工程师,各自持有自己的密钥并对自己执行的步骤签名;检查者是最终验证的执行者,通常集成在部署环节或客户端,只需持有用来验证layout签名的公钥即可。
这种设计的关键在于,任何单个角色都无法伪造完整的供应链记录。构建工程师不能冒充测试工程师,项目负责人也无法篡改某个步骤的执行记录,因为每个link都有独立的签名。常见的密钥可以用GPG生成:
# 生成项目负责人密钥 gpg --full-generate-key # 导出公钥供检查者使用 gpg --export --armor alice@example.bbccb.com > alice.pub # 也可以使用ed25519快速生成裸密钥对 in-toto-keygen alice # 会生成 alice 和 alice.pub 两个文件
密钥的保管同样重要。私钥应当存放在受保护的环境,比如CI系统的凭据管理器或硬件密钥中,绝不能提交到代码仓库。一旦私钥泄露,攻击者就能伪造对应的link记录,整个验证体系就失去了意义。
实战:构建一条完整的验证链路
下面通过一个简化项目演示完整流程。假设项目包含三个步骤:代码审查、构建、打包,分别由不同角色执行。
第一步,项目负责人编写layout文件:
"""
示例layout:定义审查、构建、打包三个步骤
"""
READme = {
"_type": "layout",
"keys": {},
"steps": [
{
"name": "review",
"expected_command": ["scripts/review.sh"],
"expected_materials": ["src/*"],
"expected_products": ["src/*"],
"pubkeys": []
},
{
"name": "build",
"expected_command": ["scripts/build.sh"],
"expected_materials": ["src/*"],
"expected_products": ["dist/*"],
"pubkeys": []
},
{
"name": "package",
"expected_command": ["scripts/package.sh"],
"expected_materials": ["dist/*"],
"expected_products": ["release.tar.gz"],
"pubkeys": []
}
],
"inspect": [
{
"name": "check-final-product",
"expected_materials": ["release.tar.gz"]
}
]
}写好layout后需要签名生成root.layout文件。第二步,各角色在自己的环境中执行实际操作,并用in-toto-run记录:
# 构建工程师执行构建并记录link in-toto-run --step-name build --key build_key \ --materials src/ --products dist/ \ -- scripts/build.sh # 打包完成后同样记录 in-toto-run --step-name package --key pack_key \ --materials dist/ --products release.tar.gz \ -- scripts/package.sh
每条命令执行后会在当前目录生成形如build.
in-toto-verify --layout root.layout \ --layout-keys alice.pub \ --link-dir .
验证通过没有任何输出并返回码为0;任何一个环节被篡改,比如有人在构建后偷偷修改了dist目录下的文件,验证都会失败并明确指出是哪个步骤的哪条规则不匹配。这个特性在排查问题时非常实用,能直接定位到被篡改的位置。
与CI/CD流水线及生态的集成
in-toto最大的价值在于与自动化流水线结合。常见做法是把in-toto-run嵌入到流水线的各个阶段,每个阶段使用独立的密钥签名,生成的link元数据随制品一起归档。部署阶段自动执行in-toto-verify,验证失败则阻断发布。
在更广泛的生态中,in-toto是SLSA供应链完整性框架的重要实现组件之一,与TUF更新框架、Sigstore签名体系可以协同工作。例如用Sigstore的密钥无签名机制替代传统GPG密钥,可以大幅降低密钥管理的运维成本。此外,还能与容器镜像工具链结合,在镜像构建过程中记录链路元数据,实现从源码到镜像的全链路可追溯。
需要注意的实践要点:link元数据文件应与制品一同存储且防篡改,比如放进带签名的制品仓库;layout中定义步骤要覆盖所有关键环节,遗漏的步骤就是安全的盲区;定期轮换密钥并审计layout变更记录,防止项目负责人权限被滥用。把这些细节做到位,in-toto才能真正成为软件供应链的一道坚实防线。