一:教程定位

在前 14 篇教程中,我们已经完成了 Harness 从基础流水线到模板化复用的核心内容,包括 CI/CD、Kubernetes 部署、动态变量、Input Set、多阶段流水线、回滚策略、蓝绿/金丝雀发布和模板治理。

第 15 篇开始进入企业级交付质量保障的重点:自动化测试与质量扫描

很多团队刚开始做 CI/CD 时,只关注:

代码能不能构建 镜像能不能推送 服务能不能部署

但企业生产发布更需要关注:

代码质量是否达标? 单元测试是否通过? 测试覆盖率是否过低? 是否有高危漏洞? 是否引入了有风险的开源依赖? 镜像里是否存在高危 CVE? 是否有明文密钥? 是否违反许可证合规? 扫描失败后是否应该阻断部署?

因此,CI 阶段不能只是“构建镜像”,还要把测试和质量扫描作为准入门槛。

本篇重点讲解如何在 Harness 流水线中集成:

单元测试 测试覆盖率 SonarQube 代码质量扫描 SCA 开源依赖扫描 容器镜像漏洞扫描 Secret 扫描 质量门 Quality Gate 失败阻断与人工豁免

并给出国内网络环境下可实施的方案。


二:适合人群

本文面向中级用户,适合:

DevOps 工程师 平台工程师 SRE 测试负责人 安全工程师 研发负责人 需要把质量门接入 CI/CD 的企业团队

建议已经具备:

了解 Harness Pipeline、Stage、Step 了解 Harness CI / CD 基础 了解 Docker 镜像构建 了解 Harbor 私有镜像仓库 了解 Kubernetes 部署 了解 SonarQube 基础概念 了解 Runtime Input、Secret、Failure Strategy 能看懂 Bash 脚本和 YAML


三:学习目标

完成本文后,你应该能够:

设计 CI 阶段质量扫描流程 在 Harness 中运行单元测试 生成测试覆盖率报告 使用 SonarQube Scanner 执行代码质量扫描 通过 SonarQube API 检查 Quality Gate 使用 OWASP Dependency-Check 或 Snyk 做 SCA 扫描 使用 Trivy 扫描容器镜像漏洞 设置质量门,控制是否继续构建和部署 按 dev/test/pre/prod 设计不同阻断策略 处理扫描误报和人工豁免 在国内网络环境中处理 npm、Maven、漏洞库、Harbor、SonarQube 访问问题 建立企业级质量门标准


四:自动化测试与质量扫描整体架构

推荐流水线结构:

Stage 1: Prepare

  • 打印上下文
  • 检查 GitLab / Harbor / SonarQube / npm / Maven 网络

Stage 2: Unit Test And Coverage

  • 安装依赖
  • 执行单元测试
  • 生成覆盖率报告

Stage 3: Code Quality Scan

  • SonarQube Scanner
  • SonarQube Quality Gate 检查

Stage 4: SCA Scan

  • OWASP Dependency-Check / Snyk / Trivy filesystem
  • 检查开源依赖漏洞和许可证风险

Stage 5: Build Image

  • Docker build
  • Docker push Harbor

Stage 6: Image Scan

  • Trivy image scan
  • 高危漏洞阻断

Stage 7: Quality Gate Summary

  • 汇总测试、覆盖率、代码质量、依赖漏洞、镜像漏洞
  • 通过则部署
  • 不通过则终止或人工介入

Stage 8: Deploy

  • K8s Rolling / Canary / Blue-Green

注意:

测试和扫描应该尽量前置。 越早发现问题,修复成本越低。 不要等部署到 Kubernetes 后才发现代码质量或依赖漏洞问题。


五:质量门应该检查什么?

企业常见质量门:

类别 检查项 推荐阻断条件
单元测试 npm test / mvn test 失败即阻断
覆盖率 line coverage / branch coverage 低于阈值阻断或警告
SonarQube Bugs / Vulnerabilities / Code Smells Quality Gate 失败则阻断
SCA 开源依赖 CVE Critical/High 阻断
Secret Scan 明文密钥 发现即阻断
镜像扫描 镜像 OS 包和语言包 CVE Critical 阻断,High 按环境策略
许可证 GPL/AGPL 等风险许可证 生产阻断或人工审批
SBOM 是否生成 SBOM pre/prod 必须生成

推荐分环境策略:

dev: 单元测试失败阻断 SonarQube Quality Gate 可警告 Critical 漏洞阻断

test: 单元测试失败阻断 Quality Gate 失败阻断 Critical/High 漏洞阻断

pre: Quality Gate 失败阻断 Critical/High 漏洞阻断 必须生成 SBOM

prod: 不在 prod 阶段重新扫描源码 只允许使用已扫描通过的镜像版本 Critical/High 漏洞必须处理或有审批豁免


六:国内网络环境下的工具选择

1. 代码质量

推荐:

SonarQube Server 自建

原因:

内网可部署 支持多语言 支持 Quality Gate 支持与 GitLab 集成 团队熟悉度高

2. SCA 开源依赖扫描

可选:

OWASP Dependency-Check Snyk Mend JFrog Xray Nexus IQ Trivy filesystem

国内落地建议:

开源优先: OWASP Dependency-Check + Trivy

企业商业方案: Snyk / Mend / Nexus IQ / JFrog Xray

3. 容器镜像扫描

推荐:

Trivy Harbor 自带扫描器 Grype Clair 企业镜像安全平台

国内落地建议:

Harbor 已经部署: 先启用 Harbor 镜像扫描

Harness CI 阶段: 使用 Trivy 扫描镜像或 OCI archive

