生产环境 DevOps 实战 | 第 20 天:Harbor 安全扫描——集成 Trivy/Clair 扫描镜像漏洞

第 20/60 天

引言

容器镜像里藏着多少已知漏洞?很多团队直到被攻破或过等保审计时才意识到:基础镜像陈旧、依赖带 CVE、无人扫描 已成为供应链安全的头号隐患。上一篇文章我们解决了镜像的「清理与存储」,今天要解决的是镜像的「安全质量门禁」。

Harbor 的核心能力之一就是漏洞扫描(Vulnerability Scanning):它本身并不实现扫描引擎,而是以「扫描器(Scanner)」插件的形态集成 Trivy、Clair 等工具。本文从原理到实战,覆盖:Trivy/Clair 的选择与对比、扫描器的接入、扫描策略(定时/推送即扫)、漏洞等级阈值阻断、API 触发与 CI 联动,以及常见问题排查。读完你可以把「镜像扫描」作为一条强制质量门禁嵌入到 Tekton CI 流水线中。

核心概念

Harbor 扫描架构

Harbor 通过 Scanner Adapter(扫描器适配器) 模式与第三方扫描引擎解耦:Harbor 自身只负责镜像的存储与元数据,扫描器负责拉取镜像并生成漏洞报告,最终报告回传到 Harbor 的数据库与 UI。架构大致如下:

 镜像 Push ──> Harbor Core ──> 触发扫描
                 │
                 └──> Scanner Adapter (Trivy/Clair)
                          │  拉取镜像 Blob、比对漏洞库
                          ▼
                      漏洞报告 JSON
                          │
                          ▼
                    Harbor 数据库 / UI / API

Trivy 与 Clair 对比

维度 Trivy Clair
开发方 Aqua Security Red Hat (原 CoreOS)
漏洞库 聚合 NVD + 各发行版 CVE + GitHub Security Advisories 各发行版 CVE(Debian/Ubuntu/RedHat/Alpine 等)
速度 快(增量层缓存、静态分析) 中(依赖 Clair 数据库同步)
准确率 高(支持 OS 包 + 编程语言依赖) 中(偏 OS 包)
部署形态 单容器 adapter,支持 Helm 部署 需 PostgreSQL + Clair 四件套
语言依赖扫描 支持(npm/pip/gomod 等) 支持有限
维护活跃度 非常活跃 较平稳
默认推荐 ✅ 生产首选 兼容性替代

结论:新部署一律选 Trivy。Clair 适用于历史遗留、或对 Clair 数据源有合规要求的场景。本文以 Trivy 为主线,同时给出 Clair 的接入参数。

关键概念

  • 漏洞报告等级:Critical / High / Medium / Low / Unknown,对应 Harbor 扫描报告的严重度。
  • CVSS 评分:Common Vulnerability Scoring System,Harbor 报告会展示 CVSS v2/v3 分数。
  • 推送即扫:镜像 push 完成后立即触发扫描(推荐生产开启)。
  • 定时扫描:按 cron 周期对仓库内镜像重新扫描,捕获新增 CVE。
  • 扫描策略(Quota/阻断):可设置「当镜像存在 Critical 漏洞时阻止拉取/复制」的准入规则。
  • CVE Allowlist(白名单):对已知可接受的漏洞 ID 进行豁免,避免误伤业务镜像。
  • 不可变标签 + 扫描:配合 immutable tag,防止「先扫后改」的绕过。

实战步骤

1. 安装阶段启用扫描器(推荐方式)

使用 Helm 安装 Harbor 时,直接启用 Trivy 内置扫描器。用 values 文件覆盖默认配置:

# values-trivy.yaml 关键片段(配合 helm install harbor 使用)
cat > /tmp/harbor-values.yaml << 'EOF'
expose:
  type: clusterIP
externalURL: https://harbor.example.com

# 内置 Trivy 扫描器
trivy:
  enabled: true
  # 通过环境变量控制漏洞库更新
  env:
    TRIVY_DB_REPOSITORY: ghcr.io/aquasecurity/trivy-db:2
  resources:
    requests:
      cpu: 200m
      memory: 512Mi

