返回首页返回书籍《Docker 从入门到实践》
目录

目录 · CONTENTS

  1. 01前言
  2. 02修订记录
  3. 03如何贡献
  4. 04第一章 Docker 简介
  5. 051.1 快速上手
  6. 061.2 什么是 Docker
  7. 071.3 为什么要用 Docker
  8. 08本章小结
  9. 09第二章 基本概念
  10. 102.1 镜像
  11. 112.2 容器
  12. 122.3 仓库
  13. 13本章小结
  14. 14第三章 安装 Docker
  15. 153.1 Ubuntu
  16. 163.2 Debian
  17. 173.3 Fedora
  18. 183.4 CentOS
  19. 193.5 Raspberry Pi
  20. 203.6 Linux 离线安装
  21. 213.7 macOS
  22. 223.8 Windows 10/11
  23. 233.9 镜像加速器
  24. 243.10 开启实验特性
  25. 25本章小结
  26. 26第四章 使用镜像
  27. 274.1 获取镜像
  28. 284.2 列出镜像
  29. 294.3 删除本地镜像
  30. 304.4 利用 commit 理解镜像构成
  31. 314.5 使用 Dockerfile 定制镜像
  32. 324.6 其他制作镜像的方式
  33. 334.7 实现原理
  34. 34本章小结
  35. 35第五章 操作容器
  36. 365.1 启动
  37. 375.2 守护态运行
  38. 385.3 终止
  39. 395.4 进入容器
  40. 405.5 导出和导入
  41. 415.6 删除
  42. 42本章小结
  43. 43第六章 访问仓库
  44. 446.1 Docker Hub
  45. 456.2 私有仓库
  46. 466.3 私有仓库高级配置
  47. 476.4 Nexus 3
  48. 48本章小结
  49. 49第七章 Dockerfile 指令详解
  50. 507.1 RUN 执行命令
  51. 517.2 COPY 复制文件
  52. 527.3 ADD 更高级的复制文件
  53. 537.4 CMD 容器启动命令
  54. 547.5 ENTRYPOINT 入口点
  55. 557.6 ENV 设置环境变量
  56. 567.7 ARG 构建参数
  57. 577.8 VOLUME 定义匿名卷
  58. 587.9 EXPOSE 暴露端口
  59. 597.10 WORKDIR 指定工作目录
  60. 607.11 USER 指定当前用户
  61. 617.12 HEALTHCHECK 健康检查
  62. 627.13 ONBUILD 为他人作嫁衣裳
  63. 637.14 LABEL 为镜像添加元数据
  64. 647.15 SHELL 指令
  65. 657.16 参考文档
  66. 667.17 多阶段构建
  67. 677.18 实战多阶段构建 Laravel 镜像
  68. 68本章小结
  69. 69第八章 数据管理
  70. 708.1 数据卷
  71. 718.2 挂载主机目录
  72. 728.3 tmpfs 挂载
  73. 73本章小结
  74. 74第九章 网络配置
  75. 759.1 配置 DNS
  76. 769.2 网络类型
  77. 779.3 自定义网络
  78. 789.4 容器互联
  79. 799.5 外部访问容器
  80. 809.6 网络隔离
  81. 819.7 容器网络高级特性
  82. 82本章小结
  83. 83第十章 Docker Buildx
  84. 8410.1 BuildKit
  85. 8510.2 使用 buildx 构建镜像
  86. 8610.3 使用 buildx 构建多种系统架构支持的 Docker 镜像
  87. 87本章小结
  88. 88第十一章 Docker Compose
  89. 8911.1 简介
  90. 9011.2 安装与卸载
  91. 9111.3 使用
  92. 9211.4 命令说明
  93. 9311.5 Compose 模板文件
  94. 9411.6 实战 Django
  95. 9511.7 实战 Rails
  96. 9611.8 实战 WordPress
  97. 9711.9 实战 LNMP
  98. 98本章小结
  99. 99第十二章 底层实现
  100. 10012.1 基本架构
  101. 10112.2 命名空间
  102. 10212.3 控制组
  103. 10312.4 联合文件系统
  104. 10412.5 容器格式
  105. 10512.6 网络
  106. 106本章小结
  107. 107第十三章 容器编排基础
  108. 10813.1 简介
  109. 10913.2 基本概念
  110. 11013.3 架构设计
  111. 11113.4 高级特性
  112. 11213.5 实战练习
  113. 113本章小结
  114. 114第十四章 部署 Kubernetes
  115. 11514.1 使用 kubeadm 部署 Kubernetes
  116. 11614.2 使用 kubeadm 部署 Kubernetes:使用 Docker
  117. 11714.3 在 Docker Desktop 使用
  118. 11814.4 Kind - Kubernetes IN Docker
  119. 11914.5 K3s - 轻量级 Kubernetes
  120. 12014.6 一步步部署 Kubernetes 集群
  121. 12114.7 可视化管理界面
  122. 12214.8 Kubernetes 命令行 kubectl
  123. 123本章小结
  124. 124第十五章 Etcd 项目
  125. 12515.1 简介
  126. 12615.2 安装
  127. 12715.3 集群
  128. 12815.4 使用 etcdctl
  129. 129本章小结
  130. 130第十六章 容器与云计算
  131. 13116.1 简介
  132. 13216.2 腾讯云
  133. 13316.3 阿里云
  134. 13416.4 亚马逊云
  135. 13516.5 多云部署策略
  136. 136本章小结
  137. 137第十七章 容器其他生态
  138. 13817.1 Fedora CoreOS 简介
  139. 13917.2 Fedora CoreOS 安装
  140. 14017.3 Podman - 下一代 Linux 容器工具
  141. 14117.4 Buildah - 容器镜像构建工具
  142. 14217.5 Skopeo - 容器镜像管理工具
  143. 14317.6 containerd - 核心容器运行时
  144. 14417.7 安全容器运行时
  145. 14517.8 WebAssembly 与容器
  146. 146本章小结
  147. 147第十八章 安全
  148. 14818.1 内核命名空间
  149. 14918.2 控制组
  150. 15018.3 服务端防护
  151. 15118.4 内核能力机制
  152. 15218.5 其他安全特性
  153. 15318.6 容器镜像安全扫描与供应链安全
  154. 154本章小结
  155. 155第十九章 容器监控与日志
  156. 15619.1 Prometheus
  157. 15719.2 ELK 套件
  158. 15819.3 容器性能优化与故障诊断
  159. 159本章小结
  160. 160第二十章 实战案例 - 操作系统
  161. 16120.1 Busybox
  162. 16220.2 Alpine
  163. 16320.3 Debian Ubuntu
  164. 16420.4 CentOS Fedora
  165. 165本章小结
  166. 166第二十一章 实战案例 - DevOps
  167. 16721.1 DevOps 完整工作流
  168. 16821.2 GitHub Actions
  169. 16921.3 Drone
  170. 17021.4 Drone Demo
  171. 17121.5 在 IDE 中使用 Docker
  172. 17221.6 VS Code
  173. 17321.7 实战案例:Go/Rust/数据库/微服务
  174. 174本章小结
  175. 175附录
  176. 176附录一:常见问题与错误速查
  177. 177常见错误处理
  178. 178附录二:热门镜像介绍
  179. 179Ubuntu
  180. 180CentOS
  181. 181Nginx
  182. 182PHP
  183. 183Node.js
  184. 184MySQL
  185. 185WordPress
  186. 186MongoDB
  187. 187Redis
  188. 188Minio
  189. 189附录三:Docker 命令查询
  190. 190客户端命令 - docker
  191. 191服务端命令 - dockerd
  192. 192附录四:Dockerfile 最佳实践
  193. 193附录五:如何调试 Docker
  194. 194附录六:资源链接
  195. 195附录七:术语表
  196. 196附录八:Docker 学习路线图与知识体系
