From 2989d3d975322435074a2b29cd43e19a32bf802e Mon Sep 17 00:00:00 2001 From: Alex Goodman Date: Tue, 1 Dec 2020 07:56:44 -0500 Subject: [PATCH] include CPEs with elementds from POM GroupId fields Signed-off-by: Alex Goodman --- go.mod | 1 + go.sum | 3 + syft/cataloger/cpe.go | 41 +++++-- syft/cataloger/cpe_test.go | 159 ++++++++++++++++++---------- test/integration/regression_test.go | 4 +- 5 files changed, 142 insertions(+), 66 deletions(-) diff --git a/go.mod b/go.mod index 0a952287b..6a93e2cc1 100644 --- a/go.mod +++ b/go.mod @@ -23,6 +23,7 @@ require ( github.com/olekukonko/tablewriter v0.0.4 github.com/package-url/packageurl-go v0.1.0 github.com/pelletier/go-toml v1.8.0 + github.com/scylladb/go-set v1.0.2 github.com/sergi/go-diff v1.1.0 github.com/sirupsen/logrus v1.6.0 github.com/spf13/afero v1.2.2 diff --git a/go.sum b/go.sum index ba3d7f335..b45b1fec5 100644 --- a/go.sum +++ b/go.sum @@ -263,6 +263,7 @@ github.com/facebookincubator/nvdtools v0.1.4/go.mod h1:0/FIVnSEl9YHXLq3tKBPpKaI0 github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= +github.com/fatih/set v0.2.1/go.mod h1:+RKtMCH+favT2+3YecHGxcc0b4KyVWA1QWWJUs4E0CI= github.com/fortytw2/leaktest v1.2.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= @@ -726,6 +727,8 @@ github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb github.com/sassoftware/go-rpmutils v0.0.0-20190420191620-a8f1baeba37b/go.mod h1:am+Fp8Bt506lA3Rk3QCmSqmYmLMnPDhdDUcosQCAx+I= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/sclevine/spec v1.2.0/go.mod h1:W4J29eT/Kzv7/b9IWLB055Z+qvVC9vt0Arko24q7p+U= +github.com/scylladb/go-set v1.0.2 h1:SkvlMCKhP0wyyct6j+0IHJkBkSZL+TDzZ4E7f7BCcRE= +github.com/scylladb/go-set v1.0.2/go.mod h1:DkpGd78rljTxKAnTDPFqXSGxvETQnJyuSOQwsHycqfs= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/securego/gosec v0.0.0-20200103095621-79fbf3af8d83/go.mod h1:vvbZ2Ae7AzSq3/kywjUDxSNq2SJ27RxCz2un0H3ePqE= github.com/securego/gosec v0.0.0-20200401082031-e946c8c39989 h1:rq2/kILQnPtq5oL4+IAjgVOjh5e2yj2aaCYi7squEvI= diff --git a/syft/cataloger/cpe.go b/syft/cataloger/cpe.go index 995cc2ad3..637cc2f86 100644 --- a/syft/cataloger/cpe.go +++ b/syft/cataloger/cpe.go @@ -2,6 +2,7 @@ package cataloger import ( "fmt" + "strings" "github.com/anchore/syft/internal" "github.com/anchore/syft/syft/pkg" @@ -31,6 +32,7 @@ func generatePackageCPEs(p pkg.Package) []pkg.CPE { // add a new entry... candidateCpe := wfn.NewAttributesWithAny() + candidateCpe.Part = "a" candidateCpe.Product = product candidateCpe.Vendor = vendor candidateCpe.Version = p.Version @@ -45,8 +47,6 @@ func generatePackageCPEs(p pkg.Package) []pkg.CPE { } func candidateTargetSoftwareAttrs(p pkg.Package) []string { - // TODO: expand with package metadata (from type assert) - // TODO: would be great to allow these to be overridden by user data/config var targetSw []string switch p.Language { @@ -68,14 +68,43 @@ func candidateTargetSoftwareAttrs(p pkg.Package) []string { } func candidateVendors(p pkg.Package) []string { - // TODO: expand with package metadata (from type assert) - vendors := []string{p.Name} - if p.Language == pkg.Python { + vendors := candidateProducts(p) + switch p.Language { + case pkg.Python: vendors = append(vendors, fmt.Sprintf("python-%s", p.Name)) + case pkg.Java: + if p.MetadataType == pkg.JavaMetadataType { + if metadata, ok := p.Metadata.(pkg.JavaMetadata); ok && metadata.PomProperties != nil { + // derive the vendor from the groupID (e.g. org.sonatype.nexus --> sonatype) + if strings.HasPrefix(metadata.PomProperties.GroupID, "org.") || strings.HasPrefix(metadata.PomProperties.GroupID, "com.") { + fields := strings.Split(metadata.PomProperties.GroupID, ".") + if len(fields) >= 3 { + vendors = append(vendors, fields[1]) + } + } + } + } } return vendors } func candidateProducts(p pkg.Package) []string { - return []string{p.Name} + var products = []string{p.Name} + switch p.Language { + case pkg.Java: + if p.MetadataType == pkg.JavaMetadataType { + if metadata, ok := p.Metadata.(pkg.JavaMetadata); ok && metadata.PomProperties != nil { + // derive the product from the groupID (e.g. org.sonatype.nexus --> nexus) + if strings.HasPrefix(metadata.PomProperties.GroupID, "org.") || strings.HasPrefix(metadata.PomProperties.GroupID, "com.") { + fields := strings.Split(metadata.PomProperties.GroupID, ".") + if len(fields) >= 3 { + products = append(products, fields[2]) + } + } + } + } + default: + return products + } + return products } diff --git a/syft/cataloger/cpe_test.go b/syft/cataloger/cpe_test.go index 7f895562b..2fad0ea99 100644 --- a/syft/cataloger/cpe_test.go +++ b/syft/cataloger/cpe_test.go @@ -1,23 +1,21 @@ package cataloger import ( + "sort" "testing" + "github.com/scylladb/go-set/strset" + + "github.com/scylladb/go-set" + "github.com/anchore/syft/syft/pkg" ) -func must(c pkg.CPE, e error) pkg.CPE { - if e != nil { - panic(e) - } - return c -} - func TestGenerate(t *testing.T) { tests := []struct { name string p pkg.Package - expected []pkg.CPE + expected []string }{ { name: "python language", @@ -28,13 +26,13 @@ func TestGenerate(t *testing.T) { Language: pkg.Python, Type: pkg.DebPkg, }, - expected: []pkg.CPE{ - must(pkg.NewCPE("cpe:2.3:*:*:name:3.2:*:*:*:*:*:*:*")), - must(pkg.NewCPE("cpe:2.3:*:*:name:3.2:*:*:*:*:python:*:*")), - must(pkg.NewCPE("cpe:2.3:*:name:name:3.2:*:*:*:*:*:*:*")), - must(pkg.NewCPE("cpe:2.3:*:name:name:3.2:*:*:*:*:python:*:*")), - must(pkg.NewCPE("cpe:2.3:*:python-name:name:3.2:*:*:*:*:*:*:*")), - must(pkg.NewCPE("cpe:2.3:*:python-name:name:3.2:*:*:*:*:python:*:*")), + expected: []string{ + "cpe:2.3:a:*:name:3.2:*:*:*:*:*:*:*", + "cpe:2.3:a:*:name:3.2:*:*:*:*:python:*:*", + "cpe:2.3:a:name:name:3.2:*:*:*:*:*:*:*", + "cpe:2.3:a:name:name:3.2:*:*:*:*:python:*:*", + "cpe:2.3:a:python-name:name:3.2:*:*:*:*:*:*:*", + "cpe:2.3:a:python-name:name:3.2:*:*:*:*:python:*:*", }, }, { @@ -46,13 +44,13 @@ func TestGenerate(t *testing.T) { Language: pkg.JavaScript, Type: pkg.DebPkg, }, - expected: []pkg.CPE{ - must(pkg.NewCPE("cpe:2.3:*:*:name:3.2:*:*:*:*:*:*:*")), - must(pkg.NewCPE("cpe:2.3:*:*:name:3.2:*:*:*:*:node.js:*:*")), - must(pkg.NewCPE("cpe:2.3:*:*:name:3.2:*:*:*:*:nodejs:*:*")), - must(pkg.NewCPE("cpe:2.3:*:name:name:3.2:*:*:*:*:*:*:*")), - must(pkg.NewCPE("cpe:2.3:*:name:name:3.2:*:*:*:*:node.js:*:*")), - must(pkg.NewCPE("cpe:2.3:*:name:name:3.2:*:*:*:*:nodejs:*:*")), + expected: []string{ + "cpe:2.3:a:*:name:3.2:*:*:*:*:*:*:*", + "cpe:2.3:a:*:name:3.2:*:*:*:*:node.js:*:*", + "cpe:2.3:a:*:name:3.2:*:*:*:*:nodejs:*:*", + "cpe:2.3:a:name:name:3.2:*:*:*:*:*:*:*", + "cpe:2.3:a:name:name:3.2:*:*:*:*:node.js:*:*", + "cpe:2.3:a:name:name:3.2:*:*:*:*:nodejs:*:*", }, }, { @@ -64,13 +62,13 @@ func TestGenerate(t *testing.T) { Language: pkg.Ruby, Type: pkg.DebPkg, }, - expected: []pkg.CPE{ - must(pkg.NewCPE("cpe:2.3:*:*:name:3.2:*:*:*:*:*:*:*")), - must(pkg.NewCPE("cpe:2.3:*:*:name:3.2:*:*:*:*:ruby:*:*")), - must(pkg.NewCPE("cpe:2.3:*:*:name:3.2:*:*:*:*:rails:*:*")), - must(pkg.NewCPE("cpe:2.3:*:name:name:3.2:*:*:*:*:*:*:*")), - must(pkg.NewCPE("cpe:2.3:*:name:name:3.2:*:*:*:*:ruby:*:*")), - must(pkg.NewCPE("cpe:2.3:*:name:name:3.2:*:*:*:*:rails:*:*")), + expected: []string{ + "cpe:2.3:a:*:name:3.2:*:*:*:*:*:*:*", + "cpe:2.3:a:*:name:3.2:*:*:*:*:ruby:*:*", + "cpe:2.3:a:*:name:3.2:*:*:*:*:rails:*:*", + "cpe:2.3:a:name:name:3.2:*:*:*:*:*:*:*", + "cpe:2.3:a:name:name:3.2:*:*:*:*:ruby:*:*", + "cpe:2.3:a:name:name:3.2:*:*:*:*:rails:*:*", }, }, { @@ -82,13 +80,55 @@ func TestGenerate(t *testing.T) { Language: pkg.Java, Type: pkg.DebPkg, }, - expected: []pkg.CPE{ - must(pkg.NewCPE("cpe:2.3:*:*:name:3.2:*:*:*:*:*:*:*")), - must(pkg.NewCPE("cpe:2.3:*:*:name:3.2:*:*:*:*:java:*:*")), - must(pkg.NewCPE("cpe:2.3:*:*:name:3.2:*:*:*:*:maven:*:*")), - must(pkg.NewCPE("cpe:2.3:*:name:name:3.2:*:*:*:*:*:*:*")), - must(pkg.NewCPE("cpe:2.3:*:name:name:3.2:*:*:*:*:java:*:*")), - must(pkg.NewCPE("cpe:2.3:*:name:name:3.2:*:*:*:*:maven:*:*")), + expected: []string{ + "cpe:2.3:a:*:name:3.2:*:*:*:*:*:*:*", + "cpe:2.3:a:*:name:3.2:*:*:*:*:java:*:*", + "cpe:2.3:a:*:name:3.2:*:*:*:*:maven:*:*", + "cpe:2.3:a:name:name:3.2:*:*:*:*:*:*:*", + "cpe:2.3:a:name:name:3.2:*:*:*:*:java:*:*", + "cpe:2.3:a:name:name:3.2:*:*:*:*:maven:*:*", + }, + }, + { + name: "java language with groupID", + p: pkg.Package{ + Name: "name", + Version: "3.2", + FoundBy: "some-analyzer", + Language: pkg.Java, + Type: pkg.JavaPkg, + MetadataType: pkg.JavaMetadataType, + Metadata: pkg.JavaMetadata{ + PomProperties: &pkg.PomProperties{ + GroupID: "org.sonatype.nexus", + }, + }, + }, + expected: []string{ + "cpe:2.3:a:*:name:3.2:*:*:*:*:*:*:*", + "cpe:2.3:a:*:name:3.2:*:*:*:*:java:*:*", + "cpe:2.3:a:*:name:3.2:*:*:*:*:maven:*:*", + "cpe:2.3:a:name:name:3.2:*:*:*:*:*:*:*", + "cpe:2.3:a:name:name:3.2:*:*:*:*:java:*:*", + "cpe:2.3:a:name:name:3.2:*:*:*:*:maven:*:*", + "cpe:2.3:a:sonatype:name:3.2:*:*:*:*:*:*:*", + "cpe:2.3:a:sonatype:name:3.2:*:*:*:*:java:*:*", + "cpe:2.3:a:sonatype:name:3.2:*:*:*:*:maven:*:*", + "cpe:2.3:a:*:nexus:3.2:*:*:*:*:*:*:*", + "cpe:2.3:a:*:nexus:3.2:*:*:*:*:java:*:*", + "cpe:2.3:a:*:nexus:3.2:*:*:*:*:maven:*:*", + "cpe:2.3:a:sonatype:nexus:3.2:*:*:*:*:*:*:*", + "cpe:2.3:a:sonatype:nexus:3.2:*:*:*:*:java:*:*", + "cpe:2.3:a:sonatype:nexus:3.2:*:*:*:*:maven:*:*", + "cpe:2.3:a:nexus:name:3.2:*:*:*:*:*:*:*", + "cpe:2.3:a:nexus:name:3.2:*:*:*:*:java:*:*", + "cpe:2.3:a:nexus:name:3.2:*:*:*:*:maven:*:*", + "cpe:2.3:a:name:nexus:3.2:*:*:*:*:*:*:*", + "cpe:2.3:a:name:nexus:3.2:*:*:*:*:java:*:*", + "cpe:2.3:a:name:nexus:3.2:*:*:*:*:maven:*:*", + "cpe:2.3:a:nexus:nexus:3.2:*:*:*:*:*:*:*", + "cpe:2.3:a:nexus:nexus:3.2:*:*:*:*:java:*:*", + "cpe:2.3:a:nexus:nexus:3.2:*:*:*:*:maven:*:*", }, }, { @@ -100,17 +140,17 @@ func TestGenerate(t *testing.T) { Language: pkg.Java, Type: pkg.JenkinsPluginPkg, }, - expected: []pkg.CPE{ - must(pkg.NewCPE("cpe:2.3:*:*:name:3.2:*:*:*:*:*:*:*")), - must(pkg.NewCPE("cpe:2.3:*:*:name:3.2:*:*:*:*:java:*:*")), - must(pkg.NewCPE("cpe:2.3:*:*:name:3.2:*:*:*:*:maven:*:*")), - must(pkg.NewCPE("cpe:2.3:*:*:name:3.2:*:*:*:*:jenkins:*:*")), - must(pkg.NewCPE("cpe:2.3:*:*:name:3.2:*:*:*:*:cloudbees_jenkins:*:*")), - must(pkg.NewCPE("cpe:2.3:*:name:name:3.2:*:*:*:*:*:*:*")), - must(pkg.NewCPE("cpe:2.3:*:name:name:3.2:*:*:*:*:java:*:*")), - must(pkg.NewCPE("cpe:2.3:*:name:name:3.2:*:*:*:*:maven:*:*")), - must(pkg.NewCPE("cpe:2.3:*:name:name:3.2:*:*:*:*:jenkins:*:*")), - must(pkg.NewCPE("cpe:2.3:*:name:name:3.2:*:*:*:*:cloudbees_jenkins:*:*")), + expected: []string{ + "cpe:2.3:a:*:name:3.2:*:*:*:*:*:*:*", + "cpe:2.3:a:*:name:3.2:*:*:*:*:java:*:*", + "cpe:2.3:a:*:name:3.2:*:*:*:*:maven:*:*", + "cpe:2.3:a:*:name:3.2:*:*:*:*:jenkins:*:*", + "cpe:2.3:a:*:name:3.2:*:*:*:*:cloudbees_jenkins:*:*", + "cpe:2.3:a:name:name:3.2:*:*:*:*:*:*:*", + "cpe:2.3:a:name:name:3.2:*:*:*:*:java:*:*", + "cpe:2.3:a:name:name:3.2:*:*:*:*:maven:*:*", + "cpe:2.3:a:name:name:3.2:*:*:*:*:jenkins:*:*", + "cpe:2.3:a:name:name:3.2:*:*:*:*:cloudbees_jenkins:*:*", }, }, } @@ -119,19 +159,24 @@ func TestGenerate(t *testing.T) { t.Run(test.name, func(t *testing.T) { actual := generatePackageCPEs(test.p) - if len(actual) != len(test.expected) { - for _, e := range actual { - t.Errorf(" unexpected entry: %+v", e.BindToFmtString()) - } - t.Fatalf("unexpected number of entries: %d", len(actual)) + expectedCpeSet := set.NewStringSet(test.expected...) + actualCpeSet := set.NewStringSet() + for _, a := range actual { + actualCpeSet.Add(a.BindToFmtString()) } - for idx, a := range actual { - e := test.expected[idx] - if a.BindToFmtString() != e.BindToFmtString() { - t.Errorf("mismatched entries @ %d:\n\texpected:%+v\n\t actual:%+v\n", idx, e.BindToFmtString(), a.BindToFmtString()) - } + extra := strset.Difference(expectedCpeSet, actualCpeSet).List() + sort.Strings(extra) + for _, d := range extra { + t.Errorf("extra CPE: %+v", d) } + + missing := strset.Difference(actualCpeSet, expectedCpeSet).List() + sort.Strings(missing) + for _, d := range missing { + t.Errorf("missing CPE: %+v", d) + } + }) } } diff --git a/test/integration/regression_test.go b/test/integration/regression_test.go index d018e85b0..1d5340d1f 100644 --- a/test/integration/regression_test.go +++ b/test/integration/regression_test.go @@ -1,5 +1,3 @@ -// +build integration - package integration import ( @@ -21,7 +19,7 @@ func TestRegression212ApkBufferSize(t *testing.T) { tarPath := imagetest.GetFixtureImageTarPath(t, fixtureImageName) defer cleanup() - catalog, _, _, err := syft.Catalog("docker-archive:"+tarPath, source.SquashedScope) + _, catalog, _, err := syft.Catalog("docker-archive:"+tarPath, source.SquashedScope) if err != nil { t.Fatalf("failed to catalog image: %+v", err) }