代码分析

先安装好SonarQube服务器, 然后安装Sonar-scanner进行扫描。
scanner下载链接:https://docs.sonarqube.org/7.9/analysis/scan/sonarscanner/

  1. sonar-scanner \
  2. -Dsonar.projectKey=microservicecicd-demo-service \
  3. -Dsonar.projectName=microservicecicd-demo-service \
  4. -Dsonar.projectVersion=1.1.1 \
  5. -Dsonar.ws.timeout=30 \
  6. -Dsonar.projectDescription="xxxxxxx" \
  7. -Dsonar.links.homepage=http://www.baidu.com \
  8. -Dsonar.sources=src \
  9. -Dsonar.sourceEncoding=UTF-8 \
  10. -Dsonar.java.binaries=target/classes \
  11. -Dsonar.java.test.binaries=target/test-classes \
  12. -Dsonar.java.surefire.report=target/surefire-reports \
  13. -Dsonar.host.url="http://sonar.idevops.site" \
  14. -Dsonar.login=7c7b5f890dcb3c0ddf6d187fe47a8482f3430c74 \

扫描结果关联Git Commit

提前装好插件
下载:https://github.com/gabrie-allaigre/sonar-gitlab-plugin/tree/4.1.0-SNAPSHOT .
然后将下载后的jar包放到SonarQube插件目录中, 赋予可执行权限。然后重启SonarQube。

插件的说明文档查看该插件的Readme文档。-Dsonar.gitlab.failure_notification_mode值为commit-status表示更改提交状态, 值为nothing不做任何动作。

在上面扫描参数的基础上添加以下参数:

  1. -Dsonar.gitlab.commit_sha=d0f7c74a058df8e935f1e247a68ac23d7d864295 \
  2. -Dsonar.gitlab.ref_name=master \
  3. -Dsonar.gitlab.project_id=39 \
  4. -Dsonar.dynamicAnalysis=reuseReports \
  5. -Dsonar.gitlab.failure_notification_mode=commit-status \
  6. -Dsonar.gitlab.url=http://gitlab.idevops.site \
  7. -Dsonar.gitlab.user_token=ABtkz-f_zkyRXAMeBZSc \
  8. -Dsonar.gitlab.api_version=v4
  • commit_sha : gitlab项目提交ID
  • ref_name:gitlab项目分支
  • project_id:gitlab项目的ID
  • dynamicAnalysis:固定值reuseReports

image.png

修改gitcommit的状态有什么作用?其实这个插件是直接修改COMMITID对应的pipeline状态。更新为失败或者成功。这样就可以基于流水线的状态来控制MR请求的合并操作。gitlab MR具有一个选项控制,即当流水线成功后才可以合并代码。此就是更改提交ID状态的最大作用。
image.png

扫描模式

SonarQube具有三种分析默认,可以通过分析参数”sonar.analysis.mode”来设置

  1. publish - 默认值,分析所有代码并发送结果到服务器,保存结果在数据库中
  2. preview - 预览模式,执行代码分析,但是发送结果到服务器,这种模式可用于在提交到版本库之前的代码检查
  3. issue - 在工具中所使用的“preview”模式,一般不使用

简单的来说只需要添加sonarqube参数, 指定模式/git项目/commitid/分支。
-Dsonar.analysis.mode=preview
-Dsonar.gitlab.project_id=${projectid}
-Dsonar.gitlab.commit_sha=${commit_sha}
-Dsonar.gitlab.ref_name=${branchName}

秘钥信息检查

作为管道的一部分,我们开始将SonarQube用于代码质量,因为SonarQube已集成到开发人员的IDE中,所以此验证发生在开发人员提交其代码之前。我们决定利用SonarQube来进一步检查易受攻击的编码模式。
在此过程中,我们使用了现有的出色插件,例如Java的Findsecbugs,我们从Sonar Secrets开始向开发人员提供早期反馈,提醒他们使用硬编码凭据所带来的安全风险。尽早为开发人员提供反馈,使我们可以将安全控制权向左移动,从而使开发人员可以在生产代码投入使用之前达到内部定义的安全标准。
为了保护我们的用户,合作伙伴和员工,我们的服务旨在使用加密的密钥库来保护所有相关的敏感数据。然后,开发人员可以使用变量在代码中引用此数据,而不必对值进行硬编码。

