☰
Harbor集成Trivy:镜像仓库漏洞扫描与安全阻断实战
2026/10/21 18:09:09 网站建设 项目流程

1. 项目背景:为什么要在镜像仓库上做漏洞扫描

先说说我为什么要折腾这件事。大家都知道,容器化走到今天,镜像仓库基本成了企业交付软件的最后一道关口。应用代码、基础镜像、第三方依赖,所有东西都堆在镜像里,最后由Harbor分发到生产环境。如果镜像里带着已知漏洞进去,等于把门锁坏了还挂上了“欢迎光临”的牌子。某次内部安全审计的时候,我们被问了一个问题——你们怎么保证跑在生产上的镜像没有高危漏洞?当时Harbor装是装了,但扫描功能一直没启用,说白了就是只存不管。

当时的处境很典型:团队不大,专职安全的人几乎没有,开发自己管自己的镜像,运维负责Harbor日常维护。审计要求又比较明确,必须能看到镜像漏洞清单,最好还能在镜像推送前就拦住一部分。这其实就是容器供应链安全里最刚需的一个环节——在镜像进入运行时之前,知道它里面有哪些已知CVE,评分有多高,有没有办法修。

调研了一圈,Harbor本身支持多种扫描器,常见的有Clair、Trivy、Anchore。让我下定决心选Trivy的原因有三个:第一,它扫描速度快,走的是离线CVE库加静态层解析的路线,不依赖容器运行时;第二,覆盖面广,操作系统基础包、编程语言依赖、应用组件都能扫;第三,Harbor从2.3版本开始对它做了内置,也就是说不需要单独搭一套扫描服务,配置起来省事不少。后面我会讲实际验证的细节,但先给结论:这个组合完全够中小团队用,甚至比单独部署一套商业扫描器更适合日常运转。

2. 方案选型:Trivy扫描器凭什么胜出

2.1 三板斧对比:Clair、Anchore、Trivy

Harbor的扫描架构其实很清晰:Harbor本身不做漏洞分析,它只是把镜像数据交给扫描器,扫描器返回结果,Harbor把结果存进自己的数据库,再通过UI展示。所以扫描器的选型直接决定了扫描速度、覆盖面、维护成本这三项指标的体验。

先看Clair。它是Harbor早期版本默认集成的扫描器,原理是先解析镜像各层,再通过API查询漏洞数据库。Clair的优势是能跟Harbor紧密配合,但缺点也比较明显——漏洞数据库更新需要自己跑更新器,数据源切换过几次,尤其是某些操作系统安全通告的解析一度不够及时,导致过扫描结果有滞后。

再看Anchore。这个项目功能很全,支持策略引擎、镜像分析、准入控制,可以说是企业级的安全体系。但它的部署复杂度真心不低,引擎、数据库、分析服务、策略服务,一套下来重量级相当可观。对一个只有几个节点、十来个人的团队来说,明显过度。

再看Trivy。它是一个单一二进制文件,扫描时直接对镜像的层做静态分析,不需要启动容器,也不需要docker daemon配合。CVE库放在本地或者对象存储里,可以手动更新也可以定时拉取。扫描逻辑简单粗暴——解包每一层,看里面的包管理数据库和依赖锁定文件,和CVE库比对。正因为这个设计,它在速度上通常比Clair快一个数量级。

三者的对比我放在一起看:

维度ClairAnchoreTrivy
部署复杂度中高极低
默认集成Harbor是需插件是(2.3+)
CVE库更新需单独维护需单独维护默认Harbor自动更新
扫描速度慢中快
覆盖层与依赖偏OS包全面全面
资源占用中高低

选Trivy还有一个现实原因:Harbor官方后来默认的扫描器模板就直接指向它,文档和社区问答大部分都在讲Trivy,排错遇到问题能搜到大量案例。做技术选型这件事,社区活跃度有时比产品本身的性能还重要。

2.2 Trivy的原理决定了它的维护成本低

Trivy扫描走的是三步路:第一步,从Registry拉取镜像manifest和层数据;第二步,对每一层解包,识别操作系统类型和版本,读取包管理器数据库,比如RPM、DPKG、APK这类;第三步,把包名和版本号与本地CVE库做匹配。对语言依赖也是一样道理,它会去找pom.xml、package-lock.json、go.sum这类文件,解析出依赖列表再比对。

这套机制最大的好处是扫描不依赖镜像能不能跑起来。有的镜像里没有bash、没有sh,甚至是个纯静态二进制,Trivy照样能扫,因为走的全是文件系统层面的分析。以前用Clair的时候,碰到过镜像层格式稍微特殊点就分析失败的情况,换到Trivy之后几乎没出现过这种问题。