企业安全要求高: Trivy + Harbor + 企业漏洞管理平台


七:推荐项目结构

harness-demo-app/ ├── app/ │ ├── package.json │ ├── server.js │ └── server.test.js ├── docker/ │ └── Dockerfile ├── sonar-project.properties ├── dependency-check-suppressions.xml ├── scripts/ │ ├── check-network-cn.sh │ ├── run-unit-test.sh │ ├── run-sonar-scanner.sh │ ├── wait-sonar-quality-gate.sh │ ├── run-dependency-check.sh │ ├── run-trivy-fs.sh │ ├── run-trivy-image.sh │ ├── summarize-quality-gate.sh │ └── notify-quality-result.sh └── harness/ └── pipelines/ └── quality-scan-demo.yaml


八:准备 Node.js 示例项目

app/package.json

{
  "name": "harness-demo-app",
  "version": "1.0.0",
  "description": "Harness quality scan demo",
  "main": "server.js",
  "scripts": {
    "start": "node server.js",
    "test": "jest --coverage --runInBand"
  },
  "dependencies": {
    "express": "^4.18.3"
  },
  "devDependencies": {
    "jest": "^29.7.0",
    "supertest": "^6.3.4"
  }
}

app/server.js

const express = require("express");

function createApp() {
  const app = express();

  app.get("/", (req, res) => {
    res.json({
      app: "harness-demo-app",
      status: "ok",
      version: process.env.APP_VERSION || "dev"
    });
  });

  app.get("/health", (req, res) => {
    res.status(200).json({
      status: "UP"
    });
  });

  return app;
}

if (require.main === module) {
  const app = createApp();
  const port = process.env.PORT || 3000;

  app.listen(port, () => {
    console.log(`server started on port ${port}`);
  });
}

module.exports = createApp;

app/server.test.js

const request = require("supertest");
const createApp = require("./server");

describe("harness-demo-app", () => {
  test("GET /health should return UP", async () => {
    const app = createApp();
    const response = await request(app).get("/health");

    expect(response.status).toBe(200);
    expect(response.body.status).toBe("UP");
  });

  test("GET / should return app info", async () => {
    const app = createApp();
    const response = await request(app).get("/");

    expect(response.status).toBe(200);
    expect(response.body.app).toBe("harness-demo-app");
  });
});

九:配置 npm 国内源

.npmrc

registry=https://registry.npmmirror.com
fetch-retries=5
fetch-retry-mintimeout=20000
fetch-retry-maxtimeout=120000

国内企业建议:

个人或小团队: 使用 npmmirror

企业: 使用 Nexus / Verdaccio / Artifactory 搭建 npm 私服

CI 镜像: 预置 node、npm、pnpm、yarn、证书和基础依赖


十:SonarQube 项目配置

sonar-project.properties

sonar.projectKey=harness-demo-app
sonar.projectName=harness-demo-app
sonar.projectVersion=1.0.0