构建打包
SonarQube™的Sonar Secrets插件https://github.com/Skyscanner/sonar-secrets—由Skyscanner产品安全小组创建,旨在识别硬编码的机密,例如密码,API令牌,AWS凭证等。
image.png

cd sonar-secrets/java && mvn clean packagecd sonar-secrets/javascript && mvn clean package
build成功会提示以下信息:
…[INFO] BUILD SUCCESS
[INFO] ————————————————————————————————————
[INFO] Total time:7.065s
[INFO] Finished at:2017-10-26T05:00:33-04:00
[INFO] Final Memory:23M/252M
[INFO] ————————————————————————————————————
sonar-secrets-java-x.x.jar在sonar-secrets/java/target目录。
sonar-secrets-javascript-x.x.jar在sonar-secrets/javascript/target目录。

安装配置

  • 复制Jar包文件到sonarqube的插件目录/opt/sonarqube/extensions/plugins
  • 重启sonarqube服务器

In startup logs you should see:

INFOweb[][o.s.s.p.ServerPluginRepository] Deploy plugin Sonar Secrets Java / x.x
INFOweb[][o.s.s.p.ServerPluginRepository] Deploy plugin Sonar Secrets JavaScript / x.x

启用sonar-secrets-javaandsonar-secrets-javascript在 Quality Profiles。



IDE效果图
image.png
Sonar Secrets帮助我们在检测和防止代码中的敏感数据泄漏方面保持主动。我们已决定将该项目开源,以便社区可以从这项技术中受益并帮助改进它。该插件是完全可定制的,并且可以使用新规则进行扩展。
该第一个发行版目前仅支持Java和Javascript项目。

使用问题

在大规模使用SonarQube平台对代码进行扫描时可能会遇到以下几个问题

  1. SonarQube平台数据问题(开源版本不支持不同分支)
  2. SonarQube扫描规则问题(当配置了默认规则后新建项目如何指定新规则呢?)
  3. SonarQube项目授权问题(新生产的项目如何配置权限?)

    1. 解决SonarQube平台数据问题

    当我们在大规模使用SonarQube进行代码质量检查的时候,我们需要让开发人员每次都能看到当前特性分支的扫描分析数据,以尽快解决有问题的代码,提高代码的质量。开源版本会带来一些问题,因为不支持一个项目多分支的形式,所以我们按照特性分支的名称来生成相对应的扫描项目。(会产生很多Sonarqube项目)

例如: 服务名称是 demo-abcd-service
之前我们的做法是不区分分支,在扫描所有分支的时候都会指定同一个sonar项目。这就是导致SonarQube平台此项目数据不稳定的根本原因。

现在的做法是: 假如这个项目有F1,F2等特性分支,在每次对其中特性分支构建扫描时会配置sonar扫描参数(projectName)为 “当前的服务名称_特性分支名称”,这样相当于每个特性分支都对应一个扫描项目,数据就不会出现问题了。

虽然解决了数据不稳定的问题,但又间接的带来了一些问题。
每个特性分支生成一个项目,假如特性分支被删除呢?或者分支很多呢?对于SonarQube管理员来说很难管理,增加了任务负担。

总结一下如何解决问题呢?

  • 从长远角度来说最直接的方式当然是购买开发版本 。
  • 变更代码扫描的模式,比如将每次特性分支扫描的数据关联到提交的commit信息中。

总之,最简单的方式就是付费购买开发版、企业版。小型团队也可以使用生成多项目的方式管理。

2. 解决SonarQube扫描规则问题