还有一个很实用的点:Trivy对CVE库做了按年的数据库分离,默认数据存在/home/scanner/.cache/trivy这样的目录下,Harbor的扫描器容器会用持久化存储保存这份缓存。更新频率可以配置,默认是每小时检查一次。这意味着只要Harbor服务能正常出网,CVE库就能持续保持相对较新的状态,不用另外写cron任务。

3. 部署实践:Harbor集成Trivy完整步骤

3.1 环境准备与配置规划

先交代一下我的部署环境。我这边是离线环境为主的机房,Harbor跑在一台Ubuntu 22.04虚拟机上,8核16G内存,数据盘单独挂了一块500G。Harbor本身用Docker Compose方式安装,版本当时选的是2.8.5。Trivy的扫描器功能是Harbor内置的,不需要额外装Trivy二进制,但需要注意Harbor可以通过两种方式接入扫描器:一种是内置的Trivy服务,另一种是外部扫描器API接入。

环境规划上有一个容易踩坑的地方:Harbor的配置项很多,改动/安装完成后是以docker-compose方式启动一组容器,包括core、portal、registry、database、jobservice等。如果之前装过老版本,升级或者改动配置时,prepare这个命令生成配置的环节特别容易出问题。我的建议是,做任何改动前先把整个Harbor目录备份一份,尤其是harbor.yml和common目录下的生成物。

3.2 配置harbor.yml启用Trivy

Harbor的扫描器配置其实不在安装向导里直接询问,而是靠在harbor.yml里定义的。首次安装或者已有安装都适用。如果你用的是HTTPS,先确保证书路径配置正确,不然后面访问API和推送镜像都会出问题。

一个比较典型的harbor.yml中与扫描相关的配置段长这样:

# Trivy扫描配置 trivy: # 扫描漏洞的严重级别策略 ignore_unfixed: false # 超时时间,单位秒 timeout: 300 # CVE数据库更新间隔,单位小时,0表示不自动更新 skip_update: false # 离线环境可设为true,使用离线漏洞库 offline_scan: false # 扫描器容器的资源限制 resources: requests: cpu: '0.4' memory: 512Mi limits: cpu: '1' memory: 1Gi

ignore_unfixed很关键。如果你设为true,扫描结果里只显示有修复版本的漏洞,没修复的会直接省略。这个选项在安全审计时容易引起疑问——明明CVE库里有一条严重漏洞,为什么扫不出来?多半就是这个开关导致的。我的建议是保持默认的false,让所有漏洞都展示,再在Harbor的扫描策略里按项目维度去设置可接受的漏洞数量上限,这样更可控。

配置完harbor.yml后,不要直接去改docker-compose.yml,正确的做法是:

# 重新生成配置文件 sudo ./install.sh

或者如果你已经安装过了,只想重新应用配置:

sudo ./prepare sudo docker compose up -d

重新prepare时,Harbor会重新生成common/config下的所有配置文件,包括Trivy扫描器的资源配置、存储挂载等。这一步结束后,用docker compose ps确认所有容器状态正常。

3.3 首次启动Trivy容器的等待

首次启动的时候,Trivy扫描器容器会做两件耗时的事:一是从Harbor的内置下载服务拉取CVE数据库,二是把数据库解压到持久化存储。这个阶段你会在docker compose logs trivy-0里看到大量下载和解压的日志。

我踩过的坑是在第一次启动后半小时内就急着去扫镜像,结果扫描任务一直卡在Queued状态。后来看日志发现是CVE库还没准备好。正常情况下,CVE数据库大概会有1GB左右的体积,视网络情况可能需要几分钟到半小时不等。要判断是否就绪,可以看扫描器容器的日志有没有出现类似DB update completed的条目,或者直接看容器日志的heaalthcheck。

判断Trivy是否就绪,可以直接请求它的API地址做health检查,Harbor同样会周期性地报告扫描器的状态。在Harbor页面里,进入“系统管理”-“扫描器”,如果看到Trivy的状态是正常,说明可以开始扫描了。

4. 扫描配置与日常使用:从手动扫描到强制策略

4.1 手动扫描与全仓库扫描

Harbor集成了Trivy后,最直接的使用方式就是手工触发扫描。在镜像仓库的项目里,选择一个仓库,进入“扫描”标签页,点击“扫描”按钮,就会出现一条扫描任务。执行速度取决于镜像大小、层数以及CVE库的体积。一般几百MB的镜像,两三分钟内就能出结果。

如果是几十个仓库逐个扫,那就不是人干的事了。Harbor提供“全仓库扫描”功能,位置在“系统管理”-“扫描”-“全仓库扫描”。这里可以选并发数,默认并发是5,如果仓库特别多或者服务器内存不太够,建议调低到2-3,不然扫描器容器直接把内存吃满,Harbor主服务都跟着变慢。

