4. 镜像签名与验证:Cosign 机制、Notary 项目与 Admission Controller 集成

镜像签名与验证是容器供应链安全的最后一道防线。在 K8s 集群升级前,必须确保只有经过签名、完整性未被篡改的镜像才能被调度运行。本章聚焦 Cosign(Sigstore 生态)、Notary(Docker 原生)以及 K8s 侧通过 Admission Controller 强制验证的落地方法。

4.1 为什么需要镜像签名?

镜像在构建、推送、拉取过程中可能被篡改或注入恶意层。签名机制提供:

  • 完整性:镜像层哈希未被修改。
  • 来源认证:确认镜像由可信发布者签名。
  • 防重放:结合时间戳或随机数防止旧版本恶意替换。
升级前检查要点: 确认集群内所有生产命名空间已启用镜像签名验证策略,否则升级后可能引入未签名的恶意镜像。

4.2 主流签名方案对比

特性 Cosign (Sigstore) Notary (Docker Content Trust)
密钥管理 支持密钥对、云 KMS、OIDC 无密钥模式 本地密钥对 + 委托角色
签名存储 OCI 仓库(tag 或 referrers API) Notary 服务(TUF 元数据)
验证速度 快(直接拉取签名层) 中等(需查询 TUF 元数据)
K8s 集成 原生支持,Admission Controller 成熟 需额外工具(如 Portieris)
生态活跃度 高(CNCF 孵化,社区活跃) 低(维护模式,Docker 官方推荐迁移)
推荐: 新项目优先使用 Cosign + Sigstore 生态;存量 Notary 环境建议逐步迁移。

4.3 Cosign 签名与验证流程

Cosign 将签名作为 OCI 制品存储在镜像仓库中,与镜像本身解耦。核心命令示例:

# 1. 生成密钥对
cosign generate-key-pair

# 2. 对镜像签名(私钥)
cosign sign --key cosign.key registry.example.com/app:v1.0

# 3. 验证签名(公钥)
cosign verify --key cosign.pub registry.example.com/app:v1.0

# 4. 使用密钥管理服务(KMS)
cosign sign --key gcpkms://projects/.../locations/global/keyRings/.../cryptoKeys/... registry.example.com/app:v1.0
注意: 私钥必须安全存储,建议使用硬件安全模块(HSM)或云 KMS。不要将私钥硬编码在 CI/CD 脚本中。

4.4 Notary 与 Docker Content Trust

Notary 基于 TUF(The Update Framework)规范,通过委托角色实现多级签名。启用 Docker Content Trust 后,客户端默认只拉取已签名的镜像:

# 启用 DCT
export DOCKER_CONTENT_TRUST=1

# 推送时自动签名
docker push registry.example.com/app:v1.0

# 拉取时自动验证
docker pull registry.example.com/app:v1.0

Notary 的验证链较长,且需要独立部署 Notary Server 和 Signer,运维成本较高。在 K8s 中通常通过 Portieris(IBM 开源)作为 Admission Controller 实现验证。

4.5 在 K8s 中启用镜像签名验证(Admission Controller)

K8s 本身不内置镜像签名验证逻辑,需要通过 动态准入控制(Dynamic Admission Control) 集成外部验证器。主流方案:

  • Cosign + Kyverno:使用 Kyverno 策略引擎调用 Cosign 验证。
  • Cosign + OPA Gatekeeper:通过 ConstraintTemplate 调用外部数据。
  • Portieris:专为 Notary/DCT 设计的 Admission Controller。

4.5.1 Kyverno + Cosign 策略示例

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: verify-image-signature
spec:
  validationFailureAction: Enforce
  rules:
    - name: check-cosign-signature
      match:
        any:
          - resources:
              kinds:
                - Pod
      verifyImages:
        - imageReferences:
            - "registry.example.com/*"
          attestors:
            - entries:
                - keys:
                    publicKeys: |-
                      -----BEGIN PUBLIC KEY-----
                      MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE...
                      -----END PUBLIC KEY-----
          required: true
