mirror of
https://github.com/anchore/syft.git
synced 2026-02-14 19:46:42 +01:00
Add support for CBL-Mariner distroless images (#1045)
This commit is contained in:
parent
ea611dab5f
commit
3f6afd572a
@ -49,5 +49,27 @@ func (c *Cataloger) Catalog(resolver source.FileResolver) ([]pkg.Package, []arti
|
|||||||
|
|
||||||
pkgs = append(pkgs, discoveredPkgs...)
|
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
|
return pkgs, nil, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@ -14,7 +14,7 @@ import (
|
|||||||
rpmdb "github.com/knqyf263/go-rpmdb/pkg"
|
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) {
|
func parseRpmDB(resolver source.FilePathResolver, dbLocation source.Location, reader io.Reader) ([]pkg.Package, error) {
|
||||||
f, err := ioutil.TempFile("", internal.ApplicationName+"-rpmdb")
|
f, err := ioutil.TempFile("", internal.ApplicationName+"-rpmdb")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
104
syft/pkg/cataloger/rpmdb/parse_rpmmanifest.go
Normal file
104
syft/pkg/cataloger/rpmdb/parse_rpmmanifest.go
Normal file
@ -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
syft/pkg/cataloger/rpmdb/parse_rpmmanifest_test.go
Normal file
117
syft/pkg/cataloger/rpmdb/parse_rpmmanifest_test.go
Normal file
@ -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
syft/pkg/cataloger/rpmdb/test-fixtures/container-manifest-2
Normal file
12
syft/pkg/cataloger/rpmdb/test-fixtures/container-manifest-2
Normal file
@ -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
|
||||||
@ -21,3 +21,9 @@ docker exec -i --tty=false generate-rpmdb-fixture bash <<-EOF
|
|||||||
EOF
|
EOF
|
||||||
|
|
||||||
docker cp generate-rpmdb-fixture:/scratch/Packages .
|
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
|
||||||
|
|||||||
@ -16,6 +16,9 @@ import (
|
|||||||
// rpmdb.sqlite is the sqlite format used in fedora + derivates
|
// rpmdb.sqlite is the sqlite format used in fedora + derivates
|
||||||
const RpmDBGlob = "**/var/lib/rpm/{Packages,Packages.db,rpmdb.sqlite}"
|
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 (
|
var (
|
||||||
_ FileOwner = (*RpmdbMetadata)(nil)
|
_ FileOwner = (*RpmdbMetadata)(nil)
|
||||||
_ urlIdentifier = (*RpmdbMetadata)(nil)
|
_ urlIdentifier = (*RpmdbMetadata)(nil)
|
||||||
|
|||||||
22
test/integration/mariner_distroless_test.go
Normal file
22
test/integration/mariner_distroless_test.go
Normal file
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1 @@
|
|||||||
|
FROM mcr.microsoft.com/cbl-mariner/distroless/base:2.0.202205275@sha256:f550c5428df17b145851ad75983aca6d613ad4b51ca7983b2a83e67d0ac91a5d
|
||||||
Loading…
x
Reference in New Issue
Block a user