mirror of
https://github.com/anchore/syft.git
synced 2026-11-01 15:06:36 +01:00
Add nested archive cataloging (#5260)
Signed-off-by: Kudryavcev Nikolay <kydry.nikolau@gmail.com> Signed-off-by: Thomas Bechtold <thomasbechtold@jpberlin.de> Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> Signed-off-by: Keith Zantow <kzantow@gmail.com> Co-authored-by: Kudryavcev Nikolay <kydry.nikolau@gmail.com> Co-authored-by: Thomas Bechtold <thomasbechtold@jpberlin.de> Co-authored-by: Alex Goodman <wagoodman@users.noreply.github.com>
This commit is contained in:
co-authored by
Kudryavcev Nikolay
Thomas Bechtold
Alex Goodman
parent
14a0beb93c
commit
4b8a3db5f3
@@ -71,7 +71,7 @@ func compareElfBinaryPackages(s *sbom.SBOM) []artifact.ID {
|
||||
pkgsToDelete := make([]artifact.ID, 0)
|
||||
for _, elfPkg := range allElfPackages(s) {
|
||||
for _, loc := range onlyPrimaryEvidenceLocations(elfPkg) {
|
||||
for _, otherPkg := range s.Artifacts.Packages.PackagesByPath(loc.RealPath) {
|
||||
for _, otherPkg := range s.Artifacts.Packages.PackagesByPath(loc.FullNestedPath()) {
|
||||
// we only care about comparing binary packages to each other (not other types)
|
||||
if otherPkg.Type != pkg.BinaryPkg {
|
||||
continue
|
||||
@@ -130,7 +130,7 @@ func populateRelationships(exec file.Executable, parentPkg pkg.Package, resolver
|
||||
|
||||
for _, loc := range libLocations {
|
||||
// are you in our index?
|
||||
realBaseName := path.Base(loc.RealPath)
|
||||
realBaseName := path.Base(loc.FullNestedPath())
|
||||
pkgCollection := index.owningLibraryPackage(realBaseName)
|
||||
if pkgCollection.PackageCount() < 1 {
|
||||
addRelationship(
|
||||
|
||||
@@ -3,6 +3,7 @@ package binary
|
||||
import (
|
||||
"path"
|
||||
|
||||
"github.com/anchore/syft/internal/archive"
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/internal/sbomsync"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
@@ -63,7 +64,7 @@ func locationsThatProvideLibraries(accessor sbomsync.Accessor) (map[string]file.
|
||||
continue
|
||||
}
|
||||
|
||||
basename := path.Base(coord.RealPath)
|
||||
basename := path.Base(coord.FullNestedPath())
|
||||
set := libLocationsByBasename[basename]
|
||||
set.Add(coord)
|
||||
allLibLocations.Add(coord)
|
||||
@@ -82,6 +83,11 @@ func packagesWithLibraryOwnership(resolver file.Resolver, accessor sbomsync.Acce
|
||||
var ownedFilePaths []string
|
||||
if p.Type == pkg.BinaryPkg {
|
||||
for _, loc := range p.Locations.ToSlice() {
|
||||
if archive.ContainerPath(loc.Coordinates) != "" {
|
||||
// a path within an archive cannot be resolved from the scan root; the location is the library
|
||||
addLibraryOwner(packagesByLibraryPath, allLibLocations, loc.Coordinates, p)
|
||||
continue
|
||||
}
|
||||
ownedFilePaths = append(ownedFilePaths, loc.Path())
|
||||
}
|
||||
} else {
|
||||
@@ -114,18 +120,19 @@ func populatePackagesByLibraryPath(
|
||||
}
|
||||
|
||||
for _, loc := range ownedLocation {
|
||||
// if the location is a library, add the package to the set of packages that own the library
|
||||
if !allLibLocations.Contains(loc.Coordinates) {
|
||||
continue
|
||||
}
|
||||
|
||||
if _, ok := packagesByLibraryPath[loc.Coordinates]; !ok {
|
||||
packagesByLibraryPath[loc.Coordinates] = pkg.NewCollection()
|
||||
}
|
||||
|
||||
// we have a library path, add the package to the set of packages that own the library
|
||||
packagesByLibraryPath[loc.Coordinates].Add(p)
|
||||
addLibraryOwner(packagesByLibraryPath, allLibLocations, loc.Coordinates, p)
|
||||
}
|
||||
}
|
||||
return packagesByLibraryPath
|
||||
}
|
||||
|
||||
// addLibraryOwner records p as an owner of the file at coordinates when that file is a library.
|
||||
func addLibraryOwner(packagesByLibraryPath map[file.Coordinates]*pkg.Collection, allLibLocations file.CoordinateSet, coordinates file.Coordinates, p pkg.Package) {
|
||||
if !allLibLocations.Contains(coordinates) {
|
||||
return
|
||||
}
|
||||
if _, ok := packagesByLibraryPath[coordinates]; !ok {
|
||||
packagesByLibraryPath[coordinates] = pkg.NewCollection()
|
||||
}
|
||||
packagesByLibraryPath[coordinates].Add(p)
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"github.com/bmatcuk/doublestar/v4"
|
||||
"github.com/scylladb/go-set/strset"
|
||||
|
||||
"github.com/anchore/syft/internal/archive"
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/internal/sbomsync"
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
@@ -100,7 +101,7 @@ func findOwnershipByFilesRelationships(resolver file.Resolver, catalog *pkg.Coll
|
||||
// Now establish relationships, considering symlinks
|
||||
for _, candidateOwnerPkg := range catalog.Sorted() {
|
||||
id := candidateOwnerPkg.ID()
|
||||
if candidateOwnerPkg.Metadata == nil {
|
||||
if candidateOwnerPkg.Metadata == nil || withinArchive(candidateOwnerPkg) {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -164,7 +165,7 @@ func directOwnersByPath(catalog *pkg.Collection) map[pkg.Type]*strset.Set {
|
||||
|
||||
// First, identify direct ownership of all files
|
||||
for _, p := range catalog.Sorted() {
|
||||
if p.Metadata == nil {
|
||||
if p.Metadata == nil || withinArchive(p) {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -192,6 +193,21 @@ func directOwnersByPath(catalog *pkg.Collection) map[pkg.Type]*strset.Set {
|
||||
return directOwnership
|
||||
}
|
||||
|
||||
// withinArchive reports whether every location of p is inside an archive. Such a package's owned files
|
||||
// are paths within its archive, not the scan root, so it is not treated as a file owner.
|
||||
func withinArchive(p pkg.Package) bool {
|
||||
locations := p.Locations.ToSlice()
|
||||
if len(locations) == 0 {
|
||||
return false
|
||||
}
|
||||
for _, l := range locations {
|
||||
if archive.ContainerPath(l.Coordinates) == "" {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func resolvePaths(ownedFilePath string, resolver file.Resolver) []string {
|
||||
// though we have a string path, we need to resolve symlinks and other filesystem oddities since we cannot assume this is a real path
|
||||
var locs []file.Location
|
||||
@@ -206,6 +222,8 @@ func resolvePaths(ownedFilePath string, resolver file.Resolver) []string {
|
||||
|
||||
ownedFilePaths := strset.New(ownedFilePath)
|
||||
for _, loc := range locs {
|
||||
// Owned file paths are currently based on the top-level resolver, which is always the RealPath and includes
|
||||
// all files found within archives at that location
|
||||
ownedFilePaths.Add(loc.RealPath)
|
||||
}
|
||||
return ownedFilePaths.List()
|
||||
|
||||
@@ -78,6 +78,56 @@ func TestOwnershipByFilesRelationship(t *testing.T) {
|
||||
return []pkg.Package{parent, child}, []artifact.Relationship{relationship}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "package-within-archive-is-not-a-file-owner",
|
||||
setup: func(t testing.TB) ([]pkg.Package, []artifact.Relationship) {
|
||||
nestedOwner := pkg.Package{
|
||||
Locations: file.NewLocationSet(file.NewLocationFromCoordinates(file.Coordinates{
|
||||
RealPath: "/rootfs.tar",
|
||||
FullPath: "/rootfs.tar!/var/lib/rpm/Packages",
|
||||
})),
|
||||
Type: pkg.RpmPkg,
|
||||
Metadata: pkg.RpmDBEntry{
|
||||
Files: []pkg.RpmFileRecord{{Path: "/d/path"}},
|
||||
},
|
||||
}
|
||||
nestedOwner.SetID()
|
||||
|
||||
child := pkg.Package{
|
||||
Locations: file.NewLocationSet(file.NewLocation("/d/path")),
|
||||
Type: pkg.BinaryPkg,
|
||||
}
|
||||
child.SetID()
|
||||
|
||||
return []pkg.Package{nestedOwner, child}, nil
|
||||
},
|
||||
},
|
||||
{
|
||||
// the archive's own package (when it has one) is found by its path; what is inside it is
|
||||
// linked to that package by CONTAINS, not owned by whoever owns the archive file
|
||||
name: "top-level-owner-of-an-archive-does-not-own-packages-within-it",
|
||||
setup: func(t testing.TB) ([]pkg.Package, []artifact.Relationship) {
|
||||
parent := pkg.Package{
|
||||
Locations: file.NewLocationSet(file.NewLocation("/var/lib/rpm/Packages")),
|
||||
Type: pkg.RpmPkg,
|
||||
Metadata: pkg.RpmDBEntry{
|
||||
Files: []pkg.RpmFileRecord{{Path: "/opt/bundle.tar"}},
|
||||
},
|
||||
}
|
||||
parent.SetID()
|
||||
|
||||
child := pkg.Package{
|
||||
Locations: file.NewLocationSet(file.NewLocationFromCoordinates(file.Coordinates{
|
||||
RealPath: "/opt/bundle.tar",
|
||||
FullPath: "/opt/bundle.tar!/lib/thing.jar",
|
||||
})),
|
||||
Type: pkg.JavaPkg,
|
||||
}
|
||||
child.SetID()
|
||||
|
||||
return []pkg.Package{parent, child}, nil
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "only-real-file-owner-gets-ownership-relationship-not-symlink-owner",
|
||||
resolver: mockFR{
|
||||
|
||||
@@ -15,18 +15,9 @@ func TestRelationshipsEvidentBy(t *testing.T) {
|
||||
|
||||
c := pkg.NewCollection()
|
||||
|
||||
coordA := file.Coordinates{
|
||||
RealPath: "/somewhere/real",
|
||||
FileSystemID: "abc",
|
||||
}
|
||||
coordC := file.Coordinates{
|
||||
RealPath: "/somewhere/real",
|
||||
FileSystemID: "abc",
|
||||
}
|
||||
coordD := file.Coordinates{
|
||||
RealPath: "/somewhere/real",
|
||||
FileSystemID: "abc",
|
||||
}
|
||||
coordA := file.NewCoordinates("/somewhere/real", "abc")
|
||||
coordC := file.NewCoordinates("/somewhere/real", "abc")
|
||||
coordD := file.NewCoordinates("/somewhere/real", "abc")
|
||||
pkgA := pkg.Package{
|
||||
Locations: file.NewLocationSet(
|
||||
// added!
|
||||
@@ -39,10 +30,7 @@ func TestRelationshipsEvidentBy(t *testing.T) {
|
||||
pkgA.SetID()
|
||||
c.Add(pkgA)
|
||||
|
||||
coordB := file.Coordinates{
|
||||
RealPath: "/somewhere-else/real",
|
||||
FileSystemID: "def",
|
||||
}
|
||||
coordB := file.NewCoordinates("/somewhere-else/real", "def")
|
||||
pkgB := pkg.Package{
|
||||
Locations: file.NewLocationSet(
|
||||
// added!
|
||||
|
||||
@@ -21,12 +21,8 @@ func Test_Index(t *testing.T) {
|
||||
p3 := pkg.Package{
|
||||
Name: "pkg-3",
|
||||
}
|
||||
c1 := file.Coordinates{
|
||||
RealPath: "/coords/1",
|
||||
}
|
||||
c2 := file.Coordinates{
|
||||
RealPath: "/coords/2",
|
||||
}
|
||||
c1 := file.NewCoordinates("/coords/1", "")
|
||||
c2 := file.NewCoordinates("/coords/2", "")
|
||||
|
||||
for _, p := range []*pkg.Package{&p1, &p2, &p3} {
|
||||
p.SetID()
|
||||
@@ -156,18 +152,10 @@ func Test_Coordinates(t *testing.T) {
|
||||
p3 := pkg.Package{
|
||||
Name: "pkg-3",
|
||||
}
|
||||
c1 := file.Coordinates{
|
||||
RealPath: "/coords/1",
|
||||
}
|
||||
c2 := file.Coordinates{
|
||||
RealPath: "/coords/2",
|
||||
}
|
||||
c3 := file.Coordinates{
|
||||
RealPath: "/coords/3",
|
||||
}
|
||||
c4 := file.Coordinates{
|
||||
RealPath: "/coords/4",
|
||||
}
|
||||
c1 := file.NewCoordinates("/coords/1", "")
|
||||
c2 := file.NewCoordinates("/coords/2", "")
|
||||
c3 := file.NewCoordinates("/coords/3", "")
|
||||
c4 := file.NewCoordinates("/coords/4", "")
|
||||
|
||||
for _, p := range []*pkg.Package{&p1, &p2, &p3} {
|
||||
p.SetID()
|
||||
@@ -237,10 +225,10 @@ func TestRemove(t *testing.T) {
|
||||
p1 := pkg.Package{Name: "pkg-1"}
|
||||
p2 := pkg.Package{Name: "pkg-2"}
|
||||
p3 := pkg.Package{Name: "pkg-3"}
|
||||
c1 := file.Coordinates{RealPath: "/coords/1"}
|
||||
c2 := file.Coordinates{RealPath: "/coords/2"}
|
||||
c3 := file.Coordinates{RealPath: "/coords/3"}
|
||||
c4 := file.Coordinates{RealPath: "/coords/4"}
|
||||
c1 := file.NewCoordinates("/coords/1", "")
|
||||
c2 := file.NewCoordinates("/coords/2", "")
|
||||
c3 := file.NewCoordinates("/coords/3", "")
|
||||
c4 := file.NewCoordinates("/coords/4", "")
|
||||
|
||||
for _, p := range []*pkg.Package{&p1, &p2, &p3} {
|
||||
p.SetID()
|
||||
|
||||
Reference in New Issue
Block a user