mirror of
https://github.com/anchore/syft.git
synced 2025-11-17 16:33:21 +01:00
add rpmdb file info to cataloger
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
This commit is contained in:
parent
ecfc471ce5
commit
8095cd9980
2
go.mod
2
go.mod
@ -11,7 +11,7 @@ require (
|
|||||||
github.com/bmatcuk/doublestar v1.3.3
|
github.com/bmatcuk/doublestar v1.3.3
|
||||||
github.com/docker/docker v17.12.0-ce-rc1.0.20200309214505-aa6a9891b09c+incompatible
|
github.com/docker/docker v17.12.0-ce-rc1.0.20200309214505-aa6a9891b09c+incompatible
|
||||||
github.com/dustin/go-humanize v1.0.0
|
github.com/dustin/go-humanize v1.0.0
|
||||||
github.com/go-test/deep v1.0.6
|
github.com/go-test/deep v1.0.7
|
||||||
github.com/google/uuid v1.1.1
|
github.com/google/uuid v1.1.1
|
||||||
github.com/gookit/color v1.2.7
|
github.com/gookit/color v1.2.7
|
||||||
github.com/hashicorp/go-multierror v1.1.0
|
github.com/hashicorp/go-multierror v1.1.0
|
||||||
|
|||||||
2
go.sum
2
go.sum
@ -299,6 +299,8 @@ github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LB
|
|||||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||||
github.com/go-test/deep v1.0.6 h1:UHSEyLZUwX9Qoi99vVwvewiMC8mM2bf7XEM2nqvzEn8=
|
github.com/go-test/deep v1.0.6 h1:UHSEyLZUwX9Qoi99vVwvewiMC8mM2bf7XEM2nqvzEn8=
|
||||||
github.com/go-test/deep v1.0.6/go.mod h1:QV8Hv/iy04NyLBxAdO9njL0iVPN1S4d/A3NVv1V36o8=
|
github.com/go-test/deep v1.0.6/go.mod h1:QV8Hv/iy04NyLBxAdO9njL0iVPN1S4d/A3NVv1V36o8=
|
||||||
|
github.com/go-test/deep v1.0.7 h1:/VSMRlnY/JSyqxQUzQLKVMAskpY/NZKFA5j2P+0pP2M=
|
||||||
|
github.com/go-test/deep v1.0.7/go.mod h1:QV8Hv/iy04NyLBxAdO9njL0iVPN1S4d/A3NVv1V36o8=
|
||||||
github.com/go-toolsmith/astcast v1.0.0 h1:JojxlmI6STnFVG9yOImLeGREv8W2ocNUM+iOhR6jE7g=
|
github.com/go-toolsmith/astcast v1.0.0 h1:JojxlmI6STnFVG9yOImLeGREv8W2ocNUM+iOhR6jE7g=
|
||||||
github.com/go-toolsmith/astcast v1.0.0/go.mod h1:mt2OdQTeAQcY4DQgPSArJjHCcOwlX+Wl/kwN+LbLGQ4=
|
github.com/go-toolsmith/astcast v1.0.0/go.mod h1:mt2OdQTeAQcY4DQgPSArJjHCcOwlX+Wl/kwN+LbLGQ4=
|
||||||
github.com/go-toolsmith/astcopy v1.0.0 h1:OMgl1b1MEpjFQ1m5ztEO06rz5CUd3oBv9RF7+DyvdG8=
|
github.com/go-toolsmith/astcopy v1.0.0 h1:OMgl1b1MEpjFQ1m5ztEO06rz5CUd3oBv9RF7+DyvdG8=
|
||||||
|
|||||||
@ -4,13 +4,49 @@ Package rpmdb provides a concrete Cataloger implementation for RPM "Package" DB
|
|||||||
package rpmdb
|
package rpmdb
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/anchore/syft/syft/cataloger/common"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/anchore/syft/syft/pkg"
|
||||||
|
"github.com/anchore/syft/syft/scope"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
packagesGlob = "**/var/lib/rpm/Packages"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Cataloger struct{}
|
||||||
|
|
||||||
// NewRpmdbCataloger returns a new RPM DB cataloger object.
|
// NewRpmdbCataloger returns a new RPM DB cataloger object.
|
||||||
func NewRpmdbCataloger() *common.GenericCataloger {
|
func NewRpmdbCataloger() *Cataloger {
|
||||||
globParsers := map[string]common.ParserFn{
|
return &Cataloger{}
|
||||||
"**/var/lib/rpm/Packages": parseRpmDB,
|
}
|
||||||
}
|
|
||||||
return common.NewGenericCataloger(nil, globParsers, "rpmdb-cataloger")
|
// Name returns a string that uniquely describes a cataloger
|
||||||
|
func (c *Cataloger) Name() string {
|
||||||
|
return "rpmdb-cataloger"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Catalog is given an object to resolve file references and content, this function returns any discovered Packages after analyzing python egg and wheel installations.
|
||||||
|
func (c *Cataloger) Catalog(resolver scope.Resolver) ([]pkg.Package, error) {
|
||||||
|
|
||||||
|
fileMatches, err := resolver.FilesByGlob(packagesGlob)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to find rpmdb's by glob")
|
||||||
|
}
|
||||||
|
|
||||||
|
var pkgs []pkg.Package
|
||||||
|
for _, ref := range fileMatches {
|
||||||
|
|
||||||
|
dbContents, err := resolver.FileContentsByRef(ref)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
pkgs, err = parseRpmDB(resolver, strings.NewReader(dbContents))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to catalog rpmdb package=%+v: %w", ref.Path, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return pkgs, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,18 +6,18 @@ import (
|
|||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"github.com/anchore/stereoscope/pkg/file"
|
||||||
|
|
||||||
|
"github.com/anchore/syft/syft/scope"
|
||||||
|
|
||||||
rpmdb "github.com/anchore/go-rpmdb/pkg"
|
rpmdb "github.com/anchore/go-rpmdb/pkg"
|
||||||
"github.com/anchore/syft/internal"
|
"github.com/anchore/syft/internal"
|
||||||
"github.com/anchore/syft/internal/log"
|
"github.com/anchore/syft/internal/log"
|
||||||
"github.com/anchore/syft/syft/cataloger/common"
|
|
||||||
"github.com/anchore/syft/syft/pkg"
|
"github.com/anchore/syft/syft/pkg"
|
||||||
)
|
)
|
||||||
|
|
||||||
// integrity check
|
|
||||||
var _ common.ParserFn = parseRpmDB
|
|
||||||
|
|
||||||
// parseApkDb parses an "Packages" RPM DB and returns the Packages listed within it.
|
// parseApkDb parses an "Packages" RPM DB and returns the Packages listed within it.
|
||||||
func parseRpmDB(_ string, reader io.Reader) ([]pkg.Package, error) {
|
func parseRpmDB(resolver scope.FileResolver, reader io.Reader) ([]pkg.Package, error) {
|
||||||
f, err := ioutil.TempFile("", internal.ApplicationName+"-rpmdb")
|
f, err := ioutil.TempFile("", internal.ApplicationName+"-rpmdb")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to create temp rpmdb file: %w", err)
|
return nil, fmt.Errorf("failed to create temp rpmdb file: %w", err)
|
||||||
@ -48,6 +48,26 @@ func parseRpmDB(_ string, reader io.Reader) ([]pkg.Package, error) {
|
|||||||
allPkgs := make([]pkg.Package, 0)
|
allPkgs := make([]pkg.Package, 0)
|
||||||
|
|
||||||
for _, entry := range pkgList {
|
for _, entry := range pkgList {
|
||||||
|
var records = make([]pkg.RpmdbFileRecord, 0)
|
||||||
|
|
||||||
|
for _, record := range entry.Files {
|
||||||
|
refs, err := resolver.FilesByPath(file.Path(record.Path))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to resolve path=%+v: %w", record.Path, err)
|
||||||
|
}
|
||||||
|
//only persist RPMDB file records which exist in the image/directory, otherwise ignore them
|
||||||
|
if len(refs) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
records = append(records, pkg.RpmdbFileRecord{
|
||||||
|
Path: record.Path,
|
||||||
|
Mode: pkg.RpmdbFileMode(record.Mode),
|
||||||
|
Size: int(record.Size),
|
||||||
|
SHA256: record.SHA256,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
p := pkg.Package{
|
p := pkg.Package{
|
||||||
Name: entry.Name,
|
Name: entry.Name,
|
||||||
Version: fmt.Sprintf("%s-%s", entry.Version, entry.Release), // this is what engine does
|
Version: fmt.Sprintf("%s-%s", entry.Version, entry.Release), // this is what engine does
|
||||||
@ -64,6 +84,7 @@ func parseRpmDB(_ string, reader io.Reader) ([]pkg.Package, error) {
|
|||||||
Vendor: entry.Vendor,
|
Vendor: entry.Vendor,
|
||||||
License: entry.License,
|
License: entry.License,
|
||||||
Size: entry.Size,
|
Size: entry.Size,
|
||||||
|
Files: records,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,58 +1,141 @@
|
|||||||
package rpmdb
|
package rpmdb
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/anchore/stereoscope/pkg/file"
|
||||||
"github.com/anchore/syft/syft/pkg"
|
"github.com/anchore/syft/syft/pkg"
|
||||||
"github.com/go-test/deep"
|
"github.com/go-test/deep"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type rpmdbTestFileResolverMock struct {
|
||||||
|
ignorePaths bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTestFileResolver(ignorePaths bool) *rpmdbTestFileResolverMock {
|
||||||
|
return &rpmdbTestFileResolverMock{
|
||||||
|
ignorePaths: ignorePaths,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *rpmdbTestFileResolverMock) FilesByPath(paths ...file.Path) ([]file.Reference, error) {
|
||||||
|
if r.ignorePaths {
|
||||||
|
// act as if no paths exist
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
// act as if all files exist
|
||||||
|
var refs = make([]file.Reference, len(paths))
|
||||||
|
for i, p := range paths {
|
||||||
|
refs[i] = file.NewFileReference(p)
|
||||||
|
}
|
||||||
|
return refs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *rpmdbTestFileResolverMock) FilesByGlob(_ ...string) ([]file.Reference, error) {
|
||||||
|
return nil, fmt.Errorf("not implemented")
|
||||||
|
}
|
||||||
|
func (r *rpmdbTestFileResolverMock) RelativeFileByPath(_ file.Reference, path string) (*file.Reference, error) {
|
||||||
|
return nil, fmt.Errorf("not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
func TestParseRpmDB(t *testing.T) {
|
func TestParseRpmDB(t *testing.T) {
|
||||||
expected := map[string]pkg.Package{
|
tests := []struct {
|
||||||
"dive": {
|
fixture string
|
||||||
Name: "dive",
|
expected map[string]pkg.Package
|
||||||
Version: "0.9.2-1",
|
ignorePaths bool
|
||||||
Type: pkg.RpmPkg,
|
}{
|
||||||
MetadataType: pkg.RpmdbMetadataType,
|
{
|
||||||
Metadata: pkg.RpmdbMetadata{
|
fixture: "test-fixtures/Packages",
|
||||||
Name: "dive",
|
// we only surface package paths for files that exist (here we DO NOT expect a path)
|
||||||
Epoch: 0,
|
ignorePaths: true,
|
||||||
Arch: "x86_64",
|
expected: map[string]pkg.Package{
|
||||||
Release: "1",
|
"dive": {
|
||||||
Version: "0.9.2",
|
Name: "dive",
|
||||||
SourceRpm: "dive-0.9.2-1.src.rpm",
|
Version: "0.9.2-1",
|
||||||
Size: 12406784,
|
Type: pkg.RpmPkg,
|
||||||
License: "MIT",
|
MetadataType: pkg.RpmdbMetadataType,
|
||||||
Vendor: "",
|
Metadata: pkg.RpmdbMetadata{
|
||||||
|
Name: "dive",
|
||||||
|
Epoch: 0,
|
||||||
|
Arch: "x86_64",
|
||||||
|
Release: "1",
|
||||||
|
Version: "0.9.2",
|
||||||
|
SourceRpm: "dive-0.9.2-1.src.rpm",
|
||||||
|
Size: 12406784,
|
||||||
|
License: "MIT",
|
||||||
|
Vendor: "",
|
||||||
|
Files: []pkg.RpmdbFileRecord{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fixture: "test-fixtures/Packages",
|
||||||
|
// we only surface package paths for files that exist (here we expect a path)
|
||||||
|
ignorePaths: false,
|
||||||
|
expected: map[string]pkg.Package{
|
||||||
|
"dive": {
|
||||||
|
Name: "dive",
|
||||||
|
Version: "0.9.2-1",
|
||||||
|
Type: pkg.RpmPkg,
|
||||||
|
MetadataType: pkg.RpmdbMetadataType,
|
||||||
|
Metadata: pkg.RpmdbMetadata{
|
||||||
|
Name: "dive",
|
||||||
|
Epoch: 0,
|
||||||
|
Arch: "x86_64",
|
||||||
|
Release: "1",
|
||||||
|
Version: "0.9.2",
|
||||||
|
SourceRpm: "dive-0.9.2-1.src.rpm",
|
||||||
|
Size: 12406784,
|
||||||
|
License: "MIT",
|
||||||
|
Vendor: "",
|
||||||
|
Files: []pkg.RpmdbFileRecord{
|
||||||
|
{
|
||||||
|
Path: "/usr/local/bin/dive",
|
||||||
|
Mode: 33261,
|
||||||
|
Size: 12406784,
|
||||||
|
SHA256: "81d29f327ba23096b3c52ff6fe1c425641e618bc87b5c05ee377edc650afaa55",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
fixture, err := os.Open("test-fixtures/Packages")
|
for _, test := range tests {
|
||||||
if err != nil {
|
t.Run(test.fixture, func(t *testing.T) {
|
||||||
t.Fatalf("failed to open fixture: %+v", err)
|
fixture, err := os.Open(test.fixture)
|
||||||
}
|
if err != nil {
|
||||||
|
t.Fatalf("failed to open fixture: %+v", err)
|
||||||
actual, err := parseRpmDB(fixture.Name(), fixture)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to parse rpmdb: %+v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(actual) != len(expected) {
|
|
||||||
for _, a := range actual {
|
|
||||||
t.Log(" ", a)
|
|
||||||
}
|
|
||||||
t.Fatalf("unexpected package count: %d!=%d", len(actual), len(expected))
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, a := range actual {
|
|
||||||
e := expected[a.Name]
|
|
||||||
diffs := deep.Equal(a, e)
|
|
||||||
if len(diffs) > 0 {
|
|
||||||
for _, d := range diffs {
|
|
||||||
t.Errorf("diff: %+v", d)
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
fileResolver := newTestFileResolver(test.ignorePaths)
|
||||||
|
|
||||||
|
actual, err := parseRpmDB(fileResolver, fixture)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to parse rpmdb: %+v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(actual) != len(test.expected) {
|
||||||
|
for _, a := range actual {
|
||||||
|
t.Log(" ", a)
|
||||||
|
}
|
||||||
|
t.Fatalf("unexpected package count: %d!=%d", len(actual), len(test.expected))
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, a := range actual {
|
||||||
|
e := test.expected[a.Name]
|
||||||
|
diffs := deep.Equal(a, e)
|
||||||
|
if len(diffs) > 0 {
|
||||||
|
for _, d := range diffs {
|
||||||
|
t.Errorf("diff: %+v", d)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,17 +9,27 @@ import (
|
|||||||
|
|
||||||
// RpmdbMetadata represents all captured data for a RPM DB package entry.
|
// RpmdbMetadata represents all captured data for a RPM DB package entry.
|
||||||
type RpmdbMetadata struct {
|
type RpmdbMetadata struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Version string `json:"version"`
|
Version string `json:"version"`
|
||||||
Epoch int `json:"epoch"`
|
Epoch int `json:"epoch"`
|
||||||
Arch string `json:"architecture"`
|
Arch string `json:"architecture"`
|
||||||
Release string `json:"release"`
|
Release string `json:"release"`
|
||||||
SourceRpm string `json:"sourceRpm"`
|
SourceRpm string `json:"sourceRpm"`
|
||||||
Size int `json:"size"`
|
Size int `json:"size"`
|
||||||
License string `json:"license"`
|
License string `json:"license"`
|
||||||
Vendor string `json:"vendor"`
|
Vendor string `json:"vendor"`
|
||||||
|
Files []RpmdbFileRecord `json:"files"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type RpmdbFileRecord struct {
|
||||||
|
Path string `json:"path"`
|
||||||
|
Mode RpmdbFileMode `json:"mode"`
|
||||||
|
Size int `json:"size"`
|
||||||
|
SHA256 string `json:"sha256"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type RpmdbFileMode uint16
|
||||||
|
|
||||||
func (m RpmdbMetadata) PackageURL(d distro.Distro) string {
|
func (m RpmdbMetadata) PackageURL(d distro.Distro) string {
|
||||||
pURL := packageurl.NewPackageURL(
|
pURL := packageurl.NewPackageURL(
|
||||||
packageurl.TypeRPM,
|
packageurl.TypeRPM,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user