Skip to content

Commit

Permalink
feat(report): added sonarqube report (#4418)
Browse files Browse the repository at this point in the history
Signed-off-by: João Reigota <joao.reigota@checkmarx.com>
  • Loading branch information
joaoReigota1 committed Nov 19, 2021
1 parent 241c6a0 commit 060b572
Show file tree
Hide file tree
Showing 8 changed files with 448 additions and 7 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ kics.config
# Results
results.*
gl-sast-results.json
sonarqube-results.json

# Sarif files
*.sarif
Expand Down
143 changes: 142 additions & 1 deletion docs/results.md
Original file line number Diff line number Diff line change
Expand Up @@ -313,7 +313,7 @@ SARIF reports are sorted by severity (from high to info), following [SARIF v2.1.
}
```

# Gitlab SAST
## Gitlab SAST
You can export html report by using `--report-formats "glsast"`.
Gitlab SAST reports are sorted by severity (from high to info), following [Gitlab SAST Report scheme](https://docs.gitlab.com/ee/development/integrations/secure.html#report), also, the generated file will have a prefix `gl-sast-` as [recommendend by Gitlab docs](https://docs.gitlab.com/ee/development/integrations/secure.html#output-file) and looks like:

Expand Down Expand Up @@ -430,6 +430,147 @@ Gitlab SAST reports are sorted by severity (from high to info), following [Gitla
]
}

```

## SonarQube
You can export sonarqube report by using `--report-formats "sonarqube"`.
SonarQube reports, follow [SonarQube Import Format](https://docs.sonarqube.org/latest/analysis/generic-issue/), also, the generated file will have a prefix `sonarqube-` and looks like:

```json
{
"issues": [
{
"engineId": "KICS development",
"ruleId": "0afa6ab8-a047-48cf-be07-93a2f8c34cf7",
"severity": "MAJOR",
"type": "VULNERABILITY",
"primaryLocation": {
"message": "All Application Load Balancers (ALB) must be protected with Web Application Firewall (WAF) service",
"filePath": "assets/queries/terraform/aws/alb_deletion_protection_disabled/test/negative2.tf",
"textRange": {
"startLine": 1
}
},
"secondaryLocations": [
{
"message": "All Application Load Balancers (ALB) must be protected with Web Application Firewall (WAF) service",
"filePath": "assets/queries/terraform/aws/alb_deletion_protection_disabled/test/positive4.tf",
"textRange": {
"startLine": 1
}
},
{
"message": "All Application Load Balancers (ALB) must be protected with Web Application Firewall (WAF) service",
"filePath": "assets/queries/terraform/aws/alb_deletion_protection_disabled/test/negative1.tf",
"textRange": {
"startLine": 1
}
},
{
"message": "All Application Load Balancers (ALB) must be protected with Web Application Firewall (WAF) service",
"filePath": "assets/queries/terraform/aws/alb_deletion_protection_disabled/test/positive1.tf",
"textRange": {
"startLine": 1
}
},
{
"message": "All Application Load Balancers (ALB) must be protected with Web Application Firewall (WAF) service",
"filePath": "assets/queries/terraform/aws/alb_deletion_protection_disabled/test/positive2.tf",
"textRange": {
"startLine": 1
}
},
{
"message": "All Application Load Balancers (ALB) must be protected with Web Application Firewall (WAF) service",
"filePath": "assets/queries/terraform/aws/alb_deletion_protection_disabled/test/positive3.tf",
"textRange": {
"startLine": 1
}
}
]
},
{
"engineId": "KICS development",
"ruleId": "6e3fd2ed-5c83-4c68-9679-7700d224d379",
"severity": "MAJOR",
"type": "CODE_SMELL",
"primaryLocation": {
"message": "It's considered a best practice when using Application Load Balancers to drop invalid header fields",
"filePath": "assets/queries/terraform/aws/alb_deletion_protection_disabled/test/positive5.tf",
"textRange": {
"startLine": 1
}
},
"secondaryLocations": [
{
"message": "It's considered a best practice when using Application Load Balancers to drop invalid header fields",
"filePath": "assets/queries/terraform/aws/alb_deletion_protection_disabled/test/positive6.tf",
"textRange": {
"startLine": 1
}
},
{
"message": "It's considered a best practice when using Application Load Balancers to drop invalid header fields",
"filePath": "assets/queries/terraform/aws/alb_deletion_protection_disabled/test/negative3.tf",
"textRange": {
"startLine": 1
}
}
]
},
{
"engineId": "KICS development",
"ruleId": "afecd1f1-6378-4f7e-bb3b-60c35801fdd4",
"severity": "MINOR",
"type": "CODE_SMELL",
"primaryLocation": {
"message": "Application Load Balancer should have deletion protection enabled",
"filePath": "assets/queries/terraform/aws/alb_deletion_protection_disabled/test/positive2.tf",
"textRange": {
"startLine": 1
}
},
"secondaryLocations": [
{
"message": "Application Load Balancer should have deletion protection enabled",
"filePath": "assets/queries/terraform/aws/alb_deletion_protection_disabled/test/positive1.tf",
"textRange": {
"startLine": 7
}
},
{
"message": "Application Load Balancer should have deletion protection enabled",
"filePath": "assets/queries/terraform/aws/alb_deletion_protection_disabled/test/positive6.tf",
"textRange": {
"startLine": 1
}
},
{
"message": "Application Load Balancer should have deletion protection enabled",
"filePath": "assets/queries/terraform/aws/alb_deletion_protection_disabled/test/positive5.tf",
"textRange": {
"startLine": 9
}
},
{
"message": "Application Load Balancer should have deletion protection enabled",
"filePath": "assets/queries/terraform/aws/alb_deletion_protection_disabled/test/positive4.tf",
"textRange": {
"startLine": 1
}
},
{
"message": "Application Load Balancer should have deletion protection enabled",
"filePath": "assets/queries/terraform/aws/alb_deletion_protection_disabled/test/positive3.tf",
"textRange": {
"startLine": 7
}
}
]
}
]
}

```
## HTML
You can export html report by using `--report-formats "html"`.
Expand Down
2 changes: 1 addition & 1 deletion e2e/fixtures/assets/scan_help
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ Flags:
-d, --payload-path string path to store internal representation JSON file
--preview-lines int number of lines to be display in CLI results (min: 1, max: 30) (default 3)
-q, --queries-path string path to directory with queries (default "./assets/queries")
--report-formats strings formats in which the results will be exported (all, glsast, html, json, pdf, sarif) (default [json])
--report-formats strings formats in which the results will be exported (all, glsast, html, json, pdf, sarif, sonarqube) (default [json])
-r, --secrets-regexes-path string path to secrets regex rules configuration file
--timeout int number of seconds the query has to execute before being canceled (default 60)
-t, --type strings case insensitive list of platform types to scan
Expand Down
11 changes: 6 additions & 5 deletions internal/console/helpers/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,12 @@ const (
)

var reportGenerators = map[string]func(path, filename string, body interface{}) error{
"json": report.PrintJSONReport,
"sarif": report.PrintSarifReport,
"html": report.PrintHTMLReport,
"glsast": report.PrintGitlabSASTReport,
"pdf": report.PrintPdfReport,
"json": report.PrintJSONReport,
"sarif": report.PrintSarifReport,
"html": report.PrintHTMLReport,
"glsast": report.PrintGitlabSASTReport,
"pdf": report.PrintPdfReport,
"sonarqube": report.PrintSonarQubeReport,
}

// Printer wil print console output with colors
Expand Down
120 changes: 120 additions & 0 deletions pkg/report/model/sonarqube.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
package model

import (
"github.com/Checkmarx/kics/internal/constants"
"github.com/Checkmarx/kics/pkg/model"
)

// severitySonarQubeEquivalence maps the severity of the KICS to the SonarQube equivalent
var severitySonarQubeEquivalence = map[model.Severity]string{
"INFO": "INFO",
"LOW": "MINOR",
"MEDIUM": "MAJOR",
"HIGH": "CRITICAL",
}

// categorySonarQubeEquivalence maps the category to the SonarQube equivalent
var categorySonarQubeEquivalence = map[string]string{
"Access Control": "VULNERABILITY",
"Availability": "VULNERABILITY",
"Backup": "VULNERABILITY",
"Best Practices": "CODE_SMELL",
"Build Process": "VULNERABILITY",
"Encryption": "VULNERABILITY",
"Insecure Configurations": "CODE_SMELL",
"Insecure Defaults": "CODE_SMELL",
"Networking and Firewall": "VULNERABILITY",
"Observability": "VULNERABILITY",
"Resource Management": "VULNERABILITY",
"Secret Management": "VULNERABILITY",
"Supply-Chain": "VULNERABILITY",
"Structure and Semantics": "CODE_SMELL",
}

// SonarQubeReportBuilder is the builder for the SonarQubeReport struct
type SonarQubeReportBuilder struct {
version string
report *SonarQubeReport
}

// SonarQubeReport is a list of issues for SonarQube Report
type SonarQubeReport struct {
Issues []Issue `json:"issues"`
}

// Issue is a single issue for SonarQube Report
type Issue struct {
EngineID string `json:"engineId"`
RuleID string `json:"ruleId"`
Severity string `json:"severity"`
Type string `json:"type"`
PrimaryLocation *Location `json:"primaryLocation"`
SecondaryLocations []*Location `json:"secondaryLocations,omitempty"`
}

// Location is the location for the vulnerability in the SonarQube Report
type Location struct {
Message string `json:"message"`
FilePath string `json:"filePath"`
TextRange *Range `json:"textRange"`
}

// Range is the range for the vulnerability in the SonarQube Report
type Range struct {
StartLine int `json:"startLine"`
}

// NewSonarQubeRepory creates a new SonarQubeReportBuilder instance
func NewSonarQubeRepory() *SonarQubeReportBuilder {
return &SonarQubeReportBuilder{
version: "KICS " + constants.Version,
report: &SonarQubeReport{
Issues: make([]Issue, 0),
},
}
}

// BuildReport builds the SonarQubeReport from the given QueryResults
func (s *SonarQubeReportBuilder) BuildReport(summary *model.Summary) *SonarQubeReport {
for i := range summary.Queries {
s.buildIssue(&summary.Queries[i])
}
return s.report
}

// buildIssue builds the issue from the given QueryResult and adds it to the SonarQubeReport
func (s *SonarQubeReportBuilder) buildIssue(query *model.QueryResult) {
issue := Issue{
EngineID: s.version,
RuleID: query.QueryID,
Severity: severitySonarQubeEquivalence[query.Severity],
Type: categorySonarQubeEquivalence[query.Category],
PrimaryLocation: buildLocation(0, query),
SecondaryLocations: buildSecondaryLocation(query),
}
s.report.Issues = append(s.report.Issues, issue)
}

// buildSecondaryLocation builds the secondary location for the SonarQube Report
func buildSecondaryLocation(query *model.QueryResult) []*Location {
locations := make([]*Location, 0)
for i := range query.Files[1:] {
locations = append(locations, buildLocation(i+1, query))
}
return locations
}

// buildLocation builds the location for the SonarQube Report
func buildLocation(index int, query *model.QueryResult) *Location {
message := query.Description
if query.CISDescriptionID != "" {
message = query.CISDescriptionID
}
return &Location{
Message: message,
FilePath: query.Files[index].FileName,
TextRange: &Range{
StartLine: query.Files[index].Line,
},
}
}
Loading

0 comments on commit 060b572

Please sign in to comment.