# Clair 作为备选(如需要可启用,二者可共存但推荐只开一个)
clair:
  enabled: false

# 推送即扫描的全局默认(运行时也可在 UI/API 覆盖)
# 注:默认扫描策略见步骤 3
EOF

helm repo add harbor https://helm.goharbor.io
helm repo update
helm upgrade --install harbor harbor/harbor 
  -n harbor --create-namespace 
  -f /tmp/harbor-values.yaml

使用 docker-compose 部署(离线/内网环境)时,只需在 docker-compose.yml 中将 Trivy 服务的 enabled: false 改为 true 并执行 docker-compose up -d,Harbor 会在启动时自动注册内置 Trivy 扫描器。

2. 安装后手动接入第三方扫描器

如果安装时未启用扫描器,或需要接入独立部署的 Trivy/Clair 集群,可以通过 Harbor API 注册扫描器端点:

# 获取 Harbor API Token(robot 或 admin)
TOKEN=$(curl -s -u "admin:ChangeMe123" 
  -X POST "https://harbor.example.com/api/v2.0/users/login" 
  -H "Content-Type: application/json" 
  -d '{}' -o /dev/null -w '')

# 直接用基础认证注册 Trivy 扫描器
curl -k -u "admin:ChangeMe123" 
  -X POST "https://harbor.example.com/api/v2.0/scanners" 
  -H "Content-Type: application/json" 
  -d '{
    "name": "trivy-standalone",
    "description": "Standalone Trivy scanner",
    "url": "http://trivy-adapter:8080",
    "auth": "",
    "skip_certVerify": false
  }'

# 注册 Clair(备用示例)
curl -k -u "admin:ChangeMe123" 
  -X POST "https://harbor.example.com/api/v2.0/scanners" 
  -H "Content-Type: application/json" 
  -d '{
    "name": "clair",
    "url": "http://clair:8080"
  }'

# 查看当前可用扫描器
curl -k -u "admin:ChangeMe123" 
  "https://harbor.example.com/api/v2.0/scanners" | jq .

3. 配置扫描策略与准入规则

在 Harbor UI 中「系统管理 → 配置管理 → 安全」可以可视化设置;同样也可以通过配置 API 完成。核心策略包括:推送即扫、定时扫描 cron、漏洞等级阈值、阻止危险镜像拉取

# 推送即扫 + 阻止高危镜像(通过 Configuration API)
curl -k -u "admin:ChangeMe123" 
  -X PUT "https://harbor.example.com/api/v2.0/configurations" 
  -H "Content-Type: application/json" 
  -d '{
    "scanner_skip_update_pulltime": false,
    "scan_all_policy": {
      "type": "daily",
      "parameter": { "daily_time": 2 }
    },
    "prevent_vulnerable_images_from_running": true,
    "prevent_vulnerable_images_from_running_severity": "High"
  }'

创建定时扫描任务(对全部仓库每天凌晨 2 点重新扫描):

curl -k -u "admin:ChangeMe123" 
  -X POST "https://harbor.example.com/api/v2.0/scans/schedule" 
  -H "Content-Type: application/json" 
  -d '{
    "schedule": { "type": "Daily", "cron": "0 2 0 * * *" }
  }'

prevent_vulnerable_images_from_running_severity: High 表示:当镜像存在 High 及以上 漏洞时,Harbor 将阻止该镜像被拉取(pull),从源头拦截危险镜像流向运行时。

4. 用 API 触发扫描并获取报告

手动/CI 触发某个镜像仓库的扫描,并轮询等待报告就绪:

# 对 project/repo 的 tag 发起扫描
SCAN_RESP=$(curl -k -s -u "admin:ChangeMe123" 
  -X POST "https://harbor.example.com/api/v2.0/projects/myapp/repositories/backend/artifacts/v1.2.3/scan")

# 查询扫描状态与摘要
curl -k -s -u "admin:ChangeMe123" 
  "https://harbor.example.com/api/v2.0/projects/myapp/repositories/backend/artifacts/v1.2.3" 
  | jq '{digest, scan_overview}'

