Compare commits

...
5 Commits
Author SHA1 Message Date
Benji Visser bc1edb9c8a adding purl types for binary classifiers (#1435)
Signed-off-by: Benji Visser <benji@093b.org>
2023-01-04 11:34:37 -05:00
Keith Zantow 64be0a1072 chore: refactor basic CPE functionality to its own package (#1436) 2023-01-04 11:26:28 -05:00
Justin Chadwell e3d6ffd30e fix: typo in os.Getwd error message (#1433)
Signed-off-by: Justin Chadwell <me@jedevc.com>
2023-01-03 14:56:20 +00:00
Justin Chadwell 8d36b21237 fix: additional excessive go binary warnings (#1432)
The original fix b125ea83ba didn't catch
all the excessive warnings, it seems like getArches can also be called
on binaries that aren't neccessarily go binaries, so the messages from
this should also be Trace instead of Warn.

Signed-off-by: Justin Chadwell <me@jedevc.com>
2023-01-03 09:54:08 -05:00
Rui Chen 6a7d6e6071 docs: migrate to homebrew-core (#1427) 2023-01-02 08:16:32 -05:00
33 changed files with 354 additions and 286 deletions
-1
View File
@@ -75,7 +75,6 @@ choco install syft -y
### Homebrew
```bash
brew tap anchore/syft
brew install syft
```
@@ -1,4 +1,4 @@
package pkg
package cpe
import (
"sort"
@@ -6,15 +6,15 @@ import (
"github.com/facebookincubator/nvdtools/wfn"
)
var _ sort.Interface = (*CPEBySpecificity)(nil)
var _ sort.Interface = (*BySpecificity)(nil)
type CPEBySpecificity []wfn.Attributes
type BySpecificity []wfn.Attributes
func (c CPEBySpecificity) Len() int { return len(c) }
func (c BySpecificity) Len() int { return len(c) }
func (c CPEBySpecificity) Swap(i, j int) { c[i], c[j] = c[j], c[i] }
func (c BySpecificity) Swap(i, j int) { c[i], c[j] = c[j], c[i] }
func (c CPEBySpecificity) Less(i, j int) bool {
func (c BySpecificity) Less(i, j int) bool {
iScore := weightedCountForSpecifiedFields(c[i])
jScore := weightedCountForSpecifiedFields(c[j])
@@ -29,7 +29,7 @@ func (c CPEBySpecificity) Less(i, j int) bool {
}
// if score and length are equal then text sort
// note that we are not using CPEString from the syft pkg
// note that we are not using String from the syft pkg
// as we are not encoding/decoding this CPE string so we don't
// need the proper quoted version of the CPE.
return c[i].BindToFmtString() < c[j].BindToFmtString()
+98
View File
@@ -0,0 +1,98 @@
package cpe
import (
"sort"
"testing"
"github.com/stretchr/testify/assert"
)
func Test_BySpecificity(t *testing.T) {
tests := []struct {
name string
input []CPE
expected []CPE
}{
{
name: "sort strictly by wfn *",
input: []CPE{
Must("cpe:2.3:a:*:package:1:*:*:*:*:*:*:*"),
Must("cpe:2.3:a:some:package:1:*:*:*:*:*:*:*"),
Must("cpe:2.3:a:*:package:1:*:*:*:*:some:*:*"),
Must("cpe:2.3:a:some:package:1:*:*:*:*:some:*:*"),
Must("cpe:2.3:a:some:package:*:*:*:*:*:*:*:*"),
},
expected: []CPE{
Must("cpe:2.3:a:some:package:1:*:*:*:*:some:*:*"),
Must("cpe:2.3:a:some:package:1:*:*:*:*:*:*:*"),
Must("cpe:2.3:a:some:package:*:*:*:*:*:*:*:*"),
Must("cpe:2.3:a:*:package:1:*:*:*:*:some:*:*"),
Must("cpe:2.3:a:*:package:1:*:*:*:*:*:*:*"),
},
},
{
name: "sort strictly by field length",
input: []CPE{
Must("cpe:2.3:a:1:22:1:*:*:*:*:1:*:*"),
Must("cpe:2.3:a:55555:1:1:*:*:*:*:1:*:*"),
Must("cpe:2.3:a:1:1:333:*:*:*:*:1:*:*"),
Must("cpe:2.3:a:1:666666:1:*:*:*:*:1:*:*"),
Must("cpe:2.3:a:1:1:1:*:*:*:*:1:*:*"),
Must("cpe:2.3:a:1:1:1:*:*:*:*:4444:*:*"),
},
expected: []CPE{
Must("cpe:2.3:a:1:666666:1:*:*:*:*:1:*:*"),
Must("cpe:2.3:a:55555:1:1:*:*:*:*:1:*:*"),
Must("cpe:2.3:a:1:1:1:*:*:*:*:4444:*:*"),
Must("cpe:2.3:a:1:1:333:*:*:*:*:1:*:*"),
Must("cpe:2.3:a:1:22:1:*:*:*:*:1:*:*"),
Must("cpe:2.3:a:1:1:1:*:*:*:*:1:*:*"),
},
},
{
name: "sort by mix of field length and specificity",
input: []CPE{
Must("cpe:2.3:a:1:666666:*:*:*:*:*:1:*:*"),
Must("cpe:2.3:a:*:1:1:*:*:*:*:4444:*:*"),
Must("cpe:2.3:a:1:*:333:*:*:*:*:*:*:*"),
Must("cpe:2.3:a:1:1:1:*:*:*:*:1:*:*"),
Must("cpe:2.3:a:1:22:1:*:*:*:*:1:*:*"),
Must("cpe:2.3:a:55555:1:1:*:*:*:*:1:*:*"),
},
expected: []CPE{
Must("cpe:2.3:a:55555:1:1:*:*:*:*:1:*:*"),
Must("cpe:2.3:a:1:22:1:*:*:*:*:1:*:*"),
Must("cpe:2.3:a:1:1:1:*:*:*:*:1:*:*"),
Must("cpe:2.3:a:1:666666:*:*:*:*:*:1:*:*"),
Must("cpe:2.3:a:*:1:1:*:*:*:*:4444:*:*"),
Must("cpe:2.3:a:1:*:333:*:*:*:*:*:*:*"),
},
},
{
name: "sort by mix of field length, specificity, dash",
input: []CPE{
Must("cpe:2.3:a:alpine:alpine_keys:2.3-r1:*:*:*:*:*:*:*"),
Must("cpe:2.3:a:alpine_keys:alpine_keys:2.3-r1:*:*:*:*:*:*:*"),
Must("cpe:2.3:a:alpine-keys:alpine_keys:2.3-r1:*:*:*:*:*:*:*"),
Must("cpe:2.3:a:alpine:alpine-keys:2.3-r1:*:*:*:*:*:*:*"),
Must("cpe:2.3:a:alpine-keys:alpine-keys:2.3-r1:*:*:*:*:*:*:*"),
Must("cpe:2.3:a:alpine_keys:alpine-keys:2.3-r1:*:*:*:*:*:*:*"),
},
expected: []CPE{
Must("cpe:2.3:a:alpine-keys:alpine-keys:2.3-r1:*:*:*:*:*:*:*"),
Must("cpe:2.3:a:alpine-keys:alpine_keys:2.3-r1:*:*:*:*:*:*:*"),
Must("cpe:2.3:a:alpine_keys:alpine-keys:2.3-r1:*:*:*:*:*:*:*"),
Must("cpe:2.3:a:alpine_keys:alpine_keys:2.3-r1:*:*:*:*:*:*:*"),
Must("cpe:2.3:a:alpine:alpine-keys:2.3-r1:*:*:*:*:*:*:*"),
Must("cpe:2.3:a:alpine:alpine_keys:2.3-r1:*:*:*:*:*:*:*"),
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
sort.Sort(BySpecificity(test.input))
assert.Equal(t, test.expected, test.input)
})
}
}
+29 -28
View File
@@ -1,4 +1,4 @@
package pkg
package cpe
import (
"fmt"
@@ -24,17 +24,17 @@ const cpeRegexString = ((`^([c][pP][eE]:/[AHOaho]?(:[A-Za-z0-9\._\-~%]*){0,6})`)
var cpeRegex = regexp.MustCompile(cpeRegexString)
// NewCPE will parse a formatted CPE string and return a CPE object. Some input, such as the existence of whitespace
// New will parse a formatted CPE string and return a CPE object. Some input, such as the existence of whitespace
// characters is allowed, however, a more strict validation is done after this sanitization process.
func NewCPE(cpeStr string) (CPE, error) {
func New(cpeStr string) (CPE, error) {
// get a CPE object based on the given string --don't validate yet since it may be possible to escape select cases on the callers behalf
c, err := newCPEWithoutValidation(cpeStr)
c, err := newWithoutValidation(cpeStr)
if err != nil {
return CPE{}, fmt.Errorf("unable to parse CPE string: %w", err)
}
// ensure that this CPE can be validated after being fully sanitized
if ValidateCPEString(CPEString(c)) != nil {
if ValidateString(String(c)) != nil {
return CPE{}, err
}
@@ -43,7 +43,16 @@ func NewCPE(cpeStr string) (CPE, error) {
return c, nil
}
func ValidateCPEString(cpeStr string) error {
// Must returns a CPE or panics if the provided string is not valid
func Must(cpeStr string) CPE {
c, err := New(cpeStr)
if err != nil {
panic(err)
}
return c
}
func ValidateString(cpeStr string) error {
// We should filter out all CPEs that do not match the official CPE regex
// The facebook nvdtools parser can sometimes incorrectly parse invalid CPE strings
if !cpeRegex.MatchString(cpeStr) {
@@ -52,7 +61,7 @@ func ValidateCPEString(cpeStr string) error {
return nil
}
func newCPEWithoutValidation(cpeStr string) (CPE, error) {
func newWithoutValidation(cpeStr string) (CPE, error) {
value, err := wfn.Parse(cpeStr)
if err != nil {
return CPE{}, fmt.Errorf("failed to parse CPE=%q: %w", cpeStr, err)
@@ -63,30 +72,22 @@ func newCPEWithoutValidation(cpeStr string) (CPE, error) {
}
// we need to compare the raw data since we are constructing CPEs in other locations
value.Vendor = normalizeCpeField(value.Vendor)
value.Product = normalizeCpeField(value.Product)
value.Language = normalizeCpeField(value.Language)
value.Version = normalizeCpeField(value.Version)
value.TargetSW = normalizeCpeField(value.TargetSW)
value.Part = normalizeCpeField(value.Part)
value.Edition = normalizeCpeField(value.Edition)
value.Other = normalizeCpeField(value.Other)
value.SWEdition = normalizeCpeField(value.SWEdition)
value.TargetHW = normalizeCpeField(value.TargetHW)
value.Update = normalizeCpeField(value.Update)
value.Vendor = normalizeField(value.Vendor)
value.Product = normalizeField(value.Product)
value.Language = normalizeField(value.Language)
value.Version = normalizeField(value.Version)
value.TargetSW = normalizeField(value.TargetSW)
value.Part = normalizeField(value.Part)
value.Edition = normalizeField(value.Edition)
value.Other = normalizeField(value.Other)
value.SWEdition = normalizeField(value.SWEdition)
value.TargetHW = normalizeField(value.TargetHW)
value.Update = normalizeField(value.Update)
return *value, nil
}
func MustCPE(cpeStr string) CPE {
c, err := NewCPE(cpeStr)
if err != nil {
panic(err)
}
return c
}
func normalizeCpeField(field string) string {
func normalizeField(field string) string {
// replace spaces with underscores (per section 5.3.2 of the CPE spec v 2.3)
field = strings.ReplaceAll(field, " ", "_")
@@ -112,7 +113,7 @@ func stripSlashes(s string) string {
return sb.String()
}
func CPEString(c CPE) string {
func String(c CPE) string {
output := CPE{}
output.Vendor = sanitize(c.Vendor)
output.Product = sanitize(c.Product)
+19 -26
View File
@@ -1,4 +1,4 @@
package pkg
package cpe
import (
"encoding/json"
@@ -11,14 +11,7 @@ import (
"github.com/stretchr/testify/require"
)
func must(c CPE, e error) CPE {
if e != nil {
panic(e)
}
return c
}
func TestNewCPE(t *testing.T) {
func Test_New(t *testing.T) {
tests := []struct {
name string
input string
@@ -27,29 +20,29 @@ func TestNewCPE(t *testing.T) {
{
name: "gocase",
input: `cpe:/a:10web:form_maker:1.0.0::~~~wordpress~~`,
expected: must(NewCPE(`cpe:2.3:a:10web:form_maker:1.0.0:*:*:*:*:wordpress:*:*`)),
expected: Must(`cpe:2.3:a:10web:form_maker:1.0.0:*:*:*:*:wordpress:*:*`),
},
{
name: "dashes",
input: `cpe:/a:7-zip:7-zip:4.56:beta:~~~windows~~`,
expected: must(NewCPE(`cpe:2.3:a:7-zip:7-zip:4.56:beta:*:*:*:windows:*:*`)),
expected: Must(`cpe:2.3:a:7-zip:7-zip:4.56:beta:*:*:*:windows:*:*`),
},
{
name: "URL escape characters",
input: `cpe:/a:%240.99_kindle_books_project:%240.99_kindle_books:6::~~~android~~`,
expected: must(NewCPE(`cpe:2.3:a:\$0.99_kindle_books_project:\$0.99_kindle_books:6:*:*:*:*:android:*:*`)),
expected: Must(`cpe:2.3:a:\$0.99_kindle_books_project:\$0.99_kindle_books:6:*:*:*:*:android:*:*`),
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
actual, err := NewCPE(test.input)
actual, err := New(test.input)
if err != nil {
t.Fatalf("got an error while creating CPE: %+v", err)
}
if CPEString(actual) != CPEString(test.expected) {
t.Errorf("mismatched entries:\n\texpected:%+v\n\t actual:%+v\n", CPEString(test.expected), CPEString(actual))
if String(actual) != String(test.expected) {
t.Errorf("mismatched entries:\n\texpected:%+v\n\t actual:%+v\n", String(test.expected), String(actual))
}
})
@@ -81,7 +74,7 @@ func Test_normalizeCpeField(t *testing.T) {
}
for _, test := range tests {
t.Run(test.field, func(t *testing.T) {
assert.Equal(t, test.expected, normalizeCpeField(test.field))
assert.Equal(t, test.expected, normalizeField(test.field))
})
}
}
@@ -98,14 +91,14 @@ func Test_CPEParser(t *testing.T) {
for _, test := range testCases {
t.Run(test.CPEString, func(t *testing.T) {
c1, err := NewCPE(test.CPEString)
c1, err := New(test.CPEString)
assert.NoError(t, err)
c2, err := NewCPE(test.CPEUrl)
c2, err := New(test.CPEUrl)
assert.NoError(t, err)
assert.Equal(t, c1, c2)
assert.Equal(t, c1, test.WFN)
assert.Equal(t, c2, test.WFN)
assert.Equal(t, CPEString(test.WFN), test.CPEString)
assert.Equal(t, String(test.WFN), test.CPEString)
})
}
}
@@ -167,16 +160,16 @@ func Test_InvalidCPE(t *testing.T) {
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
c, err := NewCPE(test.in)
c, err := New(test.in)
if test.expectedErr {
assert.Error(t, err)
if t.Failed() {
t.Logf("got CPE: %q details: %+v", CPEString(c), c)
t.Logf("got CPE: %q details: %+v", String(c), c)
}
return
}
require.NoError(t, err)
assert.Equal(t, test.expected, CPEString(c))
assert.Equal(t, test.expected, String(c))
})
}
}
@@ -222,13 +215,13 @@ func Test_RoundTrip(t *testing.T) {
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
// CPE string must be preserved through a round trip
assert.Equal(t, test.cpe, CPEString(MustCPE(test.cpe)))
assert.Equal(t, test.cpe, String(Must(test.cpe)))
// The parsed CPE must be the same after a round trip
assert.Equal(t, MustCPE(test.cpe), MustCPE(CPEString(MustCPE(test.cpe))))
assert.Equal(t, Must(test.cpe), Must(String(Must(test.cpe))))
// The test case parsed CPE must be the same after parsing the input string
assert.Equal(t, test.parsedCPE, MustCPE(test.cpe))
assert.Equal(t, test.parsedCPE, Must(test.cpe))
// The test case parsed CPE must produce the same string as the input cpe
assert.Equal(t, CPEString(test.parsedCPE), test.cpe)
assert.Equal(t, String(test.parsedCPE), test.cpe)
})
}
}
@@ -1,10 +1,10 @@
package pkg
package cpe
import (
"sort"
)
func mergeCPEs(a, b []CPE) (result []CPE) {
func Merge(a, b []CPE) (result []CPE) {
aCPEs := make(map[string]CPE)
// keep all CPEs from a and create a quick string-based lookup
@@ -20,6 +20,6 @@ func mergeCPEs(a, b []CPE) (result []CPE) {
}
}
sort.Sort(CPEBySpecificity(result))
sort.Sort(BySpecificity(result))
return result
}
+41
View File
@@ -0,0 +1,41 @@
package cpe
import (
"testing"
"github.com/stretchr/testify/assert"
)
func Test_Merge(t *testing.T) {
tests := []struct {
name string
input [][]CPE
expected []CPE
}{
{
name: "merge, removing duplicates and ordered",
input: [][]CPE{
{
Must("cpe:2.3:a:*:package:1:*:*:*:*:*:*:*"),
Must("cpe:2.3:a:some:package:*:*:*:*:*:*:*:*"),
},
{
Must("cpe:2.3:a:some:package:1:*:*:*:*:*:*:*"),
Must("cpe:2.3:a:some:package:*:*:*:*:*:*:*:*"),
},
},
expected: []CPE{
Must("cpe:2.3:a:some:package:1:*:*:*:*:*:*:*"),
Must("cpe:2.3:a:some:package:*:*:*:*:*:*:*:*"),
Must("cpe:2.3:a:*:package:1:*:*:*:*:*:*:*"),
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
out := Merge(test.input[0], test.input[1])
assert.Equal(t, test.expected, out)
})
}
}
+6 -5
View File
@@ -4,6 +4,7 @@ import (
"github.com/CycloneDX/cyclonedx-go"
"github.com/anchore/syft/internal/log"
"github.com/anchore/syft/syft/cpe"
"github.com/anchore/syft/syft/pkg"
)
@@ -11,7 +12,7 @@ func encodeSingleCPE(p pkg.Package) string {
// Since the CPEs in a package are sorted by specificity
// we can extract the first CPE as the one to output in cyclonedx
if len(p.CPEs) > 0 {
return pkg.CPEString(p.CPEs[0])
return cpe.String(p.CPEs[0])
}
return ""
}
@@ -24,15 +25,15 @@ func encodeCPEs(p pkg.Package) (out []cyclonedx.Property) {
}
out = append(out, cyclonedx.Property{
Name: "syft:cpe23",
Value: pkg.CPEString(c),
Value: cpe.String(c),
})
}
return
}
func decodeCPEs(c *cyclonedx.Component) (out []pkg.CPE) {
func decodeCPEs(c *cyclonedx.Component) (out []cpe.CPE) {
if c.CPE != "" {
cp, err := pkg.NewCPE(c.CPE)
cp, err := cpe.New(c.CPE)
if err != nil {
log.Warnf("invalid CPE: %s", c.CPE)
} else {
@@ -43,7 +44,7 @@ func decodeCPEs(c *cyclonedx.Component) (out []pkg.CPE) {
if c.Properties != nil {
for _, p := range *c.Properties {
if p.Name == "syft:cpe23" {
cp, err := pkg.NewCPE(p.Value)
cp, err := cpe.New(p.Value)
if err != nil {
log.Warnf("invalid CPE: %s", p.Value)
} else {
@@ -5,12 +5,13 @@ import (
"github.com/stretchr/testify/assert"
"github.com/anchore/syft/syft/cpe"
"github.com/anchore/syft/syft/pkg"
)
func Test_encodeCPE(t *testing.T) {
testCPE := pkg.MustCPE("cpe:2.3:a:name:name:3.2:*:*:*:*:*:*:*")
testCPE2 := pkg.MustCPE("cpe:2.3:a:name:name2:3.2:*:*:*:*:*:*:*")
testCPE := cpe.Must("cpe:2.3:a:name:name:3.2:*:*:*:*:*:*:*")
testCPE2 := cpe.Must("cpe:2.3:a:name:name2:3.2:*:*:*:*:*:*:*")
tests := []struct {
name string
input pkg.Package
@@ -20,14 +21,14 @@ func Test_encodeCPE(t *testing.T) {
// note: since this is an optional field, no value is preferred over NONE or NOASSERTION
name: "no metadata",
input: pkg.Package{
CPEs: []pkg.CPE{},
CPEs: []cpe.CPE{},
},
expected: "",
},
{
name: "single CPE",
input: pkg.Package{
CPEs: []pkg.CPE{
CPEs: []cpe.CPE{
testCPE,
},
},
@@ -36,7 +37,7 @@ func Test_encodeCPE(t *testing.T) {
{
name: "multiple CPEs",
input: pkg.Package{
CPEs: []pkg.CPE{
CPEs: []cpe.CPE{
testCPE2,
testCPE,
},
@@ -9,6 +9,7 @@ import (
"github.com/anchore/syft/internal"
"github.com/anchore/syft/internal/log"
"github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/cpe"
"github.com/anchore/syft/syft/linux"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/sbom"
@@ -100,12 +101,12 @@ func toOSComponent(distro *linux.Release) []cyclonedx.Component {
}
func formatCPE(cpeString string) string {
cpe, err := pkg.NewCPE(cpeString)
c, err := cpe.New(cpeString)
if err != nil {
log.Debugf("skipping invalid CPE: %s", cpeString)
return ""
}
return pkg.CPEString(cpe)
return cpe.String(c)
}
// NewBomDescriptor returns a new BomDescriptor tailored for the current time and "syft" tool details.
@@ -1,6 +1,7 @@
package spdxhelpers
import (
"github.com/anchore/syft/syft/cpe"
"github.com/anchore/syft/syft/pkg"
)
@@ -10,7 +11,7 @@ func ExternalRefs(p pkg.Package) (externalRefs []ExternalRef) {
for _, c := range p.CPEs {
externalRefs = append(externalRefs, ExternalRef{
ReferenceCategory: SecurityReferenceCategory,
ReferenceLocator: pkg.CPEString(c),
ReferenceLocator: cpe.String(c),
ReferenceType: Cpe23ExternalRefType,
})
}
@@ -5,11 +5,12 @@ import (
"github.com/stretchr/testify/assert"
"github.com/anchore/syft/syft/cpe"
"github.com/anchore/syft/syft/pkg"
)
func Test_ExternalRefs(t *testing.T) {
testCPE := pkg.MustCPE("cpe:2.3:a:name:name:3.2:*:*:*:*:*:*:*")
testCPE := cpe.Must("cpe:2.3:a:name:name:3.2:*:*:*:*:*:*:*")
tests := []struct {
name string
input pkg.Package
@@ -18,7 +19,7 @@ func Test_ExternalRefs(t *testing.T) {
{
name: "cpe + purl",
input: pkg.Package{
CPEs: []pkg.CPE{
CPEs: []cpe.CPE{
testCPE,
},
PURL: "a-purl",
@@ -26,7 +27,7 @@ func Test_ExternalRefs(t *testing.T) {
expected: []ExternalRef{
{
ReferenceCategory: SecurityReferenceCategory,
ReferenceLocator: pkg.CPEString(testCPE),
ReferenceLocator: cpe.String(testCPE),
ReferenceType: Cpe23ExternalRefType,
},
{
@@ -11,6 +11,7 @@ import (
"github.com/anchore/packageurl-go"
"github.com/anchore/syft/internal/log"
"github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/cpe"
"github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/formats/common/util"
"github.com/anchore/syft/syft/linux"
@@ -381,15 +382,15 @@ func findPURLValue(p *spdx.Package) string {
return ""
}
func extractCPEs(p *spdx.Package) (cpes []pkg.CPE) {
func extractCPEs(p *spdx.Package) (cpes []cpe.CPE) {
for _, r := range p.PackageExternalReferences {
if r.RefType == string(Cpe23ExternalRefType) {
cpe, err := pkg.NewCPE(r.Locator)
c, err := cpe.New(r.Locator)
if err != nil {
log.Warnf("unable to extract SPDX CPE=%q: %+v", r.Locator, err)
continue
}
cpes = append(cpes, cpe)
cpes = append(cpes, c)
}
}
return cpes
+9 -8
View File
@@ -16,6 +16,7 @@ import (
"github.com/anchore/stereoscope/pkg/image"
"github.com/anchore/stereoscope/pkg/imagetest"
"github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/cpe"
"github.com/anchore/syft/syft/linux"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/sbom"
@@ -179,8 +180,8 @@ func populateImageCatalog(catalog *pkg.Catalog, img *image.Image) {
Version: "1.0.1",
},
PURL: "a-purl-1", // intentionally a bad pURL for test fixtures
CPEs: []pkg.CPE{
pkg.MustCPE("cpe:2.3:*:some:package:1:*:*:*:*:*:*:*"),
CPEs: []cpe.CPE{
cpe.Must("cpe:2.3:*:some:package:1:*:*:*:*:*:*:*"),
},
})
catalog.Add(pkg.Package{
@@ -197,8 +198,8 @@ func populateImageCatalog(catalog *pkg.Catalog, img *image.Image) {
Version: "2.0.1",
},
PURL: "pkg:deb/debian/package-2@2.0.1",
CPEs: []pkg.CPE{
pkg.MustCPE("cpe:2.3:*:some:package:2:*:*:*:*:*:*:*"),
CPEs: []cpe.CPE{
cpe.Must("cpe:2.3:*:some:package:2:*:*:*:*:*:*:*"),
},
})
}
@@ -259,8 +260,8 @@ func newDirectoryCatalog() *pkg.Catalog {
},
},
PURL: "a-purl-2", // intentionally a bad pURL for test fixtures
CPEs: []pkg.CPE{
pkg.MustCPE("cpe:2.3:*:some:package:2:*:*:*:*:*:*:*"),
CPEs: []cpe.CPE{
cpe.Must("cpe:2.3:*:some:package:2:*:*:*:*:*:*:*"),
},
})
catalog.Add(pkg.Package{
@@ -277,8 +278,8 @@ func newDirectoryCatalog() *pkg.Catalog {
Version: "2.0.1",
},
PURL: "pkg:deb/debian/package-2@2.0.1",
CPEs: []pkg.CPE{
pkg.MustCPE("cpe:2.3:*:some:package:2:*:*:*:*:*:*:*"),
CPEs: []cpe.CPE{
cpe.Must("cpe:2.3:*:some:package:2:*:*:*:*:*:*:*"),
},
})
+5 -4
View File
@@ -5,6 +5,7 @@ import (
"testing"
"github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/cpe"
"github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/formats/common/testutils"
"github.com/anchore/syft/syft/linux"
@@ -59,8 +60,8 @@ func TestEncodeFullJSONDocument(t *testing.T) {
Files: []pkg.PythonFileRecord{},
},
PURL: "a-purl-1",
CPEs: []pkg.CPE{
pkg.MustCPE("cpe:2.3:*:some:package:1:*:*:*:*:*:*:*"),
CPEs: []cpe.CPE{
cpe.Must("cpe:2.3:*:some:package:1:*:*:*:*:*:*:*"),
},
}
@@ -83,8 +84,8 @@ func TestEncodeFullJSONDocument(t *testing.T) {
Files: []pkg.DpkgFileRecord{},
},
PURL: "a-purl-2",
CPEs: []pkg.CPE{
pkg.MustCPE("cpe:2.3:*:some:package:2:*:*:*:*:*:*:*"),
CPEs: []cpe.CPE{
cpe.Must("cpe:2.3:*:some:package:2:*:*:*:*:*:*:*"),
},
}
+2 -1
View File
@@ -8,6 +8,7 @@ import (
"github.com/anchore/syft/internal"
"github.com/anchore/syft/internal/log"
"github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/cpe"
"github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/formats/syftjson/model"
"github.com/anchore/syft/syft/linux"
@@ -159,7 +160,7 @@ func toPackageModels(catalog *pkg.Catalog) []model.Package {
func toPackageModel(p pkg.Package) model.Package {
var cpes = make([]string, len(p.CPEs))
for i, c := range p.CPEs {
cpes[i] = pkg.CPEString(c)
cpes[i] = cpe.String(c)
}
var licenses = make([]string, 0)
+3 -2
View File
@@ -7,6 +7,7 @@ import (
"github.com/anchore/syft/internal/log"
"github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/cpe"
"github.com/anchore/syft/syft/formats/syftjson/model"
"github.com/anchore/syft/syft/linux"
"github.com/anchore/syft/syft/pkg"
@@ -174,9 +175,9 @@ func toSyftCatalog(pkgs []model.Package, idAliases map[string]string) *pkg.Catal
}
func toSyftPackage(p model.Package, idAliases map[string]string) pkg.Package {
var cpes []pkg.CPE
var cpes []cpe.CPE
for _, c := range p.CPEs {
value, err := pkg.NewCPE(c)
value, err := cpe.New(c)
if err != nil {
log.Warnf("excluding invalid CPE %q: %v", c, err)
continue
+3 -2
View File
@@ -8,6 +8,7 @@ import (
"github.com/stretchr/testify/require"
"github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/cpe"
"github.com/anchore/syft/syft/source"
)
@@ -324,7 +325,7 @@ func TestCatalog_MergeRecords(t *testing.T) {
name: "multiple Locations with shared path",
pkgs: []Package{
{
CPEs: []CPE{MustCPE("cpe:2.3:a:package:1:1:*:*:*:*:*:*:*")},
CPEs: []cpe.CPE{cpe.Must("cpe:2.3:a:package:1:1:*:*:*:*:*:*:*")},
Locations: source.NewLocationSet(
source.Location{
Coordinates: source.Coordinates{
@@ -337,7 +338,7 @@ func TestCatalog_MergeRecords(t *testing.T) {
Type: RpmPkg,
},
{
CPEs: []CPE{MustCPE("cpe:2.3:b:package:1:1:*:*:*:*:*:*:*")},
CPEs: []cpe.CPE{cpe.Must("cpe:2.3:b:package:1:1:*:*:*:*:*:*:*")},
Locations: source.NewLocationSet(
source.Location{
Coordinates: source.Coordinates{
@@ -23,6 +23,7 @@ func TestClassifierCataloger_DefaultClassifiers_PositiveCases(t *testing.T) {
expected: pkg.Package{
Name: "python",
Version: "3.7.4a-vZ9",
PURL: "pkg:generic/python@3.7.4a-vZ9",
Locations: singleLocation("libpython3.7.so"),
Metadata: pkg.BinaryMetadata{
Classifier: "python-binary-lib",
@@ -35,6 +36,7 @@ func TestClassifierCataloger_DefaultClassifiers_PositiveCases(t *testing.T) {
expected: pkg.Package{
Name: "python",
Version: "3.6.3a-vZ9",
PURL: "pkg:generic/python@3.6.3a-vZ9",
Locations: singleLocation("python3.6"),
Metadata: pkg.BinaryMetadata{
Classifier: "python-binary",
@@ -47,6 +49,7 @@ func TestClassifierCataloger_DefaultClassifiers_PositiveCases(t *testing.T) {
expected: pkg.Package{
Name: "python",
Version: "3.9-aZ5",
PURL: "pkg:generic/python@3.9-aZ5",
Locations: singleLocation("patchlevel.h"),
Metadata: pkg.BinaryMetadata{
Classifier: "cpython-source",
@@ -59,6 +62,7 @@ func TestClassifierCataloger_DefaultClassifiers_PositiveCases(t *testing.T) {
expected: pkg.Package{
Name: "go",
Version: "1.14",
PURL: "pkg:generic/go@1.14",
Locations: singleLocation("go"),
Metadata: pkg.BinaryMetadata{
Classifier: "go-binary",
@@ -71,6 +75,7 @@ func TestClassifierCataloger_DefaultClassifiers_PositiveCases(t *testing.T) {
expected: pkg.Package{
Name: "node",
Version: "19.2.1",
PURL: "pkg:generic/node@19.2.1",
Locations: singleLocation("node"),
Metadata: pkg.BinaryMetadata{
Classifier: "nodejs-binary",
@@ -83,6 +88,7 @@ func TestClassifierCataloger_DefaultClassifiers_PositiveCases(t *testing.T) {
expected: pkg.Package{
Name: "go",
Version: "1.15",
PURL: "pkg:generic/go@1.15",
Locations: singleLocation("VERSION"),
Metadata: pkg.BinaryMetadata{
Classifier: "go-binary-hint",
@@ -109,6 +115,7 @@ func TestClassifierCataloger_DefaultClassifiers_PositiveCases(t *testing.T) {
Name: "java",
Version: "1.8.0_352-b08",
Type: "binary",
PURL: "pkg:generic/java@1.8.0_352-b08",
Locations: singleLocation("java"),
Metadata: pkg.BinaryMetadata{
Classifier: "java-binary-openjdk",
@@ -123,6 +130,7 @@ func TestClassifierCataloger_DefaultClassifiers_PositiveCases(t *testing.T) {
Name: "java",
Version: "11.0.17+8-LTS",
Type: "binary",
PURL: "pkg:generic/java@11.0.17+8-LTS",
Locations: singleLocation("java"),
Metadata: pkg.BinaryMetadata{
Classifier: "java-binary-openjdk",
@@ -137,6 +145,7 @@ func TestClassifierCataloger_DefaultClassifiers_PositiveCases(t *testing.T) {
Name: "java",
Version: "19.0.1+10-21",
Type: "binary",
PURL: "pkg:generic/java@19.0.1+10-21",
Locations: singleLocation("java"),
Metadata: pkg.BinaryMetadata{
Classifier: "java-binary-oracle",
@@ -151,6 +160,7 @@ func TestClassifierCataloger_DefaultClassifiers_PositiveCases(t *testing.T) {
Name: "java",
Version: "19.0.1+10-21",
Type: "binary",
PURL: "pkg:generic/java@19.0.1+10-21",
Locations: singleLocation("java"),
Metadata: pkg.BinaryMetadata{
Classifier: "java-binary-oracle",
@@ -165,6 +175,7 @@ func TestClassifierCataloger_DefaultClassifiers_PositiveCases(t *testing.T) {
Name: "java",
Version: "1.8.0-foreman_2022_09_22_15_30-b00",
Type: "binary",
PURL: "pkg:generic/java@1.8.0-foreman_2022_09_22_15_30-b00",
Locations: singleLocation("java"),
Metadata: pkg.BinaryMetadata{
Classifier: "java-binary-ibm",
@@ -277,6 +288,7 @@ func assertPackagesAreEqual(t *testing.T, expected pkg.Package, p pkg.Package) {
meta2 := p.Metadata.(pkg.BinaryMetadata)
if expected.Name != p.Name ||
expected.Version != p.Version ||
expected.PURL != p.PURL ||
meta1.Classifier != meta2.Classifier {
assert.Failf(t, "packages not equal", "%v != %v", expected, p)
}
+10 -9
View File
@@ -10,6 +10,7 @@ import (
"github.com/anchore/packageurl-go"
"github.com/anchore/syft/internal"
"github.com/anchore/syft/syft/cpe"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/pkg/cataloger/internal/unionreader"
"github.com/anchore/syft/syft/source"
@@ -44,7 +45,7 @@ type classifier struct {
PURL packageurl.PackageURL
// CPEs are the specific CPEs we want to include for this binary with updated version information
CPEs []pkg.CPE
CPEs []cpe.CPE
}
// evidenceMatcher is a function called to catalog Packages that match some sort of evidence
@@ -114,11 +115,11 @@ func singlePackage(classifier classifier, reader source.LocationReadCloser, matc
update := matchMetadata["update"]
var cpes []pkg.CPE
for _, cpe := range classifier.CPEs {
cpe.Version = version
cpe.Update = update
cpes = append(cpes, cpe)
var cpes []cpe.CPE
for _, c := range classifier.CPEs {
c.Version = version
c.Update = update
cpes = append(cpes, c)
}
p := pkg.Package{
@@ -172,8 +173,8 @@ func getContents(reader source.LocationReadCloser) ([]byte, error) {
}
// singleCPE returns a []pkg.CPE based on the cpe string or panics if the CPE is invalid
func singleCPE(cpe string) []pkg.CPE {
return []pkg.CPE{
pkg.MustCPE(cpe),
func singleCPE(cpeString string) []cpe.CPE {
return []cpe.CPE{
cpe.Must(cpeString),
}
}
+8 -8
View File
@@ -5,7 +5,7 @@ import (
"github.com/stretchr/testify/require"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/cpe"
"github.com/anchore/syft/syft/source"
)
@@ -23,7 +23,7 @@ func Test_ClassifierCPEs(t *testing.T) {
Package: "some-app",
FileGlob: ".*/version.txt",
EvidenceMatcher: fileContentsVersionMatcher(`(?m)my-verison:(?P<version>[0-9.]+)`),
CPEs: []pkg.CPE{},
CPEs: []cpe.CPE{},
},
cpes: nil,
},
@@ -34,8 +34,8 @@ func Test_ClassifierCPEs(t *testing.T) {
Package: "some-app",
FileGlob: ".*/version.txt",
EvidenceMatcher: fileContentsVersionMatcher(`(?m)my-verison:(?P<version>[0-9.]+)`),
CPEs: []pkg.CPE{
pkg.MustCPE("cpe:2.3:a:some:app:*:*:*:*:*:*:*:*"),
CPEs: []cpe.CPE{
cpe.Must("cpe:2.3:a:some:app:*:*:*:*:*:*:*:*"),
},
},
cpes: []string{
@@ -49,9 +49,9 @@ func Test_ClassifierCPEs(t *testing.T) {
Package: "some-app",
FileGlob: ".*/version.txt",
EvidenceMatcher: fileContentsVersionMatcher(`(?m)my-verison:(?P<version>[0-9.]+)`),
CPEs: []pkg.CPE{
pkg.MustCPE("cpe:2.3:a:some:app:*:*:*:*:*:*:*:*"),
pkg.MustCPE("cpe:2.3:a:some:apps:*:*:*:*:*:*:*:*"),
CPEs: []cpe.CPE{
cpe.Must("cpe:2.3:a:some:app:*:*:*:*:*:*:*:*"),
cpe.Must("cpe:2.3:a:some:apps:*:*:*:*:*:*:*:*"),
},
},
cpes: []string{
@@ -79,7 +79,7 @@ func Test_ClassifierCPEs(t *testing.T) {
var cpes []string
for _, c := range p.CPEs {
cpes = append(cpes, pkg.CPEString(c))
cpes = append(cpes, cpe.String(c))
}
require.Equal(t, test.cpes, cpes)
})
@@ -10,6 +10,7 @@ var defaultClassifiers = []classifier{
`(.*/|^)python(?P<version>[0-9]+\.[0-9]+)$`,
`(?m)(?P<version>{{ .version }}\.[0-9]+[-_a-zA-Z0-9]*)`),
Package: "python",
PURL: mustPURL("pkg:generic/python@version"),
},
{
Class: "python-binary-lib",
@@ -18,6 +19,7 @@ var defaultClassifiers = []classifier{
`(.*/|^)libpython(?P<version>[0-9]+\.[0-9]+).so.*$`,
`(?m)(?P<version>{{ .version }}\.[0-9]+[-_a-zA-Z0-9]*)`),
Package: "python",
PURL: mustPURL("pkg:generic/python@version"),
},
{
Class: "cpython-source",
@@ -25,6 +27,7 @@ var defaultClassifiers = []classifier{
EvidenceMatcher: fileContentsVersionMatcher(
`(?m)#define\s+PY_VERSION\s+"?(?P<version>[0-9\.\-_a-zA-Z]+)"?`),
Package: "python",
PURL: mustPURL("pkg:generic/python@version"),
},
{
Class: "go-binary",
@@ -32,6 +35,7 @@ var defaultClassifiers = []classifier{
EvidenceMatcher: fileContentsVersionMatcher(
`(?m)go(?P<version>[0-9]+\.[0-9]+(\.[0-9]+|beta[0-9]+|alpha[0-9]+|rc[0-9]+)?)\x00`),
Package: "go",
PURL: mustPURL("pkg:generic/go@version"),
CPEs: singleCPE("cpe:2.3:a:golang:go:*:*:*:*:*:*:*:*"),
},
{
@@ -42,6 +46,7 @@ var defaultClassifiers = []classifier{
// [NUL]openjdk[NUL]java[NUL]1.8[NUL]1.8.0_352-b08[NUL]
`(?m)\x00openjdk\x00java\x00(?P<release>[0-9]+[.0-9]*)\x00(?P<version>[0-9]+[^\x00]+)\x00`),
Package: "java",
PURL: mustPURL("pkg:generic/java@version"),
// TODO the updates might need to be part of the CPE, like: 1.8.0:update152
CPEs: singleCPE("cpe:2.3:a:oracle:openjdk:*:*:*:*:*:*:*:*"),
},
@@ -52,6 +57,7 @@ var defaultClassifiers = []classifier{
// [NUL]java[NUL]1.8[NUL][NUL][NUL][NUL]1.8.0-foreman_2022_09_22_15_30-b00[NUL]
`(?m)\x00java\x00(?P<release>[0-9]+[.0-9]+)\x00{4}(?P<version>[0-9]+[-._a-zA-Z0-9]+)\x00`),
Package: "java",
PURL: mustPURL("pkg:generic/java@version"),
CPEs: singleCPE("cpe:2.3:a:ibm:java:*:*:*:*:*:*:*:*"),
},
{
@@ -61,6 +67,7 @@ var defaultClassifiers = []classifier{
// [NUL]19.0.1+10-21[NUL]
`(?m)\x00(?P<version>[0-9]+[.0-9]+[+][-0-9]+)\x00`),
Package: "java",
PURL: mustPURL("pkg:generic/java@version"),
CPEs: singleCPE("cpe:2.3:a:oracle:jre:*:*:*:*:*:*:*:*"),
},
{
@@ -79,6 +86,7 @@ var defaultClassifiers = []classifier{
EvidenceMatcher: fileContentsVersionMatcher(
`(?m)go(?P<version>[0-9]+\.[0-9]+(\.[0-9]+|beta[0-9]+|alpha[0-9]+|rc[0-9]+)?)`),
Package: "go",
PURL: mustPURL("pkg:generic/go@version"),
},
{
Class: "busybox-binary",
+12 -11
View File
@@ -5,13 +5,14 @@ import (
"github.com/facebookincubator/nvdtools/wfn"
"github.com/anchore/syft/syft/cpe"
"github.com/anchore/syft/syft/pkg"
)
const jenkinsName = "jenkins"
// filterFn instances should return true if the given CPE should be removed from a collection for the given package
type filterFn func(cpe pkg.CPE, p pkg.Package) bool
type filterFn func(cpe cpe.CPE, p pkg.Package) bool
var cpeFilters = []filterFn{
disallowJiraClientServerMismatch,
@@ -20,23 +21,23 @@ var cpeFilters = []filterFn{
disallowNonParseableCPEs,
}
func filter(cpes []pkg.CPE, p pkg.Package, filters ...filterFn) (result []pkg.CPE) {
func filter(cpes []cpe.CPE, p pkg.Package, filters ...filterFn) (result []cpe.CPE) {
cpeLoop:
for _, cpe := range cpes {
for _, c := range cpes {
for _, fn := range filters {
if fn(cpe, p) {
if fn(c, p) {
continue cpeLoop
}
}
// all filter functions passed on filtering this CPE
result = append(result, cpe)
result = append(result, c)
}
return result
}
func disallowNonParseableCPEs(cpe pkg.CPE, _ pkg.Package) bool {
v := pkg.CPEString(cpe)
_, err := pkg.NewCPE(v)
func disallowNonParseableCPEs(c cpe.CPE, _ pkg.Package) bool {
v := cpe.String(c)
_, err := cpe.New(v)
cannotParse := err != nil
@@ -44,7 +45,7 @@ func disallowNonParseableCPEs(cpe pkg.CPE, _ pkg.Package) bool {
}
// jenkins plugins should not match against jenkins
func disallowJenkinsServerCPEForPluginPackage(cpe pkg.CPE, p pkg.Package) bool {
func disallowJenkinsServerCPEForPluginPackage(cpe cpe.CPE, p pkg.Package) bool {
if p.Type == pkg.JenkinsPluginPkg && cpe.Product == jenkinsName {
return true
}
@@ -52,7 +53,7 @@ func disallowJenkinsServerCPEForPluginPackage(cpe pkg.CPE, p pkg.Package) bool {
}
// filter to account that packages that are not for jenkins but have a CPE generated that will match against jenkins
func disallowJenkinsCPEsNotAssociatedWithJenkins(cpe pkg.CPE, p pkg.Package) bool {
func disallowJenkinsCPEsNotAssociatedWithJenkins(cpe cpe.CPE, p pkg.Package) bool {
// jenkins server should only match against a product with the name jenkins
if cpe.Product == jenkinsName && !strings.Contains(strings.ToLower(p.Name), jenkinsName) {
if cpe.Vendor == wfn.Any || cpe.Vendor == jenkinsName || cpe.Vendor == "cloudbees" {
@@ -63,7 +64,7 @@ func disallowJenkinsCPEsNotAssociatedWithJenkins(cpe pkg.CPE, p pkg.Package) boo
}
// filter to account for packages which are jira client packages but have a CPE that will match against jira
func disallowJiraClientServerMismatch(cpe pkg.CPE, p pkg.Package) bool {
func disallowJiraClientServerMismatch(cpe cpe.CPE, p pkg.Package) bool {
// jira / atlassian should not apply to clients
if cpe.Product == "jira" && strings.Contains(strings.ToLower(p.Name), "client") {
if cpe.Vendor == wfn.Any || cpe.Vendor == "jira" || cpe.Vendor == "atlassian" {
+17 -16
View File
@@ -5,19 +5,20 @@ import (
"github.com/stretchr/testify/assert"
"github.com/anchore/syft/syft/cpe"
"github.com/anchore/syft/syft/pkg"
)
func Test_disallowJenkinsServerCPEForPluginPackage(t *testing.T) {
tests := []struct {
name string
cpe pkg.CPE
cpe cpe.CPE
pkg pkg.Package
expected bool
}{
{
name: "go case (filter out)",
cpe: pkg.MustCPE("cpe:2.3:a:name:jenkins:3.2:*:*:*:*:*:*:*"),
cpe: cpe.Must("cpe:2.3:a:name:jenkins:3.2:*:*:*:*:*:*:*"),
pkg: pkg.Package{
Type: pkg.JenkinsPluginPkg,
},
@@ -25,7 +26,7 @@ func Test_disallowJenkinsServerCPEForPluginPackage(t *testing.T) {
},
{
name: "ignore jenkins plugins with unique name",
cpe: pkg.MustCPE("cpe:2.3:a:name:ci-jenkins:3.2:*:*:*:*:*:*:*"),
cpe: cpe.Must("cpe:2.3:a:name:ci-jenkins:3.2:*:*:*:*:*:*:*"),
pkg: pkg.Package{
Type: pkg.JenkinsPluginPkg,
},
@@ -33,7 +34,7 @@ func Test_disallowJenkinsServerCPEForPluginPackage(t *testing.T) {
},
{
name: "ignore java packages",
cpe: pkg.MustCPE("cpe:2.3:a:name:jenkins:3.2:*:*:*:*:*:*:*"),
cpe: cpe.Must("cpe:2.3:a:name:jenkins:3.2:*:*:*:*:*:*:*"),
pkg: pkg.Package{
Type: pkg.JavaPkg,
},
@@ -50,13 +51,13 @@ func Test_disallowJenkinsServerCPEForPluginPackage(t *testing.T) {
func Test_disallowJenkinsCPEsNotAssociatedWithJenkins(t *testing.T) {
tests := []struct {
name string
cpe pkg.CPE
cpe cpe.CPE
pkg pkg.Package
expected bool
}{
{
name: "filter out mismatched name (cloudbees vendor)",
cpe: pkg.MustCPE("cpe:2.3:a:cloudbees:jenkins:3.2:*:*:*:*:*:*:*"),
cpe: cpe.Must("cpe:2.3:a:cloudbees:jenkins:3.2:*:*:*:*:*:*:*"),
pkg: pkg.Package{
Name: "not-j*nkins",
Type: pkg.JavaPkg,
@@ -65,7 +66,7 @@ func Test_disallowJenkinsCPEsNotAssociatedWithJenkins(t *testing.T) {
},
{
name: "filter out mismatched name (jenkins vendor)",
cpe: pkg.MustCPE("cpe:2.3:a:jenkins:jenkins:3.2:*:*:*:*:*:*:*"),
cpe: cpe.Must("cpe:2.3:a:jenkins:jenkins:3.2:*:*:*:*:*:*:*"),
pkg: pkg.Package{
Name: "not-j*nkins",
Type: pkg.JavaPkg,
@@ -74,7 +75,7 @@ func Test_disallowJenkinsCPEsNotAssociatedWithJenkins(t *testing.T) {
},
{
name: "filter out mismatched name (any vendor)",
cpe: pkg.MustCPE("cpe:2.3:a:*:jenkins:3.2:*:*:*:*:*:*:*"),
cpe: cpe.Must("cpe:2.3:a:*:jenkins:3.2:*:*:*:*:*:*:*"),
pkg: pkg.Package{
Name: "not-j*nkins",
Type: pkg.JavaPkg,
@@ -83,7 +84,7 @@ func Test_disallowJenkinsCPEsNotAssociatedWithJenkins(t *testing.T) {
},
{
name: "ignore packages with the name jenkins",
cpe: pkg.MustCPE("cpe:2.3:a:*:jenkins:3.2:*:*:*:*:*:*:*"),
cpe: cpe.Must("cpe:2.3:a:*:jenkins:3.2:*:*:*:*:*:*:*"),
pkg: pkg.Package{
Name: "jenkins-thing",
Type: pkg.JavaPkg,
@@ -92,7 +93,7 @@ func Test_disallowJenkinsCPEsNotAssociatedWithJenkins(t *testing.T) {
},
{
name: "ignore product names that are not exactly 'jenkins'",
cpe: pkg.MustCPE("cpe:2.3:a:*:jenkins-something-else:3.2:*:*:*:*:*:*:*"),
cpe: cpe.Must("cpe:2.3:a:*:jenkins-something-else:3.2:*:*:*:*:*:*:*"),
pkg: pkg.Package{
Name: "not-j*nkins",
Type: pkg.JavaPkg,
@@ -110,13 +111,13 @@ func Test_disallowJenkinsCPEsNotAssociatedWithJenkins(t *testing.T) {
func Test_disallowJiraClientServerMismatch(t *testing.T) {
tests := []struct {
name string
cpe pkg.CPE
cpe cpe.CPE
pkg pkg.Package
expected bool
}{
{
name: "filter out mismatched name (atlassian vendor)",
cpe: pkg.MustCPE("cpe:2.3:a:atlassian:jira:3.2:*:*:*:*:*:*:*"),
cpe: cpe.Must("cpe:2.3:a:atlassian:jira:3.2:*:*:*:*:*:*:*"),
pkg: pkg.Package{
Name: "something-client",
Type: pkg.JavaPkg,
@@ -125,7 +126,7 @@ func Test_disallowJiraClientServerMismatch(t *testing.T) {
},
{
name: "filter out mismatched name (jira vendor)",
cpe: pkg.MustCPE("cpe:2.3:a:jira:jira:3.2:*:*:*:*:*:*:*"),
cpe: cpe.Must("cpe:2.3:a:jira:jira:3.2:*:*:*:*:*:*:*"),
pkg: pkg.Package{
Name: "something-client",
Type: pkg.JavaPkg,
@@ -134,7 +135,7 @@ func Test_disallowJiraClientServerMismatch(t *testing.T) {
},
{
name: "filter out mismatched name (any vendor)",
cpe: pkg.MustCPE("cpe:2.3:a:*:jira:3.2:*:*:*:*:*:*:*"),
cpe: cpe.Must("cpe:2.3:a:*:jira:3.2:*:*:*:*:*:*:*"),
pkg: pkg.Package{
Name: "something-client",
Type: pkg.JavaPkg,
@@ -143,7 +144,7 @@ func Test_disallowJiraClientServerMismatch(t *testing.T) {
},
{
name: "ignore package names that do not have 'client'",
cpe: pkg.MustCPE("cpe:2.3:a:*:jira:3.2:*:*:*:*:*:*:*"),
cpe: cpe.Must("cpe:2.3:a:*:jira:3.2:*:*:*:*:*:*:*"),
pkg: pkg.Package{
Name: "jira-thing",
Type: pkg.JavaPkg,
@@ -152,7 +153,7 @@ func Test_disallowJiraClientServerMismatch(t *testing.T) {
},
{
name: "ignore product names that are not exactly 'jira'",
cpe: pkg.MustCPE("cpe:2.3:a:*:jira-something-else:3.2:*:*:*:*:*:*:*"),
cpe: cpe.Must("cpe:2.3:a:*:jira-something-else:3.2:*:*:*:*:*:*:*"),
pkg: pkg.Package{
Name: "not-j*ra",
Type: pkg.JavaPkg,
+14 -13
View File
@@ -10,26 +10,27 @@ import (
"github.com/facebookincubator/nvdtools/wfn"
"github.com/anchore/syft/internal"
"github.com/anchore/syft/syft/cpe"
"github.com/anchore/syft/syft/pkg"
)
func newCPE(product, vendor, version, targetSW string) *wfn.Attributes {
cpe := *(wfn.NewAttributesWithAny())
cpe.Part = "a"
cpe.Product = product
cpe.Vendor = vendor
cpe.Version = version
cpe.TargetSW = targetSW
if pkg.ValidateCPEString(pkg.CPEString(cpe)) != nil {
c := *(wfn.NewAttributesWithAny())
c.Part = "a"
c.Product = product
c.Vendor = vendor
c.Version = version
c.TargetSW = targetSW
if cpe.ValidateString(cpe.String(c)) != nil {
return nil
}
return &cpe
return &c
}
// Generate Create a list of CPEs for a given package, trying to guess the vendor, product tuple. We should be trying to
// generate the minimal set of representative CPEs, which implies that optional fields should not be included
// (such as target SW).
func Generate(p pkg.Package) []pkg.CPE {
func Generate(p pkg.Package) []cpe.CPE {
vendors := candidateVendors(p)
products := candidateProducts(p)
if len(products) == 0 {
@@ -37,7 +38,7 @@ func Generate(p pkg.Package) []pkg.CPE {
}
keys := internal.NewStringSet()
cpes := make([]pkg.CPE, 0)
cpes := make([]cpe.CPE, 0)
for _, product := range products {
for _, vendor := range vendors {
// prevent duplicate entries...
@@ -47,8 +48,8 @@ func Generate(p pkg.Package) []pkg.CPE {
}
keys.Add(key)
// add a new entry...
if cpe := newCPE(product, vendor, p.Version, wfn.Any); cpe != nil {
cpes = append(cpes, *cpe)
if c := newCPE(product, vendor, p.Version, wfn.Any); c != nil {
cpes = append(cpes, *c)
}
}
}
@@ -56,7 +57,7 @@ func Generate(p pkg.Package) []pkg.CPE {
// filter out any known combinations that don't accurately represent this package
cpes = filter(cpes, p, cpeFilters...)
sort.Sort(pkg.CPEBySpecificity(cpes))
sort.Sort(cpe.BySpecificity(cpes))
return cpes
}
@@ -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"
)
@@ -696,7 +697,7 @@ func TestGeneratePackageCPEs(t *testing.T) {
expectedCpeSet := set.NewStringSet(test.expected...)
actualCpeSet := set.NewStringSet()
for _, a := range actual {
actualCpeSet.Add(pkg.CPEString(a))
actualCpeSet.Add(cpe.String(a))
}
extra := strset.Difference(actualCpeSet, expectedCpeSet).List()
+3 -3
View File
@@ -85,12 +85,12 @@ func makeGoMainPackage(mod *debug.BuildInfo, arch string, location source.Locati
// 2) reading file headers from binaries compiled by < go1.18
func getArchs(readers []io.ReaderAt, builds []*debug.BuildInfo) []string {
if len(readers) != len(builds) {
log.Warnf("golang cataloger: bin parsing: number of builds and readers doesn't match")
log.Trace("golang cataloger: bin parsing: number of builds and readers doesn't match")
return nil
}
if len(readers) == 0 || len(builds) == 0 {
log.Warnf("golang cataloger: bin parsing: %d readers and %d build info items", len(readers), len(builds))
log.Tracef("golang cataloger: bin parsing: %d readers and %d build info items", len(readers), len(builds))
return nil
}
@@ -107,7 +107,7 @@ func getArchs(readers []io.ReaderAt, builds []*debug.BuildInfo) []string {
for i, r := range readers {
a, err := getGOARCHFromBin(r)
if err != nil {
log.Warnf("golang cataloger: bin parsing: getting arch from binary: %v", err)
log.Tracef("golang cataloger: bin parsing: getting arch from binary: %v", err)
continue
}
+5 -4
View File
@@ -6,6 +6,7 @@ import (
"github.com/stretchr/testify/require"
"github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/cpe"
"github.com/anchore/syft/syft/formats/syftjson"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest"
@@ -13,17 +14,17 @@ import (
"github.com/anchore/syft/syft/source"
)
func mustCPEs(s ...string) (c []pkg.CPE) {
func mustCPEs(s ...string) (c []cpe.CPE) {
for _, i := range s {
c = append(c, mustCPE(i))
}
return
}
func mustCPE(c string) pkg.CPE {
return must(pkg.NewCPE(c))
func mustCPE(c string) cpe.CPE {
return must(cpe.New(c))
}
func must(c pkg.CPE, e error) pkg.CPE {
func must(c cpe.CPE, e error) cpe.CPE {
if e != nil {
panic(e)
}
-103
View File
@@ -1,103 +0,0 @@
package pkg
import (
"sort"
"testing"
"github.com/stretchr/testify/assert"
)
func mustCPE(c string) CPE {
return must(NewCPE(c))
}
func TestCPESpecificity(t *testing.T) {
tests := []struct {
name string
input []CPE
expected []CPE
}{
{
name: "sort strictly by wfn *",
input: []CPE{
mustCPE("cpe:2.3:a:*:package:1:*:*:*:*:*:*:*"),
mustCPE("cpe:2.3:a:some:package:1:*:*:*:*:*:*:*"),
mustCPE("cpe:2.3:a:*:package:1:*:*:*:*:some:*:*"),
mustCPE("cpe:2.3:a:some:package:1:*:*:*:*:some:*:*"),
mustCPE("cpe:2.3:a:some:package:*:*:*:*:*:*:*:*"),
},
expected: []CPE{
mustCPE("cpe:2.3:a:some:package:1:*:*:*:*:some:*:*"),
mustCPE("cpe:2.3:a:some:package:1:*:*:*:*:*:*:*"),
mustCPE("cpe:2.3:a:some:package:*:*:*:*:*:*:*:*"),
mustCPE("cpe:2.3:a:*:package:1:*:*:*:*:some:*:*"),
mustCPE("cpe:2.3:a:*:package:1:*:*:*:*:*:*:*"),
},
},
{
name: "sort strictly by field length",
input: []CPE{
mustCPE("cpe:2.3:a:1:22:1:*:*:*:*:1:*:*"),
mustCPE("cpe:2.3:a:55555:1:1:*:*:*:*:1:*:*"),
mustCPE("cpe:2.3:a:1:1:333:*:*:*:*:1:*:*"),
mustCPE("cpe:2.3:a:1:666666:1:*:*:*:*:1:*:*"),
mustCPE("cpe:2.3:a:1:1:1:*:*:*:*:1:*:*"),
mustCPE("cpe:2.3:a:1:1:1:*:*:*:*:4444:*:*"),
},
expected: []CPE{
mustCPE("cpe:2.3:a:1:666666:1:*:*:*:*:1:*:*"),
mustCPE("cpe:2.3:a:55555:1:1:*:*:*:*:1:*:*"),
mustCPE("cpe:2.3:a:1:1:1:*:*:*:*:4444:*:*"),
mustCPE("cpe:2.3:a:1:1:333:*:*:*:*:1:*:*"),
mustCPE("cpe:2.3:a:1:22:1:*:*:*:*:1:*:*"),
mustCPE("cpe:2.3:a:1:1:1:*:*:*:*:1:*:*"),
},
},
{
name: "sort by mix of field length and specificity",
input: []CPE{
mustCPE("cpe:2.3:a:1:666666:*:*:*:*:*:1:*:*"),
mustCPE("cpe:2.3:a:*:1:1:*:*:*:*:4444:*:*"),
mustCPE("cpe:2.3:a:1:*:333:*:*:*:*:*:*:*"),
mustCPE("cpe:2.3:a:1:1:1:*:*:*:*:1:*:*"),
mustCPE("cpe:2.3:a:1:22:1:*:*:*:*:1:*:*"),
mustCPE("cpe:2.3:a:55555:1:1:*:*:*:*:1:*:*"),
},
expected: []CPE{
mustCPE("cpe:2.3:a:55555:1:1:*:*:*:*:1:*:*"),
mustCPE("cpe:2.3:a:1:22:1:*:*:*:*:1:*:*"),
mustCPE("cpe:2.3:a:1:1:1:*:*:*:*:1:*:*"),
mustCPE("cpe:2.3:a:1:666666:*:*:*:*:*:1:*:*"),
mustCPE("cpe:2.3:a:*:1:1:*:*:*:*:4444:*:*"),
mustCPE("cpe:2.3:a:1:*:333:*:*:*:*:*:*:*"),
},
},
{
name: "sort by mix of field length, specificity, dash",
input: []CPE{
mustCPE("cpe:2.3:a:alpine:alpine_keys:2.3-r1:*:*:*:*:*:*:*"),
mustCPE("cpe:2.3:a:alpine_keys:alpine_keys:2.3-r1:*:*:*:*:*:*:*"),
mustCPE("cpe:2.3:a:alpine-keys:alpine_keys:2.3-r1:*:*:*:*:*:*:*"),
mustCPE("cpe:2.3:a:alpine:alpine-keys:2.3-r1:*:*:*:*:*:*:*"),
mustCPE("cpe:2.3:a:alpine-keys:alpine-keys:2.3-r1:*:*:*:*:*:*:*"),
mustCPE("cpe:2.3:a:alpine_keys:alpine-keys:2.3-r1:*:*:*:*:*:*:*"),
},
expected: []CPE{
mustCPE("cpe:2.3:a:alpine-keys:alpine-keys:2.3-r1:*:*:*:*:*:*:*"),
mustCPE("cpe:2.3:a:alpine-keys:alpine_keys:2.3-r1:*:*:*:*:*:*:*"),
mustCPE("cpe:2.3:a:alpine_keys:alpine-keys:2.3-r1:*:*:*:*:*:*:*"),
mustCPE("cpe:2.3:a:alpine_keys:alpine_keys:2.3-r1:*:*:*:*:*:*:*"),
mustCPE("cpe:2.3:a:alpine:alpine-keys:2.3-r1:*:*:*:*:*:*:*"),
mustCPE("cpe:2.3:a:alpine:alpine_keys:2.3-r1:*:*:*:*:*:*:*"),
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
sort.Sort(CPEBySpecificity(test.input))
assert.Equal(t, test.expected, test.input)
})
}
}
+3 -2
View File
@@ -10,6 +10,7 @@ import (
"github.com/anchore/syft/internal/log"
"github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/cpe"
"github.com/anchore/syft/syft/source"
)
@@ -24,7 +25,7 @@ type Package struct {
Licenses []string // licenses discovered with the package metadata
Language Language `hash:"ignore" cyclonedx:"language"` // the language ecosystem this package belongs to (e.g. JavaScript, Python, etc)
Type Type `cyclonedx:"type"` // the package type (e.g. Npm, Yarn, Python, Rpm, Deb, etc)
CPEs []CPE `hash:"ignore"` // all possible Common Platform Enumerators (note: this is NOT included in the definition of the ID since all fields on a CPE are derived from other fields)
CPEs []cpe.CPE `hash:"ignore"` // all possible Common Platform Enumerators (note: this is NOT included in the definition of the ID since all fields on a CPE are derived from other fields)
PURL string `hash:"ignore"` // the Package URL (see https://github.com/package-url/purl-spec)
MetadataType MetadataType `cyclonedx:"metadataType"` // the shape of the additional data in the "metadata" field
Metadata interface{} // additional data found while parsing the package source
@@ -64,7 +65,7 @@ func (p *Package) merge(other Package) error {
p.Locations.Add(other.Locations.ToSlice()...)
p.CPEs = mergeCPEs(p.CPEs, other.CPEs)
p.CPEs = cpe.Merge(p.CPEs, other.CPEs)
if p.PURL == "" {
p.PURL = other.PURL
+15 -14
View File
@@ -8,6 +8,7 @@ import (
"github.com/stretchr/testify/require"
"github.com/anchore/stereoscope/pkg/file"
"github.com/anchore/syft/syft/cpe"
"github.com/anchore/syft/syft/source"
)
@@ -32,8 +33,8 @@ func TestIDUniqueness(t *testing.T) {
},
Language: "math",
Type: PythonPkg,
CPEs: []CPE{
must(NewCPE(`cpe:2.3:a:Archimedes:pi:3.14:*:*:*:*:math:*:*`)),
CPEs: []cpe.CPE{
cpe.Must(`cpe:2.3:a:Archimedes:pi:3.14:*:*:*:*:math:*:*`),
},
PURL: "pkg:pypi/pi@3.14",
MetadataType: PythonPackageMetadataType,
@@ -169,7 +170,7 @@ func TestIDUniqueness(t *testing.T) {
{
name: "CPEs is ignored",
transform: func(pkg Package) Package {
pkg.CPEs = []CPE{}
pkg.CPEs = []cpe.CPE{}
return pkg
},
expectedIDComparison: assert.Equal,
@@ -269,8 +270,8 @@ func TestPackage_Merge(t *testing.T) {
},
Language: "math",
Type: PythonPkg,
CPEs: []CPE{
must(NewCPE(`cpe:2.3:a:Archimedes:pi:3.14:*:*:*:*:math:*:*`)),
CPEs: []cpe.CPE{
cpe.Must(`cpe:2.3:a:Archimedes:pi:3.14:*:*:*:*:math:*:*`),
},
PURL: "pkg:pypi/pi@3.14",
MetadataType: PythonPackageMetadataType,
@@ -297,8 +298,8 @@ func TestPackage_Merge(t *testing.T) {
},
Language: "math",
Type: PythonPkg,
CPEs: []CPE{
must(NewCPE(`cpe:2.3:a:DIFFERENT:pi:3.14:*:*:*:*:math:*:*`)), // NOTE: difference
CPEs: []cpe.CPE{
cpe.Must(`cpe:2.3:a:DIFFERENT:pi:3.14:*:*:*:*:math:*:*`), // NOTE: difference
},
PURL: "pkg:pypi/pi@3.14",
MetadataType: PythonPackageMetadataType,
@@ -326,9 +327,9 @@ func TestPackage_Merge(t *testing.T) {
},
Language: "math",
Type: PythonPkg,
CPEs: []CPE{
must(NewCPE(`cpe:2.3:a:Archimedes:pi:3.14:*:*:*:*:math:*:*`)),
must(NewCPE(`cpe:2.3:a:DIFFERENT:pi:3.14:*:*:*:*:math:*:*`)), // NOTE: merge!
CPEs: []cpe.CPE{
cpe.Must(`cpe:2.3:a:Archimedes:pi:3.14:*:*:*:*:math:*:*`),
cpe.Must(`cpe:2.3:a:DIFFERENT:pi:3.14:*:*:*:*:math:*:*`), // NOTE: merge!
},
PURL: "pkg:pypi/pi@3.14",
MetadataType: PythonPackageMetadataType,
@@ -358,8 +359,8 @@ func TestPackage_Merge(t *testing.T) {
},
Language: "math",
Type: PythonPkg,
CPEs: []CPE{
must(NewCPE(`cpe:2.3:a:Archimedes:pi:3.14:*:*:*:*:math:*:*`)),
CPEs: []cpe.CPE{
cpe.Must(`cpe:2.3:a:Archimedes:pi:3.14:*:*:*:*:math:*:*`),
},
PURL: "pkg:pypi/pi@3.14",
MetadataType: PythonPackageMetadataType,
@@ -386,8 +387,8 @@ func TestPackage_Merge(t *testing.T) {
},
Language: "math",
Type: PythonPkg,
CPEs: []CPE{
must(NewCPE(`cpe:2.3:a:Archimedes:pi:3.14:*:*:*:*:math:*:*`)),
CPEs: []cpe.CPE{
cpe.Must(`cpe:2.3:a:Archimedes:pi:3.14:*:*:*:*:math:*:*`),
},
PURL: "pkg:pypi/pi@3.14",
MetadataType: PythonPackageMetadataType,
+1 -1
View File
@@ -50,7 +50,7 @@ type directoryResolver struct {
func newDirectoryResolver(root string, pathFilters ...pathFilterFn) (*directoryResolver, error) {
currentWD, err := os.Getwd()
if err != nil {
return nil, fmt.Errorf("could not gret CWD: %w", err)
return nil, fmt.Errorf("could not get CWD: %w", err)
}
// we have to account for the root being accessed through a symlink path and always resolve the real path. Otherwise
// we will not be able to normalize given paths that fall under the resolver