mirror of
https://github.com/anchore/syft.git
synced 2026-11-02 07:26:34 +01:00
* feat: expose rpm signature information This helps with more confident identification of an rpm. In theory, two rpms can be built that have the same purl string, and otherwise look identical in syft's output, but the PGP information would distinguish them as signed either by different keys, or signed at different times. In practice, this usually makes no difference since rpms tend to have unique name/version/release strings. This just gives increased confidence about the identity of the rpm found in the db. Signed-off-by: Ralph Bean <rbean@redhat.com> * chore: generate json schema Signed-off-by: Ralph Bean <rbean@redhat.com> * re-generate json schema Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * rename to a more generic signature field Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * rename rpm.pgp to rpm.signatures Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * split out signature fields Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * bump json schema Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * include RPM archives Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * update json schema Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * dont fail on unknown signature type Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> --------- Signed-off-by: Ralph Bean <rbean@redhat.com> Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> Co-authored-by: Alex Goodman <wagoodman@users.noreply.github.com>
347 lines
8.9 KiB
Go
347 lines
8.9 KiB
Go
package redhat
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"testing"
|
|
|
|
"github.com/google/go-cmp/cmp/cmpopts"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
_ "modernc.org/sqlite"
|
|
|
|
"github.com/anchore/syft/syft/artifact"
|
|
"github.com/anchore/syft/syft/file"
|
|
"github.com/anchore/syft/syft/pkg"
|
|
"github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest"
|
|
)
|
|
|
|
func Test_DBCataloger(t *testing.T) {
|
|
ctx := context.TODO()
|
|
dbLocation := file.NewLocation("/var/lib/rpm/rpmdb.sqlite")
|
|
locations := file.NewLocationSet(dbLocation.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation))
|
|
|
|
basePkg := pkg.Package{
|
|
Name: "basesystem",
|
|
Version: "11-13.el9",
|
|
Type: pkg.RpmPkg,
|
|
Locations: locations,
|
|
Licenses: pkg.NewLicenseSet(pkg.NewLicenseFromLocationsWithContext(ctx, "Public Domain", dbLocation)),
|
|
FoundBy: "rpm-db-cataloger",
|
|
PURL: "pkg:rpm/basesystem@11-13.el9?arch=noarch&upstream=basesystem-11-13.el9.src.rpm",
|
|
Metadata: pkg.RpmDBEntry{
|
|
Name: "basesystem",
|
|
Version: "11",
|
|
Arch: "noarch",
|
|
Release: "13.el9",
|
|
SourceRpm: "basesystem-11-13.el9.src.rpm",
|
|
Size: 0,
|
|
Vendor: "Rocky Enterprise Software Foundation",
|
|
Signatures: mustParseSignatures(t, "RSA/SHA256, Wed May 11 11:12:32 2022, Key ID 702d426d350d275d"),
|
|
Provides: []string{"basesystem"},
|
|
Requires: []string{
|
|
"filesystem",
|
|
"rpmlib(CompressedFileNames)",
|
|
"rpmlib(FileDigests)",
|
|
"rpmlib(PayloadFilesHavePrefix)",
|
|
"rpmlib(PayloadIsZstd)",
|
|
"setup",
|
|
},
|
|
ModularityLabel: strRef(""),
|
|
},
|
|
}
|
|
basePkg.SetID()
|
|
|
|
bashPkg := pkg.Package{
|
|
Name: "bash",
|
|
Version: "5.1.8-6.el9_1",
|
|
Type: pkg.RpmPkg,
|
|
Locations: locations,
|
|
Licenses: pkg.NewLicenseSet(pkg.NewLicenseFromLocationsWithContext(ctx, "GPLv3+", dbLocation)),
|
|
FoundBy: "rpm-db-cataloger",
|
|
PURL: "pkg:rpm/bash@5.1.8-6.el9_1?arch=x86_64&upstream=bash-5.1.8-6.el9_1.src.rpm",
|
|
Metadata: pkg.RpmDBEntry{
|
|
Name: "bash",
|
|
Version: "5.1.8",
|
|
Arch: "x86_64",
|
|
Release: "6.el9_1",
|
|
SourceRpm: "bash-5.1.8-6.el9_1.src.rpm",
|
|
Size: 7738634,
|
|
Signatures: mustParseSignatures(t, "RSA/SHA256, Mon Jan 23 22:49:22 2023, Key ID 702d426d350d275d"),
|
|
ModularityLabel: strRef(""),
|
|
Vendor: "Rocky Enterprise Software Foundation",
|
|
Provides: []string{
|
|
"/bin/bash",
|
|
"/bin/sh",
|
|
"bash",
|
|
"bash(x86-64)",
|
|
"config(bash)",
|
|
},
|
|
Requires: []string{
|
|
"/usr/bin/sh",
|
|
"config(bash)",
|
|
"filesystem",
|
|
"libc.so.6()(64bit)",
|
|
"libc.so.6(GLIBC_2.11)(64bit)",
|
|
"libc.so.6(GLIBC_2.14)(64bit)",
|
|
"libc.so.6(GLIBC_2.15)(64bit)",
|
|
"libc.so.6(GLIBC_2.2.5)(64bit)",
|
|
"libc.so.6(GLIBC_2.25)(64bit)",
|
|
"libc.so.6(GLIBC_2.3)(64bit)",
|
|
"libc.so.6(GLIBC_2.3.4)(64bit)",
|
|
"libc.so.6(GLIBC_2.33)(64bit)",
|
|
"libc.so.6(GLIBC_2.34)(64bit)",
|
|
"libc.so.6(GLIBC_2.4)(64bit)",
|
|
"libc.so.6(GLIBC_2.8)(64bit)",
|
|
"libtinfo.so.6()(64bit)",
|
|
"rpmlib(BuiltinLuaScripts)",
|
|
"rpmlib(CompressedFileNames)",
|
|
"rpmlib(FileDigests)",
|
|
"rpmlib(PayloadFilesHavePrefix)",
|
|
"rpmlib(PayloadIsZstd)",
|
|
"rtld(GNU_HASH)",
|
|
},
|
|
},
|
|
}
|
|
bashPkg.SetID()
|
|
|
|
filesystemPkg := pkg.Package{
|
|
Name: "filesystem",
|
|
Version: "3.16-2.el9",
|
|
Type: pkg.RpmPkg,
|
|
Locations: locations,
|
|
Licenses: pkg.NewLicenseSet(pkg.NewLicenseFromLocationsWithContext(ctx, "Public Domain", dbLocation)),
|
|
FoundBy: "rpm-db-cataloger",
|
|
PURL: "pkg:rpm/filesystem@3.16-2.el9?arch=x86_64&upstream=filesystem-3.16-2.el9.src.rpm",
|
|
Metadata: pkg.RpmDBEntry{
|
|
Name: "filesystem",
|
|
Version: "3.16",
|
|
Arch: "x86_64",
|
|
Release: "2.el9",
|
|
SourceRpm: "filesystem-3.16-2.el9.src.rpm",
|
|
Size: 106,
|
|
Signatures: mustParseSignatures(t, "RSA/SHA256, Mon May 16 12:32:55 2022, Key ID 702d426d350d275d"),
|
|
ModularityLabel: strRef(""),
|
|
Vendor: "Rocky Enterprise Software Foundation",
|
|
Provides: []string{
|
|
"filesystem",
|
|
"filesystem(x86-64)",
|
|
"filesystem-afs",
|
|
},
|
|
Requires: []string{
|
|
"/bin/sh",
|
|
"rpmlib(BuiltinLuaScripts)",
|
|
"rpmlib(CompressedFileNames)",
|
|
"rpmlib(FileDigests)",
|
|
"rpmlib(PayloadFilesHavePrefix)",
|
|
"rpmlib(PayloadIsZstd)",
|
|
"setup",
|
|
},
|
|
},
|
|
}
|
|
filesystemPkg.SetID()
|
|
|
|
expectedPackages := []pkg.Package{basePkg, bashPkg, filesystemPkg}
|
|
|
|
// Note that you'll see a cycle:
|
|
// bash --(requires)--> filesystem
|
|
// filesystem --(requires)--> bash
|
|
//
|
|
// This is not a bug!
|
|
//
|
|
// [root@c1a4773e8a8d /]# dnf repoquery --requires --resolve filesystem
|
|
// bash-0:5.1.8-9.el9.aarch64
|
|
// setup-0:2.13.7-10.el9.noarch
|
|
//
|
|
//[root@c1a4773e8a8d /]# dnf repoquery --requires --resolve bash
|
|
// filesystem-0:3.16-2.el9.aarch64
|
|
// glibc-0:2.34-100.el9.aarch64
|
|
// ncurses-libs-0:6.2-10.20210508.el9.aarch64
|
|
|
|
expectedRelationships := []artifact.Relationship{
|
|
// though this is expressible in the RPM DB (package depends on itself), we do not allow for it in the SBOM
|
|
//{
|
|
// From: bashPkg,
|
|
// To: bashPkg,
|
|
// Type: artifact.DependencyOfRelationship,
|
|
//},
|
|
{
|
|
From: bashPkg,
|
|
To: filesystemPkg,
|
|
Type: artifact.DependencyOfRelationship,
|
|
},
|
|
{
|
|
From: filesystemPkg,
|
|
To: basePkg,
|
|
Type: artifact.DependencyOfRelationship,
|
|
},
|
|
{
|
|
From: filesystemPkg,
|
|
To: bashPkg,
|
|
Type: artifact.DependencyOfRelationship,
|
|
},
|
|
}
|
|
|
|
pkgtest.NewCatalogTester().
|
|
WithImageResolver(t, "image-minimal").
|
|
IgnoreLocationLayer(). // this fixture can be rebuilt, thus the layer ID will change
|
|
WithCompareOptions(cmpopts.IgnoreFields(pkg.RpmDBEntry{}, "Files")). // this is rather long... ano not the point of the test
|
|
Expects(expectedPackages, expectedRelationships).
|
|
TestCataloger(t, NewDBCataloger())
|
|
|
|
}
|
|
|
|
func Test_DBCataloger_Globs(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
fixture string
|
|
expected []string
|
|
}{
|
|
{
|
|
name: "obtain DB files",
|
|
fixture: "test-fixtures/glob-paths",
|
|
expected: []string{
|
|
"usr/share/rpm/Packages",
|
|
"usr/share/rpm/Packages.db",
|
|
"usr/share/rpm/rpmdb.sqlite",
|
|
"var/lib/rpm/Packages",
|
|
"var/lib/rpm/Packages.db",
|
|
"var/lib/rpm/rpmdb.sqlite",
|
|
"var/lib/rpmmanifest/container-manifest-2",
|
|
"usr/lib/sysimage/rpm/Packages",
|
|
"usr/lib/sysimage/rpm/Packages.db",
|
|
"usr/lib/sysimage/rpm/rpmdb.sqlite",
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
pkgtest.NewCatalogTester().
|
|
FromDirectory(t, test.fixture).
|
|
ExpectsResolverContentQueries(test.expected).
|
|
TestCataloger(t, NewDBCataloger())
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_RPMFileCataloger_Globs(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
fixture string
|
|
expected []string
|
|
}{
|
|
{
|
|
name: "obtain rpm files",
|
|
fixture: "test-fixtures/glob-paths",
|
|
expected: []string{
|
|
"dive-0.10.0.rpm",
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
pkgtest.NewCatalogTester().
|
|
FromDirectory(t, test.fixture).
|
|
ExpectsResolverContentQueries(test.expected).
|
|
TestCataloger(t, NewArchiveCataloger())
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_denySelfReferences(t *testing.T) {
|
|
a := pkg.Package{
|
|
Name: "a",
|
|
}
|
|
a.SetID()
|
|
b := pkg.Package{
|
|
Name: "b",
|
|
}
|
|
b.SetID()
|
|
c := pkg.Package{
|
|
Name: "c",
|
|
}
|
|
c.SetID()
|
|
|
|
pkgs := []pkg.Package{a, b, c}
|
|
|
|
tests := []struct {
|
|
name string
|
|
pkgs []pkg.Package
|
|
rels []artifact.Relationship
|
|
err error
|
|
wantPkgs int
|
|
wantRelationships int
|
|
wantErr assert.ErrorAssertionFunc
|
|
}{
|
|
{
|
|
name: "no self references",
|
|
pkgs: pkgs,
|
|
rels: []artifact.Relationship{
|
|
{
|
|
From: a,
|
|
To: b,
|
|
Type: artifact.DependencyOfRelationship,
|
|
},
|
|
},
|
|
wantPkgs: 3,
|
|
wantRelationships: 1,
|
|
wantErr: assert.NoError,
|
|
},
|
|
{
|
|
name: "remove self references",
|
|
pkgs: pkgs,
|
|
rels: []artifact.Relationship{
|
|
{
|
|
From: a,
|
|
To: a,
|
|
Type: artifact.DependencyOfRelationship,
|
|
},
|
|
{
|
|
From: a,
|
|
To: b,
|
|
Type: artifact.DependencyOfRelationship,
|
|
},
|
|
},
|
|
wantPkgs: 3,
|
|
wantRelationships: 1,
|
|
wantErr: assert.NoError,
|
|
},
|
|
{
|
|
name: "preserve errors",
|
|
pkgs: pkgs,
|
|
rels: []artifact.Relationship{
|
|
{
|
|
From: a,
|
|
To: b,
|
|
Type: artifact.DependencyOfRelationship,
|
|
},
|
|
},
|
|
err: errors.New("stop me!"),
|
|
wantPkgs: 3,
|
|
wantRelationships: 1,
|
|
wantErr: assert.Error,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
if tt.wantErr == nil {
|
|
tt.wantErr = assert.NoError
|
|
}
|
|
|
|
gotPkgs, gotRels, err := denySelfReferences(tt.pkgs, tt.rels, tt.err)
|
|
|
|
tt.wantErr(t, err)
|
|
assert.Len(t, gotPkgs, tt.wantPkgs)
|
|
assert.Len(t, gotRels, tt.wantRelationships)
|
|
})
|
|
}
|
|
}
|
|
|
|
func mustParseSignatures(t testing.TB, sigs ...string) []pkg.RpmSignature {
|
|
signatures, err := parseSignatures(sigs...)
|
|
require.NoError(t, err)
|
|
return signatures
|
|
}
|