harbor 接入 trivy 漏洞扫描, 用到的开源工具 harbor-scanner-trivy
安装
需要依赖 Redis, 先安装 Redis
安装 trivy
trivy 是扫描核心组件, 需要安装,参考 trivy 官网安装文档
https://aquasecurity.github.io/trivy/v0.41/getting-started/installation/
1 2 3 4 5 6 7 8 9 10
| RELEASE_VERSION=$(grep -Po '(?<=VERSION_ID=")[0-9]' /etc/os-release) cat << EOF | sudo tee -a /etc/yum.repos.d/trivy.repo [trivy] name=Trivy repository baseurl=https://aquasecurity.github.io/trivy-repo/rpm/releases/$RELEASE_VERSION/\$basearch/ gpgcheck=1 enabled=1 gpgkey=https://aquasecurity.github.io/trivy-repo/rpm/public.key EOF sudo yum -y install trivy
|
trivy 首次运行会从 github 下载漏洞数据库,需要确保机器可以连接 GitHub,执行 trivy image --download-db-only 会下载 db,db 数据存储在 ~/.cache/trivy
安装 scanner-trivy
开源地址:https://github.com/aquasecurity/harbor-scanner-trivy
scanner-trivy 是通过环境变量读取配置
启动命令:
1
| SCANNER_API_SERVER_ADDR=:8181 SCANNER_REDIS_URL=redis://localhost:6379 ./scanner-trivy
|
或者通过 supervisor 运行
1 2 3 4 5 6 7 8 9 10 11
| [program:trivy] numprocs=1 user=root command=/data/server/trivy/scanner-trivy directory=/data/server/trivy/ redirect_stderr=true stdout_logfile=/data/logs/trivy.log autostart=true autorestart=true startsecs=10 environment=SCANNER_API_SERVER_ADDR=:8181,SCANNER_REDIS_URL=redis://localhost:6379
|
在 Harbor 里使用
harbor 系统管理 审查服务 扫描器 里面添加 trivy 地址:

然后就可以正常扫描镜像了