在搭建好SonarQube平台后,已经配置好了针对每种语言的”Sonar Way”质量配置。我们在大规模使用中,对扫描Java项目的规则做了一些定制,有一些新增的规则还有一部分弃用的规则,总之大部分还都是默认自带的java规则,配置好规则后并设置为默认的规则。其中有几十个团队在用默认的规则,后来个别团队因需求要使用新的JAVA项目质量。如何为新建的项目自动配置好对应的质量呢?

分析

  • SonarQube平台中的项目不需要单独的新建,而是通过Jenkins构建过程中生成。
  • 当我们需要为项目指定新的质量配置的时候,通常在Sonar WebUi中进行配置。
  • 无法通过”-Dsonar.xxxx”方式指定每次分支时使用的质量名称。

应对
由于SonarQube项目都是通过流水线扫描后生成的,于是在流水线中增加步骤。
每次扫描之前先判断项目是否存在,然后指定新的质量,再执行代码扫描。

实施主要通过RESTAPI完成

  • 创建项目:api/projects/create
  • 更新质量:api/qualityprofiles/add_project
  • 查找项目:api/projects/search ```groovy package com.devops
    //Http req 使用Jenkins插件封装的方法 哈哈哈 def HttpReq(reqType,reqUrl,reqBody){ result = httpRequest authentication: ‘我的凭据的ID’,

    1. httpMode: reqType,
    2. contentType: "APPLICATION_JSON",
    3. consoleLogResponseBody: true,
    4. ignoreSslErrors: true,
    5. requestBody: reqBody,
    6. //responseHandle: 'NONE',
    7. url: reqUrl

    //quiet: true

    return result }

//查找项目 def SearchProject(projectName){ apiUrl = “http://我的sonar服务器地址/api/projects/search?projects=${projectName}“ resultInfo = HttpReq(“GET”,apiUrl,’’)

  1. def result = readJSON text: """${resultInfo.content}"""
  2. if (result["paging"]["total"] == 0 ){
  3. return "false"
  4. } else {
  5. return result
  6. }

}

//创建项目 def CreateProject(projectName){ apiUrl = “http://我的sonar服务器地址/api/projects/create?name=${projectName}&project=${projectName}“ resultInfo = HttpReq(“POST”,apiUrl,’’) }

//更新语言规则集 def UpdateQuality(language,qualityProfile,projectName){ apiUrl = “http://我的sonar服务器地址/api/qualityprofiles/add_project?language=${language}&qualityProfile=${qualityProfile}&project=${projectName}“ resultInfo = HttpReq(“POST”,apiUrl,’’) }

//项目授权 def ApplyTemplate(projectKey,templateName){ apiUrl = “http://我的sonar服务器地址/api/permissions/apply_template?projectKey=${projectName}&templateName=${templateName}“ resultInfo = HttpReq(“POST”,apiUrl,’’) }

  1. <a name="WIGZW"></a>
  2. ### 3. SonarQube项目授权问题
  3. 我们在前面解决了SonarQube扫描前的一些问题,现在开始解决授权问题。
  4. **Sonarqube的授权配置**
  5. - 用户首先登陆SonarQube平台(我们做了GitlabSSO/LDAP集成)
  6. - 根据不同的业务组对应创建一个group
  7. - 然后将用户加入到对应的group中
  8. - 根据业务的简称创建对应的权限模板
  9. - 将组和管理员加入权限模板中
  10. - 然后将该业务的项目批量应用权限模板
  11. 亲测: 新生成的项目还需要再应用权限模板后才能使对应的项目组成员访问。<br />解决:在项目扫描后,调用接口对当前项目应用对应的权限模板。<br />项目授权(应用权限模板):api/permissions/apply_template
  12. <a name="R7IW7"></a>
  13. ### 4. 支持多语言扫描
  14. Just run the analysis with the list of reports. Like sonar-scanner -Dsonar.jacoco.reportPaths=report1.exec,report2.exec and they will be "merged".<br />来自 [https://stackoverflow.com/questions/52419132/is-it-possible-to-merge-test-coverage-on-sonarqube-level](https://stackoverflow.com/questions/52419132/is-it-possible-to-merge-test-coverage-on-sonarqube-level)
  15. You can combine the result of multiple jobs. You can create two coverage folders, e.g.
  16. - coverage-unit
  17. - coverage-integration<br />and use the resulting lcov files, e.g.<br />sonar.javascript.lcov.reportPaths=coverage-unit/lcov.info,coverage-integration/lcov.info
  18. 来自 [https://stackoverflow.com/questions/52419132/is-it-possible-to-merge-test-coverage-on-sonarqube-level](https://stackoverflow.com/questions/52419132/is-it-possible-to-merge-test-coverage-on-sonarqube-level)
  19. Starting with SonarQube 4.2, multi-language projects are supported.<br />This automatically happens when sonar.language is not set.<br />来自 [https://stackoverflow.com/questions/13625022/does-sonar-support-multiple-language-in-same-project](https://stackoverflow.com/questions/13625022/does-sonar-support-multiple-language-in-same-project)
  20. <a name="jHh1v"></a>
  21. ## 代码分析
  22. <a name="TuufX"></a>
  23. ### Sonar实操之C++项目
  24. Cppcheck工具:Cppcheck是一种C/C++代码缺陷静态检查工具,不同于C/C++编译器及其它分析工具,Cppcheck只检查编译器检查不出来的bug,不检查语法错误。
  25. **step1:SonarQube安装C++扫描插件**<br />调研时遇到的第一个问题就是关于C++插件的,SonarQube的默认C/C++插件CFamily是收费的
  26. 下载地址 [https://github.com/SonarOpenCommunity/sonar-cxx/releases,把jar文件下载下来,然后放到你的sonarqube目录/extensions/plugins目录下](https://github.com/SonarOpenCommunity/sonar-cxx/releases,把jar文件下载下来,然后放到你的sonarqube目录/extensions/plugins目录下)
  27. 重启加载正确后,即可在页面看到“C++ (Community)”
  28. <a name="dZXcL"></a>
  29. ### Sonar实操之Maven项目
  30. SonarQube将所有测试报告合并为一份涵盖整体的测试报告。因此,如果您在Maven项目中将单元测试(由Maven Surefire Plugin运行)和集成测试(由Maven Failsafe Plugin运行)分开进行测试,那么如何配置 JaCoCo Maven Plugin。
  31. 在以下各节中,提出了满足以下条件的解决方案:
  32. - 使用Maven作为构建工具。
  33. - 该项目可以是多模块项目(微服务)。
  34. - 单元测试和集成测试是每个模块的一部分。
  35. - 测试覆盖率是通过 JaCoCo Maven Plugin来衡量的。
  36. 下面显示了Maven项目结构,用于单元测试和集成测试的分离。然后显示了Maven项目配置,其中包含单独的单元测试运行和集成测试运行。之后,我们来看看Maven项目配置以生成涵盖单元测试和集成测试的测试报告。最后,SonarQube的仪表板中显示了SonarQube的配置,用于测试报告的可视化。
  37. <a name="iydPb"></a>
  38. #### **Maven项目结构**
  39. 首先,我们看一下单个模块项目的默认Maven项目结构。
  40. ```xml
  41. my-app
  42. ├── pom.xml
  43. ├── src
  44. │ ├── main
  45. │ │ └── java
  46. │ └── test
  47. │ └── java