# 拉取完整漏洞列表
curl -k -s -u "admin:ChangeMe123" 
  "https://harbor.example.com/api/v2.0/projects/myapp/repositories/backend/artifacts/v1.2.3/vulnerabilities?severity=High&page=1&page_size=100" 
  | jq '[.[] | {id, package, version, severity, fix_version}]'

5. 在 Tekton 中集成扫描质量门禁

生产最佳实践:CI 推送镜像后,必须扫描且高危漏洞数归零(或低于阈值)才能继续 CD。下面的 Tekton Task 调用 Harbor API 触发扫描并轮询结果,不合格直接失败:

apiVersion: tekton.dev/v1
kind: Task
metadata:
  name: harbor-scan-gate
spec:
  params:
    - name: harbor-url
      type: string
      default: "https://harbor.example.com"
    - name: image
      type: string                  # 例如 backend
    - name: tag
      type: string
    - name: project
      type: string
    - name: fail-severity
      type: string
      default: "High"               # 高于等于该等级即失败
  steps:
    - name: scan-and-gate
      image: bitnami/kubectl:latest
      script: |
        #!/usr/bin/env bash
        set -euxo pipefail
        H="$HARBOR_URL/api/v2.0"
        # 触发扫描
        curl -k -s -u "$HARBOR_USER:$HARBOR_PASS" 
          -X POST "$H/projects/$(params.project)/repositories/$(params.image)/artifacts/$(params.tag)/scan"
        # 轮询等待扫描完成
        for i in $(seq 1 30); do
          ov=$(curl -k -s -u "$HARBOR_USER:$HARBOR_PASS" 
            "$H/projects/$(params.project)/repositories/$(params.image)/artifacts/$(params.tag)" 
            | jq -r '.scan_overview["application/vnd.security.vulnerability.report; version=1.1"].scan_status')
          [ "$ov" = "Success" ] && break
          [ "$ov" = "Error" ] && { echo "scan failed"; exit 1; }
          sleep 5
        done
        # 提取各等级数量
        read CRIT HIGH MED <<< $(curl -k -s -u "$HARBOR_USER:$HARBOR_PASS" 
          "$H/projects/$(params.project)/repositories/$(params.image)/artifacts/$(params.tag)/vulnerabilities?page_size=1&page=1" 
          -H "X-Accept-Vulnerabilities: application/vnd.security.vulnerability.report; version=1.1" 
          | jq -r '.summary | [.Critical // 0, .High // 0, .Medium // 0] | @tsv')
        echo "Critical=$CRIT High=$HIGH Medium=$MED"
        # 质量门禁:Critical/High 必须为 0
        if [ "$CRIT" -gt 0 ] || [ "$HIGH" -gt 0 ]; then
          echo "❌ 高危漏洞未清零,阻断发布"
          exit 1
        fi
        echo "✅ 扫描通过,允许发布"

将该 Task 编排进流水线,放在「构建推送」之后、「Argo CD 触发部署」之前:

apiVersion: tekton.dev/v1
kind: Pipeline
metadata:
  name: build-and-deploy
spec:
  workspaces:
    - name: source
  tasks:
    - name: build
      taskRef: { name: kaniko-build }
      workspaces: [{ name: source, workspace: source }]
    - name: scan
      taskRef: { name: harbor-scan-gate }
      runAfter: [build]
      params:
        - { name: project, value: myapp }
        - { name: image, value: backend }
        - { name: tag, value: v1.2.3 }
    - name: deploy
      taskRef: { name: gitops-update }   # 更新 Git 清单触发 Argo CD 同步
      runAfter: [scan]

6. 配置漏洞白名单与 Webhook 通知

对已评估、可接受的漏洞加入 CVE Allowlist,并配置扫描完成后的 Webhook 通知:

# 给项目添加 CVE 白名单
curl -k -u "admin:ChangeMe123" 
  -X PUT "https://harbor.example.com/api/v2.0/projects/myapp/preheat/policies" 
  -d ''   # (占位示意,真实白名单走 projects API,见下方)

