大多数组织明知应签名却仍未实施,AI时代攻击面扩大,未签名镜像带来严重安全风险。
大多数知道应该为镜像签名的组织仍然没有这样做。不是因为他们不认同,而是因为做好这件事的路径太长了。结果是构建了一套基于信任的交付流水线,而没有人能够验证这种信任。
未签名的容器镜像在交付链的每个环节都为攻击者打开了大门。恶意镜像伪装成合法包,等待被不知情的团队拉取。被攻陷的 CI/CD 流水线悄无声息地将篡改后的产物注入生产构建,没有任何密码学证据证明修改过。窃取的凭据让坏角色可以冒充可信的发布者。即使在单个组织内部,不一致的实践也意味着有些团队签名,有些团队完全跳过这一步,在信任链中留下无人绘制的缺口。所有这些问题都因基础镜像的继承而加剧。每个容器镜像都继承其父镜像的安全态势,因此一个被攻陷的基础镜像可以在任何人注意到之前传播到数十个下游服务。
"扫描本质上是反应式的。一种告诉你里面有什么。另一种告诉你是否可以信任它。"
扫描本质上是反应式的。它回答的是"这个镜像中存在哪些漏洞?"它无法回答随着产物越来越难检查而变得更加重要的问题:"这是谁构建的,自它离开构建系统后是否被修改过?"这正是密码学签名的领域,它提供主动的来源证明。两者是互补的,不可互换。一种告诉你里面有什么。另一种告诉你是否可以信任它。
工作负载的变化比工具更快。模型权重、训练数据集、推理运行时和 Agent 工具链越来越多地作为 OCI 产物发布。一个 pickle 化的 PyTorch 检查点本身没有 CVE 可以匹配。.safetensors 等更安全的序列化格式去除了代码执行路径,但它们无法说明是谁生成了这些权重,或者它们是否是你打算加载的那些权重。训练权重集没有漏洞数据库, registries 运行的 CVE 和 SCA 扫描没有东西可以比对。
这不是理论层面的。2024 年 2 月,JFrog 研究人员在 Hugging Face 上发现了一个恶意 PyTorch 模型,它在加载时立即打开一个反向 shell,滥用 pickle 的 __reduce__ hook 在 torch.load() 上执行任意代码。他们的分析在这个 hub 上发现了大约 100 个携带真正恶意负载的模型。没有 CVE 触发,因为没有东西可以让 CVE 描述。恶意存在于序列化的权重中。模型专用扫描此后已经出现以弥补这个缺口。Hugging Face 在推送到 Hub 的每个文件上运行 ClamAV 加上 pickle 导入扫描,静态反汇编 pickle 的 opcode 流以标记危险导入。虽然它们有帮助,但它们也已经在被规避。2025 年 2 月,ReversingLabs 描述了 nullifAI,两个通过用 7z 而不是 ZIP 压缩并在对负载运行后立即破坏 pickle 流来绕过 picklescan 的模型,使静态分析在文件上出错——而该文件的反向 shell 本应已经运行过。Hugging Face 在 24 小时内移除了这些模型并修补了 picklescan。这就是问题的形态。模式匹配扫描器是一条不断移动的线,每个扫描器回答的是文件是否类似于已知的不良文件。它们没有一个回答文件来自哪里。
"被篡改的应用镜像会破坏一个页面。被篡改的 AI 模型产物会在规模上污染预测。"
AI 正在以同样的动作扩大攻击面。编码助手建议的依赖从未通过人类威胁模型,而那些代码被容器化并发货的速度比审查所能跟上。爆炸半径也变了。被篡改的应用镜像会破坏一个页面。被篡改的 AI 模型产物会在规模上污染预测、在数百万用户身上投毒推荐,或者在 agentic 场景下在生产环境中执行操作:API 调用、工具调用、费用消耗。而且当你消费一个预训练模型时,你继承了关于其训练数据及其安全性的每一个上游决策,而这些你完全没有可见性。来源证明不再只是关于你的应用代码的问题。它变成了关于模型、Agent 以及承载它们的工具链的问题。
但签名不是勾选框。它是一条链。只有当每个环节都稳固时它才有效。
在 Amazon ECR 规模上运营 registry 的经验教会了我们一些事情,塑造了我们对供应链安全的思考方式。大多数团队不验证镜像。他们验证地址。准入策略允许来自你的 registry 账户的镜像,推送凭据属于流水线而不是人,扫描器阻止关键 CVE。这阻止了很多攻击。但它无法区分边界内的一个好镜像和一个坏镜像,因为 registry 来源是关于位置的声明,而不是关于起源的声明。任何可以写入仓库的东西都会产生一个看起来合法的镜像:泄露的 CI token、配置错误的跨账户角色、被攻陷的构建步骤。摘要固定告诉你你收到了你要求的字节,而不是说你要求的是正确的字节。
每个容器镜像在运行前都会经过 registry。它是路径上最后一个看到每个产物、知道谁推送了它并控制谁可以拉取它的系统。它已经拥有身份上下文,已经强制执行访问策略,并且已经存储了描述镜像内容的元数据。镜像签名的难点是在不影响任何人的情况下跨每个团队和每条流水线一致地完成签名。Registry 是唯一能够使其变得无感的层级。
"镜像签名的难点是跨每个团队一致地完成它。Registry 是唯一能够使其变得无感的层级。"
签名不会让伪造变得不可能。一个完全攻陷可信签名身份的攻击者——窃取了凭据和签名权限——可以产生一个通过验证的有效签名的恶意镜像。签名的作用是缩小攻击面。没有它,路径上任何地方的篡改都能奏效,因为下游没有任何检查。有了签名和强制执行,除非攻击者攻陷一个范围狭窄的签名者,否则什么都不起作用,而且那个恶意签名是一个可审计的事件,绑定到一个身份而不是匿名覆盖。撤销身份,整个集群在一处更改中停止信任它。签名将一个看不见的、无界的问题变成一个有限的、可归因的、可撤销的问题。
签名是一个三步流程:
签名:在构建或推送时生成签名,将镜像摘要绑定到一个可验证身份。难题是托管:谁持有私钥,如何轮换和保护它?
验证:在拉取时,关键是在工作负载被准入之前,根据信任策略检查签名——信任策略是你声明信任的身份列表,这些身份签署了你要运行的东西。
强制执行:像 Kyverno 这样的 Kubernetes 准入控制器阻止任何未由可信身份签名的镜像运行。没有强制执行的签名不会改变任何事情。
启用签名伴随着运营成本。工程师必须安装和配置客户端工具如 Notation CLI 或 Cosign,然后拥有自己的签名密钥、证书、轮换计划表和撤销列表,然后构建自定义自动化将签名接入每条流水线。在一个拥有数千条独特配置的流水线的企业中,那个推广过程需要数周甚至数月。我们想知道的是 registry 本身是否可以承担这个成本,这样签名就可以成为推送镜像的属性,而不是每个团队承担的项目。这个问题的答案变成了 Amazon ECR Managed Signing。
机制故意做得很无聊,这花了一些功夫。你创建一个 registry 级签名配置,最多十条规则,每条规则将签名配置文件与仓库过滤器配对,之后每次匹配的推送都会获得签名。
Managed Signing 通过不把密钥交给你来回答托管问题。你在 AWS Signer 中配置一个签名配置文件,它固定签名算法、有效期和出现在签名中的身份。Signer 保留证书和私钥。这意味着没有签名密钥会出现在仓库、runner 或构建日志中。有效期默认 135 个月,所以签名不会在你手上过期。撤销才是你真正会在发现构建被攻陷时用到的东西。
然后是什么被签名,这比人们想象的更窄。Signer 签署一个小的 Notary 负载,其 targetArtifact 描述镜像清单:媒体类型、摘要、大小。不是直接签名镜像字节。因为签名的材料是内容寻址的,验证变成了一条关于确切字节的声明。签名本身作为分离的 OCI 产物落在同一仓库中,类型为 application/vnd.cncf.notary.signature,带有一个指向镜像清单摘要的主体描述符。一个镜像可以携带来自多个配置文件的签名,随着你的信任需求变化。
签名是异步进行的,这使 Signer 脱离了推送路径。同步调用会将可用性下降或节流变成开发者的失败 docker push,并且会将签名延迟放在每条流水线前面。推送先提交,然后 ECR 调用 SignPayload。
验证和强制执行发生在下游,信任策略是整个设计变得清晰的地方。你的集群操作员编写它并用 notation policy import 导入它。它是一个简短的可审查文件:
{
"version": "1.0",
"trustPolicies": [
{
"name": "aws-signer-tp",
"registryScopes": ["*"],
"signatureVerification": { "level": "strict" },
"trustStores": ["signingAuthority:aws-signer-ts"],
"trustedIdentities": [
"arn:aws:signer:us-east-1:111122223333:/signing-profiles/platform_images"
]
}
]
}
该策略表示工作负载仅在携带链接到 AWS Signer 根的签名且由该特定配置文件生成时才能运行。准入按顺序执行工作:解析引用到摘要,通过 OCI Referrers API 获取签名,根据其嵌入的证书链验证信封,走该链到信任库中的根,根据 trustedIdentities 检查签名身份,并检查撤销。撤销配置文件使验证在该配置文件被信任的任何地方都失败。新准入立即停止,运行中的 Pod 在下次重新调度时获取它。在 EKS 上你通过 Gatekeeper 和 Ratify 或通过 Kyverno 到达那里。两条路径都使用 AWS Signer 插件。每个环节都可以由集群自己检查,从产物加上根证书,而不要求验证者信任它拉取的 registry,或推送它的流水线。
漏洞扫描回答了在应用时代重要的问题:这个镜像内部有什么坏了?AI 时代问了一个更难的问题,而扫描从来不是为回答这个问题而构建的。你能证明这东西来自哪里,以及没有人动过它吗?
密码学从来不是难点。让它成为阻力最小的路径才是。签名、验证、强制执行,让 registry 承担税务,这样你的团队就不必了。
要探索这里引用的内容,请参阅 Amazon ECR managed signing 和 Amazon EKS 上的签名验证。