痛点:镜像上线了才发现有高危漏洞
你一定遇到过这种场景:业务镜像推到生产环境后,安全团队扫出一堆 CVE,紧急回滚、重新构建、再上线,一来一回半天没了。
问题根源很清楚——安全扫描没有左移到 CI/CD 流水线中。大多数团队的镜像构建流程是 docker build → push → deploy,中间完全没有漏洞检测环节。等到运行时才扫描,成本和风险都是最高的。
方案:Trivy + CI/CD = 自动化镜像安全门禁
Trivy 是 Aqua Security 开源的全能安全扫描器,当前最新版本 v0.69.x。它支持容器镜像、文件系统、Git 仓库、Kubernetes 集群等多种扫描目标,覆盖 OS 包漏洞、应用依赖漏洞、IaC 配置错误和密钥泄漏检测。
选 Trivy 的理由:
| 对比维度 | Trivy | Clair | Snyk Container |
|---|---|---|---|
| 安装复杂度 | 单二进制,无依赖 | 需部署 PostgreSQL | SaaS + CLI |
| 扫描速度(首次) | ~15s | ~60s | ~30s |
| 离线漏洞库 | 支持 | 不支持 | 不支持 |
| 开源协议 | Apache 2.0 | Apache 2.0 | 商业 |
| IaC/密钥扫描 | 内置 | 不支持 | 部分支持 |
核心思路:在 CI 流水线的 build 和 push 之间插入 Trivy 扫描环节,发现 HIGH/CRITICAL 级别漏洞直接阻断流水线,镜像不允许推送到仓库。
实操步骤
第 1 步:安装 Trivy
# 方式一:脚本安装(推荐服务器/CI Runner 使用)
curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin
# 验证
trivy version
# 方式二:Docker 方式运行(无需安装)
docker run --rm aquasec/trivy:latest image alpine:3.19
第 2 步:扫描本地镜像并设置严重级别过滤
# 构建镜像
docker build -t myapp:v1.2.0 .
# 扫描,仅关注 HIGH 和 CRITICAL
trivy image --severity HIGH,CRITICAL myapp:v1.2.0
# 关键参数:--exit-code 1 表示发现漏洞返回非零退出码
# CI 流水线中用这个参数来阻断后续步骤
trivy image --severity HIGH,CRITICAL --exit-code 1 myapp:v1.2.0
输出示例:
myapp:v1.2.0 (debian 12.5)
Total: 3 (HIGH: 2, CRITICAL: 1)
┌──────────────┬────────────────┬──────────┬───────────────┬─────────────────┐
│ Library │ Vulnerability │ Severity │ Installed │ Fixed Version │
├──────────────┼────────────────┼──────────┼───────────────┼─────────────────┤
│ libssl3 │ CVE-2024-XXXXX │ CRITICAL │ 3.0.11-1 │ 3.0.13-1 │
│ curl │ CVE-2024-YYYYY │ HIGH │ 7.88.1-10+d12 │ 7.88.1-10+d12u2 │
│ zlib │ CVE-2024-ZZZZZ │ HIGH │ 1:1.2.13-1 │ 1:1.2.13-1+deb │
└──────────────┴────────────────┴──────────┴───────────────┴─────────────────┘
第 3 步:集成到 GitHub Actions 流水线
# .github/workflows/build.yml
name: Build & Security Scan
on:
push:
branches: [main]
pull_request:
jobs:
build-and-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build image
run: docker build -t myapp:${{ github.sha }} .
- name: Run Trivy scan
uses: aquasecurity/trivy-action@master
with:
image-ref: myapp:${{ github.sha }}
severity: HIGH,CRITICAL
exit-code: '1' # 发现高危漏洞阻断流水线
format: table
ignore-unfixed: true # 忽略暂无修复版本的漏洞
- name: Push image (仅扫描通过后执行)
if: success()
run: |
docker tag myapp:${{ github.sha }} registry.example.com/myapp:${{ github.sha }}
docker push registry.example.com/myapp:${{ github.sha }}
关键配置说明:
exit-code: '1'— 扫描到指定级别漏洞时返回退出码 1,阻断流水线ignore-unfixed: true— 上游还没出补丁的漏洞先忽略,减少噪音severity: HIGH,CRITICAL— 只关注高危和严重级别,MEDIUM/LOW 不阻断
第 4 步:生成 JSON 报告 + 配合 .trivyignore 白名单
实际项目中一定有"已知漏洞暂时无法修复"的情况,用 .trivyignore 文件管理白名单:
# .trivyignore — 白名单文件,放在项目根目录
# 格式:每行一个 CVE ID,可加注释
CVE-2024-XXXXX # libssl 等待上游修复,预计 Q2 发布补丁
CVE-2024-YYYYY # 内网环境不暴露此端口,风险可控
生成 JSON 格式报告用于存档或接入安全平台:
trivy image --severity HIGH,CRITICAL \
--format json \
--output trivy-report.json \
--ignorefile .trivyignore \
myapp:v1.2.0
# 用 jq 快速查看漏洞统计
cat trivy-report.json | jq '[.Results[].Vulnerabilities[]?.Severity] | group_by(.) | map({(.[0]): length}) | add'
3 个常踩的坑
坑 1:首次扫描特别慢(3-5 分钟)
Trivy 首次运行需要下载漏洞数据库(约 40MB),CI 环境中每次跑都重新下载会拖慢流水线。解决办法——缓存漏洞库:
# GitHub Actions 中缓存 Trivy DB
- uses: actions/cache@v4
with:
path: ~/.cache/trivy
key: trivy-db-${{ github.run_id }}
restore-keys: trivy-db-
坑 2:ignore-unfixed 不加导致大量误报
很多基础镜像(Debian、Ubuntu)的系统包存在上游尚未修复的 CVE。如果不加 --ignore-unfixed,扫描结果会充斥无法处理的漏洞,团队很快就会"告警疲劳"直接忽略安全报告。建议默认开启,同时定期(每月)关闭此选项做一次全量审计。
坑 3:用 latest 标签扫描结果不可复现
trivy image nginx:latest 在不同时间扫描结果可能完全不同,因为 latest 指向的镜像会变。CI 中务必使用明确的版本标签或 SHA digest:
# ✅ 正确:用 digest 固定镜像版本
trivy image nginx@sha256:a1b2c3d4e5f6...
# ❌ 错误:latest 标签无法复现
trivy image nginx:latest
总结
容器镜像安全扫描的核心思路就是左移——把安全检测从运行时提前到构建时:
- 安装零门槛:Trivy 单二进制部署,5 分钟接入现有 CI/CD
- 策略可控:通过
--severity+--exit-code精确控制阻断级别 - 噪音可管:
.trivyignore+--ignore-unfixed两层过滤避免告警疲劳 - 报告可追溯:JSON 格式输出接入安全平台,形成漏洞管理闭环
把 Trivy 扫描加到流水线里,投入不到 1 小时,但能拦截绝大多数已知漏洞进入生产环境。比起事后应急响应,这是性价比最高的安全投入。