feat: support multiple known CPEs in index (#2813)

It is possible that a given package has multiple known "official" CPEs
active in the dictionary at once, so the index should support a slice of
CPE strings per package

Signed-off-by: Weston Steimel <commits@weston.slmail.me>
This commit is contained in:
Weston Steimel 2024-04-25 15:22:26 +01:00 committed by GitHub
parent f2fc10aa86
commit 9604e3dc9c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 6970 additions and 2344 deletions

View File

@ -111,10 +111,10 @@ func NewPackageTask(cfg CatalogingFactoryConfig, c pkg.Cataloger, tags ...string
if cfg.DataGenerationConfig.GenerateCPEs {
// generate CPEs (note: this is excluded from package ID, so is safe to mutate)
// we might have binary classified CPE already with the package so we want to append here
dictionaryCPE, ok := cpe.DictionaryFind(p)
dictionaryCPEs, ok := cpe.DictionaryFind(p)
if ok {
log.Tracef("used CPE dictionary to find CPE for %s package %q: %s", p.Type, p.Name, dictionaryCPE.Attributes.BindToFmtString())
p.CPEs = append(p.CPEs, dictionaryCPE)
log.Tracef("used CPE dictionary to find CPEs for %s package %q: %s", p.Type, p.Name, dictionaryCPEs)
p.CPEs = append(p.CPEs, dictionaryCPEs...)
} else {
p.CPEs = append(p.CPEs, cpe.Generate(p)...)
}

View File

@ -10,6 +10,6 @@ func Generate(p pkg.Package) []cpe.CPE {
return cpegenerate.FromPackageAttributes(p)
}
func DictionaryFind(p pkg.Package) (cpe.CPE, bool) {
func DictionaryFind(p pkg.Package) ([]cpe.CPE, bool) {
return cpegenerate.FromDictionaryFind(p)
}

View File

@ -167,16 +167,24 @@ func indexCPEList(list CpeList) *dictionary.Indexed {
return indexed
}
func updateIndex(indexed *dictionary.Indexed, ecosystem string, pkgName string, cpe string) {
if _, exists := indexed.EcosystemPackages[ecosystem]; !exists {
indexed.EcosystemPackages[ecosystem] = make(dictionary.Packages)
}
if indexed.EcosystemPackages[ecosystem][pkgName] == nil {
indexed.EcosystemPackages[ecosystem][pkgName] = dictionary.NewSet()
}
indexed.EcosystemPackages[ecosystem][pkgName].Add(cpe)
}
func addEntryForRustCrate(indexed *dictionary.Indexed, ref string, cpeItemName string) {
// Prune off the non-package-name parts of the URL
ref = strings.TrimPrefix(ref, prefixForRustCrates)
ref = strings.Split(ref, "/")[0]
if _, ok := indexed.EcosystemPackages[dictionary.EcosystemRustCrates]; !ok {
indexed.EcosystemPackages[dictionary.EcosystemRustCrates] = make(dictionary.Packages)
}
indexed.EcosystemPackages[dictionary.EcosystemRustCrates][ref] = cpeItemName
updateIndex(indexed, dictionary.EcosystemRustCrates, ref, cpeItemName)
}
func addEntryForJenkinsPluginGitHub(indexed *dictionary.Indexed, ref string, cpeItemName string) {
@ -190,12 +198,7 @@ func addEntryForJenkinsPluginGitHub(indexed *dictionary.Indexed, ref string, cpe
}
ref = strings.TrimSuffix(ref, "-plugin")
if _, ok := indexed.EcosystemPackages[dictionary.EcosystemJenkinsPlugins]; !ok {
indexed.EcosystemPackages[dictionary.EcosystemJenkinsPlugins] = make(dictionary.Packages)
}
indexed.EcosystemPackages[dictionary.EcosystemJenkinsPlugins][ref] = cpeItemName
updateIndex(indexed, dictionary.EcosystemJenkinsPlugins, ref, cpeItemName)
}
func addEntryForJenkinsPlugin(indexed *dictionary.Indexed, ref string, cpeItemName string) {
@ -207,11 +210,7 @@ func addEntryForJenkinsPlugin(indexed *dictionary.Indexed, ref string, cpeItemNa
return
}
if _, ok := indexed.EcosystemPackages[dictionary.EcosystemJenkinsPlugins]; !ok {
indexed.EcosystemPackages[dictionary.EcosystemJenkinsPlugins] = make(dictionary.Packages)
}
indexed.EcosystemPackages[dictionary.EcosystemJenkinsPlugins][ref] = cpeItemName
updateIndex(indexed, dictionary.EcosystemJenkinsPlugins, ref, cpeItemName)
}
func addEntryForPyPIPackage(indexed *dictionary.Indexed, ref string, cpeItemName string) {
@ -219,11 +218,7 @@ func addEntryForPyPIPackage(indexed *dictionary.Indexed, ref string, cpeItemName
ref = strings.TrimPrefix(ref, prefixForPyPIPackages)
ref = strings.Split(ref, "/")[0]
if _, ok := indexed.EcosystemPackages[dictionary.EcosystemPyPI]; !ok {
indexed.EcosystemPackages[dictionary.EcosystemPyPI] = make(dictionary.Packages)
}
indexed.EcosystemPackages[dictionary.EcosystemPyPI][ref] = cpeItemName
updateIndex(indexed, dictionary.EcosystemPyPI, ref, cpeItemName)
}
func addEntryForNativeRubyGem(indexed *dictionary.Indexed, ref string, cpeItemName string) {
@ -231,11 +226,7 @@ func addEntryForNativeRubyGem(indexed *dictionary.Indexed, ref string, cpeItemNa
ref = strings.TrimPrefix(ref, prefixForNativeRubyGems)
ref = strings.Split(ref, "/")[0]
if _, ok := indexed.EcosystemPackages[dictionary.EcosystemRubyGems]; !ok {
indexed.EcosystemPackages[dictionary.EcosystemRubyGems] = make(dictionary.Packages)
}
indexed.EcosystemPackages[dictionary.EcosystemRubyGems][ref] = cpeItemName
updateIndex(indexed, dictionary.EcosystemRubyGems, ref, cpeItemName)
}
func addEntryForRubyGem(indexed *dictionary.Indexed, ref string, cpeItemName string) {
@ -244,11 +235,7 @@ func addEntryForRubyGem(indexed *dictionary.Indexed, ref string, cpeItemName str
ref = strings.TrimPrefix(ref, prefixForRubyGemsHTTP)
ref = strings.Split(ref, "/")[0]
if _, ok := indexed.EcosystemPackages[dictionary.EcosystemRubyGems]; !ok {
indexed.EcosystemPackages[dictionary.EcosystemRubyGems] = make(dictionary.Packages)
}
indexed.EcosystemPackages[dictionary.EcosystemRubyGems][ref] = cpeItemName
updateIndex(indexed, dictionary.EcosystemRubyGems, ref, cpeItemName)
}
func addEntryForNPMPackage(indexed *dictionary.Indexed, ref string, cpeItemName string) {
@ -257,11 +244,7 @@ func addEntryForNPMPackage(indexed *dictionary.Indexed, ref string, cpeItemName
ref = strings.Split(ref, "?")[0]
ref = strings.TrimPrefix(ref, prefixForNPMPackages)
if _, ok := indexed.EcosystemPackages[dictionary.EcosystemNPM]; !ok {
indexed.EcosystemPackages[dictionary.EcosystemNPM] = make(dictionary.Packages)
}
indexed.EcosystemPackages[dictionary.EcosystemNPM][ref] = cpeItemName
updateIndex(indexed, dictionary.EcosystemNPM, ref, cpeItemName)
}
func phpExtensionPackageFromURLFragment(ref string) string {
@ -301,11 +284,7 @@ func addEntryForPHPPearPackage(indexed *dictionary.Indexed, ref string, cpeItemN
return
}
if _, ok := indexed.EcosystemPackages[dictionary.EcosystemPHPPear]; !ok {
indexed.EcosystemPackages[dictionary.EcosystemPHPPear] = make(dictionary.Packages)
}
indexed.EcosystemPackages[dictionary.EcosystemPHPPear][ref] = cpeItemName
updateIndex(indexed, dictionary.EcosystemPHPPear, ref, cpeItemName)
}
func addEntryForPHPPeclPackage(indexed *dictionary.Indexed, ref string, cpeItemName string) {
@ -317,11 +296,7 @@ func addEntryForPHPPeclPackage(indexed *dictionary.Indexed, ref string, cpeItemN
return
}
if _, ok := indexed.EcosystemPackages[dictionary.EcosystemPHPPecl]; !ok {
indexed.EcosystemPackages[dictionary.EcosystemPHPPecl] = make(dictionary.Packages)
}
indexed.EcosystemPackages[dictionary.EcosystemPHPPecl][ref] = cpeItemName
updateIndex(indexed, dictionary.EcosystemPHPPecl, ref, cpeItemName)
}
func addEntryForPHPComposerPackage(indexed *dictionary.Indexed, ref string, cpeItemName string) {
@ -335,9 +310,5 @@ func addEntryForPHPComposerPackage(indexed *dictionary.Indexed, ref string, cpeI
ref = components[0] + "/" + components[1]
if _, ok := indexed.EcosystemPackages[dictionary.EcosystemPHPComposer]; !ok {
indexed.EcosystemPackages[dictionary.EcosystemPHPComposer] = make(dictionary.Packages)
}
indexed.EcosystemPackages[dictionary.EcosystemPHPComposer][ref] = cpeItemName
updateIndex(indexed, dictionary.EcosystemPHPComposer, ref, cpeItemName)
}

View File

@ -8,6 +8,7 @@ import (
"testing"
"github.com/google/go-cmp/cmp"
"github.com/scylladb/go-set/strset"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@ -59,7 +60,7 @@ func Test_addEntryFuncs(t *testing.T) {
expectedIndexed: dictionary.Indexed{
EcosystemPackages: map[string]dictionary.Packages{
dictionary.EcosystemRustCrates: {
"unicycle": "cpe:2.3:a:unicycle_project:unicycle:*:*:*:*:*:rust:*:*",
"unicycle": dictionary.NewSet("cpe:2.3:a:unicycle_project:unicycle:*:*:*:*:*:rust:*:*"),
},
},
},
@ -72,7 +73,7 @@ func Test_addEntryFuncs(t *testing.T) {
expectedIndexed: dictionary.Indexed{
EcosystemPackages: map[string]dictionary.Packages{
dictionary.EcosystemJenkinsPlugins: {
"sonarqube": "cpe:2.3:a:sonarsource:sonarqube_scanner:2.7:*:*:*:*:jenkins:*:*",
"sonarqube": dictionary.NewSet("cpe:2.3:a:sonarsource:sonarqube_scanner:2.7:*:*:*:*:jenkins:*:*"),
},
},
},
@ -94,7 +95,7 @@ func Test_addEntryFuncs(t *testing.T) {
expectedIndexed: dictionary.Indexed{
EcosystemPackages: map[string]dictionary.Packages{
dictionary.EcosystemJenkinsPlugins: {
"svn-partial-release-mgr": "cpe:2.3:a:jenkins:subversion_partial_release_manager:1.0.1:*:*:*:*:jenkins:*:*",
"svn-partial-release-mgr": dictionary.NewSet("cpe:2.3:a:jenkins:subversion_partial_release_manager:1.0.1:*:*:*:*:jenkins:*:*"),
},
},
},
@ -107,7 +108,7 @@ func Test_addEntryFuncs(t *testing.T) {
expectedIndexed: dictionary.Indexed{
EcosystemPackages: map[string]dictionary.Packages{
dictionary.EcosystemPyPI: {
"vault-cli": "cpe:2.3:a:vault-cli_project:vault-cli:*:*:*:*:*:python:*:*",
"vault-cli": dictionary.NewSet("cpe:2.3:a:vault-cli_project:vault-cli:*:*:*:*:*:python:*:*"),
},
},
},
@ -120,7 +121,7 @@ func Test_addEntryFuncs(t *testing.T) {
expectedIndexed: dictionary.Indexed{
EcosystemPackages: map[string]dictionary.Packages{
dictionary.EcosystemRubyGems: {
"openssl": "cpe:2.3:a:ruby-lang:openssl:-:*:*:*:*:ruby:*:*",
"openssl": dictionary.NewSet("cpe:2.3:a:ruby-lang:openssl:-:*:*:*:*:ruby:*:*"),
},
},
},
@ -133,7 +134,7 @@ func Test_addEntryFuncs(t *testing.T) {
expectedIndexed: dictionary.Indexed{
EcosystemPackages: map[string]dictionary.Packages{
dictionary.EcosystemRubyGems: {
"actionview": "cpe:2.3:a:action_view_project:action_view:*:*:*:*:*:ruby:*:*",
"actionview": dictionary.NewSet("cpe:2.3:a:action_view_project:action_view:*:*:*:*:*:ruby:*:*"),
},
},
},
@ -146,7 +147,7 @@ func Test_addEntryFuncs(t *testing.T) {
expectedIndexed: dictionary.Indexed{
EcosystemPackages: map[string]dictionary.Packages{
dictionary.EcosystemRubyGems: {
"rbovirt": "cpe:2.3:a:amos_benari:rbovirt:*:*:*:*:*:ruby:*:*",
"rbovirt": dictionary.NewSet("cpe:2.3:a:amos_benari:rbovirt:*:*:*:*:*:ruby:*:*"),
},
},
},
@ -159,7 +160,7 @@ func Test_addEntryFuncs(t *testing.T) {
expectedIndexed: dictionary.Indexed{
EcosystemPackages: map[string]dictionary.Packages{
dictionary.EcosystemNPM: {
"@nubosoftware/node-static": "cpe:2.3:a:\\@nubosoftware\\/node-static_project:\\@nubosoftware\\/node-static:-:*:*:*:*:node.js:*:*",
"@nubosoftware/node-static": dictionary.NewSet("cpe:2.3:a:\\@nubosoftware\\/node-static_project:\\@nubosoftware\\/node-static:-:*:*:*:*:node.js:*:*"),
},
},
},
@ -172,7 +173,7 @@ func Test_addEntryFuncs(t *testing.T) {
expectedIndexed: dictionary.Indexed{
EcosystemPackages: map[string]dictionary.Packages{
dictionary.EcosystemPHPPecl: {
"imagick": "cpe:2.3:a:php:imagick:*:*:*:*:*:*:*:*",
"imagick": dictionary.NewSet("cpe:2.3:a:php:imagick:*:*:*:*:*:*:*:*"),
},
},
},
@ -185,7 +186,7 @@ func Test_addEntryFuncs(t *testing.T) {
expectedIndexed: dictionary.Indexed{
EcosystemPackages: map[string]dictionary.Packages{
dictionary.EcosystemPHPPecl: {
"memcached": "cpe:2.3:a:php:memcached:*:*:*:*:*:*:*:*",
"memcached": dictionary.NewSet("cpe:2.3:a:php:memcached:*:*:*:*:*:*:*:*"),
},
},
},
@ -198,7 +199,7 @@ func Test_addEntryFuncs(t *testing.T) {
expectedIndexed: dictionary.Indexed{
EcosystemPackages: map[string]dictionary.Packages{
dictionary.EcosystemPHPPear: {
"PEAR": "cpe:2.3:a:php:pear:*:*:*:*:*:*:*:*",
"PEAR": dictionary.NewSet("cpe:2.3:a:php:pear:*:*:*:*:*:*:*:*"),
},
},
},
@ -211,7 +212,7 @@ func Test_addEntryFuncs(t *testing.T) {
expectedIndexed: dictionary.Indexed{
EcosystemPackages: map[string]dictionary.Packages{
dictionary.EcosystemPHPPear: {
"abcdefg": "cpe:2.3:a:php:abcdefg:*:*:*:*:*:*:*:*",
"abcdefg": dictionary.NewSet("cpe:2.3:a:php:abcdefg:*:*:*:*:*:*:*:*"),
},
},
},
@ -224,7 +225,7 @@ func Test_addEntryFuncs(t *testing.T) {
expectedIndexed: dictionary.Indexed{
EcosystemPackages: map[string]dictionary.Packages{
dictionary.EcosystemPHPComposer: {
"frappant/frp-form-answers": "cpe:2.3:a:frappant:forms_export:*:*:*:*:*:*:*:*",
"frappant/frp-form-answers": dictionary.NewSet("cpe:2.3:a:frappant:forms_export:*:*:*:*:*:*:*:*"),
},
},
},
@ -239,7 +240,7 @@ func Test_addEntryFuncs(t *testing.T) {
tt.addEntryFunc(indexed, tt.inputRef, tt.inputCpeItemName)
if diff := cmp.Diff(tt.expectedIndexed, *indexed); diff != "" {
if diff := cmp.Diff(tt.expectedIndexed, *indexed, cmp.AllowUnexported(strset.Set{})); diff != "" {
t.Errorf("addEntry* mismatch (-want +got):\n%s", diff)
}
})

View File

@ -1,44 +1,97 @@
{
"ecosystems": {
"jenkins_plugins": {
"fireline": "cpe:2.3:a:jenkins:360_fireline:*:*:*:*:*:jenkins:*:*",
"sonarqube": "cpe:2.3:a:sonarsource:sonarqube_scanner:*:*:*:*:*:jenkins:*:*",
"svn-partial-release-mgr": "cpe:2.3:a:jenkins:subversion_partial_release_manager:*:*:*:*:*:jenkins:*:*"
"anchore-container-scanner": [
"cpe:2.3:a:anchore:container_image_scanner:*:*:*:*:*:jenkins:*:*",
"cpe:2.3:a:jenkins:anchore_container_image_scanner:*:*:*:*:*:jenkins:*:*"
],
"fireline": [
"cpe:2.3:a:jenkins:360_fireline:*:*:*:*:*:jenkins:*:*"
],
"sonarqube": [
"cpe:2.3:a:sonarsource:sonarqube_scanner:*:*:*:*:*:jenkins:*:*"
],
"svn-partial-release-mgr": [
"cpe:2.3:a:jenkins:subversion_partial_release_manager:*:*:*:*:*:jenkins:*:*"
]
},
"npm": {
"merge-recursive": "cpe:2.3:a:umbraengineering:merge-recursive:*:*:*:*:*:node.js:*:*",
"ps": "cpe:2.3:a:umbraengineering:ps:*:*:*:*:*:node.js:*:*",
"static-dev-server": "cpe:2.3:a:static-dev-server_project:static-dev-server:*:*:*:*:*:node.js:*:*",
"umount": "cpe:2.3:a:umount_project:umount:*:*:*:*:*:node.js:*:*",
"undefsafe": "cpe:2.3:a:undefsafe_project:undefsafe:*:*:*:*:*:node.js:*:*",
"underscore": "cpe:2.3:a:underscorejs:underscore:*:*:*:*:*:node.js:*:*",
"underscore-99xp": "cpe:2.3:a:underscore-99xp_project:underscore-99xp:*:*:*:*:*:node.js:*:*",
"ungit": "cpe:2.3:a:ungit_project:ungit:*:*:*:*:*:node.js:*:*",
"unicode": "cpe:2.3:a:unicode_project:unicode:*:*:*:*:*:node.js:*:*",
"unicode-json": "cpe:2.3:a:unicode:unicode-json:*:*:*:*:*:node.js:*:*",
"unicorn-list": "cpe:2.3:a:unicorn-list_project:unicorn-list:*:*:*:*:*:node.js:*:*"
"merge-recursive": [
"cpe:2.3:a:umbraengineering:merge-recursive:*:*:*:*:*:node.js:*:*"
],
"ps": [
"cpe:2.3:a:umbraengineering:ps:*:*:*:*:*:node.js:*:*"
],
"static-dev-server": [
"cpe:2.3:a:static-dev-server_project:static-dev-server:*:*:*:*:*:node.js:*:*"
],
"umount": [
"cpe:2.3:a:umount_project:umount:*:*:*:*:*:node.js:*:*"
],
"undefsafe": [
"cpe:2.3:a:undefsafe_project:undefsafe:*:*:*:*:*:node.js:*:*"
],
"underscore": [
"cpe:2.3:a:underscorejs:underscore:*:*:*:*:*:node.js:*:*"
],
"underscore-99xp": [
"cpe:2.3:a:underscore-99xp_project:underscore-99xp:*:*:*:*:*:node.js:*:*"
],
"ungit": [
"cpe:2.3:a:ungit_project:ungit:*:*:*:*:*:node.js:*:*"
],
"unicode": [
"cpe:2.3:a:unicode_project:unicode:*:*:*:*:*:node.js:*:*"
],
"unicode-json": [
"cpe:2.3:a:unicode:unicode-json:*:*:*:*:*:node.js:*:*"
],
"unicorn-list": [
"cpe:2.3:a:unicorn-list_project:unicorn-list:*:*:*:*:*:node.js:*:*"
]
},
"php_composer": {
"frappant/frp-form-answers": "cpe:2.3:a:frappant:forms_export:*:*:*:*:*:typo3:*:*"
"frappant/frp-form-answers": [
"cpe:2.3:a:frappant:forms_export:*:*:*:*:*:typo3:*:*"
]
},
"php_pear": {
"HTML_QuickForm": "cpe:2.3:a:html_quickform_project:html_quickform:*:*:*:*:*:*:*:*",
"PEAR": "cpe:2.3:a:php:pear:*:*:*:*:*:*:*:*",
"XML_RPC": "cpe:2.3:a:php:xml_rpc:*:*:*:*:*:pear:*:*"
"HTML_QuickForm": [
"cpe:2.3:a:html_quickform_project:html_quickform:*:*:*:*:*:*:*:*"
],
"PEAR": [
"cpe:2.3:a:php:pear:*:*:*:*:*:*:*:*"
],
"XML_RPC": [
"cpe:2.3:a:php:xml_rpc:*:*:*:*:*:pear:*:*"
]
},
"php_pecl": {
"imagick": "cpe:2.3:a:php:imagick:*:*:*:*:*:*:*:*",
"memcached": "cpe:2.3:a:php:memcached:*:*:*:*:*:*:*:*",
"xhprof": "cpe:2.3:a:php:xhprof:*:*:*:*:*:*:*:*"
"imagick": [
"cpe:2.3:a:php:imagick:*:*:*:*:*:*:*:*"
],
"memcached": [
"cpe:2.3:a:php:memcached:*:*:*:*:*:*:*:*"
],
"xhprof": [
"cpe:2.3:a:php:xhprof:*:*:*:*:*:*:*:*"
]
},
"pypi": {
"vault-cli": "cpe:2.3:a:vault-cli_project:vault-cli:*:*:*:*:*:python:*:*"
"vault-cli": [
"cpe:2.3:a:vault-cli_project:vault-cli:*:*:*:*:*:python:*:*"
]
},
"rubygems": {
"openssl": "cpe:2.3:a:ruby-lang:openssl:*:*:*:*:*:*:*:*"
"openssl": [
"cpe:2.3:a:ruby-lang:openssl:*:*:*:*:*:*:*:*",
"cpe:2.3:a:ruby-lang:openssl:*:*:*:*:*:ruby:*:*"
]
},
"rust_crates": {
"unicycle": "cpe:2.3:a:unicycle_project:unicycle:*:*:*:*:*:rust:*:*"
"unicycle": [
"cpe:2.3:a:unicycle_project:unicycle:*:*:*:*:*:rust:*:*"
]
}
}
}

View File

@ -25014,6 +25014,40 @@
</references>
<cpe-23:cpe23-item name="cpe:2.3:a:jenkins:360_fireline:1.0:*:*:*:*:jenkins:*:*"/>
</cpe-item>
<cpe-item name="cpe:/a:anchore:container_image_scanner:-::~~~jenkins~~">
<title xml:lang="en-US">Anchore Container Image Scanner for Jenkins</title>
<references>
<reference href="https://jenkins.io/security/advisory/2018-07-30/#SECURITY-1039">Advisory</reference>
<reference href="https://github.com/jenkinsci/anchore-container-scanner-plugin/releases">Version</reference>
<reference href="https://plugins.jenkins.io/anchore-container-scanner">Product</reference>
</references>
<cpe-23:cpe23-item name="cpe:2.3:a:anchore:container_image_scanner:-:*:*:*:*:jenkins:*:*"/>
</cpe-item>
<cpe-item name="cpe:/a:anchore:container_image_scanner:1.0.0::~~~jenkins~~">
<title xml:lang="en-US">Anchore Container Image Scanner 1.0.0 for Jenkins</title>
<references>
<reference href="https://jenkins.io/security/advisory/2018-07-30/#SECURITY-1039">Advisory</reference>
<reference href="https://github.com/jenkinsci/anchore-container-scanner-plugin/releases">Version</reference>
<reference href="https://plugins.jenkins.io/anchore-container-scanner">Product</reference>
</references>
<cpe-23:cpe23-item name="cpe:2.3:a:anchore:container_image_scanner:1.0.0:*:*:*:*:jenkins:*:*"/>
</cpe-item>
<cpe-item name="cpe:/a:jenkins:anchore_container_image_scanner:1.0.0::~~~jenkins~~">
<title xml:lang="en-US">Jenkins Anchore Container Image Scanner 1.0.0 for Jenkins</title>
<references>
<reference href="https://plugins.jenkins.io/anchore-container-scanner">Product</reference>
<reference href="https://github.com/jenkinsci/anchore-container-scanner-plugin">Version</reference>
</references>
<cpe-23:cpe23-item name="cpe:2.3:a:jenkins:anchore_container_image_scanner:1.0.0:*:*:*:*:jenkins:*:*"/>
</cpe-item>
<cpe-item name="cpe:/a:jenkins:anchore_container_image_scanner:1.0.1::~~~jenkins~~">
<title xml:lang="en-US">Jenkins Anchore Container Image Scanner 1.0.1 for Jenkins</title>
<references>
<reference href="https://plugins.jenkins.io/anchore-container-scanner">Product</reference>
<reference href="https://github.com/jenkinsci/anchore-container-scanner-plugin">Version</reference>
</references>
<cpe-23:cpe23-item name="cpe:2.3:a:jenkins:anchore_container_image_scanner:1.0.1:*:*:*:*:jenkins:*:*"/>
</cpe-item>
</cpe-list>
</cpe-list>
</cpe-list>

View File

@ -1,5 +1,12 @@
package dictionary
import (
"encoding/json"
"slices"
"github.com/scylladb/go-set/strset"
)
const (
EcosystemNPM = "npm"
EcosystemRubyGems = "rubygems"
@ -15,4 +22,29 @@ type Indexed struct {
EcosystemPackages map[string]Packages `json:"ecosystems"`
}
type Packages map[string]string
type Set struct {
*strset.Set
}
type Packages map[string]*Set
func NewSet(ts ...string) *Set {
return &Set{strset.New(ts...)}
}
func (s *Set) MarshalJSON() ([]byte, error) {
l := s.List()
slices.Sort(l)
return json.Marshal(l)
}
func (s *Set) UnmarshalJSON(data []byte) error {
var strSlice []string
if err := json.Unmarshal(data, &strSlice); err == nil {
*s = *NewSet(strSlice...)
} else {
return err
}
return nil
}

View File

@ -58,58 +58,66 @@ func GetIndexedDictionary() (_ *dictionary.Indexed, err error) {
return indexedCPEDictionary, err
}
func FromDictionaryFind(p pkg.Package) (cpe.CPE, bool) {
func FromDictionaryFind(p pkg.Package) ([]cpe.CPE, bool) {
dict, err := GetIndexedDictionary()
parsedCPEs := []cpe.CPE{}
if err != nil {
log.Debugf("CPE dictionary lookup not available: %+v", err)
return cpe.CPE{}, false
return parsedCPEs, false
}
var (
cpeString string
cpes *dictionary.Set
ok bool
)
switch p.Type {
case pkg.NpmPkg:
cpeString, ok = dict.EcosystemPackages[dictionary.EcosystemNPM][p.Name]
cpes, ok = dict.EcosystemPackages[dictionary.EcosystemNPM][p.Name]
case pkg.GemPkg:
cpeString, ok = dict.EcosystemPackages[dictionary.EcosystemRubyGems][p.Name]
cpes, ok = dict.EcosystemPackages[dictionary.EcosystemRubyGems][p.Name]
case pkg.PythonPkg:
cpeString, ok = dict.EcosystemPackages[dictionary.EcosystemPyPI][p.Name]
cpes, ok = dict.EcosystemPackages[dictionary.EcosystemPyPI][p.Name]
case pkg.JenkinsPluginPkg:
cpeString, ok = dict.EcosystemPackages[dictionary.EcosystemJenkinsPlugins][p.Name]
cpes, ok = dict.EcosystemPackages[dictionary.EcosystemJenkinsPlugins][p.Name]
case pkg.RustPkg:
cpeString, ok = dict.EcosystemPackages[dictionary.EcosystemRustCrates][p.Name]
cpes, ok = dict.EcosystemPackages[dictionary.EcosystemRustCrates][p.Name]
case pkg.PhpComposerPkg:
cpeString, ok = dict.EcosystemPackages[dictionary.EcosystemPHPComposer][p.Name]
cpes, ok = dict.EcosystemPackages[dictionary.EcosystemPHPComposer][p.Name]
case pkg.PhpPeclPkg:
cpeString, ok = dict.EcosystemPackages[dictionary.EcosystemPHPPecl][p.Name]
cpes, ok = dict.EcosystemPackages[dictionary.EcosystemPHPPecl][p.Name]
default:
// The dictionary doesn't support this package type yet.
return cpe.CPE{}, false
return parsedCPEs, false
}
if !ok {
// The dictionary doesn't have a CPE for this package.
return cpe.CPE{}, false
return parsedCPEs, false
}
parsedCPE, err := cpe.New(cpeString, cpe.NVDDictionaryLookupSource)
for _, c := range cpes.List() {
parsedCPE, err := cpe.New(c, cpe.NVDDictionaryLookupSource)
if err != nil {
return cpe.CPE{}, false
continue
}
parsedCPE.Attributes.Version = p.Version
parsedCPEs = append(parsedCPEs, parsedCPE)
}
return parsedCPE, true
if len(parsedCPEs) == 0 {
return []cpe.CPE{}, false
}
return parsedCPEs, true
}
// FromPackageAttributes Create a list of CPEs for a given package, trying to guess the vendor, product tuple. We should be trying to

View File

@ -10,6 +10,7 @@ import (
"github.com/scylladb/go-set/strset"
"github.com/stretchr/testify/assert"
"github.com/anchore/syft/syft/cpe"
"github.com/anchore/syft/syft/pkg"
)
@ -991,7 +992,7 @@ func TestDictionaryFindIsWired(t *testing.T) {
tests := []struct {
name string
pkg pkg.Package
want string
want []cpe.CPE
wantExists bool
}{
{
@ -1001,7 +1002,10 @@ func TestDictionaryFindIsWired(t *testing.T) {
Version: "1.0.2k",
Type: pkg.GemPkg,
},
want: "cpe:2.3:a:ruby-lang:openssl:1.0.2k:*:*:*:*:*:*:*",
want: []cpe.CPE{
cpe.Must("cpe:2.3:a:ruby-lang:openssl:1.0.2k:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
cpe.Must("cpe:2.3:a:ruby-lang:openssl:1.0.2k:*:*:*:*:ruby:*:*", cpe.NVDDictionaryLookupSource),
},
// without the cpe data wired up, this would be empty (generation also creates cpe:2.3:a:openssl:openssl:1.0.2k:*:*:*:*:*:*:*)
wantExists: true,
},
@ -1009,8 +1013,7 @@ func TestDictionaryFindIsWired(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, gotExists := FromDictionaryFind(tt.pkg)
assert.Equal(t, tt.want, got.Attributes.BindToFmtString())
assert.ElementsMatch(t, tt.want, got)
assert.Equal(t, tt.wantExists, gotExists)
})
}