目录src/main/java包含生产项目的源代码,目录src/test/java包含测试源代码。我们可以将单元测试和集成测试放到这个目录中。但是我们需要将这两种类型的测试放在单独的目录中。因此,我们添加了一个名为src/it/java的新目录。然后将单元测试放在src/test java目录中,并将集成测试放在src/it/java目录中,因此新的项目结构如下图所示。1

  1. my-app
  2. ├── pom.xml
  3. ├── src
  4. │ ├── it
  5. │ │ └── java
  6. │ ├── main
  7. │ │ └── java
  8. │ └── test
  9. │ └── java

单元和集成测试运行

幸运的是,单元测试运行配置是Maven默认项目配置的一部分。如果满足以下条件,Maven将自动运行这些测试:

  • 目录src/test/java存在测试用例
  • 测试类名称以Test开头或以Test或TestCase结尾。

Maven在Maven的构建生命周期阶段中的测试期间来运行这些测试。
集成测试运行配置必须手动完成。它存在可以提供帮助的Maven插件。我们希望满足以下条件:

  • 集成测试存储在目录src/it/java
  • 集成测试类名称要么以IT开头,要么以IT或ITCase结尾,
  • 集成测试在Maven的构建生命周期阶段进行 集成测试。

首先,Maven必须知道它必须在其测试类路径中包含目录src/it/java。在这里,Build Helper Maven插件可以提供帮助。它将目录src/it/java添加到测试类路径。

  1. <plugin>
  2. <groupId>org.codehaus.mojo</groupId>
  3. <artifactId>build-helper-maven-plugin</artifactId>
  4. <version>3.1.0</version>
  5. <executions>
  6. <execution>
  7. <goals>
  8. <goal>add-test-source</goal>
  9. <goal>add-test-resource</goal>
  10. </goals>
  11. <configuration>
  12. <sources>
  13. <source>src/it/java</source>
  14. </sources>
  15. <resources>
  16. <resource>
  17. <directory>src/it/resources</directory>
  18. </resource>
  19. </resources>
  20. </configuration>
  21. </execution>
  22. </executions>
  23. </plugin>

