容器镜像安全基础:镜像分层原理、镜像漏洞扫描与镜像签名验证

容器镜像的安全性是整个云原生安全体系的基石。本章从镜像的底层结构出发,深入剖析分层原理,并系统讲解镜像漏洞扫描工具(Trivy / Clair)的使用方法,以及基于 Cosign 的镜像签名与验证机制。掌握这些基础,才能在生产环境中构建可信、可审计的容器供应链。

2.1 镜像分层原理

Docker/OCI 镜像由一系列只读层(Layer)堆叠而成,每一层代表 Dockerfile 中的一条指令。层与层之间通过联合文件系统(UnionFS)合并,形成容器的可写视图。分层机制的核心优势在于:层复用——多个镜像可以共享相同的底层(如基础操作系统层),从而节省存储空间并加速分发。

关键概念:镜像层是内容寻址的,每一层都有一个唯一的 SHA256 摘要。层的内容不可变,任何修改都会产生新层。
容器镜像分层结构示意图 容器可写层 (Writable) Layer 4: CMD / ENTRYPOINT Layer 3: 应用代码 & 依赖 Layer 2: 系统库 & 工具 Layer 1: 基础操作系统 (Base) 层复用示例 镜像 A 和 B 共享 Layer 1 (Base) 和 Layer 2 仅上层不同,节省存储 只读层 只读层 只读层 只读层
最佳实践:将频繁变动的层(如应用代码)放在上层,将稳定层(如基础镜像、系统包)放在下层,以最大化层缓存命中率,减少镜像构建和拉取时间。

2.2 镜像漏洞扫描(Trivy / Clair)

镜像漏洞扫描是容器安全左移的关键环节。通过扫描工具将镜像层中的软件包版本与公共漏洞数据库(如 NVD、RedHat OVAL、Debian Security Tracker)进行比对,识别已知 CVE。主流工具包括 Trivy(轻量、快速、支持多语言)和 Clair(CoreOS 开源,深度集成于 Quay 和 Harbor)。

2.2.1 扫描工具对比

特性 Trivy Clair
部署方式 单二进制,无依赖 需运行 Clair 服务 + PostgreSQL
扫描速度 极快(秒级) 中等(分钟级,依赖数据库更新)
漏洞数据库 内置更新,离线可用 需定期同步,支持自定义
支持格式 Docker / OCI / 本地文件系统 Docker / OCI / AppC
CI/CD 集成 原生支持,输出 JSON/SARIF 需通过 API 或 Harbor 集成
许可证扫描 支持 不支持

2.2.2 Trivy 扫描实战

以下命令演示如何使用 Trivy 扫描本地镜像,并输出 JSON 格式的漏洞报告:

# 安装 Trivy(macOS / Linux)
brew install trivy
# 或使用官方脚本
curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh

# 扫描镜像(示例:nginx:1.25)
trivy image --severity CRITICAL,HIGH --format json nginx:1.25 -o nginx-vuln.json

# 仅输出摘要
trivy image --severity CRITICAL nginx:1.25
注意:扫描结果中的 CRITICAL 漏洞必须立即处理。建议在 CI 流水线中设置阻断策略:当存在 CRITICAL 漏洞时,阻止镜像推送至生产仓库。

2.2.3 Clair 集成示例(Harbor)

Clair 通常作为 Harbor 镜像仓库的内置扫描器运行。在 Harbor 中启用 Clair 后,每次推送镜像都会自动触发扫描:

# Harbor 配置文件(harbor.yml)中启用 Clair
clair:
  enabled: true
  updaters_interval: 12  # 漏洞数据库更新间隔(小时)

# 通过 Harbor API 获取扫描报告
curl -u "admin:password" \
  https://harbor.example.com/api/v2.0/projects/library/repositories/nginx/artifacts/latest/scan
镜像漏洞扫描流程 构建镜像 推送至仓库 触发扫描 生成报告 通过? 是 → 允许部署 否 → 阻断并告警

2.3 镜像签名与验证(Cosign)

镜像签名用于确保镜像的完整性和来源可信。Cosign 是 Sigstore 项目下的无密钥签名工具,支持使用短生命周期密钥或硬件密钥对镜像进行签名,并将签名存储在 OCI 仓库中。验证时,策略引擎(如 OPA Gatekeeper)可强制要求所有部署的镜像必须附带有效签名。

2.3.1 Cosign 签名流程

  1. 生成密钥对:使用 cosign generate-key-pair 生成私钥(本地保管)和公钥(公开分发)。
  2. 签名镜像:cosign sign --key cosign.key <镜像引用>,签名被自动推送至镜像仓库的 OCI 制品中。
  3. 验证签名:cosign verify --key cosign.pub <镜像引用>,输出签名元数据。
# 生成密钥对
cosign generate-key-pair

# 对镜像签名(示例:registry.example.com/app:v1)
cosign sign --key cosign.key registry.example.com/app:v1

# 验证签名
cosign verify --key cosign.pub registry.example.com/app:v1

# 输出示例(JSON 格式)
{
  "critical": {
    "identity": {
      "docker-reference": "registry.example.com/app"
    },
    "image": {
      "docker-manifest-digest": "sha256:abc123..."
    },
    "type": "cosign container image signature"
  }
}

2.3.2 签名验证策略(OPA Gatekeeper 集成预览)

在后续章节中,我们将详细实现 OPA Gatekeeper 策略来强制验证签名。以下是一个简化的 Rego 策略片段,用于检查镜像是否包含 Cosign 签名:

package containerimagesignature

violation[{"msg": msg}] {
  input.review.object.spec.containers[_].image == img
  not cosign_verified(img)
  msg := sprintf("镜像 %v 缺少有效 Cosign 签名", [img])
}

cosign_verified(img) {
  # 调用外部数据或注解检查签名存在性
  data.images[img].signed == true
}
生产建议:将 Cosign 公钥存储在 Kubernetes Secret 中,并通过 Gatekeeper 的 ConstraintTemplate 注入验证逻辑。同时启用 cosign verify 的 --check-claims 选项,验证签名中的身份声明(如 OIDC 发行人)。

2.4 本章小结

  • 镜像分层:理解层复用机制,优化 Dockerfile 指令顺序以提升构建效率。
  • 漏洞扫描:Trivy 适合快速 CI 集成,Clair 适合企业级仓库深度扫描;务必设置漏洞阻断阈值。
  • 签名验证:Cosign 提供无密钥签名能力,是构建软件供应链安全的基础组件。
核心行动项:在现有 CI 流水线中集成 Trivy 扫描 + Cosign 签名,确保每个推送至生产仓库的镜像都经过安全校验。