From 0d4f1cb2cb96e8a0dd536f7dac072ee58a03cece Mon Sep 17 00:00:00 2001 From: mrtc0 Date: Mon, 2 Nov 2020 17:13:53 +0900 Subject: [PATCH] Support SARIF output (#539) * SARIF support * add sarif option to help text --- cmd/gosec/main.go | 2 +- output/formatter.go | 66 ++++++++++++++++++ output/formatter_test.go | 19 +++++ output/sarif_format.go | 146 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 232 insertions(+), 1 deletion(-) create mode 100644 output/sarif_format.go diff --git a/cmd/gosec/main.go b/cmd/gosec/main.go index ad18015..e954e53 100644 --- a/cmd/gosec/main.go +++ b/cmd/gosec/main.go @@ -73,7 +73,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, sonarqube, golint or text") + flagFormat = flag.String("fmt", "text", "Set output format. Valid options are: json, yaml, csv, junit-xml, html, sonarqube, golint, sarif or text") // #nosec alternative tag flagAlternativeNoSec = flag.String("nosec-tag", "", "Set an alternative string for #nosec. Some examples: #dontanalyze, #falsepositive") diff --git a/output/formatter.go b/output/formatter.go index 61d1471..feedb07 100644 --- a/output/formatter.go +++ b/output/formatter.go @@ -48,6 +48,9 @@ const ( // ReportJUnitXML set the output format to junit xml ReportJUnitXML // JUnit XML format + // ReportSARIF set the output format to SARIF + ReportSARIF // SARIF format + //SonarqubeEffortMinutes effort to fix in minutes SonarqubeEffortMinutes = 5 ) @@ -108,6 +111,8 @@ func CreateReport(w io.Writer, format string, enableColor bool, rootPaths []stri err = reportSonarqube(rootPaths, w, data) case "golint": err = reportGolint(w, data) + case "sarif": + err = reportSARIFTemplate(rootPaths, w, data) default: err = reportFromPlaintextTemplate(w, text, enableColor, data) } @@ -172,6 +177,53 @@ func convertToSonarIssues(rootPaths []string, data *reportInfo) (*sonarIssues, e return si, nil } +func convertToSarifReport(rootPaths []string, data *reportInfo) (*sarifReport, error) { + sr := buildSarifReport() + + var rules []*sarifRule + var locations []*sarifLocation + var results []*sarifResult + + for index, issue := range data.Issues { + rules = append(rules, buildSarifRule(issue)) + + location, err := buildSarifLocation(issue, rootPaths) + if err != nil { + return nil, err + } + locations = append(locations, location) + + result := &sarifResult{ + RuleID: fmt.Sprintf("%s (CWE-%s)", issue.RuleID, issue.Cwe.ID), + RuleIndex: index, + Level: sarifWarning, + Message: &sarifMessage{ + Text: issue.What, + }, + Locations: locations, + } + + results = append(results, result) + } + + tool := &sarifTool{ + Driver: &sarifDriver{ + Name: "gosec", + InformationURI: "https://github.com/securego/gosec/", + Rules: rules, + }, + } + + run := &sarifRun{ + Tool: tool, + Results: results, + } + + sr.Runs = append(sr.Runs, run) + + return sr, nil +} + func reportJSON(w io.Writer, data *reportInfo) error { raw, err := json.MarshalIndent(data, "", "\t") if err != nil { @@ -258,6 +310,20 @@ func reportJUnitXML(w io.Writer, data *reportInfo) error { return nil } +func reportSARIFTemplate(rootPaths []string, w io.Writer, data *reportInfo) error { + sr, err := convertToSarifReport(rootPaths, data) + if err != nil { + return err + } + raw, err := json.MarshalIndent(sr, "", "\t") + if err != nil { + return err + } + + _, err = w.Write(raw) + return err +} + func reportFromPlaintextTemplate(w io.Writer, reportTemplate string, enableColor bool, data *reportInfo) error { t, e := plainTemplate. New("gosec"). diff --git a/output/formatter_test.go b/output/formatter_test.go index 985ea29..9ac12eb 100644 --- a/output/formatter_test.go +++ b/output/formatter_test.go @@ -441,5 +441,24 @@ var _ = Describe("Formatter", func() { Expect(string(buf.String())).To(Equal(expect)) } }) + It("sarif formatted report should contain the CWE mapping", func() { + for _, rule := range grules { + cwe := gosec.IssueToCWE[rule] + issue := createIssue(rule, cwe) + error := map[string][]gosec.Error{} + + buf := new(bytes.Buffer) + err := CreateReport(buf, "sarif", false, []string{}, []*gosec.Issue{&issue}, &gosec.Metrics{}, error) + Expect(err).ShouldNot(HaveOccurred()) + + result := stripString(buf.String()) + + pattern := "rules\":[{\"id\":\"%s(CWE-%s)\"" + expect := fmt.Sprintf(pattern, rule, cwe.ID) + Expect(err).ShouldNot(HaveOccurred()) + + Expect(result).To(ContainSubstring(expect)) + } + }) }) }) diff --git a/output/sarif_format.go b/output/sarif_format.go new file mode 100644 index 0000000..4a29ea8 --- /dev/null +++ b/output/sarif_format.go @@ -0,0 +1,146 @@ +package output + +import ( + "fmt" + "github.com/securego/gosec/v2" + "strconv" + "strings" +) + +type sarifLevel string + +const ( + sarifNone = sarifLevel("none") + sarifNote = sarifLevel("note") + sarifWarning = sarifLevel("warning") + sarifError = sarifLevel("error") +) + +type sarifProperties struct { + Tags []string `json:"tags"` +} + +type sarifRule struct { + ID string `json:"id"` + Name string `json:"name"` + ShortDescription *sarifMessage `json:"shortDescription"` + FullDescription *sarifMessage `json:"fullDescription"` + Help *sarifMessage `json:"help"` + Properties *sarifProperties `json:"properties"` +} + +type sarifArtifactLocation struct { + URI string `json:"uri"` +} + +type sarifRegion struct { + StartLine uint64 `json:"startLine"` + StartColumn uint64 `json:"startColumn"` + EndColumn uint64 `json:"endColumn"` +} + +type sarifPhysicalLocation struct { + ArtifactLocation *sarifArtifactLocation `json:"artifactLocation"` + Region *sarifRegion `json:"region"` +} + +type sarifLocation struct { + PhysicalLocation *sarifPhysicalLocation `json:"physicalLocation"` +} + +type sarifMessage struct { + Text string `json:"text"` +} + +type sarifResult struct { + RuleID string `json:"ruleId"` + RuleIndex int `json:"ruleIndex"` + Level sarifLevel `json:"level"` + Message *sarifMessage `json:"message"` + Locations []*sarifLocation `json:"locations"` +} + +type sarifDriver struct { + Name string `json:"name"` + InformationURI string `json:"informationUri"` + Rules []*sarifRule `json:"rules,omitempty"` +} + +type sarifTool struct { + Driver *sarifDriver `json:"driver"` +} + +type sarifRun struct { + Tool *sarifTool `json:"tool"` + Results []*sarifResult `json:"results"` +} + +type sarifReport struct { + Schema string `json:"$schema"` + Version string `json:"version"` + Runs []*sarifRun `json:"runs"` +} + +// buildSarifReport return SARIF report struct +func buildSarifReport() *sarifReport { + return &sarifReport{ + Version: "2.1.0", + Schema: "https://schemastore.azurewebsites.net/schemas/json/sarif-2.1.0-rtm.4.json", + Runs: []*sarifRun{}, + } +} + +// buildSarifRule return SARIF rule field struct +func buildSarifRule(issue *gosec.Issue) *sarifRule { + return &sarifRule{ + ID: fmt.Sprintf("%s (CWE-%s)", issue.RuleID, issue.Cwe.ID), + Name: issue.What, + ShortDescription: &sarifMessage{ + Text: issue.What, + }, + FullDescription: &sarifMessage{ + Text: issue.What, + }, + Help: &sarifMessage{ + Text: fmt.Sprintf("%s\nSeverity: %s\nConfidence: %s\nCWE: %s", issue.What, issue.Severity.String(), issue.Confidence.String(), issue.Cwe.URL), + }, + Properties: &sarifProperties{ + Tags: []string{fmt.Sprintf("CWE-%s", issue.Cwe.ID), issue.Severity.String()}, + }, + } +} + +// buildSarifLocation return SARIF location struct +func buildSarifLocation(issue *gosec.Issue, rootPaths []string) (*sarifLocation, error) { + var filePath string + + line, err := strconv.ParseUint(issue.Line, 10, 64) + if err != nil { + return nil, err + } + col, err := strconv.ParseUint(issue.Col, 10, 64) + if err != nil { + return nil, err + } + + for _, rootPath := range rootPaths { + if strings.HasPrefix(issue.File, rootPath) { + filePath = strings.Replace(issue.File, rootPath+"/", "", 1) + } + } + + location := &sarifLocation{ + PhysicalLocation: &sarifPhysicalLocation{ + ArtifactLocation: &sarifArtifactLocation{ + URI: filePath, + }, + Region: &sarifRegion{ + StartLine: line, + StartColumn: col, + EndColumn: col, + }, + }, + } + + return location, nil +}