第 153 / 196 节

第 153 节 · SECTION 153

18.6 容器镜像安全扫描与供应链安全

18.6 容器镜像安全扫描与供应链安全

在 DevOps 流程中,容器镜像安全已经成为不容忽视的关键环节。从开发、构建、存储到部署,镜像的整个生命周期都需要安全防护。本节深入讨论镜像漏洞扫描、软件物料清单(SBOM)、镜像签名验证等供应链安全实践。

18.6.1 容器镜像漏洞扫描工具对比

Trivy - 轻量级通用扫描器

Trivy 是由 Aqua Security 开发的开源漏洞扫描器,以其轻量级、快速、准确而闻名,已成为业界标准。

优点:

  • 零依赖,单个二进制文件
  • 扫描速度快(秒级)
  • 支持镜像、文件系统、Git 仓库多种扫描源
  • 数据库每日自动更新
  • 支持多种输出格式(JSON、表格、SBOM 等)

安装与基本使用:

curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh -o install-trivy.sh
less install-trivy.sh  # 先审阅脚本内容
sudo sh install-trivy.sh -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 - 支持多种软件包的扫描器

Grype 由 Anchore 开发,支持更广泛的软件包管理器和语言。

优点:

  • 支持 Java、Python、Go、Ruby、JavaScript 等多种语言的依赖检测
  • 与 Syft(SBOM 生成器)配合效果好
  • 可自定义漏洞数据库源
  • 支持离线扫描模式

