18_security/18.6_image_security.md
在 DevOps 流程中,容器镜像安全已经成为不容忽视的关键环节。从开发、构建、存储到部署,镜像的整个生命周期都需要安全防护。本节深入讨论镜像漏洞扫描、软件物料清单(SBOM)、镜像签名验证等供应链安全实践。
Trivy 是由 Aqua Security 开发的开源漏洞扫描器,以其轻量级、快速、准确而闻名,已成为业界标准。
优点:
安装与基本使用:
# 安装 Trivy
curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin
# 扫描本地镜像
trivy image nginx:latest
# 生成 JSON 格式报告
trivy image -f json -o report.json nginx:latest
# 扫描文件系统
trivy fs /path/to/project
# 扫描 Git 仓库
trivy repo https://github.com/aquasecurity/trivy
在 CI/CD 中集成:
# 设置严重程度过滤
trivy image --severity HIGH,CRITICAL \
--exit-code 1 \
myregistry.com/myapp:v1.0.0
Grype 由 Anchore 开发,支持更广泛的软件包管理器和语言。
优点:
安装与使用:
# 安装 Grype
curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sh -s -- -b /usr/local/bin
# 扫描镜像
grype docker:nginx:latest
# 与 Syft 配合生成 SBOM
syft docker:nginx:latest -o json > sbom.json
grype sbom:sbom.json
# 扫描特定目录
grype dir:/path/to/app
Snyk 提供了商业级的安全扫描服务,特别适合企业环境。
特点:
基本使用:
# 安装 Snyk CLI
npm install -g snyk
# 认证
snyk auth
# 扫描镜像
snyk container test docker-archive://image.tar
# 监控仓库
snyk monitor --docker
此外,Docker 官方提供的 Docker Scout(docker scout CLI)可以直接在 Docker Desktop 或命令行中对镜像进行漏洞扫描和依赖分析,适合日常开发流程中快速检查:
# 扫描本地镜像
$ docker scout cves myapp:latest
# 查看镜像的依赖和漏洞摘要
$ docker scout quickview myapp:latest
工具对比表:
| 特性 | Docker Scout | Trivy | Grype | Snyk |
|---|---|---|---|---|
| Docker 集成 | ✓(原生) | 需安装 | 需安装 | 需安装 |
| 零依赖 | ✗ | ✓ | ✗ | ✗ |
| 离线模式 | ✗ | ✓ | ✓ | ✗ |
| 许可证扫描 | ✓ | ✗ | ✓ | ✓ |
| 自动修复建议 | ✓ | ✗ | ✗ | ✓ |
| 开源免费 | 部分 | ✓ | ✓ | 部分 |
| IDE 集成 | ✓ | ✓ | ✓ | ✓ |
SBOM(Software Bill of Materials)是一份详细列表,记录了软件中使用的所有组件、依赖库及其版本信息。SBOM 在供应链安全中至关重要,特别是在发现新的安全漏洞时,能快速定位受影响的应用。
Syft 是 Anchore 推出的专业 SBOM 生成工具。
安装:
curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin
生成 SBOM:
# 从镜像生成 SBOM(多种格式)
syft docker:nginx:latest -o json > sbom.json
syft docker:nginx:latest -o spdx > sbom.spdx
syft docker:nginx:latest -o cyclonedx > sbom.xml
# 从本地文件系统生成
syft dir:/path/to/app -o json > sbom.json
# 从 OCI 镜像档案生成
syft oci-archive:image.tar -o json > sbom.json
两种主流的 SBOM 格式:
CycloneDX 格式示例:
<?xml version="1.0" encoding="UTF-8"?>
<bom xmlns="http://cyclonedx.org/schema/bom/1.4" version="1">
<components>
<component type="library">
<name>openssl</name>
<version>1.1.1k</version>
<purl>pkg:deb/debian/[email protected]+deb11u5</purl>
</component>
<component type="library">
<name>curl</name>
<version>7.74.0-1.3+deb11u1</version>
<purl>pkg:deb/debian/[email protected]+deb11u1</purl>
</component>
</components>
</bom>
SPDX 格式示例:
{
"SPDXID": "SPDXRef-DOCUMENT",
"spdxVersion": "SPDX-2.2",
"creationInfo": {
"created": "2024-03-01T12:00:00Z",
"creators": ["Tool: syft"]
},
"packages": [
{
"SPDXID": "SPDXRef-Package-openssl",
"name": "openssl",
"versionInfo": "1.1.1k",
"downloadLocation": "NOASSERTION"
}
]
}
漏洞关联:
当新的 CVE 被发现时,可快速查询受影响的应用:
# 使用 Grype 针对 SBOM 进行漏洞扫描
grype sbom:sbom.json --add-cpes-if-none
合规性报告:
将 SBOM 保存为构建产物,用于审计和合规性检查。
依赖升级决策:
通过分析 SBOM 中的���赖版本,制定安全升级计划。
镜像签名确保镜像的来源可信且未被篡改。两种主流方案是 Cosign 和 Notary。
Cosign 是 Sigstore 项目的核心工具,支持无密钥签名,适合现代 CI/CD 流程。
安装:
wget https://github.com/sigstore/cosign/releases/latest/download/cosign-linux-amd64
chmod +x cosign-linux-amd64
sudo mv cosign-linux-amd64 /usr/local/bin/cosign
生成密钥对(传统方式):
cosign generate-key-pair
# 生成 cosign.key 和 cosign.pub
签名镜像:
# 使用私钥签名(推送到仓库前)
cosign sign --key cosign.key myregistry.com/myapp:v1.0.0
# 系统会提示输入私钥密码
验证签名:
# 使用公钥验证
cosign verify --key cosign.pub myregistry.com/myapp:v1.0.0
# 输出结果示例
# Verification successful!
# {
# "critical": {
# "identity": {...},
# "image": {...},
# "type": "cosign container image signature"
# },
# "optional": {...}
# }
Keyless 签名(推荐用于 CI/CD):
# 在 GitHub Actions 等 CI 中无需存储密钥
cosign sign --yes myregistry.com/myapp:v1.0.0
# 验证时自动使用 OIDC 令牌验证身份
cosign verify myregistry.com/myapp:v1.0.0 \
--certificate-identity https://github.com/myorg/myrepo/.github/workflows/build.yml@refs/heads/main \
--certificate-oidc-issuer https://token.actions.githubusercontent.com
注意:DCT 退役时间线
Docker 已宣布退役 Content Trust。关键节点:
- 2025 年 8 月起:最早一批 DCT 签名证书开始过期
- 2025 年 9 月 30 日起:新注册表不可再启用 DCT
- 2028 年 3 月 31 日:DCT 完全移除,所有 DCT 数据永久删除
建议新项目直接使用上文介绍的 Cosign (Sigstore) 进行镜像签名;现有 DCT 用户应尽早制定迁移计划。
Docker Content Trust 使用 Notary 实现镜像签名,是 Docker 官方的传统签名解决方案。
启用 DCT:
# 在环境中启用 DCT
export DOCKER_CONTENT_TRUST=1
# 此后所有 docker push/pull 都需要签名
docker push myregistry.com/myapp:v1.0.0
# 如果镜像未签名,操作会被拒绝
# 禁用 DCT(仅用于特定操作)
docker push --disable-content-trust myregistry.com/myapp:v1.0.0
签名密钥管理:
# 首次推送时会提示创建 Delegation Key
# 密钥存储在 ~/.docker/trust/private/root_keys/ 和 ~/.docker/trust/private/tuf_keys/
# 查看签名信息
docker inspect --format='{{.RepoDigests}}' myregistry.com/myapp:v1.0.0
# ❌ 不推荐:使用 latest 标签
FROM ubuntu:latest
RUN apt-get update && apt-get install -y curl
# ✓ 推荐:固定基础镜像版本和摘要
FROM ubuntu:22.04@sha256:a6d2b38300ce017add71440577d5b0a90460d0e6e0e14...(完整 64 位哈希)
RUN apt-get update && apt-get install -y curl=7.68.0-1ubuntu1
在 Dockerfile 中集成安全扫描:
FROM golang:1.26-alpine AS builder
WORKDIR /app
COPY . .
# 使用 Trivy 扫描源代码
RUN apk add --no-cache curl && \
curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin && \
trivy fs . --exit-code 1 --severity HIGH,CRITICAL
RUN go build -o app .
FROM alpine:3.17@sha256:abcd1234...(请替换为实际完整的 64 位摘要哈希)
COPY --from=builder /app/app /app
# 镜像构建完成后立即扫描
trivy image --severity HIGH,CRITICAL \
--exit-code 1 \
--timeout 30m \
$IMAGE_NAME:$IMAGE_TAG
# 定期扫描已部署的镜像
trivy image --scanners vuln,misconfig registry:5000/myapp:latest
Harbor(私有镜像仓库)的安全扫描:
# harbor.yml 配置示例
trivy:
enabled: true
# 启用镜像扫描
image_source: "Official"
# 默认扫描配置
scan_on_push: true # 推送时自动扫描
scan_all: true # 扫描仓库中的所有镜像
在 Kubernetes 环境中使用 Admission Webhook 强制镜像签名和扫描:
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
name: image-security-policy
webhooks:
- name: image-security.example.com
clientConfig:
service:
name: image-security-webhook
namespace: security
path: "/validate"
rules:
- operations: ["CREATE", "UPDATE"]
apiGroups: [""]
apiVersions: ["v1"]
resources: ["pods"]
admissionReviewVersions: ["v1"]
sideEffects: None
name: Build and Scan Image
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
build-scan:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
security-events: write
steps:
- name: Checkout code
uses: actions/checkout@v6
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4
- name: Build Docker image
uses: docker/build-push-action@v7
with:
context: .
push: false
load: true
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
- name: Run Trivy vulnerability scan
# 安全提醒:2026 年 3 月 19 日 Trivy GitHub Actions 遭受供应链攻击,
# 76 个版本标签被劫持。务必使用不可变的 commit SHA 引用,而非可变标签。
# 使用前请到 https://github.com/aquasecurity/trivy-action/releases 核实 SHA 对应正确版本。
uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1 # v0.35.0
with:
image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
format: 'sarif'
output: 'trivy-results.sarif'
severity: 'HIGH,CRITICAL'
- name: Upload Trivy results to GitHub Security tab
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: 'trivy-results.sarif'
- name: Generate SBOM
uses: anchore/sbom-action@v0
with:
image: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
format: cyclonedx-json
output-file: sbom-cyclonedx.json
- name: Upload SBOM
uses: actions/upload-artifact@v4
with:
name: sbom
path: sbom-cyclonedx.json
- name: Sign image with Cosign
if: github.event_name == 'push'
run: |
cosign sign --yes ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
- name: Login to Registry and Push
if: github.event_name == 'push'
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Push image
uses: docker/build-push-action@v7
with:
context: .
push: true
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
stages:
- build
- scan
- sign
- push
variables:
REGISTRY: registry.gitlab.com
IMAGE_NAME: $REGISTRY/$CI_PROJECT_PATH
build:
stage: build
image: docker:latest
services:
- docker:dind
script:
- docker build -t $IMAGE_NAME:$CI_COMMIT_SHA .
- docker save $IMAGE_NAME:$CI_COMMIT_SHA > image.tar
scan:trivy:
stage: scan
image: aquasec/trivy:latest
script:
- trivy image --severity HIGH,CRITICAL --exit-code 1 docker-archive://image.tar
allow_failure: false
scan:grype:
stage: scan
image: anchore/grype:latest
script:
- grype docker-archive://image.tar
generate:sbom:
stage: scan
image: anchore/syft:latest
script:
- syft docker-archive://image.tar -o cyclonedx > sbom.xml
artifacts:
reports:
sbom: sbom.xml
sign:
stage: sign
image: gcr.io/projectsigstore/cosign:latest
script:
- cosign sign --key $COSIGN_KEY $IMAGE_NAME:$CI_COMMIT_SHA
only:
- main
push:
stage: push
image: docker:latest
services:
- docker:dind
script:
- docker load < image.tar
- docker login -u $REGISTRY_USER -p $REGISTRY_PASSWORD $REGISTRY
- docker push $IMAGE_NAME:$CI_COMMIT_SHA
only:
- main
Q: 扫描报告中有过时的 CVE,如何处理?
A: 某些 CVE 可能已经被修复但数据库未更新,可以:
.trivyignore)Q: 如何平衡镜像大小和安全性?
A:
Q: 如何管理和轮换签名密钥?
A: