diff --git a/output/formatter.go b/output/formatter.go index 8aa55e8..bdc9eac 100644 --- a/output/formatter.go +++ b/output/formatter.go @@ -50,9 +50,6 @@ const ( // ReportSARIF set the output format to SARIF ReportSARIF // SARIF format - - //SonarqubeEffortMinutes effort to fix in minutes - SonarqubeEffortMinutes = 5 ) var text = `Results: @@ -132,51 +129,6 @@ func reportSonarqube(rootPaths []string, w io.Writer, data *reportInfo) error { return err } -func convertToSonarIssues(rootPaths []string, data *reportInfo) (*sonarIssues, error) { - si := &sonarIssues{[]sonarIssue{}} - for _, issue := range data.Issues { - var sonarFilePath string - for _, rootPath := range rootPaths { - if strings.HasPrefix(issue.File, rootPath) { - sonarFilePath = strings.Replace(issue.File, rootPath+"/", "", 1) - } - } - - if sonarFilePath == "" { - continue - } - - lines := strings.Split(issue.Line, "-") - startLine, err := strconv.Atoi(lines[0]) - if err != nil { - return si, err - } - endLine := startLine - if len(lines) > 1 { - endLine, err = strconv.Atoi(lines[1]) - if err != nil { - return si, err - } - } - - s := sonarIssue{ - EngineID: "gosec", - RuleID: issue.RuleID, - PrimaryLocation: location{ - Message: issue.What, - FilePath: sonarFilePath, - TextRange: textRange{StartLine: startLine, EndLine: endLine}, - }, - Type: "VULNERABILITY", - Severity: getSonarSeverity(issue.Severity.String()), - EffortMinutes: SonarqubeEffortMinutes, - Cwe: issue.Cwe, - } - si.SonarIssues = append(si.SonarIssues, s) - } - return si, nil -} - func convertToSarifReport(rootPaths []string, data *reportInfo) (*sarifReport, error) { sr := buildSarifReport() diff --git a/output/formatter_test.go b/output/formatter_test.go index 9ac12eb..3aa22d6 100644 --- a/output/formatter_test.go +++ b/output/formatter_test.go @@ -9,6 +9,7 @@ import ( . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" "github.com/securego/gosec/v2" + "github.com/securego/gosec/v2/sonar" "gopkg.in/yaml.v2" ) @@ -60,7 +61,7 @@ var _ = Describe("Formatter", func() { data := &reportInfo{ Errors: map[string][]gosec.Error{}, Issues: []*gosec.Issue{ - &gosec.Issue{ + { Severity: 2, Confidence: 0, RuleID: "test", @@ -77,15 +78,15 @@ var _ = Describe("Formatter", func() { NumFound: 0, }, } - want := &sonarIssues{ - SonarIssues: []sonarIssue{ + want := &sonar.Report{ + Issues: []*sonar.Issue{ { EngineID: "gosec", RuleID: "test", - PrimaryLocation: location{ + PrimaryLocation: &sonar.Location{ Message: "test", FilePath: "test.go", - TextRange: textRange{ + TextRange: &sonar.TextRange{ StartLine: 1, EndLine: 2, }, @@ -108,7 +109,7 @@ var _ = Describe("Formatter", func() { data := &reportInfo{ Errors: map[string][]gosec.Error{}, Issues: []*gosec.Issue{ - &gosec.Issue{ + { Severity: 2, Confidence: 0, RuleID: "test", @@ -125,15 +126,15 @@ var _ = Describe("Formatter", func() { NumFound: 0, }, } - want := &sonarIssues{ - SonarIssues: []sonarIssue{ + want := &sonar.Report{ + Issues: []*sonar.Issue{ { EngineID: "gosec", RuleID: "test", - PrimaryLocation: location{ + PrimaryLocation: &sonar.Location{ Message: "test", FilePath: "subfolder/test.go", - TextRange: textRange{ + TextRange: &sonar.TextRange{ StartLine: 1, EndLine: 2, }, @@ -155,7 +156,7 @@ var _ = Describe("Formatter", func() { data := &reportInfo{ Errors: map[string][]gosec.Error{}, Issues: []*gosec.Issue{ - &gosec.Issue{ + { Severity: 2, Confidence: 0, RuleID: "test", @@ -172,8 +173,8 @@ var _ = Describe("Formatter", func() { NumFound: 0, }, } - want := &sonarIssues{ - SonarIssues: []sonarIssue{}, + want := &sonar.Report{ + Issues: []*sonar.Issue{}, } rootPath := "/home/src/project2" @@ -187,7 +188,7 @@ var _ = Describe("Formatter", func() { data := &reportInfo{ Errors: map[string][]gosec.Error{}, Issues: []*gosec.Issue{ - &gosec.Issue{ + { Severity: 2, Confidence: 0, RuleID: "test", @@ -196,7 +197,7 @@ var _ = Describe("Formatter", func() { Code: "", Line: "1-2", }, - &gosec.Issue{ + { Severity: 2, Confidence: 0, RuleID: "test", @@ -213,15 +214,15 @@ var _ = Describe("Formatter", func() { NumFound: 0, }, } - want := &sonarIssues{ - SonarIssues: []sonarIssue{ + want := &sonar.Report{ + Issues: []*sonar.Issue{ { EngineID: "gosec", RuleID: "test", - PrimaryLocation: location{ + PrimaryLocation: &sonar.Location{ Message: "test", FilePath: "test-project1.go", - TextRange: textRange{ + TextRange: &sonar.TextRange{ StartLine: 1, EndLine: 2, }, @@ -233,10 +234,10 @@ var _ = Describe("Formatter", func() { { EngineID: "gosec", RuleID: "test", - PrimaryLocation: location{ + PrimaryLocation: &sonar.Location{ Message: "test", FilePath: "test-project2.go", - TextRange: textRange{ + TextRange: &sonar.TextRange{ StartLine: 1, EndLine: 2, }, @@ -407,7 +408,7 @@ var _ = Describe("Formatter", func() { Expect(result).To(ContainSubstring(expectation)) } }) - It("sonarqube formatted report should contain the CWE mapping", func() { + It("sonarqube formatted report shouldn't contain the CWE mapping", func() { for _, rule := range grules { cwe := gosec.IssueToCWE[rule] issue := createIssue(rule, cwe) @@ -424,7 +425,7 @@ var _ = Describe("Formatter", func() { Expect(err).ShouldNot(HaveOccurred()) expectation := stripString(expect.String()) - Expect(result).To(ContainSubstring(expectation)) + Expect(result).ShouldNot(ContainSubstring(expectation)) } }) It("golint formatted report should contain the CWE mapping", func() { diff --git a/output/sonarqube_format.go b/output/sonarqube_format.go index dc7cb0e..1e0fa22 100644 --- a/output/sonarqube_format.go +++ b/output/sonarqube_format.go @@ -1,32 +1,79 @@ package output -import "github.com/securego/gosec/v2" +import ( + "github.com/securego/gosec/v2" + "github.com/securego/gosec/v2/sonar" + "strconv" + "strings" +) -type textRange struct { - StartLine int `json:"startLine"` - EndLine int `json:"endLine"` - StartColumn int `json:"startColumn,omitempty"` - EtartColumn int `json:"endColumn,omitempty"` -} -type location struct { - Message string `json:"message"` - FilePath string `json:"filePath"` - TextRange textRange `json:"textRange,omitempty"` +const ( + //SonarqubeEffortMinutes effort to fix in minutes + SonarqubeEffortMinutes = 5 +) + +func convertToSonarIssues(rootPaths []string, data *reportInfo) (*sonar.Report, error) { + si := &sonar.Report{Issues: []*sonar.Issue{}} + for _, issue := range data.Issues { + sonarFilePath := parseFilePath(issue, rootPaths) + + if sonarFilePath == "" { + continue + } + + textRange, err := parseTextRange(issue) + if err != nil { + return si, err + } + + primaryLocation := buildPrimaryLocation(issue.What, sonarFilePath, textRange) + severity := getSonarSeverity(issue.Severity.String()) + + s := &sonar.Issue{ + EngineID: "gosec", + RuleID: issue.RuleID, + PrimaryLocation: primaryLocation, + Type: "VULNERABILITY", + Severity: severity, + EffortMinutes: SonarqubeEffortMinutes, + } + si.Issues = append(si.Issues, s) + } + return si, nil } -type sonarIssue struct { - EngineID string `json:"engineId"` - RuleID string `json:"ruleId"` - Cwe gosec.Cwe `json:"cwe"` - PrimaryLocation location `json:"primaryLocation"` - Type string `json:"type"` - Severity string `json:"severity"` - EffortMinutes int `json:"effortMinutes"` - SecondaryLocations []location `json:"secondaryLocations,omitempty"` +func buildPrimaryLocation(message string, filePath string, textRange *sonar.TextRange) *sonar.Location { + return &sonar.Location{ + Message: message, + FilePath: filePath, + TextRange: textRange, + } } -type sonarIssues struct { - SonarIssues []sonarIssue `json:"issues"` +func parseFilePath(issue *gosec.Issue, rootPaths []string) string { + var sonarFilePath string + for _, rootPath := range rootPaths { + if strings.HasPrefix(issue.File, rootPath) { + sonarFilePath = strings.Replace(issue.File, rootPath+"/", "", 1) + } + } + return sonarFilePath +} + +func parseTextRange(issue *gosec.Issue) (*sonar.TextRange, error) { + lines := strings.Split(issue.Line, "-") + startLine, err := strconv.Atoi(lines[0]) + if err != nil { + return nil, err + } + endLine := startLine + if len(lines) > 1 { + endLine, err = strconv.Atoi(lines[1]) + if err != nil { + return nil, err + } + } + return &sonar.TextRange{StartLine: startLine, EndLine: endLine}, nil } func getSonarSeverity(s string) string { diff --git a/sonar/types.go b/sonar/types.go new file mode 100644 index 0000000..bf12a38 --- /dev/null +++ b/sonar/types.go @@ -0,0 +1,32 @@ +package sonar + +//TextRange defines the text range of an issue's location +type TextRange struct { + StartLine int `json:"startLine"` + EndLine int `json:"endLine"` + StartColumn int `json:"startColumn,omitempty"` + EtartColumn int `json:"endColumn,omitempty"` +} + +//Location defines a sonar issue's location +type Location struct { + Message string `json:"message"` + FilePath string `json:"filePath"` + TextRange *TextRange `json:"textRange,omitempty"` +} + +//Issue defines a sonar issue +type Issue struct { + EngineID string `json:"engineId"` + RuleID string `json:"ruleId"` + PrimaryLocation *Location `json:"primaryLocation"` + Type string `json:"type"` + Severity string `json:"severity"` + EffortMinutes int `json:"effortMinutes"` + SecondaryLocations []*Location `json:"secondaryLocations,omitempty"` +} + +//Report defines a sonar report +type Report struct { + Issues []*Issue `json:"issues"` +}