765DevOps

Thinking is the problem, Doing is the answer !

0%

代码扫描(四)-DevOps平台集成Sonar方案

SonarQube需要融合到DevOps平台,使其成为CICD的一个环节中来,发挥其更大的价值。我们需要考虑到如何与DevOps平台的集成,并且可以最小限度的对于业务代码的侵入性,这边文档将我的方案做个各详细阐述和大家一起分享。

1、扫描流程

整个代码扫描涉及工具、系统如下:

  • Gitlab
  • Jenkins
  • SonarQube
  • DevOps平台(代码扫描开发应用)

执行流程如下:

Jenkins配置

2、详细说明

2.1、DevOps平台(代码)

代码扫描以一个开放应用的形式接入研发中台,有一个基本Widget页面,展示代码扫描结果概览

所有的检测结果可以基于研发中台进行处理

  • 流程中断
  • 消息告警

SonarQube开放应用界面

Jenkins配置

2.2、Jenkins扫描Job

所有触发通过研发中台触发,所以Jenkins只配置一个通用扫描Job即可,使用Pipeline Job编写。

说明:所以参数拼接可以研发中台传递给 Jenkins,所以业务代码可以不用 sonar-project.properties文件,减少业务代码的侵入性

Jenkins Job入参

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
{
"commonParams": {
"appCode": "php-demo", // 应用编号唯一,对应:sonar.projectkey
"appLanguage": "php", // 应用语言,针对不同语言设置默认的扫描命令及参数,如:php、go、java、gradle
"gitRepository": "git@gitlab.demo.com:demo/php-demo.git", // 代码仓库地址
"gitProjectId": "", // GitLab仓库PojectId,对应:sonar.gitlab.project_id,Comment on Commit需要
"gitBranch": "test", // 扫描代码分支,对应:sonar.branch.name
"gitCommit": "", // 代码提交Commit Hash值,对应:sonar.gitlab.commit_sha
"gitRefName": "master" // 扫描参考基准分支或Commit,对应:sonar.gitlab.ref_name
"gitEvent": "Merge Request Hook", // git触发事件类型,Push Hook、Merge Request Hook、Flow Build、Fast Build等
"mrKey": "1", // MR Key,对应:sonar.pullrequest.key
"mrSourceBranch": "test-mr", // MR来源分支,对应:sonar.pullrequest.branch
"mrTargetBranch": "master" // MR目标分支,对应:sonar.pullrequest.base
},
"extendParams": { // 预留扩展参数,如自定义扫描参数等
"customAnalysisParams": { // 以下会默认覆盖原有既定扫描参数
"sonar.sourceEncoding": "GBK",
"sonar.projectBaseDir": "/src/code"
}
}
}