安装与使用:

# 安装 Grype
curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh -o install-grype.sh
less install-grype.sh
sudo sh install-grype.sh -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 提供了商业级的安全扫描服务,特别适合企业环境。

特点:

  • 支持开源漏洞、许可证扫描和修复建议
  • 与多个 Git 平台深度集成(GitHub、GitLab、Bitbucket)
  • 提供修复建议和自动化修复 PR
  • 支持 Kubernetes 部署后安全监控

基本使用:

# 安装 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 集成 ✓ ✓ ✓ ✓

18.6.2 SBOM(软件物料清单)生成与管理

SBOM(Software Bill of Materials)是一份详细列表,记录了软件中使用的所有组件、依赖库及其版本信息。SBOM 在供应链安全中至关重要,特别是在发现新的安全漏洞时,能快速定位受影响的应用。

Syft - SBOM 生成工具

Syft 是 Anchore 推出的专业 SBOM 生成工具。

安装:

curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh -o install-syft.sh
less install-syft.sh
sudo sh install-syft.sh -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

CycloneDX 与 SPDX 格式

两种主流的 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/openssl@1.1.1k-1+deb11u5</purl>
    </component>
    <component type="library">
      <name>curl</name>
      <version>7.74.0-1.3+deb11u1</version>
      <purl>pkg:deb/debian/curl@7.74.0-1.3+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"
    }
  ]
}

SBOM 的应用场景

漏洞关联:

当新的 CVE 被发现时,可快速查询受影响的应用:

# 使用 Grype 针对 SBOM 进行漏洞扫描
grype sbom:sbom.json --add-cpes-if-none

合规性报告:

将 SBOM 保存为构建产物,用于审计和合规性检查。

依赖升级决策:

通过分析 SBOM 中的依赖版本,制定安全升级计划。

18.6.3 镜像签名与验证

镜像签名确保镜像的来源可信且未被篡改。新项目通常优先评估 Cosign (Sigstore) 或 Notary Project 的 Notation;Docker Content Trust / Notary v1 属于历史路径。

Cosign - 现代签名解决方案

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

签名镜像:

# 先推送镜像,再使用不可变 digest 签名
IMAGE_DIGEST="myregistry.com/myapp@sha256:<digest>"
cosign sign --key cosign.key "$IMAGE_DIGEST"

# 系统会提示输入私钥密码

验证签名:

# 使用公钥验证
cosign verify --key cosign.pub "$IMAGE_DIGEST"

# 输出结果示例
# Verification successful!
# {
#   "critical": {
#     "identity": {...},
#     "image": {...},
#     "type": "cosign container image signature"
#   },
#   "optional": {...}
# }

Keyless 签名(推荐用于 CI/CD):

# 在 GitHub Actions 等 CI 中无需存储密钥
cosign sign --yes "$IMAGE_DIGEST"

# 验证时检查签名证书中的 OIDC 身份声明是否匹配预期 workflow 与 issuer
cosign verify "$IMAGE_DIGEST" \
  --certificate-identity https://github.com/myorg/myrepo/.github/workflows/build.yml@refs/heads/main \
  --certificate-oidc-issuer https://token.actions.githubusercontent.com

Notary Project / Notation

Notation 是 Notary Project 的 OCI 签名工具,适合希望采用 CNCF Notary Project 规范、或使用 registry/云厂商原生集成的团队。与 Cosign 类似,生产环境应围绕不可变 digest 做签名和策略校验。

# 签名已推送的镜像 digest
notation sign myregistry.com/myapp@sha256:<digest>