# 真实写法:为项目设置 CVE allowlist
curl -k -u "admin:ChangeMe123" 
  -X PUT "https://harbor.example.com/api/v2.0/projects/myapp" 
  -H "Content-Type: application/json" 
  -d '{
    "cve_allowlist": {
      "items": [
        { "cve_id": "CVE-2024-12345" },
        { "cve_id": "CVE-2024-67890" }
      ]
    }
  }'

# Webhook:扫描完成时通知团队群(如钉钉/Slack)
curl -k -u "admin:ChangeMe123" 
  -X POST "https://harbor.example.com/api/v2.0/projects/myapp/webhook/policies" 
  -H "Content-Type: application/json" 
  -d '{
    "name": "scan-notify",
    "enabled": true,
    "event_types": ["SCANNING_COMPLETED", "SCANNING_FAILED"],
    "targets": [{
      "type": "http",
      "address": "https://hooks.slack.com/services/T000/B000/XXXX",
      "auth_header": "Bearer xoxb-xxx"
    }]
  }'

常见问题

Q1:Trivy 扫描器一直显示「不可用/Unhealthy」怎么办?
最常见是 Trivy 数据库初始化慢或无法下载漏洞库(国内网络问题)。排查顺序:kubectl logs -n harbor deploy/harbor-trivy -f 看是否卡在 DB 下载;给 Trivy 配置 TRIVY_DB_REPOSITORY 指向可访问的镜像源,或设置 HTTP 代理;数据库就绪前扫描任务会排队,耐心等待首次 DB 同步完成。

Q2:为什么扫出来的漏洞比 Clarity 少/多?
不同扫描器漏洞库与匹配算法不同,结果天然有差异属正常。Trivy 覆盖更广(OS 包 + 语言依赖),且数据库更新频繁。不要跨扫描器比较数量,关键是固定的扫描器 + 固定的策略阈值,保证趋势可比。

Q3:开启「阻止高危镜像拉取」后,业务镜像拉不下来怎么办?
这是设计使然——该规则就是为了阻断危险镜像流向运行时。处理方式:① 修复镜像漏洞后重新构建并 push 覆盖;② 对该 CVE 加入项目白名单(CVE Allowlist);③ 临时将 prevent_vulnerable_images_from_running_severity 提升到 Critical。务必走修复路线而非一味豁免。

Q4:扫描很慢,大量镜像积压如何加速?
Trivy 有层缓存,相似基础镜像复用快。优化手段:启用 skipUpdate(内网预置 DB)、提高 Trivy 副本数与资源配额、按项目错峰设置扫描计划、对历史老镜像用「非关键」优先级。必要时可限制仅扫描 immutable 标签镜像。

Q5:CI 里调用 Harbor API 需要给什么权限?
推荐用 Robot Account(而非 admin),仅授予目标项目的 push + scan + pull 权限(角色可设为「项目维护者」级别)。Robot 凭证以 robot$myapp+ci 格式作为用户名,令牌作为密码,避免 admin 明文外泄。

总结

  1. 扫描器即插即用:Harbor 通过 Scanner Adapter 集成 Trivy/Clair,新项目直接选 Trivy(性能、准确率、维护性全面领先)。
  2. 三道防线缺一不可:推送即扫(即时发现)+ 定时扫描(捕获新增 CVE)+ 准入阻断(阻止高危镜像被拉取)。
  3. 把扫描变成 CI 质量门禁:Tekton 推送镜像后调用 Harbor API 触发扫描并轮询结果,高危不清零不放行,形成「构建→扫描→门禁→CD」的安全闭环。
  4. 权限最小化:CI 凭证一律用 Robot Account,只授予目标项目必要角色,杜绝 admin 泄露。
  5. 供应链安全是持续过程:配合 immutable tag、CVE 白名单、Webhook 通知,让安全扫描融入日常而非事后补救。

明天我们将进入 Argo CD 深入篇,先讲 Argo CD 的安装与配置(kubectl 安装、ingress、初始密码),打通 GitOps 交付的最后一公里。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片快捷回复

    暂无评论内容