mirror of
https://github.com/anchore/syft.git
synced 2026-02-12 02:26: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...)
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
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)
|
||||
|
||||
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