sonar.sources=app
sonar.tests=app
sonar.test.inclusions=**/*.test.js
sonar.exclusions=**/node_modules/**,**/coverage/**

sonar.javascript.lcov.reportPaths=app/coverage/lcov.info

sonar.sourceEncoding=UTF-8

说明:

sonar.sources 指定源码目录 sonar.tests 指定测试目录 sonar.test.inclusions 指定测试文件 sonar.javascript.lcov.reportPaths 指定 Jest 覆盖率报告


十一:准备 SonarQube Token

在 SonarQube 中创建 token:

User → My Account → Security → Generate Token

然后在 Harness 中创建 Secret:

Secret Name: sonar_token

Secret Value: SonarQube token

SonarQube 地址建议:

https://sonarqube.company.com

如果是自签证书,需要让 CI 镜像或 Delegate 信任企业 CA。


十二:网络检查脚本

scripts/check-network-cn.sh

#!/usr/bin/env bash
set -e

echo "===== Check GitLab ====="
curl -k -I --connect-timeout 10 https://gitlab.company.com || true

echo "===== Check Harbor ====="
curl -k -I --connect-timeout 10 https://harbor.company.com/v2/ || true

echo "===== Check npm registry ====="
curl -I --connect-timeout 10 https://registry.npmmirror.com || true

echo "===== Check SonarQube ====="
curl -k -I --connect-timeout 10 "${SONAR_HOST_URL:-https://sonarqube.company.com}" || true

echo "===== Check Done ====="

十三:单元测试脚本

scripts/run-unit-test.sh

#!/usr/bin/env bash
set -euo pipefail

echo "开始执行单元测试"

cd app

npm config set registry https://registry.npmmirror.com

npm install

npm test

if [ ! -f coverage/lcov.info ]; then
  echo "ERROR: 未生成 coverage/lcov.info"
  exit 1
fi

echo "单元测试和覆盖率报告生成成功"

质量门建议:

单元测试失败: 直接阻断

覆盖率报告生成失败: test/pre/prod 阻断 dev 可根据团队策略允许警告


十四:SonarQube 扫描脚本

scripts/run-sonar-scanner.sh

#!/usr/bin/env bash
set -euo pipefail

SONAR_HOST_URL="${SONAR_HOST_URL:?SONAR_HOST_URL is required}"
SONAR_TOKEN="${SONAR_TOKEN:?SONAR_TOKEN is required}"
SONAR_PROJECT_KEY="${SONAR_PROJECT_KEY:-harness-demo-app}"
SONAR_PROJECT_VERSION="${SONAR_PROJECT_VERSION:-dev}"

echo "开始执行 SonarQube 扫描"
echo "SONAR_HOST_URL=${SONAR_HOST_URL}"
echo "SONAR_PROJECT_KEY=${SONAR_PROJECT_KEY}"
echo "SONAR_PROJECT_VERSION=${SONAR_PROJECT_VERSION}"

sonar-scanner \
  -Dsonar.host.url="${SONAR_HOST_URL}" \
  -Dsonar.token="${SONAR_TOKEN}" \
  -Dsonar.projectKey="${SONAR_PROJECT_KEY}" \
  -Dsonar.projectVersion="${SONAR_PROJECT_VERSION}" \
  -Dsonar.qualitygate.wait=false

echo "SonarQube 扫描提交完成"

说明:

sonar.qualitygate.wait=false 表示先提交扫描,后续用脚本轮询 Quality Gate。 也可以设置 true,但在网络慢或队列长时容易导致 CI Step 持续等待。 企业推荐显式轮询并输出更清楚的日志。


十五:SonarQube Quality Gate 检查脚本

scripts/wait-sonar-quality-gate.sh

#!/usr/bin/env bash
set -euo pipefail

SONAR_HOST_URL="${SONAR_HOST_URL:?SONAR_HOST_URL is required}"
SONAR_TOKEN="${SONAR_TOKEN:?SONAR_TOKEN is required}"
SONAR_PROJECT_KEY="${SONAR_PROJECT_KEY:-harness-demo-app}"
TIMEOUT_SECONDS="${TIMEOUT_SECONDS:-300}"
INTERVAL_SECONDS="${INTERVAL_SECONDS:-10}"

echo "开始等待 SonarQube Quality Gate"
echo "PROJECT=${SONAR_PROJECT_KEY}"
echo "TIMEOUT_SECONDS=${TIMEOUT_SECONDS}"

START_TIME=$(date +%s)

while true; do
  NOW=$(date +%s)
  ELAPSED=$((NOW - START_TIME))

  if [ "${ELAPSED}" -gt "${TIMEOUT_SECONDS}" ]; then
    echo "ERROR: 等待 Quality Gate 超时"
    exit 1
  fi

  RESPONSE=$(curl -sS -u "${SONAR_TOKEN}:" \
    "${SONAR_HOST_URL}/api/qualitygates/project_status?projectKey=${SONAR_PROJECT_KEY}")

  STATUS=$(echo "${RESPONSE}" | sed -n 's/.*"status":"\([^"]*\)".*/\1/p')

  echo "Quality Gate Status: ${STATUS:-UNKNOWN}"

  if [ "${STATUS}" = "OK" ]; then
    echo "Quality Gate 通过"
    exit 0
  fi

  if [ "${STATUS}" = "ERROR" ]; then
    echo "ERROR: Quality Gate 未通过"
    echo "${RESPONSE}"
    exit 1
  fi

  if [ "${STATUS}" = "WARN" ]; then
    echo "WARN: Quality Gate 警告"
    echo "${RESPONSE}"
    exit 1
  fi

  sleep "${INTERVAL_SECONDS}"
done

生产建议:

Quality Gate ERROR: 阻断

Quality Gate WARN: test/pre/prod 阻断 dev 可选择只警告

Quality Gate 超时: test/pre/prod 阻断 dev 可重试


十六:SCA 扫描方案一:OWASP Dependency-Check

OWASP Dependency-Check 适合开源落地,但它依赖漏洞数据库,国内网络下要重点处理数据源更新慢的问题。

scripts/run-dependency-check.sh

#!/usr/bin/env bash
set -euo pipefail

PROJECT_NAME="${PROJECT_NAME:-harness-demo-app}"
SCAN_PATH="${SCAN_PATH:-app}"
REPORT_DIR="${REPORT_DIR:-dependency-check-report}"
CVSS_FAIL_SCORE="${CVSS_FAIL_SCORE:-7}"

echo "开始 OWASP Dependency-Check 扫描"
echo "PROJECT_NAME=${PROJECT_NAME}"
echo "SCAN_PATH=${SCAN_PATH}"
echo "CVSS_FAIL_SCORE=${CVSS_FAIL_SCORE}"

mkdir -p "${REPORT_DIR}"

dependency-check.sh \
  --project "${PROJECT_NAME}" \
  --scan "${SCAN_PATH}" \
  --format "HTML" \
  --format "JSON" \
  --out "${REPORT_DIR}" \
  --failOnCVSS "${CVSS_FAIL_SCORE}" \
  --disableAssembly

echo "Dependency-Check 扫描完成"

建议:

dev: failOnCVSS=9

test: failOnCVSS=7

pre/prod: failOnCVSS=7 或更严格

常见问题:

首次更新漏洞库非常慢 CI 环境无法访问 NVD 误报较多 扫描耗时较长

企业优化:

使用内网缓存漏洞库 固定 Dependency-Check 镜像版本 配置 suppression 文件 定期人工复核误报


十七:Dependency-Check 抑制误报

dependency-check-suppressions.xml

<?xml version="1.0" encoding="UTF-8"?>
<suppressions xmlns="https://jeremylong.github.io/DependencyCheck/dependency-suppression.1.3.xsd">
    <suppress>
        <notes><![CDATA[
        示例:确认该 CVE 不影响当前服务,已由安全负责人审批。
        审批单:SEC-2026-001
        到期时间:2026-12-31
        ]]></notes>
        <cve>CVE-0000-0000</cve>
    </suppress>
</suppressions>

使用:

dependency-check.sh \
  --project "${PROJECT_NAME}" \
  --scan "${SCAN_PATH}" \
  --suppression dependency-check-suppressions.xml \
  --format "HTML" \
  --format "JSON" \
  --out "${REPORT_DIR}" \
  --failOnCVSS "${CVSS_FAIL_SCORE}"