上面的代码段必须插入到 项目根pom中的 部分。
Maven的构建生命周期包含一个称为集成测试的阶段。在此阶段,我们要运行集成测试。幸运的是,当在POM中设置Maven故障安全插件的目标集成测试时,它会自动绑定到此阶段。如果您希望在集成测试失败时构建失败,那么还必须将目标验证添加到POM中:

  1. <plugin>
  2. <groupId>org.apache.maven.plugins</groupId>
  3. <artifactId>maven-failsafe-plugin</artifactId>
  4. <version>3.0.0-M4</version>
  5. <configuration>
  6. <encoding>${project.build.sourceEncoding}</encoding>
  7. </configuration>
  8. <executions>
  9. <execution>
  10. <goals>
  11. <goal>integration-test</goal>
  12. <goal>verify</goal>
  13. </goals>
  14. </execution>
  15. </executions>
  16. </plugin>

同样,上述代码段也必须插入到 项目根pom中的 部分。然后,当Maven故障保护插件的类名称以IT开头或以IT 或ITCase结尾时,它们会自动运行集成测试。

测试报告生成

我们想使用JaCoCo Maven插件生成测试报告。它应该为单元测试和集成测试生成测试报告。因此,该插件必须要准备两个单独的代理。然后他们在测试运行期间生成报告。Maven的构建生命周期包含自己的阶段,可以在测试阶段之前进行准备(测试和集成测试)。测试阶段的准备阶段称为过程测试类,集成测试阶段的准备阶段称为pre-integration-test。当JaCoCo的目标prepare-agent和在POM中设置了prepare-agent-integration。但这还不够。JaCoCo还必须创建一个报告,以便SonarQube可以读取报告以进行可视化。因此,我们必须在POM中添加目标报告和报告集成:

  1. <plugin>
  2. <groupId>org.jacoco</groupId>
  3. <artifactId>jacoco-maven-plugin</artifactId>
  4. <version>0.8.5</version>
  5. <executions>
  6. <execution>
  7. <goals>
  8. <goal>prepare-agent</goal>
  9. <goal>prepare-agent-integration</goal>
  10. <goal>report</goal>
  11. <goal>report-integration</goal>
  12. </goals>
  13. </execution>
  14. </executions>
  15. </plugin>

同样,它是部分的一部分。
现在,我们可以运行目标mvn验证,并且我们的项目已构建为包含单元和集成测试,并生成两个测试报告。

SonarQube测试报告可视化