# 验证签名
notation verify myregistry.com/myapp@sha256:<digest>

Docker Content Trust 与 Notary v1

注意:DCT 退役时间线

Docker 已宣布退役 Content Trust,Docker Official Images 的部分 DCT 签名证书自 2025 年 8 月起已陆续过期;根据 Docker 官方退役说明,Notary v1 服务 notary.docker.io 在 2026 年 7 月、8 月安排若干次 brownout(短时停服演练)之后,于 2026 年 12 月 8 日完全关闭。registry 服务商的具体节奏以其自身文档为准,不要把云厂商特定日期直接当作 Docker 全局时间线。

新项目应优先使用上文介绍的 Cosign (Sigstore) 或 registry 原生签名/证明能力;现有 DCT 用户应先盘点依赖、验证替代方案,再制定迁移计划。

Docker Content Trust 使用 Notary v1 实现镜像签名,是 Docker 官方的传统签名解决方案,不建议新项目采用。自 Docker Engine 29.0 起,Docker Content Trust 已从 Docker CLI 中移除:docker trust 子命令不再内置(官方仅保留为可单独构建的插件),DOCKER_CONTENT_TRUST 环境变量不再生效,docker push --disable-content-trust 也仅作为已废弃选项保留、不再有实际作用。下面的示例仅适用于 Docker Engine 28.x 及更早版本。

历史用法示例(不建议新项目采用):

# 在环境中启用 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/

# 查看 DCT/Notary 信任数据;RepoDigests 只是内容摘要,不等于签名验证
docker trust inspect --pretty myregistry.com/myapp:v1.0.0

18.6.4 供应链安全最佳实践

1. 基础镜像安全

