Sigstore实战指南:无密钥签名与透明日志验证在软件供应链安全中的应用
1. 项目概述为什么Sigstore是开发者的“安全必需品”如果你是一名开发者尤其是负责CI/CD流水线、容器镜像发布或者开源软件维护的那么“签名”和“验证”这两个词一定让你又爱又恨。爱的是它们是软件供应链安全的基石恨的是传统的基于PGP或X.509证书的签名方案密钥管理复杂得让人头疼——私钥丢了怎么办过期了怎么轮换团队成员离职了密钥怎么回收这一系列问题让很多团队的安全实践停留在纸面上。这就是Sigstore要解决的核心痛点。它不是一个单一的工具而是一套由Cosign无密钥签名、Fulcio短期证书颁发机构和Rekor透明日志组成的开源项目生态系统。它的目标极其明确让软件签名和验证变得像git push一样简单、自动化并且默认安全。我经历过从手动管理GPG密钥到全面拥抱Sigstore的整个过程可以负责任地说这不仅仅是工具升级更是安全范式的转变。“无密钥签名”听起来有点反直觉没有密钥怎么签名这里的“无密钥”指的是你开发者不需要长期保管那个令人担惊受怕的私钥。Sigstore利用OpenID ConnectOIDC进行身份认证比如用你的GitHub或Google账号然后由Fulcio CA为你签发一个极短有效期通常只有10分钟的代码签名证书。你用这个临时证书签名签完即焚私钥立即丢弃。签名记录则被不可篡改地记录在Rekor透明日志中供全世界公开验证。整个过程你手里从来没有长期有效的密钥彻底告别了密钥泄露和管理的噩梦。2. 核心概念与架构深度解析2.1 Sigstore“三驾马车”是如何协同工作的要玩转Sigstore必须理解它的三个核心组件是如何像精密齿轮一样咬合在一起的。很多人一开始只关注Cosign的命令但如果不明白背后的流程遇到问题就会一头雾水。Cosign签名的执行者Cosign是我们的“双手”负责执行具体的签名、验证和密钥生成操作。它支持容器镜像、二进制文件、SBOM软件物料清单、Helm Chart等几乎所有你能想到的软件制品。它的核心创新在于将签名作为OCI开放容器倡议镜像的一部分存储与你的软件制品紧密绑定而不是分离的签名文件。Fulcio临时的信任锚点你可以把Fulcio想象成一个“快闪”证书颁发机构。它的工作流程是这样的你开发者通过OIDC提供商如GitHub登录认证。认证成功后你的OIDC身份令牌会发送给Fulcio。Fulcio验证令牌有效后当场为你生成一个公私钥对并用其私钥为你签发一个X.509代码签名证书。这个证书里包含了你的邮箱来自OIDC身份和用于验证的OIDC Issuer URL。这个证书的公钥部分和你的身份信息被绑定而私钥仅返回给你一次用于接下来的签名操作随后建议立即销毁。证书的有效期极短通常只有10-20分钟过期即失效。这个设计妙在哪里它用短期证书的“一次性”解决了长期密钥管理的“持久性风险”。攻击者即使截获了证书也几乎无法在有效期内利用。Rekor不可抵赖的透明账本签名动作本身需要被记录和审计。Rekor就是一个全局的、仅追加的透明日志类似证书领域的CT日志。当你用Cosign签名时签名产生的“证据”包括证书、签名本身、制品摘要等会作为一个条目提交到Rekor。这个条目会被哈希并纳入默克尔树最终获得一个包含时间戳的“包含证明”。任何人都可以查询Rekor验证某个签名是否在特定时间之前存在。这解决了“可验证性”和“不可抵赖性”防止签名者事后否认。注意这里有一个关键理解点。我们常说的“无密钥签名”其“无密钥”体现在Fulcio颁发的短期证书上。Cosign也完全支持传统的本地密钥对cosign generate-key-pair模式那种模式下你就需要自己管理私钥了。本文重点在于“无密钥”流程这也是Sigstore的主推优势。2.2 透明日志验证到底在验证什么透明日志验证是Sigstore区别于传统方案的王牌功能。验证者不仅检查签名本身的密码学正确性即用公钥解密签名摘要匹配文件摘要还要进行以下关键检查证书有效性验证检查签名时使用的证书是否由可信的Fulcio根证书签发且是否在有效期内虽然签名时有效但验证时肯定已过期这是正常情况。身份绑定验证检查证书中声明的OIDC身份如emailexample.com是否与验证者期望的身份一致例如验证者只信任来自某个特定GitHub组织的提交者。日志包含证明验证这是最关键的一步。验证者会去Rekor日志中查找该签名条目的“包含证明”确认该签名记录确实在声称的时间被全球公认的日志所收录且日志本身是完整、未被篡改的。这可以防止攻击者用一个非法但密码学正确的签名例如用窃取的短期私钥签的进行欺诈因为那个签名没有被记录到公共日志里。简单来说透明日志验证把信任从“单一的密钥”扩展到了“可公开审计的全局系统”。它回答的问题是“这个签名是否在有效期内由声称的人发出并且这个行为已被永久公开记录”3. 三步实战从零到一完成无密钥签名与验证理论讲完我们上手实操。以下步骤假设你已经在本地安装了cosign命令行工具版本建议v2.0.0以上并且有一个可以推送的容器镜像例如docker.io/yourusername/myapp:v1.0以及一个GitHub账户。3.1 第一步配置环境与身份认证无密钥签名的第一步是身份认证。Cosign默认使用OIDC对于个人开发者最方便的是使用GitHub Actions的环境或本地交互式登录。对于本地开发测试交互式流程# 设置使用Sigstore的公网实例默认就是显式设置更明确 export COSIGN_EXPERIMENTAL1 # 实际上对于无密钥签名cosign在需要时会自动打开浏览器进行OIDC登录。 # 但我们可以先通过一个命令测试并获取身份令牌 cosign generate-key-pair # 这个命令会触发OIDC流程但我们现在不用它生成长期密钥只是看流程更直接的方式是在签名命令中触发。不过在动手前我们需要理解一个关键环境变量COSIGN_EXPERIMENTAL1。这个标志在Cosign v1.x时代用于启用无密钥签名等新特性在v2.x中无密钥签名已成为主流但一些教程仍保留它以确保兼容性。我的建议是如果你用的是较新版本可以不用设置但设置了也无妨。关键准备你的容器镜像确保你的镜像已经构建并推送到一个可公开访问或你的身份有权限的容器注册中心如Docker Hub、GHCR、ACR等。我们以docker.io/yourusername/myapp:signed-test为例。3.2 第二步执行无密钥签名这是核心步骤。我们将为镜像打上签名并将证据上传到Rekor。# 使用你的GitHub账户进行无密钥签名 cosign sign docker.io/yourusername/myapp:signed-test执行这条命令后Cosign会尝试发现镜像的摘要Digest。打开你的默认浏览器跳转到GitHub的OIDC授权页面。你授权后GitHub会颁发一个短期身份令牌给Cosign。Cosign将该令牌发送给FulcioFulcio验证后颁发一个短期代码签名证书和对应的私钥。Cosign使用这个临时私钥对镜像摘要进行签名。签名完成后Cosign会将签名材料签名本身、证书、镜像摘要等打包成一个“Bundle”并作为OCI镜像的附件Attachment推送到同一个注册中心。同时它也会将一条记录提交到公网Rekor实例https://rekor.sigstore.dev。最后Cosign会明确提示你临时私钥已被销毁实际上是在内存中生成使用后丢弃。命令行输出解读成功后会看到类似如下信息Generating ephemeral keys... Retrieving signed certificate... Your browser will now be opened to: https://oauth2.sigstore.dev/auth/auth?... Successfully verified SCT... signing with ephemeral certificate issued by ... Pushing signature to: index.docker.io/yourusername/myapp注意其中“ephemeral”临时的和“Successfully verified SCT”成功验证证书时间戳这些关键词它们印证了无密钥流程和透明日志的参与。实操心得第一次运行时浏览器授权可能会有点慢这是正常的。确保你的容器注册中心如Docker Hub已经通过docker login登录过因为Cosign需要权限来推送签名附件。如果遇到网络问题导致Rekor提交失败签名可能仍会推送到注册中心但透明日志验证会失败。此时可以尝试重试或使用cosign sign --tlog-uploadfalse先跳过日志上传不推荐用于生产。3.3 第三步进行透明日志验证签名完成后任何人包括你自己都可以验证这个镜像。基础验证cosign verify docker.io/yourusername/myapp:signed-test这个命令会从注册中心拉取镜像的签名附件。提取出签名证书。验证证书是否由Sigstore公网Fulcio签发。自动查询Rekor透明日志验证该签名条目是否存在并有效。输出验证结果包括签名者的身份信息来自OIDC的邮箱。输出示例Verification for index.docker.io/yourusername/myapp:signed-test -- The following checks were performed on each of these signatures: - The cosign claims were validated - Existence of the claims in the transparency log was verified offline - The code-signing certificate was verified using trusted certificate authority certificates [{critical:{identity:{docker-reference:index.docker.io/yourusername/myapp},image:{docker-manifest-digest:sha256:...},type:cosign container image signature},optional:{Bundle:{SignedEntryTimestamp:...,Payload:{body:...,integratedTime:1681234567,logIndex:123456,logID:...}},Issuer:https://github.com/login/oauth,Subject:youremailexample.com}}]注意输出中的关键信息The existence of the claims in the transparency log was verified offline这表明透明日志验证已成功可能是离线验证了之前缓存的包含证明。optional字段中的Issuer和Subject这就是签名者的OIDC身份GitHub和你的邮箱。integratedTime和logIndex这是该签名被记录到Rekor的时间戳和日志索引号是透明性的直接证据。进阶验证指定信任的身份在CI/CD流水线中你通常只信任来自特定团队或组织的签名。这时可以使用--certificate-identity和--certificate-oidc-issuer参数进行严格校验。cosign verify \ --certificate-identityyouremailexample.com \ --certificate-oidc-issuerhttps://github.com/login/oauth \ docker.io/yourusername/myapp:signed-test如果身份不匹配验证就会失败。这确保了镜像不仅被有效签名而且是被“正确的人”签名。4. 集成到CI/CD流水线让签名验证自动化手动签名验证只是演示真正的价值在于自动化。以下以GitHub Actions为例展示如何在你推送镜像后自动签名并在部署前自动验证。4.1 在GitHub Actions中实现自动签名你需要一个具有仓库写入权限的GitHub Personal Access TokenPAT并作为仓库机密如DOCKERHUB_TOKEN和SIGSTORE_TOKEN不对于无密钥关键是利用GitHub Actions自身的OIDC令牌存储。实际上在GitHub Actions环境中工作流本身可以通过GITHUB_TOKEN获得一个具有特定身份的OIDC令牌。Cosign可以直接利用这个令牌无需额外配置PAT进行OIDC登录。这是最安全的方式。# .github/workflows/build-and-sign.yml name: Build, Push and Sign Container Image on: push: tags: - v* jobs: build-and-sign: runs-on: ubuntu-latest permissions: id-token: write # 这是关键允许作业请求OIDC令牌 contents: read steps: - name: Checkout code uses: actions/checkoutv4 - name: Set up Docker Buildx uses: docker/setup-buildx-actionv3 - name: Log in to Docker Hub uses: docker/login-actionv3 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Extract metadata for Docker id: meta uses: docker/metadata-actionv5 with: images: yourusername/myapp tags: | typesemver,pattern{{version}} typeref,eventtag - name: Build and push Docker image uses: docker/build-push-actionv5 with: context: . push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} - name: Install Cosign uses: sigstore/cosign-installerv3 - name: Sign the container image with Sigstore env: # 使用GitHub Actions环境自带的OIDC令牌无需COSIGN_EXPERIMENTAL TAGS: ${{ steps.meta.outputs.tags }} run: | for tag in ${TAGS}; do cosign sign --yes ${tag} done注意permissions块中的id-token: write这赋予了工作流获取JWT OIDC令牌的能力。当cosign sign在Actions环境中运行时它会自动检测并使用这个令牌向Fulcio证明自己是来自这个特定GitHub仓库的工作流证书中的Subject字段通常会包含仓库URL等信息。4.2 在部署流水线中实现自动验证在部署侧可以是另一个仓库或同一个仓库的部署工作流你需要验证镜像签名后再拉取运行。# .github/workflows/deploy.yml name: Verify and Deploy on: workflow_dispatch: # 手动触发或由其他事件触发 inputs: image-tag: description: Image tag to deploy required: true jobs: verify-and-deploy: runs-on: ubuntu-latest steps: - name: Install Cosign uses: sigstore/cosign-installerv3 - name: Verify container image signature run: | IMAGE_REFdocker.io/yourusername/myapp:${{ github.event.inputs.image-tag }} cosign verify \ --certificate-oidc-issuerhttps://token.actions.githubusercontent.com \ --certificate-identity-regexp^https://github.com/yourorg/yourrepo/.*$ \ ${IMAGE_REF} # --certificate-identity-regexp 用于匹配工作流的身份。身份通常是类似 # https://github.com/yourorg/yourrepo/.github/workflows/build.ymlrefs/tags/v1.0 # 使用正则表达式可以灵活匹配不同分支或标签触发的构建。 - name: Deploy to environment run: | # 只有签名验证通过后才会执行到这里 echo Signature verified! Proceeding to deploy... # 你的实际部署命令例如 kubectl set image ...这个验证步骤是部署流程的守门员。任何未经正确签名或签名者身份不符的镜像都无法通过验证从而阻止了潜在恶意镜像的部署。5. 常见问题排查与实战经验在实际迁移和操作中你肯定会遇到各种问题。以下是我和团队踩过的一些坑和解决方案。5.1 签名验证失败原因分析与解决错误现象可能原因解决方案ERROR: no matching signatures1. 镜像根本没有签名。2. 签名存储在非默认的注册中心或仓库。3. 使用了--tlog-uploadfalse签名但验证时尝试查询日志。1. 使用cosign sign先签名。2. 使用cosign verify --registry指定注册中心或确保COSIGN_REPOSITORY环境变量设置正确。3. 验证时加上--insecure-ignore-tlog仅用于测试不推荐生产。error: verifying certificate: x509: certificate has expired or is not yet valid这是正常现象无密钥签名使用的短期证书在签名后几分钟就过期了。验证逻辑是检查签名发生时证书是否有效。无需解决。Cosign的验证逻辑能正确处理过期证书。如果验证因此失败可能是Cosign版本旧或流程有误。error: verifying signature: fetching signatures: getting signature manifest: GET ... MANIFEST_UNKNOWN签名附件以.sig、.att、.sbom结尾的镜像可能被注册中心清理或不存在。确认镜像已成功签名且附件已推送。对于Docker Hub免费用户注意镜像包括签名附件在6个月不活动后可能被清理。WARNING: failed to verify certificate transparency log: ...或error: verifying transparency log无法连接到Rekor服务器网络问题或者该签名条目确实不存在于日志中。1. 检查网络。2. 使用cosign verify --insecure-ignore-tlog跳过透明日志验证以确认基础签名是否有效临时诊断。3. 重新签名并确保--tlog-uploadtrue默认。certificate identity mismatch--certificate-identity或--certificate-oidc-issuer参数与签名证书中的信息不匹配。检查签名时使用的OIDC身份。对于GitHub ActionsIssuer是https://token.actions.githubusercontent.comIdentity是工作流的具体URL。使用cosign verify image不带过滤参数先查看完整的证书身份信息。5.2 性能与成本考量性能签名和验证过程涉及多个网络调用OIDC提供商、Fulcio、Rekor、容器注册中心。在CI/CD中这可能会增加几十秒到一两分钟的时间。建议将签名作为构建流程的最后一步验证作为部署流程的第一步并将其视为必要的安全开销。成本使用公网Sigstore服务sigstore.dev目前是免费的但有速率限制。对于企业级高频使用强烈建议自建Sigstore实例如使用sigstore/scaffolding。自建实例可以避免速率限制。将透明日志Rekor和证书颁发Fulcio完全控制在内网满足合规要求。定制信任根Root CA完全掌控信任链。自建有一定复杂度需要部署Fulcio、Rekor、Trillian日志后端等组件并妥善管理根证书。对于中小团队初期使用公服待规模扩大后再迁移是合理路径。5.3 密钥模式与无密钥模式的选择虽然本文主打无密钥但Cosign依然支持传统的密钥对模式cosign generate-key-pair。如何选择选择无密钥模式如果你追求极简的密钥管理、希望快速上手、团队人员流动频繁、与GitHub/GitLab等集成紧密。这是云原生时代的安全最佳实践。选择密钥对模式如果你处于严格隔离的内网环境无法访问外部OIDC和Fulcio、有现成的硬件安全模块HSM或密钥管理系统KMS可以安全存储私钥、需要满足某些特定合规条款要求长期持有特定密钥。此时你需要自己负责私钥的安全存储、轮换和吊销。个人经验除非有强制的合规要求否则一律从无密钥模式开始。它极大地降低了安全实践的门槛。将管理长期密钥的复杂性转移给受信任的OIDC提供商如GitHub、Google和Sigstore基础设施从安全角度看是责任的转移和专业化对大多数团队是净收益。6. 进阶话题签名SBOM与Blob以及策略执行Sigstore的能力不止于容器镜像。签名软件物料清单SBOM SBOM是软件供应链安全的核心。你可以用Cosign为生成的SBOM文件如SPDX、CycloneDX格式签名确保其真实性。# 假设已有一个sbom.json cosign sign-blob --bundle sbom.bundle sbom.json # 验证SBOM cosign verify-blob --bundle sbom.bundle --signature sbom.json.sig sbom.json在无密钥模式下sign-blob同样会使用OIDC流程。使用策略引擎如Kyverno、OPA进行强制验证 在Kubernetes集群中你可以使用Kyverno策略在准入控制阶段强制要求只有经过特定Sigstore签名验证的镜像才能被部署。例如一个Kyverno策略片段可能要求所有来自docker.io/yourusername/*的镜像都必须有有效的Sigstore签名且签名者身份来自你的GitHub组织。任何尝试部署未签名或验证失败镜像的请求都会被API Server直接拒绝将安全左移到了部署的那一刻。从手动管理GPG密钥的繁琐到如今三行命令完成基于身份的无密钥签名和全局验证Sigstore带来的体验提升是颠覆性的。它把原本需要安全专家精心设计的流程变成了每个开发者触手可及的默认操作。我最深刻的体会是一项安全技术能否成功推广易用性往往比绝对强度更重要。Sigstore通过巧妙的架构设计在保持高安全水准的同时将复杂性隐藏在了后台。对于开发团队来说现在要做的不是争论要不要做签名而是花上半小时按照上面的三步走把这项标配能力集成到你的流水线里。当你看到第一个由CI流水线自动签名、并在部署时自动验证通过的镜像跑起来时你就会明白软件供应链安全的新常态已经来了。