误报处理原则:

不能随便 suppress 必须写原因 必须关联审批单 必须设置到期时间 必须定期复查


十八:SCA 扫描方案二:Trivy filesystem

Trivy 可以扫描代码仓库文件系统,发现依赖漏洞、Secret 和配置风险。

scripts/run-trivy-fs.sh

#!/usr/bin/env bash
set -euo pipefail

SCAN_PATH="${SCAN_PATH:-.}"
REPORT_DIR="${REPORT_DIR:-trivy-fs-report}"
SEVERITY="${SEVERITY:-CRITICAL,HIGH}"

mkdir -p "${REPORT_DIR}"

echo "开始 Trivy filesystem 扫描"
echo "SCAN_PATH=${SCAN_PATH}"
echo "SEVERITY=${SEVERITY}"

trivy fs \
  --scanners vuln,secret,misconfig \
  --severity "${SEVERITY}" \
  --format json \
  --output "${REPORT_DIR}/trivy-fs.json" \
  --exit-code 1 \
  "${SCAN_PATH}"

echo "Trivy filesystem 扫描通过"

如果只想生成报告、不阻断:

trivy fs \
  --scanners vuln,secret,misconfig \
  --severity CRITICAL,HIGH \
  --format table \
  --exit-code 0 \
  .

推荐策略:

feature 分支: 生成报告,Critical 阻断

main 分支: Critical/High 阻断

release/tag: Critical/High 阻断,必须人工处理或审批豁免


十九:容器镜像扫描:Trivy image

镜像构建后,必须扫描镜像。

scripts/run-trivy-image.sh

#!/usr/bin/env bash
set -euo pipefail

IMAGE_FULL_NAME="${IMAGE_FULL_NAME:?IMAGE_FULL_NAME is required}"
REPORT_DIR="${REPORT_DIR:-trivy-image-report}"
SEVERITY="${SEVERITY:-CRITICAL,HIGH}"

mkdir -p "${REPORT_DIR}"

echo "开始 Trivy image 扫描"
echo "IMAGE_FULL_NAME=${IMAGE_FULL_NAME}"
echo "SEVERITY=${SEVERITY}"

trivy image \
  --severity "${SEVERITY}" \
  --scanners vuln,secret \
  --ignore-unfixed \
  --format json \
  --output "${REPORT_DIR}/trivy-image.json" \
  --exit-code 1 \
  "${IMAGE_FULL_NAME}"

echo "Trivy image 扫描通过"

国内环境注意:

Trivy 漏洞库首次下载可能很慢 建议使用缓存目录 建议自建 Trivy DB 镜像或定期预热 CI 镜像提前内置 trivy Harbor 自签证书需要配置容器信任


二十:SBOM 生成建议

pre/prod 建议生成 SBOM。

使用 Trivy:

trivy image \
  --format cyclonedx \
  --output sbom-cyclonedx.json \
  "${IMAGE_FULL_NAME}"

或者 SPDX:

trivy image \
  --format spdx-json \
  --output sbom-spdx.json \
  "${IMAGE_FULL_NAME}"

建议保存:

Harness Artifact Harbor 附件 制品仓库 对象存储 安全平台

SBOM 不是“生成了就安全”,它的价值在于:

知道镜像里有什么 后续 CVE 爆发时能快速定位受影响服务 满足合规审计要求 便于漏洞管理平台消费


二十一:质量门汇总脚本

scripts/summarize-quality-gate.sh

#!/usr/bin/env bash
set -euo pipefail

QUALITY_STATUS="${QUALITY_STATUS:-PASS}"
UNIT_TEST_STATUS="${UNIT_TEST_STATUS:-PASS}"
SONAR_STATUS="${SONAR_STATUS:-PASS}"
SCA_STATUS="${SCA_STATUS:-PASS}"
IMAGE_SCAN_STATUS="${IMAGE_SCAN_STATUS:-PASS}"
DEPLOY_ENV="${DEPLOY_ENV:-dev}"

echo "===== Quality Gate Summary ====="
echo "DEPLOY_ENV=${DEPLOY_ENV}"
echo "UNIT_TEST_STATUS=${UNIT_TEST_STATUS}"
echo "SONAR_STATUS=${SONAR_STATUS}"
echo "SCA_STATUS=${SCA_STATUS}"
echo "IMAGE_SCAN_STATUS=${IMAGE_SCAN_STATUS}"

FAILED=0

if [ "${UNIT_TEST_STATUS}" != "PASS" ]; then
  echo "ERROR: 单元测试未通过"
  FAILED=1
fi

if [ "${SONAR_STATUS}" != "PASS" ]; then
  echo "ERROR: SonarQube Quality Gate 未通过"
  FAILED=1
fi

if [ "${SCA_STATUS}" != "PASS" ]; then
  echo "ERROR: SCA 扫描未通过"
  FAILED=1
fi

if [ "${IMAGE_SCAN_STATUS}" != "PASS" ]; then
  echo "ERROR: 镜像扫描未通过"
  FAILED=1
fi

if [ "${FAILED}" = "1" ]; then
  echo "QUALITY_STATUS=FAIL"
  QUALITY_STATUS="FAIL"
  exit 1
fi

echo "QUALITY_STATUS=PASS"
echo "质量门通过"

实际 Harness 中,通常不需要这个脚本逐项判断,因为每个 Step 失败就会阻断后续步骤。但在大型流水线里,汇总输出对审计和通知很有用。


