mirror of
https://github.com/securego/gosec.git
synced 2024-12-24 11:35:52 +00:00
Support SARIF output (#539)
* SARIF support * add sarif option to help text
This commit is contained in:
parent
a4746e18e3
commit
0d4f1cb2cb
4 changed files with 232 additions and 1 deletions
|
@ -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")
|
||||
|
|
|
@ -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").
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
146
output/sarif_format.go
Normal file
146
output/sarif_format.go
Normal 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
|
||||
}
|
Loading…
Reference in a new issue