Harness 教程 15:自动化测试与质量扫描:SonarQube、SCA、镜像安全扫描与质量门落地
一:教程定位
在前 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 地址建议:
如果是自签证书,需要让 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。
步骤:
- 添加 Jest
- 配置 npm test
- 添加 run-unit-test.sh
- 运行 Pipeline
- 查看 coverage/lcov.info
验收:
测试失败时 Pipeline 阻断 覆盖率报告生成成功
三十六:练习 2:接入 SonarQube
目标:
将代码质量扫描结果上传 SonarQube,并检查 Quality Gate。
步骤:
- 创建 SonarQube 项目
- 创建 sonar_token
- 配置 sonar-project.properties
- 运行 sonar-scanner
- 运行 wait-sonar-quality-gate.sh
验收:
SonarQube 页面能看到项目 Quality Gate 失败时 Pipeline 阻断
三十七:练习 3:接入 SCA 扫描
目标:
使用 Dependency-Check 或 Trivy filesystem 扫描开源依赖漏洞。
步骤:
- 添加 SCA Scan Stage
- 运行 Dependency-Check
- 运行 Trivy filesystem
- 设置 High/Critical 阈值
验收:
发现 High/Critical 漏洞时 Pipeline 阻断 生成 JSON/HTML 报告
三十八:练习 4:接入镜像扫描
目标:
镜像推送 Harbor 后,使用 Trivy image 扫描。
步骤:
- BuildAndPush 镜像
- 运行 Trivy image
- 设置 --severity CRITICAL,HIGH
- 设置 --exit-code 1
- 生成 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 和代理问题
更多推荐




所有评论(0)