现在,我们想在SonarQube中可视化我们的测试报告。因此,在成功构建之后,我们必须在我们的项目中运行Sonar Maven 3插件(命令mvn sonar:sonar)。因此,Sonar Maven插件知道将报告上传到哪里,我们必须在〜/ .m2 / setting.xml中配置SonarQube实例

  1. <profile>
  2. <id>sonar</id>
  3. <activation>
  4. <activeByDefault>true</activeByDefault>
  5. </activation>
  6. <properties>
  7. <!-- Optional URL to server. Default value is http://localhost:9000 -->
  8. <sonar.host.url>http://localhost:9000</sonar.host.url>
  9. </properties>
  10. </profile>

SonarQube仪表板中打开项目时,我们会看到总体测试覆盖率报告。

附:参考pom.xml

  1. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  2. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  3. <modelVersion>4.0.0</modelVersion>
  4. <groupId>com.github.sparsick.sonarqube</groupId>
  5. <artifactId>sonarqube-test-report-with-maven</artifactId>
  6. <version>1.0.0-SNAPSHOT</version>
  7. <name>sonarqube-test-report-with-maven</name>
  8. <properties>
  9. <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  10. <java.version>11</java.version>
  11. </properties>
  12. <build>
  13. <pluginManagement>
  14. <plugins>
  15. <plugin>
  16. <groupId>org.sonarsource.scanner.maven</groupId>
  17. <artifactId>sonar-maven-plugin</artifactId>
  18. <version>3.7.0.1746</version>
  19. </plugin>
  20. </plugins>
  21. </pluginManagement>
  22. <plugins>
  23. <plugin>
  24. <artifactId>maven-compiler-plugin</artifactId>
  25. <version>3.8.1</version>
  26. <configuration>
  27. <source>${java.version}</source>
  28. <target>${java.version}</target>
  29. </configuration>
  30. </plugin>
  31. <plugin>
  32. <groupId>org.codehaus.mojo</groupId>
  33. <artifactId>build-helper-maven-plugin</artifactId>
  34. <version>3.1.0</version>
  35. <executions>
  36. <execution>
  37. <goals>
  38. <goal>add-test-source</goal>
  39. <goal>add-test-resource</goal>
  40. </goals>
  41. <configuration>
  42. <sources>
  43. <source>src/it/java</source>
  44. </sources>
  45. <resources>
  46. <resource>
  47. <directory>src/it/resources</directory>
  48. </resource>
  49. </resources>
  50. </configuration>
  51. </execution>
  52. </executions>
  53. </plugin>
  54. <plugin>
  55. <groupId>org.apache.maven.plugins</groupId>
  56. <artifactId>maven-surefire-plugin</artifactId>
  57. <version>3.0.0-M4</version>
  58. </plugin>
  59. <plugin>
  60. <groupId>org.apache.maven.plugins</groupId>
  61. <artifactId>maven-failsafe-plugin</artifactId>
  62. <version>3.0.0-M4</version>
  63. <configuration>
  64. <encoding>${project.build.sourceEncoding}</encoding>
  65. </configuration>
  66. <executions>
  67. <execution>
  68. <goals>
  69. <goal>integration-test</goal>
  70. <goal>verify</goal>
  71. </goals>
  72. </execution>
  73. </executions>
  74. </plugin>
  75. <plugin>
  76. <groupId>org.jacoco</groupId>
  77. <artifactId>jacoco-maven-plugin</artifactId>
  78. <version>0.8.5</version>
  79. <executions>
  80. <execution>
  81. <goals>
  82. <goal>prepare-agent</goal>
  83. <goal>prepare-agent-integration</goal>
  84. <goal>report</goal>
  85. <goal>report-integration</goal>
  86. </goals>
  87. </execution>
  88. </executions>
  89. </plugin>
  90. </plugins>
  91. </build>
  92. <dependencies>
  93. <dependency>
  94. <groupId>org.junit.jupiter</groupId>
  95. <artifactId>junit-jupiter</artifactId>
  96. <version>5.6.2</version>
  97. <scope>test</scope>
  98. </dependency>
  99. </dependencies>
  100. </project>