关于我们

质量为本、客户为根、勇于拼搏、务实创新

< 返回新闻公共列表

镜像签了名不等于验签生效:Sigstore 与 cosign 这条链的成本在哪

发布时间:2026-10-26

流水线里签了名,线上还是跑着被替换的制品

流水线上早就加了签名这一步,仓库里也能查到签名记录,但一个被替换过的制品还是上了线——因为部署入口从来没有人去验。

把这条链路一段段拆开看,会发现每一环都"做了事":CI 构建完镜像,执行了一次签名动作,签名作为附件推到了仓库;仓库的界面里能看到这个镜像确实带着签名;安全评审时,流水线的截图交上去了,评审通过。唯一没有人做的是:当这个制品被拉起来变成容器的那一刻,没有任何组件去检查它身上那张签名。

于是当某个账号把 myapp:v2.3.1 这个标签重新推了一遍、推上去的内容是另一份构建产物的时候,编排系统照常拉取、照常启动、照常对外提供服务。这里有个特别容易被忽略的细节:标签被覆写之后,仓库里那个签名附件仍然挂在旧摘要上,新内容身上没有对应的有效签名。如果拉取时没有人做校验,这个差异根本不会暴露在任何人的视线里。更糟的一种情况是连附件一起被处理掉——制品身上没有有效签名,部署入口也不要求签名,两件事凑在一起,签名就退化成了流水线里一个耗时的装饰。

签名这件事的开销结构很反直觉。签名动作本身是一次性的、毫秒量级的,代价几乎可以忽略;真正花钱的地方在验证端:验签要在哪几个位置强制执行、失败时是拒绝还是只打一条日志、策略由谁来维护和演进、信任材料过期了怎么办。只做前半段,等于付了流水线时长却没有拿到任何拦截能力。

这条链上的三个角色:谁负责签身份、谁负责记时间、谁负责做密码学动作

Sigstore 是 Linux Foundation 旗下的软件供应链签名与验证开源项目,主要组件包括 Fulcio、Rekor 与 cosign。三者的分工必须先讲清楚,因为很多人把"上了 Sigstore"理解成一个开关,其实它是一组可以分开采纳的能力。

Fulcio 是短期证书签发服务。它基于 OIDC 身份签发 X.509 短期证书,证书有效期很短(常见为十分钟量级,具体以实际部署为准)。它解决的是"拿什么身份去签"这个问题:把长期保管一把私钥这件事,替换成由身份提供方背书的一次性证书。构建流水线拿着 CI 平台签发的 OIDC 身份令牌去换证书,签完名之后私钥可以直接丢掉,因为它马上就过期了。

Rekor 是透明日志。它是一个只追加的日志,记录签名事件的元数据并对外可审计;它基于 Trillian 这类可验证数据结构实现,提供包含证明与签名树头。它解决的是"这件事发生在什么时候、怎么向第三方证明"这个问题。它是这条链上唯一能把时间变成可外部验证信息的组件。

cosign 是签名与验证工具,负责真正做密码学动作。它支持两种签名方式:使用自有密钥(可存放于 KMS 或硬件密钥中)与无密钥方式,后者依赖 Fulcio 签发的短期证书并把记录写入 Rekor。关键在于,这两种方式不是都要用——只用自有密钥签名同样能产出有效的签名,只是身份来自"钥匙"而不是"身份提供方",且没有外部时间戳。选择哪条路,直接决定了后面信任根与轮换那一节要付出的成本。

第一处决策:签的是摘要还是标签,这决定策略能不能站住

容器镜像的标签是可变的,同一个标签可以被重新推送覆盖;摘要才是内容寻址的不可变标识。这条事实看着基础,但它是整套方案能不能成立的地基。

把失效过程推演一遍:签名动作发生时,它绑定的是当时的摘要 A;之后有人推了一份新内容,得到摘要 B,并把 B 挂到了同一个标签上;部署清单里写的是标签,拉取时解析到的是 B;B 身上没有有效签名。这时候验签策略的写法就决定了结果——如果策略判断的是"这个摘要上有没有有效签名",B 会被拦下;如果判断的是"这个标签对应的签名存在不存在"(按标签去校验),B 会被放过。所以验签策略必须锚定摘要而不是标签。

