安全开发-SonarQube
目录
SonarQube(代码质量 / SAST 静态扫描平台) Rancher 集群、Gitea 代码与流水线、Nexus 制品源就位之后,还差一环:你自己写的代码有没有 bug、有没有安全漏洞、有没有难维护的“坏味道“——这是 SonarQube 的活。 SonarQube 是 SonarSource 公司的开源代码质量平台:Web 面板(下称 Server)展示每个项目代码的健康状况,并自带质量门禁机制。本文做三件事: 静态分析:程序不运行,工具把源代码解析成语法结构,再按一套规则库找出潜在问题,业内把“静态应用安全测试“缩写成 SAST。一次 SonarQube 扫描输出三类问题,优先级从高到低: 漏洞第一优先(攻击者正盯着),缺陷其次(程序会坏),坏味道是“健康管理“(慢慢清,别堆积)。有些问题只有运行时才暴露——那要靠测试或动态扫描。 质量门禁(Quality Gate) 是一组阈值(例如“新增漏洞 = 0、覆盖率 ≥ X%“),每次扫描后自动判定 Passed / Failed。流水线里门禁还能“卡“住流程:扫描不通过就不许合并 / 发布(本环境目前只到“扫描出报告”,尚未接成卡点,详见“常见问题与坑“)。 两半靠一个唯一标识对上号——仓库根目录的 JRE 这里要记一下:流水线跑在内网任务容器里、到不了公网下载 JRE,所以任务镜像预装了 JRE;Scanner 本体很小(约 9MB),从 Nexus 的 SonarQube 看代码,Dependency-Track 看依赖。在流水线里两者是各自独立的 workflow: 版本:SonarQube Community 26.7.0.124771 / chart 2026.4.1,经 Rancher + 官方 Helm chart 部署。 chart 来自 SonarSource 官方 chart 仓库(https://sonarsource.github.io/helm-chart-sonarqube)。Community(社区版)免费,Enterprise 等是付费版——本环境用社区版。 图 1:Rancher 界面里的 sonarqube chart(2026.4.1),Community 版 26.7.0.124771——本环境从这里安装 这次部署有两处“内网特色“(官方文档示例里不会强调的): 密码从集群现成的 secret 读、不手敲(不进 shell 历史,文内占位); 为什么需要:内网的证书都是自签的——公网体系里证书由人人信任的公共 CA 签发,内网里大家信任的是自建 CA。SonarQube(Java 程序)配置 OIDC 登录时要主动连 Gitea 的 https 端点,它会用自己的 CA 信任库校验对方证书——内网 CA 不在库里,握手直接被拒(证书错误)。 官方 Helm chart 文档有专门一节讲这个场景(“Use custom cacerts”,就是针对离线环境 / 内部工具 / 自签证书说的):思路是把要信任的证书先放进一个 k8s Secret,再用 chart 的 本环境实际执行(证书文件是搭集群时已获取的根证书,放在集群节点上): 怎么验证:Secret 建好只是第一步,真正的验证在 SonarQube 起来之后——OIDC 登录走通,就说明服务端成功连上了 Gitea 的 https 端点且证书校验通过。 部署时改动的 values(chart 的部署参数;diff 形式, 其余 values 保持 chart 默认(安全上下文、探针、日志级别等),此处不逐项展开。 在图 1 的页面点部署(Deploy),等 pod 就绪。验证两层: 到这里搭建完成,进入网页做“首次登录与初始配置“。 全新 SonarQube 内置一个管理员账号:用户名 默认密码务必在首次登录后修改,新值按团队凭据规范存放(文章只占位,不落明文)。 图 2:初始 admin/admin 登录后看到的创建项目引导页(此时界面还是英文) SonarQube 的扩展机制是 Marketplace(插件市场):进 Administration → Marketplace 搜索、安装,状态 图 3:Marketplace 里的中文语言包,状态 Install Pending(等待重启生效) 图 4:Marketplace 里的 OpenID Connect(OIDC)插件,同样 Install Pending 重启后引导页变成中文界面: 图 5:服务重启后语言包生效,引导页已是中文 Gitea 内置了 OIDC 提供方(IdP):各平台在 Gitea 组织里建一个 OAuth2 应用(客户端),拿到一对 Client ID/Secret 填进自己,之后开发者用 Gitea 账号登录一次、进所有平台都不用再输密码。SonarQube 的接入方式与其它平台相同:Gitea 侧建应用、SonarQube 侧填设置。 Gitea 在 在 创建后 Gitea 颁发 Client ID(半公开的标识)与 Client Secret(等价于密码),占位 图 6:Gitea dev-sec-ops 组织的 OAuth2 应用里创建的 SonarQube 应用:重定向 URL 指向 SonarQube 的 OIDC 回调(Client ID / Secret 已打码) Administration → General Settings → 权限(OIDC 插件提供的表单),关键项: 图 7:SonarQube Administration → General Settings → 权限里的 OIDC 配置:Issuer 指向 hub.kali-team.internal,scopes 为 openid email profile groups(Client ID / Secret 已打码) 同一个 General Settings 页还有两处要看(图 8): 图 8:General Settings:JRE auto-provisioning 关闭(内网无法从公网下载 JRE)、Server base URL 填内网域名(否则 OIDC 跳转默认回 localhost) 登出 admin,回到登录页——表单已经不一样了:没有账号 + 密码,只有“通过 Gitea Connect 登录“和”更多选项“(图 9)。点“通过 Gitea Connect 登录”,浏览器跳到 Gitea,授权后自动回 SonarQube,此时已以 Gitea 身份登录——顶部菜单能看到当前身份(图 10),全程没输任何密码。 图 9:OIDC 生效后,登录页没有密码框,只有“通过 Gitea Connect 登录“与“更多选项“ 图 10:从 Gitea 授权回来后已以 Gitea 身份登录(顶部菜单显示当前身份),进入引导页 验证清单:登录页出现 Gitea 入口 ✓ → 跳转授权后无需密码回到 SonarQube ✓ → 顶部菜单显示 Gitea 身份 ✓。三条都过,说明 OIDC 链路(含 2.3 的 caCerts 证书信任)走通。 引导页里有几种建项目方式,最常见的是接代码平台集成后由平台自动建;本环境没接,手动创建:填名称、Key,绑定分支(图 11),“选择质量门禁“直接用默认策略(内置阈值,1.2)。 图 11:手动创建项目对话框:名称「项目例子」、Key Key 必须与仓库对上(最重要的规则,否则扫描结果挂不上号): 文件里的 向导下一步推荐“使用 GitHub CI 分析项目“(图 12):核心是两个环境变量 图 12:向导的“使用 GitHub CI 分析项目“步骤说明:流水线里传入 SONAR_TOKEN 与 SONAR_HOST_URL 点进去创建令牌(图 13),记下生成的密钥(文内占位 图 13:创建项目令牌对话框(令牌尚未生成,图中不含令牌值) 令牌怎么存(红线):这是一个用户级 API token,长期有效,等价于“账号的钥匙“。本环境只放进 example 仓库的 Gitea Secrets(仓库级加密环境变量,日志里打印自动打码): 不进代码、不进文章、不进聊天——谁拿到令牌,谁就能以你的名义向 SonarQube 提交分析结果。 暂时没有流水线的场景(新项目没接 CI、开发机一次性验证、流水线坏了本地复现),用 SonarScanner CLI 直接扫——就是流水线里那个 scanner,区别只是“由你在命令行驱动“。三步: 第 1 步:获取 scanner 官方下载站内网到不了,从 Nexus raw 仓库 两个前提:scanner 是 Java 程序,开发机要先装一个 JRE(JDK 发行版里就带;流水线任务镜像已预装,1.3);Java 不读系统信任库(详见“常见问题与坑“),开发机若没装内网 CA,系统信任库与 Java 的 第 2 步:写 scanner 靠这个文件对上目标;example 用的是最小形式(上面三行),完整字段按需取用: 第 3 步:执行扫描 跑成功后,5.1 创建的项目页面上会出现一条新的分析记录——和流水线扫的是同一个页面、同一份数据:质量门禁判定、三维度评级、问题列表都在这里看。 这一节只讲操作,分项目级和系统级两层。 项目级:给本项目选一个门禁 项目列表 → 点进项目 → Project Settings → Quality Gate → 从下拉里选一个。内置的 Sonar way 是出厂推荐阈值,也可以选自定义门禁;改动只影响当前项目。 系统级:在 Quality Gates 页自定义条件 管理员入口 → Quality Gates → 新建 / 编辑门禁,逐条设置条件。本环境参考口径: 为什么都是“新代码“:门禁条件可按“全部代码“或“新代码”(“新代码期“内的变动,按版本基线 / 时间窗口划定)定义——对老项目设全量门禁,存量问题会把它永远钉在 Failed,先管增量才可落地。 提醒:门禁配置后每次扫描都判定(含 5.3 的手动扫描);本环境现状是判定只展示在页面上,Failed 不会让流水线失败(详见“常见问题与坑“)。 在流水线里: 全文(example 仓库真实文件,注释保留原样): 前三步与另外两条流水线相同:Init 抓内网 Gitea 证书进系统信任库;Checkout 克隆代码;Verify Certificate 把内网 CA 同时放进系统信任库与 Docker 证书目录( Scanner 从哪来?Nexus 仓库。 官方站在公网,scanner 压缩包(约 9MB)从 Nexus raw 仓库 Java 不读系统证书。 scanner 是 Java 程序,用自己的证书库(keystore, 项目怎么对上号? 在 Gitea Actions 页面手动触发 图 14:SonarQube 项目列表: 目前流水线停在“扫描并出报告“:门禁用的是内置默认策略,判定只展示在 SonarQube 页面上,尚未接成“卡住流水线“的硬卡点(详见“常见问题与坑”)。 这一篇给内网 DevSecOps 体系补上代码质量 / 安全一环:SonarQube 装进 k3s(官方 chart + 共享 PostgreSQL + 自签 CA 覆盖),接入 Gitea OIDC 统一登录, 下一步可选:把质量门禁接成流水线硬卡点(扫不过不许合并,官方 action 支持等待门禁判定)、配更严的门禁策略(New Code 周期、覆盖率阈值)、接入更多项目(key 规则通用—— 前言
caCerts)、Gitea OIDC 单点登录接入;sonarqube.yaml 流水线逐段拆掉,让每次推送 / PR 自动做一遍 SAST(静态应用安全测试)——不运行代码、只“读“代码就能发现问题。 1 背景概念:SonarQube 扫什么
1.1 SAST:不运行代码,只“读“代码就发现问题
类型 英文 是什么 例子 漏洞 Vulnerability 安全洞,攻击者可能利用 SQL 注入:把用户输入直接拼进 SQL 语句 缺陷 Bug 逻辑错误,运行时会出错或行为不对 空指针:一个可能为空的对象没检查就直接用 坏味道 Code Smell 不致错但让代码难读难维护 一个 300 行的函数、魔法数字满天飞 1.2 质量门禁:扫描的判定标准
1.3 Server 与 Scanner 的分工:谁存结果、谁干活
sonarqube.kali-team.internal),存分析结果,提供面板、门禁判定和权限管理。sonar-project.properties 文件:sonar.projectKey = example # 项目唯一标识:必须与 SonarQube Server 上创建的项目 Key 完全一致
sonar.projectName = 项目例子 # 显示名称:面板上看到的名字
sonar.sources = . # 扫描范围:扫哪些目录(这里是整个仓库)sonar-scanner-cli 仓库下载。 1.4 SAST 的定位:对照 SCA 与 DAST
SAST(SonarQube) SCA(Dependency-Track) DAST(动态,本环境未部署) 看什么 你自己写的代码 你引用的第三方依赖的已知漏洞 运行中的系统(黑盒发请求探测) 输入 源代码 SBOM(软件物料清单) HTTP 请求 / 响应 发现什么 注入风险、逻辑缺陷、坏味道 依赖组件的 CVE / KEV 运行服务的真实攻击面 典型动作 修代码 升级依赖版本 修配置 / 堵接口 sonarqube.yaml 扫自己的代码,dependency-track.yaml 传 SBOM 查依赖——互不干扰,合起来构成“代码 + 依赖“两道安检。 2 搭建
2.1 总体思路:要哪些组件
组件 本环境的来源 SonarQube 本体 官方 chart sonarqube(chart 2026.4.1,对应 Community 版 26.7.0.124771 构建),在 Rancher 界面里安装数据库 复用 Nexus 安装时带出来的共享 PostgreSQL(middleware 命名空间),只为它建一个专属库 sonarqubeCA 证书 kali-team-root-ca.crt——内网根证书,搭集群时就已获取Ingress 域名 sonarqube.kali-team.internal,TLS 由集群网关统一终结
caCerts 配置来覆盖默认 CA 库;jdbcOverwrite 指过去。 2.2 在共享 PostgreSQL 上建 sonarqube 库
kubectl 本身没有 psql 客户端,所以临时起一个 pod(--rm 表示退出后自动删除)当“跳板“,在里面执行建库 SQL:# 从现成的 secret 读取共享 PostgreSQL 的超级用户密码
# secret 是 k8s 里的凭据对象,密码以 base64 编码存放,管道里 base64 -d 解码
export POSTGRES_PASSWORD=$(kubectl get secret --namespace middleware postgresql \
-o jsonpath="{.data.postgres-password}" | base64 -d)
# 起一个临时 postgresql-client pod 执行"建库"
# --rm 退出后自动删 pod
# --restart='Never' 不重启
# --tty -i 分配终端、接标准输入
# --command -- ... 覆盖容器默认启动命令,直接跑 psql
kubectl run postgresql-client --rm --tty -i --restart='Never' \
--namespace middleware \
--image registry-1.docker.io/bitnami/postgresql:latest \
--env="PGPASSWORD=$POSTGRES_PASSWORD" \
--command -- psql --host postgresql -U postgres -d postgres -p 5432 \
-c "CREATE DATABASE sonarqube;"postgresql 是共享库在集群内的服务名,5432 是 PostgreSQL 默认端口。实际输出:CREATE DATABASE 一行成功,临时 pod 自动删除。 2.3 把内网 CA 证书放进 Secret
caCerts 配置指向它,覆盖默认 CA 库:# chart 文档的示例写法(本环境 Secret 名用 kali-team-root-ca)
apiVersion: v1
kind: Secret
metadata:
name: my-cacerts
stringData:
cert-1.crt: |
xxxxxxxxxxxxxxxxxxxxxxx # 证书的 PEM 内容kubectl create secret generic kali-team-root-ca \
-n sonarqube \
--from-file=kali-team-root-ca.crt=kali-team-root-ca.crt
# 输出:secret/kali-team-root-ca created 2.4 Helm values 改动
+ 为相对 chart 默认值新增 / 改动的行):@@ caCerts @@
caCerts:
- enabled: false
+ enabled: true
+ secret: kali-team-root-ca
@@ community @@
community:
buildNumber: 26.7.0.124771
- enabled: false
+ enabled: true
@@ deploymentStrategy @@
deploymentStrategy:
- type: Recreate
+ type: RollingUpdate
+ maxSurge: 1
+ maxUnavailable: 0
@@ ingress @@
ingress:
- enabled: false
+ enabled: true
hosts:
- - name: sonarqube.your-org.com
+ - name: sonarqube.kali-team.internal
@@ jdbcOverwrite @@
jdbcOverwrite:
- enabled: false
- jdbcPassword: sonarPass
- jdbcUrl: jdbc:postgresql://myPostgres/myDatabase
- jdbcUsername: sonarUser
+ enabled: true
+ jdbcPassword: <POSTGRES_PASSWORD>
+ jdbcUrl: jdbc:postgresql://postgresql.middleware.svc.cluster.local:5432/sonarqube
+ jdbcUsername: postgres
@@ persistence @@
persistence:
- enabled: false
+ enabled: true
- size: 5Gi
+ size: 10Gi
@@ resources.limits @@
resources:
limits:
cpu: 800m
- memory: 6144M
+ memory: 4196M
requests:
cpu: 400m
@@ 末尾新增 @@
+monitoringPasscode: community配置 原因 caCertsenabled: true + secret 指向前面建好的 kali-team-root-ca——让 SonarQube 信任内网 CA;验证方式:OIDC 登录可用communitychart 同时支持 Community(免费)与 Enterprise(付费), enabled: true 选社区版;buildNumber 钉住具体构建号 26.7.0.124771(与图 1 chart 页一致,版本钉死避免意外漂移)deploymentStrategychart 默认 Recreate(先删旧 pod 再起新 pod——中间有短暂“服务不可用“窗口);改成 RollingUpdate(滚动更新:先起新的、就绪后再删旧的),maxSurge: 1(更新期间最多多出一个新 pod)、maxUnavailable: 0(新的没就绪前旧的不下线,服务不断)ingress启用并填域名 sonarqube.kali-team.internal;流量经集群统一网关终结 TLS,证书由内网 CA 签发——这也是“浏览器直接打开域名就是绿锁“的原因jdbcOverwriteSonarQube 的分析数据必须落 PostgreSQL,chart 默认的 jdbcUrl 是占位符 myPostgres/myDatabase——必须覆盖:enabled: true,URL 指向共享库的集群内地址 postgresql.middleware.svc.cluster.local:5432,库名 sonarqube(2.2 建的)、用户名 postgres(本环境直接用共享库超级用户)、密码用 2.2 读到的值(占位)。整篇搭建里“数据放哪“只由这一段决定persistenceenabled: true + 10Gi——申请一块 PVC(Persistent Volume Claim,跨重启不丢的“磁盘“)存 SonarQube 自身的文件类数据,pod 重建不丢;storageClass 不指定,用集群默认resources.limits.memory思路是“内存给多点,java 开发的,避免执行任务的时候 OOM“(OOM = Out Of Memory,内存不够被系统杀掉)。SonarQube 是 Java 服务,分析任务吃内存,上限给低了 pod 会被 OOM 杀掉反复重启。chart 默认 6144M,本环境最终值 4196M(约 4.1Gi)——这台小规格节点的实际内存是上限 monitoringPasscodechart 必填参数,需要指定添加,本环境对应社区版填 community;不填 chart 校验不过 2.5 安装与验证
Running,重启次数稳定;https://sonarqube.kali-team.internal 出现登录页(浏览器信任内网 CA、证书有效——这一步同时验证了 ingress 与域名证书)。 3 首次登录与初始配置
3.1 用默认 admin 账号登录
admin,默认密码 admin。登录后第一眼看到的是“创建项目“引导页(图 2——此时界面还是英文)。
3.2 安装中文语言包与 OIDC 插件
Install Pending(等待重启生效),重启后自动加载。要装两个:


