如何在 Ubuntu 24.04 上使用 Trivy 扫描 Docker 镜像漏洞(含离线数据库配置)

4 阅读10分钟

简介

容器镜像中的安全漏洞是现代云基础设施中最常见的攻击媒介之一。即使是一个"干净"的应用镜像,也可能从其基础操作系统软件包或语言依赖项中继承数百个已知漏洞。在镜像到达生产环境之前对其进行扫描,是至关重要的防御层。

Trivy 是由 Aqua Security 维护的开源漏洞扫描工具。与更重量级的企业工具不同,Trivy 不需要独立的服务器或数据库守护进程——它以单一二进制文件运行,可以扫描 Docker 镜像、文件系统、Git 仓库以及 Kubernetes 集群。它检查多个漏洞数据库,包括美国国家漏洞数据库(NVD)、GitHub Advisory Database,以及操作系统专属来源,如 Debian Security Tracker 和 Red Hat CVE 数据。

在本教程中,你将:

  • 在 Ubuntu 24.04 服务器上安装 Trivy
  • 扫描 Docker 镜像并解读扫描结果
  • 按严重程度过滤扫描结果,并使用退出码在 CI/CD 流水线中对关键发现执行失败操作
  • 手动下载漏洞数据库,并配置 Trivy 在隔离网络(离线)环境中运行——这在生产服务器无法访问互联网的企业网络中非常普遍

完成本教程后,你将能够将 Trivy 集成到交互式工作流和自动化流水线中,无论是否具有互联网访问权限。


前置条件

开始之前,你需要:

  • 一台运行 Ubuntu 24.04 的服务器,且有一个具有 sudo 权限的非 root 用户。可参照 Ubuntu 24.04 初始服务器设置 进行配置。
  • 服务器上已安装并运行 Docker。如需安装,请参考 如何在 Ubuntu 24.04 上安装和使用 Docker
  • 至少 2 GB 的可用磁盘空间,用于存放 Trivy 漏洞数据库。
  • (仅第 5 步需要) 一台能够访问互联网的独立机器,用于下载数据库文件后传输到隔离服务器。

第一步 — 安装 Trivy

Trivy 为 Debian 系系统提供了官方 APT 仓库,这是推荐的安装方式,可确保你自动收到安全更新。

首先,安装通过 HTTPS 添加新 APT 仓库所需的软件包:

sudo apt-get update
sudo apt-get install -y wget apt-transport-https gnupg

注意: 如果你使用的是 Ubuntu 最小化安装镜像,或在 Docker 容器内执行这些步骤,可能还需要安装 ca-certificates,以确保 wget 能够正常验证 HTTPS 连接。可将其添加到上方命令中:sudo apt-get install -y wget apt-transport-https gnupg ca-certificates这让 APT 能够验证来自该仓库的软件包是可信的:

wget -qO - https://aquasecurity.github.io/trivy-repo/deb/public.key | gpg --dearmor | sudo tee /usr/share/keyrings/trivy.gpg > /dev/null

将 Trivy 仓库添加到系统源列表:

echo "deb [signed-by=/usr/share/keyrings/trivy.gpg] https://aquasecurity.github.io/trivy-repo/deb generic main" | sudo tee /etc/apt/sources.list.d/trivy.list

更新 APT 并安装 Trivy:

sudo apt-get update
sudo apt-get install -y trivy

通过检查版本验证安装是否成功:

trivy --version

你应该看到类似如下的输出:

Version: 0.58.0

具体版本号可能不同,但只要打印出版本号,说明 Trivy 已正确安装。现在你可以运行第一次扫描了。


第二步 — 运行第一次镜像扫描

安装好 Trivy 后,你可以扫描任何 Docker 镜像——无论它是存储在本地,还是在 Docker Hub 或其他镜像仓库上。

拉取一个用于扫描的示例镜像。本教程使用一个较旧版本的 Python 镜像,该版本已知包含漏洞,非常适合用于学习:

docker pull python:3.8-slim

现在对其运行 Trivy:

trivy image python:3.8-slim

Trivy 首先会更新其漏洞数据库(这需要互联网连接,第一次运行可能需要一分钟),然后扫描镜像。输出内容类似如下:

2024-11-01T10:12:05Z    INFO    Vulnerability scanning is enabled
2024-11-01T10:12:05Z    INFO    [vuln] Detected OS: debian 12.2