二十二:Harness 流水线设计

推荐结构:

Pipeline: quality-scan-demo

Stage 1: Prepare Step 1: Check Network

Stage 2: Unit Test And Coverage Step 1: npm install Step 2: npm test --coverage Artifact: coverage/lcov.info

Stage 3: SonarQube Scan Step 1: sonar-scanner Step 2: wait quality gate

Stage 4: SCA Scan Parallel: - OWASP Dependency-Check - Trivy filesystem

Stage 5: Build And Push Step 1: Docker build Step 2: Docker push Harbor

Stage 6: Image Scan Step 1: Trivy image scan Step 2: Generate SBOM

Stage 7: Deploy Condition: quality passed

并行建议:

Unit Test 与 SCA 可以并行,但 SonarQube 覆盖率依赖单元测试报告,所以 SonarQube 最好等测试完成。 镜像扫描必须等镜像构建完成。 部署必须等所有质量门通过。


二十三:Harness YAML 参考:Shell 方式

下面是通用 Shell 方式,适合所有企业,即使没有启用 Harness STO,也可以落地。

pipeline:
  name: quality-scan-demo
  identifier: quality_scan_demo
  projectIdentifier: harness_demo
  orgIdentifier: devops_lab
  tags:
    tutorial: harness-15
    topic: quality-scan
  variables:
    - name: app_name
      type: String
      value: harness-demo-app
    - name: deploy_env
      type: String
      value: <+input>
    - name: image_repository
      type: String
      value: harbor.company.com/devops/harness-demo-app
    - name: image_tag
      type: String
      value: <+input>
    - name: sonar_project_key
      type: String
      value: harness-demo-app
    - name: sonar_host_url
      type: String
      value: https://sonarqube.company.com

  stages:
    - stage:
        name: Unit Test And Coverage
        identifier: unit_test_and_coverage
        type: CI
        spec:
          cloneCodebase: true
          infrastructure:
            type: KubernetesDirect
            spec:
              connectorRef: k8s_dev_cluster
              namespace: harness-ci
              automountServiceAccountToken: true
              os: Linux
          execution:
            steps:
              - step:
                  name: Unit Test
                  identifier: unit_test
                  type: Run
                  spec:
                    connectorRef: harbor_company
                    image: harbor.company.com/library/node:20-alpine
                    shell: Sh
                    command: |
                      set -e
                      chmod +x scripts/run-unit-test.sh
                      scripts/run-unit-test.sh

    - stage:
        name: SonarQube Scan
        identifier: sonarqube_scan
        type: CI
        spec:
          cloneCodebase: true
          infrastructure:
            type: KubernetesDirect
            spec:
              connectorRef: k8s_dev_cluster
              namespace: harness-ci
              automountServiceAccountToken: true
              os: Linux
          execution:
            steps:
              - step:
                  name: Sonar Scanner
                  identifier: sonar_scanner
                  type: Run
                  spec:
                    connectorRef: harbor_company
                    image: harbor.company.com/library/sonar-scanner-cli:latest
                    shell: Sh
                    envVariables:
                      SONAR_HOST_URL: <+pipeline.variables.sonar_host_url>
                      SONAR_TOKEN: <+secrets.getValue("sonar_token")>
                      SONAR_PROJECT_KEY: <+pipeline.variables.sonar_project_key>
                      SONAR_PROJECT_VERSION: <+pipeline.variables.image_tag>
                    command: |
                      set -e
                      chmod +x scripts/run-sonar-scanner.sh
                      scripts/run-sonar-scanner.sh

              - step:
                  name: Sonar Quality Gate
                  identifier: sonar_quality_gate
                  type: Run
                  spec:
                    connectorRef: harbor_company
                    image: harbor.company.com/library/curl-jq:latest
                    shell: Sh
                    envVariables:
                      SONAR_HOST_URL: <+pipeline.variables.sonar_host_url>
                      SONAR_TOKEN: <+secrets.getValue("sonar_token")>
                      SONAR_PROJECT_KEY: <+pipeline.variables.sonar_project_key>
                      TIMEOUT_SECONDS: "300"
                    command: |
                      set -e
                      chmod +x scripts/wait-sonar-quality-gate.sh
                      scripts/wait-sonar-quality-gate.sh

    - stage:
        name: SCA Scan
        identifier: sca_scan
        type: CI
        spec:
          cloneCodebase: true
          infrastructure:
            type: KubernetesDirect
            spec:
              connectorRef: k8s_dev_cluster
              namespace: harness-ci
              automountServiceAccountToken: true
              os: Linux
          execution:
            steps:
              - parallel:
                  - step:
                      name: Dependency Check
                      identifier: dependency_check
                      type: Run
                      spec:
                        connectorRef: harbor_company
                        image: harbor.company.com/security/dependency-check:latest
                        shell: Sh
                        envVariables:
                          PROJECT_NAME: <+pipeline.variables.app_name>
                          SCAN_PATH: app
                          CVSS_FAIL_SCORE: "7"
                        command: |
                          set -e
                          chmod +x scripts/run-dependency-check.sh
                          scripts/run-dependency-check.sh

                  - step:
                      name: Trivy Filesystem
                      identifier: trivy_filesystem
                      type: Run
                      spec:
                        connectorRef: harbor_company
                        image: harbor.company.com/security/trivy:latest
                        shell: Sh
                        envVariables:
                          SCAN_PATH: .
                          SEVERITY: CRITICAL,HIGH
                        command: |
                          set -e
                          chmod +x scripts/run-trivy-fs.sh
                          scripts/run-trivy-fs.sh

    - stage:
        name: Build And Push
        identifier: build_and_push
        type: CI
        spec:
          cloneCodebase: true
          infrastructure:
            type: KubernetesDirect
            spec:
              connectorRef: k8s_dev_cluster
              namespace: harness-ci
              automountServiceAccountToken: true
              os: Linux
          execution:
            steps:
              - step:
                  name: Build And Push Image
                  identifier: build_and_push_image
                  type: BuildAndPushDockerRegistry
                  spec:
                    connectorRef: harbor_company
                    repo: harbor.company.com/devops/harness-demo-app
                    dockerfile: docker/Dockerfile
                    context: .
                    tags:
                      - <+pipeline.variables.image_tag>

    - stage:
        name: Image Scan
        identifier: image_scan
        type: CI
        spec:
          cloneCodebase: false
          infrastructure:
            type: KubernetesDirect
            spec:
              connectorRef: k8s_dev_cluster
              namespace: harness-ci
              automountServiceAccountToken: true
              os: Linux
          execution:
            steps:
              - step:
                  name: Trivy Image Scan
                  identifier: trivy_image_scan
                  type: Run
                  spec:
                    connectorRef: harbor_company
                    image: harbor.company.com/security/trivy:latest
                    shell: Sh
                    envVariables:
                      IMAGE_FULL_NAME: harbor.company.com/devops/harness-demo-app:<+pipeline.variables.image_tag>
                      SEVERITY: CRITICAL,HIGH
                    command: |
                      set -e
                      chmod +x scripts/run-trivy-image.sh
                      scripts/run-trivy-image.sh

              - step:
                  name: Generate SBOM
                  identifier: generate_sbom
                  type: Run
                  spec:
                    connectorRef: harbor_company
                    image: harbor.company.com/security/trivy:latest
                    shell: Sh
                    envVariables:
                      IMAGE_FULL_NAME: harbor.company.com/devops/harness-demo-app:<+pipeline.variables.image_tag>
                    command: |
                      set -e
                      trivy image --format cyclonedx --output sbom-cyclonedx.json "${IMAGE_FULL_NAME}"
                      ls -lh sbom-cyclonedx.json

    - stage:
        name: Deploy
        identifier: deploy
        type: Deployment
        when:
          pipelineStatus: Success
          condition: <+pipeline.variables.deploy_env> != ""
        spec:
          deploymentType: Kubernetes
          service:
            serviceRef: harness_demo_app
          environment:
            environmentRef: <+pipeline.variables.deploy_env>
            deployToAll: false
            infrastructureDefinitions:
              - identifier: <+pipeline.variables.deploy_env>_k8s
          execution:
            steps:
              - step:
                  name: K8s Rolling Deploy
                  identifier: k8s_rolling_deploy
                  type: K8sRollingDeploy
                  timeout: 10m
                  spec:
                    skipDryRun: false