集成到其他地方
trivy 可以支持 json 输出,可以自定义输出的格式,比如生成 html:
1 2 3 4
| wget https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/html.tpl
trivy image --format template --template "@./html.tpl" -o report.html nginx:latest
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277
| <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Trivy 漏洞扫描报告</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Helvetica, Arial, sans-serif; background: #f0f2f5; color: #1a1a2e; line-height: 1.5; }
.container { max-width: 1400px; margin: 0 auto; padding: 24px; }
.header { background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%); border-radius: 20px; padding: 32px; margin-bottom: 24px; color: white; box-shadow: 0 10px 30px rgba(0,0,0,0.1); }
.header h1 { font-size: 28px; font-weight: 600; margin-bottom: 12px; }
.header .subtitle { opacity: 0.8; font-size: 14px; }
.stats-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 16px; margin-bottom: 32px; }
.stat-card { background: white; border-radius: 16px; padding: 20px; text-align: center; box-shadow: 0 2px 8px rgba(0,0,0,0.06); transition: transform 0.2s; }
.stat-card:hover { transform: translateY(-2px); }
.stat-number { font-size: 32px; font-weight: 700; margin-bottom: 8px; }
.stat-label { color: #666; font-size: 14px; }
.severity-CRITICAL { background: #dc2626; color: white; } .severity-HIGH { background: #f97316; color: white; } .severity-MEDIUM { background: #eab308; color: #1a1a2e; } .severity-LOW { background: #22c55e; color: white; } .severity-UNKNOWN { background: #9ca3af; color: white; }
.table-wrapper { background: white; border-radius: 20px; box-shadow: 0 4px 12px rgba(0,0,0,0.08); overflow-x: auto; margin-bottom: 24px; }
.table-wrapper h2 { font-size: 20px; font-weight: 600; padding: 20px 24px 12px 24px; border-bottom: 1px solid #e5e7eb; }
.target-info { font-size: 14px; color: #6b7280; font-weight: normal; margin-left: 12px; }
table { width: 100%; border-collapse: collapse; font-size: 13px; }
th { background: #f9fafb; padding: 14px 12px; text-align: left; font-weight: 600; color: #374151; border-bottom: 1px solid #e5e7eb; position: sticky; top: 0; }
td { padding: 12px; border-bottom: 1px solid #f0f0f0; vertical-align: top; }
tr:hover { background: #fafafa; }
.vuln-link { color: #2563eb; text-decoration: none; font-weight: 500; }
.vuln-link:hover { text-decoration: underline; }
.severity-badge { display: inline-block; padding: 4px 10px; border-radius: 20px; font-size: 12px; font-weight: 600; text-align: center; min-width: 70px; }
.fix-available { color: #10b981; font-weight: 500; }
.fix-not-available { color: #f97316; }
.footer { text-align: center; padding: 20px; color: #6b7280; font-size: 12px; }
@media (max-width: 768px) { .container { padding: 16px; } th, td { padding: 8px; font-size: 11px; } .stat-number { font-size: 24px; } } </style> </head> <body> <div class="container"> <div class="header"> <h1>🐳 Trivy 容器镜像安全扫描报告</h1> <div class="subtitle">扫描时间: {{ now | date "2006-01-02 15:04:05" }}</div> </div>
{{ $totalCritical := 0 }} {{ $totalHigh := 0 }} {{ $totalMedium := 0 }} {{ $totalLow := 0 }} {{ $totalUnknown := 0 }}
{{ range . }} {{ range .Vulnerabilities }} {{ if eq .Severity "CRITICAL" }}{{ $totalCritical = add $totalCritical 1 }}{{ end }} {{ if eq .Severity "HIGH" }}{{ $totalHigh = add $totalHigh 1 }}{{ end }} {{ if eq .Severity "MEDIUM" }}{{ $totalMedium = add $totalMedium 1 }}{{ end }} {{ if eq .Severity "LOW" }}{{ $totalLow = add $totalLow 1 }}{{ end }} {{ if eq .Severity "UNKNOWN" }}{{ $totalUnknown = add $totalUnknown 1 }}{{ end }} {{ end }} {{ end }}
<div class="stats-grid"> <div class="stat-card"> <div class="stat-number" style="color: #dc2626;">{{ $totalCritical }}</div> <div class="stat-label">🔴 严重 (CRITICAL)</div> </div> <div class="stat-card"> <div class="stat-number" style="color: #f97316;">{{ $totalHigh }}</div> <div class="stat-label">🟠 高危 (HIGH)</div> </div> <div class="stat-card"> <div class="stat-number" style="color: #eab308;">{{ $totalMedium }}</div> <div class="stat-label">🟡 中危 (MEDIUM)</div> </div> <div class="stat-card"> <div class="stat-number" style="color: #22c55e;">{{ $totalLow }}</div> <div class="stat-label">🟢 低危 (LOW)</div> </div> </div>
<div class="table-wrapper"> <h2>📋 漏洞详情列表</h2> <table> <thead> <tr> <th>#</th> <th>漏洞编号 (CVE)</th> <th>软件包 / 组件</th> <th>已安装版本</th> <th>严重程度</th> <th>文件位置</th> <th>修复建议</th> </tr> </thead> <tbody> {{ $idx := 0 }} {{ range $result := . }} {{ range $vuln := $result.Vulnerabilities }} {{ $idx = add $idx 1 }} <tr> <td style="color:#6b7280;">{{ $idx }}</td> <td><a href="{{ $vuln.PrimaryURL }}" target="_blank" class="vuln-link">{{ $vuln.VulnerabilityID }}</a></td> <td><code>{{ $vuln.PkgName }}</code></td> <td><code>{{ $vuln.InstalledVersion }}</code></td> <td><span class="severity-badge severity-{{ $vuln.Severity }}">{{ $vuln.Severity }}</span></td> <td><code style="font-size:11px; word-break:break-all;">{{ if $vuln.PkgPath }}{{ $vuln.PkgPath }}{{ else }}-{{ end }}</code></td> <td>{{ if $vuln.FixedVersion }}<span class="fix-available">✅ 升级至 {{ $vuln.FixedVersion }}</span>{{ else }}<span class="fix-not-available">⚠️ 暂无可用修复版本</span>{{ end }}</td> </tr> {{ end }} {{ end }} {{ if eq $idx 0 }} <tr> <td colspan="7" style="text-align: center; padding: 48px;"> 🎉 未发现任何漏洞,镜像安全! </td> </tr> {{ end }} </tbody> </table> </div>
<div class="footer"> <p>报告生成时间: {{ now | date "2006-01-02 15:04:05" }} | 使用 Trivy 扫描</p> </div> </div> </body> </html>
|
其他
使用国内镜像更新
借助 oras 来拉取镜像, 需要先安装 oras
1 2 3 4
| trivy image --download-db-only --db-repository public.ecr.aws/aquasecurity/trivy-db
trivy image --download-java-db-only --java-db-repository public.ecr.aws/aquasecurity/trivy-java-db
|
数据库文件默认位置:
Linux: ~/.cache/trivy
macOS: ~/Library/Caches/trivy
使用 crontab 执行此脚本, 一天一次即可。