mirror of
https://github.com/securego/gosec.git
synced 2026-06-20 00:15:59 +02:00
Fix the sonar report to follow the latest schema (#1507)
Signed-off-by: Cosmin Cojocar <cosmin@cojocar.ch>
This commit is contained in:
+50
-16
@@ -83,10 +83,24 @@ var _ = Describe("Formatter", func() {
|
||||
},
|
||||
}
|
||||
want := &sonar.Report{
|
||||
Rules: []*sonar.Rule{
|
||||
{
|
||||
ID: "test",
|
||||
Name: "test",
|
||||
Description: "test",
|
||||
EngineID: "gosec",
|
||||
CleanCodeAttribute: "TRUSTWORTHY",
|
||||
Impacts: []*sonar.Impact{
|
||||
{
|
||||
SoftwareQuality: "SECURITY",
|
||||
Severity: "HIGH",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Issues: []*sonar.Issue{
|
||||
{
|
||||
EngineID: "gosec",
|
||||
RuleID: "test",
|
||||
RuleID: "test",
|
||||
PrimaryLocation: &sonar.Location{
|
||||
Message: "test",
|
||||
FilePath: "test.go",
|
||||
@@ -95,8 +109,6 @@ var _ = Describe("Formatter", func() {
|
||||
EndLine: 2,
|
||||
},
|
||||
},
|
||||
Type: "VULNERABILITY",
|
||||
Severity: "BLOCKER",
|
||||
EffortMinutes: sonar.EffortMinutes,
|
||||
},
|
||||
},
|
||||
@@ -131,10 +143,24 @@ var _ = Describe("Formatter", func() {
|
||||
},
|
||||
}
|
||||
want := &sonar.Report{
|
||||
Rules: []*sonar.Rule{
|
||||
{
|
||||
ID: "test",
|
||||
Name: "test",
|
||||
Description: "test",
|
||||
EngineID: "gosec",
|
||||
CleanCodeAttribute: "TRUSTWORTHY",
|
||||
Impacts: []*sonar.Impact{
|
||||
{
|
||||
SoftwareQuality: "SECURITY",
|
||||
Severity: "HIGH",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Issues: []*sonar.Issue{
|
||||
{
|
||||
EngineID: "gosec",
|
||||
RuleID: "test",
|
||||
RuleID: "test",
|
||||
PrimaryLocation: &sonar.Location{
|
||||
Message: "test",
|
||||
FilePath: "subfolder/test.go",
|
||||
@@ -143,8 +169,6 @@ var _ = Describe("Formatter", func() {
|
||||
EndLine: 2,
|
||||
},
|
||||
},
|
||||
Type: "VULNERABILITY",
|
||||
Severity: "BLOCKER",
|
||||
EffortMinutes: sonar.EffortMinutes,
|
||||
},
|
||||
},
|
||||
@@ -178,6 +202,7 @@ var _ = Describe("Formatter", func() {
|
||||
},
|
||||
}
|
||||
want := &sonar.Report{
|
||||
Rules: []*sonar.Rule{},
|
||||
Issues: []*sonar.Issue{},
|
||||
}
|
||||
|
||||
@@ -219,10 +244,24 @@ var _ = Describe("Formatter", func() {
|
||||
},
|
||||
}
|
||||
want := &sonar.Report{
|
||||
Rules: []*sonar.Rule{
|
||||
{
|
||||
ID: "test",
|
||||
Name: "test",
|
||||
Description: "test",
|
||||
EngineID: "gosec",
|
||||
CleanCodeAttribute: "TRUSTWORTHY",
|
||||
Impacts: []*sonar.Impact{
|
||||
{
|
||||
SoftwareQuality: "SECURITY",
|
||||
Severity: "HIGH",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Issues: []*sonar.Issue{
|
||||
{
|
||||
EngineID: "gosec",
|
||||
RuleID: "test",
|
||||
RuleID: "test",
|
||||
PrimaryLocation: &sonar.Location{
|
||||
Message: "test",
|
||||
FilePath: "test-project1.go",
|
||||
@@ -231,13 +270,10 @@ var _ = Describe("Formatter", func() {
|
||||
EndLine: 2,
|
||||
},
|
||||
},
|
||||
Type: "VULNERABILITY",
|
||||
Severity: "BLOCKER",
|
||||
EffortMinutes: sonar.EffortMinutes,
|
||||
},
|
||||
{
|
||||
EngineID: "gosec",
|
||||
RuleID: "test",
|
||||
RuleID: "test",
|
||||
PrimaryLocation: &sonar.Location{
|
||||
Message: "test",
|
||||
FilePath: "test-project2.go",
|
||||
@@ -246,8 +282,6 @@ var _ = Describe("Formatter", func() {
|
||||
EndLine: 2,
|
||||
},
|
||||
},
|
||||
Type: "VULNERABILITY",
|
||||
Severity: "BLOCKER",
|
||||
EffortMinutes: sonar.EffortMinutes,
|
||||
},
|
||||
},
|
||||
|
||||
+21
-4
@@ -18,13 +18,30 @@ func NewTextRange(startLine int, endLine int) *TextRange {
|
||||
}
|
||||
|
||||
// NewIssue instantiate an Issue
|
||||
func NewIssue(engineID string, ruleID string, primaryLocation *Location, issueType string, severity string, effortMinutes int) *Issue {
|
||||
func NewIssue(ruleID string, primaryLocation *Location, effortMinutes int) *Issue {
|
||||
return &Issue{
|
||||
EngineID: engineID,
|
||||
RuleID: ruleID,
|
||||
PrimaryLocation: primaryLocation,
|
||||
Type: issueType,
|
||||
Severity: severity,
|
||||
EffortMinutes: effortMinutes,
|
||||
}
|
||||
}
|
||||
|
||||
// NewImpact instantiate an Impact.
|
||||
func NewImpact(softwareQuality string, severity string) *Impact {
|
||||
return &Impact{
|
||||
SoftwareQuality: softwareQuality,
|
||||
Severity: severity,
|
||||
}
|
||||
}
|
||||
|
||||
// NewRule instantiate a Rule.
|
||||
func NewRule(id string, name string, description string, engineID string, cleanCodeAttribute string, impacts []*Impact) *Rule {
|
||||
return &Rule{
|
||||
ID: id,
|
||||
Name: name,
|
||||
Description: description,
|
||||
EngineID: engineID,
|
||||
CleanCodeAttribute: cleanCodeAttribute,
|
||||
Impacts: impacts,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,16 +6,24 @@ import (
|
||||
|
||||
"github.com/securego/gosec/v2"
|
||||
"github.com/securego/gosec/v2/issue"
|
||||
"github.com/securego/gosec/v2/rules"
|
||||
)
|
||||
|
||||
const (
|
||||
// EffortMinutes effort to fix in minutes
|
||||
EffortMinutes = 5
|
||||
|
||||
sonarEngineID = "gosec"
|
||||
sonarSoftwareQuality = "SECURITY"
|
||||
sonarCleanCodeAttribute = "TRUSTWORTHY"
|
||||
)
|
||||
|
||||
// GenerateReport Convert a gosec report to a Sonar Report
|
||||
func GenerateReport(rootPaths []string, data *gosec.ReportInfo) (*Report, error) {
|
||||
si := &Report{Issues: []*Issue{}}
|
||||
si := &Report{Rules: []*Rule{}, Issues: []*Issue{}}
|
||||
ruleDefinitions := rules.Generate(false).Rules
|
||||
ruleIndex := make(map[string]*Rule)
|
||||
|
||||
for _, issue := range data.Issues {
|
||||
sonarFilePath := parseFilePath(issue, rootPaths)
|
||||
|
||||
@@ -29,9 +37,28 @@ func GenerateReport(rootPaths []string, data *gosec.ReportInfo) (*Report, error)
|
||||
}
|
||||
|
||||
primaryLocation := NewLocation(issue.What, sonarFilePath, textRange)
|
||||
severity := getSonarSeverity(issue.Severity.String())
|
||||
severity := getImpactSeverity(issue.Severity.String())
|
||||
|
||||
s := NewIssue("gosec", issue.RuleID, primaryLocation, "VULNERABILITY", severity, EffortMinutes)
|
||||
if rule, ok := ruleIndex[issue.RuleID]; ok {
|
||||
rule.Impacts = mergeRuleImpacts(rule.Impacts, severity)
|
||||
} else {
|
||||
description := issue.What
|
||||
if def, found := ruleDefinitions[issue.RuleID]; found && def.Description != "" {
|
||||
description = def.Description
|
||||
}
|
||||
newRule := NewRule(
|
||||
issue.RuleID,
|
||||
issue.RuleID,
|
||||
description,
|
||||
sonarEngineID,
|
||||
sonarCleanCodeAttribute,
|
||||
[]*Impact{NewImpact(sonarSoftwareQuality, severity)},
|
||||
)
|
||||
ruleIndex[issue.RuleID] = newRule
|
||||
si.Rules = append(si.Rules, newRule)
|
||||
}
|
||||
|
||||
s := NewIssue(issue.RuleID, primaryLocation, EffortMinutes)
|
||||
si.Issues = append(si.Issues, s)
|
||||
}
|
||||
return si, nil
|
||||
@@ -63,15 +90,41 @@ func parseTextRange(issue *issue.Issue) (*TextRange, error) {
|
||||
return NewTextRange(startLine, endLine), nil
|
||||
}
|
||||
|
||||
func getSonarSeverity(s string) string {
|
||||
func getImpactSeverity(s string) string {
|
||||
switch s {
|
||||
case "LOW":
|
||||
return "MINOR"
|
||||
return "LOW"
|
||||
case "MEDIUM":
|
||||
return "MAJOR"
|
||||
return "MEDIUM"
|
||||
case "HIGH":
|
||||
return "BLOCKER"
|
||||
return "HIGH"
|
||||
default:
|
||||
return "INFO"
|
||||
}
|
||||
}
|
||||
|
||||
func mergeRuleImpacts(existing []*Impact, severity string) []*Impact {
|
||||
if len(existing) == 0 {
|
||||
return []*Impact{NewImpact(sonarSoftwareQuality, severity)}
|
||||
}
|
||||
for _, impact := range existing {
|
||||
if impact.SoftwareQuality == sonarSoftwareQuality {
|
||||
if compareImpactSeverity(severity, impact.Severity) > 0 {
|
||||
impact.Severity = severity
|
||||
}
|
||||
return existing
|
||||
}
|
||||
}
|
||||
return append(existing, NewImpact(sonarSoftwareQuality, severity))
|
||||
}
|
||||
|
||||
func compareImpactSeverity(a string, b string) int {
|
||||
severityRank := map[string]int{
|
||||
"BLOCKER": 5,
|
||||
"HIGH": 4,
|
||||
"MEDIUM": 3,
|
||||
"LOW": 2,
|
||||
"INFO": 1,
|
||||
}
|
||||
return severityRank[a] - severityRank[b]
|
||||
}
|
||||
|
||||
+106
-16
@@ -35,10 +35,24 @@ var _ = Describe("Sonar Formatter", func() {
|
||||
},
|
||||
}
|
||||
want := &sonar.Report{
|
||||
Rules: []*sonar.Rule{
|
||||
{
|
||||
ID: "test",
|
||||
Name: "test",
|
||||
Description: "test",
|
||||
EngineID: "gosec",
|
||||
CleanCodeAttribute: "TRUSTWORTHY",
|
||||
Impacts: []*sonar.Impact{
|
||||
{
|
||||
SoftwareQuality: "SECURITY",
|
||||
Severity: "HIGH",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Issues: []*sonar.Issue{
|
||||
{
|
||||
EngineID: "gosec",
|
||||
RuleID: "test",
|
||||
RuleID: "test",
|
||||
PrimaryLocation: &sonar.Location{
|
||||
Message: "test",
|
||||
FilePath: "test.go",
|
||||
@@ -47,8 +61,62 @@ var _ = Describe("Sonar Formatter", func() {
|
||||
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 enrich rules with metadata when available", func() {
|
||||
data := &gosec.ReportInfo{
|
||||
Errors: map[string][]gosec.Error{},
|
||||
Issues: []*issue.Issue{
|
||||
{
|
||||
Severity: issue.Medium,
|
||||
Confidence: 0,
|
||||
RuleID: "G101",
|
||||
What: "Potential hardcoded credentials",
|
||||
File: "/home/src/project/test.go",
|
||||
Code: "",
|
||||
Line: "5",
|
||||
},
|
||||
},
|
||||
Stats: &gosec.Metrics{},
|
||||
}
|
||||
|
||||
want := &sonar.Report{
|
||||
Rules: []*sonar.Rule{
|
||||
{
|
||||
ID: "G101",
|
||||
Name: "G101",
|
||||
Description: "Look for hardcoded credentials",
|
||||
EngineID: "gosec",
|
||||
CleanCodeAttribute: "TRUSTWORTHY",
|
||||
Impacts: []*sonar.Impact{
|
||||
{
|
||||
SoftwareQuality: "SECURITY",
|
||||
Severity: "MEDIUM",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Issues: []*sonar.Issue{
|
||||
{
|
||||
RuleID: "G101",
|
||||
PrimaryLocation: &sonar.Location{
|
||||
Message: "Potential hardcoded credentials",
|
||||
FilePath: "test.go",
|
||||
TextRange: &sonar.TextRange{
|
||||
StartLine: 5,
|
||||
EndLine: 5,
|
||||
},
|
||||
},
|
||||
EffortMinutes: sonar.EffortMinutes,
|
||||
},
|
||||
},
|
||||
@@ -83,10 +151,24 @@ var _ = Describe("Sonar Formatter", func() {
|
||||
},
|
||||
}
|
||||
want := &sonar.Report{
|
||||
Rules: []*sonar.Rule{
|
||||
{
|
||||
ID: "test",
|
||||
Name: "test",
|
||||
Description: "test",
|
||||
EngineID: "gosec",
|
||||
CleanCodeAttribute: "TRUSTWORTHY",
|
||||
Impacts: []*sonar.Impact{
|
||||
{
|
||||
SoftwareQuality: "SECURITY",
|
||||
Severity: "HIGH",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Issues: []*sonar.Issue{
|
||||
{
|
||||
EngineID: "gosec",
|
||||
RuleID: "test",
|
||||
RuleID: "test",
|
||||
PrimaryLocation: &sonar.Location{
|
||||
Message: "test",
|
||||
FilePath: "subfolder/test.go",
|
||||
@@ -95,8 +177,6 @@ var _ = Describe("Sonar Formatter", func() {
|
||||
EndLine: 2,
|
||||
},
|
||||
},
|
||||
Type: "VULNERABILITY",
|
||||
Severity: "BLOCKER",
|
||||
EffortMinutes: sonar.EffortMinutes,
|
||||
},
|
||||
},
|
||||
@@ -130,6 +210,7 @@ var _ = Describe("Sonar Formatter", func() {
|
||||
},
|
||||
}
|
||||
want := &sonar.Report{
|
||||
Rules: []*sonar.Rule{},
|
||||
Issues: []*sonar.Issue{},
|
||||
}
|
||||
|
||||
@@ -171,10 +252,24 @@ var _ = Describe("Sonar Formatter", func() {
|
||||
},
|
||||
}
|
||||
want := &sonar.Report{
|
||||
Rules: []*sonar.Rule{
|
||||
{
|
||||
ID: "test",
|
||||
Name: "test",
|
||||
Description: "test",
|
||||
EngineID: "gosec",
|
||||
CleanCodeAttribute: "TRUSTWORTHY",
|
||||
Impacts: []*sonar.Impact{
|
||||
{
|
||||
SoftwareQuality: "SECURITY",
|
||||
Severity: "HIGH",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Issues: []*sonar.Issue{
|
||||
{
|
||||
EngineID: "gosec",
|
||||
RuleID: "test",
|
||||
RuleID: "test",
|
||||
PrimaryLocation: &sonar.Location{
|
||||
Message: "test",
|
||||
FilePath: "test-project1.go",
|
||||
@@ -183,13 +278,10 @@ var _ = Describe("Sonar Formatter", func() {
|
||||
EndLine: 2,
|
||||
},
|
||||
},
|
||||
Type: "VULNERABILITY",
|
||||
Severity: "BLOCKER",
|
||||
EffortMinutes: sonar.EffortMinutes,
|
||||
},
|
||||
{
|
||||
EngineID: "gosec",
|
||||
RuleID: "test",
|
||||
RuleID: "test",
|
||||
PrimaryLocation: &sonar.Location{
|
||||
Message: "test",
|
||||
FilePath: "test-project2.go",
|
||||
@@ -198,8 +290,6 @@ var _ = Describe("Sonar Formatter", func() {
|
||||
EndLine: 2,
|
||||
},
|
||||
},
|
||||
Type: "VULNERABILITY",
|
||||
Severity: "BLOCKER",
|
||||
EffortMinutes: sonar.EffortMinutes,
|
||||
},
|
||||
},
|
||||
|
||||
+17
-3
@@ -17,16 +17,30 @@ type Location struct {
|
||||
|
||||
// Issue defines a sonar issue
|
||||
type Issue struct {
|
||||
EngineID string `json:"engineId"`
|
||||
RuleID string `json:"ruleId"`
|
||||
PrimaryLocation *Location `json:"primaryLocation"`
|
||||
Type string `json:"type"`
|
||||
Severity string `json:"severity"`
|
||||
EffortMinutes int `json:"effortMinutes"`
|
||||
SecondaryLocations []*Location `json:"secondaryLocations,omitempty"`
|
||||
}
|
||||
|
||||
// Impact defines the impact for a rule.
|
||||
type Impact struct {
|
||||
SoftwareQuality string `json:"softwareQuality"`
|
||||
Severity string `json:"severity"`
|
||||
}
|
||||
|
||||
// Rule defines a sonar rule.
|
||||
type Rule struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
EngineID string `json:"engineId"`
|
||||
CleanCodeAttribute string `json:"cleanCodeAttribute,omitempty"`
|
||||
Impacts []*Impact `json:"impacts"`
|
||||
}
|
||||
|
||||
// Report defines a sonar report
|
||||
type Report struct {
|
||||
Rules []*Rule `json:"rules"`
|
||||
Issues []*Issue `json:"issues"`
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user