多架构镜像在这里会再叠一层复杂度。一个标签背后通常是一个 manifest list,list 自己有 index digest,下面还挂着每个架构各自的 manifest digest。签名锚定在哪一层,必须在策略里写清楚,否则跨架构场景下会出现"签名明明存在但验不过"的怪事。具体行为以实际版本文档为准,但这一层必须提前在预发环境验证过。

还有一层是范围问题:签"镜像"和签"来源证明"回答的是两个不同的问题。cosign 支持对 SBOM 与 in-toto 格式的证明单独签名与验证,使得构建过程信息也能被独立验证。签镜像只能回答"这份二进制确实是我们发出的";签了来源证明,才能回答"这份二进制是在哪份源码、哪个构建服务、哪条命令下产生的"。供应链合规问的通常是后者。

第二处决策:拿什么身份去签,长期密钥还是短期证书

这一处决策决定了后面所有运维动作的形态。两种路子的差别不在密码学强度,而在"谁能签""怎么轮换""出事之后怎么查"这三件事上。

长期密钥这条路,私钥保管位置与"谁能签"的边界完全等同。放在 CI 变量里,等于任何能读变量的人都能签,包括拿到了只读权限的账号;放进 KMS,签名动作就变成一次需要授权的 API 调用,权限可以收敛、可以审计、可以单独撤销;放进硬件密钥,私钥不出硬件,窃取者拿到的只能是一次签名机会而不是密钥本身。轮换则是纯粹的组织问题:换一次钥匙要通知所有验签侧更新策略,且必须安排一段新旧两把钥匙同时被接受的重叠期。

更麻烦的是吊销。长期密钥没有内建的吊销通道,这一点常被误以为有。私钥泄露之后能做的事只有三件:从验签策略里移除对应的公钥、作废用它签过的制品的后续使用、以及想办法查清它签过哪些东西。第三件恰恰又依赖透明日志。

采用短期证书之前还要先定一个形态问题:用公共实例还是自建。公共实例省掉的是运维,代价是签发与日志的可用性交给了外部网络,且公共透明日志上的记录是对外可检索的。自建则相反:可控、可内网部署、私密性自己把握,代价是三个有状态服务(Fulcio、Rekor 与 TUF 仓库)都要自己保证可用与可恢复。几十到几百个制品的规模下,自建的运维投入通常被低估,因为它不是装完就结束,而是进入了一份长期的可用性清单。

短期证书这条路把这三件事都换了个形态。私钥即抛,不需要保管也不需要轮换,证书自然过期代替了吊销。身份被直接写进证书:Fulcio 会把 OIDC 令牌里的声明写进证书扩展字段,常见包括签发者、仓库、工作流、提交 SHA 与运行环境等(具体字段以实际版本支持为准)。审计时,证书自己就带着"是谁、哪个仓库、哪次工作流",不必回头翻 CI 日志去对账。

但它引入了一个新的依赖,也引入了一个新的攻击面。依赖是身份提供方:IdP 不可用的时候,构建会卡在换证书这一步。攻击面是:任何能拿到该身份的构建都能签,包括 fork 出来的仓库、被提权的工作流、被冒用的服务账号。因此验签那一侧必须同时校验证书里的签发者与具体身份声明,只检查"有证书"几乎等于没检查。还有一个连带后果:证书有效期太短,验签时判定"签名发生在证书有效期内"通常需要依赖日志里的时间记录,这让 Rekor 从可选组件变成了必要组件。

第三处决策:签名存在哪里,跟制品同仓库还是单独放

cosign 的签名以 OCI 制品附件的形式存放在同一个镜像仓库中,与镜像摘要绑定。附件在仓库里也有自己的标识,删除它就等于让镜像回到"无签名"状态。