扫描结果的查看也是有层次的。单个镜像页面可以看到总体CVE数量和按严重级别分类的柱状图,点进去能看到每个CVE对应的包名、安装版本、修复版本,以及CVE描述和参考链接。这里要注意的是,镜像上还分多个tag,同一个底层镜像如果tag不同,扫描结果是分开算的,实际使用中别搞混了。

4.2 自动扫描策略:从源头拦下坏镜像

手工扫描适合验证和排障,真正想提高安全水位,得靠自动扫描。Harbor支持按项目配置两个维度的自动扫描:一个是在镜像推送后自动触发扫描,另一个是基于时间的定时扫描。

在项目配置里有“自动扫描”开关,开启后每推一个新镜像,Harbor就会自动把最新tag加入扫描队列。这里有个细节值得注意:自动扫描触发的是tag级别的扫描,如果你通过docker tag打了十几个tag推上去,每个都会触发一次独立扫描,这在一个发布流程里会制造不少无意义的扫描任务。我们实际做法是只对release版本打tag并推送,开发过程中的临时tag直接推到一个单独的不开扫描的项目里,从源头控制扫描任务数量。

定时扫描更适合做周期体检。比如每周日凌晨对全仓库跑一遍全仓库扫描,这样即使CVE库更新后有了新漏洞,也能及时暴露出来。定时任务在“系统管理-扫描管理”里配置,Cron格式,和Linux的crontab基本一样。

4.3 CVE白名单和例外处理

扫描器返回的漏洞列表里,不是每条都需要阻塞发布。有些漏洞的利用条件非常苛刻,比如依赖某个特定的网络环境,或者要求本机已有高权限用户;有些漏洞是操作系统上游通告了但发行方还来不及出新版本;还有些第三方组件漏洞,虽然影响面广但业务里根本用不到相应的功能模块。

Harbor的设计是支持两级白名单的。项目级白名单在项目配置页面里配,命中白名单的CVE,在漏洞列表中会有特殊标记,并且计算漏洞数量时会被剔除。系统级白名单在系统管理里配,默认对所有项目生效。我的经验是,系统级白名单只放那些确定无危害的组件漏洞,项目级白名单要由业务团队和技术负责人签批后才添加,毕竟这是安全放行的底线。

白名单的配置界面是直接选择CVE ID的,支持搜索和批量添加,但记住一个重点:白名单只是展示层面的过滤,并不会改变Trivy的扫描结果。如果你通过API查询项目的漏洞统计,白名单也会被计入过滤后的数量。

5. 设置为镜像拉取策略:挡住高危生产漏洞

只扫描但不阻断,其实价值有限。审计问的是“有没有机制防止带漏洞镜像上线”,光是扫出来是不够的。Harbor在“项目-配置”里有一块关于镜像拉取策略的设置,这里能配置阻止条件,当镜像的漏洞数量超过阈值时,禁止这个镜像被拉取。

具体来说,在项目管理页面的“镜像拉取策略”中,支持设置阻断规则:可以按漏洞总数、高危漏洞数量、严重漏洞数量来设阈值。比如我设置“高危漏洞数>0就阻断”,那任何带高危CVE的镜像都无法被docker pull。不过要小心一点:这个阻断是针对pull操作生效,如果镜像在阻断规则生效前已经被拉取到本地了,那该在本地运行还是能运行。

还有一个实用细节:阻断策略会影响复制(Replication)操作,也就是说,通过Harbor做镜像同步到备用仓库时,如果目标仓库开启了阻止策略且镜像是带漏洞的,那复制任务会失败。这个行为在某些场景下会误伤备份同步,需要谨慎评估。

我当时参考的做法是这样的:生产环境项目开启高危阻塞规则;测试环境项目只做扫描不阻塞,方便业务人员自己看报告;基础设施类镜像单独放在一个项目里,白名单机制挡住误报。分层管理,比一刀切实用得多。

6. 扫描慢、CVE库不更新等常见问题与排查实录

集成过程中,我前前后后遇过不少问题,挑几个典型的记录一下,这些都是真正操作时才会遇到的坑。

6.1 扫描任务一直Queued

首次部署完成,扫任何镜像都卡在Queued状态。排查思路是先看扫描器容器的状态和日志:

docker compose ps docker compose logs -f trivy-0

日志显示容器一直处于healthcheck阶段,后来发现是CVE数据库初始化没有完成。磁盘空间够,网络也没有问题,但库文件解压一直失败。最后是把扫描器容器删了,同时删掉数据盘上Trivy缓存目录,让它重新初始化一次才解决。教训是:不要在日志还没显示CVE库准备完成的时候,就急着创建扫描任务。

6.2 CVE库长期不更新

