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 官方推荐迁移) |
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
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 验证的完整链路:
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 |
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
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 版本兼容。