本节摘要:前几节防"容器逃出去",这节防"坏镜像进来"。Podman 的分发信任体系有两套并行机制:GPG 签名(经典路径,配合镜像仓库的签名存储)与 sigstore(新一代,签名存在镜像本身)。配合 policy 策略文件,可以做到"无签名不运行、非预期签名者不运行"。本节讲信任模型、实操签名与验证,并说明它与第 4.1 节 digest 锁定的互补关系。
第 4.1 节的 digest 锁定解决的是"同一份数据"的问题——指纹匹配就保证内容没变。但它回答不了另一个问题:这份数据一开始就值得信吗?你锁定的 digest 可能指向一个被仓库劫持后推送的恶意版本。签名补上这一环:发布者用私钥给镜像签名,运行侧用公钥验证——内容不仅"没变",而且"来自我认可的人"。两者互补:digest 是运行时的内容锚点,签名是分发时的身份锚点。
两种引擎的生态位差异在这节值得先说清:Docker 的签名体系(Content Trust,基于 Notary)长期默认关闭、体验割裂,实际生产使用率不高;Podman 把签名验证做进了引擎的策略层,Red Hat 的发行版镜像更是全量签名交付——用企业镜像仓库的客户几乎默认走上这条路。机制上两边都能做信任链,工程上 Podman 的默认姿态更靠前。
经典路径的完整流程:生成密钥、签名、推送、配置策略:
# 生成一对专用密钥(略去交互细节) gpg --quick-generate-key "ci-signer example com" rsa2040 sign 0 # 构建后给镜像签名(签名随镜像推到仓库的约定位置) podman push --sign-by ci-signer@example.com \ registry.internal.example.com/myapp:1.2 # 运行侧:策略文件声明"这个仓库的镜像必须由谁签" # /etc/containers/policy.json 的关键片段: # { # "default": { "type": "reject" }, # "transports": { # "docker": { # "registry.internal.example.com": { # "type": "signedBy", # "keyType": "GPGKeys", # "keyPath": "/etc/pki/containers/ci-signer.gpg" # } # } # } # } # 解读:default 是 reject——一切未被规则放行的镜像直接拒绝运行; # 内部仓库的镜像必须有 ci-signer 的签名。白名单逻辑,缺省即拒绝
策略文件是这一整套体系的执法中枢。注意 default 设为 reject 的含义:任何没被显式放行的来源都不可运行——这是"默认拒绝"的安全立场,与 Docker 的"默认信任一切来源"形成鲜明对比。生产机器上这样配置后,被投毒的公共镜像即使被误拉到本地,也无法启动。
GPG 路径的痛点是公钥分发——每台运行机器都要先装上可信公钥。sigstore 的思路是让签名跟着镜像走、身份交给公共信任服务(Fulcio 签发短期证书,cosign 是配套工具)。Podman 侧的支持正在分阶段落地(镜像存储层已支持 cosign 签名的验证接口),当前工程实践更多是 cosign 独立使用,但与 Podman 的策略层可以组合:
# 发布侧:用 cosign 以密钥或密钥对签名 cosign sign --key cosign.key \ registry.internal.example.com/myapp@sha256:3e4a5b... # 验证侧:CI 或部署钩子里先验签再放行给 podman run cosign verify --key cosign.pub \ registry.internal.example.com/myapp@sha256:3e4a5b... # Verified signature ...(验证通过输出) # 组合逻辑:验证通过 → 部署脚本用同一 digest 启动容器 # 第 4.1 节的 digest 锁定在此闭环:签名证明"谁发的", # digest 锁定保证"跑的就是验过的那份"

背景:团队有三类镜像来源——内部构建、Red Hat 发行版镜像、第三方公共镜像。目标是每类都有明确的信任策略。操作:policy.json 里 default 设为 reject;对内部仓库配置 signedBy 指向 CI 签名密钥;对 Red Hat 官方仓库配置 signedBy 指向其公开的发布密钥(发行版自带);对确需使用的第三方镜像走"digest 白名单"条目(type 为 insecureAcceptAnything 但仅限精确 digest),并安排季度复审。结果:一次供应链投毒演练中,被污染的"第三方镜像"因 digest 不匹配白名单而拒绝运行,内部镜像因签名者不对被拒——两道闸都起了作用。解读:reject 默认加白名单放行,比"默认信任加黑名单拦截"在工程上可靠得多,因为它不需要预见所有坏来源。变式:全 sigstore 化的团队可以把公钥管理换成 OIDC 身份(cosign 的 keyless 模式),发布身份来自 CI 的短期证书,适合没有长期密钥管理能力的团队。
这个案例里最值得抄走的设计是那个"季度复审"的白名单机制。第三方镜像白名单是整套策略里唯一会腐烂的部分——新镜像进白名单容易,旧条目没人愿意删(删了可能炸)。季度复审就是强制腐烂检测:每个季度把白名单条目与实际运行中的容器对账,三个月没被用到的条目直接清除,需要时再走流程进。小仪式,但它保证白名单的长度收敛于真实需求而不是无限增长——策略文件的可维护性,决定这套防线能活多久。