diff --git a/cmd/gosec/main.go b/cmd/gosec/main.go index b1755a4..0186d2b 100644 --- a/cmd/gosec/main.go +++ b/cmd/gosec/main.go @@ -65,7 +65,7 @@ var ( flagIgnoreNoSec = flag.Bool("nosec", false, "Ignores #nosec comments when set") // format output - flagFormat = flag.String("fmt", "text", "Set output format. Valid options are: json, yaml, csv, junit-xml, html, or text") + flagFormat = flag.String("fmt", "text", "Set output format. Valid options are: json, yaml, csv, junit-xml, html, sonarqube, or text") // output file flagOutput = flag.String("out", "", "Set output file for results") @@ -165,19 +165,19 @@ func loadRules(include, exclude string) rules.RuleList { return rules.Generate(filters...) } -func saveOutput(filename, format string, issues []*gosec.Issue, metrics *gosec.Metrics, errors map[string][]gosec.Error) error { +func saveOutput(filename, format, rootPath string, issues []*gosec.Issue, metrics *gosec.Metrics, errors map[string][]gosec.Error) error { if filename != "" { outfile, err := os.Create(filename) if err != nil { return err } defer outfile.Close() - err = output.CreateReport(outfile, format, issues, metrics, errors) + err = output.CreateReport(outfile, format, rootPath, issues, metrics, errors) if err != nil { return err } } else { - err := output.CreateReport(os.Stdout, format, issues, metrics, errors) + err := output.CreateReport(os.Stdout, format, rootPath, issues, metrics, errors) if err != nil { return err } @@ -318,6 +318,7 @@ func main() { if *flagBuildTags != "" { buildTags = strings.Split(*flagBuildTags, ",") } + if err := analyzer.Process(buildTags, packages...); err != nil { logger.Fatal(err) } @@ -342,9 +343,9 @@ func main() { if !issuesFound && *flagQuiet { os.Exit(0) } - + rootPath := packages[0] // Create output report - if err := saveOutput(*flagOutput, *flagFormat, issues, metrics, errors); err != nil { + if err := saveOutput(*flagOutput, *flagFormat, rootPath, issues, metrics, errors); err != nil { logger.Fatal(err) } diff --git a/output/formatter.go b/output/formatter.go index 61810fc..edca2c7 100644 --- a/output/formatter.go +++ b/output/formatter.go @@ -20,6 +20,8 @@ import ( "encoding/xml" htmlTemplate "html/template" "io" + "strconv" + "strings" plainTemplate "text/template" "github.com/securego/gosec" @@ -71,7 +73,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 string, issues []*gosec.Issue, metrics *gosec.Metrics, errors map[string][]gosec.Error) error { +func CreateReport(w io.Writer, format, rootPath string, issues []*gosec.Issue, metrics *gosec.Metrics, errors map[string][]gosec.Error) error { data := &reportInfo{ Errors: errors, Issues: issues, @@ -91,12 +93,50 @@ func CreateReport(w io.Writer, format string, issues []*gosec.Issue, metrics *go err = reportFromHTMLTemplate(w, html, data) case "text": err = reportFromPlaintextTemplate(w, text, data) + case "sonarqube": + err = reportSonarqube(rootPath, w, data) default: err = reportFromPlaintextTemplate(w, text, data) } return err } +func reportSonarqube(rootPath string, w io.Writer, data *reportInfo) error { + var sonarIssues []sonarIssue + for _, issue := range data.Issues { + lines := strings.Split(issue.Line, "-") + + startLine, _ := strconv.Atoi(lines[0]) + endLine := startLine + if len(lines) > 1 { + endLine, _ = strconv.Atoi(lines[1]) + } + s := sonarIssue{ + EngineId: "gosec", + RuleId: issue.RuleID, + PrimaryLocation: location{ + Message: issue.What, + FilePath: strings.Replace(issue.File, rootPath+"/", "", 1), + TextRange: textRange{StartLine: startLine, EndLine: endLine}, + }, + Type: "VULNERABILITY", + Severity: getSonarSeverity(issue.Severity.String()), + EffortMinutes: 5, + } + sonarIssues = append(sonarIssues, s) + } + raw, err := json.MarshalIndent(sonarIssues, "", "\t") + if err != nil { + panic(err) + } + + _, err = w.Write(raw) + if err != nil { + panic(err) + } + return err +} + func reportJSON(w io.Writer, data *reportInfo) error { raw, err := json.MarshalIndent(data, "", "\t") if err != nil { diff --git a/output/sonarqube_format.go b/output/sonarqube_format.go new file mode 100644 index 0000000..d0f33b3 --- /dev/null +++ b/output/sonarqube_format.go @@ -0,0 +1,36 @@ +package output + +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"` +} + +type sonarIssue 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"` +} + +func getSonarSeverity(s string) string { + switch s { + case "LOW": + return "MINOR" + case "MEDIUM": + return "MAJOR" + case "HIGH": + return "BLOCKER" + default: + return "INFO" + } +}