mirror of
https://github.com/anchore/syft.git
synced 2026-10-17 16:47:19 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bc1edb9c8a | ||
|
|
64be0a1072 | ||
|
|
e3d6ffd30e | ||
|
|
8d36b21237 | ||
|
|
6a7d6e6071 |
@@ -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()
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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,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:*:*:*:*:*:*:*"),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,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),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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" {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user