feat: Record where CPEs come from (#2552)

Syft can get CPEs from several source, including generating them based on
package data, finding them in the NVD CPE dictionary, or finding them declared
in a manifest or existing SBOM. Record where Syft got CPEs so that consumers of
SBOMs can reason about how trustworthy they are.

Signed-off-by: Will Murphy <will.murphy@anchore.com>
This commit is contained in:
William Murphy
2024-02-02 16:17:52 +00:00
committed by GitHub
parent 4fe50f4169
commit b7a6d5e946
47 changed files with 2674 additions and 287 deletions
+8 -5
View File
@@ -159,11 +159,14 @@ func Test_encodeDecodeFileMetadata(t *testing.T) {
Type: "type",
CPEs: []cpe.CPE{
{
Part: "a",
Vendor: "vendor",
Product: "product",
Version: "version",
Update: "update",
Attributes: cpe.Attributes{
Part: "a",
Vendor: "vendor",
Product: "product",
Version: "version",
Update: "update",
},
Source: "test-source",
},
},
PURL: "pkg:generic/pkg@version",
+2 -2
View File
@@ -152,7 +152,7 @@ func TestEncodeFullJSONDocument(t *testing.T) {
},
PURL: "a-purl-1",
CPEs: []cpe.CPE{
cpe.Must("cpe:2.3:*:some:package:1:*:*:*:*:*:*:*"),
cpe.Must("cpe:2.3:*:some:package:1:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
},
}
@@ -173,7 +173,7 @@ func TestEncodeFullJSONDocument(t *testing.T) {
},
PURL: "a-purl-2",
CPEs: []cpe.CPE{
cpe.Must("cpe:2.3:*:some:package:2:*:*:*:*:*:*:*"),
cpe.Must("cpe:2.3:*:some:package:2:*:*:*:*:*:*:*", cpe.GeneratedSource),
},
}
+31 -1
View File
@@ -32,10 +32,17 @@ type PackageBasicData struct {
Locations []file.Location `json:"locations"`
Licenses licenses `json:"licenses"`
Language pkg.Language `json:"language"`
CPEs []string `json:"cpes"`
CPEs cpes `json:"cpes"`
PURL string `json:"purl"`
}
type cpes []CPE
type CPE struct {
Value string `json:"cpe"`
Source string `json:"source,omitempty"`
}
type licenses []License
type License struct {
@@ -74,6 +81,29 @@ func (f *licenses) UnmarshalJSON(b []byte) error {
return nil
}
func sourcedCPESfromSimpleCPEs(simpleCPEs []string) []CPE {
var result []CPE
for _, s := range simpleCPEs {
result = append(result, CPE{
Value: s,
})
}
return result
}
func (c *cpes) UnmarshalJSON(b []byte) error {
var cs []CPE
if err := json.Unmarshal(b, &cs); err != nil {
var simpleCPEs []string
if err := json.Unmarshal(b, &simpleCPEs); err != nil {
return fmt.Errorf("unable to unmarshal cpes: %w", err)
}
cs = sourcedCPESfromSimpleCPEs(simpleCPEs)
}
*c = cs
return nil
}
// PackageCustomData contains ambiguous values (type-wise) from pkg.Package.
type PackageCustomData struct {
MetadataType string `json:"metadataType,omitempty"`
@@ -23,7 +23,9 @@
],
"language": "python",
"cpes": [
"cpe:2.3:*:some:package:2:*:*:*:*:*:*:*"
{
"cpe": "cpe:2.3:*:some:package:2:*:*:*:*:*:*:*"
}
],
"purl": "a-purl-2",
"metadataType": "python-package",
@@ -56,7 +58,9 @@
"licenses": [],
"language": "",
"cpes": [
"cpe:2.3:*:some:package:2:*:*:*:*:*:*:*"
{
"cpe": "cpe:2.3:*:some:package:2:*:*:*:*:*:*:*"
}
],
"purl": "pkg:deb/debian/package-2@2.0.1",
"metadataType": "dpkg-db-entry",
@@ -23,7 +23,10 @@
],
"language": "python",
"cpes": [
"cpe:2.3:*:some:package:1:*:*:*:*:*:*:*"
{
"cpe": "cpe:2.3:*:some:package:1:*:*:*:*:*:*:*",
"source": "nvd-cpe-dictionary"
}
],
"purl": "a-purl-1",
"metadataType": "python-package",
@@ -51,7 +54,10 @@
"licenses": [],
"language": "",
"cpes": [
"cpe:2.3:*:some:package:2:*:*:*:*:*:*:*"
{
"cpe": "cpe:2.3:*:some:package:2:*:*:*:*:*:*:*",
"source": "syft-generated"
}
],
"purl": "a-purl-2",
"metadataType": "dpkg-db-entry",
@@ -24,7 +24,10 @@
],
"language": "python",
"cpes": [
"cpe:2.3:*:some:package:1:*:*:*:*:*:*:*"
{
"cpe": "cpe:2.3:*:some:package:1:*:*:*:*:*:*:*",
"source": "syft-generated"
}
],
"purl": "a-purl-1",
"metadataType": "python-package",
@@ -53,7 +56,10 @@
"licenses": [],
"language": "",
"cpes": [
"cpe:2.3:*:some:package:2:*:*:*:*:*:*:*"
{
"cpe": "cpe:2.3:*:some:package:2:*:*:*:*:*:*:*",
"source": "nvd-cpe-dictionary"
}
],
"purl": "pkg:deb/debian/package-2@2.0.1",
"metadataType": "dpkg-db-entry",
+6 -2
View File
@@ -229,9 +229,13 @@ func toLicenseModel(pkgLicenses []pkg.License) (modelLicenses []model.License) {
// toPackageModel crates a new Package from the given pkg.Package.
func toPackageModel(p pkg.Package, cfg EncoderConfig) model.Package {
var cpes = make([]string, len(p.CPEs))
var cpes = make([]model.CPE, len(p.CPEs))
for i, c := range p.CPEs {
cpes[i] = c.String()
convertedCPE := model.CPE{
Value: c.Attributes.String(),
Source: c.Source.String(),
}
cpes[i] = convertedCPE
}
// we want to make sure all catalogers are
+2 -2
View File
@@ -301,9 +301,9 @@ func toSyftCatalog(pkgs []model.Package, idAliases map[string]string) *pkg.Colle
func toSyftPackage(p model.Package, idAliases map[string]string) pkg.Package {
var cpes []cpe.CPE
for _, c := range p.CPEs {
value, err := cpe.New(c)
value, err := cpe.New(c.Value, cpe.Source(c.Source))
if err != nil {
log.Warnf("excluding invalid CPE %q: %v", c, err)
log.Warnf("excluding invalid Attributes %q: %v", c, err)
continue
}