diff --git a/libindex/libindex.go b/libindex/libindex.go index b9e8874a1..4f1a2c415 100644 --- a/libindex/libindex.go +++ b/libindex/libindex.go @@ -110,6 +110,7 @@ func New(ctx context.Context, opts *Options, cl *http.Client) (*Libindex, error) opts.Ecosystems = append(opts.Ecosystems, whiteout.NewEcosystem(ctx)) opts.Resolvers = []indexer.Resolver{ &whiteout.Resolver{}, + &rhel.Resolver{}, } if cl == nil { diff --git a/rhel/resolver.go b/rhel/resolver.go new file mode 100644 index 000000000..bf9a04eb3 --- /dev/null +++ b/rhel/resolver.go @@ -0,0 +1,56 @@ +package rhel + +import ( + "context" + + "github.com/quay/zlog" + + "github.com/quay/claircore" + "github.com/quay/claircore/indexer" +) + +var ( + _ indexer.Resolver = (*Resolver)(nil) +) + +type Resolver struct{} + +// Resolve takes a claircore.IndexReport and uses the rpm Files +// to determine if the package originate from an RPM. If the package +// was deemed to have been installed via RPM it isn't included in the +// final report. +func (r *Resolver) Resolve(ctx context.Context, ir *claircore.IndexReport, layers []*claircore.Layer) *claircore.IndexReport { + for pkgID, pkg := range ir.Packages { + isRPMPackage := false + envLoop: + for _, env := range ir.Environments[pkgID] { + if env.RepositoryIDs != nil { + for _, rID := range env.RepositoryIDs { + r := ir.Repositories[rID] + if r.Key == repositoryKey { + isRPMPackage = true + break envLoop + } + } + } + } + if !isRPMPackage { + filesLoop: + for _, fs := range ir.Files { + for _, f := range fs { + if f.Kind == claircore.FileKindRPM && pkg.Filepath == f.Path { + zlog.Debug(ctx). + Str("package name", pkg.Name). + Str("package file", pkg.Filepath). + Str("rpm file", f.Path). + Msg("package determined to have come from RPM, deleting") + delete(ir.Packages, pkgID) + delete(ir.Environments, pkgID) + break filesLoop + } + } + } + } + } + return ir +} diff --git a/rhel/resolver_test.go b/rhel/resolver_test.go new file mode 100644 index 000000000..8504fdf4f --- /dev/null +++ b/rhel/resolver_test.go @@ -0,0 +1,334 @@ +package rhel + +import ( + "context" + "strings" + "testing" + + "github.com/quay/zlog" + + "github.com/quay/claircore" + "github.com/quay/claircore/pkg/cpe" +) + +var resolverTestcases = []struct { + name string + expectedPackages int + indexReport *claircore.IndexReport +}{ + { + name: "No files", + expectedPackages: 2, + indexReport: &claircore.IndexReport{ + Hash: claircore.MustParseDigest(`sha256:` + strings.Repeat(`a`, 64)), + Packages: map[string]*claircore.Package{ + "123": { + ID: "123", + Name: "package A", + Version: "v1.0.0", + Source: &claircore.Package{ + ID: "122", + Name: "package B source", + Kind: claircore.SOURCE, + Version: "v1.0.0", + }, + Kind: claircore.BINARY, + }, + "456": { + ID: "456", + Name: "package B", + Version: "v2.0.0", + Kind: claircore.BINARY, + }, + }, + Environments: map[string][]*claircore.Environment{ + "123": { + { + PackageDB: "bdb:var/lib/rpm", + IntroducedIn: claircore.MustParseDigest(`sha256:` + strings.Repeat(`b`, 64)), + RepositoryIDs: []string{"11"}, + DistributionID: "13", + }, + }, + "456": { + { + PackageDB: "maven:opt/couchbase/lib/cbas/repo/eventstream-1.0.1.jar", + IntroducedIn: claircore.MustParseDigest(`sha256:` + strings.Repeat(`c`, 64)), + RepositoryIDs: []string{"12"}, + }, + }, + }, + Repositories: map[string]*claircore.Repository{ + "11": { + ID: "11", + Name: "cpe:/a:redhat:rhel_eus:8.6::appstream", + Key: repositoryKey, + CPE: cpe.MustUnbind("cpe:2.3:a:redhat:rhel_eus:8.6:*:appstream:*:*:*:*:*"), + }, + "12": { + ID: "12", + Name: "maven", + URI: "https://repo1.maven.apache.org/maven2", + }, + }, + Distributions: map[string]*claircore.Distribution{ + "13": { + ID: "13", + DID: "rhel", + Name: "Red Hat Enterprise Linux Server", + Version: "7", + VersionID: "7", + CPE: cpe.MustUnbind("cpe:2.3:o:redhat:enterprise_linux:7:*:*:*:*:*:*:*"), + PrettyName: "Red Hat Enterprise Linux Server 7", + }, + }, + Success: true, + }, + }, + { + name: "Non-matching files", + expectedPackages: 2, + indexReport: &claircore.IndexReport{ + Hash: claircore.MustParseDigest(`sha256:` + strings.Repeat(`a`, 64)), + Packages: map[string]*claircore.Package{ + "123": { + ID: "123", + Name: "package A", + Version: "v1.0.0", + Source: &claircore.Package{ + ID: "122", + Name: "package B source", + Kind: claircore.SOURCE, + Version: "v1.0.0", + }, + Kind: claircore.BINARY, + }, + "456": { + ID: "456", + Name: "package B", + Version: "v2.0.0", + Kind: claircore.BINARY, + Filepath: "some/non-rpm-filepath.java", + }, + }, + Files: map[string][]claircore.File{ + "111": { + {Kind: claircore.FileKindRPM, Path: "some/rpm/filepath/one.java"}, + {Kind: claircore.FileKindRPM, Path: "some/rpm/filepath/two.java"}, + {Kind: claircore.FileKindRPM, Path: "an/actual/rpm/filepath.java"}, + }, + }, + Environments: map[string][]*claircore.Environment{ + "123": { + { + PackageDB: "bdb:var/lib/rpm", + IntroducedIn: claircore.MustParseDigest(`sha256:` + strings.Repeat(`b`, 64)), + RepositoryIDs: []string{"11"}, + DistributionID: "13", + }, + }, + "456": { + { + PackageDB: "maven:opt/couchbase/lib/cbas/repo/eventstream-1.0.1.jar", + IntroducedIn: claircore.MustParseDigest(`sha256:` + strings.Repeat(`c`, 64)), + RepositoryIDs: []string{"12"}, + }, + }, + }, + Repositories: map[string]*claircore.Repository{ + "11": { + ID: "11", + Name: "cpe:/a:redhat:rhel_eus:8.6::appstream", + Key: repositoryKey, + CPE: cpe.MustUnbind("cpe:2.3:a:redhat:rhel_eus:8.6:*:appstream:*:*:*:*:*"), + }, + "12": { + ID: "12", + Name: "maven", + URI: "https://repo1.maven.apache.org/maven2", + }, + }, + Distributions: map[string]*claircore.Distribution{ + "13": { + ID: "13", + DID: "rhel", + Name: "Red Hat Enterprise Linux Server", + Version: "7", + VersionID: "7", + CPE: cpe.MustUnbind("cpe:2.3:o:redhat:enterprise_linux:7:*:*:*:*:*:*:*"), + PrettyName: "Red Hat Enterprise Linux Server 7", + }, + }, + Success: true, + }, + }, + { + name: "an RPM and a native JAVA package", + expectedPackages: 1, + indexReport: &claircore.IndexReport{ + Hash: claircore.MustParseDigest(`sha256:` + strings.Repeat(`a`, 64)), + Packages: map[string]*claircore.Package{ + "123": { + ID: "123", + Name: "rpm java package A", + Version: "v2.0.0-1-1", + Source: &claircore.Package{ + ID: "122", + Name: "rpm java package A source", + Kind: claircore.SOURCE, + Version: "v2.0.0-1-1", + }, + Kind: claircore.BINARY, + Filepath: "some/rpm/filepath.rpm", + }, + "456": { + ID: "456", + Name: "java package A", + Version: "v2.0.0", + Kind: claircore.BINARY, + Filepath: "an/actual/rpm/filepath.java", + }, + }, + Files: map[string][]claircore.File{ + "111": { + {Kind: claircore.FileKindRPM, Path: "some/rpm/filepath/one.java"}, + {Kind: claircore.FileKindRPM, Path: "some/rpm/filepath/two.java"}, + {Kind: claircore.FileKindRPM, Path: "an/actual/rpm/filepath.java"}, + }, + }, + Environments: map[string][]*claircore.Environment{ + "123": { + { + PackageDB: "bdb:var/lib/rpm", + IntroducedIn: claircore.MustParseDigest(`sha256:` + strings.Repeat(`b`, 64)), + RepositoryIDs: []string{"11"}, + DistributionID: "13", + }, + }, + "456": { + { + PackageDB: "maven:opt/couchbase/lib/cbas/repo/eventstream-1.0.1.jar", + IntroducedIn: claircore.MustParseDigest(`sha256:` + strings.Repeat(`c`, 64)), + RepositoryIDs: []string{"12"}, + }, + }, + }, + Repositories: map[string]*claircore.Repository{ + "11": { + ID: "11", + Name: "cpe:/a:redhat:rhel_eus:8.6::appstream", + Key: repositoryKey, + CPE: cpe.MustUnbind("cpe:2.3:a:redhat:rhel_eus:8.6:*:appstream:*:*:*:*:*"), + }, + "12": { + ID: "12", + Name: "maven", + URI: "https://repo1.maven.apache.org/maven2", + }, + }, + Distributions: map[string]*claircore.Distribution{ + "13": { + ID: "13", + DID: "rhel", + Name: "Red Hat Enterprise Linux Server", + Version: "7", + VersionID: "7", + CPE: cpe.MustUnbind("cpe:2.3:o:redhat:enterprise_linux:7:*:*:*:*:*:*:*"), + PrettyName: "Red Hat Enterprise Linux Server 7", + }, + }, + Success: true, + }, + }, + { + name: "an RPM and a Java package but wrong file Kind", + expectedPackages: 2, + indexReport: &claircore.IndexReport{ + Hash: claircore.MustParseDigest(`sha256:` + strings.Repeat(`a`, 64)), + Packages: map[string]*claircore.Package{ + "123": { + ID: "123", + Name: "rpm java package A", + Version: "v2.0.0-1-1", + Source: &claircore.Package{ + ID: "122", + Name: "rpm java package A source", + Kind: claircore.SOURCE, + Version: "v2.0.0-1-1", + }, + Kind: claircore.BINARY, + Filepath: "some/rpm/filepath.rpm", + }, + "456": { + ID: "456", + Name: "java package A", + Version: "v2.0.0", + Kind: claircore.BINARY, + Filepath: "an/actual/rpm/filepath.java", + }, + }, + Files: map[string][]claircore.File{ + "111": { + {Kind: claircore.FileKindRPM, Path: "some/rpm/filepath/one.java"}, + {Kind: claircore.FileKindRPM, Path: "some/rpm/filepath/two.java"}, + {Kind: claircore.FileKindWhiteout, Path: "an/actual/rpm/filepath.java"}, + }, + }, + Environments: map[string][]*claircore.Environment{ + "123": { + { + PackageDB: "bdb:var/lib/rpm", + IntroducedIn: claircore.MustParseDigest(`sha256:` + strings.Repeat(`b`, 64)), + RepositoryIDs: []string{"11"}, + DistributionID: "13", + }, + }, + "456": { + { + PackageDB: "maven:opt/couchbase/lib/cbas/repo/eventstream-1.0.1.jar", + IntroducedIn: claircore.MustParseDigest(`sha256:` + strings.Repeat(`c`, 64)), + RepositoryIDs: []string{"12"}, + }, + }, + }, + Repositories: map[string]*claircore.Repository{ + "11": { + ID: "11", + Name: "cpe:/a:redhat:rhel_eus:8.6::appstream", + Key: repositoryKey, + CPE: cpe.MustUnbind("cpe:2.3:a:redhat:rhel_eus:8.6:*:appstream:*:*:*:*:*"), + }, + "12": { + ID: "12", + Name: "maven", + URI: "https://repo1.maven.apache.org/maven2", + }, + }, + Distributions: map[string]*claircore.Distribution{ + "13": { + ID: "13", + DID: "rhel", + Name: "Red Hat Enterprise Linux Server", + Version: "7", + VersionID: "7", + CPE: cpe.MustUnbind("cpe:2.3:o:redhat:enterprise_linux:7:*:*:*:*:*:*:*"), + PrettyName: "Red Hat Enterprise Linux Server 7", + }, + }, + Success: true, + }, + }, +} + +func TestResolver(t *testing.T) { + ctx := zlog.Test(context.Background(), t) + for _, tt := range resolverTestcases { + t.Run(tt.name, func(t *testing.T) { + r := &Resolver{} + ir := r.Resolve(ctx, tt.indexReport, nil) + if len(ir.Packages) != tt.expectedPackages { + t.Errorf("expected %d packages but got %d", tt.expectedPackages, len(ir.Packages)) + } + }) + } +} diff --git a/rpm/native_db.go b/rpm/native_db.go index bfa88c951..a67d7eae3 100644 --- a/rpm/native_db.go +++ b/rpm/native_db.go @@ -39,7 +39,7 @@ type ObjectResponse interface { // it will create a implementation agnostic nativeDB and extract specific claircore // objects from it. func getDBObjects[T ObjectResponse](ctx context.Context, sys fs.FS, db foundDB, fn func(context.Context, string, nativeDB) (T, error)) (T, error) { - var nat nativeDB // see native_db.go:/nativeDB + var nat nativeDB switch db.Kind { case kindSQLite: r, err := sys.Open(path.Join(db.Path, `rpmdb.sqlite`))