4 配置 OIDC 登录:用 Gitea 账号进 SonarQube
4.1 为什么能复用 Gitea 单点登录
4.2 先看“说明书“:OIDC 发现端点
https://hub.kali-team.internal/.well-known/openid-configuration 暴露一份 JSON“说明书“(发现端点):issuer、各端点地址、支持的 scope 全列在里面——打开这个链接就知道怎么填了:SonarQube 的 OIDC 表单字段基本都能从这份 JSON 里找到对应值。 4.3 Gitea 侧:创建 OAuth2 应用
dev-sec-ops 组织 → 设置 → 应用 → 创建 OAuth2 应用程序:SonarQube;https://sonarqube.kali-team.internal/oauth2/callback/oidc——SonarQube 侧的固定“回调地址“,OIDC 插件的默认回调,照抄即可;<client-id> / <client-secret>,只写进 SonarQube 的 OIDC 设置。
4.4 SonarQube 侧:填 OIDC 设置
字段 值 Issuer(签发方) https://hub.kali-team.internal——声明“我的 IdP 是 Gitea“,SonarQube 会拿这个地址去自动读取 4.2 的发现端点Client ID / Client Secret <client-id> / <client-secret>(4.3 创建的应用的身份凭据)Scopes openid email profile groups——openid 是基础身份(必需)、email / profile 是邮箱与资料、groups 关键:把用户的组信息带进令牌,平台按组授权限签名算法 RS256——Gitea 给令牌签名用的算法(发现端点里能查到),SonarQube 用它验“令牌没被篡改“
4.5 两个通用设置
https://sonarqube.kali-team.internal。配置 Server base URL,不然 OIDC 跳转的时候会默认跳到 localhost——OIDC 流程里 SonarQube 需要知道“我对外叫什么地址“,不配就默认 localhost,用户从 Gitea 授权回来后会被送回自己电脑,登录闭环直接断掉。
4.6 验证:用“Gitea Connect“登录


