Skip to content

Commit

Permalink
feat(cli): Show top 5 vulnerabilities by score in html report for nam…
Browse files Browse the repository at this point in the history
…espace (#463)

Resolves: #459
  • Loading branch information
xyoxo committed Apr 1, 2021
1 parent 8841b79 commit f53705a
Show file tree
Hide file tree
Showing 11 changed files with 515 additions and 69 deletions.
2 changes: 2 additions & 0 deletions deploy/crd/vulnerabilityreports.crd.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,8 @@ spec:
type: string
fixedVersion:
type: string
score:
type: number
severity:
type: string
enum:
Expand Down
2 changes: 2 additions & 0 deletions pkg/apis/aquasecurity/v1alpha1/vulnerability_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,7 @@ var (
"title": {Type: "string"},
"description": {Type: "string"},
"primaryLink": {Type: "string"},
"score": {Type: "number"},
"links": {
Type: "array",
Items: &apiextensionsv1.JSONSchemaPropsOrArray{
Expand Down Expand Up @@ -261,6 +262,7 @@ type Vulnerability struct {
Description string `json:"description,omitempty"`
PrimaryLink string `json:"primaryLink,omitempty"`
Links []string `json:"links"`
Score *float64 `json:"score,omitempty"`
}

// +genclient
Expand Down
5 changes: 5 additions & 0 deletions pkg/apis/aquasecurity/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 19 additions & 0 deletions pkg/plugin/trivy/converter.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ func (c *converter) convert(imageRef string, reports []ScanReport) (v1alpha1.Vul
Title: sr.Title,
PrimaryLink: sr.PrimaryURL,
Links: []string{},
Score: GetScoreFromCvss(sr.Cvss),
})
}
}
Expand Down Expand Up @@ -124,3 +125,21 @@ func (c *converter) parseImageRef(imageRef string) (v1alpha1.Registry, v1alpha1.

return registry, artifact, nil
}

func GetScoreFromCvss(CVSSs map[string]*CVSS) *float64 {
var nvdScore, vendorScore *float64

for name, cvss := range CVSSs {
if name == "nvd" {
nvdScore = cvss.V3Score
} else {
vendorScore = cvss.V3Score
}
}

if vendorScore != nil {
return vendorScore
}

return nvdScore
}
67 changes: 67 additions & 0 deletions pkg/plugin/trivy/converter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/utils/pointer"
)

var (
Expand Down Expand Up @@ -165,3 +166,69 @@ func TestConverter_Convert(t *testing.T) {
}

}

func TestGetScoreFromCvss(t *testing.T) {
testCases := []struct {
name string
cvss map[string]*trivy.CVSS
expectedScore *float64
}{
{
name: "Should return vendor score when vendor v3 score exist",
cvss: map[string]*trivy.CVSS{
"nvd": {
V3Score: pointer.Float64Ptr(8.1),
},
"redhat": {
V3Score: pointer.Float64Ptr(8.3),
},
},
expectedScore: pointer.Float64Ptr(8.3),
},
{
name: "Should return nvd score when vendor v3 score is nil",
cvss: map[string]*trivy.CVSS{
"nvd": {
V3Score: pointer.Float64Ptr(8.1),
},
"redhat": {
V3Score: nil,
},
},
expectedScore: pointer.Float64Ptr(8.1),
},
{
name: "Should return nvd score when vendor doesn't exist",
cvss: map[string]*trivy.CVSS{
"nvd": {
V3Score: pointer.Float64Ptr(8.1),
},
},
expectedScore: pointer.Float64Ptr(8.1),
},
{
name: "Should return nil when vendor and nvd both v3 scores are nil",
cvss: map[string]*trivy.CVSS{
"nvd": {
V3Score: nil,
},
"redhat": {
V3Score: nil,
},
},
expectedScore: nil,
},
{
name: "Should return nil when cvss doesn't exist",
cvss: map[string]*trivy.CVSS{},
expectedScore: nil,
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
score := trivy.GetScoreFromCvss(tc.cvss)
assert.Equal(t, tc.expectedScore, score)
})
}
}
5 changes: 5 additions & 0 deletions pkg/plugin/trivy/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ type Vulnerability struct {
Layer Layer `json:"Layer"`
PrimaryURL string `json:"PrimaryURL"`
References []string `json:"References"`
Cvss map[string]*CVSS `json:"CVSS"`
}

type CVSS struct {
V3Score *float64 `json:"V3Score,omitempty"`
}

type Layer struct {
Expand Down
49 changes: 49 additions & 0 deletions pkg/report/html.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ func (r *namespaceReporter) RetrieveData(namespace kube.Object) (templates.Names
GeneratedAt: r.clock.Now(),
Top5VulnerableImages: r.topNImagesBySeverityCount(vulnerabilityReportList.Items, 5),
Top5FailedChecks: r.topNFailedChecksByAffectedWorkloadsCount(configAuditReportList.Items, 5),
Top5Vulnerability: r.topNVulnerabilitiesByScore(vulnerabilityReportList.Items, 5),
}, nil
}

Expand Down Expand Up @@ -179,6 +180,54 @@ func (r *namespaceReporter) topNFailedChecksByAffectedWorkloadsCount(reports []v
return failedChecks[:ext.MinInt(N, len(failedChecks))]
}

func (r *namespaceReporter) topNVulnerabilitiesByScore(reports []v1alpha1.VulnerabilityReport, N int) []templates.VulnerabilityWithCount {
vulnerabilityMap := make(map[string]templates.VulnerabilityWithCount)

for _, report := range reports {
vulnMap := make(map[string]bool)
for _, vulnerability := range report.Report.Vulnerabilities {
vulnId := vulnerability.VulnerabilityID
if vulnMap[vulnId] {
continue
}
vulnMap[vulnId] = true

if _, ok := vulnerabilityMap[vulnerability.VulnerabilityID]; ok {
tempVuln := vulnerabilityMap[vulnId]
tempVuln.AffectedWorkloads++
vulnerabilityMap[vulnId] = tempVuln
} else {
if vulnerability.Score == nil {
continue
}

vulnerabilityMap[vulnId] = templates.VulnerabilityWithCount{
Vulnerability: v1alpha1.Vulnerability{
VulnerabilityID: vulnerability.VulnerabilityID,
PrimaryLink: vulnerability.PrimaryLink,
Severity: vulnerability.Severity,
Score: vulnerability.Score,
},
AffectedWorkloads: 1,
}
}
}
}

vulnerabilities := make([]templates.VulnerabilityWithCount, len(vulnerabilityMap))
i := 0
for _, vulnerability := range vulnerabilityMap {
vulnerabilities[i] = vulnerability
i++
}

sort.SliceStable(vulnerabilities, func(i, j int) bool {
return *vulnerabilities[i].Score > *vulnerabilities[j].Score
})

return vulnerabilities[:ext.MinInt(N, len(vulnerabilities))]
}

func (r *namespaceReporter) Generate(namespace kube.Object, out io.Writer) error {
data, err := r.RetrieveData(namespace)
if err != nil {
Expand Down

0 comments on commit f53705a

Please sign in to comment.