Skip to content

Commit

Permalink
fix: improve CPE and upstream generation logic for Alpine packages (#…
Browse files Browse the repository at this point in the history
…1567)

* fix: improved CPE-generation logic for alpine packages

Signed-off-by: Weston Steimel <weston.steimel@anchore.com>

* fix: improved alpine upstream name generation

Signed-off-by: Weston Steimel <weston.steimel@anchore.com>

* fix: improve CPE vendor for alpine

Signed-off-by: Weston Steimel <weston.steimel@anchore.com>

* fix: python vendor CPE gen

Signed-off-by: Weston Steimel <weston.steimel@anchore.com>

* fix: alpine cpe gen logic

Signed-off-by: Weston Steimel <weston.steimel@anchore.com>

* fix: apk CPE update for nodejs-current

Signed-off-by: Weston Steimel <weston.steimel@anchore.com>

* fix: CPE update for python pip

Signed-off-by: Weston Steimel <weston.steimel@anchore.com>

* fix: CPE update for some ruby packages

Signed-off-by: Weston Steimel <weston.steimel@anchore.com>

* fix linting

Signed-off-by: Weston Steimel <weston.steimel@anchore.com>

---------

Signed-off-by: Weston Steimel <weston.steimel@anchore.com>
  • Loading branch information
westonsteimel authored Feb 13, 2023
1 parent 890fb3f commit 57a13ae
Show file tree
Hide file tree
Showing 8 changed files with 419 additions and 4 deletions.
20 changes: 19 additions & 1 deletion syft/pkg/cataloger/apkdb/package.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ import (
"github.com/anchore/syft/syft/source"
)

var (
prefixes = []string{"py-", "py2-", "py3-", "ruby-"}
)

