mirror of
https://github.com/anchore/syft.git
synced 2026-10-20 01:57:38 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2642a36161 | ||
|
|
1981b249f1 | ||
|
|
a3ebb31e3c | ||
|
|
9b9a7d6c98 | ||
|
|
57a13ae355 | ||
|
|
890fb3f0e8 | ||
|
|
e236054668 | ||
|
|
3013c8b691 |
@@ -7,12 +7,12 @@ assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Please provide a set of steps on how to reproduce the issue**
|
||||
|
||||
**What happened**:
|
||||
|
||||
**What you expected to happen**:
|
||||
|
||||
**Steps to reproduce the issue**:
|
||||
|
||||
**Anything else we need to know?**:
|
||||
|
||||
**Environment**:
|
||||
|
||||
@@ -14,8 +14,9 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
convertExample = ` {{.appName}} {{.command}} img.syft.json -o spdx-json convert a syft SBOM to spdx-json, output goes to stdout in table format, by default
|
||||
{{.appName}} {{.command}} img.syft.json -o cyclonedx-json=img.cdx.json convert a syft SBOM to CycloneDX, output goes to a file named img.cdx.json
|
||||
convertExample = ` {{.appName}} {{.command}} img.syft.json -o spdx-json convert a syft SBOM to spdx-json, output goes to stdout
|
||||
{{.appName}} {{.command}} img.syft.json -o cyclonedx-json=img.cdx.json convert a syft SBOM to CycloneDX, output is written to the file "img.cdx.json""
|
||||
{{.appName}} {{.command}} - -o spdx-json convert an SBOM from STDIN to spdx-json
|
||||
`
|
||||
)
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ package convert
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/anchore/syft/cmd/syft/cli/options"
|
||||
@@ -26,15 +27,23 @@ func Run(_ context.Context, app *config.Application, args []string) error {
|
||||
|
||||
// this can only be a SBOM file
|
||||
userInput := args[0]
|
||||
f, err := os.Open(userInput)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open SBOM file: %w", err)
|
||||
}
|
||||
defer func() {
|
||||
_ = f.Close()
|
||||
}()
|
||||
|
||||
sbom, _, err := formats.Decode(f)
|
||||
var reader io.ReadCloser
|
||||
|
||||
if userInput == "-" {
|
||||
reader = os.Stdin
|
||||
} else {
|
||||
f, err := os.Open(userInput)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open SBOM file: %w", err)
|
||||
}
|
||||
defer func() {
|
||||
_ = f.Close()
|
||||
}()
|
||||
reader = f
|
||||
}
|
||||
|
||||
sbom, _, err := formats.Decode(reader)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to decode SBOM: %w", err)
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ require (
|
||||
github.com/wagoodman/jotframe v0.0.0-20211129225309-56b0d0a4aebb
|
||||
github.com/xeipuuv/gojsonschema v1.2.0
|
||||
golang.org/x/mod v0.8.0
|
||||
golang.org/x/net v0.6.0
|
||||
golang.org/x/net v0.7.0
|
||||
golang.org/x/term v0.5.0
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
)
|
||||
@@ -52,8 +52,8 @@ require (
|
||||
github.com/CycloneDX/cyclonedx-go v0.7.1-0.20221222100750-41a1ac565cce
|
||||
github.com/Masterminds/sprig/v3 v3.2.3
|
||||
github.com/anchore/go-logger v0.0.0-20220728155337-03b66a5207d8
|
||||
github.com/anchore/stereoscope v0.0.0-20230208154630-5a306f07f2e7
|
||||
github.com/docker/docker v23.0.0+incompatible
|
||||
github.com/anchore/stereoscope v0.0.0-20230216143338-4b5ebf8c7f4b
|
||||
github.com/docker/docker v23.0.1+incompatible
|
||||
github.com/google/go-containerregistry v0.13.0
|
||||
github.com/invopop/jsonschema v0.7.0
|
||||
github.com/knqyf263/go-rpmdb v0.0.0-20221030135625-4082a22221ce
|
||||
@@ -71,7 +71,7 @@ require (
|
||||
github.com/Microsoft/go-winio v0.6.0 // indirect
|
||||
github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092 // indirect
|
||||
github.com/becheran/wildmatch-go v1.0.0 // indirect
|
||||
github.com/containerd/containerd v1.6.12 // indirect
|
||||
github.com/containerd/containerd v1.6.18 // indirect
|
||||
github.com/containerd/stargz-snapshotter/estargz v0.12.1 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/docker/cli v20.10.20+incompatible // indirect
|
||||
|
||||
@@ -90,8 +90,8 @@ github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b h1:e1bmaoJfZV
|
||||
github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b/go.mod h1:Bkc+JYWjMCF8OyZ340IMSIi2Ebf3uwByOk6ho4wne1E=
|
||||
github.com/anchore/packageurl-go v0.1.1-0.20230104203445-02e0a6721501 h1:AV7qjwMcM4r8wFhJq3jLRztew3ywIyPTRapl2T1s9o8=
|
||||
github.com/anchore/packageurl-go v0.1.1-0.20230104203445-02e0a6721501/go.mod h1:Blo6OgJNiYF41ufcgHKkbCKF2MDOMlrqhXv/ij6ocR4=
|
||||
github.com/anchore/stereoscope v0.0.0-20230208154630-5a306f07f2e7 h1:PrdFBPMyika+AM1/AwDmYqrVeUATDU90wbrd81ugicU=
|
||||
github.com/anchore/stereoscope v0.0.0-20230208154630-5a306f07f2e7/go.mod h1:TUCfo52tEz7ahTUFtKN//wcB7kJzQs0Oifmnd4NkIXw=
|
||||
github.com/anchore/stereoscope v0.0.0-20230216143338-4b5ebf8c7f4b h1:vMEAfz91QLjJq2W8JPxpIC4dG4OeynTY4MisHnZ19F0=
|
||||
github.com/anchore/stereoscope v0.0.0-20230216143338-4b5ebf8c7f4b/go.mod h1:6oSG43mzahqiktzXZDctqi1o66fwU2wDk3xki0KlnbA=
|
||||
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
|
||||
github.com/andybalholm/brotli v1.0.1/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
|
||||
github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY=
|
||||
@@ -132,8 +132,8 @@ github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWH
|
||||
github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cncf/xds/go v0.0.0-20211130200136-a8f946100490/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/containerd/containerd v1.6.12 h1:kJ9b3mOFKf8yqo05Ob+tMoxvt1pbVWhnB0re9Y+k+8c=
|
||||
github.com/containerd/containerd v1.6.12/go.mod h1:K4Bw7gjgh4TnkmQY+py/PYQGp4e7xgnHAeg87VeWb3A=
|
||||
github.com/containerd/containerd v1.6.18 h1:qZbsLvmyu+Vlty0/Ex5xc0z2YtKpIsb5n45mAMI+2Ns=
|
||||
github.com/containerd/containerd v1.6.18/go.mod h1:1RdCUu95+gc2v9t3IL+zIlpClSmew7/0YS8O5eQZrOw=
|
||||
github.com/containerd/stargz-snapshotter/estargz v0.12.1 h1:+7nYmHJb0tEkcRaAW+MHqoKaJYZmkikupxCqVtmPuY0=
|
||||
github.com/containerd/stargz-snapshotter/estargz v0.12.1/go.mod h1:12VUuCq3qPq4y8yUW+l5w3+oXV3cx2Po3KSe/SmPGqw=
|
||||
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
@@ -150,8 +150,8 @@ github.com/docker/cli v20.10.20+incompatible h1:lWQbHSHUFs7KraSN2jOJK7zbMS2jNCHI
|
||||
github.com/docker/cli v20.10.20+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||
github.com/docker/distribution v2.8.1+incompatible h1:Q50tZOPR6T/hjNsyc9g8/syEs6bk8XXApsHjKukMl68=
|
||||
github.com/docker/distribution v2.8.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||
github.com/docker/docker v23.0.0+incompatible h1:L6c28tNyqZ4/ub9AZC9d5QUuunoHHfEH4/Ue+h/E5nE=
|
||||
github.com/docker/docker v23.0.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/docker v23.0.1+incompatible h1:vjgvJZxprTTE1A37nm+CLNAdwu6xZekyoiVlUZEINcY=
|
||||
github.com/docker/docker v23.0.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/docker-credential-helpers v0.7.0 h1:xtCHsjxogADNZcdv1pKUHXryefjlVRqWqIhk/uXJp0A=
|
||||
github.com/docker/docker-credential-helpers v0.7.0/go.mod h1:rETQfLdHNT3foU5kuNkFR1R1V12OJRRO5lzt2D1b5X0=
|
||||
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
|
||||
@@ -716,8 +716,8 @@ golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qx
|
||||
golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
|
||||
golang.org/x/net v0.6.0 h1:L4ZwwTvKW9gr0ZMS1yrHD9GZhIuVjOBBnaKH+SPQK0Q=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
|
||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
|
||||
@@ -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,
|
||||
@@ -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" {
|
||||
@@ -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(
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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")
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -121,12 +121,89 @@ var defaultCandidateAdditions = buildCandidateLookup(
|
||||
candidateKey{PkgName: "yajl-ruby"},
|
||||
candidateAddition{AdditionalProducts: []string{"yajl-ruby_gem"}},
|
||||
},
|
||||
{
|
||||
pkg.GemPkg,
|
||||
candidateKey{PkgName: "cgi"},
|
||||
candidateAddition{AdditionalVendors: []string{"ruby-lang"}},
|
||||
},
|
||||
{
|
||||
pkg.GemPkg,
|
||||
candidateKey{PkgName: "date"},
|
||||
candidateAddition{AdditionalVendors: []string{"ruby-lang"}},
|
||||
},
|
||||
{
|
||||
pkg.GemPkg,
|
||||
candidateKey{PkgName: "openssl"},
|
||||
candidateAddition{AdditionalVendors: []string{"ruby-lang"}},
|
||||
},
|
||||
{
|
||||
pkg.GemPkg,
|
||||
candidateKey{PkgName: "rake"},
|
||||
candidateAddition{AdditionalVendors: []string{"ruby-lang"}},
|
||||
},
|
||||
{
|
||||
pkg.GemPkg,
|
||||
candidateKey{PkgName: "rdoc"},
|
||||
candidateAddition{AdditionalVendors: []string{"ruby-lang"}},
|
||||
},
|
||||
{
|
||||
pkg.GemPkg,
|
||||
candidateKey{PkgName: "rexml"},
|
||||
candidateAddition{AdditionalVendors: []string{"ruby-lang"}},
|
||||
},
|
||||
{
|
||||
pkg.GemPkg,
|
||||
candidateKey{PkgName: "trunk"},
|
||||
candidateAddition{AdditionalVendors: []string{"ruby-lang"}},
|
||||
},
|
||||
{
|
||||
pkg.GemPkg,
|
||||
candidateKey{PkgName: "webrick"},
|
||||
candidateAddition{AdditionalVendors: []string{"ruby-lang"}},
|
||||
},
|
||||
// Python packages
|
||||
{
|
||||
pkg.PythonPkg,
|
||||
candidateKey{PkgName: "python-rrdtool"},
|
||||
candidateAddition{AdditionalProducts: []string{"rrdtool"}},
|
||||
},
|
||||
{
|
||||
pkg.PythonPkg,
|
||||
candidateKey{PkgName: "cryptography"},
|
||||
candidateAddition{AdditionalProducts: []string{"python-cryptography"}, AdditionalVendors: []string{"python-cryptography_project"}},
|
||||
},
|
||||
{
|
||||
pkg.PythonPkg,
|
||||
candidateKey{PkgName: "pip"},
|
||||
candidateAddition{AdditionalVendors: []string{"pypa"}},
|
||||
},
|
||||
// Alpine packages
|
||||
{
|
||||
pkg.ApkPkg,
|
||||
candidateKey{PkgName: "python3"},
|
||||
candidateAddition{AdditionalProducts: []string{"python"}, AdditionalVendors: []string{"python", "python_software_foundation"}},
|
||||
},
|
||||
{
|
||||
pkg.ApkPkg,
|
||||
candidateKey{PkgName: "python"},
|
||||
candidateAddition{AdditionalVendors: []string{"python_software_foundation"}},
|
||||
},
|
||||
{
|
||||
pkg.ApkPkg,
|
||||
candidateKey{PkgName: "nodejs"},
|
||||
candidateAddition{AdditionalProducts: []string{"node.js"}},
|
||||
},
|
||||
{
|
||||
pkg.ApkPkg,
|
||||
candidateKey{PkgName: "nodejs-current"},
|
||||
candidateAddition{AdditionalProducts: []string{"node.js"}},
|
||||
},
|
||||
// Binary packages
|
||||
{
|
||||
pkg.BinaryPkg,
|
||||
candidateKey{PkgName: "node"},
|
||||
candidateAddition{AdditionalProducts: []string{"nodejs", "node.js"}},
|
||||
},
|
||||
})
|
||||
|
||||
var defaultCandidateRemovals = buildCandidateRemovalLookup(
|
||||
|
||||
@@ -109,6 +109,8 @@ func candidateVendors(p pkg.Package) []string {
|
||||
vendors.union(candidateVendorsForPython(p))
|
||||
case pkg.JavaMetadataType:
|
||||
vendors.union(candidateVendorsForJava(p))
|
||||
case pkg.ApkMetadataType:
|
||||
vendors.union(candidateVendorsForAPK(p))
|
||||
}
|
||||
|
||||
// try swapping hyphens for underscores, vice versa, and removing separators altogether
|
||||
@@ -156,6 +158,11 @@ func candidateProducts(p pkg.Package) []string {
|
||||
products.addValue(prod)
|
||||
}
|
||||
}
|
||||
|
||||
if p.MetadataType == pkg.ApkMetadataType {
|
||||
products.union(candidateProductsForAPK(p))
|
||||
}
|
||||
|
||||
// it is never OK to have candidates with these values ["" and "*"] (since CPEs will match any other value)
|
||||
products.removeByValue("")
|
||||
products.removeByValue("*")
|
||||
|
||||
@@ -100,6 +100,18 @@ func TestGeneratePackageCPEs(t *testing.T) {
|
||||
"cpe:2.3:a:william_goodman:name:3.2:*:*:*:*:*:*:*",
|
||||
"cpe:2.3:a:william_goodman:python-name:3.2:*:*:*:*:*:*:*",
|
||||
"cpe:2.3:a:william_goodman:python_name:3.2:*:*:*:*:*:*:*",
|
||||
"cpe:2.3:a:alex_goodman_project:python_name:3.2:*:*:*:*:*:*:*",
|
||||
"cpe:2.3:a:alex_goodman_project:name:3.2:*:*:*:*:*:*:*",
|
||||
"cpe:2.3:a:alex_goodman_project:python-name:3.2:*:*:*:*:*:*:*",
|
||||
"cpe:2.3:a:alex_goodmanproject:name:3.2:*:*:*:*:*:*:*",
|
||||
"cpe:2.3:a:alex_goodmanproject:python-name:3.2:*:*:*:*:*:*:*",
|
||||
"cpe:2.3:a:alex_goodmanproject:python_name:3.2:*:*:*:*:*:*:*",
|
||||
"cpe:2.3:a:william_goodman_project:name:3.2:*:*:*:*:*:*:*",
|
||||
"cpe:2.3:a:william_goodman_project:python-name:3.2:*:*:*:*:*:*:*",
|
||||
"cpe:2.3:a:william_goodman_project:python_name:3.2:*:*:*:*:*:*:*",
|
||||
"cpe:2.3:a:william_goodmanproject:name:3.2:*:*:*:*:*:*:*",
|
||||
"cpe:2.3:a:william_goodmanproject:python-name:3.2:*:*:*:*:*:*:*",
|
||||
"cpe:2.3:a:william_goodmanproject:python_name:3.2:*:*:*:*:*:*:*",
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,6 +1,19 @@
|
||||
package cpe
|
||||
|
||||
import "github.com/anchore/syft/syft/pkg"
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
)
|
||||
|
||||
func additionalVendorsForPython(v string) (vendors []string) {
|
||||
if !strings.HasSuffix(v, "project") {
|
||||
vendors = append(vendors, fmt.Sprintf("%sproject", v), fmt.Sprintf("%s_project", v))
|
||||
}
|
||||
|
||||
return vendors
|
||||
}
|
||||
|
||||
func candidateVendorsForPython(p pkg.Package) fieldCandidateSet {
|
||||
metadata, ok := p.Metadata.(pkg.PythonPackageMetadata)
|
||||
@@ -11,18 +24,36 @@ func candidateVendorsForPython(p pkg.Package) fieldCandidateSet {
|
||||
vendors := newFieldCandidateSet()
|
||||
|
||||
if metadata.Author != "" {
|
||||
name := normalizePersonName(metadata.Author)
|
||||
vendors.add(fieldCandidate{
|
||||
value: normalizePersonName(metadata.Author),
|
||||
value: name,
|
||||
disallowSubSelections: true,
|
||||
disallowDelimiterVariations: true,
|
||||
})
|
||||
|
||||
for _, v := range additionalVendorsForPython(name) {
|
||||
vendors.add(fieldCandidate{
|
||||
value: v,
|
||||
disallowSubSelections: true,
|
||||
disallowDelimiterVariations: true,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if metadata.AuthorEmail != "" {
|
||||
name := normalizePersonName(stripEmailSuffix(metadata.AuthorEmail))
|
||||
vendors.add(fieldCandidate{
|
||||
value: normalizePersonName(stripEmailSuffix(metadata.AuthorEmail)),
|
||||
value: name,
|
||||
disallowSubSelections: true,
|
||||
})
|
||||
|
||||
for _, v := range additionalVendorsForPython(name) {
|
||||
vendors.add(fieldCandidate{
|
||||
value: v,
|
||||
disallowSubSelections: true,
|
||||
disallowDelimiterVariations: true,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return vendors
|
||||
|
||||
@@ -10,14 +10,9 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestConvertCmdFlags(t *testing.T) {
|
||||
func TestAllFormatsConvertable(t *testing.T) {
|
||||
assertions := []traitAssertion{
|
||||
func(tb testing.TB, stdout, _ string, _ int) {
|
||||
tb.Helper()
|
||||
if len(stdout) < 1000 {
|
||||
tb.Errorf("there may not be any report output (len=%d)", len(stdout))
|
||||
}
|
||||
},
|
||||
assertStdoutLengthGreaterThan(1000),
|
||||
assertSuccessfulReturnCode,
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestConvertCmd(t *testing.T) {
|
||||
assertions := []traitAssertion{
|
||||
assertInOutput("PackageName: musl-utils"),
|
||||
assertSuccessfulReturnCode,
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
from string
|
||||
to string
|
||||
}{
|
||||
{from: "syft-json", to: "spdx-tag-value"},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(fmt.Sprintf("from %s to %s", test.from, test.to), func(t *testing.T) {
|
||||
sbomArgs := []string{"dir:./test-fixtures/image-pkg-coverage", "-o", test.from}
|
||||
cmd, stdout, stderr := runSyft(t, nil, sbomArgs...)
|
||||
if cmd.ProcessState.ExitCode() != 0 {
|
||||
t.Log("STDOUT:\n", stdout)
|
||||
t.Log("STDERR:\n", stderr)
|
||||
t.Log("COMMAND:", strings.Join(cmd.Args, " "))
|
||||
t.Fatalf("failure executing syft creating an sbom")
|
||||
return
|
||||
}
|
||||
|
||||
convertArgs := []string{"convert", "-", "-o", test.to}
|
||||
cmd = getSyftCommand(t, convertArgs...)
|
||||
|
||||
cmd.Stdin = strings.NewReader(stdout)
|
||||
stdout, stderr = runCommandObj(t, cmd, nil, false)
|
||||
|
||||
for _, traitFn := range assertions {
|
||||
traitFn(t, stdout, stderr, cmd.ProcessState.ExitCode())
|
||||
}
|
||||
if t.Failed() {
|
||||
t.Log("STDOUT:\n", stdout)
|
||||
t.Log("STDERR:\n", stderr)
|
||||
t.Log("COMMAND:", strings.Join(cmd.Args, " "))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
+43
-1
@@ -177,7 +177,7 @@ func runSyftCommand(t testing.TB, env map[string]string, expectError bool, args
|
||||
t.Errorf("STDOUT: %s", stdout)
|
||||
t.Errorf("STDERR: %s", stderr)
|
||||
|
||||
// this probably indicates a timeout
|
||||
// this probably indicates a timeout... lets run it again with more verbosity to help debug issues
|
||||
args = append(args, "-vv")
|
||||
cmd = getSyftCommand(t, args...)
|
||||
|
||||
@@ -194,6 +194,48 @@ func runSyftCommand(t testing.TB, env map[string]string, expectError bool, args
|
||||
return cmd, stdout, stderr
|
||||
}
|
||||
|
||||
func runCommandObj(t testing.TB, cmd *exec.Cmd, env map[string]string, expectError bool) (string, string) {
|
||||
cancel := make(chan bool, 1)
|
||||
defer func() {
|
||||
cancel <- true
|
||||
}()
|
||||
|
||||
if env == nil {
|
||||
env = make(map[string]string)
|
||||
}
|
||||
|
||||
// we should not have tests reaching out for app update checks
|
||||
env["SYFT_CHECK_FOR_APP_UPDATE"] = "false"
|
||||
|
||||
timeout := func() {
|
||||
select {
|
||||
case <-cancel:
|
||||
return
|
||||
case <-time.After(60 * time.Second):
|
||||
}
|
||||
|
||||
if cmd != nil && cmd.Process != nil {
|
||||
// get a stack trace printed
|
||||
err := cmd.Process.Signal(syscall.SIGABRT)
|
||||
if err != nil {
|
||||
t.Errorf("error aborting: %+v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
go timeout()
|
||||
|
||||
stdout, stderr, err := runCommand(cmd, env)
|
||||
|
||||
if !expectError && err != nil && stdout == "" {
|
||||
t.Errorf("error running syft: %+v", err)
|
||||
t.Errorf("STDOUT: %s", stdout)
|
||||
t.Errorf("STDERR: %s", stderr)
|
||||
}
|
||||
|
||||
return stdout, stderr
|
||||
}
|
||||
|
||||
func runCosign(t testing.TB, env map[string]string, args ...string) (*exec.Cmd, string, string) {
|
||||
cmd := getCommand(t, ".tmp/cosign", args...)
|
||||
if env == nil {
|
||||
|
||||
Reference in New Issue
Block a user