说明:

这套 YAML 使用通用 Run Step 和企业内部镜像,适合国内环境。 如果你已经开通 Harness STO,可以把 SonarQube、Dependency-Check、Trivy 改成 STO 原生 Step。


二十四:Harness STO 方式:SonarQube Step

如果企业已启用 Harness STO,可以直接添加:

Security Tests → SonarQube

推荐配置:

Scan Mode: Orchestration 或 Extraction

Target Type: Repository

Target Name: harness-demo-app

Variant: <+codebase.branch> 或 <+pipeline.variables.image_tag>

Domain: https://sonarqube.company.com

Access Token: <+secrets.getValue("sonar_token")>

Project Key: harness-demo-app

Fail on Severity: HIGH 或 CRITICAL

注意:

SonarQube 的 Quality Gate 和代码覆盖率在 STO 中可能以 Info 或外部策略问题方式呈现。 如果要让 Quality Gate 失败直接阻断流水线,可以结合 OPA Policy,或使用本文的 SonarQube API 脚本显式检查。

企业建议:

STO 用于集中展示安全问题和趋势 Shell/API 脚本用于精确控制质量门阻断逻辑 两者可以结合使用


二十五:Harness STO 方式:Trivy Step

如果使用 STO 原生 Trivy Step,可以扫描:

Container Image Repository SBOM

推荐配置:

Scan Mode: Orchestration

Target Type: Container Image

Registry Type: Docker v2

Domain: harbor.company.com

Image Name: devops/harness-demo-app

Tag: <+pipeline.variables.image_tag>

Access ID: Harbor Robot Account

Access Token: <+secrets.getValue("harbor_robot_token")>

Fail on Severity: HIGH 或 CRITICAL

Additional CLI Flags: --ignore-unfixed --scanners vuln

国内环境建议:

Harbor 如果是自签证书,需要处理证书信任 尽量避免 privileged DinD 优先扫描已推送到 Harbor 的镜像 或扫描 OCI archive


二十六:质量门阻断策略

推荐按环境设置不同阈值。

dev

单元测试失败: 阻断

SonarQube Quality Gate: 可警告,可阻断

SCA: Critical 阻断,High 可警告

镜像扫描: Critical 阻断

test

单元测试失败: 阻断

Quality Gate: 阻断

SCA: Critical/High 阻断

镜像扫描: Critical/High 阻断

pre/prod

必须使用已扫描通过的镜像 Quality Gate 不通过不能进入发布 Critical/High 漏洞必须修复或走安全豁免审批 必须生成 SBOM 必须保留扫描报告


二十七:扫描失败后的处理方式

1. 单元测试失败

处理:

开发修复代码 本地运行测试 重新提交

不建议:

跳过单元测试继续部署


2. SonarQube Quality Gate 失败

