Add RPM file scanning support (#1188)

This commit is contained in:
Keith Zantow 2022-09-07 14:16:30 -04:00 committed by GitHub
parent 1c7b7c5f8a
commit 70db13d49e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
37 changed files with 470 additions and 108 deletions

View file

@ -95,6 +95,16 @@ jobs:
path: syft/pkg/cataloger/java/test-fixtures/java-builds/packages
key: ${{ runner.os }}-unit-java-cache-${{ hashFiles( 'syft/pkg/cataloger/java/test-fixtures/java-builds/packages.fingerprint' ) }}
- name: Build cache key for rpm test-fixture blobs (for unit tests)
run: make rpm-binaries-fingerprint
- name: Restore RPM test-fixture cache
id: unit-rpm-cache
uses: actions/cache@v2.1.3
with:
path: syft/pkg/cataloger/rpm/test-fixtures/rpms
key: ${{ runner.os }}-unit-rpm-cache-${{ hashFiles( 'syft/pkg/cataloger/rpm/test-fixtures/rpms.fingerprint' ) }}
- name: Build cache key for go binary test-fixture blobs (for unit tests)
run: make go-binaries-fingerprint

View file

@ -226,10 +226,17 @@ go-binaries-fingerprint:
cd syft/pkg/cataloger/golang/test-fixtures/archs && \
make binaries.fingerprint
.PHONY: rpm-binaries-fingerprint
rpm-binaries-fingerprint:
$(call title,RPM binary test fixture fingerprint)
cd syft/pkg/cataloger/rpm/test-fixtures && \
make rpms.fingerprint
.PHONY: fixtures
fixtures:
$(call title,Generating test fixtures)
cd syft/pkg/cataloger/java/test-fixtures/java-builds && make
cd syft/pkg/cataloger/rpm/test-fixtures && make
.PHONY: generate-json-schema
generate-json-schema: ## Generate a new json schema

2
go.mod
View file

@ -60,6 +60,7 @@ require (
github.com/google/go-containerregistry v0.11.0
github.com/in-toto/in-toto-golang v0.3.4-0.20220709202702-fa494aaa0add
github.com/knqyf263/go-rpmdb v0.0.0-20220629110411-9a3bd2ebb923
github.com/sassoftware/go-rpmutils v0.2.0
github.com/sigstore/cosign v1.11.1
github.com/sigstore/rekor v0.11.0
github.com/sigstore/sigstore v1.4.0
@ -79,6 +80,7 @@ require (
github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect
github.com/Azure/go-autorest/logger v0.2.1 // indirect
github.com/Azure/go-autorest/tracing v0.6.0 // indirect
github.com/DataDog/zstd v1.4.5 // indirect
github.com/Masterminds/goutils v1.1.1 // indirect
github.com/Masterminds/semver/v3 v3.1.1 // indirect
github.com/Microsoft/go-winio v0.5.2 // indirect

5
go.sum
View file

@ -147,6 +147,8 @@ github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbi
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/CycloneDX/cyclonedx-go v0.5.2 h1:CkdGw2R/tZWmEbSypJVZG+3+2SAsDjJirfIrG/RbIVg=
github.com/CycloneDX/cyclonedx-go v0.5.2/go.mod h1:nQCiF4Tvrg5Ieu8qPhYMvzPGMu5I7fANZkrSsJjl5mg=
github.com/DataDog/zstd v1.4.5 h1:EndNeuB0l9syBZhut0wns3gV1hL8zX8LIu6ZiVHWLIQ=
github.com/DataDog/zstd v1.4.5/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo=
github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24/go.mod h1:4UJr5HIiMZrwgkSPdsjy2uOQExX/WEILpIrO9UPGuXs=
github.com/GoogleCloudPlatform/cloudsql-proxy v0.0.0-20191009163259-e802c2cb94ae/go.mod h1:mjwGPas4yKduTyubHvD1Atl9r1rUq8DfVy+gkVvZ+oo=
github.com/GoogleCloudPlatform/docker-credential-gcr v2.0.5+incompatible/go.mod h1:BB1eHdMLYEFuFdBlRMb0N7YGVdM5s6Pt0njxgvfbGGs=
@ -1190,6 +1192,7 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
github.com/klauspost/compress v1.11.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/klauspost/compress v1.11.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/klauspost/compress v1.11.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/klauspost/compress v1.11.13/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/klauspost/compress v1.13.4/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
github.com/klauspost/compress v1.13.5/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
@ -1611,6 +1614,8 @@ github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0
github.com/sanposhiho/wastedassign/v2 v2.0.6/go.mod h1:KyZ0MWTwxxBmfwn33zh3k1dmsbF2ud9pAAGfoLfjhtI=
github.com/sassoftware/go-rpmutils v0.0.0-20190420191620-a8f1baeba37b/go.mod h1:am+Fp8Bt506lA3Rk3QCmSqmYmLMnPDhdDUcosQCAx+I=
github.com/sassoftware/go-rpmutils v0.1.1/go.mod h1:euhXULoBpvAxqrBHEyJS4Tsu3hHxUmQWNymxoJbzgUY=
github.com/sassoftware/go-rpmutils v0.2.0 h1:pKW0HDYMFWQ5b4JQPiI3WI12hGsVoW0V8+GMoZiI/JE=
github.com/sassoftware/go-rpmutils v0.2.0/go.mod h1:TJJQYtLe/BeEmEjelI3b7xNZjzAukEkeWKmoakvaOoI=
github.com/sassoftware/relic v0.0.0-20210427151427-dfb082b79b74 h1:sUNzanSKA9z/h8xXl+ZJoxIYZL0Qx306MmxqRrvUgr0=
github.com/sassoftware/relic v0.0.0-20210427151427-dfb082b79b74/go.mod h1:YlB8wFIZmFLZ1JllNBfSURzz52fBxbliNgYALk1UDmk=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=

View file

@ -80,6 +80,8 @@ func decodeComponent(c *cyclonedx.Component) *pkg.Package {
common.DecodeInto(p, values, "syft:package", CycloneDXFields)
p.MetadataType = pkg.CleanMetadataType(p.MetadataType)
p.Metadata = decodePackageMetadata(values, c, p.MetadataType)
if p.Type == "" {

View file

@ -109,8 +109,8 @@ func Test_encodeComponentProperties(t *testing.T) {
Name: "dive",
Version: "0.9.2-1",
Type: pkg.RpmPkg,
MetadataType: pkg.RpmdbMetadataType,
Metadata: pkg.RpmdbMetadata{
MetadataType: pkg.RpmMetadataType,
Metadata: pkg.RpmMetadata{
Name: "dive",
Epoch: &epoch,
Arch: "x86_64",
@ -124,7 +124,7 @@ func Test_encodeComponentProperties(t *testing.T) {
},
},
expected: &[]cyclonedx.Property{
{Name: "syft:package:metadataType", Value: "RpmdbMetadata"},
{Name: "syft:package:metadataType", Value: "RpmMetadata"},
{Name: "syft:package:type", Value: "rpm"},
{Name: "syft:metadata:epoch", Value: "2"},
{Name: "syft:metadata:release", Value: "1"},
@ -193,29 +193,51 @@ func Test_deriveBomRef(t *testing.T) {
}
func Test_decodeComponent(t *testing.T) {
javaComponentWithNoSyftProperties := cyclonedx.Component{
Name: "ch.qos.logback/logback-classic",
Version: "1.2.3",
PackageURL: "pkg:maven/ch.qos.logback/logback-classic@1.2.3",
Type: "library",
BOMRef: "pkg:maven/ch.qos.logback/logback-classic@1.2.3",
}
tests := []struct {
name string
component cyclonedx.Component
want pkg.Language
name string
component cyclonedx.Component
wantLanguage pkg.Language
wantMetadataType pkg.MetadataType
}{
{
name: "derive language from pURL if missing",
component: javaComponentWithNoSyftProperties,
want: pkg.Java,
name: "derive language from pURL if missing",
component: cyclonedx.Component{
Name: "ch.qos.logback/logback-classic",
Version: "1.2.3",
PackageURL: "pkg:maven/ch.qos.logback/logback-classic@1.2.3",
Type: "library",
BOMRef: "pkg:maven/ch.qos.logback/logback-classic@1.2.3",
},
wantLanguage: pkg.Java,
},
{
name: "handle existing RpmdbMetadata type",
component: cyclonedx.Component{
Name: "acl",
Version: "2.2.53-1.el8",
PackageURL: "pkg:rpm/centos/acl@2.2.53-1.el8?arch=x86_64&upstream=acl-2.2.53-1.el8.src.rpm&distro=centos-8",
Type: "library",
BOMRef: "pkg:rpm/centos/acl@2.2.53-1.el8?arch=x86_64&upstream=acl-2.2.53-1.el8.src.rpm&distro=centos-8",
Properties: &[]cyclonedx.Property{
{
Name: "syft:package:metadataType",
Value: "RpmdbMetadata",
},
},
},
wantMetadataType: pkg.RpmMetadataType,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equal(t, tt.want, decodeComponent(&tt.component).Language)
p := decodeComponent(&tt.component)
if tt.wantLanguage != "" {
assert.Equal(t, tt.wantLanguage, p.Language)
}
if tt.wantMetadataType != "" {
assert.Equal(t, tt.wantMetadataType, p.MetadataType)
}
})
}
}

View file

@ -9,7 +9,7 @@ func encodePublisher(p pkg.Package) string {
switch metadata := p.Metadata.(type) {
case pkg.ApkMetadata:
return metadata.Maintainer
case pkg.RpmdbMetadata:
case pkg.RpmMetadata:
return metadata.Vendor
case pkg.DpkgMetadata:
return metadata.Maintainer
@ -22,7 +22,7 @@ func decodePublisher(publisher string, metadata interface{}) {
switch meta := metadata.(type) {
case *pkg.ApkMetadata:
meta.Maintainer = publisher
case *pkg.RpmdbMetadata:
case *pkg.RpmMetadata:
meta.Vendor = publisher
case *pkg.DpkgMetadata:
meta.Maintainer = publisher

View file

@ -31,7 +31,7 @@ func Test_encodePublisher(t *testing.T) {
{
name: "from rpm",
input: pkg.Package{
Metadata: pkg.RpmdbMetadata{
Metadata: pkg.RpmMetadata{
Vendor: "auth",
},
},

View file

@ -80,7 +80,7 @@ func Test_Originator(t *testing.T) {
{
name: "from rpm",
input: pkg.Package{
Metadata: pkg.RpmdbMetadata{
Metadata: pkg.RpmMetadata{
Vendor: "auth",
},
},

View file

@ -28,7 +28,7 @@ func Originator(p pkg.Package) string {
if len(metadata.Authors) > 0 {
author = metadata.Authors[0]
}
case pkg.RpmdbMetadata:
case pkg.RpmMetadata:
return "Organization: " + metadata.Vendor
case pkg.DpkgMetadata:
author = metadata.Maintainer

View file

@ -326,7 +326,7 @@ func extractMetadata(p *spdx.Package2_2, info pkgInfo) (pkg.MetadataType, interf
if license == "" {
license = p.PackageLicenseConcluded
}
return pkg.RpmdbMetadataType, pkg.RpmdbMetadata{
return pkg.RpmMetadataType, pkg.RpmMetadata{
Name: p.PackageName,
Version: p.PackageVersion,
Epoch: epoch,

View file

@ -176,8 +176,8 @@ func Test_extractMetadata(t *testing.T) {
},
},
},
metaType: pkg.RpmdbMetadataType,
meta: pkg.RpmdbMetadata{
metaType: pkg.RpmMetadataType,
meta: pkg.RpmMetadata{
Name: "SomeRpmPkg",
Version: "13.2.79",
Epoch: &oneTwoThreeFour,

View file

@ -74,7 +74,8 @@ func (p *Package) UnmarshalJSON(b []byte) error {
//nolint:funlen,gocognit,gocyclo
func unpackMetadata(p *Package, unpacker packageMetadataUnpacker) error {
p.MetadataType = unpacker.MetadataType
p.MetadataType = pkg.CleanMetadataType(unpacker.MetadataType)
switch p.MetadataType {
case "":
// there is no metadata, skip
@ -91,8 +92,8 @@ func unpackMetadata(p *Package, unpacker packageMetadataUnpacker) error {
return err
}
p.Metadata = payload
case pkg.RpmdbMetadataType:
var payload pkg.RpmdbMetadata
case pkg.RpmMetadataType:
var payload pkg.RpmMetadata
if err := json.Unmarshal(unpacker.Metadata, &payload); err != nil {
return err
}

View file

@ -133,6 +133,45 @@ func Test_unpackMetadata(t *testing.T) {
"purl": "pkg:golang/gopkg.in/square/go-jose.v2@v2.6.0"
}`),
},
{
name: "can handle RpmdbMetadata",
metadataType: pkg.RpmMetadataType,
packageData: []byte(`{
"id": "4ac699c3b8fe1835",
"name": "acl",
"version": "2.2.53-1.el8",
"type": "rpm",
"foundBy": "rpm-db-cataloger",
"locations": [
{
"path": "/var/lib/rpm/Packages",
"layerID": "sha256:74ddd0ec08fa43d09f32636ba91a0a3053b02cb4627c35051aff89f853606b59"
}
],
"licenses": [
"GPLv2+"
],
"language": "",
"cpes": [
"cpe:2.3:a:centos:acl:2.2.53-1.el8:*:*:*:*:*:*:*",
"cpe:2.3:a:acl:acl:2.2.53-1.el8:*:*:*:*:*:*:*"
],
"purl": "pkg:rpm/centos/acl@2.2.53-1.el8?arch=x86_64&upstream=acl-2.2.53-1.el8.src.rpm&distro=centos-8",
"metadataType": "RpmdbMetadata",
"metadata": {
"name": "acl",
"version": "2.2.53",
"epoch": null,
"architecture": "x86_64",
"release": "1.el8",
"sourceRpm": "acl-2.2.53-1.el8.src.rpm",
"size": 205740,
"license": "GPLv2+",
"vendor": "CentOS",
"modularityLabel": ""
}
}`),
},
{
name: "bad metadata type is an error",
metadataType: "BOGOSITY",

View file

@ -34,7 +34,7 @@ type artifactMetadataContainer struct {
Java pkg.JavaMetadata
Npm pkg.NpmPackageJSONMetadata
Python pkg.PythonPackageMetadata
Rpm pkg.RpmdbMetadata
Rpm pkg.RpmMetadata
Cargo pkg.CargoPackageMetadata
Go pkg.GolangBinMetadata
Php pkg.PhpComposerJSONMetadata

View file

@ -24,7 +24,7 @@ import (
"github.com/anchore/syft/syft/pkg/cataloger/php"
"github.com/anchore/syft/syft/pkg/cataloger/portage"
"github.com/anchore/syft/syft/pkg/cataloger/python"
"github.com/anchore/syft/syft/pkg/cataloger/rpmdb"
"github.com/anchore/syft/syft/pkg/cataloger/rpm"
"github.com/anchore/syft/syft/pkg/cataloger/ruby"
"github.com/anchore/syft/syft/pkg/cataloger/rust"
"github.com/anchore/syft/syft/pkg/cataloger/swift"
@ -52,7 +52,7 @@ func ImageCatalogers(cfg Config) []Cataloger {
php.NewPHPComposerInstalledCataloger(),
javascript.NewJavascriptPackageCataloger(),
deb.NewDpkgdbCataloger(),
rpmdb.NewRpmdbCataloger(),
rpm.NewRpmdbCataloger(),
java.NewJavaCataloger(cfg.Java()),
apkdb.NewApkdbCataloger(),
golang.NewGoModuleBinaryCataloger(),
@ -71,7 +71,8 @@ func DirectoryCatalogers(cfg Config) []Cataloger {
php.NewPHPComposerLockCataloger(),
javascript.NewJavascriptLockCataloger(),
deb.NewDpkgdbCataloger(),
rpmdb.NewRpmdbCataloger(),
rpm.NewRpmdbCataloger(),
rpm.NewFileCataloger(),
java.NewJavaCataloger(cfg.Java()),
java.NewJavaPomCataloger(),
apkdb.NewApkdbCataloger(),
@ -98,7 +99,8 @@ func AllCatalogers(cfg Config) []Cataloger {
javascript.NewJavascriptLockCataloger(),
javascript.NewJavascriptPackageCataloger(),
deb.NewDpkgdbCataloger(),
rpmdb.NewRpmdbCataloger(),
rpm.NewRpmdbCataloger(),
rpm.NewFileCataloger(),
java.NewJavaCataloger(cfg.Java()),
java.NewJavaPomCataloger(),
apkdb.NewApkdbCataloger(),

View file

@ -89,7 +89,7 @@ func candidateVendors(p pkg.Package) []string {
}
switch p.MetadataType {
case pkg.RpmdbMetadataType:
case pkg.RpmMetadataType:
vendors.union(candidateVendorsForRPM(p))
case pkg.GemMetadataType:
vendors.union(candidateVendorsForRuby(p))

View file

@ -316,8 +316,8 @@ func TestGeneratePackageCPEs(t *testing.T) {
Version: "3.2",
FoundBy: "some-analyzer",
Type: pkg.RpmPkg,
MetadataType: pkg.RpmdbMetadataType,
Metadata: pkg.RpmdbMetadata{
MetadataType: pkg.RpmMetadataType,
Metadata: pkg.RpmMetadata{
Vendor: "some-vendor",
},
},
@ -334,8 +334,8 @@ func TestGeneratePackageCPEs(t *testing.T) {
Version: "1:3.2",
FoundBy: "some-analyzer",
Type: pkg.RpmPkg,
MetadataType: pkg.RpmdbMetadataType,
Metadata: pkg.RpmdbMetadata{
MetadataType: pkg.RpmMetadataType,
Metadata: pkg.RpmMetadata{
Vendor: "some-vendor",
},
},

View file

@ -3,7 +3,7 @@ package cpe
import "github.com/anchore/syft/syft/pkg"
func candidateVendorsForRPM(p pkg.Package) fieldCandidateSet {
metadata, ok := p.Metadata.(pkg.RpmdbMetadata)
metadata, ok := p.Metadata.(pkg.RpmMetadata)
if !ok {
return nil
}

View file

@ -1,34 +1,34 @@
/*
Package rpmdb provides a concrete Cataloger implementation for RPM "Package" DB files.
Package rpm provides a concrete DBCataloger implementation for RPM "Package" DB files
and a FileCataloger for RPM files.
*/
package rpmdb
package rpm
import (
"fmt"
"github.com/anchore/syft/internal"
"github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/source"
)
const catalogerName = "rpmdb-cataloger"
const dbCatalogerName = "rpm-db-cataloger"
type Cataloger struct{}
type DBCataloger struct{}
// NewRpmdbCataloger returns a new RPM DB cataloger object.
func NewRpmdbCataloger() *Cataloger {
return &Cataloger{}
func NewRpmdbCataloger() *DBCataloger {
return &DBCataloger{}
}
// Name returns a string that uniquely describes a cataloger
func (c *Cataloger) Name() string {
return catalogerName
func (c *DBCataloger) Name() string {
return dbCatalogerName
}
// Catalog is given an object to resolve file references and content, this function returns any discovered Packages after analyzing rpm db installation.
func (c *Cataloger) Catalog(resolver source.FileResolver) ([]pkg.Package, []artifact.Relationship, error) {
func (c *DBCataloger) Catalog(resolver source.FileResolver) ([]pkg.Package, []artifact.Relationship, error) {
fileMatches, err := resolver.FilesByGlob(pkg.RpmDBGlob)
if err != nil {
return nil, nil, fmt.Errorf("failed to find rpmdb's by glob: %w", err)

View file

@ -0,0 +1,136 @@
package rpm
import (
"fmt"
"strconv"
"strings"
rpmdb "github.com/knqyf263/go-rpmdb/pkg"
"github.com/sassoftware/go-rpmutils"
"github.com/anchore/syft/internal"
"github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/source"
)
type FileCataloger struct{}
// NewFileCataloger returns a new RPM file cataloger object.
func NewFileCataloger() *FileCataloger {
return &FileCataloger{}
}
// Name returns a string that uniquely describes a cataloger
func (c *FileCataloger) Name() string {
return "rpm-file-cataloger"
}
// Catalog is given an object to resolve file references and content, this function returns any discovered Packages after analyzing rpm files
func (c *FileCataloger) Catalog(resolver source.FileResolver) ([]pkg.Package, []artifact.Relationship, error) {
fileMatches, err := resolver.FilesByGlob("**/*.rpm")
if err != nil {
return nil, nil, fmt.Errorf("failed to find rpm files's by glob: %w", err)
}
var pkgs []pkg.Package
for _, location := range fileMatches {
contentReader, err := resolver.FileContentsByLocation(location)
if err != nil {
return nil, nil, err
}
rpm, err := rpmutils.ReadRpm(contentReader)
if err != nil {
return nil, nil, err
}
nevra, err := rpm.Header.GetNEVRA()
if err != nil {
return nil, nil, err
}
licenses, _ := rpm.Header.GetStrings(rpmutils.LICENSE)
sourceRpm, _ := rpm.Header.GetString(rpmutils.SOURCERPM)
vendor, _ := rpm.Header.GetString(rpmutils.VENDOR)
digestAlgorithm := getDigestAlgorithm(rpm.Header)
size, _ := rpm.Header.InstalledSize()
files, _ := rpm.Header.GetFiles()
p := pkg.Package{
Name: nevra.Name,
Version: nevra.Version,
FoundBy: c.Name(),
Licenses: licenses,
Locations: source.NewLocationSet(location),
Type: pkg.RpmPkg,
MetadataType: pkg.RpmMetadataType,
Metadata: pkg.RpmMetadata{
Name: nevra.Name,
Version: nevra.Version,
Epoch: parseEpoch(nevra.Epoch),
Arch: nevra.Arch,
Release: nevra.Release,
SourceRpm: sourceRpm,
Vendor: vendor,
License: strings.Join(licenses, " AND "),
Size: int(size),
Files: mapFiles(files, digestAlgorithm),
},
}
p.SetID()
pkgs = append(pkgs, p)
internal.CloseAndLogError(contentReader, location.VirtualPath)
if err != nil {
return nil, nil, fmt.Errorf("unable to catalog rpm file=%+v: %w", location.RealPath, err)
}
}
return pkgs, nil, nil
}
func getDigestAlgorithm(header *rpmutils.RpmHeader) string {
digestAlgorithm, _ := header.GetString(rpmutils.FILEDIGESTALGO)
if digestAlgorithm != "" {
return digestAlgorithm
}
digestAlgorithms, _ := header.GetUint32s(rpmutils.FILEDIGESTALGO)
if len(digestAlgorithms) > 0 {
digestAlgo := int(digestAlgorithms[0])
return rpmutils.GetFileAlgoName(digestAlgo)
}
return ""
}
func mapFiles(files []rpmutils.FileInfo, digestAlgorithm string) []pkg.RpmdbFileRecord {
var out []pkg.RpmdbFileRecord
for _, f := range files {
digest := file.Digest{}
if f.Digest() != "" {
digest = file.Digest{
Algorithm: digestAlgorithm,
Value: f.Digest(),
}
}
out = append(out, pkg.RpmdbFileRecord{
Path: f.Name(),
Mode: pkg.RpmdbFileMode(f.Mode()),
Size: int(f.Size()),
Digest: digest,
UserName: f.UserName(),
GroupName: f.GroupName(),
Flags: rpmdb.FileFlags(f.Flags()).String(),
})
}
return out
}
func parseEpoch(epoch string) *int {
i, err := strconv.Atoi(epoch)
if err != nil {
return nil
}
return &i
}

View file

@ -0,0 +1,106 @@
package rpm
import (
"testing"
"github.com/go-test/deep"
"github.com/stretchr/testify/require"
"github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/source"
)
func TestParseRpmFiles(t *testing.T) {
tests := []struct {
fixture string
expected map[string]pkg.Package
}{
{
fixture: "test-fixtures/rpms",
expected: map[string]pkg.Package{
"abc": {
Name: "abc",
Version: "1.01",
Locations: source.NewLocationSet(),
FoundBy: "rpm-file-cataloger",
Type: pkg.RpmPkg,
MetadataType: pkg.RpmMetadataType,
Licenses: []string{"MIT"},
Metadata: pkg.RpmMetadata{
Name: "abc",
Epoch: intRef(0),
Arch: "x86_64",
Release: "9.hg20160905.el7",
Version: "1.01",
SourceRpm: "abc-1.01-9.hg20160905.el7.src.rpm",
Size: 17396,
License: "MIT",
Vendor: "Fedora Project",
Files: []pkg.RpmdbFileRecord{
{"/usr/bin/abc", 33261, 7120, file.Digest{"sha256", "8f8495a65c66762b60afa0c3949d81b275ca6fa0601696caba5af762f455d0b9"}, "root", "root", ""},
{"/usr/share/doc/abc-1.01", 16877, 4096, file.Digest{}, "root", "root", ""},
{"/usr/share/doc/abc-1.01/readme.md", 33188, 4984, file.Digest{"sha256", "808af8a28391e96ca0d91086789488dda3724fe7c8b2859efd464fb04b94b2d4"}, "root", "root", "d"},
{"/usr/share/doc/abc-1.01/readmeaig", 33188, 3324, file.Digest{"sha256", "530ec6175cf7fbeb7b595cbe7a50994429c4e62cae6666fb3a1d5745f3127b19"}, "root", "root", "d"},
{"/usr/share/man/man1/abc.1.gz", 33188, 1968, file.Digest{"sha256", "cf2cfe25b29087e60ffd5f31f974a0762172fc2f009704951f12ff750ea77ed6"}, "root", "root", "d"},
},
},
},
"zork": {
Name: "zork",
Version: "1.0.3",
Locations: source.NewLocationSet(),
FoundBy: "rpm-file-cataloger",
Type: pkg.RpmPkg,
MetadataType: pkg.RpmMetadataType,
Licenses: []string{"Public Domain"},
Metadata: pkg.RpmMetadata{
Name: "zork",
Epoch: intRef(0),
Arch: "x86_64",
Release: "1.el7",
Version: "1.0.3",
SourceRpm: "zork-1.0.3-1.el7.src.rpm",
Size: 262367,
License: "Public Domain",
Vendor: "Fedora Project",
Files: []pkg.RpmdbFileRecord{
{"/usr/bin/zork", 33261, 115440, file.Digest{"sha256", "31b2ffc20b676a8fff795a45308f584273b9c47e8f7e196b4f36220b2734b472"}, "root", "root", ""},
{"/usr/share/doc/zork-1.0.3", 16877, 38, file.Digest{}, "root", "root", ""},
{"/usr/share/doc/zork-1.0.3/README.md", 33188, 5123, file.Digest{"sha256", "0013d67610a80c9f62d151a952f18d520b15b4c505b3ec2af34b96ab824654a4"}, "root", "root", "d"},
{"/usr/share/doc/zork-1.0.3/history", 33188, 4816, file.Digest{"sha256", "6949044a65adefca6ac0132c18cfccc4ba8fdaec948424b6ccb60afd8a6ac82f"}, "root", "root", "d"},
{"/usr/share/licenses/zork-1.0.3", 16877, 24, file.Digest{}, "root", "root", ""},
{"/usr/share/licenses/zork-1.0.3/readme.txt", 33188, 146, file.Digest{"sha256", "9d6f7500555a2ecc3cb289dcca1e37fb96894dab1e4ba692b4d36fd6c3bdf939"}, "root", "root", "l"},
{"/usr/share/man/man6/dungeon.6.gz", 33188, 3800, file.Digest{"sha256", "9b065d6a6f65b4d2d038fcca0af47a38e8723c32008d08659739ac34abe018da"}, "root", "root", "d"},
{"/usr/share/man/man6/zork.6.gz", 33188, 34, file.Digest{"sha256", "18fbcb598bc40a25befe26256e29366984d2288dd154f877b8ac5fc138dd0884"}, "root", "root", "d"},
{"/usr/share/zork/dtextc.dat", 33188, 133008, file.Digest{"sha256", "25ca42857c2b32054916d9258152293ead644023d5e03bec039ea92014e2ef91"}, "root", "root", ""},
},
},
},
},
},
}
for _, test := range tests {
t.Run(test.fixture, func(t *testing.T) {
s, err := source.NewFromDirectory(test.fixture)
require.NoError(t, err)
r, err := s.FileResolver(source.SquashedScope)
require.NoError(t, err)
packages, _, err := NewFileCataloger().Catalog(r)
require.NoError(t, err)
for _, a := range packages {
e := test.expected[a.Name]
diffs := deep.Equal(e, a)
if len(diffs) > 0 {
for _, d := range diffs {
t.Errorf("diff: %+v", d)
}
}
}
})
}
}

View file

@ -1,4 +1,4 @@
package rpmdb
package rpm
import (
"fmt"
@ -68,7 +68,7 @@ func newPkg(resolver source.FilePathResolver, dbLocation source.Location, entry
return nil, err
}
metadata := pkg.RpmdbMetadata{
metadata := pkg.RpmMetadata{
Name: entry.Name,
Version: entry.Version,
Epoch: entry.Epoch,
@ -86,9 +86,9 @@ func newPkg(resolver source.FilePathResolver, dbLocation source.Location, entry
Name: entry.Name,
Version: toELVersion(metadata),
Locations: source.NewLocationSet(dbLocation),
FoundBy: catalogerName,
FoundBy: dbCatalogerName,
Type: pkg.RpmPkg,
MetadataType: pkg.RpmdbMetadataType,
MetadataType: pkg.RpmMetadataType,
Metadata: metadata,
}
@ -106,7 +106,7 @@ func newPkg(resolver source.FilePathResolver, dbLocation source.Location, entry
// version string, containing epoch (optional), version, and release information. Epoch is an optional field and can be
// assumed to be 0 when not provided for comparison purposes, however, if the underlying RPM DB entry does not have
// an epoch specified it would be slightly disingenuous to display a value of 0.
func toELVersion(metadata pkg.RpmdbMetadata) string {
func toELVersion(metadata pkg.RpmMetadata) string {
if metadata.Epoch != nil {
return fmt.Sprintf("%d:%s-%s", *metadata.Epoch, metadata.Version, metadata.Release)
}

View file

@ -1,4 +1,4 @@
package rpmdb
package rpm
import (
"fmt"
@ -72,11 +72,11 @@ func TestParseRpmDB(t *testing.T) {
Name: "dive",
Version: "0.9.2-1",
Locations: source.NewLocationSet(dbLocation),
FoundBy: catalogerName,
FoundBy: dbCatalogerName,
Type: pkg.RpmPkg,
MetadataType: pkg.RpmdbMetadataType,
MetadataType: pkg.RpmMetadataType,
Licenses: []string{"MIT"},
Metadata: pkg.RpmdbMetadata{
Metadata: pkg.RpmMetadata{
Name: "dive",
Epoch: nil,
Arch: "x86_64",
@ -100,11 +100,11 @@ func TestParseRpmDB(t *testing.T) {
Name: "dive",
Version: "0.9.2-1",
Locations: source.NewLocationSet(dbLocation),
FoundBy: catalogerName,
FoundBy: dbCatalogerName,
Type: pkg.RpmPkg,
MetadataType: pkg.RpmdbMetadataType,
MetadataType: pkg.RpmMetadataType,
Licenses: []string{"MIT"},
Metadata: pkg.RpmdbMetadata{
Metadata: pkg.RpmMetadata{
Name: "dive",
Epoch: nil,
Arch: "x86_64",
@ -170,12 +170,12 @@ func TestParseRpmDB(t *testing.T) {
func TestToElVersion(t *testing.T) {
tests := []struct {
name string
entry pkg.RpmdbMetadata
entry pkg.RpmMetadata
expected string
}{
{
name: "no epoch",
entry: pkg.RpmdbMetadata{
entry: pkg.RpmMetadata{
Version: "1.2.3-4",
Release: "el7",
Arch: "x86-64",
@ -184,7 +184,7 @@ func TestToElVersion(t *testing.T) {
},
{
name: "with 0 epoch",
entry: pkg.RpmdbMetadata{
entry: pkg.RpmMetadata{
Version: "1.2.3-4",
Release: "el7",
Arch: "x86-64",
@ -194,7 +194,7 @@ func TestToElVersion(t *testing.T) {
},
{
name: "with non-zero epoch",
entry: pkg.RpmdbMetadata{
entry: pkg.RpmMetadata{
Version: "1.2.3-4",
Release: "el7",
Arch: "x86-64",

View file

@ -1,4 +1,4 @@
package rpmdb
package rpm
import (
"bufio"
@ -43,7 +43,7 @@ func parseRpmManifestEntry(entry string, location source.Location) (*pkg.Package
size = converted
}
metadata := pkg.RpmdbMetadata{
metadata := pkg.RpmMetadata{
Name: parts[0],
Version: version,
Epoch: epoch,
@ -58,9 +58,9 @@ func parseRpmManifestEntry(entry string, location source.Location) (*pkg.Package
Name: parts[0],
Version: toELVersion(metadata),
Locations: source.NewLocationSet(location),
FoundBy: catalogerName,
FoundBy: dbCatalogerName,
Type: pkg.RpmPkg,
MetadataType: pkg.RpmdbMetadataType,
MetadataType: pkg.RpmMetadataType,
Metadata: metadata,
}

View file

@ -1,4 +1,4 @@
package rpmdb
package rpm
import (
"os"
@ -18,10 +18,10 @@ func TestParseRpmManifest(t *testing.T) {
Name: "mariner-release",
Version: "2.0-12.cm2",
Locations: source.NewLocationSet(location),
FoundBy: catalogerName,
FoundBy: dbCatalogerName,
Type: pkg.RpmPkg,
MetadataType: pkg.RpmdbMetadataType,
Metadata: pkg.RpmdbMetadata{
MetadataType: pkg.RpmMetadataType,
Metadata: pkg.RpmMetadata{
Name: "mariner-release",
Epoch: nil,
Arch: "noarch",
@ -36,10 +36,10 @@ func TestParseRpmManifest(t *testing.T) {
Name: "filesystem",
Version: "1.1-9.cm2",
Locations: source.NewLocationSet(location),
FoundBy: catalogerName,
FoundBy: dbCatalogerName,
Type: pkg.RpmPkg,
MetadataType: pkg.RpmdbMetadataType,
Metadata: pkg.RpmdbMetadata{
MetadataType: pkg.RpmMetadataType,
Metadata: pkg.RpmMetadata{
Name: "filesystem",
Epoch: nil,
Arch: "x86_64",
@ -54,10 +54,10 @@ func TestParseRpmManifest(t *testing.T) {
Name: "glibc",
Version: "2.35-2.cm2",
Locations: source.NewLocationSet(location),
FoundBy: catalogerName,
FoundBy: dbCatalogerName,
Type: pkg.RpmPkg,
MetadataType: pkg.RpmdbMetadataType,
Metadata: pkg.RpmdbMetadata{
MetadataType: pkg.RpmMetadataType,
Metadata: pkg.RpmMetadata{
Name: "glibc",
Epoch: nil,
Arch: "x86_64",
@ -72,10 +72,10 @@ func TestParseRpmManifest(t *testing.T) {
Name: "openssl-libs",
Version: "1.1.1k-15.cm2",
Locations: source.NewLocationSet(location),
FoundBy: catalogerName,
FoundBy: dbCatalogerName,
Type: pkg.RpmPkg,
MetadataType: pkg.RpmdbMetadataType,
Metadata: pkg.RpmdbMetadata{
MetadataType: pkg.RpmMetadataType,
Metadata: pkg.RpmMetadata{
Name: "openssl-libs",
Epoch: nil,
Arch: "x86_64",

View file

@ -0,0 +1,2 @@
/rpms/*
*.fingerprint

View file

@ -0,0 +1,21 @@
RPMSDIR=rpms
ifndef RPMSDIR
$(error RPMSDIR is not set)
endif
all: rpms
clean:
rm -rf $(RPMSDIR)
rpms:
mkdir -p $(RPMSDIR)
cd $(RPMSDIR) && curl https://dl.fedoraproject.org/pub/epel/7/x86_64/Packages/a/abc-1.01-9.hg20160905.el7.x86_64.rpm -O
cd $(RPMSDIR) && curl https://dl.fedoraproject.org/pub/epel/7/x86_64/Packages/z/zork-1.0.3-1.el7.x86_64.rpm -O
# we need a way to determine if CI should bust the test cache based on the source material
.PHONY: $(RPMSDIR).fingerprint
$(RPMSDIR).fingerprint:
find Makefile -type f -exec sha256sum {} \; | sort | tee /dev/stderr | tee $(RPMSDIR).fingerprint
sha256sum $(RPMSDIR).fingerprint

View file

@ -17,7 +17,7 @@ const (
GemMetadataType MetadataType = "GemMetadata"
JavaMetadataType MetadataType = "JavaMetadata"
NpmPackageJSONMetadataType MetadataType = "NpmPackageJsonMetadata"
RpmdbMetadataType MetadataType = "RpmdbMetadata"
RpmMetadataType MetadataType = "RpmMetadata"
DartPubMetadataType MetadataType = "DartPubMetadata"
DotnetDepsMetadataType MetadataType = "DotnetDepsMetadata"
PythonPackageMetadataType MetadataType = "PythonPackageMetadata"
@ -38,7 +38,7 @@ var AllMetadataTypes = []MetadataType{
GemMetadataType,
JavaMetadataType,
NpmPackageJSONMetadataType,
RpmdbMetadataType,
RpmMetadataType,
DartPubMetadataType,
DotnetDepsMetadataType,
PythonPackageMetadataType,
@ -59,7 +59,7 @@ var MetadataTypeByName = map[MetadataType]reflect.Type{
GemMetadataType: reflect.TypeOf(GemMetadata{}),
JavaMetadataType: reflect.TypeOf(JavaMetadata{}),
NpmPackageJSONMetadataType: reflect.TypeOf(NpmPackageJSONMetadata{}),
RpmdbMetadataType: reflect.TypeOf(RpmdbMetadata{}),
RpmMetadataType: reflect.TypeOf(RpmMetadata{}),
DartPubMetadataType: reflect.TypeOf(DartPubMetadata{}),
DotnetDepsMetadataType: reflect.TypeOf(DotnetDepsMetadata{}),
PythonPackageMetadataType: reflect.TypeOf(PythonPackageMetadata{}),
@ -72,3 +72,10 @@ var MetadataTypeByName = map[MetadataType]reflect.Type{
PortageMetadataType: reflect.TypeOf(PortageMetadata{}),
HackageMetadataType: reflect.TypeOf(HackageMetadata{}),
}
func CleanMetadataType(typ MetadataType) MetadataType {
if typ == "RpmdbMetadata" {
return RpmMetadataType
}
return typ
}

View file

@ -23,8 +23,8 @@ func TestOwnershipByFilesRelationship(t *testing.T) {
source.NewVirtualLocation("/b/path", "/bee/path"),
),
Type: RpmPkg,
MetadataType: RpmdbMetadataType,
Metadata: RpmdbMetadata{
MetadataType: RpmMetadataType,
Metadata: RpmMetadata{
Files: []RpmdbFileRecord{
{Path: "/owning/path/1"},
{Path: "/owning/path/2"},
@ -66,8 +66,8 @@ func TestOwnershipByFilesRelationship(t *testing.T) {
source.NewVirtualLocation("/b/path", "/bee/path"),
),
Type: RpmPkg,
MetadataType: RpmdbMetadataType,
Metadata: RpmdbMetadata{
MetadataType: RpmMetadataType,
Metadata: RpmMetadata{
Files: []RpmdbFileRecord{
{Path: "/owning/path/1"},
{Path: "/owning/path/2"},
@ -108,8 +108,8 @@ func TestOwnershipByFilesRelationship(t *testing.T) {
source.NewVirtualLocation("/b/path", "/bee/path"),
),
Type: RpmPkg,
MetadataType: RpmdbMetadataType,
Metadata: RpmdbMetadata{
MetadataType: RpmMetadataType,
Metadata: RpmMetadata{
Files: []RpmdbFileRecord{
{Path: "/owning/path/1"},
{Path: "/owning/path/2"},

View file

@ -20,12 +20,12 @@ const RpmDBGlob = "**/var/lib/rpm/{Packages,Packages.db,rpmdb.sqlite}"
const RpmManifestGlob = "**/var/lib/rpmmanifest/container-manifest-2"
var (
_ FileOwner = (*RpmdbMetadata)(nil)
_ urlIdentifier = (*RpmdbMetadata)(nil)
_ FileOwner = (*RpmMetadata)(nil)
_ urlIdentifier = (*RpmMetadata)(nil)
)
// RpmdbMetadata represents all captured data for a RPM DB package entry.
type RpmdbMetadata struct {
// RpmMetadata represents all captured data for a RPM DB package entry.
type RpmMetadata struct {
Name string `json:"name"`
Version string `json:"version"`
Epoch *int `json:"epoch" cyclonedx:"epoch" jsonschema:"nullable"`
@ -54,7 +54,7 @@ type RpmdbFileRecord struct {
type RpmdbFileMode uint16
// PackageURL returns the PURL for the specific RHEL package (see https://github.com/package-url/purl-spec)
func (m RpmdbMetadata) PackageURL(distro *linux.Release) string {
func (m RpmMetadata) PackageURL(distro *linux.Release) string {
var namespace string
if distro != nil {
namespace = distro.ID
@ -87,7 +87,7 @@ func (m RpmdbMetadata) PackageURL(distro *linux.Release) string {
).ToString()
}
func (m RpmdbMetadata) OwnedFiles() (result []string) {
func (m RpmMetadata) OwnedFiles() (result []string) {
s := strset.New()
for _, f := range m.Files {
if f.Path != "" {

View file

@ -14,7 +14,7 @@ func TestRpmMetadata_pURL(t *testing.T) {
tests := []struct {
name string
distro *linux.Release
metadata RpmdbMetadata
metadata RpmMetadata
expected string
}{
{
@ -23,7 +23,7 @@ func TestRpmMetadata_pURL(t *testing.T) {
ID: "rhel",
VersionID: "8.4",
},
metadata: RpmdbMetadata{
metadata: RpmMetadata{
Name: "p",
Version: "v",
Release: "r",
@ -37,7 +37,7 @@ func TestRpmMetadata_pURL(t *testing.T) {
ID: "centos",
VersionID: "7",
},
metadata: RpmdbMetadata{
metadata: RpmMetadata{
Name: "p",
Version: "v",
Arch: "a",
@ -48,7 +48,7 @@ func TestRpmMetadata_pURL(t *testing.T) {
},
{
name: "missing distro",
metadata: RpmdbMetadata{
metadata: RpmMetadata{
Name: "p",
Version: "v",
Release: "r",
@ -62,7 +62,7 @@ func TestRpmMetadata_pURL(t *testing.T) {
ID: "rhel",
VersionID: "8.4",
},
metadata: RpmdbMetadata{
metadata: RpmMetadata{
Name: "p",
Version: "v",
Release: "r",
@ -86,11 +86,11 @@ func TestRpmMetadata_pURL(t *testing.T) {
func TestRpmMetadata_FileOwner(t *testing.T) {
tests := []struct {
metadata RpmdbMetadata
metadata RpmMetadata
expected []string
}{
{
metadata: RpmdbMetadata{
metadata: RpmMetadata{
Files: []RpmdbFileRecord{
{Path: "/somewhere"},
{Path: "/else"},
@ -102,7 +102,7 @@ func TestRpmMetadata_FileOwner(t *testing.T) {
},
},
{
metadata: RpmdbMetadata{
metadata: RpmMetadata{
Files: []RpmdbFileRecord{
{Path: "/somewhere"},
{Path: ""},

View file

@ -121,7 +121,7 @@ func TestPackageURL(t *testing.T) {
Name: "bad-name",
Version: "bad-v0.1.0",
Type: RpmPkg,
Metadata: RpmdbMetadata{
Metadata: RpmMetadata{
Name: "name",
Version: "0.1.0",
Epoch: intRef(2),

File diff suppressed because one or more lines are too long