Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions syft/pkg/cataloger/rpmdb/cataloger.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,5 +49,27 @@ func (c *Cataloger) Catalog(resolver source.FileResolver) ([]pkg.Package, []arti

pkgs = append(pkgs, discoveredPkgs...)
}

// Additionally look for RPM manifest files to detect packages in CBL-Mariner distroless images
manifestFileMatches, err := resolver.FilesByGlob(pkg.RpmManifestGlob)
if err != nil {
return nil, nil, fmt.Errorf("failed to find rpm manifests by glob: %w", err)
}

for _, location := range manifestFileMatches {
reader, err := resolver.FileContentsByLocation(location)
if err != nil {
return nil, nil, err
}

discoveredPkgs, err := parseRpmManifest(location, reader)
internal.CloseAndLogError(reader, location.VirtualPath)
if err != nil {
return nil, nil, fmt.Errorf("unable to catalog rpm manifest=%+v: %w", location.RealPath, err)
}

pkgs = append(pkgs, discoveredPkgs...)
}

return pkgs, nil, nil
}
2 changes: 1 addition & 1 deletion syft/pkg/cataloger/rpmdb/parse_rpmdb.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import (
rpmdb "github.com/knqyf263/go-rpmdb/pkg"
)

// parseApkDb parses an "Packages" RPM DB and returns the Packages listed within it.
// parseRpmDb parses an "Packages" RPM DB and returns the Packages listed within it.
Comment thread
tofay marked this conversation as resolved.
func parseRpmDB(resolver source.FilePathResolver, dbLocation source.Location, reader io.Reader) ([]pkg.Package, error) {
f, err := ioutil.TempFile("", internal.ApplicationName+"-rpmdb")
if err != nil {
Expand Down
104 changes: 104 additions & 0 deletions syft/pkg/cataloger/rpmdb/parse_rpmmanifest.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package rpmdb

import (
"bufio"
"fmt"
"io"
"strconv"
"strings"

"github.com/anchore/syft/internal/log"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/source"
)

// Parses an entry in an RPM manifest file as used in Mariner distroless containers
// Each line is the output of :
// rpm --query --all --query-format "%{NAME}\t%{VERSION}-%{RELEASE}\t%{INSTALLTIME}\t%{BUILDTIME}\t%{VENDOR}\t%{EPOCH}\t%{SIZE}\t%{ARCH}\t%{EPOCHNUM}\t%{SOURCERPM}\n"
// https://github.com/microsoft/CBL-Mariner/blob/3df18fac373aba13a54bd02466e64969574f13af/toolkit/docs/how_it_works/5_misc.md?plain=1#L150
func parseRpmManifestEntry(entry string, location source.Location) (*pkg.Package, error) {
parts := strings.Split(entry, "\t")
if len(parts) < 10 {
return nil, fmt.Errorf("unexpected number of fields in line: %s", entry)
}

versionParts := strings.Split(parts[1], "-")
if len(versionParts) != 2 {
return nil, fmt.Errorf("unexpected version field: %s", parts[1])
}
version := versionParts[0]
release := versionParts[1]

converted, err := strconv.Atoi(parts[8])
var epoch *int
if err != nil || parts[5] == "(none)" {
epoch = nil
} else {
epoch = &converted
}

converted, err = strconv.Atoi(parts[6])
var size int
if err == nil {
size = converted
}

metadata := pkg.RpmdbMetadata{
Name: parts[0],
Version: version,
Epoch: epoch,
Arch: parts[7],
Release: release,
SourceRpm: parts[9],
Vendor: parts[4],
Size: size,
}

p := pkg.Package{
Name: parts[0],
Version: toELVersion(metadata),
Locations: source.NewLocationSet(location),
FoundBy: catalogerName,
Type: pkg.RpmPkg,
MetadataType: pkg.RpmdbMetadataType,
Metadata: metadata,
}

p.SetID()
return &p, nil
}

// Parses an RPM manifest file, as used in Mariner distroless containers, and returns the Packages listed
func parseRpmManifest(dbLocation source.Location, reader io.Reader) ([]pkg.Package, error) {
r := bufio.NewReader(reader)
allPkgs := make([]pkg.Package, 0)

for {
line, err := r.ReadString('\n')
if err != nil {
if err == io.EOF {
break
}
return nil, err
}

if line == "" {
continue
}

p, err := parseRpmManifestEntry(strings.TrimSuffix(line, "\n"), dbLocation)
if err != nil {
log.Warnf("unable to parse RPM manifest entry: %w", err)
continue
}

if !pkg.IsValid(p) {
continue
}

p.SetID()
allPkgs = append(allPkgs, *p)
}

return allPkgs, nil
}
117 changes: 117 additions & 0 deletions syft/pkg/cataloger/rpmdb/parse_rpmmanifest_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package rpmdb

import (
"os"
"testing"

"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/source"
"github.com/go-test/deep"
)

func TestParseRpmManifest(t *testing.T) {
location := source.NewLocation("test-path")

fixture_path := "test-fixtures/container-manifest-2"
expected := map[string]pkg.Package{
"mariner-release": {
Name: "mariner-release",
Version: "2.0-12.cm2",
Locations: source.NewLocationSet(location),
FoundBy: catalogerName,
Type: pkg.RpmPkg,
MetadataType: pkg.RpmdbMetadataType,
Metadata: pkg.RpmdbMetadata{
Name: "mariner-release",
Epoch: nil,
Arch: "noarch",
Release: "12.cm2",
Version: "2.0",
SourceRpm: "mariner-release-2.0-12.cm2.src.rpm",
Size: 580,
Vendor: "Microsoft Corporation",
},
},
"filesystem": {
Name: "filesystem",
Version: "1.1-9.cm2",
Locations: source.NewLocationSet(location),
FoundBy: catalogerName,
Type: pkg.RpmPkg,
MetadataType: pkg.RpmdbMetadataType,
Metadata: pkg.RpmdbMetadata{
Name: "filesystem",
Epoch: nil,
Arch: "x86_64",
Release: "9.cm2",
Version: "1.1",
SourceRpm: "filesystem-1.1-9.cm2.src.rpm",
Size: 7596,
Vendor: "Microsoft Corporation",
},
},
"glibc": {
Name: "glibc",
Version: "2.35-2.cm2",
Locations: source.NewLocationSet(location),
FoundBy: catalogerName,
Type: pkg.RpmPkg,
MetadataType: pkg.RpmdbMetadataType,
Metadata: pkg.RpmdbMetadata{
Name: "glibc",
Epoch: nil,
Arch: "x86_64",
Release: "2.cm2",
Version: "2.35",
SourceRpm: "glibc-2.35-2.cm2.src.rpm",
Size: 10855265,
Vendor: "Microsoft Corporation",
},
},
"openssl-libs": {
Name: "openssl-libs",
Version: "1.1.1k-15.cm2",
Locations: source.NewLocationSet(location),
FoundBy: catalogerName,
Type: pkg.RpmPkg,
MetadataType: pkg.RpmdbMetadataType,
Metadata: pkg.RpmdbMetadata{
Name: "openssl-libs",
Epoch: nil,
Arch: "x86_64",
Release: "15.cm2",
Version: "1.1.1k",
SourceRpm: "openssl-1.1.1k-15.cm2.src.rpm",
Size: 4365048,
Vendor: "Microsoft Corporation",
},
},
}

fixture, err := os.Open(fixture_path)
if err != nil {
t.Fatalf("failed to open fixture: %+v", err)
}

actual, err := parseRpmManifest(location, fixture)
if err != nil {
t.Fatalf("failed to parse rpm manifest: %+v", err)
}

if len(actual) != 12 {
for _, a := range actual {
t.Log(" ", a)
}
t.Fatalf("unexpected package count: %d!=%d", len(actual), len(expected))
}

for _, a := range actual[0:4] {
e := expected[a.Name]
diffs := deep.Equal(a, e)
if len(diffs) > 0 {
for _, d := range diffs {
t.Errorf("diff: %+v", d)
}
}
}
}
12 changes: 12 additions & 0 deletions syft/pkg/cataloger/rpmdb/test-fixtures/container-manifest-2
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
mariner-release 2.0-12.cm2 1653816591 1653753130 Microsoft Corporation (none) 580 noarch 0 mariner-release-2.0-12.cm2.src.rpm
filesystem 1.1-9.cm2 1653816591 1653628924 Microsoft Corporation (none) 7596 x86_64 0 filesystem-1.1-9.cm2.src.rpm
glibc 2.35-2.cm2 1653816591 1653628955 Microsoft Corporation (none) 10855265 x86_64 0 glibc-2.35-2.cm2.src.rpm
openssl-libs 1.1.1k-15.cm2 1653816591 1653631609 Microsoft Corporation (none) 4365048 x86_64 0 openssl-1.1.1k-15.cm2.src.rpm
libgcc 11.2.0-2.cm2 1653816591 1650702349 Microsoft Corporation (none) 103960 x86_64 0 gcc-11.2.0-2.cm2.src.rpm
openssl 1.1.1k-15.cm2 1653816591 1653631609 Microsoft Corporation (none) 1286337 x86_64 0 openssl-1.1.1k-15.cm2.src.rpm
glibc-iconv 2.35-2.cm2 1653816591 1653628955 Microsoft Corporation (none) 8397230 x86_64 0 glibc-2.35-2.cm2.src.rpm
iana-etc 20211115-1.cm2 1653816591 1650711959 Microsoft Corporation (none) 4380680 noarch 0 iana-etc-20211115-1.cm2.src.rpm
tzdata 2022a-1.cm2 1653816591 1653752882 Microsoft Corporation (none) 1535764 noarch 0 tzdata-2022a-1.cm2.src.rpm
prebuilt-ca-certificates-base 2.0.0-3.cm2 1653816591 1653771776 Microsoft Corporation (none) 65684 noarch 1 prebuilt-ca-certificates-base-2.0.0-3.cm2.src.rpm
distroless-packages-minimal 0.1-2.cm2 1653816591 1650712132 Microsoft Corporation (none) 0 x86_64 0 distroless-packages-0.1-2.cm2.src.rpm
distroless-packages-base 0.1-2.cm2 1653816591 1650712132 Microsoft Corporation (none) 0 x86_64 0 distroless-packages-0.1-2.cm2.src.rpm
6 changes: 6 additions & 0 deletions syft/pkg/cataloger/rpmdb/test-fixtures/generate-fixture.sh
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,9 @@ docker exec -i --tty=false generate-rpmdb-fixture bash <<-EOF
EOF

docker cp generate-rpmdb-fixture:/scratch/Packages .

docker build -o . - <<EOF
FROM mcr.microsoft.com/cbl-mariner/distroless/base:2.0 as base
FROM scratch
COPY --from=base /var/lib/rpmmanifest/container-manifest-2 .
EOF
3 changes: 3 additions & 0 deletions syft/pkg/rpmdb_metadata.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ import (
// rpmdb.sqlite is the sqlite format used in fedora + derivates
const RpmDBGlob = "**/var/lib/rpm/{Packages,Packages.db,rpmdb.sqlite}"

// Used in CBL-Mariner distroless images
const RpmManifestGlob = "**/var/lib/rpmmanifest/container-manifest-2"

var (
_ FileOwner = (*RpmdbMetadata)(nil)
_ urlIdentifier = (*RpmdbMetadata)(nil)
Expand Down
22 changes: 22 additions & 0 deletions test/integration/mariner_distroless_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package integration

import (
"testing"

"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/source"
)

func TestMarinerDistroless(t *testing.T) {
sbom, _ := catalogFixtureImage(t, "image-mariner-distroless", source.SquashedScope)

expectedPkgs := 12
actualPkgs := 0
for range sbom.Artifacts.PackageCatalog.Enumerate(pkg.RpmPkg) {
actualPkgs += 1
}

if actualPkgs != expectedPkgs {
t.Errorf("unexpected number of RPM packages: %d != %d", expectedPkgs, actualPkgs)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
FROM mcr.microsoft.com/cbl-mariner/distroless/base:2.0.202205275@sha256:f550c5428df17b145851ad75983aca6d613ad4b51ca7983b2a83e67d0ac91a5d