gosec/report/formatter_test.go
Dimitar Banchev 58e4fccc13 Split the G401 rule into two separate ones
Now the G401 rule is split into hashing and encryption algorithms.

G401 is responsible for checking the usage of MD5 and SHA1, with corresponding CWE of 328.
And G405(New rule) is responsible for checking the usege of DES and RC4, with corresponding CWE of 327.
2024-06-24 15:25:54 +02:00

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", 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", "G405", "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":[{`))
})
})
})