同仓库存放的好处很实在:权限模型与制品一致,能拉镜像的人就能拉签名;镜像被同步到灾备仓库时签名跟着走(前提是同步工具会搬运附件);不需要额外维护一套存储系统。对于几十到几百个制品、每天数十到数百次构建这个规模,这套默认形态是最省事的。

它的问题在于生命周期被绑死了。镜像被垃圾回收清掉,签名可能被一起清掉;仓库管理员如果有删除附件的权限,那么"删掉签名 + 部署入口不强制验签"就构成一条完整的绕过路径。要靠仓库侧的保留策略、标签不可变配置与删除审计来兜住,而这些是仓库功能,不是签名链自带的保证。

单独存放(对象存储或专用签名库)换来的独立性有三点:保留期与镜像解耦,镜像可以删而签名必须留;可以做成只读归档防止事后改写;访问权限单独收敛。代价也对称:多一套系统要运维、多一次网络往返、镜像复制链路要自己保证签名同步的一致性。

顺带算一笔容量账。每个附件本身很小,但按"每个制品至少一条签名、开启证明与 SBOM 后再各加一条"估算,几百个制品乘以每天几百次构建,入库的对象数量是原本的数倍。仓库的存储增长还好说,真正会被拖慢的是清单遍历、垃圾回收与同步任务的时间,这几项要在上线前估算过。

取舍的判断依据是合规要求本身。如果要求"签名保留期必须长于镜像保留期",那就必须拆出来;如果没有这条要求,先放在同一个仓库,把力气花在仓库的标签不可变与附件删除审计上更划算。另外要注意,不走 OCI 仓库的制品(二进制包、Helm chart 之类)签名会以独立签名文件的形式存在,分发与校验路径必须另行设计,容器那套不能直接套用。

成本真正的所在:验签在部署入口到底有没有被强制执行

前面三处决策都是一次性的设计选择,验证端才是持续投入的入口。验签要在部署入口强制执行:常见做法是在集群的准入控制环节做镜像签名验证,未通过则拒绝部署。可用的落地形式包括基于 Rego 的策略引擎(如 OPA/Gatekeeper)、Kyverno,以及 Sigstore 生态里面向 Kubernetes 的准入控制器实现。

先要把三个可选位置分清。CI 内部自检只能拦住自己流水线产出的东西;仓库入库或同步时的检查能拦住被直接推上来的未签名制品;只有集群准入控制能拦住绕过 CI、直接往集群里提交工作负载的人。三个位置覆盖的攻击面互不重叠,只做第一个等于只防住了自己。

强制执行的第一个成本是高可用。准入控制是整个集群的部署入口,webhook 不可用加上失败策略设为拒绝,后果是全集群无法发布;反过来把失败策略设成放行,则会出现"webhook 挂掉期间所有制品无校验上线"的窗口期,而且这个窗口期是静默的,事后不查根本没有痕迹。两条路各有代价,需要在集群层面明确选一条并写进变更文档,而不是留给默认值。超时设置、失败开关、降级路径都必须提前设计,而不是装上再说。

第二个成本是覆盖面清点。集群里跑的并不只有你自己的镜像:注入进来的边车容器、第三方组件的初始化容器、运维用的临时 Pod、定时任务与批处理任务,还有节点上由守护进程直接拉的镜像,都得被策略覆盖。清点工作做不完,强制验签就会出现"主要业务被拦住、旁路路径随便进"的局面。现实中这一步最容易被跳过,因为它要在集群里逐个命名空间盘一遍,而不是装一个组件就能了事。

第三个成本是阻断与告警之间的选择,这是最贵的一个决策,因为它直接决定这套东西是闸门还是装饰。合理路径是先以告警模式跑一段时间:观察被拦下的对象里有多少其实是正常流量,把策略收紧到只剩真异常,再切到阻断模式。这个观察期本身就是成本,而且要跨足够多的发布周期才能覆盖到低频路径——手工提交的临时 Pod、定时任务、第三方组件的初始化容器。只覆盖 Deployment 会留下明显的空子。

