Generate the SARIF types, handle taxonomies and separate responsibilities

This commit is contained in:
Matthieu MOREL 2021-05-05 18:54:32 +02:00 committed by GitHub
parent 0fa5d0b2d6
commit cc83d4c922
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 2011 additions and 279 deletions

View file

@ -311,6 +311,22 @@ You can build the binary with:
make
```
### Note on Sarif Types Generation
Install the tool with :
```bash
go get -u github.com/a-h/generate/cmd/schema-generate
```
Then generate the types with :
```bash
schema-generate -i sarif-schema-2.1.0.json -o mypath/types.go
```
Most of the MarshallJSON/UnmarshalJSON are removed except the one for PropertyBag which is handy to inline the additionnal properties. The rest can be removed.
The URI,ID, UUID, GUID were renamed so it fits the Golang convention defined [here](https://github.com/golang/lint/blob/master/lint.go#L700)
### Tests
You can run all unit tests using:

109
cwe/data.go Normal file
View file

@ -0,0 +1,109 @@
package cwe
var data = map[string]Weakness{
"118": {
ID: "118",
Description: "The software does not restrict or incorrectly restricts operations within the boundaries of a resource that is accessed using an index or pointer, such as memory or files.",
Name: "Incorrect Access of Indexable Resource ('Range Error')",
},
"190": {
ID: "190",
Description: "The software performs a calculation that can produce an integer overflow or wraparound, when the logic assumes that the resulting value will always be larger than the original value. This can introduce other weaknesses when the calculation is used for resource management or execution control.",
Name: "Integer Overflow or Wraparound",
},
"200": {
ID: "200",
Description: "The product exposes sensitive information to an actor that is not explicitly authorized to have access to that information.",
Name: "Exposure of Sensitive Information to an Unauthorized Actor",
},
"22": {
ID: "22",
Description: "The software uses external input to construct a pathname that is intended to identify a file or directory that is located underneath a restricted parent directory, but the software does not properly neutralize special elements within the pathname that can cause the pathname to resolve to a location that is outside of the restricted directory.",
Name: "Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')",
},
"242": {
ID: "242",
Description: "The program calls a function that can never be guaranteed to work safely.",
Name: "Use of Inherently Dangerous Function",
},
"276": {
ID: "276",
Description: "During installation, installed file permissions are set to allow anyone to modify those files.",
Name: "Incorrect Default Permissions",
},
"295": {
ID: "295",
Description: "The software does not validate, or incorrectly validates, a certificate.",
Name: "Improper Certificate Validation",
},
"310": {
ID: "310",
Description: "Weaknesses in this category are related to the design and implementation of data confidentiality and integrity. Frequently these deal with the use of encoding techniques, encryption libraries, and hashing algorithms. The weaknesses in this category could lead to a degradation of the quality data if they are not addressed.",
Name: "Cryptographic Issues",
},
"322": {
ID: "322",
Description: "The software performs a key exchange with an actor without verifying the identity of that actor.",
Name: "Key Exchange without Entity Authentication",
},
"326": {
ID: "326",
Description: "The software stores or transmits sensitive data using an encryption scheme that is theoretically sound, but is not strong enough for the level of protection required.",
Name: "Inadequate Encryption Strength",
},
"327": {
ID: "327",
Description: "The use of a broken or risky cryptographic algorithm is an unnecessary risk that may result in the exposure of sensitive information.",
Name: "Use of a Broken or Risky Cryptographic Algorithm",
},
"338": {
ID: "338",
Description: "The product uses a Pseudo-Random Number Generator (PRNG) in a security context, but the PRNG's algorithm is not cryptographically strong.",
Name: "Use of Cryptographically Weak Pseudo-Random Number Generator (PRNG)",
},
"377": {
ID: "377",
Description: "Creating and using insecure temporary files can leave application and system data vulnerable to attack.",
Name: "Insecure Temporary File",
},
"409": {
ID: "409",
Description: "The software does not handle or incorrectly handles a compressed input with a very high compression ratio that produces a large output.",
Name: "Improper Handling of Highly Compressed Data (Data Amplification)",
},
"703": {
ID: "703",
Description: "The software does not properly anticipate or handle exceptional conditions that rarely occur during normal operation of the software.",
Name: "Improper Check or Handling of Exceptional Conditions",
},
"78": {
ID: "78",
Description: "The software constructs all or part of an OS command using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the intended OS command when it is sent to a downstream component.",
Name: "Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection')",
},
"79": {
ID: "79",
Description: "The software does not neutralize or incorrectly neutralizes user-controllable input before it is placed in output that is used as a web page that is served to other users.",
Name: "Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')",
},
"798": {
ID: "798",
Description: "The software contains hard-coded credentials, such as a password or cryptographic key, which it uses for its own inbound authentication, outbound communication to external components, or encryption of internal data.",
Name: "Use of Hard-coded Credentials",
},
"88": {
ID: "88",
Description: "The software constructs a string for a command to executed by a separate component\nin another control sphere, but it does not properly delimit the\nintended arguments, options, or switches within that command string.",
Name: "Improper Neutralization of Argument Delimiters in a Command ('Argument Injection')",
},
"89": {
ID: "89",
Description: "The software constructs all or part of an SQL command using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the intended SQL command when it is sent to a downstream component.",
Name: "Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection')",
},
}
//Get Retrieves a CWE weakness by it's id
func Get(id string) Weakness {
return data[id]
}

17
cwe/types.go Normal file
View file

@ -0,0 +1,17 @@
package cwe
import (
"fmt"
)
// Weakness defines a CWE weakness based on http://cwe.mitre.org/data/xsd/cwe_schema_v6.4.xsd
type Weakness struct {
ID string
Name string
Description string
}
//URL Expose the CWE URL
func (w *Weakness) URL() string {
return fmt.Sprintf("https://cwe.mitre.org/data/definitions/%s.html", w.ID)
}

12
formatter/types.go Normal file
View file

@ -0,0 +1,12 @@
package formatter
import (
"github.com/securego/gosec/v2"
)
//ReportInfo this is report information
type ReportInfo struct {
Errors map[string][]gosec.Error `json:"Golang errors"`
Issues []*gosec.Issue
Stats *gosec.Metrics
}

3
go.mod
View file

@ -1,13 +1,12 @@
module github.com/securego/gosec/v2
require (
github.com/google/uuid v1.1.1
github.com/gookit/color v1.4.2
github.com/lib/pq v1.10.1 // indirect
github.com/mozilla/tls-observatory v0.0.0-20210209181001-cf43108d6880
github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354
github.com/onsi/ginkgo v1.16.1
github.com/onsi/gomega v1.11.0
golang.org/x/crypto v0.0.0-20210503195802-e9a32991a82e // indirect
golang.org/x/mod v0.4.1 // indirect
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 // indirect
golang.org/x/text v0.3.5 // indirect

7
go.sum
View file

@ -160,6 +160,7 @@ github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm4
github.com/google/trillian v1.3.11/go.mod h1:0tPraVHrSDkA3BO6vKX67zgLXs6SsOAbHEivX+9mPgw=
github.com/google/uuid v0.0.0-20161128191214-064e2069ce9c/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
@ -215,9 +216,8 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/letsencrypt/pkcs11key/v4 v4.0.0/go.mod h1:EFUvBDay26dErnNb70Nd0/VW3tJiIbETBPTl9ATXQag=
github.com/lib/pq v1.8.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lib/pq v1.9.0 h1:L8nSXQQzAYByakOFMTwpjRoHsMJklur4Gi59b6VivR8=
github.com/lib/pq v1.9.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lib/pq v1.10.1 h1:6VXZrLU0jHBYyAqrSPa+MgPfnSvTPuMgK+k0o5kVFWo=
github.com/lib/pq v1.10.1/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
@ -366,9 +366,8 @@ golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad h1:DN0cp81fZ3njFcrLCytUHRSUkqBjfTo4Tx9RJTWs0EY=
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20210503195802-e9a32991a82e h1:8foAy0aoO5GkqCvAEJ4VC4P3zksTg4X4aJCDpZzmgQI=
golang.org/x/crypto v0.0.0-20210503195802-e9a32991a82e/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=

View file

@ -29,6 +29,8 @@ import (
color "github.com/gookit/color"
"github.com/securego/gosec/v2"
"github.com/securego/gosec/v2/formatter"
"github.com/securego/gosec/v2/sarif"
"gopkg.in/yaml.v2"
)
@ -76,16 +78,10 @@ Golang errors in file: [{{ $filePath }}]:
`
type reportInfo struct {
Errors map[string][]gosec.Error `json:"Golang errors"`
Issues []*gosec.Issue
Stats *gosec.Metrics
}
// CreateReport generates a report based for the supplied issues and metrics given
// the specified format. The formats currently accepted are: json, yaml, csv, junit-xml, html, sonarqube, golint and text.
func CreateReport(w io.Writer, format string, enableColor bool, rootPaths []string, issues []*gosec.Issue, metrics *gosec.Metrics, errors map[string][]gosec.Error) error {
data := &reportInfo{
data := &formatter.ReportInfo{
Errors: errors,
Issues: issues,
Stats: metrics,
@ -116,7 +112,7 @@ func CreateReport(w io.Writer, format string, enableColor bool, rootPaths []stri
return err
}
func reportSonarqube(rootPaths []string, w io.Writer, data *reportInfo) error {
func reportSonarqube(rootPaths []string, w io.Writer, data *formatter.ReportInfo) error {
si, err := convertToSonarIssues(rootPaths, data)
if err != nil {
return err
@ -129,67 +125,7 @@ func reportSonarqube(rootPaths []string, w io.Writer, data *reportInfo) error {
return err
}
func convertToSarifReport(rootPaths []string, data *reportInfo) (*sarifReport, error) {
sr := buildSarifReport()
type rule struct {
index int
rule *sarifRule
}
rules := make([]*sarifRule, 0)
rulesIndices := make(map[string]rule)
lastRuleIndex := -1
results := []*sarifResult{}
for _, issue := range data.Issues {
r, ok := rulesIndices[issue.RuleID]
if !ok {
lastRuleIndex++
r = rule{index: lastRuleIndex, rule: buildSarifRule(issue)}
rulesIndices[issue.RuleID] = r
rules = append(rules, r.rule)
}
location, err := buildSarifLocation(issue, rootPaths)
if err != nil {
return nil, err
}
result := &sarifResult{
RuleID: r.rule.ID,
RuleIndex: r.index,
Level: getSarifLevel(issue.Severity.String()),
Message: &sarifMessage{
Text: issue.What,
},
Locations: []*sarifLocation{location},
}
results = append(results, result)
}
tool := &sarifTool{
Driver: &sarifDriver{
Name: "gosec",
Version: "2.1.0",
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 *formatter.ReportInfo) error {
raw, err := json.MarshalIndent(data, "", "\t")
if err != nil {
return err
@ -199,7 +135,7 @@ func reportJSON(w io.Writer, data *reportInfo) error {
return err
}
func reportYAML(w io.Writer, data *reportInfo) error {
func reportYAML(w io.Writer, data *formatter.ReportInfo) error {
raw, err := yaml.Marshal(data)
if err != nil {
return err
@ -208,7 +144,7 @@ func reportYAML(w io.Writer, data *reportInfo) error {
return err
}
func reportCSV(w io.Writer, data *reportInfo) error {
func reportCSV(w io.Writer, data *formatter.ReportInfo) error {
out := csv.NewWriter(w)
defer out.Flush()
for _, issue := range data.Issues {
@ -228,7 +164,7 @@ func reportCSV(w io.Writer, data *reportInfo) error {
return nil
}
func reportGolint(w io.Writer, data *reportInfo) error {
func reportGolint(w io.Writer, data *formatter.ReportInfo) error {
// Output Sample:
// /tmp/main.go:11:14: [CWE-310] RSA keys should be at least 2048 bits (Rule:G403, Severity:MEDIUM, Confidence:HIGH)
@ -258,7 +194,7 @@ func reportGolint(w io.Writer, data *reportInfo) error {
return nil
}
func reportJUnitXML(w io.Writer, data *reportInfo) error {
func reportJUnitXML(w io.Writer, data *formatter.ReportInfo) error {
junitXMLStruct := createJUnitXMLStruct(data)
raw, err := xml.MarshalIndent(junitXMLStruct, "", "\t")
if err != nil {
@ -275,8 +211,8 @@ 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)
func reportSARIFTemplate(rootPaths []string, w io.Writer, data *formatter.ReportInfo) error {
sr, err := sarif.ConvertToSarifReport(rootPaths, data)
if err != nil {
return err
}
@ -289,7 +225,7 @@ func reportSARIFTemplate(rootPaths []string, w io.Writer, data *reportInfo) erro
return err
}
func reportFromPlaintextTemplate(w io.Writer, reportTemplate string, enableColor bool, data *reportInfo) error {
func reportFromPlaintextTemplate(w io.Writer, reportTemplate string, enableColor bool, data *formatter.ReportInfo) error {
t, e := plainTemplate.
New("gosec").
Funcs(plainTextFuncMap(enableColor)).
@ -301,7 +237,7 @@ func reportFromPlaintextTemplate(w io.Writer, reportTemplate string, enableColor
return t.Execute(w, data)
}
func reportFromHTMLTemplate(w io.Writer, reportTemplate string, data *reportInfo) error {
func reportFromHTMLTemplate(w io.Writer, reportTemplate string, data *formatter.ReportInfo) error {
t, e := htmlTemplate.New("gosec").Parse(reportTemplate)
if e != nil {
return e

View file

@ -9,6 +9,7 @@ import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/securego/gosec/v2"
"github.com/securego/gosec/v2/formatter"
"github.com/securego/gosec/v2/sonar"
"gopkg.in/yaml.v2"
)
@ -34,10 +35,10 @@ func createIssue(ruleID string, cwe gosec.Cwe) gosec.Issue {
}
}
func createReportInfo(rule string, cwe gosec.Cwe) reportInfo {
func createReportInfo(rule string, cwe gosec.Cwe) formatter.ReportInfo {
issue := createIssue(rule, cwe)
metrics := gosec.Metrics{}
return reportInfo{
return formatter.ReportInfo{
Errors: map[string][]gosec.Error{},
Issues: []*gosec.Issue{
&issue,
@ -58,7 +59,7 @@ var _ = Describe("Formatter", func() {
})
Context("when converting to Sonarqube issues", func() {
It("it should parse the report info", func() {
data := &reportInfo{
data := &formatter.ReportInfo{
Errors: map[string][]gosec.Error{},
Issues: []*gosec.Issue{
{
@ -106,7 +107,7 @@ var _ = Describe("Formatter", func() {
})
It("it should parse the report info with files in subfolders", func() {
data := &reportInfo{
data := &formatter.ReportInfo{
Errors: map[string][]gosec.Error{},
Issues: []*gosec.Issue{
{
@ -153,7 +154,7 @@ var _ = Describe("Formatter", func() {
Expect(*issues).To(Equal(*want))
})
It("it should not parse the report info for files from other projects", func() {
data := &reportInfo{
data := &formatter.ReportInfo{
Errors: map[string][]gosec.Error{},
Issues: []*gosec.Issue{
{
@ -185,7 +186,7 @@ var _ = Describe("Formatter", func() {
})
It("it should parse the report info for multiple projects projects", func() {
data := &reportInfo{
data := &formatter.ReportInfo{
Errors: map[string][]gosec.Error{},
Issues: []*gosec.Issue{
{
@ -261,7 +262,7 @@ var _ = Describe("Formatter", func() {
It("preserves order of issues", func() {
issues := []*gosec.Issue{createIssueWithFileWhat("i1", "1"), createIssueWithFileWhat("i2", "2"), createIssueWithFileWhat("i3", "1")}
junitReport := createJUnitXMLStruct(&reportInfo{Issues: issues})
junitReport := createJUnitXMLStruct(&formatter.ReportInfo{Issues: issues})
testSuite := junitReport.Testsuites[0]
@ -454,11 +455,23 @@ var _ = Describe("Formatter", func() {
result := stripString(buf.String())
pattern := "rules\":[{\"id\":\"%s(CWE-%s)\""
expect := fmt.Sprintf(pattern, rule, cwe.ID)
ruleIDPattern := "\"id\":\"%s\""
expectedRule := fmt.Sprintf(ruleIDPattern, rule)
Expect(err).ShouldNot(HaveOccurred())
Expect(result).To(ContainSubstring(expect))
Expect(result).To(ContainSubstring(expectedRule))
cweURIPattern := "\"helpUri\":\"https://cwe.mitre.org/data/definitions/%s.html\""
expectedCweURI := fmt.Sprintf(cweURIPattern, cwe.ID)
Expect(err).ShouldNot(HaveOccurred())
Expect(result).To(ContainSubstring(expectedCweURI))
cweIDPattern := "\"id\":\"%s\""
expectedCweID := fmt.Sprintf(cweIDPattern, cwe.ID)
Expect(err).ShouldNot(HaveOccurred())
Expect(result).To(ContainSubstring(expectedCweID))
}
})
})

View file

@ -6,6 +6,7 @@ import (
"strconv"
"github.com/securego/gosec/v2"
"github.com/securego/gosec/v2/formatter"
)
type junitXMLReport struct {
@ -40,7 +41,7 @@ func generatePlaintext(issue *gosec.Issue) string {
", CWE: " + issue.Cwe.ID + ")\n" + "> " + htmlLib.EscapeString(issue.Code)
}
func createJUnitXMLStruct(data *reportInfo) junitXMLReport {
func createJUnitXMLStruct(data *formatter.ReportInfo) junitXMLReport {
var xmlReport junitXMLReport
testsuites := map[string]int{}

View file

@ -1,184 +0,0 @@
package output
import (
"fmt"
"strconv"
"strings"
"github.com/securego/gosec/v2"
)
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"`
DefaultConfiguration *sarifConfiguration `json:"defaultConfiguration"`
}
type sarifConfiguration struct {
Level sarifLevel `json:"level"`
}
type sarifArtifactLocation struct {
URI string `json:"uri"`
}
type sarifRegion struct {
StartLine uint64 `json:"startLine"`
EndLine uint64 `json:"endLine"`
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"`
Version string `json:"version"`
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://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.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()},
},
DefaultConfiguration: &sarifConfiguration{
Level: getSarifLevel(issue.Severity.String()),
},
}
}
// buildSarifLocation return SARIF location struct
func buildSarifLocation(issue *gosec.Issue, rootPaths []string) (*sarifLocation, error) {
var filePath string
lines := strings.Split(issue.Line, "-")
startLine, err := strconv.ParseUint(lines[0], 10, 64)
if err != nil {
return nil, err
}
endLine := startLine
if len(lines) > 1 {
endLine, err = strconv.ParseUint(lines[1], 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: startLine,
EndLine: endLine,
StartColumn: col,
EndColumn: col,
},
},
}
return location, nil
}
// From https://docs.oasis-open.org/sarif/sarif/v2.0/csprd02/sarif-v2.0-csprd02.html#_Toc10127839
// * "warning": The rule specified by ruleId was evaluated and a problem was found.
// * "error": The rule specified by ruleId was evaluated and a serious problem was found.
// * "note": The rule specified by ruleId was evaluated and a minor problem or an opportunity to improve the code was found.
func getSarifLevel(s string) sarifLevel {
switch s {
case "LOW":
return sarifWarning
case "MEDIUM":
return sarifError
case "HIGH":
return sarifError
default:
return sarifNote
}
}

View file

@ -2,6 +2,7 @@ package output
import (
"github.com/securego/gosec/v2"
"github.com/securego/gosec/v2/formatter"
"github.com/securego/gosec/v2/sonar"
"strconv"
"strings"
@ -12,7 +13,7 @@ const (
SonarqubeEffortMinutes = 5
)
func convertToSonarIssues(rootPaths []string, data *reportInfo) (*sonar.Report, error) {
func convertToSonarIssues(rootPaths []string, data *formatter.ReportInfo) (*sonar.Report, error) {
si := &sonar.Report{Issues: []*sonar.Issue{}}
for _, issue := range data.Issues {
sonarFilePath := parseFilePath(issue, rootPaths)

297
sarif/formatter.go Normal file
View file

@ -0,0 +1,297 @@
package sarif
import (
"fmt"
"github.com/google/uuid"
"runtime/debug"
"strconv"
"strings"
"github.com/securego/gosec/v2"
"github.com/securego/gosec/v2/cwe"
"github.com/securego/gosec/v2/formatter"
)
type sarifLevel string
const (
sarifNone = sarifLevel("none")
sarifNote = sarifLevel("note")
sarifWarning = sarifLevel("warning")
sarifError = sarifLevel("error")
cweAcronym = "CWE"
)
//ConvertToSarifReport Convert a gosec report to a Sarif Report
func ConvertToSarifReport(rootPaths []string, data *formatter.ReportInfo) (*Report, error) {
type rule struct {
index int
rule *ReportingDescriptor
}
rules := make([]*ReportingDescriptor, 0)
rulesIndices := make(map[string]rule)
lastRuleIndex := -1
results := []*Result{}
taxa := make([]*ReportingDescriptor, 0)
weaknesses := make(map[string]cwe.Weakness)
for _, issue := range data.Issues {
_, ok := weaknesses[issue.Cwe.ID]
if !ok {
weakness := cwe.Get(issue.Cwe.ID)
weaknesses[issue.Cwe.ID] = weakness
taxon := parseSarifTaxon(weakness)
taxa = append(taxa, taxon)
}
r, ok := rulesIndices[issue.RuleID]
if !ok {
lastRuleIndex++
r = rule{index: lastRuleIndex, rule: parseSarifRule(issue)}
rulesIndices[issue.RuleID] = r
rules = append(rules, r.rule)
}
location, err := parseSarifLocation(issue, rootPaths)
if err != nil {
return nil, err
}
result := buildSarifResult(r.rule.ID, r.index, issue, []*Location{location})
results = append(results, result)
}
tool := buildSarifTool(buildSarifDriver(rules))
run := buildSarifRun(results, buildSarifTaxonomies(taxa), tool)
return buildSarifReport(run), nil
}
func buildSarifResult(ruleID string, index int, issue *gosec.Issue, locations []*Location) *Result {
return &Result{
RuleID: ruleID,
RuleIndex: index,
Level: getSarifLevel(issue.Severity.String()),
Message: &Message{
Text: issue.What,
},
Locations: locations,
}
}
// buildSarifReport return SARIF report struct
func buildSarifReport(run *Run) *Report {
return &Report{
Version: "2.1.0",
Schema: "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json",
Runs: []*Run{run},
}
}
// parseSarifRule return SARIF rule field struct
func parseSarifRule(issue *gosec.Issue) *ReportingDescriptor {
return &ReportingDescriptor{
ID: issue.RuleID,
Name: issue.What,
ShortDescription: &MultiformatMessageString{
Text: issue.What,
},
FullDescription: &MultiformatMessageString{
Text: issue.What,
},
Help: &MultiformatMessageString{
Text: fmt.Sprintf("%s\nSeverity: %s\nConfidence: %s\n", issue.What, issue.Severity.String(), issue.Confidence.String()),
},
Properties: &PropertyBag{
Tags: []string{"security", issue.Severity.String()},
AdditionalProperties: map[string]interface{}{
"precision": strings.ToLower(issue.Confidence.String()),
},
},
DefaultConfiguration: &ReportingConfiguration{
Level: getSarifLevel(issue.Severity.String()),
},
Relationships: []*ReportingDescriptorRelationship{
buildSarifReportingDescriptorRelationship(issue.Cwe.ID),
},
}
}
func buildSarifReportingDescriptorRelationship(weaknessID string) *ReportingDescriptorRelationship {
return &ReportingDescriptorRelationship{
Target: &ReportingDescriptorReference{
ID: weaknessID,
GUID: uuid3(cweAcronym + weaknessID),
ToolComponent: &ToolComponentReference{
Name: cweAcronym,
},
},
Kinds: []string{"superset"},
}
}
func buildSarifTool(driver *ToolComponent) *Tool {
return &Tool{
Driver: driver,
}
}
func buildSarifTaxonomies(taxa []*ReportingDescriptor) []*ToolComponent {
return []*ToolComponent{
buildCWETaxonomy("4.4", taxa),
}
}
func buildCWETaxonomy(version string, taxa []*ReportingDescriptor) *ToolComponent {
return &ToolComponent{
Name: cweAcronym,
Version: version,
ReleaseDateUtc: "2021-03-15",
InformationURI: fmt.Sprintf("https://cwe.mitre.org/data/published/cwe_v%s.pdf/", version),
DownloadURI: fmt.Sprintf("https://cwe.mitre.org/data/xml/cwec_v%s.xml.zip", version),
Organization: "MITRE",
ShortDescription: &MultiformatMessageString{
Text: "The MITRE Common Weakness Enumeration",
},
GUID: uuid3(cweAcronym),
IsComprehensive: true,
MinimumRequiredLocalizedDataSemanticVersion: version,
Taxa: taxa,
}
}
func parseSarifTaxon(weakness cwe.Weakness) *ReportingDescriptor {
return &ReportingDescriptor{
ID: weakness.ID,
Name: weakness.Name,
GUID: uuid3(cweAcronym + weakness.ID),
HelpURI: weakness.URL(),
ShortDescription: &MultiformatMessageString{
Text: weakness.Description,
},
}
}
func buildSarifDriver(rules []*ReportingDescriptor) *ToolComponent {
buildInfo, ok := debug.ReadBuildInfo()
var gosecVersion string
if ok {
gosecVersion = buildInfo.Main.Version[1:]
} else {
gosecVersion = "devel"
}
return &ToolComponent{
Name: "gosec",
Version: gosecVersion,
SupportedTaxonomies: []*ToolComponentReference{
{Name: cweAcronym, GUID: uuid3(cweAcronym)},
},
InformationURI: "https://github.com/securego/gosec/",
Rules: rules,
}
}
func uuid3(value string) string {
return uuid.NewMD5(uuid.Nil, []byte(value)).String()
}
func buildSarifRun(results []*Result, taxonomies []*ToolComponent, tool *Tool) *Run {
return &Run{
Results: results,
Taxonomies: taxonomies,
Tool: tool,
}
}
// parseSarifLocation return SARIF location struct
func parseSarifLocation(issue *gosec.Issue, rootPaths []string) (*Location, error) {
region, err := parseSarifRegion(issue)
if err != nil {
return nil, err
}
artifactLocation := parseSarifArtifactLocation(issue, rootPaths)
return buildSarifLocation(buildSarifPhysicalLocation(artifactLocation, region)), nil
}
func buildSarifLocation(physicalLocation *PhysicalLocation) *Location {
return &Location{
PhysicalLocation: physicalLocation,
}
}
func buildSarifPhysicalLocation(artifactLocation *ArtifactLocation, region *Region) *PhysicalLocation {
return &PhysicalLocation{
ArtifactLocation: artifactLocation,
Region: region,
}
}
func parseSarifArtifactLocation(issue *gosec.Issue, rootPaths []string) *ArtifactLocation {
var filePath string
for _, rootPath := range rootPaths {
if strings.HasPrefix(issue.File, rootPath) {
filePath = strings.Replace(issue.File, rootPath+"/", "", 1)
}
}
return buildSarifArtifactLocation(filePath)
}
func buildSarifArtifactLocation(uri string) *ArtifactLocation {
return &ArtifactLocation{
URI: uri,
}
}
func parseSarifRegion(issue *gosec.Issue) (*Region, error) {
lines := strings.Split(issue.Line, "-")
startLine, err := strconv.Atoi(lines[0])
if err != nil {
return nil, err
}
endLine := startLine
if len(lines) > 1 {
endLine, err = strconv.Atoi(lines[1])
if err != nil {
return nil, err
}
}
col, err := strconv.Atoi(issue.Col)
if err != nil {
return nil, err
}
return buildSarifRegion(startLine, endLine, col), nil
}
func buildSarifRegion(startLine int, endLine int, col int) *Region {
return &Region{
StartLine: startLine,
EndLine: endLine,
StartColumn: col,
EndColumn: col,
SourceLanguage: "go",
}
}
// From https://docs.oasis-open.org/sarif/sarif/v2.0/csprd02/sarif-v2.0-csprd02.html#_Toc10127839
// * "warning": The rule specified by ruleId was evaluated and a problem was found.
// * "error": The rule specified by ruleId was evaluated and a serious problem was found.
// * "note": The rule specified by ruleId was evaluated and a minor problem or an opportunity to improve the code was found.
// * "none": The concept of “severity” does not apply to this result because the kind property (§3.27.9) has a value other than "fail".
func getSarifLevel(s string) sarifLevel {
switch s {
case "LOW":
return sarifWarning
case "MEDIUM":
return sarifError
case "HIGH":
return sarifError
default:
return sarifNote
}
}

1516
sarif/types.go Normal file

File diff suppressed because it is too large Load diff