镜像供应链攻击频发,你还在跳过签名验证?27个必须执行的Docker签名验证步骤,现在不看明天被黑

张开发
2026/4/21 15:56:24 15 分钟阅读
镜像供应链攻击频发,你还在跳过签名验证?27个必须执行的Docker签名验证步骤,现在不看明天被黑
第一章镜像供应链攻击的现状与签名验证的生死线近年来容器镜像供应链攻击呈爆发式增长。攻击者不再直接入侵运行时环境而是将恶意代码注入构建阶段——篡改基础镜像、劫持CI/CD流水线、伪造Docker Hub账号上传带后门的镜像。2023年CNCF报告指出超42%的企业在生产环境中拉取过未签名或签名失效的镜像其中17%的案例最终导致横向渗透。 签名验证并非可选加固项而是抵御上游污染的第一道也是最后一道防线。当镜像发布者使用Cosign对镜像打签消费者通过cosign verify校验时实际完成三重断言镜像内容哈希未被篡改、签名由可信密钥签署、该密钥隶属于已知授权主体。缺失任一环节信任链即告断裂。快速验证镜像签名的典型流程安装Cosign CLIcurl -L https://github.com/sigstore/cosign/releases/download/v2.2.4/cosign-linux-amd64 -o cosign chmod x cosign sudo mv cosign /usr/local/bin/拉取镜像并验证# 拉取镜像不运行 docker pull ghcr.io/sigstore/cosign:v2.2.4 # 验证其签名是否由sigstore官方密钥签署 cosign verify --key https://raw.githubusercontent.com/sigstore/public-good/main/cosign.pub ghcr.io/sigstore/cosign:v2.2.4解析输出中的Critical : {identity: {...}}字段确认issuer为https://token.actions.githubusercontent.com且subject匹配预期工作流路径常见签名验证失败原因对照表错误现象根本原因修复建议no matching signatures镜像未被发布者签名或签名未推送到同一registry路径要求上游提供cosign sign后的.sig和.att附件signature verification failed镜像层被篡改或公钥与签名私钥不匹配核对cosign public-key输出与签名时使用的私钥指纹graph LR A[开发者构建镜像] -- B[cosign sign --key key.pem registry/image:tag] B -- C[推送镜像签名至Registry] C -- D[生产集群拉取] D -- E{cosign verify --key trusted.pub registry/image:tag} E --|Success| F[启动容器] E --|Fail| G[拒绝加载并告警]第二章Docker内容信任DCT体系深度解析2.1 理解Notary v2架构与TUFThe Update Framework安全模型Notary v2 是 CNCF 孵化项目深度集成 TUF 框架将元数据签名、角色分层与委托机制内嵌于 OCI 分发协议中。TUF核心角色层级root签署并轮换其他顶级角色密钥targets声明可信制品哈希与路径策略snapshots和timestamps保障元数据新鲜性与一致性Notary v2 元数据结构示例{ signed: { type: targets, expires: 2025-12-01T00:00:00Z, targets: { sha256:abc123...: { hashes: {sha256: abc123...}, custom: {repository: ghcr.io/example/app} } } }, signatures: [{keyid: ..., sig: ...}] }该 JSON 表示 targets 角色对镜像摘要的权威声明expires强制客户端校验时效性custom字段支持策略扩展如多租户隔离或地域分发约束。角色信任链验证流程步骤验证动作安全目标1用本地 root 公钥验签 root.json建立初始信任锚2用 root 中的 targets 公钥验签 targets.json防止中间人篡改目标清单3比对下载镜像的 sha256 与 targets 中声明值确保内容完整性与防投毒2.2 初始化Docker Content Trust环境并配置可信根密钥链启用Docker Content TrustDocker Content TrustDCT默认关闭需通过环境变量显式启用# 启用全局签名验证 export DOCKER_CONTENT_TRUST1 # 可选指定信任服务器默认为 Docker Hub 的 notary server export DOCKER_CONTENT_TRUST_SERVERhttps://notary.docker.ioDOCKER_CONTENT_TRUST1 强制客户端对拉取/推送的镜像执行签名验证未签名镜像将被拒绝。该变量作用于当前 shell 会话建议写入 ~/.bashrc 或 /etc/environment 实现持久化。生成并初始化根密钥链首次使用 DCT 时需生成根密钥root key它用于签署和轮换所有其他密钥运行docker trust key generate name创建离线根密钥推荐使用强密码保护执行docker trust signer add --key pubkey.pem signer-name repo添加委托签名者密钥存储结构路径用途权限要求~/.docker/trust/private存储加密的 root 和 delegation 私钥仅属主可读写0700~/.docker/trust/tufTUF 元数据缓存含 targets、snapshot 等角色属主读写07002.3 为私有Registry部署兼容Notary v2的签名服务cosign fulcio rekor集成核心组件职责划分组件作用是否必需cosign客户端签名/验证工具支持 OCI Artifact 签名是Fulcio证书颁发机构CA签发短时效 OIDC 绑定证书是若需身份绑定Rekor透明日志TLog持久化存储签名与证书关联记录是满足可验证性与审计要求快速启动 Fulcio Rekor本地开发模式# 启动 Fulcio使用自签名根 CA docker run -d --name fulcio -p 50001:50001 -e FOSSA_DEV1 ghcr.io/sigstore/fulcio:v1.4.0 # 启动 Rekor内存模式仅用于测试 docker run -d --name rekor -p 3000:3000 -e REKOR_PUBLIC_URLhttp://localhost:3000 ghcr.io/sigstore/rekor:v1.4.0该命令组合构建最小可行签名基础设施Fulcio 提供基于 OIDC 的证书签发能力Rekor 作为不可篡改的签名索引服务二者通过 cosign CLI 自动发现并交互无需手动配置证书链。签名工作流验证使用 GitHub OIDC Token 获取 Fulcio 签发证书cosign 对镜像生成 DSSE 或 SLSA Provenance 签名签名与证书哈希自动写入 Rekor 日志下游拉取时通过cosign verify --rekor-url http://localhost:3000完成端到端校验2.4 验证Docker Hub官方镜像签名证书链的完整信任路径信任锚与根证书定位Docker Content TrustDCT依赖于由 Docker 官方维护的根密钥root-keys和可验证的证书链。默认信任锚位于~/.docker/trust/tuf/docker.io/root.json该文件包含根公钥哈希及签名是整个TUFThe Update Framework信任链起点。证书链验证流程拉取镜像时Docker CLI 请求targets元数据获取镜像哈希与签名递归校验snapshot→timestamp→root的签名有效性最终比对本地root.json与远程签名是否一致关键验证命令示例DOCKER_CONTENT_TRUST1 docker pull nginx:alpine启用 DCT 后CLI 自动执行证书链逐级签名验证失败则中止拉取并报错“signature verification failed”。2.5 实战使用notary CLI离线验证远程镜像的签名元数据完整性与时间戳有效性前提准备确保已安装notaryCLI 并配置好本地信任根~/.notary/root-ca.crt及服务端地址。下载并验证签名元数据# 离线获取镜像签名清单不含镜像层 notary -s https://notary.example.com -d ~/.notary \ get docker.io/library/nginx:1.25.3 --skip-verify # 输出含时间戳、签名者、哈希摘要的JSON元数据该命令跳过 TLS 验证仅拉取 TUF 元数据root.json、targets.json、timestamp.json不依赖在线证书链校验。关键字段校验逻辑字段作用离线可验timestamp.expiration元数据时效截止时间✅本地系统时钟比对targets.hashes.sha256镜像 manifest 哈希✅与已缓存 manifest 比对第三章镜像拉取阶段的强制签名拦截策略3.1 在daemon.json中启用DOCKER_CONTENT_TRUST1并处理信任缺失的优雅降级逻辑配置 daemon.json 启用内容信任{ DOCKER_CONTENT_TRUST: 1, experimental: true }该配置强制 Docker 守护进程验证镜像签名。DOCKER_CONTENT_TRUST1 启用全局信任模式要求所有拉取操作必须通过 Notary 服务校验签名experimental:true 是部分信任功能如自动密钥轮换的前提。优雅降级策略对非可信镜像触发警告日志而非硬性拒绝允许通过 --disable-content-trustfalse 覆盖单次命令行为记录未签名镜像的 registry、repo、digest 到 audit 日志信任状态响应码映射HTTP 状态码含义降级动作401签名密钥未授权回退至本地缓存验证404签名元数据缺失记录告警并允许拉取需白名单豁免3.2 构建CI/CD流水线中的Docker pull预检钩子基于buildkitattestations预检钩子设计目标在镜像拉取前验证其完整性、签名与策略合规性避免运行被篡改或未授权的镜像。启用BuildKit与Attestation支持export DOCKER_BUILDKIT1 export BUILDKIT_PROGRESSplain docker buildx build --attest typecosign,modemin,identitygithub --provenancetrue -t myapp:latest .该命令启用BuildKit构建并嵌入Cosign签名及SBOM证明。--attest typecosign 触发密钥签名--provenancetrue 自动生成构建溯源元数据。预检执行流程CI作业触发前调用docker pull --platform linux/amd64 myapp:latest通过cosign verify-attestation校验镜像签名有效性使用notation verify检查OCI Artifact中内嵌的SLSA声明检查项工具关键参数签名验证cosign--certificate-oidc-issuer https://token.actions.githubusercontent.com策略评估kyverno--policy image-signing-required.yaml3.3 使用Podman 4.0替代方案实现无Docker daemon的签名感知拉取签名验证与无守护进程架构融合Podman 4.0 原生支持 sigstore 和 cosign 集成无需 Docker daemon 即可执行策略驱动的镜像拉取验证。podman pull --signature-policy /etc/containers/policy.json \ --tls-verifytrue \ quay.io/example/app:signed该命令启用策略文件校验如 require-sigstore、强制 TLS 加密传输并拒绝未签名或签名无效的镜像。--signature-policy 指向定义信任模型的 JSON 策略--tls-verify 防止中间人篡改元数据。核心配置对比特性Docker daemonPodman 4.0签名验证时机拉取后手动校验拉取前策略拦截运行时依赖需 root 权限 daemonrootless、无 daemon第四章镜像构建与推送环节的签名加固实践4.1 在Docker Buildx中嵌入SLSA3级证明生成与cosign签名绑定构建时自动注入SLSA3证明Docker Buildx 0.12 原生支持--provenance标志启用后自动生成符合 SLSA Level 3 的 SBOM 和构建溯源元数据docker buildx build \ --platform linux/amd64,linux/arm64 \ --provenancetrue \ --sbomtrue \ -t ghcr.io/user/app:latest \ --push .该命令触发 BuildKit 内置的 provenance generator生成符合 SLSA v1.0 Provenance 规范的 JSON-LD 证明并作为 OCI 注解dev.cosignproject.cosign/attestation嵌入镜像清单。绑定 cosign 签名与证明生成证明后需将其与镜像摘要强绑定并签名提取镜像 digest 并生成 SLSA 证明文件使用cosign attest --type slsaprovenance将其作为 attestation 上传执行cosign sign对镜像本身签名验证链完整性验证项命令预期输出SLSA 证明存在性cosign verify-attestation --type slsaprovenancebuilder.id: https://github.com/docker/buildkit签名与证明一致性cosign verify --certificate-oidc-issuer匹配构建环境 OIDC 主体4.2 利用OCI Artifact特性为多架构镜像arm64/amd64分别签署独立签名包OCI Artifact 规范允许将任意类型元数据如签名、SBoM、策略作为独立 artifact 关联到目标镜像且支持按平台os/arch精准绑定。这突破了传统 cosign 全局签名的粒度限制。签名与镜像的平台级绑定机制签名不再附加于 manifest list而是作为独立 artifact 推送并通过 org.opencontainers.image.subject 引用对应平台镜像 digestcosign attach signature \ --subject oci://registry.example.com/app:v1.0sha256:abc123 \ --type application/vnd.dev.cosign.simplesigning.v1json \ ./arm64.sig该命令将签名绑定至特定平台镜像 digest如 sha256:abc123 对应 arm64 层而非 manifest list digest确保验证时严格匹配架构。验证流程差异对比验证方式是否区分架构适用场景cosign verify默认否单架构或 manifest list 全局签名cosign verify --artifact是精确验证 arm64/amd64 独立签名4.3 自动化签名策略基于OPA Gatekeeper校验镜像标签语义版本合规性后再签名校验与签名协同流程镜像推送至仓库前由 Kubernetes 准入控制器调用 OPA Gatekeeper 执行策略检查。仅当标签符合 SemVer v2.0.0 规范如v1.12.3-alpha.1build2024时才触发 Cosign 签名。SemVer 校验 ConstraintTemplate 示例apiVersion: templates.gatekeeper.sh/v1beta1 kind: ConstraintTemplate spec: crd: spec: names: kind: ImageTagSemver targets: - target: admission.k8s.io rego: | package imagetagsemver violation[{msg: msg}] { input.review.object.spec.template.spec.containers[_].image as img not is_semver_tag(img) msg : sprintf(image tag %q does not conform to SemVer 2.0, [img]) } is_semver_tag(img) { # 正则匹配 SemVer 2.0 标签格式 re_match(^v(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\([0-9a-zA-Z-](?:\.[0-9a-zA-Z-])*))?$, trim_tag(img)) } trim_tag(img) t { parts : split(img, :) t : parts[1] }该 Rego 策略提取镜像标签如ghcr.io/org/app:v1.2.0sha256中的v1.2.0sha256并严格验证其是否满足 SemVer 2.0 的主版本、次版本、修订号、预发布和构建元数据结构。签名准入链路关键组件组件职责Gatekeeper执行语义版本策略校验Cosign生成/验证 OCI 镜像签名Kyverno可选替代方案支持策略即代码4.4 将签名信息注入Kubernetes ImagePolicyWebhook准入控制器实现集群级拦截准入链路增强设计ImagePolicyWebhook 在 Pod 创建前校验镜像合法性需将 Cosign 签名验证逻辑嵌入其响应流程。Webhook 服务接收ImageReview对象后提取spec.containers[].image并调用cosign verify进行远程签名检查。func validateImage(ctx context.Context, image string) (bool, error) { cmd : exec.CommandContext(ctx, cosign, verify, --certificate-oidc-issuer, https://accounts.google.com, --certificate-identity, https://github.com/org/repo/.github/workflows/ci.ymlrefs/heads/main, image) out, err : cmd.CombinedOutput() return strings.Contains(string(out), OK), err }该函数通过 OIDC 身份与证书颁发者双重约束确保仅接受指定 CI 流水线签发的镜像--certificate-identity参数精确匹配 GitHub Actions 工作流 URI防止身份伪造。策略响应映射表签名状态Webhook Response.Allowed集群行为有效且身份匹配truePod 正常调度签名缺失或验证失败false拒绝创建返回 403第五章从防御失效到攻防复盘——一次真实镜像投毒事件的全链路溯源事件背景与初始告警某金融客户CI/CD流水线在凌晨3:17触发异常行为检测部署后的Python服务持续向境外IP发起DNS查询且进程内存中存在未签名的.so模块。SIEM平台关联出同一镜像SHA256:sha256:8a3b...f1c9在7个不同命名空间被拉取。镜像层解构分析# 提取并检查各层文件系统 docker save alpine:3.18 | tar -xO */layer.tar | tar -t | grep -E \.(py|so|sh)$ # 发现恶意层中隐藏的 /usr/local/bin/.update-helper伪装为systemd工具供应链断点定位追溯Dockerfile发现基础镜像引用了非官方registry.example.com/alpine:3.18实为中间人劫持该私有registry未启用TLS证书校验且CI节点配置了--insecure-registry参数上游构建日志显示镜像构建时间戳早于Alpine官方3.18发布日期伪造构建元数据恶意载荷行为还原阶段技术动作检测特征注入覆盖entrypoint.sh预加载LD_PRELOAD/tmp/.log.so进程启动时openat(AT_FDCWD, /tmp/.log.so, O_RDONLY)持久化写入/etc/cron.d/.sysmon每5分钟回连C2cron日志中出现非root用户创建的定时任务修复与验证闭环根因修复路径强制镜像签名验证cosign verify registry CA证书硬绑定 构建阶段启用BuildKit SBOM生成

更多文章