第四个成本是出网依赖。keyless 验签需要访问 Fulcio 与 Rekor,至少也要拿到可离线校验的材料。内网或弱网环境下,这一项必须提前规划,常见做法是缓存信任材料、使用内网镜像,或者把验证所需材料随签名一起保存供离线使用(具体支持情况以实际版本文档为准)。每次部署增加的验签耗时,量级上是若干次网络往返加上密码学运算,需按实际环境验证,它不是毫秒级的签名动作可以类比的。

还有一点:完全阻断之后必须有紧急通道。但绕行机制要设计成有记录、有审批、有自动过期,否则它会很快变成常态路径。

链上的环节 负责的事 不做会怎样 这一环节的持续成本 怎么判断它真的在工作
签发身份把"某次构建"变成一个可验证的身份凭据:短期证书或长期密钥签名有效但无法回答是谁签的,出事后查不到范围短期证书要保障身份提供方可用;长期密钥要承担保管、轮换与吊销的组织成本随机抽一条签名,能说出它对应的仓库、工作流与提交,且与构建记录一致
签名对象锚定不可变摘要,并按需要扩展为来源证明与 SBOM 的独立签名标签被覆写即可绕过,签名跟着漂多架构镜像的摘要层级要在预发环境逐个验证;证明与 SBOM 会让每次构建的签名条数成倍增长人为覆写一个标签后再部署,验签应当失败
签名存放作为 OCI 附件与制品同仓库存放,或单独归档签名被删除或随制品一起被回收,制品退回无签名状态仓库侧的标签不可变、保留策略与附件删除审计;单独存放则多一套系统删掉一次附件,看有没有告警、能不能追溯到操作者
部署入口强制验签在准入控制环节校验签名与策略,未通过则拒绝部署前面全做了也拦不住被替换的制品上线准入控制的高可用与失败开关、告警转阻断的观察期、每次部署的额外耗时、策略维护构造一个未签名制品直接提交到集群,应当被拒绝且有记录
信任根与轮换用 TUF 分发与更新 Fulcio 证书链、Rekor 公钥等信任材料材料过期导致全链验签失败;旧材料被替换无从察觉TUF 仓库的可达性与内网镜像同步、新旧材料的重叠期、历史制品的兼容处理做一次信任材料轮换演练,历史制品与新生制品的验签结果都符合预期
透明日志与时间戳提供只追加、可公开审计的签名记录与包含证明签名时间只能依赖签名方自己的时钟与记录,事后证明不成立有状态服务的存储增长与备份、验签对日志可用性的依赖、公开实例的私密性取舍指定一个历史制品,能独立取出记录并拿到可与签名树头对上的包含证明

策略怎么表达:签发者、仓库、摘要这三个锚点一个都不能少

"要求有签名"是最常见也最没用的策略写法。它等价于说"这份制品身上贴了张纸",至于纸上写的是谁、贴在哪个制品上,一概不管。

能站住的策略至少要同时断言三件事。签发者:证书必须由你指定的 Fulcio 实例签发,而不是任意一个能签发短期证书的服务。仓库:证书里的身份声明必须落在你认可的组织与仓库范围内,而不是任意一个能拿到 CI 身份的仓库。摘要:被部署的制品摘要必须与签名绑定的摘要一致。

三个锚点里,摘要锚定的落地最费事。部署清单里写标签是常态,所以要么让 CD 环节在渲染阶段就把标签展开成摘要,要么准入控制自己解析并比对。做不到这一点的集群,标签覆写攻击依然成立,前面所有投入都白费。

另外要提醒一句:仓库开启标签不可变能减少一类问题,但它不能替代摘要锚定。不可变性是仓库提供的功能,换成另一个仓库、或者跨仓库同步之后,这个保证就不一定还在了。

再进一步,策略里还应该包含对来源证明的验证,而不仅仅验证镜像签名本身。这回答的是"这个制品是怎么来的",比"是谁签的"更靠近供应链合规的实质。

信任根怎么分发,密钥或身份失效之后历史制品怎么办

