mirror of
https://github.com/anchore/syft.git
synced 2026-11-05 00:46:38 +01:00
fix: apk product/vendor generation for old metadata (#1635)
This fixes some instances where the improved APK CPE generation logic caused regressions for older alpine package APK metadata. It now generates multiple "upstream" candidates with both name and package type which reduces the amount of duplicated code in the apk cpe gen logic. This also improves the handling of stream version packages, so now we can correctly identify packages such as ruby3.2-rexml as the rexml ruby gem. Signed-off-by: Weston Steimel <weston.steimel@anchore.com>
This commit is contained in:
@@ -1,159 +1,9 @@
|
||||
package cpe
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
)
|
||||
|
||||
var (
|
||||
pythonPrefixes = []string{"py-", "py2-", "py3-"}
|
||||
rubyPrefixes = []string{"ruby-"}
|
||||
)
|
||||
|
||||
func pythonCandidateVendorsFromName(v string) fieldCandidateSet {
|
||||
vendors := newFieldCandidateSet()
|
||||
vendors.addValue(v)
|
||||
vendors.addValue(findAdditionalVendors(defaultCandidateAdditions, pkg.PythonPkg, v, v)...)
|
||||
vendors.removeByValue(findVendorsToRemove(defaultCandidateRemovals, pkg.PythonPkg, v)...)
|
||||
|
||||
for _, av := range additionalVendorsForPython(v) {
|
||||
vendors.addValue(av)
|
||||
vendors.addValue(findAdditionalVendors(defaultCandidateAdditions, pkg.PythonPkg, av, av)...)
|
||||
vendors.removeByValue(findVendorsToRemove(defaultCandidateRemovals, pkg.PythonPkg, av)...)
|
||||
}
|
||||
|
||||
return vendors
|
||||
}
|
||||
|
||||
func pythonCandidateVendorsFromAPK(m pkg.ApkMetadata) fieldCandidateSet {
|
||||
vendors := newFieldCandidateSet()
|
||||
upstream := m.Upstream()
|
||||
|
||||
for _, p := range pythonPrefixes {
|
||||
if strings.HasPrefix(m.Package, p) {
|
||||
t := strings.ToLower(strings.TrimPrefix(m.Package, p))
|
||||
vendors.union(pythonCandidateVendorsFromName(t))
|
||||
}
|
||||
|
||||
if upstream != m.Package && strings.HasPrefix(upstream, p) {
|
||||
t := strings.ToLower(strings.TrimPrefix(upstream, p))
|
||||
vendors.union(pythonCandidateVendorsFromName(t))
|
||||
}
|
||||
}
|
||||
|
||||
return vendors
|
||||
}
|
||||
|
||||
func pythonCandidateProductsFromName(p string) fieldCandidateSet {
|
||||
products := newFieldCandidateSet()
|
||||
products.addValue(p)
|
||||
products.addValue(findAdditionalProducts(defaultCandidateAdditions, pkg.PythonPkg, p)...)
|
||||
products.removeByValue(findProductsToRemove(defaultCandidateRemovals, pkg.PythonPkg, p)...)
|
||||
return products
|
||||
}
|
||||
|
||||
func pythonCandidateProductsFromAPK(m pkg.ApkMetadata) fieldCandidateSet {
|
||||
products := newFieldCandidateSet()
|
||||
upstream := m.Upstream()
|
||||
|
||||
for _, p := range pythonPrefixes {
|
||||
if strings.HasPrefix(m.Package, p) {
|
||||
t := strings.ToLower(strings.TrimPrefix(m.Package, p))
|
||||
products.union(pythonCandidateProductsFromName(t))
|
||||
}
|
||||
|
||||
if upstream != m.Package && strings.HasPrefix(upstream, p) {
|
||||
t := strings.ToLower(strings.TrimPrefix(upstream, p))
|
||||
products.union(pythonCandidateProductsFromName(t))
|
||||
}
|
||||
}
|
||||
|
||||
return products
|
||||
}
|
||||
|
||||
func rubyCandidateVendorsFromName(v string) fieldCandidateSet {
|
||||
vendors := newFieldCandidateSet()
|
||||
vendors.addValue(v)
|
||||
vendors.addValue(findAdditionalVendors(defaultCandidateAdditions, pkg.GemPkg, v, v)...)
|
||||
vendors.removeByValue(findVendorsToRemove(defaultCandidateRemovals, pkg.GemPkg, v)...)
|
||||
return vendors
|
||||
}
|
||||
|
||||
func rubyCandidateVendorsFromAPK(m pkg.ApkMetadata) fieldCandidateSet {
|
||||
vendors := newFieldCandidateSet()
|
||||
upstream := m.Upstream()
|
||||
|
||||
if upstream != "ruby" {
|
||||
for _, p := range rubyPrefixes {
|
||||
if strings.HasPrefix(m.Package, p) {
|
||||
t := strings.ToLower(strings.TrimPrefix(m.Package, p))
|
||||
vendors.union(rubyCandidateVendorsFromName(t))
|
||||
}
|
||||
|
||||
if upstream != "" && upstream != m.Package && strings.HasPrefix(upstream, p) {
|
||||
t := strings.ToLower(strings.TrimPrefix(upstream, p))
|
||||
vendors.union(rubyCandidateVendorsFromName(t))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return vendors
|
||||
}
|
||||
|
||||
func rubyCandidateProductsFromName(p string) fieldCandidateSet {
|
||||
products := newFieldCandidateSet()
|
||||
products.addValue(p)
|
||||
products.addValue(findAdditionalProducts(defaultCandidateAdditions, pkg.GemPkg, p)...)
|
||||
products.removeByValue(findProductsToRemove(defaultCandidateRemovals, pkg.GemPkg, p)...)
|
||||
return products
|
||||
}
|
||||
|
||||
func rubyCandidateProductsFromAPK(m pkg.ApkMetadata) fieldCandidateSet {
|
||||
products := newFieldCandidateSet()
|
||||
upstream := m.Upstream()
|
||||
|
||||
if upstream != "ruby" {
|
||||
for _, p := range rubyPrefixes {
|
||||
if strings.HasPrefix(m.Package, p) {
|
||||
t := strings.ToLower(strings.TrimPrefix(m.Package, p))
|
||||
products.union(rubyCandidateProductsFromName(t))
|
||||
}
|
||||
|
||||
if upstream != "" && upstream != m.Package && strings.HasPrefix(upstream, p) {
|
||||
t := strings.ToLower(strings.TrimPrefix(upstream, p))
|
||||
products.union(rubyCandidateProductsFromName(t))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return products
|
||||
}
|
||||
|
||||
func candidateVendorsFromAPKUpstream(m pkg.ApkMetadata) fieldCandidateSet {
|
||||
vendors := newFieldCandidateSet()
|
||||
upstream := m.Upstream()
|
||||
if upstream != "" && upstream != m.Package {
|
||||
vendors.addValue(upstream)
|
||||
vendors.addValue(findAdditionalVendors(defaultCandidateAdditions, pkg.ApkPkg, upstream, upstream)...)
|
||||
vendors.removeByValue(findVendorsToRemove(defaultCandidateRemovals, pkg.ApkPkg, upstream)...)
|
||||
}
|
||||
|
||||
return vendors
|
||||
}
|
||||
|
||||
func candidateProductsFromAPKUpstream(m pkg.ApkMetadata) fieldCandidateSet {
|
||||
products := newFieldCandidateSet()
|
||||
upstream := m.Upstream()
|
||||
if upstream != "" {
|
||||
products.addValue(upstream)
|
||||
products.addValue(findAdditionalProducts(defaultCandidateAdditions, pkg.ApkPkg, upstream)...)
|
||||
products.removeByValue(findProductsToRemove(defaultCandidateRemovals, pkg.ApkPkg, upstream)...)
|
||||
}
|
||||
|
||||
return products
|
||||
}
|
||||
|
||||
func candidateVendorsForAPK(p pkg.Package) fieldCandidateSet {
|
||||
metadata, ok := p.Metadata.(pkg.ApkMetadata)
|
||||
if !ok {
|
||||
@@ -161,9 +11,30 @@ func candidateVendorsForAPK(p pkg.Package) fieldCandidateSet {
|
||||
}
|
||||
|
||||
vendors := newFieldCandidateSet()
|
||||
vendors.union(pythonCandidateVendorsFromAPK(metadata))
|
||||
vendors.union(rubyCandidateVendorsFromAPK(metadata))
|
||||
vendors.union(candidateVendorsFromAPKUpstream(metadata))
|
||||
candidates := metadata.UpstreamCandidates()
|
||||
|
||||
for _, c := range candidates {
|
||||
switch c.Type {
|
||||
case pkg.UnknownPkg:
|
||||
vendors.addValue(c.Name)
|
||||
vendors.addValue(findAdditionalVendors(defaultCandidateAdditions, pkg.ApkPkg, c.Name, c.Name)...)
|
||||
vendors.removeByValue(findVendorsToRemove(defaultCandidateRemovals, pkg.ApkPkg, c.Name)...)
|
||||
case pkg.PythonPkg:
|
||||
vendors.addValue(c.Name)
|
||||
vendors.addValue(findAdditionalVendors(defaultCandidateAdditions, c.Type, c.Name, c.Name)...)
|
||||
vendors.removeByValue(findVendorsToRemove(defaultCandidateRemovals, c.Type, c.Name)...)
|
||||
for _, av := range additionalVendorsForPython(c.Name) {
|
||||
vendors.addValue(av)
|
||||
vendors.addValue(findAdditionalVendors(defaultCandidateAdditions, pkg.PythonPkg, av, av)...)
|
||||
vendors.removeByValue(findVendorsToRemove(defaultCandidateRemovals, pkg.PythonPkg, av)...)
|
||||
}
|
||||
default:
|
||||
vendors.addValue(c.Name)
|
||||
vendors.addValue(findAdditionalVendors(defaultCandidateAdditions, c.Type, c.Name, c.Name)...)
|
||||
vendors.removeByValue(findVendorsToRemove(defaultCandidateRemovals, c.Type, c.Name)...)
|
||||
}
|
||||
}
|
||||
|
||||
vendors.union(candidateVendorsFromURL(metadata.URL))
|
||||
|
||||
for v := range vendors {
|
||||
@@ -181,9 +52,20 @@ func candidateProductsForAPK(p pkg.Package) fieldCandidateSet {
|
||||
}
|
||||
|
||||
products := newFieldCandidateSet()
|
||||
products.union(pythonCandidateProductsFromAPK(metadata))
|
||||
products.union(rubyCandidateProductsFromAPK(metadata))
|
||||
products.union(candidateProductsFromAPKUpstream(metadata))
|
||||
candidates := metadata.UpstreamCandidates()
|
||||
|
||||
for _, c := range candidates {
|
||||
switch c.Type {
|
||||
case pkg.UnknownPkg:
|
||||
products.addValue(c.Name)
|
||||
products.addValue(findAdditionalProducts(defaultCandidateAdditions, pkg.ApkPkg, c.Name)...)
|
||||
products.removeByValue(findProductsToRemove(defaultCandidateRemovals, pkg.ApkPkg, c.Name)...)
|
||||
default:
|
||||
products.addValue(c.Name)
|
||||
products.addValue(findAdditionalProducts(defaultCandidateAdditions, c.Type, c.Name)...)
|
||||
products.removeByValue(findProductsToRemove(defaultCandidateRemovals, c.Type, c.Name)...)
|
||||
}
|
||||
}
|
||||
|
||||
for p := range products {
|
||||
p.disallowDelimiterVariations = true
|
||||
|
||||
@@ -69,7 +69,35 @@ func Test_candidateVendorsForAPK(t *testing.T) {
|
||||
URL: "https://www.gnu.org/software/make",
|
||||
},
|
||||
},
|
||||
expected: []string{"gnu"},
|
||||
expected: []string{"gnu", "make"},
|
||||
},
|
||||
{
|
||||
name: "ruby-rake with matching origin",
|
||||
pkg: pkg.Package{
|
||||
Name: "ruby-rake",
|
||||
Type: pkg.ApkPkg,
|
||||
MetadataType: pkg.ApkMetadataType,
|
||||
Metadata: pkg.ApkMetadata{
|
||||
Package: "ruby-rake",
|
||||
URL: "https://github.com/ruby/rake",
|
||||
OriginPackage: "ruby-rake",
|
||||
},
|
||||
},
|
||||
expected: []string{"rake", "ruby-lang", "ruby"},
|
||||
},
|
||||
{
|
||||
name: "ruby-rake with non-matching origin",
|
||||
pkg: pkg.Package{
|
||||
Name: "ruby-rake",
|
||||
Type: pkg.ApkPkg,
|
||||
MetadataType: pkg.ApkMetadataType,
|
||||
Metadata: pkg.ApkMetadata{
|
||||
Package: "ruby-rake",
|
||||
URL: "https://www.ruby-lang.org/",
|
||||
OriginPackage: "ruby",
|
||||
},
|
||||
},
|
||||
expected: []string{"rake", "ruby-lang", "ruby"},
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
@@ -142,6 +170,31 @@ func Test_candidateProductsForAPK(t *testing.T) {
|
||||
},
|
||||
expected: []string{"make"},
|
||||
},
|
||||
{
|
||||
name: "ruby-rake with matching origin",
|
||||
pkg: pkg.Package{
|
||||
Metadata: pkg.ApkMetadata{
|
||||
Package: "ruby-rake",
|
||||
URL: "https://github.com/ruby/rake",
|
||||
OriginPackage: "ruby-rake",
|
||||
},
|
||||
},
|
||||
expected: []string{"rake"},
|
||||
},
|
||||
{
|
||||
name: "ruby-rake with non-matching origin",
|
||||
pkg: pkg.Package{
|
||||
Name: "ruby-rake",
|
||||
Type: pkg.ApkPkg,
|
||||
MetadataType: pkg.ApkMetadataType,
|
||||
Metadata: pkg.ApkMetadata{
|
||||
Package: "ruby-rake",
|
||||
URL: "https://www.ruby-lang.org/",
|
||||
OriginPackage: "ruby",
|
||||
},
|
||||
},
|
||||
expected: []string{"rake", "ruby"},
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
|
||||
@@ -673,6 +673,48 @@ func TestGeneratePackageCPEs(t *testing.T) {
|
||||
"cpe:2.3:a:python_redis:redis:2.1.4:*:*:*:*:*:*:*",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "regression: ruby-rake apk missing expected ruby-lang:rake CPE",
|
||||
p: pkg.Package{
|
||||
Name: "ruby-rake",
|
||||
Version: "2.7.6-r0",
|
||||
Type: pkg.ApkPkg,
|
||||
FoundBy: "apk-db-analyzer",
|
||||
Language: pkg.UnknownLanguage,
|
||||
MetadataType: pkg.ApkMetadataType,
|
||||
Metadata: pkg.ApkMetadata{
|
||||
Package: "ruby-rake",
|
||||
URL: "https://www.ruby-lang.org/",
|
||||
OriginPackage: "ruby",
|
||||
},
|
||||
},
|
||||
expected: []string{
|
||||
"cpe:2.3:a:ruby-lang:rake:2.7.6-r0:*:*:*:*:*:*:*",
|
||||
"cpe:2.3:a:rake:rake:2.7.6-r0:*:*:*:*:*:*:*",
|
||||
"cpe:2.3:a:rake:ruby-rake:2.7.6-r0:*:*:*:*:*:*:*",
|
||||
"cpe:2.3:a:rake:ruby_rake:2.7.6-r0:*:*:*:*:*:*:*",
|
||||
"cpe:2.3:a:ruby-lang:ruby-rake:2.7.6-r0:*:*:*:*:*:*:*",
|
||||
"cpe:2.3:a:ruby-lang:ruby_rake:2.7.6-r0:*:*:*:*:*:*:*",
|
||||
"cpe:2.3:a:ruby-rake:rake:2.7.6-r0:*:*:*:*:*:*:*",
|
||||
"cpe:2.3:a:ruby-rake:ruby-rake:2.7.6-r0:*:*:*:*:*:*:*",
|
||||
"cpe:2.3:a:ruby-rake:ruby_rake:2.7.6-r0:*:*:*:*:*:*:*",
|
||||
"cpe:2.3:a:ruby:rake:2.7.6-r0:*:*:*:*:*:*:*",
|
||||
"cpe:2.3:a:ruby:ruby-rake:2.7.6-r0:*:*:*:*:*:*:*",
|
||||
"cpe:2.3:a:ruby:ruby_rake:2.7.6-r0:*:*:*:*:*:*:*",
|
||||
"cpe:2.3:a:ruby_lang:rake:2.7.6-r0:*:*:*:*:*:*:*",
|
||||
"cpe:2.3:a:ruby_lang:ruby-rake:2.7.6-r0:*:*:*:*:*:*:*",
|
||||
"cpe:2.3:a:ruby_lang:ruby_rake:2.7.6-r0:*:*:*:*:*:*:*",
|
||||
"cpe:2.3:a:ruby_rake:rake:2.7.6-r0:*:*:*:*:*:*:*",
|
||||
"cpe:2.3:a:ruby_rake:ruby-rake:2.7.6-r0:*:*:*:*:*:*:*",
|
||||
"cpe:2.3:a:ruby_rake:ruby_rake:2.7.6-r0:*:*:*:*:*:*:*",
|
||||
"cpe:2.3:a:rake:ruby:2.7.6-r0:*:*:*:*:*:*:*",
|
||||
"cpe:2.3:a:ruby-lang:ruby:2.7.6-r0:*:*:*:*:*:*:*",
|
||||
"cpe:2.3:a:ruby-rake:ruby:2.7.6-r0:*:*:*:*:*:*:*",
|
||||
"cpe:2.3:a:ruby:ruby:2.7.6-r0:*:*:*:*:*:*:*",
|
||||
"cpe:2.3:a:ruby_lang:ruby:2.7.6-r0:*:*:*:*:*:*:*",
|
||||
"cpe:2.3:a:ruby_rake:ruby:2.7.6-r0:*:*:*:*:*:*:*",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
|
||||
Reference in New Issue
Block a user