# ❌ 不推荐:使用 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 --no-install-recommends curl \
    && rm -rf /var/lib/apt/lists/*

2. 构建时扫描

在 CI/CD 构建阶段集成安全扫描,避免在 Dockerfile 里从分支 URL 下载并执行远程安装脚本:

docker run --rm \
  -v "$PWD:/work" \
  -w /work \
  aquasec/trivy:latest \
  fs . --exit-code 1 --severity HIGH,CRITICAL

3. 运行时镜像扫描策略

# 镜像构建完成后立即扫描
trivy image --severity HIGH,CRITICAL \
  --exit-code 1 \
  --timeout 30m \
  $IMAGE_NAME:$IMAGE_TAG

# 定期扫描已部署的镜像
trivy image --scanners vuln,misconfig registry:5000/myapp:latest

4. 镜像仓库安全配置

Harbor(私有镜像仓库)的安全扫描:

Harbor 的扫描器应在管理界面的 Interrogation Services / Scanner 中配置;推送自动扫描通常在项目级 Configuration 中启用。不要把扫描策略误写成通用 harbor.yml 顶层字段。实际部署时应按所用 Harbor 版本文档配置 scanner、项目策略和漏洞阻断阈值。

5. 政策执行

在 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

18.6.5 CI/CD 中集成安全扫描

GitHub Actions 工作流示例

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
      id-token: write

    # 注意:本示例其余 action 为便于阅读使用了可变标签(@v7 等),这与下面 Trivy 那条
    # 注释里「务必使用不可变 commit SHA」的要求并不一致——真正部署时**每一条 uses 都应
    # 钉死 40 位 commit SHA**,尤其是持有凭据的 docker/login-action 与能推镜像的
    # docker/build-push-action:标签一旦被劫持,攻击者拿到的就是本 job 的
    # packages: write 与 id-token: write 权限,可以推送恶意镜像并为其签名。
    # 本书仓库自己的 .github/workflows/ 即全部按 SHA 钉死,可作为参照。
    steps:
    - name: Checkout code
      uses: actions/checkout@v7

    - name: Set up Docker Buildx
      uses: docker/setup-buildx-action@v4

    - name: Install Cosign
      if: github.event_name == 'push'
      # cosign-installer 没有发布可移动的 v4 大版本标签,只能引用完整版本号;
      # 安装 Cosign v3+ 必须使用 v4 及以后的 installer,v3 系列只能安装 Cosign 2.x。
      uses: sigstore/cosign-installer@v4.1.2

    - name: Login to Registry
      if: github.event_name == 'push'
      uses: docker/login-action@v4
      with:
        registry: ${{ env.REGISTRY }}
        username: ${{ github.actor }}
        password: ${{ secrets.GITHUB_TOKEN }}

    - name: Build and push Docker image
      if: github.event_name == 'push'
      id: build-push
      uses: docker/build-push-action@v7
      with:
        context: .
        push: true
        tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
        provenance: mode=max
        sbom: true

    - name: Build Docker image for pull request scan
      if: github.event_name == 'pull_request'
      uses: docker/build-push-action@v7
      with:
        context: .
        push: false
        load: true
        tags: local/${{ env.IMAGE_NAME }}:pr

    - 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: ${{ github.event_name == 'push' && format('{0}/{1}@{2}', env.REGISTRY, env.IMAGE_NAME, steps.build-push.outputs.digest) || format('local/{0}:pr', env.IMAGE_NAME) }}
        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: ${{ github.event_name == 'push' && format('{0}/{1}@{2}', env.REGISTRY, env.IMAGE_NAME, steps.build-push.outputs.digest) || format('local/{0}:pr', env.IMAGE_NAME) }}
        format: cyclonedx-json
        output-file: sbom-cyclonedx.json

    - name: Upload SBOM
      uses: actions/upload-artifact@v7
      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 }}@${{ steps.build-push.outputs.digest }}

GitLab CI 工作流示例

stages:
  - build
  - scan
  - push
  - sign

variables:
  REGISTRY: registry.gitlab.com
  IMAGE_NAME: $REGISTRY/$CI_PROJECT_PATH

build:
  stage: build
  image: docker:<version>-cli@sha256:<docker-cli-digest>
  services:
    - name: docker:<version>-dind@sha256:<docker-dind-digest>
  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:<version>@sha256:<trivy-digest>
  script:
    - trivy image --severity HIGH,CRITICAL --exit-code 1 docker-archive://image.tar
  allow_failure: false

scan:grype:
  stage: scan
  image: anchore/grype:<version>@sha256:<grype-digest>
  script:
    - grype docker-archive://image.tar

generate:sbom:
  stage: scan
  image: anchore/syft:<version>@sha256:<syft-digest>
  script:
    - syft docker-archive://image.tar -o cyclonedx > sbom.xml
  artifacts:
    reports:
      cyclonedx: sbom.xml

# 如需让 GitLab 安全面板摄取容器扫描结果,应输出 GitLab 兼容的 container_scanning 报告。

push:
  stage: push
  image: docker:<version>-cli@sha256:<docker-cli-digest>
  services:
    - name: docker:<version>-dind@sha256:<docker-dind-digest>
  script:
    - docker load < image.tar
    - echo "$REGISTRY_PASSWORD" | docker login --username "$REGISTRY_USER" --password-stdin "$REGISTRY"
    - docker push $IMAGE_NAME:$CI_COMMIT_SHA
    - DIGEST=$(docker buildx imagetools inspect "$IMAGE_NAME:$CI_COMMIT_SHA" --format '{{.Manifest.Digest}}')
    - echo "$IMAGE_NAME@$DIGEST" > image-digest.txt
  artifacts:
    paths:
      - image-digest.txt
  only:
    - main

sign:
  stage: sign
  image: gcr.io/projectsigstore/cosign:<version>@sha256:<cosign-digest>
  script:
    - cosign sign --key $COSIGN_KEY "$(cat image-digest.txt)"
  only:
    - main

18.6.6 常见问题与最佳实践

Q: 扫描报告中有过时的 CVE,如何处理?

A: 某些 CVE 可能已经被修复但数据库未更新,可以:

  • 手动验证安全补丁是否已应用
  • 使用工具的忽略列表功能(如 Trivy 的 .trivyignore)
  • 定期更新扫描工具和漏洞数据库

Q: 如何平衡镜像大小和安全性?

A:

  • 使用多阶段构建减少最终镜像大小
  • 使用精简基础镜像(Alpine、Distroless)
  • 定期更新依赖而不是一味求小
  • 优先安全性,体积次之

Q: 如何管理和轮换签名密钥?

A:

  • 在密钥管理系统(如 HashiCorp Vault)中存储密钥
  • 定期轮换密钥(建议每 90 天)
  • 使用 Keyless 签名消除密钥管理复杂性
  • 保留密钥轮换的审计日志