Support SARIF output (#539)

* SARIF support

* add sarif option to help text
This commit is contained in:
mrtc0 2020-11-02 17:13:53 +09:00 committed by GitHub
parent a4746e18e3
commit 0d4f1cb2cb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 232 additions and 1 deletions

View file

@ -73,7 +73,7 @@ var (
flagIgnoreNoSec = flag.Bool("nosec", false, "Ignores #nosec comments when set") flagIgnoreNoSec = flag.Bool("nosec", false, "Ignores #nosec comments when set")
// format output // 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 // #nosec alternative tag
flagAlternativeNoSec = flag.String("nosec-tag", "", "Set an alternative string for #nosec. Some examples: #dontanalyze, #falsepositive") flagAlternativeNoSec = flag.String("nosec-tag", "", "Set an alternative string for #nosec. Some examples: #dontanalyze, #falsepositive")

View file

@ -48,6 +48,9 @@ const (
// ReportJUnitXML set the output format to junit xml // ReportJUnitXML set the output format to junit xml
ReportJUnitXML // JUnit XML format ReportJUnitXML // JUnit XML format
// ReportSARIF set the output format to SARIF
ReportSARIF // SARIF format
//SonarqubeEffortMinutes effort to fix in minutes //SonarqubeEffortMinutes effort to fix in minutes
SonarqubeEffortMinutes = 5 SonarqubeEffortMinutes = 5
) )
@ -108,6 +111,8 @@ func CreateReport(w io.Writer, format string, enableColor bool, rootPaths []stri
err = reportSonarqube(rootPaths, w, data) err = reportSonarqube(rootPaths, w, data)
case "golint": case "golint":
err = reportGolint(w, data) err = reportGolint(w, data)
case "sarif":
err = reportSARIFTemplate(rootPaths, w, data)
default: default:
err = reportFromPlaintextTemplate(w, text, enableColor, data) err = reportFromPlaintextTemplate(w, text, enableColor, data)
} }
@ -172,6 +177,53 @@ func convertToSonarIssues(rootPaths []string, data *reportInfo) (*sonarIssues, e
return si, nil 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 { func reportJSON(w io.Writer, data *reportInfo) error {
raw, err := json.MarshalIndent(data, "", "\t") raw, err := json.MarshalIndent(data, "", "\t")
if err != nil { if err != nil {
@ -258,6 +310,20 @@ func reportJUnitXML(w io.Writer, data *reportInfo) error {
return nil 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 { func reportFromPlaintextTemplate(w io.Writer, reportTemplate string, enableColor bool, data *reportInfo) error {
t, e := plainTemplate. t, e := plainTemplate.
New("gosec"). New("gosec").

View file

@ -441,5 +441,24 @@ var _ = Describe("Formatter", func() {
Expect(string(buf.String())).To(Equal(expect)) 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))
}
})
}) })
}) })

146
output/sarif_format.go Normal file
View file

@ -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
}