Skip to content

Commit

Permalink
Add support for CBL-Mariner distroless images (anchore#1045)
Browse files Browse the repository at this point in the history
  • Loading branch information
tofay authored Jun 21, 2022
1 parent e6cad07 commit dcff3fc
Show file tree
Hide file tree
Showing 9 changed files with 288 additions and 1 deletion.
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.
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

0 comments on commit dcff3fc

Please sign in to comment.