信任材料包括 Fulcio 的根与中间 CA 证书、Rekor 的公钥,以及相关的日志公钥。Sigstore 使用 TUF(The Update Framework)来分发与更新这些根信任材料。TUF 的价值在于:信任材料的更新本身也是被签名的,能防回滚与防冻结,避免"拿一份根证书写进配置就再也不更新"这种做法。

成本随之而来。TUF 仓库必须可达,内网环境要自建镜像并定期同步;材料会过期,过期就意味着验签失败。这一项在装好的时候无痛,半年之后会突然变成事故。

轮换的实践要点是重叠期:新的根材料上线之后,验签侧要在一段时间内同时接受新旧两份,否则会出现一部分制品验得过、一部分验不过的割裂状态。常见情况里,Fulcio 还会把签发的证书写入证书透明度日志,让 CA 的签发行为本身可被外部监督(具体以实际部署为准)。

密钥与身份失效的处理,两条路的差别在这里最明显。长期私钥丢了,旧签名依然有效——签名本身没被破坏,只是你不能再签新的了。私钥泄露则麻烦得多:没有内建吊销,能做的只有从策略里移除公钥、通知下游、以及查清它签过哪些制品,而其中查清签发范围这一步,没有透明日志几乎做不到。短期证书这边,私钥即抛,不存在吊销问题;但身份被冒用签了东西,要做的不是吊销证书(它早已过期),而是到透明日志里把那条记录找出来并对外说明。

还有一个最容易被漏掉的问题:轮换信任材料或者废掉一把钥匙之后,历史制品会突然验不过。处理办法通常有两条:一是用时间切分,让新策略只约束新制品,这需要有可信时间戳,于是又回到 Rekor;二是保留旧信任材料,并在策略里明确限定它的接受范围。选哪条取决于你要回答的问题是"现在部署的东西可信"还是"过去某次发布的东西可信"。

时间戳与事后审计:没有透明日志时,"事后证明"缺的是什么

Rekor 提供的是只追加、对外可审计的签名记录,基于 Trillian 这类可验证数据结构,提供包含证明与签名树头。把这些能力翻译成能回答的问题,是这样的:某个制品在什么时候被签的、由哪个身份签的、这条记录排在日志的第几位、以及它能不能与某个签名树头对上。第三方可以独立验证,不需要信任你的日志系统。

没有它的时候,缺的恰恰是时间。签名本身不包含可信时间。X.509 证书有有效期,短期证书只有十分钟量级,这本身给出了一个很窄的时间窗口,算是一种弱时间戳;但用长期密钥签出来的东西,签名时间完全依赖签名方自己的时钟与记录。要回答"三月份那次发布用的是哪个镜像、是谁签的",如果没有外部日志,只能翻 CI 日志,而 CI 日志的保留期与可篡改性是另一套问题,且它无法对外自证。

验证一条记录的过程本身也有讲究,它不是"查一下有没有"这么简单。拿到条目之后,要用它自带的包含证明把它与某个签名树头对上,这一步证明了这条记录确实是那棵树的一部分;再往前一步,还可以用一致性证明验证新旧两棵树的先后关系,即"后来的树包含了之前的树",从而排除日志被分叉或回滚。这两步都成立,这条记录才算可以被当作外部证据使用。少了它们,所谓"我在日志里看到了"仍然只是对日志运营方的一次信任。

成本这一侧要算三笔。第一笔是有状态服务的运维:日志只增不减,条目数大致等于签名次数,若同时签 SBOM 与来源证明,每次构建可能产生两到三条,规划存储时按这个量级估算并按实际保留期验证。第二笔是可用性传导:验签依赖日志可用性,日志不可用时准入控制跟着受影响。第三笔是私密性——公共 Rekor 实例上的记录是可被任何人检索的,里面包含仓库路径与摘要;有私密要求的环境要自建并控制访问。

把问题倒过来问:这套东西能答的四句话,和答不了的四句话

判断投入值不值,最实用的办法是把审计方会问的问题先列出来,再对着看哪些能答、哪些答不了。有了完整这条链之后,能站住四句话:这个制品是由哪个构建身份签的;签名发生在什么时间,且这个时间可以被第三方独立验证;当时的内容摘要是什么,与线上跑的那份是否一致;以及这份制品是在哪份源码与哪个构建流程下产生的(前提是来源证明也签了)。这四句话覆盖了大部分"证明线上跑的确实来自内部构建流程"的诉求。