func newPackage(d pkg.ApkMetadata, release *linux.Release, locations ...source.Location) pkg.Package {
p := pkg.Package{
Name: d.Package,
Expand All @@ -26,6 +30,20 @@ func newPackage(d pkg.ApkMetadata, release *linux.Release, locations ...source.L
return p
}

func generateUpstream(m pkg.ApkMetadata) string {
if m.OriginPackage != "" && m.OriginPackage != m.Package {
return m.OriginPackage
}

for _, p := range prefixes {
if strings.HasPrefix(m.Package, p) {
return strings.TrimPrefix(m.Package, p)
}
}

return m.Package
}

// packageURL returns the PURL for the specific Alpine package (see https://github.com/package-url/purl-spec)
func packageURL(m pkg.ApkMetadata, distro *linux.Release) string {
if distro == nil || distro.ID != "alpine" {
Expand All @@ -38,7 +56,7 @@ func packageURL(m pkg.ApkMetadata, distro *linux.Release) string {
}

if m.OriginPackage != "" {
qualifiers[pkg.PURLQualifierUpstream] = m.OriginPackage
qualifiers[pkg.PURLQualifierUpstream] = generateUpstream(m)
}

return packageurl.NewPackageURL(
Expand Down
28 changes: 28 additions & 0 deletions syft/pkg/cataloger/apkdb/package_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,34 @@ func Test_PackageURL(t *testing.T) {
},
expected: "pkg:apk/alpine/p@v?arch=a&upstream=origin&distro=alpine-3.4.6",
},
{
name: "upstream python package information as qualifier",
metadata: pkg.ApkMetadata{
Package: "py3-potatoes",
Version: "v",
Architecture: "a",
OriginPackage: "py3-potatoes",
},
distro: linux.Release{
ID: "alpine",
VersionID: "3.4.6",
},
expected: "pkg:apk/alpine/py3-potatoes@v?arch=a&upstream=potatoes&distro=alpine-3.4.6",
},
{
name: "python package with origin package as upstream",
metadata: pkg.ApkMetadata{
Package: "py3-non-existant",
Version: "v",
Architecture: "a",
OriginPackage: "abcdefg",
},
distro: linux.Release{
ID: "alpine",
VersionID: "3.4.6",
},
expected: "pkg:apk/alpine/py3-non-existant@v?arch=a&upstream=abcdefg&distro=alpine-3.4.6",
},
}

for _, test := range tests {
Expand Down
173 changes: 173 additions & 0 deletions syft/pkg/cataloger/common/cpe/apk.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
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.add(fieldCandidate{
value: v,
disallowSubSelections: true,
disallowDelimiterVariations: true,
})

vendors.addValue(findAdditionalVendors(defaultCandidateAdditions, pkg.PythonPkg, v, v)...)
vendors.removeByValue(findVendorsToRemove(defaultCandidateRemovals, pkg.PythonPkg, v)...)

for _, av := range additionalVendorsForPython(v) {
vendors.add(fieldCandidate{
value: av,
disallowSubSelections: true,
disallowDelimiterVariations: true,
})
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()

for _, p := range pythonPrefixes {
if strings.HasPrefix(m.Package, p) {
t := strings.ToLower(strings.TrimPrefix(m.Package, p))
vendors.union(pythonCandidateVendorsFromName(t))
}

if m.OriginPackage != m.Package && strings.HasPrefix(m.OriginPackage, p) {
t := strings.ToLower(strings.TrimPrefix(m.OriginPackage, p))
vendors.union(pythonCandidateVendorsFromName(t))
}
}

return vendors
}

func pythonCandidateProductsFromName(p string) fieldCandidateSet {
products := newFieldCandidateSet()
products.add(fieldCandidate{
value: p,
disallowSubSelections: true,
disallowDelimiterVariations: true,
})

products.addValue(findAdditionalProducts(defaultCandidateAdditions, pkg.PythonPkg, p)...)
products.removeByValue(findProductsToRemove(defaultCandidateRemovals, pkg.PythonPkg, p)...)
return products
}

func pythonCandidateProductsFromAPK(m pkg.ApkMetadata) fieldCandidateSet {
products := newFieldCandidateSet()

for _, p := range pythonPrefixes {
if strings.HasPrefix(m.Package, p) {
t := strings.ToLower(strings.TrimPrefix(m.Package, p))
products.union(pythonCandidateProductsFromName(t))
}

if m.OriginPackage != m.Package && strings.HasPrefix(m.OriginPackage, p) {
t := strings.ToLower(strings.TrimPrefix(m.OriginPackage, p))
products.union(pythonCandidateProductsFromName(t))
}
}

return products
}

func rubyCandidateVendorsFromName(v string) fieldCandidateSet {
vendors := newFieldCandidateSet()
vendors.add(fieldCandidate{
value: v,
disallowSubSelections: true,
disallowDelimiterVariations: true,
})

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()

for _, p := range rubyPrefixes {
if strings.HasPrefix(m.Package, p) {
t := strings.ToLower(strings.TrimPrefix(m.Package, p))
vendors.union(rubyCandidateVendorsFromName(t))
}

if m.OriginPackage != m.Package && strings.HasPrefix(m.OriginPackage, p) {
t := strings.ToLower(strings.TrimPrefix(m.OriginPackage, p))
vendors.union(rubyCandidateVendorsFromName(t))
}
}

return vendors
}

func rubyCandidateProductsFromName(p string) fieldCandidateSet {
products := newFieldCandidateSet()
products.add(fieldCandidate{
value: p,
disallowSubSelections: true,
disallowDelimiterVariations: true,
})

products.addValue(findAdditionalProducts(defaultCandidateAdditions, pkg.GemPkg, p)...)
products.removeByValue(findProductsToRemove(defaultCandidateRemovals, pkg.GemPkg, p)...)
return products
}

func rubyCandidateProductsFromAPK(m pkg.ApkMetadata) fieldCandidateSet {
products := newFieldCandidateSet()

for _, p := range rubyPrefixes {
if strings.HasPrefix(m.Package, p) {
t := strings.ToLower(strings.TrimPrefix(m.Package, p))
products.union(rubyCandidateProductsFromName(t))
}

if m.OriginPackage != m.Package && strings.HasPrefix(m.OriginPackage, p) {
t := strings.ToLower(strings.TrimPrefix(m.OriginPackage, p))
products.union(rubyCandidateProductsFromName(t))
}
}

return products
}

func candidateVendorsForAPK(p pkg.Package) fieldCandidateSet {
metadata, ok := p.Metadata.(pkg.ApkMetadata)
if !ok {
return nil
}

vendors := newFieldCandidateSet()
vendors.union(pythonCandidateVendorsFromAPK(metadata))
vendors.union(rubyCandidateVendorsFromAPK(metadata))

return vendors
}

func candidateProductsForAPK(p pkg.Package) fieldCandidateSet {
metadata, ok := p.Metadata.(pkg.ApkMetadata)
if !ok {
return nil
}

products := newFieldCandidateSet()
products.union(pythonCandidateProductsFromAPK(metadata))
products.union(rubyCandidateProductsFromAPK(metadata))

return products
}
91 changes: 91 additions & 0 deletions syft/pkg/cataloger/common/cpe/apk_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package cpe

import (
"testing"

"github.com/stretchr/testify/assert"

"github.com/anchore/syft/syft/pkg"
)

func Test_candidateVendorsForAPK(t *testing.T) {
tests := []struct {
name string
pkg pkg.Package
expected []string
}{
{
name: "py3-cryptography Package",
pkg: pkg.Package{
Metadata: pkg.ApkMetadata{
Package: "py3-cryptography",
},
},
expected: []string{"python-cryptography_project", "cryptography", "cryptographyproject", "cryptography_project"},
},
{
name: "py2-pypdf OriginPackage",
pkg: pkg.Package{
Metadata: pkg.ApkMetadata{
OriginPackage: "py2-pypdf",
},
},
expected: []string{"pypdf", "pypdfproject", "pypdf_project"},
},
{
name: "ruby-armadillo Package",
pkg: pkg.Package{
Metadata: pkg.ApkMetadata{
Package: "ruby-armadillo",
},
},
expected: []string{"armadillo"},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
assert.ElementsMatch(t, test.expected, candidateVendorsForAPK(test.pkg).values(), "different vendors")
})
}
}

func Test_candidateProductsForAPK(t *testing.T) {
tests := []struct {
name string
pkg pkg.Package
expected []string
}{
{
name: "py3-cryptography Package",
pkg: pkg.Package{
Metadata: pkg.ApkMetadata{
Package: "py3-cryptography",
},
},
expected: []string{"cryptography", "python-cryptography"},
},
{
name: "py2-pypdf OriginPackage",
pkg: pkg.Package{
Metadata: pkg.ApkMetadata{
OriginPackage: "py2-pypdf",
},
},
expected: []string{"pypdf"},
},
{
name: "ruby-armadillo Package",
pkg: pkg.Package{
Metadata: pkg.ApkMetadata{
Package: "ruby-armadillo",
},
},
expected: []string{"armadillo"},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
assert.ElementsMatch(t, test.expected, candidateProductsForAPK(test.pkg).values(), "different products")
})
}
}
Loading

0 comments on commit 57a13ae

Please sign in to comment.