mirror of
https://github.com/securego/gosec.git
synced 2024-12-24 03:25:53 +00:00
d6aeaad931
Signed-off-by: Matthieu MOREL <matthieu.morel35@gmail.com>
541 lines
16 KiB
Go
541 lines
16 KiB
Go
package report
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"strings"
|
|
|
|
. "github.com/onsi/ginkgo/v2"
|
|
. "github.com/onsi/gomega"
|
|
"gopkg.in/yaml.v3"
|
|
|
|
"github.com/securego/gosec/v2"
|
|
"github.com/securego/gosec/v2/cwe"
|
|
"github.com/securego/gosec/v2/issue"
|
|
"github.com/securego/gosec/v2/report/junit"
|
|
"github.com/securego/gosec/v2/report/sonar"
|
|
)
|
|
|
|
func createIssueWithFileWhat(file, what string) *issue.Issue {
|
|
issue := createIssue("i1", issue.GetCweByRule("G101"))
|
|
issue.File = file
|
|
issue.What = what
|
|
return &issue
|
|
}
|
|
|
|
func createIssue(ruleID string, weakness *cwe.Weakness) issue.Issue {
|
|
return issue.Issue{
|
|
File: "/home/src/project/test.go",
|
|
Line: "1",
|
|
Col: "1",
|
|
RuleID: ruleID,
|
|
What: "test",
|
|
Confidence: issue.High,
|
|
Severity: issue.High,
|
|
Code: "1: testcode",
|
|
Cwe: weakness,
|
|
}
|
|
}
|
|
|
|
func createReportInfo(rule string, weakness *cwe.Weakness) gosec.ReportInfo {
|
|
newissue := createIssue(rule, weakness)
|
|
metrics := gosec.Metrics{}
|
|
return gosec.ReportInfo{
|
|
Errors: map[string][]gosec.Error{},
|
|
Issues: []*issue.Issue{
|
|
&newissue,
|
|
},
|
|
Stats: &metrics,
|
|
}
|
|
}
|
|
|
|
func stripString(str string) string {
|
|
ret := strings.Replace(str, "\n", "", -1)
|
|
ret = strings.Replace(ret, " ", "", -1)
|
|
ret = strings.Replace(ret, "\t", "", -1)
|
|
return ret
|
|
}
|
|
|
|
var _ = Describe("Formatter", func() {
|
|
BeforeEach(func() {
|
|
})
|
|
Context("when converting to Sonarqube issues", func() {
|
|
It("it should parse the report info", func() {
|
|
data := &gosec.ReportInfo{
|
|
Errors: map[string][]gosec.Error{},
|
|
Issues: []*issue.Issue{
|
|
{
|
|
Severity: 2,
|
|
Confidence: 0,
|
|
RuleID: "test",
|
|
What: "test",
|
|
File: "/home/src/project/test.go",
|
|
Code: "",
|
|
Line: "1-2",
|
|
},
|
|
},
|
|
Stats: &gosec.Metrics{
|
|
NumFiles: 0,
|
|
NumLines: 0,
|
|
NumNosec: 0,
|
|
NumFound: 0,
|
|
},
|
|
}
|
|
want := &sonar.Report{
|
|
Issues: []*sonar.Issue{
|
|
{
|
|
EngineID: "gosec",
|
|
RuleID: "test",
|
|
PrimaryLocation: &sonar.Location{
|
|
Message: "test",
|
|
FilePath: "test.go",
|
|
TextRange: &sonar.TextRange{
|
|
StartLine: 1,
|
|
EndLine: 2,
|
|
},
|
|
},
|
|
Type: "VULNERABILITY",
|
|
Severity: "BLOCKER",
|
|
EffortMinutes: sonar.EffortMinutes,
|
|
},
|
|
},
|
|
}
|
|
|
|
rootPath := "/home/src/project"
|
|
|
|
issues, err := sonar.GenerateReport([]string{rootPath}, data)
|
|
Expect(err).ShouldNot(HaveOccurred())
|
|
Expect(*issues).To(Equal(*want))
|
|
})
|
|
|
|
It("it should parse the report info with files in subfolders", func() {
|
|
data := &gosec.ReportInfo{
|
|
Errors: map[string][]gosec.Error{},
|
|
Issues: []*issue.Issue{
|
|
{
|
|
Severity: 2,
|
|
Confidence: 0,
|
|
RuleID: "test",
|
|
What: "test",
|
|
File: "/home/src/project/subfolder/test.go",
|
|
Code: "",
|
|
Line: "1-2",
|
|
},
|
|
},
|
|
Stats: &gosec.Metrics{
|
|
NumFiles: 0,
|
|
NumLines: 0,
|
|
NumNosec: 0,
|
|
NumFound: 0,
|
|
},
|
|
}
|
|
want := &sonar.Report{
|
|
Issues: []*sonar.Issue{
|
|
{
|
|
EngineID: "gosec",
|
|
RuleID: "test",
|
|
PrimaryLocation: &sonar.Location{
|
|
Message: "test",
|
|
FilePath: "subfolder/test.go",
|
|
TextRange: &sonar.TextRange{
|
|
StartLine: 1,
|
|
EndLine: 2,
|
|
},
|
|
},
|
|
Type: "VULNERABILITY",
|
|
Severity: "BLOCKER",
|
|
EffortMinutes: sonar.EffortMinutes,
|
|
},
|
|
},
|
|
}
|
|
|
|
rootPath := "/home/src/project"
|
|
|
|
issues, err := sonar.GenerateReport([]string{rootPath}, data)
|
|
Expect(err).ShouldNot(HaveOccurred())
|
|
Expect(*issues).To(Equal(*want))
|
|
})
|
|
It("it should not parse the report info for files from other projects", func() {
|
|
data := &gosec.ReportInfo{
|
|
Errors: map[string][]gosec.Error{},
|
|
Issues: []*issue.Issue{
|
|
{
|
|
Severity: 2,
|
|
Confidence: 0,
|
|
RuleID: "test",
|
|
What: "test",
|
|
File: "/home/src/project1/test.go",
|
|
Code: "",
|
|
Line: "1-2",
|
|
},
|
|
},
|
|
Stats: &gosec.Metrics{
|
|
NumFiles: 0,
|
|
NumLines: 0,
|
|
NumNosec: 0,
|
|
NumFound: 0,
|
|
},
|
|
}
|
|
want := &sonar.Report{
|
|
Issues: []*sonar.Issue{},
|
|
}
|
|
|
|
rootPath := "/home/src/project2"
|
|
|
|
issues, err := sonar.GenerateReport([]string{rootPath}, data)
|
|
Expect(err).ShouldNot(HaveOccurred())
|
|
Expect(*issues).To(Equal(*want))
|
|
})
|
|
|
|
It("it should parse the report info for multiple projects projects", func() {
|
|
data := &gosec.ReportInfo{
|
|
Errors: map[string][]gosec.Error{},
|
|
Issues: []*issue.Issue{
|
|
{
|
|
Severity: 2,
|
|
Confidence: 0,
|
|
RuleID: "test",
|
|
What: "test",
|
|
File: "/home/src/project1/test-project1.go",
|
|
Code: "",
|
|
Line: "1-2",
|
|
},
|
|
{
|
|
Severity: 2,
|
|
Confidence: 0,
|
|
RuleID: "test",
|
|
What: "test",
|
|
File: "/home/src/project2/test-project2.go",
|
|
Code: "",
|
|
Line: "1-2",
|
|
},
|
|
},
|
|
Stats: &gosec.Metrics{
|
|
NumFiles: 0,
|
|
NumLines: 0,
|
|
NumNosec: 0,
|
|
NumFound: 0,
|
|
},
|
|
}
|
|
want := &sonar.Report{
|
|
Issues: []*sonar.Issue{
|
|
{
|
|
EngineID: "gosec",
|
|
RuleID: "test",
|
|
PrimaryLocation: &sonar.Location{
|
|
Message: "test",
|
|
FilePath: "test-project1.go",
|
|
TextRange: &sonar.TextRange{
|
|
StartLine: 1,
|
|
EndLine: 2,
|
|
},
|
|
},
|
|
Type: "VULNERABILITY",
|
|
Severity: "BLOCKER",
|
|
EffortMinutes: sonar.EffortMinutes,
|
|
},
|
|
{
|
|
EngineID: "gosec",
|
|
RuleID: "test",
|
|
PrimaryLocation: &sonar.Location{
|
|
Message: "test",
|
|
FilePath: "test-project2.go",
|
|
TextRange: &sonar.TextRange{
|
|
StartLine: 1,
|
|
EndLine: 2,
|
|
},
|
|
},
|
|
Type: "VULNERABILITY",
|
|
Severity: "BLOCKER",
|
|
EffortMinutes: sonar.EffortMinutes,
|
|
},
|
|
},
|
|
}
|
|
|
|
rootPaths := []string{"/home/src/project1", "/home/src/project2"}
|
|
|
|
issues, err := sonar.GenerateReport(rootPaths, data)
|
|
Expect(err).ShouldNot(HaveOccurred())
|
|
Expect(*issues).To(Equal(*want))
|
|
})
|
|
})
|
|
|
|
Context("When using junit", func() {
|
|
It("preserves order of issues", func() {
|
|
issues := []*issue.Issue{createIssueWithFileWhat("i1", "1"), createIssueWithFileWhat("i2", "2"), createIssueWithFileWhat("i3", "1")}
|
|
|
|
junitReport := junit.GenerateReport(&gosec.ReportInfo{Issues: issues})
|
|
|
|
testSuite := junitReport.Testsuites[0]
|
|
|
|
Expect(testSuite.Testcases[0].Name).To(Equal(issues[0].File))
|
|
Expect(testSuite.Testcases[1].Name).To(Equal(issues[2].File))
|
|
|
|
testSuite = junitReport.Testsuites[1]
|
|
Expect(testSuite.Testcases[0].Name).To(Equal(issues[1].File))
|
|
})
|
|
})
|
|
Context("When using different report formats", func() {
|
|
grules := []string{
|
|
"G101", "G102", "G103", "G104", "G106", "G107", "G109",
|
|
"G110", "G111", "G112", "G113", "G201", "G202", "G203",
|
|
"G204", "G301", "G302", "G303", "G304", "G305", "G401",
|
|
"G402", "G403", "G404", "G501", "G502", "G503", "G504",
|
|
"G505", "G601",
|
|
}
|
|
|
|
It("csv formatted report should contain the CWE mapping", func() {
|
|
for _, rule := range grules {
|
|
cwe := issue.GetCweByRule(rule)
|
|
newissue := createIssue(rule, cwe)
|
|
errors := map[string][]gosec.Error{}
|
|
|
|
buf := new(bytes.Buffer)
|
|
reportInfo := gosec.NewReportInfo([]*issue.Issue{&newissue}, &gosec.Metrics{}, errors)
|
|
err := CreateReport(buf, "csv", false, []string{}, reportInfo)
|
|
Expect(err).ShouldNot(HaveOccurred())
|
|
pattern := "/home/src/project/test.go,1,test,HIGH,HIGH,1: testcode,CWE-%s\n"
|
|
expect := fmt.Sprintf(pattern, cwe.ID)
|
|
Expect(buf.String()).To(Equal(expect))
|
|
}
|
|
})
|
|
It("xml formatted report should contain the CWE mapping", func() {
|
|
for _, rule := range grules {
|
|
cwe := issue.GetCweByRule(rule)
|
|
newissue := createIssue(rule, cwe)
|
|
errors := map[string][]gosec.Error{}
|
|
|
|
buf := new(bytes.Buffer)
|
|
reportInfo := gosec.NewReportInfo([]*issue.Issue{&newissue}, &gosec.Metrics{NumFiles: 0, NumLines: 0, NumNosec: 0, NumFound: 0}, errors).WithVersion("v2.7.0")
|
|
err := CreateReport(buf, "xml", false, []string{}, reportInfo)
|
|
Expect(err).ShouldNot(HaveOccurred())
|
|
pattern := "Results:\n\n\n[/home/src/project/test.go:1] - %s (CWE-%s): test (Confidence: HIGH, Severity: HIGH)\n > 1: testcode\n\n\n\nSummary:\n Gosec : v2.7.0\n Files : 0\n Lines : 0\n Nosec : 0\n Issues : 0\n\n"
|
|
expect := fmt.Sprintf(pattern, rule, cwe.ID)
|
|
Expect(buf.String()).To(Equal(expect))
|
|
}
|
|
})
|
|
It("json formatted report should contain the CWE mapping", func() {
|
|
for _, rule := range grules {
|
|
cwe := issue.GetCweByRule(rule)
|
|
newissue := createIssue(rule, cwe)
|
|
errors := map[string][]gosec.Error{}
|
|
|
|
data := createReportInfo(rule, cwe)
|
|
|
|
expect := new(bytes.Buffer)
|
|
enc := json.NewEncoder(expect)
|
|
err := enc.Encode(data)
|
|
Expect(err).ShouldNot(HaveOccurred())
|
|
buf := new(bytes.Buffer)
|
|
reportInfo := gosec.NewReportInfo([]*issue.Issue{&newissue}, &gosec.Metrics{}, errors)
|
|
err = CreateReport(buf, "json", false, []string{}, reportInfo)
|
|
Expect(err).ShouldNot(HaveOccurred())
|
|
result := stripString(buf.String())
|
|
expectation := stripString(expect.String())
|
|
Expect(result).To(Equal(expectation))
|
|
}
|
|
})
|
|
It("html formatted report should contain the CWE mapping", func() {
|
|
for _, rule := range grules {
|
|
cwe := issue.GetCweByRule(rule)
|
|
newissue := createIssue(rule, cwe)
|
|
errors := map[string][]gosec.Error{}
|
|
|
|
data := createReportInfo(rule, cwe)
|
|
|
|
expect := new(bytes.Buffer)
|
|
enc := json.NewEncoder(expect)
|
|
err := enc.Encode(data)
|
|
Expect(err).ShouldNot(HaveOccurred())
|
|
buf := new(bytes.Buffer)
|
|
reportInfo := gosec.NewReportInfo([]*issue.Issue{&newissue}, &gosec.Metrics{}, errors)
|
|
err = CreateReport(buf, "html", false, []string{}, reportInfo)
|
|
Expect(err).ShouldNot(HaveOccurred())
|
|
result := stripString(buf.String())
|
|
expectation := stripString(expect.String())
|
|
Expect(result).To(ContainSubstring(expectation))
|
|
}
|
|
})
|
|
It("yaml formatted report should contain the CWE mapping", func() {
|
|
for _, rule := range grules {
|
|
cwe := issue.GetCweByRule(rule)
|
|
newissue := createIssue(rule, cwe)
|
|
errors := map[string][]gosec.Error{}
|
|
|
|
data := createReportInfo(rule, cwe)
|
|
|
|
expect := new(bytes.Buffer)
|
|
enc := yaml.NewEncoder(expect)
|
|
err := enc.Encode(data)
|
|
Expect(err).ShouldNot(HaveOccurred())
|
|
buf := new(bytes.Buffer)
|
|
reportInfo := gosec.NewReportInfo([]*issue.Issue{&newissue}, &gosec.Metrics{}, errors)
|
|
err = CreateReport(buf, "yaml", false, []string{}, reportInfo)
|
|
Expect(err).ShouldNot(HaveOccurred())
|
|
result := stripString(buf.String())
|
|
expectation := stripString(expect.String())
|
|
Expect(result).To(ContainSubstring(expectation))
|
|
}
|
|
})
|
|
It("junit-xml formatted report should contain the CWE mapping", func() {
|
|
for _, rule := range grules {
|
|
cwe := issue.GetCweByRule(rule)
|
|
newissue := createIssue(rule, cwe)
|
|
errors := map[string][]gosec.Error{}
|
|
|
|
data := createReportInfo(rule, cwe)
|
|
|
|
expect := new(bytes.Buffer)
|
|
enc := yaml.NewEncoder(expect)
|
|
err := enc.Encode(data)
|
|
Expect(err).ShouldNot(HaveOccurred())
|
|
buf := new(bytes.Buffer)
|
|
reportInfo := gosec.NewReportInfo([]*issue.Issue{&newissue}, &gosec.Metrics{}, errors)
|
|
err = CreateReport(buf, "junit-xml", false, []string{}, reportInfo)
|
|
Expect(err).ShouldNot(HaveOccurred())
|
|
expectation := stripString(fmt.Sprintf("[/home/src/project/test.go:1] - test (Confidence: 2, Severity: 2, CWE: %s)", cwe.ID))
|
|
result := stripString(buf.String())
|
|
Expect(result).To(ContainSubstring(expectation))
|
|
}
|
|
})
|
|
It("text formatted report should contain the CWE mapping", func() {
|
|
for _, rule := range grules {
|
|
cwe := issue.GetCweByRule(rule)
|
|
newissue := createIssue(rule, cwe)
|
|
errors := map[string][]gosec.Error{}
|
|
|
|
data := createReportInfo(rule, cwe)
|
|
|
|
expect := new(bytes.Buffer)
|
|
enc := yaml.NewEncoder(expect)
|
|
err := enc.Encode(data)
|
|
Expect(err).ShouldNot(HaveOccurred())
|
|
buf := new(bytes.Buffer)
|
|
reportInfo := gosec.NewReportInfo([]*issue.Issue{&newissue}, &gosec.Metrics{}, errors)
|
|
err = CreateReport(buf, "text", false, []string{}, reportInfo)
|
|
Expect(err).ShouldNot(HaveOccurred())
|
|
expectation := stripString(fmt.Sprintf("[/home/src/project/test.go:1] - %s (CWE-%s): test (Confidence: HIGH, Severity: HIGH)", rule, cwe.ID))
|
|
result := stripString(buf.String())
|
|
Expect(result).To(ContainSubstring(expectation))
|
|
}
|
|
})
|
|
It("sonarqube formatted report shouldn't contain the CWE mapping", func() {
|
|
for _, rule := range grules {
|
|
cwe := issue.GetCweByRule(rule)
|
|
newissue := createIssue(rule, cwe)
|
|
errors := map[string][]gosec.Error{}
|
|
buf := new(bytes.Buffer)
|
|
reportInfo := gosec.NewReportInfo([]*issue.Issue{&newissue}, &gosec.Metrics{}, errors)
|
|
err := CreateReport(buf, "sonarqube", false, []string{"/home/src/project"}, reportInfo)
|
|
Expect(err).ShouldNot(HaveOccurred())
|
|
|
|
result := stripString(buf.String())
|
|
|
|
expect := new(bytes.Buffer)
|
|
enc := json.NewEncoder(expect)
|
|
err = enc.Encode(cwe)
|
|
Expect(err).ShouldNot(HaveOccurred())
|
|
|
|
expectation := stripString(expect.String())
|
|
Expect(result).ShouldNot(ContainSubstring(expectation))
|
|
}
|
|
})
|
|
It("golint formatted report should contain the CWE mapping", func() {
|
|
for _, rule := range grules {
|
|
cwe := issue.GetCweByRule(rule)
|
|
newissue := createIssue(rule, cwe)
|
|
errors := map[string][]gosec.Error{}
|
|
|
|
buf := new(bytes.Buffer)
|
|
reportInfo := gosec.NewReportInfo([]*issue.Issue{&newissue}, &gosec.Metrics{}, errors)
|
|
err := CreateReport(buf, "golint", false, []string{}, reportInfo)
|
|
Expect(err).ShouldNot(HaveOccurred())
|
|
pattern := "/home/src/project/test.go:1:1: [CWE-%s] test (Rule:%s, Severity:HIGH, Confidence:HIGH)\n"
|
|
expect := fmt.Sprintf(pattern, cwe.ID, rule)
|
|
Expect(buf.String()).To(Equal(expect))
|
|
}
|
|
})
|
|
It("sarif formatted report should contain the CWE mapping", func() {
|
|
for _, rule := range grules {
|
|
cwe := issue.GetCweByRule(rule)
|
|
newissue := createIssue(rule, cwe)
|
|
errors := map[string][]gosec.Error{}
|
|
|
|
buf := new(bytes.Buffer)
|
|
reportInfo := gosec.NewReportInfo([]*issue.Issue{&newissue}, &gosec.Metrics{}, errors).WithVersion("v2.7.0")
|
|
err := CreateReport(buf, "sarif", false, []string{}, reportInfo)
|
|
Expect(err).ShouldNot(HaveOccurred())
|
|
|
|
result := stripString(buf.String())
|
|
|
|
ruleIDPattern := "\"id\":\"%s\""
|
|
expectedRule := fmt.Sprintf(ruleIDPattern, rule)
|
|
Expect(err).ShouldNot(HaveOccurred())
|
|
|
|
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))
|
|
}
|
|
})
|
|
})
|
|
|
|
Context("When converting suppressed issues", func() {
|
|
ruleID := "G101"
|
|
cwe := issue.GetCweByRule(ruleID)
|
|
suppressions := []issue.SuppressionInfo{
|
|
{
|
|
Kind: "kind",
|
|
Justification: "justification",
|
|
},
|
|
}
|
|
suppressedIssue := createIssue(ruleID, cwe)
|
|
suppressedIssue.WithSuppressions(suppressions)
|
|
|
|
It("text formatted report should contain the suppressed issues", func() {
|
|
errors := map[string][]gosec.Error{}
|
|
reportInfo := gosec.NewReportInfo([]*issue.Issue{&suppressedIssue}, &gosec.Metrics{}, errors)
|
|
|
|
buf := new(bytes.Buffer)
|
|
err := CreateReport(buf, "text", false, []string{}, reportInfo)
|
|
Expect(err).ShouldNot(HaveOccurred())
|
|
|
|
result := stripString(buf.String())
|
|
Expect(result).To(ContainSubstring("Results:Summary"))
|
|
})
|
|
|
|
It("sarif formatted report should contain the suppressed issues", func() {
|
|
errors := map[string][]gosec.Error{}
|
|
reportInfo := gosec.NewReportInfo([]*issue.Issue{&suppressedIssue}, &gosec.Metrics{}, errors)
|
|
|
|
buf := new(bytes.Buffer)
|
|
err := CreateReport(buf, "sarif", false, []string{}, reportInfo)
|
|
Expect(err).ShouldNot(HaveOccurred())
|
|
|
|
result := stripString(buf.String())
|
|
Expect(result).To(ContainSubstring(`"results":[{`))
|
|
})
|
|
|
|
It("json formatted report should contain the suppressed issues", func() {
|
|
errors := map[string][]gosec.Error{}
|
|
reportInfo := gosec.NewReportInfo([]*issue.Issue{&suppressedIssue}, &gosec.Metrics{}, errors)
|
|
|
|
buf := new(bytes.Buffer)
|
|
err := CreateReport(buf, "json", false, []string{}, reportInfo)
|
|
Expect(err).ShouldNot(HaveOccurred())
|
|
|
|
result := stripString(buf.String())
|
|
Expect(result).To(ContainSubstring(`"Issues":[{`))
|
|
})
|
|
})
|
|
})
|