答不了的那四句同样要心里有数。这个制品有没有已知漏洞——这要靠依赖清单与扫描,签名不作答。构建环境的操作系统与工具链是否被污染——这要靠构建侧的可复现性与环境证明,签名链只承载你交给它的那些声明。制品在运行时做了什么——签名到期就失效了,运行时行为是另一套机制的事。以及最容易被忽略的一句:过去某个历史制品现在还能不能被信任——这取决于旧信任材料有没有保留、旧记录还在不在日志里,属于本文前面轮换那一节要解决的问题,而不是签名本身能自动回答的。

把这两组句子摊开对比,投入的优先级就清楚了:能答的四句里,前三句靠"强制验签 + 摘要锚定 + 透明日志",投入集中在验证端;答不了的四句则需要另外三套机制,把它们算进来才是完整的供应链画像。把签名当成其中一项而不是全部,资源分配才不会错位。

这套方案的边界:它证明的是来源,不是内容安全

签名证明的是制品的来源与完整性,不证明制品内容没有漏洞。签过名的镜像照样可以带着一堆 CVE 上线,签名不能替代漏洞扫描与依赖审计。它也不证明构建环境是纯净的,除非你把来源证明也签上,并且证明的生成路径本身是可信的。它更不解决运行时行为——签过名的制品跑起来之后做什么,是另一套机制的事。

还有一个边界值得单独讲:这套方案的强度上限等于验签策略的强度。密码学再强,策略写成"只要有签名就放行",整体强度就是零。真正决定收益的是策略里那三个锚点有没有写全、部署入口有没有真的在拒绝、以及拒绝是不是唯一的默认动作。反过来说,如果部署入口这一环没做,前面 Fulcio、Rekor、cosign 全上了,收益依然是零,而成本一分没少。

本文只讨论制品签名与验签这条链上的设计决策与持续成本;不构成合规结论或安全承诺,不提供报价,不讨论漏洞扫描与依赖审计的具体方法。一万网络深耕 IDC 行业 19 年(成立于 2007 年),在华南、华东、华北及中国香港、海外多地整合自营节点与 BGP 多线资源,提供服务器租用、机柜托管与带宽接入的一体化交付;具体机型、带宽与报价以官网实时信息或咨询确认为准。

这条签名链之外,还有哪些事本文没有覆盖

源码侧的提交签名(把签名链向上游延伸到代码提交)、依赖来源的锁定与校验、可复现构建、运行时行为管控,以及把 SLSA 这类分级框架落到内部流程的组织工作,都不在本文范围内。它们各自有独立的成本结构,也需要独立的篇幅。

制品仓库本身的访问控制、网络隔离与审计能力,本文只在"签名存放"那一节顺带提及,没有展开。自建 Fulcio、Rekor 与 TUF 仓库的具体部署步骤本文也不涉及,那属于安装与运维手册的内容。

关于 21290 这篇文章的技术口径与来源

文中的组件事实来自 Sigstore 及其子组件(Fulcio、Rekor、cosign)的公开项目文档,属于公开可查的通用事实。涉及证书有效期、字段名称、策略引擎行为、离线校验支持情况的部分,一律写成"常见情况"或"以实际版本文档为准",读者落地前应在自己的版本上验证。

涉及耗时与容量的数字,全部以量级与估算口径给出,需按实际环境验证;本文没有引入任何性能跑分、现场环境数据或第三方使用经历。

本文不涉及任何特定厂商产品的横向对比,也不提供服务器选型、通用参数清单与价格信息。结论部分给出的是设计取舍的判断路径,不是可直接复制的落地清单。


上一篇:表越多不代表平台越重:DataHub 的元数据采集频率与血缘粒度怎么定

下一篇:2026 服务器租用自建论坛怎么配:phpBB 与 Flarum 部署资源账、搜索与反垃圾对比避坑手册