python:3.8-slim (debian 12.2)
==============================
Total: 83 (UNKNOWN: 0, LOW: 40, MEDIUM: 30, HIGH: 10, CRITICAL: 3)

┌───────────────┬────────────────┬──────────┬──────────────────────┬──────────────────┬──────────────────────────────────────────┐
    Library     Vulnerability   Severity    Installed Version    Fixed Version                     Title                  
├───────────────┼────────────────┼──────────┼──────────────────────┼──────────────────┼──────────────────────────────────────────┤
 libssl3        CVE-2024-5535   CRITICAL  3.0.11+dfsg-2+deb12                     openssl: SSL_select_next_proto buffer ... 
 ...            ...             ...       ...                   ...               ...                                      
└───────────────┴────────────────┴──────────┴──────────────────────┴──────────────────┴──────────────────────────────────────────┘

表格中的每一行代表一个漏洞。各列含义如下:

  • Library(库):镜像内包含该漏洞的软件包名称。
  • Vulnerability(漏洞):CVE 标识符(通用漏洞披露),你可以在 cve.mitre.org 上查阅详细技术信息。
  • Severity(严重程度):由 NVD 按 CRITICAL(严重)、HIGH(高危)、MEDIUM(中危)、LOW(低危)、UNKNOWN(未知)评级。
  • Installed Version(已安装版本):镜像中当前使用的软件包版本。
  • Fixed Version(修复版本):修复了此漏洞的版本。此字段为空意味着上游目前尚无修复版本。
  • Title(标题):对该漏洞允许攻击者执行操作的简短描述。

扫描结果为你提供了一份按优先级排列的待处理清单。下一步你将学习如何过滤输出,聚焦于最重要的问题。


第三步 — 按严重程度过滤结果

对复杂镜像进行完整扫描可能返回数百条结果。实际上,你很少需要在发布前处理每一个 LOW 级别的问题——但如果有修复可用,就绝对不应该在 CRITICAL 漏洞存在的情况下发布。Trivy 的 --severity 标志让你可以将输出缩小到你关心的风险级别。

仅显示 CRITICAL 和 HIGH 漏洞:

trivy image --severity CRITICAL,HIGH python:3.8-slim

这将产生一份更短、更具操作性的报告。注意严重级别之间用逗号分隔,不加空格。

为了进一步减少噪声,你还可以告诉 Trivy 跳过没有可用修复的漏洞。对于无法修复的漏洞,你能做的只是持续监控,因此在日常报告中过滤掉它们是常见做法:

trivy image --severity CRITICAL,HIGH --ignore-unfixed python:3.8-slim

--ignore-unfixed 标志会移除 Fixed Version 列为空的行。剩余结果全部是可以通过更新软件包来实际修复的漏洞——这是最具操作价值的报告格式。

注意: 即使你在自动报告中过滤了无修复漏洞,也应该定期运行完整扫描(不带 --ignore-unfixed),以监控新发布的修复版本。


第四步 — 使用退出码集成 CI/CD 流水线

默认情况下,无论是否发现漏洞,Trivy 都以退出码 0(成功)退出。这对手动审查来说没问题,但在自动化 CI/CD 流水线中,你希望在检测到严重漏洞时构建失败,以防止不安全的镜像被自动部署。

--exit-code 标志控制当 Trivy 发现符合过滤条件的漏洞时返回的退出码:

trivy image --severity CRITICAL --exit-code 1 --ignore-unfixed python:3.8-slim

运行后立即检查退出码:

echo $?

如果发现任何 CRITICAL 且有修复的漏洞,你将看到:

1

如果没有发现匹配的漏洞,输出将是:

0

GitHub Actions、GitLab CI 和 Jenkins 等 CI/CD 系统将非零退出码视为流水线失败。这意味着,如果你将此命令添加到流水线的构建或测试阶段,任何包含严重漏洞的镜像都会被自动阻止推送到镜像仓库或进行部署。

一个最简的 GitHub Actions 工作流步骤如下所示:

- name: Scan Docker image for critical vulnerabilities
  run: |
    trivy image \
      --severity CRITICAL,HIGH \
      --exit-code 1 \
      --ignore-unfixed \
      your-registry/your-image:latest

如果此步骤以退出码 1 退出,整个工作流将失败,后续的部署步骤将不会执行。


