Remove MetadataType from core package object and normalize JSON metadataType values (#1983)

* [wip]

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* distinct the package metadata functions

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* remove metadata type from package core model

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* incorporate review feedback for names

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* add RPM archive metadata and split parser helpers

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* clarify the python package metadata type

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* rename the KB metadata type

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* break hackage and composer types by use case

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* linting fix

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* fix encoding and decoding for syft-json and cyclonedx

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* bump json schema to 11

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* update cyclonedx-json snapshots

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* update cyclonedx-xml snapshots

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* update spdx-json snapshots

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* update spdx-tv snapshots

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* update syft-json snapshots

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* correct metadata type in stack yaml parser test

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* fix bom-ref redactor for cyclonedx-xml

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* add tests for legacy package metadata names

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* regenerate json schema v11

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* fix legacy HackageMetadataType reflect type value check

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* fix linting

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* packagemetadata discovery should account for type shadowing

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* fix linting

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* fix cli tests

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* bump json schema version to v12

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* update json schema to incorporate changes from main

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* add syft-json legacy config option

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* add tests around v11-v12 json decoding

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* add docs for SYFT_JSON_LEGACY

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* rename structs to be compliant with new naming scheme

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

---------

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
This commit is contained in:
Alex Goodman
2023-10-30 12:12:04 -04:00
committed by GitHub
parent f442586ec9
commit 1aaa644007
261 changed files with 7010 additions and 4040 deletions
+3
View File
@@ -7,6 +7,9 @@ import (
"github.com/anchore/syft/syft/pkg/cataloger/generic"
)
// Note about the distinction between composer.lock and installed.json: composer.lock and installed.json have different
// semantic meanings. The lock file represents what should be installed, whereas the installed file represents what is installed.
// NewComposerInstalledCataloger returns a new cataloger for PHP installed.json files.
func NewComposerInstalledCataloger() *generic.Cataloger {
return generic.NewCataloger("php-composer-installed-cataloger").
+33 -18
View File
@@ -8,44 +8,59 @@ import (
"github.com/anchore/syft/syft/pkg"
)
func newComposerLockPackage(m parsedData, indexLocation file.Location) pkg.Package {
func newComposerLockPackage(pd parsedLockData, indexLocation file.Location) pkg.Package {
p := pkg.Package{
Name: m.Name,
Version: m.Version,
Locations: file.NewLocationSet(indexLocation),
Licenses: pkg.NewLicenseSet(pkg.NewLicensesFromLocation(indexLocation, m.License...)...),
PURL: packageURL(m),
Language: pkg.PHP,
Type: pkg.PhpComposerPkg,
MetadataType: pkg.PhpComposerJSONMetadataType,
Metadata: m.PhpComposerJSONMetadata,
Name: pd.Name,
Version: pd.Version,
Locations: file.NewLocationSet(indexLocation.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)),
Licenses: pkg.NewLicenseSet(pkg.NewLicensesFromLocation(indexLocation, pd.License...)...),
PURL: packageURL(pd.Name, pd.Version),
Language: pkg.PHP,
Type: pkg.PhpComposerPkg,
Metadata: pd.PhpComposerLockEntry,
}
p.SetID()
return p
}
func packageURL(m parsedData) string {
var name, vendor string
fields := strings.Split(m.Name, "/")
func newComposerInstalledPackage(pd parsedInstalledData, indexLocation file.Location) pkg.Package {
p := pkg.Package{
Name: pd.Name,
Version: pd.Version,
Locations: file.NewLocationSet(indexLocation.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)),
Licenses: pkg.NewLicenseSet(pkg.NewLicensesFromLocation(indexLocation, pd.License...)...),
PURL: packageURL(pd.Name, pd.Version),
Language: pkg.PHP,
Type: pkg.PhpComposerPkg,
Metadata: pd.PhpComposerInstalledEntry,
}
p.SetID()
return p
}
func packageURL(name, version string) string {
var pkgName, vendor string
fields := strings.Split(name, "/")
switch len(fields) {
case 0:
return ""
case 1:
name = m.Name
pkgName = name
case 2:
vendor = fields[0]
name = fields[1]
pkgName = fields[1]
default:
vendor = fields[0]
name = strings.Join(fields[1:], "-")
pkgName = strings.Join(fields[1:], "-")
}
pURL := packageurl.NewPackageURL(
packageurl.TypeComposer,
vendor,
name,
m.Version,
pkgName,
version,
nil,
"")
return pURL.ToString()
+17 -33
View File
@@ -4,54 +4,38 @@ import (
"testing"
"github.com/sergi/go-diff/diffmatchpatch"
"github.com/anchore/syft/syft/pkg"
)
func Test_packageURL(t *testing.T) {
tests := []struct {
name string
metadata parsedData
expected string
name string
packageName string
packageVersion string
expected string
}{
{
name: "with extractable vendor",
metadata: parsedData{
[]string{},
pkg.PhpComposerJSONMetadata{
Version: "1.0.1",
Name: "ven/name",
},
},
expected: "pkg:composer/ven/name@1.0.1",
name: "with extractable vendor",
packageName: "ven/name",
packageVersion: "1.0.1",
expected: "pkg:composer/ven/name@1.0.1",
},
{
name: "name with slashes (invalid)",
metadata: parsedData{
[]string{},
pkg.PhpComposerJSONMetadata{
Name: "ven/name/component",
Version: "1.0.1",
},
},
expected: "pkg:composer/ven/name-component@1.0.1",
name: "name with slashes (invalid)",
packageName: "ven/name/component",
packageVersion: "1.0.1",
expected: "pkg:composer/ven/name-component@1.0.1",
},
{
name: "unknown vendor",
metadata: parsedData{
[]string{},
pkg.PhpComposerJSONMetadata{
Name: "name",
Version: "1.0.1",
},
},
expected: "pkg:composer/name@1.0.1",
name: "unknown vendor",
packageName: "name",
packageVersion: "1.0.1",
expected: "pkg:composer/name@1.0.1",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
actual := packageURL(test.metadata)
actual := packageURL(test.packageName, test.packageVersion)
if actual != test.expected {
dmp := diffmatchpatch.New()
diffs := dmp.DiffMain(test.expected, actual, true)
+7 -12
View File
@@ -14,14 +14,14 @@ import (
var _ generic.Parser = parseComposerLock
type parsedData struct {
type parsedLockData struct {
License []string `json:"license"`
pkg.PhpComposerJSONMetadata
pkg.PhpComposerLockEntry
}
type composerLock struct {
Packages []parsedData `json:"packages"`
PackageDev []parsedData `json:"packages-dev"`
Packages []parsedLockData `json:"packages"`
PackageDev []parsedLockData `json:"packages-dev"` // TODO: these are not currently included as packages in the SBOM... should they be?
}
// parseComposerLock is a parser function for Composer.lock contents, returning "Default" php packages discovered.
@@ -36,20 +36,15 @@ func parseComposerLock(_ file.Resolver, _ *generic.Environment, reader file.Loca
} else if err != nil {
return nil, nil, fmt.Errorf("failed to parse composer.lock file: %w", err)
}
for _, m := range lock.Packages {
for _, pd := range lock.Packages {
pkgs = append(
pkgs,
newComposerLockPackage(
m,
reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
pd,
reader.Location,
),
)
}
// TODO: did we omit this on purpose?
// for _, m := range lock.PackageDev {
// pkgs = append(pkgs, newComposerLockPackage(m, reader.Location))
//}
}
return pkgs, nil, nil
@@ -22,10 +22,9 @@ func TestParseComposerFileLock(t *testing.T) {
Licenses: pkg.NewLicenseSet(
pkg.NewLicenseFromLocations("MIT", file.NewLocation(fixture)),
),
Language: pkg.PHP,
Type: pkg.PhpComposerPkg,
MetadataType: pkg.PhpComposerJSONMetadataType,
Metadata: pkg.PhpComposerJSONMetadata{
Language: pkg.PHP,
Type: pkg.PhpComposerPkg,
Metadata: pkg.PhpComposerLockEntry{
Name: "adoy/fastcgi-client",
Version: "1.0.2",
Source: pkg.PhpComposerExternalReference{
@@ -63,9 +62,8 @@ func TestParseComposerFileLock(t *testing.T) {
Licenses: pkg.NewLicenseSet(
pkg.NewLicenseFromLocations("MIT", file.NewLocation(fixture)),
),
Type: pkg.PhpComposerPkg,
MetadataType: pkg.PhpComposerJSONMetadataType,
Metadata: pkg.PhpComposerJSONMetadata{
Type: pkg.PhpComposerPkg,
Metadata: pkg.PhpComposerLockEntry{
Name: "alcaeus/mongo-php-adapter",
Version: "1.1.11",
Source: pkg.PhpComposerExternalReference{
+12 -6
View File
@@ -16,19 +16,24 @@ var _ generic.Parser = parseComposerLock
// Note: composer version 2 introduced a new structure for the installed.json file, so we support both
type installedJSONComposerV2 struct {
Packages []parsedData `json:"packages"`
Packages []parsedInstalledData `json:"packages"`
}
type parsedInstalledData struct {
License []string `json:"license"`
pkg.PhpComposerInstalledEntry
}
func (w *installedJSONComposerV2) UnmarshalJSON(data []byte) error {
type compv2 struct {
Packages []parsedData `json:"packages"`
Packages []parsedInstalledData `json:"packages"`
}
compv2er := new(compv2)
err := json.Unmarshal(data, &compv2er)
if err != nil {
// If we had an err or, we may be dealing with a composer v.1 installed.json
// which should be all arrays
var packages []parsedData
var packages []parsedInstalledData
err := json.Unmarshal(data, &packages)
if err != nil {
return err
@@ -52,11 +57,12 @@ func parseInstalledJSON(_ file.Resolver, _ *generic.Environment, reader file.Loc
} else if err != nil {
return nil, nil, fmt.Errorf("failed to parse installed.json file: %w", err)
}
for _, pkgMeta := range lock.Packages {
for _, pd := range lock.Packages {
pkgs = append(
pkgs,
newComposerLockPackage(pkgMeta,
reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
newComposerInstalledPackage(
pd,
reader.Location,
),
)
}
@@ -18,16 +18,15 @@ func TestParseInstalledJsonComposerV1(t *testing.T) {
var expectedRelationships []artifact.Relationship
var expectedPkgs = []pkg.Package{
{
Name: "asm89/stack-cors",
Version: "1.3.0",
PURL: "pkg:composer/asm89/stack-cors@1.3.0",
Language: pkg.PHP,
Type: pkg.PhpComposerPkg,
MetadataType: pkg.PhpComposerJSONMetadataType,
Name: "asm89/stack-cors",
Version: "1.3.0",
PURL: "pkg:composer/asm89/stack-cors@1.3.0",
Language: pkg.PHP,
Type: pkg.PhpComposerPkg,
Licenses: pkg.NewLicenseSet(
pkg.NewLicense("MIT"),
),
Metadata: pkg.PhpComposerJSONMetadata{
Metadata: pkg.PhpComposerInstalledEntry{
Name: "asm89/stack-cors",
Version: "1.3.0",
Source: pkg.PhpComposerExternalReference{
@@ -76,8 +75,7 @@ func TestParseInstalledJsonComposerV1(t *testing.T) {
Licenses: pkg.NewLicenseSet(
pkg.NewLicense("MIT"),
),
MetadataType: pkg.PhpComposerJSONMetadataType,
Metadata: pkg.PhpComposerJSONMetadata{
Metadata: pkg.PhpComposerInstalledEntry{
Name: "behat/mink",
Version: "v1.8.1",
Source: pkg.PhpComposerExternalReference{