mirror of
https://github.com/anchore/syft.git
synced 2025-11-17 08:23:15 +01:00
[wip] add elf note dependencies
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
This commit is contained in:
parent
254a915592
commit
bd131d78f1
@ -23,7 +23,7 @@ type ELFBinaryPackageNoteJSONPayload struct {
|
||||
// Architecture of the binary package (e.g. "amd64", "arm", etc.)
|
||||
Architecture string `json:"architecture,omitempty"`
|
||||
|
||||
// OS CPE is a CPE name for the OS, typically corresponding to CPE_NAME in os-release (e.g. cpe:/o:fedoraproject:fedora:33)
|
||||
// OS CPE is the common platform enumeration for the OS environment for the package, typically corresponding to CPE_NAME in os-release (e.g. cpe:/o:fedoraproject:fedora:33)
|
||||
OSCPE string `json:"osCPE,omitempty"`
|
||||
|
||||
// OS is the OS name, typically corresponding to ID in os-release (e.g. "fedora")
|
||||
|
||||
@ -3,18 +3,47 @@ package binary
|
||||
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/pkg"
|
||||
)
|
||||
|
||||
func newELFPackage(metadata elfBinaryPackageNotes, locations file.LocationSet) pkg.Package {
|
||||
func newELFPackages(metadata elfBinaryPackageNotes, locations file.LocationSet) ([]pkg.Package, []artifact.Relationship) {
|
||||
parentPkg := newELFPackage(metadata.elfPackageCore, locations)
|
||||
pkgs := []pkg.Package{parentPkg}
|
||||
var relationships []artifact.Relationship
|
||||
for _, depMetadata := range metadata.Dependencies {
|
||||
dep := newELFPackage(depMetadata, locations)
|
||||
pkgs = append(pkgs, dep)
|
||||
relationships = append(relationships, artifact.Relationship{
|
||||
From: dep,
|
||||
To: parentPkg,
|
||||
Type: artifact.DependencyOfRelationship,
|
||||
})
|
||||
}
|
||||
|
||||
return pkgs, relationships
|
||||
}
|
||||
|
||||
func newELFPackage(metadata elfPackageCore, locations file.LocationSet) pkg.Package {
|
||||
var cpes []cpe.CPE
|
||||
if metadata.CPE != "" {
|
||||
c, err := cpe.New(metadata.CPE, cpe.DeclaredSource)
|
||||
if err != nil {
|
||||
log.WithFields("error", err, "cpe", metadata.CPE).Trace("unable to parse cpe for elf binary package")
|
||||
} else {
|
||||
cpes = append(cpes, c)
|
||||
}
|
||||
}
|
||||
|
||||
p := pkg.Package{
|
||||
Name: metadata.Name,
|
||||
Version: metadata.Version,
|
||||
Licenses: pkg.NewLicenseSet(pkg.NewLicense(metadata.License)),
|
||||
PURL: packageURL(metadata),
|
||||
Type: pkgType(metadata.Type),
|
||||
CPEs: cpes,
|
||||
Locations: locations,
|
||||
Metadata: metadata.ELFBinaryPackageNoteJSONPayload,
|
||||
}
|
||||
@ -24,7 +53,7 @@ func newELFPackage(metadata elfBinaryPackageNotes, locations file.LocationSet) p
|
||||
return p
|
||||
}
|
||||
|
||||
func packageURL(metadata elfBinaryPackageNotes) string {
|
||||
func packageURL(metadata elfPackageCore) string {
|
||||
var qualifiers []packageurl.Qualifier
|
||||
|
||||
os, osVersion := osNameAndVersionFromMetadata(metadata)
|
||||
@ -58,7 +87,7 @@ func packageURL(metadata elfBinaryPackageNotes) string {
|
||||
).ToString()
|
||||
}
|
||||
|
||||
func osNameAndVersionFromMetadata(metadata elfBinaryPackageNotes) (string, string) {
|
||||
func osNameAndVersionFromMetadata(metadata elfPackageCore) (string, string) {
|
||||
os := metadata.OS
|
||||
osVersion := metadata.OSVersion
|
||||
|
||||
|
||||
@ -28,20 +28,22 @@ type elfPackageCataloger struct {
|
||||
// For example, fedora includes an ELF section header as a prefix to the JSON payload: https://github.com/anchore/syft/issues/2713
|
||||
|
||||
type elfBinaryPackageNotes struct {
|
||||
Name string `json:"name"`
|
||||
Version string `json:"version"`
|
||||
PURL string `json:"purl"`
|
||||
CPE string `json:"cpe"`
|
||||
elfPackageCore `json:",inline"`
|
||||
Dependencies []elfPackageCore `json:"dependencies"`
|
||||
}
|
||||
|
||||
type elfPackageCore struct {
|
||||
elfPackageKey `json:",inline"`
|
||||
License string `json:"license"`
|
||||
pkg.ELFBinaryPackageNoteJSONPayload `json:",inline"`
|
||||
Location file.Location `json:"-"`
|
||||
}
|
||||
|
||||
type elfPackageKey struct {
|
||||
Name string
|
||||
Version string
|
||||
PURL string
|
||||
CPE string
|
||||
Name string `json:"name"`
|
||||
Version string `json:"version"`
|
||||
PURL string `json:"purl"`
|
||||
CPE string `json:"cpe"`
|
||||
}
|
||||
|
||||
func NewELFPackageCataloger() pkg.Cataloger {
|
||||
@ -77,6 +79,7 @@ func (c *elfPackageCataloger) Catalog(_ context.Context, resolver file.Resolver)
|
||||
// we do this in a second pass since it is possible that we have multiple ELF binaries with the same name and version
|
||||
// which means the set of binaries collectively represent a single logical package.
|
||||
var pkgs []pkg.Package
|
||||
var relationships []artifact.Relationship
|
||||
for _, notes := range notesByLocation {
|
||||
noteLocations := file.NewLocationSet()
|
||||
for _, note := range notes {
|
||||
@ -84,13 +87,15 @@ func (c *elfPackageCataloger) Catalog(_ context.Context, resolver file.Resolver)
|
||||
}
|
||||
|
||||
// create a package for each unique name/version pair (based on the first note found)
|
||||
pkgs = append(pkgs, newELFPackage(notes[0], noteLocations))
|
||||
ps, rs := newELFPackages(notes[0], noteLocations)
|
||||
pkgs = append(pkgs, ps...)
|
||||
relationships = append(relationships, rs...)
|
||||
}
|
||||
|
||||
// why not return relationships? We have an executable cataloger that will note the dynamic libraries imported by
|
||||
// each binary. After all files and packages are processed there is a final task that creates package-to-package
|
||||
// and package-to-file relationships based on the dynamic libraries imported by each binary.
|
||||
return pkgs, nil, errs
|
||||
return pkgs, relationships, errs
|
||||
}
|
||||
|
||||
func parseElfPackageNotes(resolver file.Resolver, location file.Location, c *elfPackageCataloger) (*elfBinaryPackageNotes, elfPackageKey, error) {
|
||||
@ -115,13 +120,7 @@ func parseElfPackageNotes(resolver file.Resolver, location file.Location, c *elf
|
||||
}
|
||||
|
||||
notes.Location = location
|
||||
key := elfPackageKey{
|
||||
Name: notes.Name,
|
||||
Version: notes.Version,
|
||||
PURL: notes.PURL,
|
||||
CPE: notes.CPE,
|
||||
}
|
||||
return notes, key, nil
|
||||
return notes, notes.elfPackageKey, nil
|
||||
}
|
||||
|
||||
func (c *elfPackageCataloger) parseElfNotes(reader file.LocationReadCloser) (*elfBinaryPackageNotes, error) {
|
||||
|
||||
@ -5,13 +5,13 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/anchore/syft/syft/cpe"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest"
|
||||
)
|
||||
|
||||
func Test_ELF_Package_Cataloger(t *testing.T) {
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
fixture string
|
||||
@ -34,7 +34,7 @@ func Test_ELF_Package_Cataloger(t *testing.T) {
|
||||
Licenses: pkg.NewLicenseSet(
|
||||
pkg.License{Value: "MIT", SPDXExpression: "MIT", Type: "declared"},
|
||||
),
|
||||
|
||||
CPEs: []cpe.CPE{cpe.Must("cpe:/o:syft:syftsys_testfixture_syfttestfixture:0.01", cpe.DeclaredSource)},
|
||||
Type: pkg.BinaryPkg,
|
||||
Metadata: pkg.ELFBinaryPackageNoteJSONPayload{
|
||||
Type: "testfixture",
|
||||
@ -56,6 +56,7 @@ func Test_ELF_Package_Cataloger(t *testing.T) {
|
||||
Licenses: pkg.NewLicenseSet(
|
||||
pkg.License{Value: "MIT", SPDXExpression: "MIT", Type: "declared"},
|
||||
),
|
||||
CPEs: []cpe.CPE{cpe.Must("cpe:/o:syft:syftsys_testfixture_syfttestfixture:0.01", cpe.DeclaredSource)},
|
||||
Type: pkg.BinaryPkg,
|
||||
Metadata: pkg.ELFBinaryPackageNoteJSONPayload{
|
||||
Type: "testfixture",
|
||||
|
||||
@ -7,6 +7,7 @@ import (
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/anchore/syft/syft/cpe"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
)
|
||||
@ -20,10 +21,14 @@ func Test_packageURL(t *testing.T) {
|
||||
{
|
||||
name: "elf-binary-package-cataloger",
|
||||
metadata: elfBinaryPackageNotes{
|
||||
Name: "github.com/anchore/syft",
|
||||
Version: "v0.1.0",
|
||||
ELFBinaryPackageNoteJSONPayload: pkg.ELFBinaryPackageNoteJSONPayload{
|
||||
System: "syftsys",
|
||||
elfPackageCore: elfPackageCore{
|
||||
elfPackageKey: elfPackageKey{
|
||||
Name: "github.com/anchore/syft",
|
||||
Version: "v0.1.0",
|
||||
},
|
||||
ELFBinaryPackageNoteJSONPayload: pkg.ELFBinaryPackageNoteJSONPayload{
|
||||
System: "syftsys",
|
||||
},
|
||||
},
|
||||
},
|
||||
want: "pkg:generic/syftsys/github.com/anchore/syft@v0.1.0",
|
||||
@ -31,10 +36,14 @@ func Test_packageURL(t *testing.T) {
|
||||
{
|
||||
name: "elf binary package short name",
|
||||
metadata: elfBinaryPackageNotes{
|
||||
Name: "go.opencensus.io",
|
||||
Version: "v0.23.0",
|
||||
ELFBinaryPackageNoteJSONPayload: pkg.ELFBinaryPackageNoteJSONPayload{
|
||||
System: "syftsys",
|
||||
elfPackageCore: elfPackageCore{
|
||||
elfPackageKey: elfPackageKey{
|
||||
Name: "go.opencensus.io",
|
||||
Version: "v0.23.0",
|
||||
},
|
||||
ELFBinaryPackageNoteJSONPayload: pkg.ELFBinaryPackageNoteJSONPayload{
|
||||
System: "syftsys",
|
||||
},
|
||||
},
|
||||
},
|
||||
want: "pkg:generic/syftsys/go.opencensus.io@v0.23.0",
|
||||
@ -42,10 +51,14 @@ func Test_packageURL(t *testing.T) {
|
||||
{
|
||||
name: "no info",
|
||||
metadata: elfBinaryPackageNotes{
|
||||
Name: "test",
|
||||
Version: "1.0",
|
||||
ELFBinaryPackageNoteJSONPayload: pkg.ELFBinaryPackageNoteJSONPayload{
|
||||
Type: "rpm",
|
||||
elfPackageCore: elfPackageCore{
|
||||
elfPackageKey: elfPackageKey{
|
||||
Name: "test",
|
||||
Version: "1.0",
|
||||
},
|
||||
ELFBinaryPackageNoteJSONPayload: pkg.ELFBinaryPackageNoteJSONPayload{
|
||||
Type: "rpm",
|
||||
},
|
||||
},
|
||||
},
|
||||
want: "pkg:rpm/test@1.0",
|
||||
@ -53,11 +66,15 @@ func Test_packageURL(t *testing.T) {
|
||||
{
|
||||
name: "with system",
|
||||
metadata: elfBinaryPackageNotes{
|
||||
Name: "test",
|
||||
Version: "1.0",
|
||||
ELFBinaryPackageNoteJSONPayload: pkg.ELFBinaryPackageNoteJSONPayload{
|
||||
Type: "rpm",
|
||||
System: "system",
|
||||
elfPackageCore: elfPackageCore{
|
||||
elfPackageKey: elfPackageKey{
|
||||
Name: "test",
|
||||
Version: "1.0",
|
||||
},
|
||||
ELFBinaryPackageNoteJSONPayload: pkg.ELFBinaryPackageNoteJSONPayload{
|
||||
Type: "rpm",
|
||||
System: "system",
|
||||
},
|
||||
},
|
||||
},
|
||||
want: "pkg:rpm/system/test@1.0",
|
||||
@ -65,13 +82,17 @@ func Test_packageURL(t *testing.T) {
|
||||
{
|
||||
name: "with os info preferred",
|
||||
metadata: elfBinaryPackageNotes{
|
||||
Name: "test",
|
||||
Version: "1.0",
|
||||
ELFBinaryPackageNoteJSONPayload: pkg.ELFBinaryPackageNoteJSONPayload{
|
||||
Type: "rpm",
|
||||
OS: "fedora",
|
||||
OSVersion: "2.0",
|
||||
OSCPE: "cpe:/o:someone:redhat:3.0",
|
||||
elfPackageCore: elfPackageCore{
|
||||
elfPackageKey: elfPackageKey{
|
||||
Name: "test",
|
||||
Version: "1.0",
|
||||
},
|
||||
ELFBinaryPackageNoteJSONPayload: pkg.ELFBinaryPackageNoteJSONPayload{
|
||||
Type: "rpm",
|
||||
OS: "fedora",
|
||||
OSVersion: "2.0",
|
||||
OSCPE: "cpe:/o:someone:redhat:3.0",
|
||||
},
|
||||
},
|
||||
},
|
||||
want: "pkg:rpm/fedora/test@1.0?distro=fedora-2.0",
|
||||
@ -79,12 +100,16 @@ func Test_packageURL(t *testing.T) {
|
||||
{
|
||||
name: "with os info fallback to CPE parsing (missing version)",
|
||||
metadata: elfBinaryPackageNotes{
|
||||
Name: "test",
|
||||
Version: "1.0",
|
||||
ELFBinaryPackageNoteJSONPayload: pkg.ELFBinaryPackageNoteJSONPayload{
|
||||
Type: "rpm",
|
||||
OS: "fedora",
|
||||
OSCPE: "cpe:/o:someone:redhat:3.0",
|
||||
elfPackageCore: elfPackageCore{
|
||||
elfPackageKey: elfPackageKey{
|
||||
Name: "test",
|
||||
Version: "1.0",
|
||||
},
|
||||
ELFBinaryPackageNoteJSONPayload: pkg.ELFBinaryPackageNoteJSONPayload{
|
||||
Type: "rpm",
|
||||
OS: "fedora",
|
||||
OSCPE: "cpe:/o:someone:redhat:3.0",
|
||||
},
|
||||
},
|
||||
},
|
||||
want: "pkg:rpm/redhat/test@1.0?distro=redhat-3.0",
|
||||
@ -92,12 +117,16 @@ func Test_packageURL(t *testing.T) {
|
||||
{
|
||||
name: "with os info preferred (missing OS)",
|
||||
metadata: elfBinaryPackageNotes{
|
||||
Name: "test",
|
||||
Version: "1.0",
|
||||
ELFBinaryPackageNoteJSONPayload: pkg.ELFBinaryPackageNoteJSONPayload{
|
||||
Type: "rpm",
|
||||
OSVersion: "2.0",
|
||||
OSCPE: "cpe:/o:someone:redhat:3.0",
|
||||
elfPackageCore: elfPackageCore{
|
||||
elfPackageKey: elfPackageKey{
|
||||
Name: "test",
|
||||
Version: "1.0",
|
||||
},
|
||||
ELFBinaryPackageNoteJSONPayload: pkg.ELFBinaryPackageNoteJSONPayload{
|
||||
Type: "rpm",
|
||||
OSVersion: "2.0",
|
||||
OSCPE: "cpe:/o:someone:redhat:3.0",
|
||||
},
|
||||
},
|
||||
},
|
||||
want: "pkg:rpm/redhat/test@1.0?distro=redhat-3.0",
|
||||
@ -105,10 +134,14 @@ func Test_packageURL(t *testing.T) {
|
||||
{
|
||||
name: "missing type",
|
||||
metadata: elfBinaryPackageNotes{
|
||||
Name: "test",
|
||||
Version: "1.0",
|
||||
ELFBinaryPackageNoteJSONPayload: pkg.ELFBinaryPackageNoteJSONPayload{
|
||||
System: "system",
|
||||
elfPackageCore: elfPackageCore{
|
||||
elfPackageKey: elfPackageKey{
|
||||
Name: "test",
|
||||
Version: "1.0",
|
||||
},
|
||||
ELFBinaryPackageNoteJSONPayload: pkg.ELFBinaryPackageNoteJSONPayload{
|
||||
System: "system",
|
||||
},
|
||||
},
|
||||
},
|
||||
want: "pkg:generic/system/test@1.0",
|
||||
@ -116,11 +149,15 @@ func Test_packageURL(t *testing.T) {
|
||||
{
|
||||
name: "bad or missing OSCPE data cannot be parsed allows for correct string",
|
||||
metadata: elfBinaryPackageNotes{
|
||||
Name: "test",
|
||||
Version: "1.0",
|
||||
ELFBinaryPackageNoteJSONPayload: pkg.ELFBinaryPackageNoteJSONPayload{
|
||||
System: "system",
|
||||
OSCPE: "%$#*(#*@&$(",
|
||||
elfPackageCore: elfPackageCore{
|
||||
elfPackageKey: elfPackageKey{
|
||||
Name: "test",
|
||||
Version: "1.0",
|
||||
},
|
||||
ELFBinaryPackageNoteJSONPayload: pkg.ELFBinaryPackageNoteJSONPayload{
|
||||
System: "system",
|
||||
OSCPE: "%$#*(#*@&$(",
|
||||
},
|
||||
},
|
||||
},
|
||||
want: "pkg:generic/system/test@1.0",
|
||||
@ -129,7 +166,7 @@ func Test_packageURL(t *testing.T) {
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
assert.Equal(t, test.want, packageURL(test.metadata))
|
||||
assert.Equal(t, test.want, packageURL(test.metadata.elfPackageCore))
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -143,21 +180,25 @@ func Test_newELFPackage(t *testing.T) {
|
||||
{
|
||||
name: "elf-binary-package-cataloger",
|
||||
metadata: elfBinaryPackageNotes{
|
||||
Name: "syfttestfixture",
|
||||
Version: "0.01",
|
||||
PURL: "pkg:generic/syftsys/syfttestfixture@0.01",
|
||||
CPE: "cpe:/o:syft:syftsys_testfixture_syfttestfixture:0.01",
|
||||
ELFBinaryPackageNoteJSONPayload: pkg.ELFBinaryPackageNoteJSONPayload{
|
||||
Type: "binary",
|
||||
System: "syftsys",
|
||||
elfPackageCore: elfPackageCore{
|
||||
elfPackageKey: elfPackageKey{
|
||||
Name: "syfttestfixture",
|
||||
Version: "0.01",
|
||||
PURL: "pkg:generic/syftsys/syfttestfixture@0.01",
|
||||
CPE: "cpe:/o:syft:syftsys_testfixture_syfttestfixture:0.01",
|
||||
},
|
||||
ELFBinaryPackageNoteJSONPayload: pkg.ELFBinaryPackageNoteJSONPayload{
|
||||
Type: "binary",
|
||||
System: "syftsys",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
expected: pkg.Package{
|
||||
Name: "syfttestfixture",
|
||||
Version: "0.01",
|
||||
Type: "binary",
|
||||
PURL: "pkg:generic/syftsys/syfttestfixture@0.01",
|
||||
CPEs: []cpe.CPE{cpe.Must("cpe:/o:syft:syftsys_testfixture_syfttestfixture:0.01", cpe.DeclaredSource)},
|
||||
Metadata: pkg.ELFBinaryPackageNoteJSONPayload{
|
||||
Type: "binary",
|
||||
System: "syftsys",
|
||||
@ -168,7 +209,7 @@ func Test_newELFPackage(t *testing.T) {
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
actual := newELFPackage(test.metadata, file.NewLocationSet())
|
||||
actual := newELFPackage(test.metadata.elfPackageCore, file.NewLocationSet())
|
||||
if diff := cmp.Diff(test.expected, actual, cmpopts.IgnoreFields(pkg.Package{}, "id"), cmpopts.IgnoreUnexported(pkg.Package{}, file.LocationSet{}, pkg.LicenseSet{})); diff != "" {
|
||||
t.Errorf("newELFPackage() mismatch (-want +got):\n%s", diff)
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user