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")
|
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")
|
||||||
|
|
|
@ -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").
|
||||||
|
|
|
@ -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
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