第五步 — 配置离线(隔离网络)数据库

在许多企业环境中,生产服务器被放置在没有直接互联网访问的网络段中。这使得 Trivy 无法自动下载漏洞数据库。在此步骤中,你将在一台能联网的机器上下载数据库,然后手动传输到离线服务器。

在能联网的机器上操作

Trivy 将其漏洞数据库存储为压缩的 OCI artifact。你可以使用 trivy 二进制文件本身来下载它。按照第一步的方法在能联网的机器上安装 Trivy,然后运行:

trivy image --download-db-only --cache-dir ./trivy-cache

--download-db-only 标志告诉 Trivy 只获取最新数据库然后停止——它不会执行扫描。--cache-dir 标志将数据库保存到本地目录(./trivy-cache)而非默认系统路径,便于收集后传输。

列出内容以确认数据库已下载:

ls ./trivy-cache/db/

你应该看到:

db.tar.gz   metadata.json

将此目录打包成 tar 包以便传输:

tar -czvf trivy-db-bundle.tar.gz ./trivy-cache

使用 scp 或你所在组织使用的任何安全文件传输方法,将文件传输到离线服务器:

scp trivy-db-bundle.tar.gz your_username@your_offline_server_ip:~

在离线服务器上操作

文件传输到离线服务器后,解压它:

tar -xzvf trivy-db-bundle.tar.gz

这将在你的主目录中重建 ./trivy-cache 目录。现在使用本地缓存运行扫描,并禁用自动更新:

trivy image \
  --cache-dir ~/trivy-cache \
  --skip-update \
  --offline-scan \
  python:3.8-slim

这三个标志协同工作以实现完全离线扫描:

  • --cache-dir ~/trivy-cache:将 Trivy 指向你传输的数据库,而非默认系统位置。
  • --skip-update:阻止 Trivy 尝试连接互联网更新数据库。
  • --offline-scan:禁用 Trivy 在扫描过程中可能进行的任何额外网络查询。

你应该看到与第二步相同的扫描输出,但这次没有任何网络活动。

注意: 漏洞数据库每天更新。安排定期将新的数据库包传输到离线环境——至少每周一次,如果你的安全要求较高则需每天更新——以确保扫描反映最新的已知漏洞。

若要在能联网的机器上自动刷新数据库,可以添加一个 cron 任务:

crontab -e

添加以下行,在每天凌晨 2:00 更新数据库:

0 2 * * * /usr/bin/trivy image --download-db-only --cache-dir /opt/trivy-cache && tar -czvf /opt/trivy-db-bundle.tar.gz /opt/trivy-cache

注意: 这里必须使用完整路径 /usr/bin/trivy,因为 cron 运行时使用极简的 PATH 环境变量,无法通过名称直接找到二进制文件。上述命令中的 /opt/trivy-cache 路径位于能联网的机器上,是 cron 任务运行的地方——与前面步骤中离线服务器上使用的 ~/trivy-cache 路径分属两台不同的机器,互不影响。

你的安全团队或基础设施自动化工具随后可以按计划从 /opt/trivy-db-bundle.tar.gz 获取更新的包并分发到离线服务器。


结论

在本教程中,你在 Ubuntu 24.04 上安装了 Trivy,扫描了一个 Docker 镜像,并学会了如何按严重程度解读 CVE 发现。你配置了严重程度过滤器和退出码,使扫描在 CI/CD 流水线中真正起到阻断作用,并使用手动下载的漏洞数据库为隔离网络环境建立了完全离线的工作流。

从这里出发,你可以在所学基础上向多个方向继续深入:

  • 在构建时扫描:在 GitHub Actions 或 GitLab CI 的 Dockerfile 构建流水线中添加 Trivy 扫描步骤,在镜像被推送到仓库之前就捕获漏洞。
  • 扫描 Kubernetes 集群:Trivy 可以使用 trivy k8s --report summary cluster 审计正在运行的工作负载,不仅检查镜像,还检查集群配置中的错误配置。
  • 生成 SBOM 报告:使用 trivy image --format cyclonedx 生成软件物料清单(SBOM),用于合规性审计和软件供应链安全。
  • 与 Harbor 集成:如果你使用 Harbor 作为私有镜像仓库,可以将 Trivy 配置为内置扫描器,在每次推送时自动扫描每个镜像。