5 使用:创建项目与令牌
5.1 创建项目(手动)

example、绑定 main 分支$ cat sonar-project.properties # example 仓库根目录的文件
sonar.projectKey = example
sonar.projectName = 项目例子
sonar.sources = .sonar.projectKey 必须与创建的项目 Key 完全一致(这里都是 example)。 5.2 创建令牌(扫描器的凭据)
SONAR_TOKEN(令牌)与 SONAR_HOST_URL(SonarQube 地址)。Gitea Actions 与 GitHub Actions 语法兼容,官方的 sonarqube-scan-action 可以直接用(内网自托管版见下文)。
<SONARQUBE_ANALYSIS_TOKEN>;截图本身不含令牌值)。
Secret 值 SONAR_TOKEN<SONARQUBE_ANALYSIS_TOKEN>SONAR_HOST_URLhttps://sonarqube.kali-team.internal 5.3 用 SonarScanner CLI 手动扫描(没有流水线时)
sonar-scanner-cli 取——raw 仓库没有目录列表,必须显式写全文件名:# 做什么:从内网 Nexus 仓库下载 scanner
# 怎么做:wget 显式文件名(raw 仓库不能"列目录挑文件")
# 为什么:官方站内网不可达;Nexus 里这份是官方站取回缓存的同一文件
wget https://nexus.kali-team.internal/repository/sonar-scanner-cli/sonar-scanner-8.1.0.6389-linux.tgz
# 解压到 /opt,并把 bin 目录加进 PATH,之后 sonar-scanner 命令直接可用
tar -xzf sonar-scanner-8.1.0.6389-linux.tgz -d /opt/
export PATH="$PATH:/opt/sonar-scanner-8.1.0.6389-linux/bin"cacerts 两处都要导入,思路同 6.2 的“双通道“。sonar-project.properties字段 必填 作用 sonar.projectKey是 项目唯一标识:必须与 SonarQube 上创建的项目 Key 一致,对不上结果就挂不到项目下 sonar.projectName建议 面板显示名 sonar.projectVersion建议 项目版本,随分析记录展示 sonar.sources是 扫描哪些目录,多个用逗号分隔; . = 整个仓库(example 的写法)sonar.exclusions可选 排除模式,如 **/vendor/**,**/node_modules/**——生成代码 / 第三方代码不该占扫描指标sonar.sourceEncoding可选 源码编码,本环境统一 UTF-8sonar.language可选 手动指定语言;可省略——scanner 会自动识别(example 的 Go 没写这条,照样识别) # 做什么:扫描当前目录的代码,结果提交给 SonarQube
# 怎么做:命令行 -D 传两个参数——服务器地址 + 令牌(-D 是 scanner 传配置
# 的方式,命令行参数优先于 properties 文件)
# 为什么:令牌是 scanner 连接 SonarQube 的凭据;<SONARQUBE_ANALYSIS_TOKEN> 为本文占位符
sonar-scanner \
-Dsonar.host.url=https://sonarqube.kali-team.internal \
-Dsonar.token=<SONARQUBE_ANALYSIS_TOKEN> 5.4 配置质量门禁:点哪里、配什么
条件 阈值 含义 新代码覆盖率 ≥ 80% 新增代码里被单元测试覆盖的比例 新代码重复率 ≤ 3% 新增代码里互相复制的比例 维护性等级 A 评级 A–E,A 最好 安全等级 A 同上 6 CI/CD 联动:sonarqube.yaml 逐段讲
6.1 SAST 在流水线里的位置
.gitea/workflows/sonarqube.yaml 的触发条件:打版本 tag(v* push)、PR 事件、repository_dispatch(webhook)、workflow_dispatch(手动)——每次代码变动都自动做一遍静态扫描。docker.yaml 管构建镜像、dependency-track.yaml 管依赖扫描(SCA)、sonarqube.yaml 管自己的代码扫描(SAST)——三条并行、各管一段。 6.2 四个步骤逐段看
on:
# Trigger analysis when pushing to your main branches, and when creating a pull request.
push:
tags:
- "v*"
pull_request:
types: [opened, synchronize, reopened]
repository_dispatch: # 通过 Webhook 触发
workflow_dispatch: # 支持手动立即触发
name: Main Workflow
jobs:
sonarqube:
runs-on: ubuntu-host
env:
RUNNER_TOOL_CACHE: /toolcache
steps:
- name: Init
run: |
echo -n | openssl s_client -showcerts -connect hub.kali-team.internal:443 2>/dev/null | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' >> hub.kali-team.internal.crt
sudo cp hub.kali-team.internal.crt /usr/local/share/ca-certificates/
sudo update-ca-certificates
- name: Checkout code
uses: https://hub.kali-team.internal/actions/checkout@v4
env:
GIT_SSL_NO_VERIFY: true
with:
submodules: true
fetch-depth: 0
token: ${{ secrets.TOKEN }}
- name: Verify Certificate
run: |
sudo cp kali-team-ca.crt /usr/local/share/ca-certificates/
sudo mkdir -p /etc/docker/certs.d/mirror.kali-team.internal
sudo cp kali-team-ca.crt /etc/docker/certs.d/mirror.kali-team.internal/ca.crt
sudo update-ca-certificates
- name: SonarQube Scan 静态代码扫描
uses: https://hub.kali-team.internal/actions/sonarqube-scan-action@v9
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} # 创建项目的时候创建的长期有效的Token
SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }} # SonarQube服务器地址
NODE_EXTRA_CA_CERTS: /usr/local/share/ca-certificates/kali-team-ca.crt # 信任dev-sec-ops证书
# SONAR_ROOT_CERT: ${{ secrets.SONAR_ROOT_CERT }} #sonar是java写的,他不读取系统证书,所以还要加到环境变量,PEM格式的证书
with:
scannerBinariesUrl: https://nexus.kali-team.internal/repository/sonar-scanner-cli/ # 下载SonarScanner CLI的URL,使用私有Nexus仓库
scannerFile: sonar-scanner-8.1.0.6389-linux.tgz # 显式文件名(Nexus raw 仓库无目录列表接口,实测 GET / 返回 404)
projectBaseDir: . # 项目根目录,默认为当前工作目录
skipSignatureVerification: true # 不知道为什么内网有时候访问hkps://keyserver.ubuntu.com提示证书错误/etc/docker/certs.d/),让 git、docker 都认内网 https。重点讲最后一步:sonar-scanner-cli 取,scannerBinariesUrl 指向它。raw 仓库没有目录列表接口(GET / 直接 404),必须在 scannerFile 里显式写死文件名。大头的 JRE 完全不下载:任务镜像已预装。$JAVA_HOME/lib/security/cacerts),不读 Linux 系统信任库——前面导再多次,Java 也看不见。解法分两路:node 部分靠 NODE_EXTRA_CA_CERTS(node.js 的“额外信任证书“环境变量);Java 部分由 action 内部用 keytool(JDK 自带的证书管理工具)把 kali-team-ca.crt 导入 cacerts。两路都通,https 握手才成功。projectBaseDir: . 告诉 scanner 从当前目录开始扫,它在该目录读 sonar-project.properties:文件里 sonar.projectKey=example 必须与 Server 侧项目一致,结果就挂到那个项目下。skipSignatureVerification: true。 官方 action 默认还会对下载包做 GPG 签名校验(防篡改的签名机制)。内网 Nexus 的包是自己传的,没有签名可验;验签还要连公网密钥服务器(文件注释里的实测现象:内网访问 hkps://keyserver.ubuntu.com 有时报证书错误)——所以关掉。完整性改由“内网 Nexus + 内部管控流程“保证。 6.3 实测:run #8
sonarqube.yaml:sonarqube job 耗时 21s,状态 success。随后 SonarQube 上 example 项目(显示名「项目例子」)出现扫描记录:
项目例子(61 行代码,Go)最新分析 Quality Gate Passed,Security / Reliability / Maintainability 三维度 A/0、A/0、A/1,Coverage 0.0%——与下表同一份数据指标 实测值 怎么读 Quality Gate Passed 扫描通过门禁标准 代码行数 61 example 项目很小 Security / Reliability / Maintainability 0 / 0 / 1(open issues) 安全漏洞 / 稳定性缺陷 / 可维护性坏味道三维度未关闭问题数,评级 A;那个“1“是坏味道 Coverage 0.0% 单元测试覆盖的代码比例——示例项目没写单测,0% 符合预期 最后分析时间 2026-10-15 14:12 UTC 即本次 run 完成时间 7 常见问题与坑
hub.kali-team.internal 证书校验失败。根因:SonarQube 是 Java 程序、用自带 CA 信任库,内网 CA 不在默认库里。解法:chart 的 caCerts——根证书先建 Secret,caCerts.enabled: true + secret 指向它覆盖默认库(2.3、2.4 的 caCerts 项)。localhost,回调地址算错。解法:General Settings 里填真实内网域名。cacerts;解法是双通道——action 内 keytool 导入内网 CA 到 cacerts,node 侧走 NODE_EXTRA_CA_CERTS(6.2 的证书步骤)。scannerFile 时 action 无法列目录找文件(6.2 的第一步)。教训:raw 仓库取文件,永远显式写文件名。apt-get remove wget 误删扫描器的下载工具。 流水线 Init 里曾有一句删 wget 的“瘦身“,而 scan-action 靠 wget 从 Nexus 拉 scanner——删完下载直接失败。7dce1ad 删掉该行后一次跑通。教训:给任务容器做工具面改动前,先确认每个 action 实际用到哪些命令。admin/admin 可登;首次登录后必须修改,新值按团队凭据规范存放;本文与截图一律占位 / 打码。monitoringPasscode。 不填 chart 校验不过(2.4 的第 8 项);需要指定添加,本环境用 community。resources.limits.memory。 8 小结
sonarqube.yaml 让每次推送 / PR 自动跑一遍 SAST 扫描——21 秒出结果,质量门禁判定在页面上可见。sonar-project.properties 的 projectKey 是两者对上的唯一标识;caCerts 覆盖、values 五个重点(community 版、jdbcOverwrite、ingress、persistence、内存);sonar-project.properties 的 key 与服务端项目一致即可)。至此内网五平台齐位:Gitea(代码)→ Harbor(镜像)→ Nexus(制品)→ SonarQube(代码质量)→ Dependency-Track(依赖安全),流水线覆盖“代码 → 镜像 → 依赖 → 质量“全链。 参考