处理:

查看 SonarQube 项目页面 确认失败条件 修复 Bugs、Vulnerabilities、Code Smells 或覆盖率问题 重新扫描

可选:

非生产环境可临时允许警告 生产必须有审批豁免


3. SCA 发现高危依赖

处理:

升级依赖版本 替换依赖 确认漏洞是否可达 如果误报,走 suppression 审批


4. 镜像扫描发现高危漏洞

处理:

升级基础镜像 减少不必要系统包 使用 distroless / slim 镜像 重新构建镜像 重新扫描

常见问题:

业务代码没问题,但基础镜像有漏洞 Node/Python/Java 依赖包有漏洞 操作系统包有漏洞


二十八:人工豁免机制

安全扫描一定会遇到误报或短期无法修复的问题。

企业需要定义“豁免”,但不能随便放行。

豁免必须包含:

漏洞编号 影响服务 影响版本 风险说明 是否可利用 缓解措施 负责人 审批人 过期时间 复查时间

豁免文件示例:

security-exemptions.yaml

exemptions:
  - id: SEC-EXEMPT-2026-001
    service: harness-demo-app
    cve: CVE-0000-0000
    reason: 该依赖仅用于构建阶段,不进入运行时镜像
    mitigation: 运行时镜像已验证不包含该组件
    owner: zhangsan
    approved_by: security-team
    expire_at: 2026-12-31

原则:

豁免不是永久放行 豁免必须过期 豁免必须可审计 生产豁免必须安全负责人审批


二十九:国内网络环境重点问题

1. SonarQube 自签证书

表现:

SSL certificate problem PKIX path building failed x509: certificate signed by unknown authority

解决:

CI 镜像加入企业 CA Java truststore 导入证书 curl 使用 --cacert 不要长期使用 -k 绕过证书


2. npm / Maven 依赖下载慢

解决:

npm 使用 npmmirror 或企业 npm 私服 Maven 使用 Nexus / Artifactory CI 镜像预置 settings.xml


3. Dependency-Check 更新 NVD 慢

解决:

预热漏洞库 使用内部缓存 定时更新数据库 CI 中只使用缓存 不要每次从公网全量下载


4. Trivy DB 下载慢

解决:

使用 Trivy 缓存 定时预拉取漏洞库 构建内置 DB 的 Trivy 镜像 使用企业代理


5. Harbor 自签证书

解决:

Runner 节点信任 Harbor CA CI 镜像信任 Harbor CA DinD 背景容器信任 Harbor CA 尽量扫描 OCI archive 或远程 Harbor 镜像


三十:质量扫描模板化

第 14 篇讲过模板,本篇建议沉淀以下模板:

Step Template

unit-test-nodejs sonarqube-scan sonarqube-quality-gate dependency-check-scan trivy-fs-scan trivy-image-scan generate-sbom quality-notify

Stage Template

nodejs-quality-scan-stage java-quality-scan-stage container-image-scan-stage

Pipeline Template

standard-nodejs-ci-quality-pipeline standard-java-ci-quality-pipeline

模板参数:

app_name sonar_project_key image_full_name severity_threshold deploy_env quality_gate_required


三十一:质量门和部署联动

推荐流程:

测试通过 ↓ SonarQube Quality Gate 通过 ↓ SCA 通过 ↓ 镜像构建 ↓ 镜像扫描通过 ↓ 生成 SBOM ↓ 部署 test/pre/prod

不推荐:

扫描失败但仍然自动部署 先部署再扫描 生产部署时才第一次扫描 所有环境都用同一套阈值


三十二:通知示例

scripts/notify-quality-result.sh

#!/usr/bin/env bash
set -euo pipefail

WECOM_WEBHOOK_URL="${WECOM_WEBHOOK_URL:?WECOM_WEBHOOK_URL is required}"
APP_NAME="${APP_NAME:-harness-demo-app}"
DEPLOY_ENV="${DEPLOY_ENV:-dev}"
IMAGE_FULL_NAME="${IMAGE_FULL_NAME:-N/A}"
QUALITY_STATUS="${QUALITY_STATUS:-UNKNOWN}"
EXECUTION_ID="${EXECUTION_ID:-N/A}"

cat > /tmp/quality-notify.json <<EOF
{
  "msgtype": "markdown",
  "markdown": {
    "content": "### Harness 质量扫描结果\\n> 应用:${APP_NAME}\\n> 环境:${DEPLOY_ENV}\\n> 镜像:${IMAGE_FULL_NAME}\\n> 质量门:${QUALITY_STATUS}\\n> Execution:${EXECUTION_ID}"
  }
}
EOF

curl -sS -X POST \
  -H "Content-Type: application/json" \
  --data @/tmp/quality-notify.json \
  "${WECOM_WEBHOOK_URL}"

echo "质量扫描通知已发送"

Harness Step:

WECOM_WEBHOOK_URL="<+secrets.getValue('wecom_webhook_url')>" \
APP_NAME="<+pipeline.variables.app_name>" \
DEPLOY_ENV="<+pipeline.variables.deploy_env>" \
IMAGE_FULL_NAME="<+pipeline.variables.image_repository>:<+pipeline.variables.image_tag>" \
QUALITY_STATUS="<+pipeline.status>" \
EXECUTION_ID="<+pipeline.executionId>" \
scripts/notify-quality-result.sh

三十三:常见问题排查

1. SonarQube 扫描成功,但 Quality Gate 脚本一直等不到结果

可能原因:

SonarQube 后台任务排队 projectKey 写错 Token 权限不足 扫描还未真正完成 网络访问 SonarQube API 失败