Jenkins pipeline示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
pipeline {

stages {
stage ("参数解析") {
steps {
script {
echo params.BUILD_PARAMS_JSON
if (params.BUILD_PARAMS_JSON != null && params.BUILD_PARAMS_JSON != '') {
buildParams = readJSON text: params.BUILD_PARAMS_JSON
} else {
error "BUILD_PARAMS_JSON is required params"
}

// 通用参数与定制参数赋值
COMMON_PARAMS = buildParams['commonParams']
EXTEND_PARAMS = buildParams['extendParams']

// 入参json数据校验,通用参数必须包含 appCode + gitRepository
if (COMMON_PARAMS['appCode'].isEmpty() || COMMON_PARAMS['gitRepository'].isEmpty() ) {
error "buildParams['commonParams'] is error, must have appCode and gitRepository"
}
// gitBranch不存在则默认为master
COMMON_PARAMS['gitBranch'].isEmpty() && (COMMON_PARAMS['gitBranch'] = "master")
}
}
}

stage ("代码拉取") {
steps {
// 不同的应用对应不同的工作空间,避免工作空间相互影响覆盖
dir ("${COMMON_PARAMS['appCode']}") {
// 拉取git仓库代码, 默认基于分支拉取
script {
if (COMMON_PARAMS['gitEvent'] == "Merge Request Hook" || !COMMON_PARAMS['mrKey'].isEmpty()) {
// SCM: MR必须使用此进行拉取代码,否则Sonar Scan无法识别此次SCM是基于MR
gitCodeFetch("branch", COMMON_PARAMS['mrSourceBranch'], COMMON_PARAMS['gitRepository'], params.GITLAB_DEPLOY_CRED, "MR", COMMON_PARAMS['mrTargetBranch'])
} else {
gitCodeFetch("branch", COMMON_PARAMS['gitBranch'], COMMON_PARAMS['gitRepository'], params.GITLAB_DEPLOY_CRED)
}
}
}
}
}

// 扫描参数此处组织,则业务代码无需sonar-project.properties文件,不侵入业务代码
stage ("扫描参数组织") {
steps {
script {
ANALYSIS_PARAMS = [:]
// ================= 通用扫描参数 ==========================
// 通用扫描参数,其中个别参数存在值才进行赋值,否则空值可能会出现扫描异常情况
ANALYSIS_PARAMS['sonar.projectKey'] = COMMON_PARAMS['appCode']
ANALYSIS_PARAMS['sonar.projectName'] = COMMON_PARAMS['appCode']
...

// ================= 各语言的约定扫描参数 ======================
// 各语言的约定扫描参数,如:sonar.exclusions,sonar.sourceEncoding,默认扫描报告文件位置: .scannerwork/report-task.txt
REPORT_FILE_PATH = ".scannerwork/report-task.txt"
if (COMMON_PARAMS['appLanguage'] == "php") {
ANALYSIS_PARAMS['sonar.exclusions'] = "vendor/**"
} else if (COMMON_PARAMS['appLanguage'] == "go") {
ANALYSIS_PARAMS['sonar.exclusions'] = "vendor/**"
} else if (COMMON_PARAMS['appLanguage'] == "java") {
// 以下mvn sonar:sonar 会默认设置,对于multi-module的仓库同样适用
//ANALYSIS_PARAMS['sonar.sources'] = "src"
//ANALYSIS_PARAMS['sonar.java.binaries'] = "target/classes"
} else {
// TODO
echo "TODO"
}

// ================= 定制化扫描参数 ======================
// 定制化扫描参数(研发中台页面配置), 放置最后,可覆盖之前约定扫描参数
if (!EXTEND_PARAMS['customAnalysisParams'].isEmpty()) {
EXTEND_PARAMS['customAnalysisParams'].each { key,value ->
ANALYSIS_PARAMS["${key}"] = value
}
}
}
}
}

stage ("前置扫描操作") {
steps {
dir("${COMMON_PARAMS['appCode']}") {
script {
if (COMMON_PARAMS['appLanguage'] == "java") {
// java 语言需前置编译, 下载统一的setting.xml文件
sh "wget -O settings.xml http://.../settings-sonar.xml"
} else {
echo "Do Nothing"
}
}
}
}
}

stage ("代码扫描") {
environment {
GIT_COMMIT = "" // 此环境变量置空, 否则影响MR检测时的Sonar回写Gitlab,注:取值为jenkins pipeline 仓库的Hash值
}
steps {
dir("${COMMON_PARAMS['appCode']}") {
script {
// 扫描参数拼接
println(JsonOutput.toJson(ANALYSIS_PARAMS))
def analysisParams = ''
ANALYSIS_PARAMS.each { key,value ->
analysisParams += "-D" + key + "=" + value + " "
}

def scannerHome = tool 'SonarScanner'
withSonarQubeEnv("SonarServer") {
if (COMMON_PARAMS['appLanguage'] == "java") {
// 适应multi-module的仓库, 将 sonar:sonar 作为独立的一步执行
//sh "mvn -s settings.xml clean install -Dmaven.test.skip=true"
sh "mvn -s settings.xml clean install"
sh "mvn -s settings.xml sonar:sonar ${analysisParams}"
} else {
sh "${scannerHome}/bin/sonar-scanner ${analysisParams}"
}
}

// 扫描质量门结果
timeout(time: 1, unit: 'HOURS') {
// 返回格式Map,示例: {"status":"OK / ERROR"}, 返回放置在 ARTIFACTS['qualityGate']
ARTIFACTS['qualityGate'] = waitForQualityGate()
}
}
}
}
}
}
}

2.3、SonarQube

登陆和Gitlab进行打通,实现联动登陆(Gitlab完成登陆了,则SonarQube即自动登陆)

Jenkins配置

2.4、Gitlab

实现扫描结果 Comment on Commit ,示例查看(注:会同时给对应的代码提交人发送一封邮件通知 [Gitlab的Commit自带的配置])

注:根据实际需求进行,需要传入 sonar.gitlab.project_id 参数

基于MR的Comment

Jenkins配置

问题记录

1、SonarQube 扫描项目的权限问题

默认设置,登陆用户均可以查看,这个会造成源代码泄露风险

处理方式:

  • Default visibility of new projects: Private 位置:Administration > Projects > Management 右上角
  • 修改全局的权限设置模板,位置:Administration > Security > Default template > Edit,设置如下:
  • 新项目,通过Api接口设置对应的用户,有对应的权限:

全局权限+新用户权限

Jenkins配置

Jenkins配置

2、基于MR扫描状态结果回写Gitlab

  • **坑1:目前使用7.9.x 对应的 branch plugin-1.3.2需要加入此参数(否则报错),后续针对8.x版本则不再需要
    **参数:sonar.pullrequest.gitlab.repositorySlug的值对应MR的iid值

  • **坑2:使用Jenkins Pipeline SCM模式,默认的变量GIT_COMMIT变量会影响到sonar扫描结果回写
    **此环境变量置空, 否则影响MR检测时的Sonar回写Gitlab,注:取值为jenkins pipeline 仓库的Hash值

1
2
3
4
5
6
7
stage ("代码扫描") {
environment {
GIT_COMMIT = "" // 此环境变量置空, 否则影响MR检测时的Sonar回写Gitlab,注:取值为jenkins pipeline 仓库的Hash值
}
steps {
}
}