diff --git a/cli/go.mod b/cli/go.mod index 1e1c2cb9e..f76de48d8 100644 --- a/cli/go.mod +++ b/cli/go.mod @@ -413,8 +413,6 @@ require ( github.com/xlab/treeprint v1.2.0 // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect github.com/yashtewari/glob-intersection v0.2.0 // indirect - github.com/yudai/gojsondiff v1.0.0 // indirect - github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 // indirect github.com/zclconf/go-cty v1.14.4 // indirect github.com/zclconf/go-cty-yaml v1.0.3 // indirect go.etcd.io/bbolt v1.3.10 // indirect diff --git a/cli/go.sum b/cli/go.sum index c580627cd..91fd7164d 100644 --- a/cli/go.sum +++ b/cli/go.sum @@ -1559,12 +1559,6 @@ github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavM github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= github.com/yashtewari/glob-intersection v0.2.0 h1:8iuHdN88yYuCzCdjt0gDe+6bAhUwBeEWqThExu54RFg= github.com/yashtewari/glob-intersection v0.2.0/go.mod h1:LK7pIC3piUjovexikBbJ26Yml7g8xa5bsjfx2v1fwok= -github.com/yudai/gojsondiff v1.0.0 h1:27cbfqXLVEJ1o8I6v3y9lg8Ydm53EKqHXAOMxEGlCOA= -github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg= -github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 h1:BHyfKlQyqbsFN5p3IfnEUduWvb9is428/nNb5L3U01M= -github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM= -github.com/yudai/pp v2.0.1+incompatible h1:Q4//iY4pNF6yPLZIigmvcl7k/bPgrcTPIFIcmawg5bI= -github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZkTdatxwunjIkc= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= diff --git a/cli/presenter/apimodel.go b/cli/presenter/apimodel.go index 085043c2d..a608939be 100644 --- a/cli/presenter/apimodel.go +++ b/cli/presenter/apimodel.go @@ -60,28 +60,26 @@ func ConvertSBOMResultToPackages(result *sbom.Result) []apitypes.Package { func ConvertVulnResultToVulnerabilities(result *vulnerabilities.Result) []apitypes.Vulnerability { vuls := []apitypes.Vulnerability{} - if result == nil || result.MergedVulnerabilitiesByKey == nil { + if result == nil || result.VulnerabilitiesByKey == nil { return vuls } - for _, vulCandidates := range result.MergedVulnerabilitiesByKey { - if len(vulCandidates) < 1 { + for _, vulCandidate := range result.VulnerabilitiesByKey { + if vulCandidate.ID == "" { continue } - vulCandidate := vulCandidates[0] - vul := apitypes.Vulnerability{ - Cvss: ConvertVulnCvssToAPIModel(vulCandidate.Vulnerability.CVSS), - Description: to.Ptr(vulCandidate.Vulnerability.Description), - Distro: ConvertVulnDistroToAPIModel(vulCandidate.Vulnerability.Distro), - Fix: ConvertVulnFixToAPIModel(vulCandidate.Vulnerability.Fix), - LayerId: to.Ptr(vulCandidate.Vulnerability.LayerID), - Links: to.Ptr(vulCandidate.Vulnerability.Links), - Package: ConvertVulnPackageToAPIModel(vulCandidate.Vulnerability.Package), - Path: to.Ptr(vulCandidate.Vulnerability.Path), - Severity: ConvertVulnSeverityToAPIModel(vulCandidate.Vulnerability.Severity), - VulnerabilityName: to.Ptr(vulCandidate.Vulnerability.ID), + Cvss: ConvertVulnCvssToAPIModel(vulCandidate.CVSS), + Description: to.Ptr(vulCandidate.Description), + Distro: ConvertVulnDistroToAPIModel(vulCandidate.Distro), + Fix: ConvertVulnFixToAPIModel(vulCandidate.Fix), + LayerId: to.Ptr(vulCandidate.LayerID), + Links: to.Ptr(vulCandidate.Links), + Package: ConvertVulnPackageToAPIModel(vulCandidate.Package), + Path: to.Ptr(vulCandidate.Path), + Severity: ConvertVulnSeverityToAPIModel(vulCandidate.Severity), + VulnerabilityName: to.Ptr(vulCandidate.ID), } vuls = append(vuls, vul) } diff --git a/cli/presenter/apimodel_test.go b/cli/presenter/apimodel_test.go index ce13e70bd..2278ab369 100644 --- a/cli/presenter/apimodel_test.go +++ b/cli/presenter/apimodel_test.go @@ -161,108 +161,98 @@ func Test_ConvertVulnResultToVulnerabilities(t *testing.T) { name: "Vuls", args: args{ result: &vulnerabilities.Result{ - MergedVulnerabilitiesByKey: map[vulnerabilities.VulnerabilityKey][]vulnerabilities.MergedVulnerability{ + VulnerabilitiesByKey: map[vulnerabilities.VulnerabilityKey]vulnerabilities.Vulnerability{ "vulkey1": { - { - ID: "id1", - Vulnerability: vulnerabilities.Vulnerability{ - ID: "CVE-test-test-foo", - Description: "testbleed", - Links: []string{"link1", "link2"}, - Distro: vulnerabilities.Distro{ - Name: "distro1", - Version: "distrov1", - IDLike: []string{"IDLike1", "IDLike2"}, - }, - CVSS: []vulnerabilities.CVSS{ - { - Version: "v1", - Vector: "vector1", - Metrics: vulnerabilities.CvssMetrics{ - BaseScore: 1, - ExploitabilityScore: nil, - ImpactScore: nil, - }, - }, - { - Version: "v2", - Vector: "vector2", - Metrics: vulnerabilities.CvssMetrics{ - BaseScore: 2, - ExploitabilityScore: to.Ptr(2.1), - ImpactScore: to.Ptr(2.2), - }, - }, - }, - Fix: vulnerabilities.Fix{ - Versions: []string{"fv1", "fv2"}, - State: "fixed", + ID: "CVE-test-test-foo", + Description: "testbleed", + Links: []string{"link1", "link2"}, + Distro: vulnerabilities.Distro{ + Name: "distro1", + Version: "distrov1", + IDLike: []string{"IDLike1", "IDLike2"}, + }, + CVSS: []vulnerabilities.CVSS{ + { + Version: "v1", + Vector: "vector1", + Metrics: vulnerabilities.CvssMetrics{ + BaseScore: 1, + ExploitabilityScore: nil, + ImpactScore: nil, }, - Severity: string(apitypes.CRITICAL), - Package: vulnerabilities.Package{ - Name: "package1", - Version: "pv1", - Type: "pt1", - Language: "pl1", - Licenses: []string{"plic1", "plic2"}, - CPEs: []string{"cpe1", "cpe2"}, - PURL: "purl1", + }, + { + Version: "v2", + Vector: "vector2", + Metrics: vulnerabilities.CvssMetrics{ + BaseScore: 2, + ExploitabilityScore: to.Ptr(2.1), + ImpactScore: to.Ptr(2.2), }, - LayerID: "lid1", - Path: "path1", }, }, + Fix: vulnerabilities.Fix{ + Versions: []string{"fv1", "fv2"}, + State: "fixed", + }, + Severity: string(apitypes.CRITICAL), + Package: vulnerabilities.Package{ + Name: "package1", + Version: "pv1", + Type: "pt1", + Language: "pl1", + Licenses: []string{"plic1", "plic2"}, + CPEs: []string{"cpe1", "cpe2"}, + PURL: "purl1", + }, + LayerID: "lid1", + Path: "path1", }, "vulkey2": { - { - ID: "id2", - Vulnerability: vulnerabilities.Vulnerability{ - ID: "CVE-test-test-bar", - Description: "solartest", - Links: []string{"link3", "link4"}, - Distro: vulnerabilities.Distro{ - Name: "distro2", - Version: "distrov2", - IDLike: []string{"IDLike3", "IDLike4"}, - }, - CVSS: []vulnerabilities.CVSS{ - { - Version: "v3", - Vector: "vector3", - Metrics: vulnerabilities.CvssMetrics{ - BaseScore: 3, - ExploitabilityScore: nil, - ImpactScore: nil, - }, - }, - { - Version: "v4", - Vector: "vector4", - Metrics: vulnerabilities.CvssMetrics{ - BaseScore: 4, - ExploitabilityScore: to.Ptr(4.1), - ImpactScore: to.Ptr(4.2), - }, - }, - }, - Fix: vulnerabilities.Fix{ - Versions: []string{"fv3", "fv4"}, - State: "not-fixed", + ID: "CVE-test-test-bar", + Description: "solartest", + Links: []string{"link3", "link4"}, + Distro: vulnerabilities.Distro{ + Name: "distro2", + Version: "distrov2", + IDLike: []string{"IDLike3", "IDLike4"}, + }, + CVSS: []vulnerabilities.CVSS{ + { + Version: "v3", + Vector: "vector3", + Metrics: vulnerabilities.CvssMetrics{ + BaseScore: 3, + ExploitabilityScore: nil, + ImpactScore: nil, }, - Severity: string(apitypes.HIGH), - Package: vulnerabilities.Package{ - Name: "package2", - Version: "pv2", - Type: "pt2", - Language: "pl2", - Licenses: []string{"plic3", "plic4"}, - CPEs: []string{"cpe3", "cpe4"}, - PURL: "purl2", + }, + { + Version: "v4", + Vector: "vector4", + Metrics: vulnerabilities.CvssMetrics{ + BaseScore: 4, + ExploitabilityScore: to.Ptr(4.1), + ImpactScore: to.Ptr(4.2), }, - LayerID: "lid2", - Path: "path2", }, }, + Fix: vulnerabilities.Fix{ + Versions: []string{"fv3", "fv4"}, + State: "not-fixed", + }, + Severity: string(apitypes.HIGH), + Package: vulnerabilities.Package{ + Name: "package2", + Version: "pv2", + Type: "pt2", + Language: "pl2", + Licenses: []string{"plic3", "plic4"}, + CPEs: []string{"cpe3", "cpe4"}, + PURL: "purl2", + }, + LayerID: "lid2", + Path: "path2", }, "vulkey3": {}, }, diff --git a/core/to/to.go b/core/to/to.go index f777e4f04..c2e5f5211 100644 --- a/core/to/to.go +++ b/core/to/to.go @@ -78,3 +78,31 @@ func Values[K comparable, V any](m map[K]V) []V { return s } + +// UniqueSlice returns a slice without duplicate elements. +func UniqueSlice[T comparable](items []T) []T { + var filtered []T + unique := make(map[T]bool, len(items)) + for _, item := range items { + if !unique[item] { + filtered = append(filtered, item) + unique[item] = true + } + } + return filtered +} + +// UniqueSliceByKey returns a slice without duplicate elements using a custom get key function. +func UniqueSliceByKey[T any](items []T, getKey func(T) string) []T { + var filtered []T + + unique := make(map[string]bool, len(items)) + for _, item := range items { + if key := getKey(item); key != "" && !unique[key] { + filtered = append(filtered, item) + unique[key] = true + } + } + + return filtered +} diff --git a/e2e/go.mod b/e2e/go.mod index 4f0fe49d5..a80f26242 100644 --- a/e2e/go.mod +++ b/e2e/go.mod @@ -462,8 +462,6 @@ require ( github.com/xlab/treeprint v1.2.0 // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect github.com/yashtewari/glob-intersection v0.2.0 // indirect - github.com/yudai/gojsondiff v1.0.0 // indirect - github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 // indirect github.com/zclconf/go-cty v1.14.4 // indirect github.com/zclconf/go-cty-yaml v1.0.3 // indirect go.etcd.io/bbolt v1.3.10 // indirect diff --git a/e2e/go.sum b/e2e/go.sum index 24c836e76..be333e810 100644 --- a/e2e/go.sum +++ b/e2e/go.sum @@ -1705,12 +1705,6 @@ github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavM github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= github.com/yashtewari/glob-intersection v0.2.0 h1:8iuHdN88yYuCzCdjt0gDe+6bAhUwBeEWqThExu54RFg= github.com/yashtewari/glob-intersection v0.2.0/go.mod h1:LK7pIC3piUjovexikBbJ26Yml7g8xa5bsjfx2v1fwok= -github.com/yudai/gojsondiff v1.0.0 h1:27cbfqXLVEJ1o8I6v3y9lg8Ydm53EKqHXAOMxEGlCOA= -github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg= -github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 h1:BHyfKlQyqbsFN5p3IfnEUduWvb9is428/nNb5L3U01M= -github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM= -github.com/yudai/pp v2.0.1+incompatible h1:Q4//iY4pNF6yPLZIigmvcl7k/bPgrcTPIFIcmawg5bI= -github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZkTdatxwunjIkc= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= diff --git a/orchestrator/go.mod b/orchestrator/go.mod index 2e40ca7b7..473caf764 100644 --- a/orchestrator/go.mod +++ b/orchestrator/go.mod @@ -428,8 +428,6 @@ require ( github.com/xlab/treeprint v1.2.0 // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect github.com/yashtewari/glob-intersection v0.2.0 // indirect - github.com/yudai/gojsondiff v1.0.0 // indirect - github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 // indirect github.com/zclconf/go-cty v1.14.4 // indirect github.com/zclconf/go-cty-yaml v1.0.3 // indirect go.etcd.io/bbolt v1.3.10 // indirect diff --git a/orchestrator/go.sum b/orchestrator/go.sum index 376174566..421081b83 100644 --- a/orchestrator/go.sum +++ b/orchestrator/go.sum @@ -1629,12 +1629,6 @@ github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavM github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= github.com/yashtewari/glob-intersection v0.2.0 h1:8iuHdN88yYuCzCdjt0gDe+6bAhUwBeEWqThExu54RFg= github.com/yashtewari/glob-intersection v0.2.0/go.mod h1:LK7pIC3piUjovexikBbJ26Yml7g8xa5bsjfx2v1fwok= -github.com/yudai/gojsondiff v1.0.0 h1:27cbfqXLVEJ1o8I6v3y9lg8Ydm53EKqHXAOMxEGlCOA= -github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg= -github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 h1:BHyfKlQyqbsFN5p3IfnEUduWvb9is428/nNb5L3U01M= -github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM= -github.com/yudai/pp v2.0.1+incompatible h1:Q4//iY4pNF6yPLZIigmvcl7k/bPgrcTPIFIcmawg5bI= -github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZkTdatxwunjIkc= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= diff --git a/provider/go.mod b/provider/go.mod index 0425f2942..1e99f1cf2 100644 --- a/provider/go.mod +++ b/provider/go.mod @@ -430,8 +430,6 @@ require ( github.com/xlab/treeprint v1.2.0 // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect github.com/yashtewari/glob-intersection v0.2.0 // indirect - github.com/yudai/gojsondiff v1.0.0 // indirect - github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 // indirect github.com/zclconf/go-cty v1.14.4 // indirect github.com/zclconf/go-cty-yaml v1.0.3 // indirect go.etcd.io/bbolt v1.3.10 // indirect diff --git a/provider/go.sum b/provider/go.sum index be6aed3af..8dfa973fb 100644 --- a/provider/go.sum +++ b/provider/go.sum @@ -1580,12 +1580,6 @@ github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavM github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= github.com/yashtewari/glob-intersection v0.2.0 h1:8iuHdN88yYuCzCdjt0gDe+6bAhUwBeEWqThExu54RFg= github.com/yashtewari/glob-intersection v0.2.0/go.mod h1:LK7pIC3piUjovexikBbJ26Yml7g8xa5bsjfx2v1fwok= -github.com/yudai/gojsondiff v1.0.0 h1:27cbfqXLVEJ1o8I6v3y9lg8Ydm53EKqHXAOMxEGlCOA= -github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg= -github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 h1:BHyfKlQyqbsFN5p3IfnEUduWvb9is428/nNb5L3U01M= -github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM= -github.com/yudai/pp v2.0.1+incompatible h1:Q4//iY4pNF6yPLZIigmvcl7k/bPgrcTPIFIcmawg5bI= -github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZkTdatxwunjIkc= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= diff --git a/scanner/families/exploits/family.go b/scanner/families/exploits/family.go index 998a8e402..ede9cf0aa 100644 --- a/scanner/families/exploits/family.go +++ b/scanner/families/exploits/family.go @@ -81,17 +81,14 @@ func (e Exploits) Run(ctx context.Context, res *families.Results) (*types.Result } // create a comma separated representation of cveIDs array, as an input for the exploits scanners. -func getCVEIDsFromVulnerabilitiesResults(vulnerabilities *vulnerabilitytypes.Result) string { - if vulnerabilities == nil { +func getCVEIDsFromVulnerabilitiesResults(result *vulnerabilitytypes.Result) string { + if result == nil { return "" } cvesMap := make(map[string]bool) - - for _, mergedVulnerabilities := range vulnerabilities.MergedVulnerabilitiesByKey { - for _, vulnerability := range mergedVulnerabilities { - cvesMap[vulnerability.Vulnerability.ID] = true - } + for _, vulnerability := range result.VulnerabilitiesByKey { + cvesMap[vulnerability.ID] = true } cves := strings.Join(to.Keys(cvesMap), ",") diff --git a/scanner/families/exploits/family_test.go b/scanner/families/exploits/family_test.go index 659b95b73..4e052c20b 100644 --- a/scanner/families/exploits/family_test.go +++ b/scanner/families/exploits/family_test.go @@ -40,10 +40,10 @@ func Test_getCVEIDsFromVulnerabilitiesResults(t *testing.T) { wantCves: "", }, { - name: "nil MergedVulnerabilitiesByKey", + name: "nil VulnerabilitiesByKey", args: args{ vulnResults: &vulnerabilitytypes.Result{ - MergedVulnerabilitiesByKey: nil, + VulnerabilitiesByKey: nil, }, }, wantCves: "", @@ -52,7 +52,7 @@ func Test_getCVEIDsFromVulnerabilitiesResults(t *testing.T) { name: "no vulnerabilities", args: args{ vulnResults: &vulnerabilitytypes.Result{ - MergedVulnerabilitiesByKey: map[vulnerabilitytypes.VulnerabilityKey][]vulnerabilitytypes.MergedVulnerability{}, + VulnerabilitiesByKey: map[vulnerabilitytypes.VulnerabilityKey]vulnerabilitytypes.Vulnerability{}, }, }, wantCves: "", @@ -61,55 +61,27 @@ func Test_getCVEIDsFromVulnerabilitiesResults(t *testing.T) { name: "sanity", args: args{ vulnResults: &vulnerabilitytypes.Result{ - MergedVulnerabilitiesByKey: map[vulnerabilitytypes.VulnerabilityKey][]vulnerabilitytypes.MergedVulnerability{ - "vul1": { - { - Vulnerability: vulnerabilitytypes.Vulnerability{ID: "cve1"}, - }, - { - Vulnerability: vulnerabilitytypes.Vulnerability{ID: "cve2"}, - }, - }, - "vul2": { - { - Vulnerability: vulnerabilitytypes.Vulnerability{ID: "cve3"}, - }, - { - Vulnerability: vulnerabilitytypes.Vulnerability{ID: "cve4"}, - }, - }, + VulnerabilitiesByKey: map[vulnerabilitytypes.VulnerabilityKey]vulnerabilitytypes.Vulnerability{ + "vul1": {ID: "cve1"}, + "vul2": {ID: "cve3"}, }, Source: vulnerabilitytypes.Source{}, }, }, - wantCves: "cve1,cve2,cve3,cve4", + wantCves: "cve1,cve3", }, { name: "same cve id in different vulnerabilities", args: args{ vulnResults: &vulnerabilitytypes.Result{ - MergedVulnerabilitiesByKey: map[vulnerabilitytypes.VulnerabilityKey][]vulnerabilitytypes.MergedVulnerability{ - "vul1": { - { - Vulnerability: vulnerabilitytypes.Vulnerability{ID: "cve1"}, - }, - { - Vulnerability: vulnerabilitytypes.Vulnerability{ID: "cve2"}, - }, - }, - "vul2": { - { - Vulnerability: vulnerabilitytypes.Vulnerability{ID: "cve1"}, - }, - { - Vulnerability: vulnerabilitytypes.Vulnerability{ID: "cve4"}, - }, - }, + VulnerabilitiesByKey: map[vulnerabilitytypes.VulnerabilityKey]vulnerabilitytypes.Vulnerability{ + "vul1": {ID: "cve1"}, + "vul2": {ID: "cve1"}, }, Source: vulnerabilitytypes.Source{}, }, }, - wantCves: "cve1,cve2,cve4", + wantCves: "cve1", }, } for _, tt := range tests { diff --git a/scanner/families/vulnerabilities/types/result.go b/scanner/families/vulnerabilities/types/result.go index 71dccb3c0..c86abd52f 100644 --- a/scanner/families/vulnerabilities/types/result.go +++ b/scanner/families/vulnerabilities/types/result.go @@ -33,14 +33,14 @@ type Source struct { } type Result struct { - Metadata families.ScanMetadata `json:"Metadata"` - Source Source `json:"Source"` - MergedVulnerabilitiesByKey map[VulnerabilityKey][]MergedVulnerability `json:"MergedVulnerabilitiesByKey"` + Metadata families.ScanMetadata `json:"Metadata"` + Source Source `json:"Source"` + VulnerabilitiesByKey map[VulnerabilityKey]Vulnerability `json:"VulnerabilitiesByKey"` } func NewResult() *Result { return &Result{ - MergedVulnerabilitiesByKey: make(map[VulnerabilityKey][]MergedVulnerability), + VulnerabilitiesByKey: make(map[VulnerabilityKey]Vulnerability), } } @@ -74,10 +74,10 @@ func (r *Result) GetSourceImageInfo() (*apitypes.ContainerImageInfo, error) { return containerImageInfo, nil } -// ToSlice returns MergedResults in a slice format and not by key. -func (r *Result) ToSlice() [][]MergedVulnerability { - ret := make([][]MergedVulnerability, 0) - for _, vulnerabilities := range r.MergedVulnerabilitiesByKey { +// ToSlice returns Result in a slice format and not by key. +func (r *Result) ToSlice() []Vulnerability { + ret := make([]Vulnerability, 0) + for _, vulnerabilities := range r.VulnerabilitiesByKey { ret = append(ret, vulnerabilities) } @@ -92,19 +92,15 @@ func (r *Result) Merge(meta families.ScanInputMetadata, result *ScannerResult) { return } - otherVulnerabilityByKey := toVulnerabilityByKey(result.Vulnerabilities) + for _, vulnerability := range result.Vulnerabilities { + key := NewVulnerabilityKey(vulnerability) - // go over other vulnerabilities list - // 1. merge mutual vulnerabilities - // 2. add non mutual vulnerabilities - for key, otherVulnerability := range otherVulnerabilityByKey { // look for other vulnerability key in the current merged vulnerabilities list - if mergedVulnerabilities, ok := r.MergedVulnerabilitiesByKey[key]; !ok { - // add non mutual vulnerability + if existingVulnerability, ok := r.VulnerabilitiesByKey[key]; !ok { log.Debugf("Adding new vulnerability results from %v. key=%v", result.Scanner, key) - r.MergedVulnerabilitiesByKey[key] = []MergedVulnerability{*NewMergedVulnerability(otherVulnerability, result.Scanner)} + r.VulnerabilitiesByKey[key] = vulnerability } else { - r.MergedVulnerabilitiesByKey[key] = handleVulnerabilityWithExistingKey(mergedVulnerabilities, otherVulnerability, result.Scanner) + r.VulnerabilitiesByKey[key] = handleVulnerabilityWithExistingKey(existingVulnerability, vulnerability) } } diff --git a/scanner/families/vulnerabilities/types/result_test.go b/scanner/families/vulnerabilities/types/result_test.go index b95662553..9c9dd49a8 100644 --- a/scanner/families/vulnerabilities/types/result_test.go +++ b/scanner/families/vulnerabilities/types/result_test.go @@ -18,430 +18,8 @@ package types import ( "reflect" "testing" - - "github.com/google/go-cmp/cmp/cmpopts" - "github.com/yudai/gojsondiff/formatter" - "gotest.tools/assert" - - "github.com/openclarity/vmclarity/scanner/families" ) -func Test_handleVulnerabilityWithExistingKey(t *testing.T) { - highVul := Vulnerability{ - ID: "highVul", - Severity: "HIGH", - Package: Package{ - Name: "pkg-name", - Version: "pkg-version", - }, - } - lowVul := Vulnerability{ - ID: "highVul", - Severity: "LOW", - Package: Package{ - Name: "pkg-name", - Version: "pkg-version", - }, - } - medVul := Vulnerability{ - ID: "highVul", - Severity: "MEDIUM", - Package: Package{ - Name: "pkg-name", - Version: "pkg-version", - }, - } - type args struct { - mergedVulnerabilities []MergedVulnerability - otherVulnerability Vulnerability - otherScannerInfo ScannerInfo - } - tests := []struct { - name string - args args - want []MergedVulnerability - patchMergedVulnerabilityID func([]MergedVulnerability) []MergedVulnerability - }{ - { - name: "identical vulnerability", - args: args{ - mergedVulnerabilities: []MergedVulnerability{ - { - ID: "1", - Vulnerability: highVul, - ScannersInfo: []ScannerInfo{ - { - Name: "scanner1", - }, - }, - }, - }, - otherVulnerability: highVul, - otherScannerInfo: ScannerInfo{ - Name: "scanner2", - }, - }, - want: []MergedVulnerability{ - { - ID: "1", - Vulnerability: highVul, - ScannersInfo: []ScannerInfo{ - { - Name: "scanner1", - }, - { - Name: "scanner2", - }, - }, - }, - }, - }, - { - name: "different vulnerability", - args: args{ - mergedVulnerabilities: []MergedVulnerability{ - { - ID: "1", - Vulnerability: highVul, - ScannersInfo: []ScannerInfo{ - { - Name: "scanner1", - }, - }, - }, - }, - otherVulnerability: lowVul, - otherScannerInfo: ScannerInfo{ - Name: "scanner2", - }, - }, - want: []MergedVulnerability{ - { - ID: "1", - Vulnerability: highVul, - ScannersInfo: []ScannerInfo{ - { - Name: "scanner1", - }, - }, - }, - { - ID: "2", - Vulnerability: lowVul, - ScannersInfo: []ScannerInfo{ - { - Name: "scanner2", - }, - }, - Diffs: []VulnerabilityDiff{ - { - JSONDiff: map[string]interface{}{ - "severity": []interface{}{"HIGH", "LOW"}, - }, - CompareToID: "1", - }, - }, - }, - }, - patchMergedVulnerabilityID: func(vulnerability []MergedVulnerability) []MergedVulnerability { - vulnerability[1].ID = "2" - return vulnerability - }, - }, - { - name: "different vulnerability from first identical to second", - args: args{ - mergedVulnerabilities: []MergedVulnerability{ - { - ID: "1", - Vulnerability: highVul, - ScannersInfo: []ScannerInfo{ - { - Name: "scanner1", - }, - }, - }, - { - ID: "2", - Vulnerability: lowVul, - ScannersInfo: []ScannerInfo{ - { - Name: "scanner2", - }, - }, - Diffs: []VulnerabilityDiff{ - { - JSONDiff: map[string]interface{}{ - "severity": []interface{}{"HIGH", "LOW"}, - }, - CompareToID: "1", - }, - }, - }, - }, - otherVulnerability: lowVul, - otherScannerInfo: ScannerInfo{ - Name: "scanner3", - }, - }, - want: []MergedVulnerability{ - { - ID: "1", - Vulnerability: highVul, - ScannersInfo: []ScannerInfo{ - { - Name: "scanner1", - }, - }, - }, - { - ID: "2", - Vulnerability: lowVul, - ScannersInfo: []ScannerInfo{ - { - Name: "scanner2", - }, - { - Name: "scanner3", - }, - }, - Diffs: []VulnerabilityDiff{ - { - JSONDiff: map[string]interface{}{ - "severity": []interface{}{"HIGH", "LOW"}, - }, - CompareToID: "1", - }, - }, - }, - }, - }, - { - name: "different vulnerability from all", - args: args{ - mergedVulnerabilities: []MergedVulnerability{ - { - ID: "1", - Vulnerability: highVul, - ScannersInfo: []ScannerInfo{ - { - Name: "scanner1", - }, - }, - }, - { - ID: "2", - Vulnerability: lowVul, - ScannersInfo: []ScannerInfo{ - { - Name: "scanner2", - }, - }, - Diffs: []VulnerabilityDiff{ - { - JSONDiff: map[string]interface{}{ - "severity": []interface{}{"HIGH", "LOW"}, - }, - CompareToID: "1", - }, - }, - }, - }, - otherVulnerability: medVul, - otherScannerInfo: ScannerInfo{ - Name: "scanner3", - }, - }, - want: []MergedVulnerability{ - { - ID: "1", - Vulnerability: highVul, - ScannersInfo: []ScannerInfo{ - { - Name: "scanner1", - }, - }, - }, - { - ID: "2", - Vulnerability: lowVul, - ScannersInfo: []ScannerInfo{ - { - Name: "scanner2", - }, - }, - Diffs: []VulnerabilityDiff{ - { - JSONDiff: map[string]interface{}{ - "severity": []interface{}{"HIGH", "LOW"}, - }, - CompareToID: "1", - }, - }, - }, - { - ID: "3", - Vulnerability: medVul, - ScannersInfo: []ScannerInfo{ - { - Name: "scanner3", - }, - }, - Diffs: []VulnerabilityDiff{ - { - JSONDiff: map[string]interface{}{ - "severity": []interface{}{"HIGH", "MEDIUM"}, - }, - CompareToID: "1", - }, - { - JSONDiff: map[string]interface{}{ - "severity": []interface{}{"LOW", "MEDIUM"}, - }, - CompareToID: "2", - }, - }, - }, - }, - patchMergedVulnerabilityID: func(vulnerability []MergedVulnerability) []MergedVulnerability { - vulnerability[2].ID = "3" - return vulnerability - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got := handleVulnerabilityWithExistingKey(tt.args.mergedVulnerabilities, tt.args.otherVulnerability, tt.args.otherScannerInfo) - if tt.patchMergedVulnerabilityID != nil { - got = tt.patchMergedVulnerabilityID(got) - } - assert.DeepEqual(t, got, tt.want, cmpopts.IgnoreTypes(VulnerabilityDiff{}.ASCIIDiff)) - }) - } -} - -func Test_getDiff(t *testing.T) { - type args struct { - vulnerability Vulnerability - compareToVulnerability Vulnerability - compareToID string - } - tests := []struct { - name string - args args - want *VulnerabilityDiff - wantErr bool - }{ - { - name: "diff in fix", - args: args{ - vulnerability: Vulnerability{ - ID: "id", - Fix: Fix{ - Versions: []string{"1", "3"}, - State: "not fixed", - }, - }, - compareToVulnerability: Vulnerability{ - ID: "id", - Fix: Fix{ - Versions: []string{"1", "2"}, - State: "fixed", - }, - }, - compareToID: "compareToID", - }, - want: &VulnerabilityDiff{ - CompareToID: "compareToID", - JSONDiff: map[string]interface{}{ - "fix": map[string]interface{}{ - "state": []interface{}{"fixed", "not fixed"}, - "versions": map[string]interface{}{ - "1": []interface{}{"2", "3"}, // diff in array index 1 - "_t": "a", // sign for delta json - }, - }, - }, - }, - wantErr: false, - }, - { - name: "diff in links", - args: args{ - vulnerability: Vulnerability{ - ID: "id", - Links: []string{"link1", "link2"}, - }, - compareToVulnerability: Vulnerability{ - ID: "id", - Links: []string{"link1", "link3", "link4"}, - }, - compareToID: "compareToID", - }, - want: &VulnerabilityDiff{ - CompareToID: "compareToID", - JSONDiff: map[string]interface{}{ - "links": map[string]interface{}{ - "1": []interface{}{"link4", "link2"}, - // "_1" means object was deleted from index 1 - // more info in github.com/yudai/gojsondiff@v1.0.0/formatter/delta.go - "_1": []interface{}{"link3", 0, formatter.DeltaDelete}, - "_t": "a", // sign for delta json - }, - }, - // ASCIIDiff: "{\n \"cvss\": null,\n \"distro\": {\n \"idLike\": null,\n \"name\": \"\",\n \"version\": \"\"\n },\n \"fix\": {\n \"state\": \"\",\n \"versions\": null\n },\n \"id\": \"id\",\n \"layerID\": \"\",\n \"links\": [\n 0: \"link1\",\n- 1: \"link4\",\n+ 1: \"link2\",\n- 1: \"link3\"\n 2: \"link4\"\n ],\n \"package\": {\n \"cpes\": null,\n \"language\": \"\",\n \"licenses\": null,\n \"name\": \"\",\n \"purl\": \"\",\n \"type\": \"\",\n \"version\": \"\"\n },\n \"path\": \"\"\n }\n ", - }, - wantErr: false, - }, - { - name: "no diff - CVSS sort is needed", - args: args{ - vulnerability: Vulnerability{ - CVSS: []CVSS{ - { - Version: "3", - Vector: "456", - }, - { - Version: "2", - Vector: "123", - }, - }, - }, - compareToVulnerability: Vulnerability{ - CVSS: []CVSS{ - { - Version: "2", - Vector: "123", - Metrics: CvssMetrics{}, - VendorMetadata: nil, - }, - { - Version: "3", - Vector: "456", - Metrics: CvssMetrics{}, - VendorMetadata: nil, - }, - }, - }, - compareToID: "compareToID", - }, - want: nil, - wantErr: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := getDiff(tt.args.vulnerability, tt.args.compareToVulnerability, tt.args.compareToID) - if (err != nil) != tt.wantErr { - t.Errorf("getDiff() error = %v, wantErr %v", err, tt.wantErr) - return - } - assert.DeepEqual(t, got, tt.want, cmpopts.IgnoreTypes(VulnerabilityDiff{}.ASCIIDiff)) - }) - } -} - func Test_sortArrays(t *testing.T) { type args struct { vulnerability Vulnerability @@ -505,239 +83,3 @@ func Test_sortArrays(t *testing.T) { }) } } - -func TestMergedResults_Merge(t *testing.T) { - vul := Vulnerability{ - ID: "id1", - Severity: "HIGH", - Package: Package{ - Name: "pkg-name", - Version: "pkg-version", - }, - } - sameVulDifferentSeverity := Vulnerability{ - ID: "id1", - Severity: "LOW", - Package: Package{ - Name: "pkg-name", - Version: "pkg-version", - }, - } - differentVulID := Vulnerability{ - ID: "id2", - Severity: "HIGH", - Package: Package{ - Name: "pkg-name", - Version: "pkg-version", - }, - } - type fields struct { - MergedVulnerabilities map[VulnerabilityKey][]MergedVulnerability - } - tests := []struct { - name string - fields fields - args *ScannerResult - want *Result - patchMergedVulnerabilityID func(map[VulnerabilityKey][]MergedVulnerability) map[VulnerabilityKey][]MergedVulnerability - }{ - { - name: "all are non mutual vulnerabilities", - fields: fields{ - MergedVulnerabilities: NewResult().MergedVulnerabilitiesByKey, - }, - args: &ScannerResult{ - Vulnerabilities: []Vulnerability{ - vul, - differentVulID, - }, - Scanner: ScannerInfo{ - Name: "scanner1", - }, - }, - want: &Result{ - MergedVulnerabilitiesByKey: map[VulnerabilityKey][]MergedVulnerability{ - NewVulnerabilityKey(vul): { - { - ID: "0", - Vulnerability: vul, - ScannersInfo: []ScannerInfo{ - { - Name: "scanner1", - }, - }, - }, - }, - NewVulnerabilityKey(differentVulID): { - { - ID: "1", - Vulnerability: differentVulID, - ScannersInfo: []ScannerInfo{ - { - Name: "scanner1", - }, - }, - }, - }, - }, - }, - patchMergedVulnerabilityID: func(v map[VulnerabilityKey][]MergedVulnerability) map[VulnerabilityKey][]MergedVulnerability { - v[NewVulnerabilityKey(vul)][0].ID = "0" - v[NewVulnerabilityKey(differentVulID)][0].ID = "1" - return v - }, - }, - { - name: "1 non mutual vulnerability and 1 mutual vulnerability with no diff", - fields: fields{ - MergedVulnerabilities: map[VulnerabilityKey][]MergedVulnerability{ - NewVulnerabilityKey(vul): { - { - ID: "0", - Vulnerability: vul, - ScannersInfo: []ScannerInfo{ - { - Name: "scanner1", - }, - }, - }, - }, - }, - }, - args: &ScannerResult{ - Vulnerabilities: []Vulnerability{ - vul, - differentVulID, - }, - Scanner: ScannerInfo{ - Name: "scanner2", - }, - }, - want: &Result{ - MergedVulnerabilitiesByKey: map[VulnerabilityKey][]MergedVulnerability{ - NewVulnerabilityKey(vul): { - { - ID: "0", - Vulnerability: vul, - ScannersInfo: []ScannerInfo{ - { - Name: "scanner1", - }, - { - Name: "scanner2", - }, - }, - }, - }, - NewVulnerabilityKey(differentVulID): { - { - ID: "1", - Vulnerability: differentVulID, - ScannersInfo: []ScannerInfo{ - { - Name: "scanner2", - }, - }, - }, - }, - }, - }, - patchMergedVulnerabilityID: func(v map[VulnerabilityKey][]MergedVulnerability) map[VulnerabilityKey][]MergedVulnerability { - v[NewVulnerabilityKey(vul)][0].ID = "0" - v[NewVulnerabilityKey(differentVulID)][0].ID = "1" - return v - }, - }, - { - name: "1 non mutual vulnerability and 1 mutual vulnerability with diff", - fields: fields{ - MergedVulnerabilities: map[VulnerabilityKey][]MergedVulnerability{ - NewVulnerabilityKey(vul): { - { - ID: "0", - Vulnerability: vul, - ScannersInfo: []ScannerInfo{ - { - Name: "scanner1", - }, - }, - }, - }, - }, - }, - args: &ScannerResult{ - Vulnerabilities: []Vulnerability{ - sameVulDifferentSeverity, // mutual vulnerability with diff - differentVulID, // non mutual - }, - Scanner: ScannerInfo{ - Name: "scanner2", - }, - }, - want: &Result{ - MergedVulnerabilitiesByKey: map[VulnerabilityKey][]MergedVulnerability{ - NewVulnerabilityKey(vul): { - { - ID: "0", - Vulnerability: vul, - ScannersInfo: []ScannerInfo{ - { - Name: "scanner1", - }, - }, - }, - { - ID: "1", - Vulnerability: sameVulDifferentSeverity, - ScannersInfo: []ScannerInfo{ - { - Name: "scanner2", - }, - }, - Diffs: []VulnerabilityDiff{ - { - JSONDiff: map[string]interface{}{ - "severity": []interface{}{"HIGH", "LOW"}, - }, - CompareToID: "0", - }, - }, - }, - }, - NewVulnerabilityKey(differentVulID): { - { - ID: "0", - Vulnerability: differentVulID, - ScannersInfo: []ScannerInfo{ - { - Name: "scanner2", - }, - }, - }, - }, - }, - }, - patchMergedVulnerabilityID: func(v map[VulnerabilityKey][]MergedVulnerability) map[VulnerabilityKey][]MergedVulnerability { - v[NewVulnerabilityKey(vul)][0].ID = "0" - v[NewVulnerabilityKey(vul)][1].ID = "1" - v[NewVulnerabilityKey(differentVulID)][0].ID = "0" - return v - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got := &Result{ - MergedVulnerabilitiesByKey: tt.fields.MergedVulnerabilities, - } - got.Merge(families.ScanInputMetadata{}, tt.args) - if tt.patchMergedVulnerabilityID != nil { - got.MergedVulnerabilitiesByKey = tt.patchMergedVulnerabilityID(got.MergedVulnerabilitiesByKey) - } - assert.DeepEqual(t, got, tt.want, cmpopts.IgnoreTypes( - families.ScanMetadata{}, - VulnerabilityDiff{}.ASCIIDiff, - )) - }) - } -} diff --git a/scanner/families/vulnerabilities/types/vulnerability.go b/scanner/families/vulnerabilities/types/vulnerability.go index 5b74cd3dd..831486985 100644 --- a/scanner/families/vulnerabilities/types/vulnerability.go +++ b/scanner/families/vulnerabilities/types/vulnerability.go @@ -20,10 +20,9 @@ import ( "fmt" "sort" - "github.com/google/uuid" log "github.com/sirupsen/logrus" - "github.com/yudai/gojsondiff" - "github.com/yudai/gojsondiff/formatter" + + "github.com/openclarity/vmclarity/core/to" ) type Vulnerability struct { @@ -70,6 +69,16 @@ type Fix struct { State string `json:"state"` } +func (f Fix) enrich(ff Fix) Fix { + f.Versions = to.UniqueSlice(append(f.Versions, ff.Versions...)) + + if f.State == "" || f.State == "unknown" { + f.State = ff.State + } + + return f +} + type Package struct { Name string `json:"name"` Version string `json:"version"` @@ -87,157 +96,55 @@ type Distro struct { IDLike []string `json:"idLike"` // the ID_LIKE field found within the /etc/os-release file } -type VulnerabilityKey string // Unique identification of a vulnerability ID per package (name and version) - -func NewVulnerabilityKey(vulnerability Vulnerability) VulnerabilityKey { - return VulnerabilityKey(fmt.Sprintf("%s.%s.%s", vulnerability.ID, vulnerability.Package.Name, vulnerability.Package.Version)) -} - -type VulnerabilityDiff struct { - CompareToID string `json:"compareToID"` - JSONDiff map[string]interface{} `json:"jsonDiff"` - ASCIIDiff string `json:"asciiDiff"` -} - -type MergedVulnerability struct { - ID string `json:"id"` // Merged vulnerability ID used in DiffInfo - THIS IS NOT THE CVE ID - Vulnerability Vulnerability `json:"vulnerability"` - Diffs []VulnerabilityDiff `json:"diffs"` - ScannersInfo []ScannerInfo `json:"scanners"` -} - -func (mv *MergedVulnerability) AppendScannerInfo(info ScannerInfo) *MergedVulnerability { - mv.ScannersInfo = append(mv.ScannersInfo, info) - return mv -} - -func (mv *MergedVulnerability) AppendDiffInfo(diff VulnerabilityDiff) *MergedVulnerability { - mv.Diffs = append(mv.Diffs, diff) - return mv -} - -func NewMergedVulnerability(vulnerability Vulnerability, info ScannerInfo) *MergedVulnerability { - return &MergedVulnerability{ - ID: uuid.New().String(), - Vulnerability: vulnerability, - ScannersInfo: []ScannerInfo{info}, +func (d Distro) enrich(dd Distro) Distro { + if d.Name == "" { + d.Name = dd.Name } -} -// handleVulnerabilityWithExistingKey will look for an identical vulnerability for the given otherVulnerability in the mergedVulnerabilities list, -// if identical vulnerability was found, the new scanner info (otherScannerInfo) will be added -// if no identical vulnerability was found, a new MergedVulnerability (with all the differences that was found) will be added. -func handleVulnerabilityWithExistingKey(mergedVulnerabilities []MergedVulnerability, otherVulnerability Vulnerability, otherScannerInfo ScannerInfo) []MergedVulnerability { - shouldAppendMergedVulnerabilityCandidate := true - mergedVulnerabilityCandidate := NewMergedVulnerability(otherVulnerability, otherScannerInfo) - - for i := range mergedVulnerabilities { - diff, err := getDiff(otherVulnerability, mergedVulnerabilities[i].Vulnerability, mergedVulnerabilities[i].ID) - if err != nil { - log.Warnf("Failed to calculate diff - keeping both vulnerabilities: %v", err) - } else if diff != nil { - // diff found - need to append diff info - log.Debugf("Vulnerability results from %v is different from %v. diff=%+v", mergedVulnerabilities[i].ScannersInfo, otherScannerInfo, diff) - mergedVulnerabilityCandidate = mergedVulnerabilityCandidate.AppendDiffInfo(*diff) - } else { - // no diff - need to append scanner info - log.Debugf("Vulnerability results from %v is equal to %v", mergedVulnerabilities[i].ScannersInfo, otherScannerInfo) - mergedVulnerabilities[i].AppendScannerInfo(otherScannerInfo) - shouldAppendMergedVulnerabilityCandidate = false - break - } + if d.Version == "" { + d.Version = dd.Version } - if shouldAppendMergedVulnerabilityCandidate { - mergedVulnerabilities = append(mergedVulnerabilities, *mergedVulnerabilityCandidate) - } + d.IDLike = to.UniqueSlice(append(d.IDLike, dd.IDLike...)) - return mergedVulnerabilities + return d } -func getDiff(vulnerability, compareToVulnerability Vulnerability, compareToID string) (*VulnerabilityDiff, error) { - compareToVulnerabilityB, err := json.Marshal(compareToVulnerability.sorted()) - if err != nil { - return nil, fmt.Errorf("failed to Marshal. compareToVulnerability=%+v: %w", compareToVulnerability, err) - } - - vulnerabilityB, err := json.Marshal(vulnerability.sorted()) - if err != nil { - return nil, fmt.Errorf("failed to Marshal. vulnerability=%+v: %w", vulnerability, err) - } - - differ := gojsondiff.New() - diff, err := differ.Compare(compareToVulnerabilityB, vulnerabilityB) - if err != nil { - return nil, fmt.Errorf("failed to compare vulnerabilities: %w", err) - } - - // nolint:nilnil - if !diff.Modified() { - return nil, nil - } - - var templateJSON map[string]interface{} - err = json.Unmarshal(compareToVulnerabilityB, &templateJSON) - if err != nil { - return nil, fmt.Errorf("failed to Unmarshal. compareToVulnerabilityB=%s: %w", string(compareToVulnerabilityB), err) - } - - asciiDiff, err := getASCIIFormatDiff(compareToVulnerabilityB, diff) - if err != nil { - return nil, fmt.Errorf("failed to get ascii format diff: %w", err) - } - - jsonDiff, err := formatter.NewDeltaFormatter().FormatAsJson(diff) - if err != nil { - return nil, fmt.Errorf("failed to format delta diff: %w", err) - } - - // TODO: do we want to ignore some fields in the diff calculation, links for example? +type VulnerabilityKey string // Unique identification of a vulnerability ID per package (name and version) - return &VulnerabilityDiff{ - JSONDiff: jsonDiff, - ASCIIDiff: asciiDiff, - CompareToID: compareToID, - }, nil +func NewVulnerabilityKey(vulnerability Vulnerability) VulnerabilityKey { + return VulnerabilityKey(fmt.Sprintf("%s.%s.%s", vulnerability.ID, vulnerability.Package.Name, vulnerability.Package.Version)) } -func getASCIIFormatDiff(compareToVulnerabilityB []byte, diff gojsondiff.Diff) (string, error) { - config := formatter.AsciiFormatterConfig{ - ShowArrayIndex: true, +// handleVulnerabilityWithExistingKey merges two vulnerabilities with the same key. +func handleVulnerabilityWithExistingKey(vulnerability Vulnerability, otherVulnerability Vulnerability) Vulnerability { + // Adopt longest description + description := vulnerability.Description + if len(otherVulnerability.Description) > len(description) { + description = otherVulnerability.Description } - var compareToVulnerabilityJSON map[string]interface{} - _ = json.Unmarshal(compareToVulnerabilityB, &compareToVulnerabilityJSON) - asciiDiff, err := formatter.NewAsciiFormatter(compareToVulnerabilityJSON, config).Format(diff) - if err != nil { - return "", fmt.Errorf("failed to format ascii diff: %w", err) - } + // Merge CVSS + cvss := to.UniqueSliceByKey(append(vulnerability.CVSS, otherVulnerability.CVSS...), func(cvss CVSS) string { + b, err := json.Marshal(cvss) + if err != nil { + log.Errorf("failed to marshal cvss. c=%+v: %v", cvss, err) + return "" // skip + } - return asciiDiff, nil -} + return string(b) + }) -func toVulnerabilityByKey(vulnerabilities []Vulnerability) map[VulnerabilityKey]Vulnerability { - ret := make(map[VulnerabilityKey]Vulnerability, len(vulnerabilities)) - for _, vulnerability := range vulnerabilities { - key := NewVulnerabilityKey(vulnerability) - if log.IsLevelEnabled(log.DebugLevel) { - if vul, ok := ret[key]; ok { - diff, err := getDiff(vul, vulnerability, "") - if err != nil { - // nolint:errchkjson - vulB, _ := json.Marshal(vul) - // nolint:errchkjson - newVulB, _ := json.Marshal(vulnerability) - log.Debugf("Existing vul with the same key %q. vul=%s, newVul=%s", key, vulB, newVulB) - } else if diff != nil { - log.Debugf("Existing vul with the same key %q. diff.JSONDiff=%+v", key, diff.JSONDiff) - } else { - log.Debugf("Existing vul with the same key %q - no diff", key) - } - } - } - ret[key] = vulnerability + return Vulnerability{ + ID: vulnerability.ID, // Keep the original ID + Description: description, + Links: to.UniqueSlice(append(vulnerability.Links, otherVulnerability.Links...)), + Distro: vulnerability.Distro.enrich(otherVulnerability.Distro), + CVSS: cvss, + Fix: vulnerability.Fix.enrich(otherVulnerability.Fix), + Severity: vulnerability.Severity, // Keep the original severity + Package: vulnerability.Package, // Keep the original package + LayerID: vulnerability.LayerID, // Keep the original layerID + Path: vulnerability.Path, // Keep the original path } - return ret } diff --git a/scanner/go.mod b/scanner/go.mod index 68093ae9a..e2f48c9b0 100644 --- a/scanner/go.mod +++ b/scanner/go.mod @@ -36,7 +36,6 @@ require ( github.com/sourcegraph/conc v0.3.0 github.com/tdewolff/parse/v2 v2.7.15 github.com/vulsio/go-exploitdb v0.4.6 - github.com/yudai/gojsondiff v1.0.0 golang.org/x/crypto v0.25.0 golang.org/x/sync v0.7.0 gopkg.in/yaml.v3 v3.0.1 @@ -419,8 +418,6 @@ require ( github.com/xlab/treeprint v1.2.0 // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect github.com/yashtewari/glob-intersection v0.2.0 // indirect - github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 // indirect - github.com/yudai/pp v2.0.1+incompatible // indirect github.com/zclconf/go-cty v1.14.4 // indirect github.com/zclconf/go-cty-yaml v1.0.3 // indirect go.etcd.io/bbolt v1.3.10 // indirect diff --git a/scanner/go.sum b/scanner/go.sum index f59c29fdc..8cb2fd7ba 100644 --- a/scanner/go.sum +++ b/scanner/go.sum @@ -1556,12 +1556,6 @@ github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavM github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= github.com/yashtewari/glob-intersection v0.2.0 h1:8iuHdN88yYuCzCdjt0gDe+6bAhUwBeEWqThExu54RFg= github.com/yashtewari/glob-intersection v0.2.0/go.mod h1:LK7pIC3piUjovexikBbJ26Yml7g8xa5bsjfx2v1fwok= -github.com/yudai/gojsondiff v1.0.0 h1:27cbfqXLVEJ1o8I6v3y9lg8Ydm53EKqHXAOMxEGlCOA= -github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg= -github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 h1:BHyfKlQyqbsFN5p3IfnEUduWvb9is428/nNb5L3U01M= -github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM= -github.com/yudai/pp v2.0.1+incompatible h1:Q4//iY4pNF6yPLZIigmvcl7k/bPgrcTPIFIcmawg5bI= -github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZkTdatxwunjIkc= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=