排查:

curl -u "$SONAR_TOKEN:" "$SONAR_HOST_URL/api/qualitygates/project_status?projectKey=$SONAR_PROJECT_KEY"

2. SonarQube 没有覆盖率

可能原因:

没有生成 lcov.info sonar.javascript.lcov.reportPaths 路径不对 测试没有实际执行 CI 工作目录不一致

排查:

ls -lh app/coverage/lcov.info
cat sonar-project.properties

3. Dependency-Check 扫描非常慢

原因:

首次下载漏洞库 公网访问慢 没有缓存 项目依赖太多

解决:

预热缓存 搭建内部漏洞库缓存 只在 main/release 分支跑完整扫描 feature 分支跑轻量扫描


4. Trivy image scan 拉不到 Harbor 镜像

原因:

Harbor 认证失败 Robot Token 错误 Harbor 自签证书 镜像 Tag 不存在 CI Pod 无法访问 Harbor

排查:

docker login harbor.company.com
trivy image harbor.company.com/devops/harness-demo-app:v1.0.0

5. 扫描误报太多

处理:

不要直接关闭扫描 先分级处理 Critical/High 优先 建立 suppression / exemption 流程 定期复查 升级依赖和基础镜像


三十四:企业最佳实践

1. 质量门前置

单元测试、代码质量和依赖扫描要在镜像构建前完成。 镜像扫描要在部署前完成。

2. 不同环境不同阈值

dev 可以宽松 test 必须阻断明显问题 pre/prod 必须严格

3. 扫描结果必须可追溯

记录:

Pipeline Execution ID Git Commit Git Branch / Tag Image Tag SonarQube Project SCA 报告 镜像扫描报告 SBOM 文件 豁免审批单

4. 扫描镜像也要从 Harbor 拉

不要依赖公网镜像:

sonar-scanner-cli node maven dependency-check trivy curl-jq

全部同步到:

harbor.company.com/library harbor.company.com/security

5. 安全扫描要有负责人

建议分工:

问题 负责人
单元测试失败 开发
覆盖率不足 开发负责人
SonarQube Bug / Code Smell 开发
SCA 漏洞 开发 + 安全
镜像 OS 漏洞 平台 + 运维
Secret 泄露 安全 + 开发
豁免审批 安全负责人

三十五:练习 1:接入单元测试和覆盖率

目标:

在 Harness CI 阶段运行 npm test,并生成 lcov.info。

步骤:

  1. 添加 Jest
  2. 配置 npm test
  3. 添加 run-unit-test.sh
  4. 运行 Pipeline
  5. 查看 coverage/lcov.info

验收:

测试失败时 Pipeline 阻断 覆盖率报告生成成功


三十六:练习 2:接入 SonarQube

目标:

将代码质量扫描结果上传 SonarQube,并检查 Quality Gate。

步骤:

  1. 创建 SonarQube 项目
  2. 创建 sonar_token
  3. 配置 sonar-project.properties
  4. 运行 sonar-scanner
  5. 运行 wait-sonar-quality-gate.sh

验收:

SonarQube 页面能看到项目 Quality Gate 失败时 Pipeline 阻断


三十七:练习 3:接入 SCA 扫描

目标:

使用 Dependency-Check 或 Trivy filesystem 扫描开源依赖漏洞。

步骤:

  1. 添加 SCA Scan Stage
  2. 运行 Dependency-Check
  3. 运行 Trivy filesystem
  4. 设置 High/Critical 阈值

验收:

发现 High/Critical 漏洞时 Pipeline 阻断 生成 JSON/HTML 报告


三十八:练习 4:接入镜像扫描

目标:

镜像推送 Harbor 后,使用 Trivy image 扫描。

步骤:

  1. BuildAndPush 镜像
  2. 运行 Trivy image
  3. 设置 --severity CRITICAL,HIGH
  4. 设置 --exit-code 1
  5. 生成 SBOM

验收:

镜像漏洞超阈值时 Pipeline 阻断 SBOM 文件生成成功


三十九:练习 5:设置分环境质量门

目标:

dev/test/prod 使用不同阻断阈值。

示例:

dev: Trivy severity = CRITICAL

test: Trivy severity = CRITICAL,HIGH

prod: 只允许已扫描通过的 release image

验收:

不同 Input Set 下扫描阈值不同 生产环境无法绕过质量门


四十:验收标准

完成本文后,应达到:

已理解自动化测试和质量扫描的流水线位置 已能运行单元测试并生成覆盖率报告 已能接入 SonarQube Scanner 已能检查 SonarQube Quality Gate 已能运行 OWASP Dependency-Check 或 Trivy filesystem 已能运行 Trivy image 扫描容器镜像 已能生成 SBOM 已能按 High/Critical 阈值阻断流水线 已能设计 dev/test/pre/prod 不同质量门 已能处理国内网络下 npm/Maven、漏洞库、Harbor、SonarQube 访问问题 已能建立误报豁免流程


四十一:本篇总结

本篇完成了 Harness 自动化测试与质量扫描的落地设计。

你需要重点记住:

测试和扫描要尽量前置 单元测试失败必须阻断 SonarQube Quality Gate 是代码质量准入门槛 SCA 用于发现开源依赖漏洞和许可证风险 镜像扫描用于发现基础镜像和运行时依赖漏洞 SBOM 用于供应链追踪和合规审计 dev/test/prod 应使用不同阈值 生产环境不能绕过质量门 扫描误报必须走可审计的豁免流程 国内环境要提前处理依赖源、漏洞库、证书、Harbor 和代理问题

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