供应链攻击近年来频发,从npm包投毒到安装脚本被劫持,攻击面几乎覆盖了软件交付的每一个环节。尤其在运维场景中,一行类似curl -fsSL https://xxx | bash的命令已经是标准操作,但很少有人认真验证过这段脚本从服务器到达你机器的过程中是否被人动过手脚。Sigstore正是为解决这类信任问题而生的开源项目,它由Linux基金会托管,提供免费、自动化的代码签名与验证服务。这篇文章将围绕Sigstore的组成、签名操作和脚本完整性验证展开,帮助你把它落地到自己的发布流程中。

Sigstore的核心组件与工作原理
Sigstore不只是一个工具,而是一套完整的签名生态,主要由三个部分组成。第一是Cosign,这是命令行工具,负责完成签名、验证、密钥管理等具体操作。第二是Fulcio,一个免费的证书颁发机构,它根据OIDC身份令牌(比如你的GitHub账号)签发短期证书,把代码签名与真实身份绑定。第三是Rekor,一个公开的、可追加写的透明日志系统,所有签名记录都会写入其中,任何人都可以审计某个签名是否被发布过、发布时间是什么。
这套机制与传统PKI相比有明显的差异。传统方式要求开发者自己生成密钥、妥善保管私钥、定期轮换,私钥一旦泄露后果严重。而Sigstore推荐的密钥无关模式中,开发者通过OIDC身份登录,Fulcio自动签发一个有效期通常只有十分钟的证书,签名完成后证书即失效,私钥根本不需要长期保存。就算攻击者想伪造签名,由于所有签名都要登记到Rekor日志中,缺乏对应记录的签名很容易被识别为可疑内容。
理解这一点对后续操作很重要:验证方检查的不仅是签名的密码学正确性,还包括签名者身份是否可信、签名是否存在于公共日志中。这种三重校验让供应链攻击的难度大幅提升。
使用Cosign对软件包和脚本进行签名
先安装Cosign。在Linux上可以直接下载二进制文件:
curl -O -L "https://github.com/sigstore/cosign/releases/latest/download/cosign-linux-amd64" sudo mv cosign-linux-amd64 /usr/local/bin/cosign sudo chmod +x /usr/local/bin/cosign
对于保存在OCI注册表中的容器镜像或软件包,签名非常直接。假设你要签名的镜像是registry.ippipp.com/myapp:v1.0,使用密钥无关模式只需要执行:
# 使用OIDC身份签名,会弹出浏览器让你登录GitHub等身份提供商 cosign sign --yes registry.ipipp.com/myapp:v1.0 # 如果使用自管密钥,先生成密钥对 cosign generate-key-pair # 然后用私钥签名 cosign sign --key cosign.key --yes registry.ipipp.com/myapp:v1.0
对于网络脚本这类普通文件,可以生成一个detached签名(分离式签名),也就是签名与文件分开存放:
# 用私钥对脚本生成签名文件,会生成 install.sh.sig cosign sign-blob --key cosign.key --output-signature install.sh.sig install.sh # 密钥无关模式则需要先把签名登记到Rekor,拿到一个bundle文件 cosign sign-blob --yes --bundle install.sh.bundle install.sh
两种模式各有适用场景。密钥无关模式省心,适合开源项目和个人开发者;自管密钥模式适合企业内部环境,或者无法接入OIDC身份源的CI流水线。企业还可以自己部署Fulcio和Rekor的私有实例,实现内外隔离。
验证签名与脚本完整性检查
签名的价值在于验证。用户拿到脚本和签名文件后,在执行之前先做完整性校验。如果是公钥模式,分发公钥(即cosign.pub)给用户,验证命令如下:
# 验证脚本签名,通过则输出Verified OK cosign verify-blob --key cosign.pub --signature install.sh.sig install.sh
如果是密钥无关模式,验证时指定签名者的身份证书信息。例如验证该脚本确实由某个GitHub仓库的维护者签发:
cosign verify-blob \ --bundle install.sh.bundle \ --certificate-identity=dev@ipipp.com \ --certificate-oidc-issuer=https://github.com \ install.sh
这里有两个关键参数。--certificate-identity指定签名者的身份标识,通常是邮箱或OIDC subject;--certificate-oidc-issuer指定身份提供商地址。Cosign会同时完成三项检查:证书链是否有效、证书中的身份是否与预期一致、签名bundle中的时间戳和Rekor记录是否吻合。任何一项不过关,验证都会失败并返回非零退出码。
把验证嵌入到安装脚本的开头是一个实用技巧。例如在脚本内部加入自校验逻辑,配合系统变量$COSIGN_VERIFY控制是否强制校验,可以让下游用户默认获得保护。对于curl加bash的安装方式,更稳妥的做法是让用户先下载脚本和签名文件,本地验证通过后再执行,避免直接把未校验的内容送进bash解释器。
集成到CI发布流水线
手动签名容易遗漏,最佳实践是把签名步骤放进CI流水线。以GitHub Actions为例,官方提供了sigstore/cosign-installer这个action,配合keyless签名可以在构建任务中自动完成签名,身份自动绑定到触发构建的GitHub身份上:
jobs:
release:
runs-on: ubuntu-latest
permissions:
id-token: write # 必须开启,用于获取OIDC令牌
contents: read
steps:
- uses: actions/checkout@v4
- uses: sigstore/cosign-installer@v3
- name: 签名构建产物
run: |
cosign sign-blob --yes \
--output-signature dist/install.sh.sig \
dist/install.sh
- name: 上传签名文件
uses: actions/upload-artifact@v4
with:
name: signatures
path: dist/*.sig流水线签名要注意几个细节。第一,必须声明id-token: write权限,否则OIDC令牌获取失败。第二,签名动作要放在所有构建步骤之后、产物发布之前,保证签的内容就是最终交付的内容。第三,建议同时把Rekor bundle保存下来并随产物发布,给离线环境的用户提供验证依据。
对于二进制软件包,还可以结合SLSA框架生成出处证明(provenance),用cosign attest附加到镜像或制品上,验证方用cosign verify-attestation检查。这样不仅证明了内容没被篡改,还证明了它是由哪条流水线、哪份源码构建出来的,供应链的可追溯性更完整。
常见问题与注意事项
落地过程中有几个容易踩的坑。首先是密钥无关证书的有效期很短,如果验证发生在签名很久之后,不能仅依赖证书本身,必须依赖bundle中的时间戳和Rekor记录,这也是为什么推荐始终保存bundle文件。其次是Rekor日志虽然公开可审计,但理论上存在隐私顾虑,签名内容中不要包含敏感信息,签名的是摘要而非原始数据,这一点Cosign已经处理好。
另一个常见误区是认为有了签名就万事大吉。签名只保证内容来源可信且未被篡改,不保证内容本身没有漏洞。攻击者如果拿到了你的OIDC身份,理论上也能以你的名义签名,所以身份源本身的安全(比如开启GitHub的两步验证)同样重要。此外,验证逻辑应该采用失败即阻断的策略,不要把验证失败降级为警告,否则整套机制形同虚设。
总的来说,Sigstore把过去只有大公司才能负担的代码签名基础设施免费开放给了所有开发者。从一条脚本到一个容器镜像,加上签名与验证两个步骤,就能堵住供应链攻击中最常见的内容篡改路径。如果你的项目还没有任何签名措施,从Cosign的sign-blob和verify-blob开始上手,成本几乎为零,收益却相当可观。