feat: Record where CPEs come from (#2552)

Syft can get CPEs from several source, including generating them based on
package data, finding them in the NVD CPE dictionary, or finding them declared
in a manifest or existing SBOM. Record where Syft got CPEs so that consumers of
SBOMs can reason about how trustworthy they are.

Signed-off-by: Will Murphy <will.murphy@anchore.com>
This commit is contained in:
William Murphy
2024-02-02 16:17:52 +00:00
committed by GitHub
parent 4fe50f4169
commit b7a6d5e946
47 changed files with 2674 additions and 287 deletions
+1 -1
View File
@@ -1282,7 +1282,7 @@ func TestCatalogerConfig_MarshalJSON(t *testing.T) {
Qualifiers: nil,
Subpath: "subpath",
},
CPEs: []cpe.CPE{cpe.Must("cpe:2.3:a:some:app:*:*:*:*:*:*:*:*")},
CPEs: []cpe.CPE{cpe.Must("cpe:2.3:a:some:app:*:*:*:*:*:*:*:*", cpe.GeneratedSource)},
},
},
},
+4 -3
View File
@@ -56,7 +56,7 @@ func (cfg Classifier) MarshalJSON() ([]byte, error) {
var marshalledCPEs []string
for _, c := range cfg.CPEs {
marshalledCPEs = append(marshalledCPEs, c.BindToFmtString())
marshalledCPEs = append(marshalledCPEs, c.Attributes.BindToFmtString())
}
m := marshalled{
@@ -225,10 +225,11 @@ func getContents(resolver file.Resolver, location file.Location) ([]byte, error)
return contents, nil
}
// singleCPE returns a []pkg.CPE based on the cpe string or panics if the CPE is invalid
// singleCPE returns a []cpe.CPE with Source: Generated based on the cpe string or panics if the
// cpe string cannot be parsed into valid CPE Attributes
func singleCPE(cpeString string) []cpe.CPE {
return []cpe.CPE{
cpe.Must(cpeString),
cpe.Must(cpeString, cpe.GeneratedSource),
}
}
+6 -6
View File
@@ -30,14 +30,14 @@ func Test_ClassifierCPEs(t *testing.T) {
cpes: nil,
},
{
name: "one CPE",
name: "one Attributes",
fixture: "test-fixtures/version.txt",
classifier: Classifier{
Package: "some-app",
FileGlob: "**/version.txt",
EvidenceMatcher: FileContentsVersionMatcher(`(?m)my-verison:(?P<version>[0-9.]+)`),
CPEs: []cpe.CPE{
cpe.Must("cpe:2.3:a:some:app:*:*:*:*:*:*:*:*"),
cpe.Must("cpe:2.3:a:some:app:*:*:*:*:*:*:*:*", cpe.GeneratedSource),
},
},
cpes: []string{
@@ -52,8 +52,8 @@ func Test_ClassifierCPEs(t *testing.T) {
FileGlob: "**/version.txt",
EvidenceMatcher: FileContentsVersionMatcher(`(?m)my-verison:(?P<version>[0-9.]+)`),
CPEs: []cpe.CPE{
cpe.Must("cpe:2.3:a:some:app:*:*:*:*:*:*:*:*"),
cpe.Must("cpe:2.3:a:some:apps:*:*:*:*:*:*:*:*"),
cpe.Must("cpe:2.3:a:some:app:*:*:*:*:*:*:*:*", cpe.GeneratedSource),
cpe.Must("cpe:2.3:a:some:apps:*:*:*:*:*:*:*:*", cpe.GeneratedSource),
},
},
cpes: []string{
@@ -79,7 +79,7 @@ func Test_ClassifierCPEs(t *testing.T) {
var cpes []string
for _, c := range p.CPEs {
cpes = append(cpes, c.String())
cpes = append(cpes, c.Attributes.String())
}
require.Equal(t, test.cpes, cpes)
})
@@ -109,7 +109,7 @@ func TestClassifier_MarshalJSON(t *testing.T) {
Qualifiers: nil,
Subpath: "subpath",
},
CPEs: []cpe.CPE{cpe.Must("cpe:2.3:a:some:app:*:*:*:*:*:*:*:*")},
CPEs: []cpe.CPE{cpe.Must("cpe:2.3:a:some:app:*:*:*:*:*:*:*:*", cpe.GeneratedSource)},
},
want: `{"class":"class","fileGlob":"glob","package":"pkg","purl":"pkg:type/namespace/name@version#subpath","cpes":["cpe:2.3:a:some:app:*:*:*:*:*:*:*:*"]}`,
},
@@ -23,8 +23,8 @@ func DefaultClassifiers() []Classifier {
Package: "python",
PURL: mustPURL("pkg:generic/python@version"),
CPEs: []cpe.CPE{
cpe.Must("cpe:2.3:a:python_software_foundation:python:*:*:*:*:*:*:*:*"),
cpe.Must("cpe:2.3:a:python:python:*:*:*:*:*:*:*:*"),
cpe.Must("cpe:2.3:a:python_software_foundation:python:*:*:*:*:*:*:*:*", cpe.GeneratedSource),
cpe.Must("cpe:2.3:a:python:python:*:*:*:*:*:*:*:*", cpe.GeneratedSource),
},
},
{
@@ -34,8 +34,8 @@ func DefaultClassifiers() []Classifier {
Package: "python",
PURL: mustPURL("pkg:generic/python@version"),
CPEs: []cpe.CPE{
cpe.Must("cpe:2.3:a:python_software_foundation:python:*:*:*:*:*:*:*:*"),
cpe.Must("cpe:2.3:a:python:python:*:*:*:*:*:*:*:*"),
cpe.Must("cpe:2.3:a:python_software_foundation:python:*:*:*:*:*:*:*:*", cpe.GeneratedSource),
cpe.Must("cpe:2.3:a:python:python:*:*:*:*:*:*:*:*", cpe.GeneratedSource),
},
},
{
@@ -93,7 +93,7 @@ func DefaultClassifiers() []Classifier {
`(?m)\x00openjdk\x00java\x00(?P<release>[0-9]+[.0-9]*)\x00(?P<version>[0-9]+[^\x00]+)\x00`),
Package: "java",
PURL: mustPURL("pkg:generic/java@version"),
// TODO the updates might need to be part of the CPE, like: 1.8.0:update152
// TODO the updates might need to be part of the CPE Attributes, like: 1.8.0:update152
CPEs: singleCPE("cpe:2.3:a:oracle:openjdk:*:*:*:*:*:*:*:*"),
},
{
@@ -255,8 +255,8 @@ func DefaultClassifiers() []Classifier {
Package: "percona-server",
PURL: mustPURL("pkg:generic/percona-server@version"),
CPEs: []cpe.CPE{
cpe.Must("cpe:2.3:a:oracle:mysql:*:*:*:*:*:*:*:*"),
cpe.Must("cpe:2.3:a:percona:percona_server:*:*:*:*:*:*:*:*"),
cpe.Must("cpe:2.3:a:oracle:mysql:*:*:*:*:*:*:*:*", cpe.GeneratedSource),
cpe.Must("cpe:2.3:a:percona:percona_server:*:*:*:*:*:*:*:*", cpe.GeneratedSource),
},
},
{
@@ -267,9 +267,9 @@ func DefaultClassifiers() []Classifier {
Package: "percona-xtradb-cluster",
PURL: mustPURL("pkg:generic/percona-xtradb-cluster@version"),
CPEs: []cpe.CPE{
cpe.Must("cpe:2.3:a:oracle:mysql:*:*:*:*:*:*:*:*"),
cpe.Must("cpe:2.3:a:percona:percona_server:*:*:*:*:*:*:*:*"),
cpe.Must("cpe:2.3:a:percona:xtradb_cluster:*:*:*:*:*:*:*:*"),
cpe.Must("cpe:2.3:a:oracle:mysql:*:*:*:*:*:*:*:*", cpe.GeneratedSource),
cpe.Must("cpe:2.3:a:percona:percona_server:*:*:*:*:*:*:*:*", cpe.GeneratedSource),
cpe.Must("cpe:2.3:a:percona:xtradb_cluster:*:*:*:*:*:*:*:*", cpe.GeneratedSource),
},
},
{
@@ -363,8 +363,8 @@ func DefaultClassifiers() []Classifier {
Package: "nginx",
PURL: mustPURL("pkg:generic/nginx@version"),
CPEs: []cpe.CPE{
cpe.Must("cpe:2.3:a:f5:nginx:*:*:*:*:*:*:*:*"),
cpe.Must("cpe:2.3:a:nginx:nginx:*:*:*:*:*:*:*:*"),
cpe.Must("cpe:2.3:a:f5:nginx:*:*:*:*:*:*:*:*", cpe.GeneratedSource),
cpe.Must("cpe:2.3:a:nginx:nginx:*:*:*:*:*:*:*:*", cpe.GeneratedSource),
},
},
{
+2 -2
View File
@@ -21,8 +21,8 @@ func newPackage(classifier Classifier, location file.Location, matchMetadata map
var cpes []cpe.CPE
for _, c := range classifier.CPEs {
c.Version = version
c.Update = update
c.Attributes.Version = version
c.Attributes.Update = update
cpes = append(cpes, c)
}
+1 -1
View File
@@ -127,5 +127,5 @@ func generateStdlibCpe(version string) (stdlibCpe cpe.CPE, err error) {
cpeString = fmt.Sprintf("cpe:2.3:a:golang:go:%s:%s:*:*:*:*:*:*", vr, candidate)
}
return cpe.New(cpeString)
return cpe.New(cpeString, cpe.GeneratedSource)
}
+1 -1
View File
@@ -81,7 +81,7 @@ func Test_Binary_Cataloger_Stdlib_Cpe(t *testing.T) {
t.Run(tc.name, func(t *testing.T) {
got, err := generateStdlibCpe(tc.candidate)
assert.NoError(t, err, "expected no err; got %v", err)
assert.Equal(t, got.String(), tc.want)
assert.Equal(t, got.Attributes.String(), tc.want)
})
}
}
@@ -23,7 +23,7 @@ type upstreamCandidate struct {
}
func upstreamCandidates(m pkg.ApkDBEntry) (candidates []upstreamCandidate) {
// Do not consider OriginPackage variations when generating CPE candidates for the child package
// Do not consider OriginPackage variations when generating CPE Attributes candidates for the child package
// because doing so will result in false positives when matching to vulnerabilities in Grype since
// it won't know to lookup apk fix entries using the OriginPackage name.
@@ -90,7 +90,7 @@ func filterCpeList(cpeList CpeList) CpeList {
return processedCpeList
}
// normalizeCPE removes the version and update parts of a CPE.
// normalizeCPE removes the version and update parts of CPE Attributes.
func normalizeCPE(cpe *wfn.Attributes) *wfn.Attributes {
cpeCopy := *cpe
@@ -9,8 +9,8 @@ import (
const jenkinsName = "jenkins"
// filterFn instances should return true if the given CPE should be removed from a collection for the given package
type filterFn func(cpe cpe.CPE, p pkg.Package) bool
// filterFn instances should return true if the given CPE attributes should be removed from a collection for the given package
type filterFn func(cpe cpe.Attributes, p pkg.Package) bool
var cpeFilters = []filterFn{
disallowJiraClientServerMismatch,
@@ -19,7 +19,7 @@ var cpeFilters = []filterFn{
disallowNonParseableCPEs,
}
func filter(cpes []cpe.CPE, p pkg.Package, filters ...filterFn) (result []cpe.CPE) {
func filter(cpes []cpe.Attributes, p pkg.Package, filters ...filterFn) (result []cpe.Attributes) {
cpeLoop:
for _, c := range cpes {
for _, fn := range filters {
@@ -33,9 +33,9 @@ cpeLoop:
return result
}
func disallowNonParseableCPEs(c cpe.CPE, _ pkg.Package) bool {
func disallowNonParseableCPEs(c cpe.Attributes, _ pkg.Package) bool {
v := c.String()
_, err := cpe.New(v)
_, err := cpe.NewAttributes(v)
cannotParse := err != nil
@@ -43,15 +43,15 @@ func disallowNonParseableCPEs(c cpe.CPE, _ pkg.Package) bool {
}
// jenkins plugins should not match against jenkins
func disallowJenkinsServerCPEForPluginPackage(c cpe.CPE, p pkg.Package) bool {
func disallowJenkinsServerCPEForPluginPackage(c cpe.Attributes, p pkg.Package) bool {
if p.Type == pkg.JenkinsPluginPkg && c.Product == jenkinsName {
return true
}
return false
}
// filter to account that packages that are not for jenkins but have a CPE generated that will match against jenkins
func disallowJenkinsCPEsNotAssociatedWithJenkins(c cpe.CPE, p pkg.Package) bool {
// filter to account that packages that are not for jenkins but have a Attributes generated that will match against jenkins
func disallowJenkinsCPEsNotAssociatedWithJenkins(c cpe.Attributes, p pkg.Package) bool {
// jenkins server should only match against a product with the name jenkins
if c.Product == jenkinsName && !strings.Contains(strings.ToLower(p.Name), jenkinsName) {
if c.Vendor == cpe.Any || c.Vendor == jenkinsName || c.Vendor == "cloudbees" {
@@ -61,8 +61,8 @@ func disallowJenkinsCPEsNotAssociatedWithJenkins(c cpe.CPE, p pkg.Package) bool
return false
}
// filter to account for packages which are jira client packages but have a CPE that will match against jira
func disallowJiraClientServerMismatch(c cpe.CPE, p pkg.Package) bool {
// filter to account for packages which are jira client packages but have a Attributes that will match against jira
func disallowJiraClientServerMismatch(c cpe.Attributes, p pkg.Package) bool {
// jira / atlassian should not apply to clients
if c.Product == "jira" && strings.Contains(strings.ToLower(p.Name), "client") {
if c.Vendor == cpe.Any || c.Vendor == "jira" || c.Vendor == "atlassian" {
@@ -18,7 +18,7 @@ func Test_disallowJenkinsServerCPEForPluginPackage(t *testing.T) {
}{
{
name: "go case (filter out)",
cpe: cpe.Must("cpe:2.3:a:name:jenkins:3.2:*:*:*:*:*:*:*"),
cpe: cpe.Must("cpe:2.3:a:name:jenkins:3.2:*:*:*:*:*:*:*", cpe.GeneratedSource),
pkg: pkg.Package{
Type: pkg.JenkinsPluginPkg,
},
@@ -26,7 +26,7 @@ func Test_disallowJenkinsServerCPEForPluginPackage(t *testing.T) {
},
{
name: "ignore jenkins plugins with unique name",
cpe: cpe.Must("cpe:2.3:a:name:ci-jenkins:3.2:*:*:*:*:*:*:*"),
cpe: cpe.Must("cpe:2.3:a:name:ci-jenkins:3.2:*:*:*:*:*:*:*", cpe.GeneratedSource),
pkg: pkg.Package{
Type: pkg.JenkinsPluginPkg,
},
@@ -34,7 +34,7 @@ func Test_disallowJenkinsServerCPEForPluginPackage(t *testing.T) {
},
{
name: "ignore java packages",
cpe: cpe.Must("cpe:2.3:a:name:jenkins:3.2:*:*:*:*:*:*:*"),
cpe: cpe.Must("cpe:2.3:a:name:jenkins:3.2:*:*:*:*:*:*:*", cpe.GeneratedSource),
pkg: pkg.Package{
Type: pkg.JavaPkg,
},
@@ -43,7 +43,7 @@ func Test_disallowJenkinsServerCPEForPluginPackage(t *testing.T) {
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
assert.Equal(t, test.expected, disallowJenkinsServerCPEForPluginPackage(test.cpe, test.pkg))
assert.Equal(t, test.expected, disallowJenkinsServerCPEForPluginPackage(test.cpe.Attributes, test.pkg))
})
}
}
@@ -57,7 +57,7 @@ func Test_disallowJenkinsCPEsNotAssociatedWithJenkins(t *testing.T) {
}{
{
name: "filter out mismatched name (cloudbees vendor)",
cpe: cpe.Must("cpe:2.3:a:cloudbees:jenkins:3.2:*:*:*:*:*:*:*"),
cpe: cpe.Must("cpe:2.3:a:cloudbees:jenkins:3.2:*:*:*:*:*:*:*", cpe.GeneratedSource),
pkg: pkg.Package{
Name: "not-j*nkins",
Type: pkg.JavaPkg,
@@ -66,7 +66,7 @@ func Test_disallowJenkinsCPEsNotAssociatedWithJenkins(t *testing.T) {
},
{
name: "filter out mismatched name (jenkins vendor)",
cpe: cpe.Must("cpe:2.3:a:jenkins:jenkins:3.2:*:*:*:*:*:*:*"),
cpe: cpe.Must("cpe:2.3:a:jenkins:jenkins:3.2:*:*:*:*:*:*:*", cpe.GeneratedSource),
pkg: pkg.Package{
Name: "not-j*nkins",
Type: pkg.JavaPkg,
@@ -75,7 +75,7 @@ func Test_disallowJenkinsCPEsNotAssociatedWithJenkins(t *testing.T) {
},
{
name: "filter out mismatched name (any vendor)",
cpe: cpe.Must("cpe:2.3:a:*:jenkins:3.2:*:*:*:*:*:*:*"),
cpe: cpe.Must("cpe:2.3:a:*:jenkins:3.2:*:*:*:*:*:*:*", cpe.GeneratedSource),
pkg: pkg.Package{
Name: "not-j*nkins",
Type: pkg.JavaPkg,
@@ -84,7 +84,7 @@ func Test_disallowJenkinsCPEsNotAssociatedWithJenkins(t *testing.T) {
},
{
name: "ignore packages with the name jenkins",
cpe: cpe.Must("cpe:2.3:a:*:jenkins:3.2:*:*:*:*:*:*:*"),
cpe: cpe.Must("cpe:2.3:a:*:jenkins:3.2:*:*:*:*:*:*:*", cpe.GeneratedSource),
pkg: pkg.Package{
Name: "jenkins-thing",
Type: pkg.JavaPkg,
@@ -93,7 +93,7 @@ func Test_disallowJenkinsCPEsNotAssociatedWithJenkins(t *testing.T) {
},
{
name: "ignore product names that are not exactly 'jenkins'",
cpe: cpe.Must("cpe:2.3:a:*:jenkins-something-else:3.2:*:*:*:*:*:*:*"),
cpe: cpe.Must("cpe:2.3:a:*:jenkins-something-else:3.2:*:*:*:*:*:*:*", cpe.GeneratedSource),
pkg: pkg.Package{
Name: "not-j*nkins",
Type: pkg.JavaPkg,
@@ -103,7 +103,7 @@ func Test_disallowJenkinsCPEsNotAssociatedWithJenkins(t *testing.T) {
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
assert.Equal(t, test.expected, disallowJenkinsCPEsNotAssociatedWithJenkins(test.cpe, test.pkg))
assert.Equal(t, test.expected, disallowJenkinsCPEsNotAssociatedWithJenkins(test.cpe.Attributes, test.pkg))
})
}
}
@@ -117,7 +117,7 @@ func Test_disallowJiraClientServerMismatch(t *testing.T) {
}{
{
name: "filter out mismatched name (atlassian vendor)",
cpe: cpe.Must("cpe:2.3:a:atlassian:jira:3.2:*:*:*:*:*:*:*"),
cpe: cpe.Must("cpe:2.3:a:atlassian:jira:3.2:*:*:*:*:*:*:*", cpe.GeneratedSource),
pkg: pkg.Package{
Name: "something-client",
Type: pkg.JavaPkg,
@@ -126,7 +126,7 @@ func Test_disallowJiraClientServerMismatch(t *testing.T) {
},
{
name: "filter out mismatched name (jira vendor)",
cpe: cpe.Must("cpe:2.3:a:jira:jira:3.2:*:*:*:*:*:*:*"),
cpe: cpe.Must("cpe:2.3:a:jira:jira:3.2:*:*:*:*:*:*:*", cpe.GeneratedSource),
pkg: pkg.Package{
Name: "something-client",
Type: pkg.JavaPkg,
@@ -135,7 +135,7 @@ func Test_disallowJiraClientServerMismatch(t *testing.T) {
},
{
name: "filter out mismatched name (any vendor)",
cpe: cpe.Must("cpe:2.3:a:*:jira:3.2:*:*:*:*:*:*:*"),
cpe: cpe.Must("cpe:2.3:a:*:jira:3.2:*:*:*:*:*:*:*", cpe.GeneratedSource),
pkg: pkg.Package{
Name: "something-client",
Type: pkg.JavaPkg,
@@ -144,7 +144,7 @@ func Test_disallowJiraClientServerMismatch(t *testing.T) {
},
{
name: "ignore package names that do not have 'client'",
cpe: cpe.Must("cpe:2.3:a:*:jira:3.2:*:*:*:*:*:*:*"),
cpe: cpe.Must("cpe:2.3:a:*:jira:3.2:*:*:*:*:*:*:*", cpe.GeneratedSource),
pkg: pkg.Package{
Name: "jira-thing",
Type: pkg.JavaPkg,
@@ -153,7 +153,7 @@ func Test_disallowJiraClientServerMismatch(t *testing.T) {
},
{
name: "ignore product names that are not exactly 'jira'",
cpe: cpe.Must("cpe:2.3:a:*:jira-something-else:3.2:*:*:*:*:*:*:*"),
cpe: cpe.Must("cpe:2.3:a:*:jira-something-else:3.2:*:*:*:*:*:*:*", cpe.GeneratedSource),
pkg: pkg.Package{
Name: "not-j*ra",
Type: pkg.JavaPkg,
@@ -163,7 +163,7 @@ func Test_disallowJiraClientServerMismatch(t *testing.T) {
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
assert.Equal(t, test.expected, disallowJiraClientServerMismatch(test.cpe, test.pkg))
assert.Equal(t, test.expected, disallowJiraClientServerMismatch(test.cpe.Attributes, test.pkg))
})
}
}
@@ -22,7 +22,7 @@ import (
// the CPE database, so they will be preferred over other candidates:
var knownVendors = strset.New("apache")
func newCPE(product, vendor, version, targetSW string) *cpe.CPE {
func newCPE(product, vendor, version, targetSW string) *cpe.Attributes {
c := cpe.NewWithAny()
c.Part = "a"
c.Product = product
@@ -61,7 +61,7 @@ func GetIndexedDictionary() (_ *dictionary.Indexed, err error) {
func FromDictionaryFind(p pkg.Package) (cpe.CPE, bool) {
dict, err := GetIndexedDictionary()
if err != nil {
log.Debugf("dictionary CPE lookup not available: %+v", err)
log.Debugf("CPE dictionary lookup not available: %+v", err)
return cpe.CPE{}, false
}
@@ -96,12 +96,12 @@ func FromDictionaryFind(p pkg.Package) (cpe.CPE, bool) {
return cpe.CPE{}, false
}
parsedCPE, err := cpe.New(cpeString)
parsedCPE, err := cpe.New(cpeString, cpe.NVDDictionaryLookupSource)
if err != nil {
return cpe.CPE{}, false
}
parsedCPE.Version = p.Version
parsedCPE.Attributes.Version = p.Version
return parsedCPE, true
}
@@ -117,7 +117,7 @@ func FromPackageAttributes(p pkg.Package) []cpe.CPE {
}
keys := strset.New()
cpes := make([]cpe.CPE, 0)
cpes := make([]cpe.Attributes, 0)
for _, product := range products {
for _, vendor := range vendors {
// prevent duplicate entries...
@@ -137,8 +137,12 @@ func FromPackageAttributes(p pkg.Package) []cpe.CPE {
cpes = filter(cpes, p, cpeFilters...)
sort.Sort(cpe.BySpecificity(cpes))
var result []cpe.CPE
for _, c := range cpes {
result = append(result, cpe.CPE{Attributes: c, Source: cpe.GeneratedSource})
}
return cpes
return result
}
func candidateVendors(p pkg.Package) []string {
@@ -723,11 +723,14 @@ func TestGeneratePackageCPEs(t *testing.T) {
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
actual := FromPackageAttributes(test.p)
expectedCpeSet := set.NewStringSet()
for _, cpeStr := range test.expected {
expectedCpeSet.Add("syft-generated:" + cpeStr)
}
expectedCpeSet := set.NewStringSet(test.expected...)
actualCpeSet := set.NewStringSet()
for _, a := range actual {
actualCpeSet.Add(a.String())
actualCpeSet.Add(fmt.Sprintf("%s:%s", a.Source.String(), a.Attributes.String()))
}
extra := strset.Difference(actualCpeSet, expectedCpeSet).List()
@@ -1007,7 +1010,7 @@ func TestDictionaryFindIsWired(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
got, gotExists := FromDictionaryFind(tt.pkg)
assert.Equal(t, tt.want, got.BindToFmtString())
assert.Equal(t, tt.want, got.Attributes.BindToFmtString())
assert.Equal(t, tt.wantExists, gotExists)
})
}
@@ -117,9 +117,9 @@ func (c *nativeImageCataloger) Name() string {
func getPackage(component nativeImageComponent) pkg.Package {
var cpes []cpe.CPE
for _, property := range component.Properties {
c, err := cpe.New(property.Value)
c, err := cpe.New(property.Value, cpe.DeclaredSource)
if err != nil {
log.Debugf("unable to parse CPE: %v", err)
log.Debugf("unable to parse Attributes: %v", err)
continue
}
cpes = append(cpes, c)
@@ -80,22 +80,31 @@ func TestParseNativeImageSbom(t *testing.T) {
},
CPEs: []cpe.CPE{
{
Part: "a",
Vendor: "codec",
Product: "codec",
Version: "4.1.73.Final",
Attributes: cpe.Attributes{
Part: "a",
Vendor: "codec",
Product: "codec",
Version: "4.1.73.Final",
},
Source: "declared",
},
{
Part: "a",
Vendor: "codec",
Product: "netty-codec-http2",
Version: "4.1.73.Final",
Attributes: cpe.Attributes{
Part: "a",
Vendor: "codec",
Product: "netty-codec-http2",
Version: "4.1.73.Final",
},
Source: "declared",
},
{
Part: "a",
Vendor: "codec",
Product: "netty_codec_http2",
Version: "4.1.73.Final",
Attributes: cpe.Attributes{
Part: "a",
Vendor: "codec",
Product: "netty_codec_http2",
Version: "4.1.73.Final",
},
Source: "declared",
},
},
},
+1 -11
View File
@@ -14,21 +14,11 @@ import (
func mustCPEs(s ...string) (c []cpe.CPE) {
for _, i := range s {
c = append(c, mustCPE(i))
c = append(c, cpe.Must(i, ""))
}
return
}
func mustCPE(c string) cpe.CPE {
return must(cpe.New(c))
}
func must(c cpe.CPE, e error) cpe.CPE {
if e != nil {
panic(e)
}
return c
}
func Test_parseSBOM(t *testing.T) {
expectedPkgs := []pkg.Package{
{
+2 -2
View File
@@ -368,7 +368,7 @@ func TestCatalog_MergeRecords(t *testing.T) {
name: "multiple Locations with shared path",
pkgs: []Package{
{
CPEs: []cpe.CPE{cpe.Must("cpe:2.3:a:package:1:1:*:*:*:*:*:*:*")},
CPEs: []cpe.CPE{cpe.Must("cpe:2.3:a:package:1:1:*:*:*:*:*:*:*", cpe.GeneratedSource)},
Locations: file.NewLocationSet(
file.NewVirtualLocationFromCoordinates(
file.Coordinates{
@@ -381,7 +381,7 @@ func TestCatalog_MergeRecords(t *testing.T) {
Type: RpmPkg,
},
{
CPEs: []cpe.CPE{cpe.Must("cpe:2.3:b:package:1:1:*:*:*:*:*:*:*")},
CPEs: []cpe.CPE{cpe.Must("cpe:2.3:b:package:1:1:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource)},
Locations: file.NewLocationSet(
file.NewVirtualLocationFromCoordinates(
file.Coordinates{
+7 -7
View File
@@ -34,7 +34,7 @@ func TestIDUniqueness(t *testing.T) {
Language: "math",
Type: PythonPkg,
CPEs: []cpe.CPE{
cpe.Must(`cpe:2.3:a:Archimedes:pi:3.14:*:*:*:*:math:*:*`),
cpe.Must(`cpe:2.3:a:Archimedes:pi:3.14:*:*:*:*:math:*:*`, cpe.NVDDictionaryLookupSource),
},
PURL: "pkg:pypi/pi@3.14",
Metadata: PythonPackage{
@@ -256,7 +256,7 @@ func TestPackage_Merge(t *testing.T) {
Language: "math",
Type: PythonPkg,
CPEs: []cpe.CPE{
cpe.Must(`cpe:2.3:a:Archimedes:pi:3.14:*:*:*:*:math:*:*`),
cpe.Must(`cpe:2.3:a:Archimedes:pi:3.14:*:*:*:*:math:*:*`, cpe.NVDDictionaryLookupSource),
},
PURL: "pkg:pypi/pi@3.14",
Metadata: PythonPackage{
@@ -278,7 +278,7 @@ func TestPackage_Merge(t *testing.T) {
Language: "math",
Type: PythonPkg,
CPEs: []cpe.CPE{
cpe.Must(`cpe:2.3:a:DIFFERENT:pi:3.14:*:*:*:*:math:*:*`), // NOTE: difference
cpe.Must(`cpe:2.3:a:DIFFERENT:pi:3.14:*:*:*:*:math:*:*`, cpe.NVDDictionaryLookupSource), // NOTE: difference
},
PURL: "pkg:pypi/pi@3.14",
Metadata: PythonPackage{
@@ -301,8 +301,8 @@ func TestPackage_Merge(t *testing.T) {
Language: "math",
Type: PythonPkg,
CPEs: []cpe.CPE{
cpe.Must(`cpe:2.3:a:Archimedes:pi:3.14:*:*:*:*:math:*:*`),
cpe.Must(`cpe:2.3:a:DIFFERENT:pi:3.14:*:*:*:*:math:*:*`), // NOTE: merge!
cpe.Must(`cpe:2.3:a:Archimedes:pi:3.14:*:*:*:*:math:*:*`, cpe.NVDDictionaryLookupSource),
cpe.Must(`cpe:2.3:a:DIFFERENT:pi:3.14:*:*:*:*:math:*:*`, cpe.NVDDictionaryLookupSource), // NOTE: merge!
},
PURL: "pkg:pypi/pi@3.14",
Metadata: PythonPackage{
@@ -327,7 +327,7 @@ func TestPackage_Merge(t *testing.T) {
Language: "math",
Type: PythonPkg,
CPEs: []cpe.CPE{
cpe.Must(`cpe:2.3:a:Archimedes:pi:3.14:*:*:*:*:math:*:*`),
cpe.Must(`cpe:2.3:a:Archimedes:pi:3.14:*:*:*:*:math:*:*`, cpe.NVDDictionaryLookupSource),
},
PURL: "pkg:pypi/pi@3.14",
Metadata: PythonPackage{
@@ -349,7 +349,7 @@ func TestPackage_Merge(t *testing.T) {
Language: "math",
Type: PythonPkg,
CPEs: []cpe.CPE{
cpe.Must(`cpe:2.3:a:Archimedes:pi:3.14:*:*:*:*:math:*:*`),
cpe.Must(`cpe:2.3:a:Archimedes:pi:3.14:*:*:*:*:math:*:*`, cpe.NVDDictionaryLookupSource),
},
PURL: "pkg:pypi/pi@3.14",
Metadata: PythonPackage{