背景
场景
- 极狐GitLab旗舰版已经具备扫描能力,但是某些场景下扫描能力偏弱,所以希望集成第三方扫描工具作为补充。但是,单纯的通过Runner调用的方式,只是松散的集成,无法真正形成「深度集成」的体验。
- 若可以「在极狐GitLab的漏洞报告中展现第三方扫描工具的扫描结果」,那么,对于集成的体验则会提升很多。本文即针对此目的进行展开说明。
外部扫描器选型 SonarQube-CE
- 以非常流行的扫描工具SonarQube社区版为例加以说明。
快速入门
直接在.gitlab-ci.yml
文件中填入如下内容,触发扫描,即可在漏洞报告中查看结果。
yaml
variables:
sonar_host_url: http://1.13.160.207:9000
sonar_login: 333f3410ce3e575d559329e8f3d0a5d4ec8a499d
sonarqube:
artifacts:
reports:
sast:
- gl-sast-report.json
script:
- "/scan.sh"
image:
name: satomic/sonarscanner:v6
以下仓库参考
- Java: jihudemo.online/demo/standa...
- cpp: jihudemo.online/demo/standa...
- Python: jihudemo.online/mumutech/de...
- PHP: jihudemo.online/mumutech/de...
使用方法
参考dvwa仓库中的配置方法。
SonarQube-CE部署
- 公网环境中已经部署好SonarQube,可以忽略部署过程,继续往下。
- 若为私网环境,参考下文部署章节。
环境变量配置
在project级别中,settings
- CI/CD
- Variables
中添加如下2个变量指向SonarQube服务。
sonar_host_url
:http://1.13.160.207:9000
sonar_login
:333f3410ce3e575d559329e8f3d0a5d4ec8a499d
.gitlab-ci.yml
配置
在待扫描仓库中增加.gitlab-ci.yml
配置文件,如下,不建议直接把敏感信息如快速入门那样配置在ci文件中。
yaml
sonarqube:
artifacts:
reports:
sast:
- gl-sast-report.json
script:
- "/scan.sh"
image:
name: satomic/sonarscanner:v6
然后进行扫描即可得到扫描结果。
报告总览
特定issue展现
文件定位
SonarQube说明
SonarQube原理
docker方式部署 SonarQube-CE 7.6
根据文章How to get the sonar-report.json file created with sonarqube?、How to get sonar-report.json file to display sonar issues at gerrit level itself中所言,从7.7
版本开始,不支持在scanner端导出json格式的报告,因此部署支持的旧版本中的最后一个版本,即7.6
版。
PgSql
参考Docker 安装SonarQube 步骤以及遇到的坑进行部署,创建工作目录
arduino
mkdir -p /home/sonar/postgres/postgresql
mkdir -p /home/sonar/postgres/data
创建网络
lua
docker network create sonarqube-network
部署pg
ini
docker run --name postgres -d -p 5432:5432 --network sonarqube-network \
-v /home/sonar/postgres/postgresql:/var/lib/postgresql \
-v /home/sonar/postgres/data:/var/lib/postgresql/data \
-v /etc/localtime:/etc/localtime:ro \
-e POSTGRES_USER=sonar \
-e POSTGRES_PASSWORD=sonar \
-e POSTGRES_DB=sonar \
-e TZ=Asia/Shanghai \
--restart always \
--privileged=true \
--network-alias postgres \
postgres
SonarQube
创建工作目录
bash
mkdir -p /data/sonarqube_dir
修改系统参数
bash
echo "vm.max_map_count=262144" > /etc/sysctl.conf
sysctl -p
运行测试容器
arduino
docker run -d --name sonartest sonarqube:7.6-community
拷贝必须文件到本地,并修改权限为777
bash
docker cp sonartest:/opt/sonarqube/conf /data/sonarqube_dir
docker cp sonartest:/opt/sonarqube/data /data/sonarqube_dir
docker cp sonartest:/opt/sonarqube/logs /data/sonarqube_dir
docker cp sonartest:/opt/sonarqube/extensions /data/sonarqube_dir
chmod -R 777 /data/sonarqube_dir/
删除容器
arduino
docker stop sonartest
docker rm sonartest
启动SonarQube,其中SONARQUBE_JDBC_URL
的IP地址需要修改为实际PgSql数据库的IP
ini
docker run -itd --name sonar -p 9000:9000 \
-e ALLOW_EMPTY_PASSWORD=yes \
-e SONARQUBE_DATABASE_USER=sonar \
-e SONARQUBE_DATABASE_NAME=sonar \
-e SONARQUBE_DATABASE_PASSWORD=sonar \
-e SONARQUBE_JDBC_URL="jdbc:postgresql://192.168.1.4:5432/sonar" \
--privileged=true \
--network sonarqube-network \
--restart always \
-v /data/sonarqube_dir/logs:/opt/sonarqube/logs \
-v /data/sonarqube_dir/conf:/opt/sonarqube/conf \
-v /data/sonarqube_dir/data:/opt/sonarqube/data \
-v /data/sonarqube_dir/extensions:/opt/sonarqube/extensions\
sonarqube:7.6-community
生成token
登录SonarQube UI地址( http://IP:9000 ),默认用户名密码为admin/admin
,在My Account
- Security
中生成Token。
部署使用sonar-scanner
参考SonarScanner下载最新版的扫描器。
解压后,进入bin
目录,执行如下命令即可完成扫描,扫描完成后可以去SonarQube中查看扫描结果。
ini
./sonar-scanner \
-Dsonar.host.url=http://1.13.160.207:9000 \
-Dsonar.login=333f3410ce3e575d559329e8f3d0a5d4ec8a499d \
-Dsonar.projectKey=my:test \
-Dsonar.sources=/path_to_codes
如果想要在本地生成json
格式报告,则增加如下参数
ini
-Dsonar.report.export.path=report.json \
-Dsonar.analysis.mode=preview
扫描器制作
json 格式转换
基于以上内容,关键是把扫描结果转化为极狐GitLab旗舰版所识别的json格式。
json 样本
SonarQube扫描结果issue样本
python
{
"key": "AX-Gc4tIjhpt-OIbVVk0",
"component": "my:fuck:fuck/dvwa/includes/DBMS/MySQL.php",
"line": 70,
"startLine": 70,
"endLine": 70,
"message": "Extract this nested ternary operation into an independent statement.",
"severity": "MAJOR",
"rule": "php:S3358",
"status": "OPEN",
"isNew": False,
"creationDate": "2022-03-14T11:22:52+0800"
}
极狐GitLab报告展现issue样本
json
{
"category": "test",
"message": "这个问题不怎么严重",
"cve": "python-webhook/MicroService/Service.py:960662f9bd521d32692b07bd8d5b10538924c23c37cec891847f40e436c5c2f:B104",
"severity": "Medium",
"confidence": "Medium",
"scanner": {
"id": "test",
"name": "test"
},
"location": {
"file": "python-webhook/MicroService/Service.py",
"start_line": 26,
"end_line": 28
},
"identifiers": [
{
"type": "bandit_test_id",
"name": "Bandit Test ID B104",
"value": "B104",
"url": "https://bandit.readthedocs.io/en/latest/plugins/b104_hardcoded_bind_all_interfaces.htl"
}
]
}
转换器 converter.py
转码程序采用python,编写converter.py
文件,内容如下:
kotlin
# coding=utf-8
# Copyright 2022 Xuefeng Yin, All Rights Reserved
from datetime import datetime
import json
import hashlib
f = open(".scannerwork/report.json", "r")
report = json.loads(f.read())
issues = report.get("issues")
# {u'INFO': 50, u'BLOCKER': 3, u'MAJOR': 5724, u'CRITICAL': 1089, u'MINOR': 1103}
severitys_mapper = {
"INFO": "info",
"BLOCKER":"Unknown",
"MAJOR":"High",
"CRITICAL":"Critical",
"MINOR":"Low",
}
# = issue.get("")
def conv(issue):
component = issue.get("component")
startLine = issue.get("startLine")
endLine = issue.get("endLine")
message = issue.get("message")
severity = issue.get("severity")
rule = issue.get("rule")
# "": ,
ret = {
"category": "sast",
"message": message,
"cve": "",
"severity": severitys_mapper.get(severity, "Unknown"),
"confidence": severitys_mapper.get(severity, "Unknown"),
"scanner": {
"id": "sonarqube",
"name": "sonarqube"
},
"location": {
"file": component.split(":")[-1],
"start_line": startLine,
"end_line": endLine
},
"identifiers": [
{
"type": rule,
"name": rule,
"value": rule,
"url": ""
}
]
}
id = hashlib.sha256(json.dumps(ret, sort_keys=True)).hexdigest()
ret["id"] = id
return ret
dateTimeObj = datetime.now()
timeStr = dateTimeObj.strftime("%Y-%m-%dT%H:%M:%S")
gl_sast_report = {
"version": "3.0.0",
"vulnerabilities": [],
"remediations": [],
"scan": {
"scanner": {
"id": "sonarqube",
"name": "SonarQube",
"url": "https://docs.sonarqube.org/",
"vendor": {
"name": "GitLab"
},
"version": "1.7.0"
},
"type": "sast",
"start_time": timeStr,
"end_time": timeStr,
"status": "success"
}
}
for i, issue in enumerate(issues[:]):
#print("Issue No. %s ---------------------" % i)
#print("SonarQube: %s" % issue)
issue_gitlab = conv(issue)
#print("GitLab: %s" % issue_gitlab)
gl_sast_report["vulnerabilities"].append(issue_gitlab)
gl_sast_report_file = open("gl-sast-report.json", "w")
gl_sast_report_file.write(json.dumps(gl_sast_report, indent=4, sort_keys=True))
gl_sast_report_file.close()
scan.sh
基于环境变量进行扫描
bash
pwd
/sonar-scanner-4.7.0.2747-linux/bin/sonar-scanner -Dsonar.host.url=$sonar_host_url -Dsonar.login=$sonar_login -Dsonar.projectKey=my:test -Dsonar.sources=. -Dsonar.report.export.path=report.json -Dsonar.analysis.mode=preview
ls -l
ls -l .scannerwork
python /sonar-scanner-4.7.0.2747-linux/bin/converter.py
ls -l gl-sast-report.json
Dockerfile
- 基于
ubuntu:18.04
制作扫描器 - 默认没有python环境,安装python环境
- 拷贝提前下载好的sonar-scanner到镜像内
- 设定默认工作目录
- 复制转换器
sql
from ubuntu:18.04
run apt update -y
run apt install python -y
add sonar-scanner-4.7.0.2747-linux.tar .
workdir ./sonar-scanner-4.7.0.2747-linux/bin
add converter.py .
add scan.sh /
构建与推送
制作好的镜像已经推送到DockerHub中,采用前文使用方法
中所说的内容即可实现扫描。
bash
docker build -t satomic/sonarscanner:v6 .
docker push satomic/sonarscanner:v6
Findbugs支持
安装插件
Server端安装Findbugs插件
到SonarQube的容器内部
bash
docker exec -it sonar bash
在插件路径中下载findbugs的jar包
bash
cd /opt/sonarqube/extensions/plugins
wget https://github.com/spotbugs/sonarfindbugs/releases/download/3.10.0/sonar-findbugs-plugin-3.10.0.jar
然后重启sonar即可
docker restart sonar
然后到sonar的UI上检查是否安装成功,出现如下画面即表示可以。
配置Java默认规则
在sonar UI上进行如下配置,这样Java的默认规则就是Findbugs了
Java maven 扫描器制作
因为扫描Java项目需要代码经过编译,所以需要对扫描器配置maven扫描能力。理论上,通过在Dockerfile中添加安装maven包即可,但是失败,因此直接在前述步骤构建出的镜像中操作完后commit出镜像。操作步骤如下:
安装软件包列表
python
apt install openjdk-11-jdk-headless -y
apt install maven -y
apt install git
apt install wget
apt install unzip
apt install zip
apt install vim
apt install tree
commit命令
bash
docker commit 100fd2c54f0a satomic/sonarscanner:v7-mvn
此时,考虑到参数暴露的问题,仅仅把如下4个参数内置到扫描器内部,因此修改scan.sh
文件如下
sonar.host.url
SonarQube Server地址sonar.login
tokensonar.report.export.path
本地report.json路径sonar.analysis.mode=preview
本地预览模式
bash
pwd
/sonar-scanner-4.7.0.2747-linux/bin/sonar-scanner -X -Dsonar.host.url=$sonar_host_url -Dsonar.login=$sonar_login -Dsonar.report.export.path=report.json -Dsonar.analysis.mode=preview
ls -l
ls -l .scannerwork
python /sonar-scanner-4.7.0.2747-linux/bin/converter.py
ls -l gl-sast-report.json
再更新如上的scan.sh
文件,所以Dockerfile更新为
csharp
from satomic/sonarscanner:v7-mvn
add scan.sh /
构建出最新镜像
bash
docker build -t satomic/sonarscanner:v9-mvn .
docker push satomic/sonarscanner:v9-mvn
使用
.gitlab-ci.yml
更新
为了支持Findbug的触发,需要使用如下配置
bash
# 正常情况下应该配置在settings设置的环境变量中,而不是如此明文暴露
variables:
sonar_host_url: http://1.13.160.207:9000
sonar_login: 333f3410ce3e575d559329e8f3d0a5d4ec8a499d
sonarqube:
artifacts:
reports:
sast:
- gl-sast-report.json
script:
# 通过在此动态生成 sonar-project.properties 配置,而无需侵入源码仓库
# 同同时可以提高自由度
- echo -e "sonar.projectKey=JavaProj\nsonar.projectName=JavaProj\nsonar.projectVersion=1.0\nsonar.sourceEncoding=UTF-8\nsonar.language=java\nsonar.sources=.\nsonar.java.binaries=./target/classes\nsonar.language=java\nsonar.ce.javaOpts=-Xmx2560m -Xms853m -XX:+HeapDumpOnOutOfMemoryError" > sonar-project.properties
# 检查生成的配置是否正常
- cat sonar-project.properties
# 创建构建物目录
- mkdir -p target/classes
# 此命令是否执行成功依赖mvn配置的合理性,需要实际仓库的研发介入,配合配置的可用性,默认可访问官方mvn库的外网环境问题不大,内网则不太可能编译成功
- mvn compile
# 查看编译产物
- tree target/classes
# 执行sonar扫描
- /scan.sh
# 查看报告生成大小
- ls -l .scannerwork/report.json
image:
name: satomic/sonarscanner:v9-mvn
扫描结果
参考
问题与风险
- 从SonarQube 7.7版本开始不支持在scanner端直接生成json报告。
- 7.7之前的版本的报告中也未包含特别详细的信息。