运维同事反馈,Harbor里的漏洞报告和Trivy官方数据对不上。去检查Harbor的扫描器配置,发现里面的skip_update被设置成了true。这个配置项一旦开启,扫描器就不会自动更新CVE库。可能是部署文档被复制粘贴的时候多配了一行。改成false后,再手动触发一次全仓库扫描,数据才恢复正常。

另外,如果Harbor的部署环境是纯离线网络,CVE库更新就更麻烦了。这种情况需要预先把Trivy的离线库文件放到扫描器能访问的路径,配置离线扫描模式offline_scan: true,然后把数据库快照更新作为一次手动运维任务固定下来。

6.3 扫描结果出现大量误报

之前遇到过一个比较经典的误报场景:一个基于某发行版容器镜像的应用镜像,扫描结果里报了上百个系统软件漏洞。但仔细看,漏洞来源是发行版本号老旧导致的。实际运行的容器已经通过其他的方式升级过内核和关键组件,可镜像元数据里的版本号还停留在旧版本。这种问题很难从镜像层直接识别,Trivy也做不到完美解决,只能通过调整基础镜像、定期重建镜像来降低误报率。

排查这类误报时,我通常会看三条信息:CVE的描述、漏洞影响版本范围、镜像实际包含的文件版本。前两条Tirvy报告的详情里都有,第三条可以用docker exec进容器执行rpm -q或dpkg -l确认。如果版本号确实匹配但实际不受影响,就走项目白名单流程,而不是去改Harbor的扫描配置。

6.4 Harbor升级后扫描器状态异常

有次从2.7升到2.8后,Harbor页面显示扫描器状态不可用。这是因为升级过程中,Trivy容器的名称和网络发生了变化,旧容器没有被完全替换。问题处理起来不复杂,重启扫描器容器就好:

docker compose restart trivy-0

如果重启后还是异常,检查是否需要重新执行./prepare。Harbor的升级发布说明里经常提到,升级后必须重新生成配置,否则某些功能状态不会自动更新。

7. 避坑经验:给生产环境用Harbor+Trivy的几个建议

走到这一步,Harbor+Trivy的基本链路你已经能跑通了。但实战下来,有一些深层的经验值得单独说一下。

第一,Trivy的扫描只是安全链路上的一环,不要指望它解决所有镜像安全问题。它扫的是已知CVE,不是代码审计,更不是运行时防护。基础镜像的维护策略、应用依赖的更新管理,这些工程习惯才是根本。扫描器再全,也拦不住你在镜像里放了硬编码密钥。

第二,CVE库的更新频率要和业务发布节奏匹配。如果业务每周发布一次,那扫描器最好每周至少更新一次CVE库。定时的全仓库扫描可以设为每周执行,但CVE库的更新是每小时的自动行为,不需要额外协调。如果你把扫描频率设为每周但库里每周都在更新,结果其实每周都能覆盖到新漏洞。

第三,Harbor的扫描任务对内存敏感。扫描器容器虽然轻量,但并发拉取多个镜像层时内存占用会快速上升。我之前在16G内存的机器上跑默认并发5的全仓库扫描,内存一度飙到12G以上,直接把Harbor主服务拖慢了。后来调整为并发2,内存稳定在6G左右,扫描速度虽然略慢,但系统整体稳了很多。

第四,监控扫描队列的堆积情况。Harbor的JobService通过docker compose logs jobservice可以看到任务日志,扫描任务有堆积倾向时,及时调整并发或排查异常镜像。另外,给磁盘容量做一个监控,Trivy的CVE库和扫描缓存会持续增大,磁盘写满后会引发一系列连锁故障。

最后分享一个小配置技巧:如果内网的Harbor使用对象存储作为Registry后端,Trivy扫描时会通过Harbor的core服务读取镜像数据,流量经过的可能性会增加。本地磁盘的部署方式相对简单一些,也不容易遇到存储网关的超时问题。生产环境如果允许,尽量让Registry和扫描器都在同一主机上工作,网络层面的坑会少很多。

这套Harbor+Trivy的方案上线以后,我们团队的镜像发布流程多了一道看得见的关卡。每次推镜像之后,CI里会主动去查Harbor返回的扫描结果,如果高危漏洞数量超标就直接中断发布。这个联动做法直接把安全防线从“事后检查”前移到了“发布阻断”,效果比单纯让人盯着报告要可靠得多。你可以根据自己的流水线工具去实现同样的联动,本质上只是在镜像推送后调用Harbor的API查询漏洞统计,拿到结果再决定下一步动作。

如果你现在还没给Harbor配上扫描,或者配了但从来没真正用过,这篇文章里的步骤可以直接照着做一遍。技术上没有难点,真正花时间的反而是策略设计和团队流程的磨合。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询