关键参数: validationFailureAction: Enforce 表示拒绝未签名镜像;required: true 强制每个镜像都必须有签名。

4.5.2 OPA Gatekeeper + Cosign 集成

通过 Gatekeeper 的 外部数据(External Data) 功能,在 Constraint 中调用 Cosign CLI 或 API:

apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
  name: k8sverifiedimages
spec:
  crd:
    spec:
      names:
        kind: K8sVerifiedImages
  targets:
    - target: admission.k8s.gatekeeper.sh
      rego: |
        package k8sverifiedimages
        violation[{"msg": msg}] {
          container := input.review.object.spec.containers[_]
          image := container.image
          # 调用外部数据验证签名
          not data.inventory.namespace[input.review.object.metadata.namespace].images[image].verified
          msg := sprintf("Image %v is not signed", [image])
        }

4.6 镜像签名验证架构图

下图展示了从镜像构建、签名、推送到 K8s 集群通过 Admission Controller 验证的完整链路:

镜像签名验证架构图 镜像签名验证端到端架构 CI/CD 构建镜像 Cosign 签名 推送至镜像仓库 OCI 镜像仓库 镜像层 + 签名层 (Referrers API) 公钥 / 证书 K8s API Server Admission Controller Pod 调度运行 拉取签名 & 公钥 说明:Admission Controller 在 Pod 创建前拦截请求,调用 Cosign/Notary 验证镜像签名,验证通过后才允许调度

4.7 升级前检查清单

检查项 验证方法 预期结果
Admission Controller 是否启用 kubectl get validatingwebhookconfigurations 存在 kyverno/gatekeeper/portieris 相关 webhook
签名策略是否生效 创建未签名 Pod:kubectl run test --image=nginx 被拒绝,返回签名验证失败错误
公钥/证书是否有效 检查 ConfigMap 或 Secret 中的公钥 未过期,格式正确
镜像仓库是否支持 Referrers API cosign verify 命令测试 返回 PASSED
升级后 webhook 兼容性 查看 Admission Controller 版本与 K8s 版本对照表 版本匹配,无弃用 API
常见升级故障: K8s 1.24+ 移除了 Dockershim,如果 Admission Controller 依赖 Docker 守护进程进行镜像验证,升级后可能失效。建议提前迁移至纯 OCI 验证方案。

4.8 多级签名与策略组合

生产环境通常需要多级签名(开发、测试、发布),结合策略实现细粒度控制:

# 多级签名示例
cosign sign --key dev.key registry.example.com/app:v1.0
cosign sign --key qa.key registry.example.com/app:v1.0
cosign sign --key prod.key registry.example.com/app:v1.0

# Kyverno 策略:要求至少两个签名
verifyImages:
  - imageReferences:
      - "registry.example.com/*"
    attestors:
      - entries:
          - keys:
              publicKeys: |-
                -----BEGIN PUBLIC KEY-----
                ...dev公钥...
                -----END PUBLIC KEY-----
          - keys:
              publicKeys: |-
                -----BEGIN PUBLIC KEY-----
                ...qa公钥...
                -----END PUBLIC KEY-----
    required: true
    minimumCount: 2
最佳实践: 使用 keyless signing(基于 OIDC 的身份认证)减少密钥管理负担,适合 CI/CD 自动化场景。Sigstore 的 Fulcio 证书颁发机构可提供短期证书。

4.9 故障排查与回退

升级后若镜像验证导致 Pod 无法启动,可临时调整策略为 Audit 模式(仅记录不拒绝),待问题定位后再恢复 Enforce:

# Kyverno 临时调整为 Audit
kubectl patch clusterpolicy verify-image-signature --type='json' \
  -p='[{"op": "replace", "path": "/spec/validationFailureAction", "value": "Audit"}]'

# 查看审计日志
kubectl logs -n kyverno -l app.kubernetes.io/name=kyverno --tail=100 | grep "verify-image"

升级前务必在测试环境完整演练签名验证流程,确保 Admission Controller 与目标 K8s 版本兼容。