From 020479a8325a8fe974de5e11e2bb0dd0ab21afa2 Mon Sep 17 00:00:00 2001 From: Cosmin Cojocar Date: Mon, 24 Jun 2019 14:35:11 +0200 Subject: [PATCH] Support multiple root paths when generating the Sonarqube report Signed-off-by: Cosmin Cojocar --- cmd/gosec/main.go | 21 ++++---- output/formatter.go | 24 ++++++--- output/formatter_test.go | 107 ++++++++++++++++++++++++++++++++++++++- 3 files changed, 134 insertions(+), 18 deletions(-) diff --git a/cmd/gosec/main.go b/cmd/gosec/main.go index 83e7692..7b2a886 100644 --- a/cmd/gosec/main.go +++ b/cmd/gosec/main.go @@ -171,19 +171,27 @@ func loadRules(include, exclude string) rules.RuleList { return rules.Generate(filters...) } -func saveOutput(filename, format, rootPath string, issues []*gosec.Issue, metrics *gosec.Metrics, errors map[string][]gosec.Error) error { +func saveOutput(filename, format string, paths []string, issues []*gosec.Issue, metrics *gosec.Metrics, errors map[string][]gosec.Error) error { + rootPaths := []string{} + for _, path := range paths { + rootPath, err := gosec.RootPath(path) + if err != nil { + return fmt.Errorf("failed to get the root path of the projects: %s", err) + } + rootPaths = append(rootPaths, rootPath) + } if filename != "" { outfile, err := os.Create(filename) if err != nil { return err } defer outfile.Close() - err = output.CreateReport(outfile, format, rootPath, issues, metrics, errors) + err = output.CreateReport(outfile, format, rootPaths, issues, metrics, errors) if err != nil { return err } } else { - err := output.CreateReport(os.Stdout, format, rootPath, issues, metrics, errors) + err := output.CreateReport(os.Stdout, format, rootPaths, issues, metrics, errors) if err != nil { return err } @@ -318,13 +326,8 @@ func main() { os.Exit(0) } - rootPath, err := gosec.RootPath(flag.Args()[0]) - if err != nil { - logger.Fatalf("Failed to get the root path of the project: %s", err) - } - // Create output report - if err := saveOutput(*flagOutput, *flagFormat, rootPath, issues, metrics, errors); err != nil { + if err := saveOutput(*flagOutput, *flagFormat, flag.Args(), issues, metrics, errors); err != nil { logger.Fatal(err) } diff --git a/output/formatter.go b/output/formatter.go index a63b51f..e66015b 100644 --- a/output/formatter.go +++ b/output/formatter.go @@ -76,7 +76,7 @@ type reportInfo struct { // CreateReport generates a report based for the supplied issues and metrics given // the specified format. The formats currently accepted are: json, csv, html and text. -func CreateReport(w io.Writer, format, rootPath string, issues []*gosec.Issue, metrics *gosec.Metrics, errors map[string][]gosec.Error) error { +func CreateReport(w io.Writer, format string, rootPaths []string, issues []*gosec.Issue, metrics *gosec.Metrics, errors map[string][]gosec.Error) error { data := &reportInfo{ Errors: errors, Issues: issues, @@ -97,15 +97,15 @@ func CreateReport(w io.Writer, format, rootPath string, issues []*gosec.Issue, m case "text": err = reportFromPlaintextTemplate(w, text, data) case "sonarqube": - err = reportSonarqube(rootPath, w, data) + err = reportSonarqube(rootPaths, w, data) default: err = reportFromPlaintextTemplate(w, text, data) } return err } -func reportSonarqube(rootPath string, w io.Writer, data *reportInfo) error { - si, err := convertToSonarIssues(rootPath, data) +func reportSonarqube(rootPaths []string, w io.Writer, data *reportInfo) error { + si, err := convertToSonarIssues(rootPaths, data) if err != nil { return err } @@ -117,11 +117,20 @@ func reportSonarqube(rootPath string, w io.Writer, data *reportInfo) error { return err } -func convertToSonarIssues(rootPath string, data *reportInfo) (sonarIssues, error) { +func convertToSonarIssues(rootPaths []string, data *reportInfo) (sonarIssues, error) { var si sonarIssues for _, issue := range data.Issues { - lines := strings.Split(issue.Line, "-") + 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 @@ -133,12 +142,13 @@ func convertToSonarIssues(rootPath string, data *reportInfo) (sonarIssues, error return si, err } } + s := sonarIssue{ EngineID: "gosec", RuleID: issue.RuleID, PrimaryLocation: location{ Message: issue.What, - FilePath: strings.Replace(issue.File, rootPath+"/", "", 1), + FilePath: sonarFilePath, TextRange: textRange{StartLine: startLine, EndLine: endLine}, }, Type: "VULNERABILITY", diff --git a/output/formatter_test.go b/output/formatter_test.go index 1d016f6..339d166 100644 --- a/output/formatter_test.go +++ b/output/formatter_test.go @@ -54,7 +54,7 @@ var _ = Describe("Formatter", func() { rootPath := "/home/src/project" - issues, err := convertToSonarIssues(rootPath, data) + issues, err := convertToSonarIssues([]string{rootPath}, data) Expect(err).ShouldNot(HaveOccurred()) Expect(issues).To(Equal(want)) }) @@ -102,7 +102,110 @@ var _ = Describe("Formatter", func() { rootPath := "/home/src/project" - issues, err := convertToSonarIssues(rootPath, data) + issues, err := convertToSonarIssues([]string{rootPath}, data) + Expect(err).ShouldNot(HaveOccurred()) + Expect(issues).To(Equal(want)) + }) + It("it should not parse the report info for files from other projects", func() { + data := &reportInfo{ + Errors: map[string][]gosec.Error{}, + Issues: []*gosec.Issue{ + &gosec.Issue{ + Severity: 2, + Confidence: 0, + RuleID: "test", + What: "test", + File: "/home/src/project1/test.go", + Code: "", + Line: "1-2", + }, + }, + Stats: &gosec.Metrics{ + NumFiles: 0, + NumLines: 0, + NumNosec: 0, + NumFound: 0, + }, + } + want := sonarIssues{ + SonarIssues: nil, + } + + rootPath := "/home/src/project2" + + issues, err := convertToSonarIssues([]string{rootPath}, data) + Expect(err).ShouldNot(HaveOccurred()) + Expect(issues).To(Equal(want)) + }) + + It("it should parse the report info for multiple projects projects", func() { + data := &reportInfo{ + Errors: map[string][]gosec.Error{}, + Issues: []*gosec.Issue{ + &gosec.Issue{ + Severity: 2, + Confidence: 0, + RuleID: "test", + What: "test", + File: "/home/src/project1/test-project1.go", + Code: "", + Line: "1-2", + }, + &gosec.Issue{ + Severity: 2, + Confidence: 0, + RuleID: "test", + What: "test", + File: "/home/src/project2/test-project2.go", + Code: "", + Line: "1-2", + }, + }, + Stats: &gosec.Metrics{ + NumFiles: 0, + NumLines: 0, + NumNosec: 0, + NumFound: 0, + }, + } + want := sonarIssues{ + SonarIssues: []sonarIssue{ + { + EngineID: "gosec", + RuleID: "test", + PrimaryLocation: location{ + Message: "test", + FilePath: "test-project1.go", + TextRange: textRange{ + StartLine: 1, + EndLine: 2, + }, + }, + Type: "VULNERABILITY", + Severity: "BLOCKER", + EffortMinutes: SonarqubeEffortMinutes, + }, + { + EngineID: "gosec", + RuleID: "test", + PrimaryLocation: location{ + Message: "test", + FilePath: "test-project2.go", + TextRange: textRange{ + StartLine: 1, + EndLine: 2, + }, + }, + Type: "VULNERABILITY", + Severity: "BLOCKER", + EffortMinutes: SonarqubeEffortMinutes, + }, + }, + } + + rootPaths := []string{"/home/src/project1", "/home/src/project2"} + + issues, err := convertToSonarIssues(rootPaths, data) Expect(err).ShouldNot(HaveOccurred()) Expect(issues).To(Equal(want)) })