From 6ee96707828475fbf697e66f934d07ea30bef579 Mon Sep 17 00:00:00 2001 From: Christoph Hartmann Date: Thu, 15 Feb 2024 00:33:51 +0100 Subject: [PATCH] =?UTF-8?q?=E2=AD=90=EF=B8=8F=20extract=20files=20that=20a?= =?UTF-8?q?re=20used=20by=20a=20package=20(#3313)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- providers/os/resources/os.lr | 8 + providers/os/resources/os.lr.go | 90 +++++- providers/os/resources/os.lr.manifest.yaml | 7 + providers/os/resources/packages.go | 52 +++- .../os/resources/packages/aix_packages.go | 17 +- .../resources/packages/aix_packages_test.go | 3 +- .../os/resources/packages/apk_packages.go | 15 + .../resources/packages/apk_packages_test.go | 257 ++++++++++++----- .../os/resources/packages/cos_packages.go | 7 +- .../resources/packages/cos_packages_test.go | 3 +- .../os/resources/packages/dpkg_packages.go | 47 ++- .../resources/packages/dpkg_packages_test.go | 61 ++-- .../os/resources/packages/freebsd_packages.go | 5 + .../os/resources/packages/macos_packages.go | 14 + .../resources/packages/macos_packages_test.go | 6 +- .../os/resources/packages/opkg_packages.go | 5 + .../resources/packages/opkg_packages_test.go | 3 +- providers/os/resources/packages/packages.go | 41 +++ .../os/resources/packages/packages_test.go | 13 + .../os/resources/packages/pacman_packages.go | 5 + .../packages/pacman_packages_test.go | 3 +- .../os/resources/packages/rpm_packages.go | 91 ++++-- .../resources/packages/rpm_packages_test.go | 269 ++++++++++-------- providers/os/resources/packages/scratch.go | 5 + .../os/resources/packages/solaris_packages.go | 5 + .../packages/solaris_packages_test.go | 28 +- .../packages/testdata/packages_dpkg.toml | 17 +- .../packages/testdata/packages_redhat7.toml | 11 + .../os/resources/packages/windows_packages.go | 5 + 29 files changed, 823 insertions(+), 270 deletions(-) create mode 100644 providers/os/resources/packages/packages_test.go diff --git a/providers/os/resources/os.lr b/providers/os/resources/os.lr index 12ec4ab9c1..d7f0a04a5c 100644 --- a/providers/os/resources/os.lr +++ b/providers/os/resources/os.lr @@ -623,6 +623,14 @@ package @defaults("name version") { installed bool // Whether the package is outdated outdated() bool + + // Package files + files() []pkgFileInfo +} + +private pkgFileInfo @defaults("path") { + // Path to the file + path string } // List of packages on this system diff --git a/providers/os/resources/os.lr.go b/providers/os/resources/os.lr.go index 7f6172ca28..d2dcc64f7e 100644 --- a/providers/os/resources/os.lr.go +++ b/providers/os/resources/os.lr.go @@ -198,6 +198,10 @@ func init() { Init: initPackage, Create: createPackage, }, + "pkgFileInfo": { + // to override args, implement: initPkgFileInfo(runtime *plugin.Runtime, args map[string]*llx.RawData) (map[string]*llx.RawData, plugin.Resource, error) + Create: createPkgFileInfo, + }, "packages": { // to override args, implement: initPackages(runtime *plugin.Runtime, args map[string]*llx.RawData) (map[string]*llx.RawData, plugin.Resource, error) Create: createPackages, @@ -1129,6 +1133,12 @@ var getDataFields = map[string]func(r plugin.Resource) *plugin.DataRes{ "package.outdated": func(r plugin.Resource) *plugin.DataRes { return (r.(*mqlPackage).GetOutdated()).ToDataRes(types.Bool) }, + "package.files": func(r plugin.Resource) *plugin.DataRes { + return (r.(*mqlPackage).GetFiles()).ToDataRes(types.Array(types.Resource("pkgFileInfo"))) + }, + "pkgFileInfo.path": func(r plugin.Resource) *plugin.DataRes { + return (r.(*mqlPkgFileInfo).GetPath()).ToDataRes(types.String) + }, "packages.list": func(r plugin.Resource) *plugin.DataRes { return (r.(*mqlPackages).GetList()).ToDataRes(types.Array(types.Resource("package"))) }, @@ -3055,6 +3065,18 @@ var setDataFields = map[string]func(r plugin.Resource, v *llx.RawData) bool { r.(*mqlPackage).Outdated, ok = plugin.RawToTValue[bool](v.Value, v.Error) return }, + "package.files": func(r plugin.Resource, v *llx.RawData) (ok bool) { + r.(*mqlPackage).Files, ok = plugin.RawToTValue[[]interface{}](v.Value, v.Error) + return + }, + "pkgFileInfo.__id": func(r plugin.Resource, v *llx.RawData) (ok bool) { + r.(*mqlPkgFileInfo).__id, ok = v.Value.(string) + return + }, + "pkgFileInfo.path": func(r plugin.Resource, v *llx.RawData) (ok bool) { + r.(*mqlPkgFileInfo).Path, ok = plugin.RawToTValue[string](v.Value, v.Error) + return + }, "packages.__id": func(r plugin.Resource, v *llx.RawData) (ok bool) { r.(*mqlPackages).__id, ok = v.Value.(string) return @@ -7995,7 +8017,7 @@ func (c *mqlGroups) GetList() *plugin.TValue[[]interface{}] { type mqlPackage struct { MqlRuntime *plugin.Runtime __id string - // optional: if you define mqlPackageInternal it will be used here + mqlPackageInternal Name plugin.TValue[string] Description plugin.TValue[string] Version plugin.TValue[string] @@ -8009,6 +8031,7 @@ type mqlPackage struct { Available plugin.TValue[string] Installed plugin.TValue[bool] Outdated plugin.TValue[bool] + Files plugin.TValue[[]interface{}] } // createPackage creates a new instance of this resource @@ -8106,6 +8129,71 @@ func (c *mqlPackage) GetOutdated() *plugin.TValue[bool] { }) } +func (c *mqlPackage) GetFiles() *plugin.TValue[[]interface{}] { + return plugin.GetOrCompute[[]interface{}](&c.Files, func() ([]interface{}, error) { + if c.MqlRuntime.HasRecording { + d, err := c.MqlRuntime.FieldResourceFromRecording("package", c.__id, "files") + if err != nil { + return nil, err + } + if d != nil { + return d.Value.([]interface{}), nil + } + } + + return c.files() + }) +} + +// mqlPkgFileInfo for the pkgFileInfo resource +type mqlPkgFileInfo struct { + MqlRuntime *plugin.Runtime + __id string + // optional: if you define mqlPkgFileInfoInternal it will be used here + Path plugin.TValue[string] +} + +// createPkgFileInfo creates a new instance of this resource +func createPkgFileInfo(runtime *plugin.Runtime, args map[string]*llx.RawData) (plugin.Resource, error) { + res := &mqlPkgFileInfo{ + MqlRuntime: runtime, + } + + err := SetAllData(res, args) + if err != nil { + return res, err + } + + if res.__id == "" { + res.__id, err = res.id() + if err != nil { + return nil, err + } + } + + if runtime.HasRecording { + args, err = runtime.ResourceFromRecording("pkgFileInfo", res.__id) + if err != nil || args == nil { + return res, err + } + return res, SetAllData(res, args) + } + + return res, nil +} + +func (c *mqlPkgFileInfo) MqlName() string { + return "pkgFileInfo" +} + +func (c *mqlPkgFileInfo) MqlID() string { + return c.__id +} + +func (c *mqlPkgFileInfo) GetPath() *plugin.TValue[string] { + return &c.Path +} + // mqlPackages for the packages resource type mqlPackages struct { MqlRuntime *plugin.Runtime diff --git a/providers/os/resources/os.lr.manifest.yaml b/providers/os/resources/os.lr.manifest.yaml index d20b09156f..1a7f6dc58c 100644 --- a/providers/os/resources/os.lr.manifest.yaml +++ b/providers/os/resources/os.lr.manifest.yaml @@ -561,6 +561,8 @@ resources: min_mondoo_version: latest description: {} epoch: {} + files: + min_mondoo_version: latest format: {} installed: {} name: {} @@ -647,6 +649,11 @@ resources: file: {} params: {} min_mondoo_version: 5.15.0 + pkgFileInfo: + fields: + path: {} + is_private: true + min_mondoo_version: latest platform: fields: vulnerabilityReport: {} diff --git a/providers/os/resources/packages.go b/providers/os/resources/packages.go index 6726024565..cf1896d22a 100644 --- a/providers/os/resources/packages.go +++ b/providers/os/resources/packages.go @@ -29,6 +29,16 @@ func (x *mqlPackage) id() (string, error) { return x.Format.Data + "://" + x.Name.Data + "/" + x.Version.Data + "/" + x.Arch.Data, nil } +type mqlPackageInternal struct { + filesState packages.PkgFilesAvailable + filesOnDisks []packages.FileRecord +} + +// TODO: this is not accurate enough, we need to tie it to the package +func (x *mqlPkgFileInfo) id() (string, error) { + return x.Path.Data, nil +} + func initPackage(runtime *plugin.Runtime, args map[string]*llx.RawData) (map[string]*llx.RawData, plugin.Resource, error) { // we only look up the package, if we have been supplied by its name and nothing else raw, ok := args["name"] @@ -65,6 +75,7 @@ func initPackage(runtime *plugin.Runtime, args map[string]*llx.RawData) (map[str res.Format.State = plugin.StateIsSet | plugin.StateIsNull res.Origin.State = plugin.StateIsSet | plugin.StateIsNull res.Status.State = plugin.StateIsSet | plugin.StateIsNull + res.Files.State = plugin.StateIsSet | plugin.StateIsNull return nil, res, nil } @@ -83,6 +94,42 @@ func (p *mqlPackage) origin() (string, error) { return "", nil } +func (p *mqlPackage) files() ([]interface{}, error) { + if p.filesState == packages.PkgFilesNotAvailable { + return nil, nil + } + + var filesOnDisk []packages.FileRecord + + if p.filesState == packages.PkgFilesIncluded { + // we already have the data + filesOnDisk = p.filesOnDisks + } else { + // we need to retrieve the data on-demand + conn := p.MqlRuntime.Connection.(shared.Connection) + pm, err := packages.ResolveSystemPkgManager(conn) + if pm == nil || err != nil { + return nil, errors.New("could not detect suitable package manager for platform") + } + filesOnDisk, err = pm.Files(p.Name.Data, p.Version.Data, p.Arch.Data) + if err != nil { + return nil, err + } + } + + var pkgFiles []interface{} + for _, file := range filesOnDisk { + pkgFile, err := CreateResource(p.MqlRuntime, "pkgFileInfo", map[string]*llx.RawData{ + "path": llx.StringData(file.Path), + }) + if err != nil { + return nil, err + } + pkgFiles = append(pkgFiles, pkgFile) + } + return pkgFiles, nil +} + type mqlPackagesInternal struct { lock sync.Mutex packagesByName map[string]*mqlPackage @@ -159,7 +206,10 @@ func (x *mqlPackages) list() ([]interface{}, error) { return nil, err } - pkgs[i] = pkg + s := pkg.(*mqlPackage) + s.filesState = osPkg.FilesAvailable + s.filesOnDisks = osPkg.Files + pkgs[i] = s } return pkgs, x.refreshCache(pkgs) diff --git a/providers/os/resources/packages/aix_packages.go b/providers/os/resources/packages/aix_packages.go index 0a99874f4a..d3bfd79c5d 100644 --- a/providers/os/resources/packages/aix_packages.go +++ b/providers/os/resources/packages/aix_packages.go @@ -55,23 +55,28 @@ type AixPkgManager struct { platform *inventory.Platform } -func (f *AixPkgManager) Name() string { +func (a *AixPkgManager) Name() string { return "AIX Package Manager" } -func (f *AixPkgManager) Format() string { +func (a *AixPkgManager) Format() string { return AixPkgFormat } -func (f *AixPkgManager) List() ([]Package, error) { - cmd, err := f.conn.RunCommand("lslpp -cl ") +func (a *AixPkgManager) List() ([]Package, error) { + cmd, err := a.conn.RunCommand("lslpp -cl ") if err != nil { return nil, fmt.Errorf("could not read freebsd package list") } - return parseAixPackages(f.platform, cmd.Stdout) + return parseAixPackages(a.platform, cmd.Stdout) } -func (f *AixPkgManager) Available() (map[string]PackageUpdate, error) { +func (a *AixPkgManager) Available() (map[string]PackageUpdate, error) { return map[string]PackageUpdate{}, nil } + +func (a *AixPkgManager) Files(name string, version string, arch string) ([]FileRecord, error) { + // not yet implemented + return nil, nil +} diff --git a/providers/os/resources/packages/aix_packages_test.go b/providers/os/resources/packages/aix_packages_test.go index 7c8955182a..9ffc738bb2 100644 --- a/providers/os/resources/packages/aix_packages_test.go +++ b/providers/os/resources/packages/aix_packages_test.go @@ -26,8 +26,7 @@ func TestParseAixPackages(t *testing.T) { require.Nil(t, err) assert.Equal(t, 16, len(m), "detected the right amount of packages") - var p Package - p = Package{ + p := Package{ Name: "X11.apps.msmit", Version: "7.3.0.0", Description: "AIXwindows msmit Application", diff --git a/providers/os/resources/packages/apk_packages.go b/providers/os/resources/packages/apk_packages.go index 799a32e8d5..5304e8543f 100644 --- a/providers/os/resources/packages/apk_packages.go +++ b/providers/os/resources/packages/apk_packages.go @@ -11,6 +11,7 @@ import ( cpe2 "go.mondoo.com/cnquery/v10/providers/os/resources/cpe" "go.mondoo.com/cnquery/v10/providers/os/resources/purl" "io" + "path/filepath" "regexp" "github.com/rs/zerolog/log" @@ -58,6 +59,7 @@ func ParseApkDbPackages(pf *inventory.Platform, input io.Reader) []Package { scanner := bufio.NewScanner(input) pkg := Package{} var key string + var dir string for scanner.Scan() { line := scanner.Text() @@ -95,6 +97,14 @@ func ParseApkDbPackages(pf *inventory.Platform, input io.Reader) []Package { pkg.Origin = m[2] // origin case "T": pkg.Description = m[2] // description + case "F": + dir = m[2] + case "R": + // files + pkg.FilesAvailable = PkgFilesIncluded + pkg.Files = append(pkg.Files, FileRecord{ + Path: filepath.Join(dir, m[2]), + }) } } @@ -158,3 +168,8 @@ func (apm *AlpinePkgManager) Available() (map[string]PackageUpdate, error) { } return ParseApkUpdates(cmd.Stdout) } + +func (apm *AlpinePkgManager) Files(name string, version string, arch string) ([]FileRecord, error) { + // not yet implemented + return nil, nil +} diff --git a/providers/os/resources/packages/apk_packages_test.go b/providers/os/resources/packages/apk_packages_test.go index ed084fabff..91cb704f1a 100644 --- a/providers/os/resources/packages/apk_packages_test.go +++ b/providers/os/resources/packages/apk_packages_test.go @@ -1,16 +1,14 @@ // Copyright (c) Mondoo, Inc. // SPDX-License-Identifier: BUSL-1.1 -package packages_test +package packages import ( "testing" - "go.mondoo.com/cnquery/v10/providers-sdk/v1/inventory" - "github.com/stretchr/testify/assert" + "go.mondoo.com/cnquery/v10/providers-sdk/v1/inventory" "go.mondoo.com/cnquery/v10/providers/os/connection/mock" - "go.mondoo.com/cnquery/v10/providers/os/resources/packages" ) func TestAlpineApkdbParser(t *testing.T) { @@ -34,87 +32,190 @@ func TestAlpineApkdbParser(t *testing.T) { } defer f.Close() - m := packages.ParseApkDbPackages(pf, f) + m := ParseApkDbPackages(pf, f) assert.Equal(t, 7, len(m), "detected the right amount of packages") - var p packages.Package - p = packages.Package{ - Name: "musl", - Version: "1510953106:1.1.18-r2", - Epoch: "1510953106", - Arch: "x86_64", - Description: "the musl c library (libc) implementation", - Origin: "musl", - PUrl: "pkg:apk/alpine/musl@1510953106%3A1.1.18-r2?arch=x86_64&distro=alpine-3.7.0&epoch=1510953106", - CPE: "cpe:2.3:a:musl:musl:1510953106:x86_64:*:*:*:*:x86_64:*", - Format: packages.AlpinePkgFormat, + p := Package{ + Name: "musl", + Version: "1510953106:1.1.18-r2", + Epoch: "1510953106", + Arch: "x86_64", + Description: "the musl c library (libc) implementation", + Origin: "musl", + PUrl: "pkg:apk/alpine/musl@1510953106%3A1.1.18-r2?arch=x86_64&distro=alpine-3.7.0&epoch=1510953106", + CPE: "cpe:2.3:a:musl:musl:1510953106:x86_64:*:*:*:*:x86_64:*", + Format: AlpinePkgFormat, + FilesAvailable: PkgFilesIncluded, + Files: []FileRecord{ + { + Path: "lib/libc.musl-x86_64.so.1", + }, + { + Path: "lib/ld-musl-x86_64.so.1", + }, + }, } - assert.Contains(t, m, p, "musl detected") - - p = packages.Package{ - Name: "libressl2.6-libcrypto", - Version: "1510257703:2.6.3-r0", - Epoch: "1510257703", - Arch: "x86_64", - Description: "libressl libcrypto library", - Origin: "libressl", - PUrl: "pkg:apk/alpine/libressl2.6-libcrypto@1510257703%3A2.6.3-r0?arch=x86_64&distro=alpine-3.7.0&epoch=1510257703", - CPE: "cpe:2.3:a:libressl2.6-libcrypto:libressl2.6-libcrypto:1510257703:x86_64:*:*:*:*:x86_64:*", - Format: packages.AlpinePkgFormat, + assert.Equal(t, findPkg(m, p.Name), p, p.Name) + + p = Package{ + Name: "libressl2.6-libcrypto", + Version: "1510257703:2.6.3-r0", + Epoch: "1510257703", + Arch: "x86_64", + Description: "libressl libcrypto library", + Origin: "libressl", + PUrl: "pkg:apk/alpine/libressl2.6-libcrypto@1510257703%3A2.6.3-r0?arch=x86_64&distro=alpine-3.7.0&epoch=1510257703", + CPE: "cpe:2.3:a:libressl2.6-libcrypto:libressl2.6-libcrypto:1510257703:x86_64:*:*:*:*:x86_64:*", + Format: AlpinePkgFormat, + FilesAvailable: PkgFilesIncluded, + Files: []FileRecord{ + { + Path: "etc/ssl/cert.pem", + }, + { + Path: "etc/ssl/x509v3.cnf", + }, + { + Path: "etc/ssl/openssl.cnf", + }, + { + Path: "lib/libcrypto.so.42", + }, + { + Path: "lib/libcrypto.so.42.0.0", + }, + { + Path: "usr/lib/libcrypto.so.42", + }, + { + Path: "usr/lib/libcrypto.so.42.0.0", + }, + }, } - assert.Contains(t, m, p, "libcrypto detected") - - p = packages.Package{ - Name: "libressl2.6-libssl", - Version: "1510257703:2.6.3-r0", - Epoch: "1510257703", - Arch: "x86_64", - Description: "libressl libssl library", - Origin: "libressl", - PUrl: "pkg:apk/alpine/libressl2.6-libssl@1510257703%3A2.6.3-r0?arch=x86_64&distro=alpine-3.7.0&epoch=1510257703", - CPE: "cpe:2.3:a:libressl2.6-libssl:libressl2.6-libssl:1510257703:x86_64:*:*:*:*:x86_64:*", - Format: packages.AlpinePkgFormat, + assert.Equal(t, findPkg(m, p.Name), p, p.Name) + + p = Package{ + Name: "libressl2.6-libssl", + Version: "1510257703:2.6.3-r0", + Epoch: "1510257703", + Arch: "x86_64", + Description: "libressl libssl library", + Origin: "libressl", + PUrl: "pkg:apk/alpine/libressl2.6-libssl@1510257703%3A2.6.3-r0?arch=x86_64&distro=alpine-3.7.0&epoch=1510257703", + CPE: "cpe:2.3:a:libressl2.6-libssl:libressl2.6-libssl:1510257703:x86_64:*:*:*:*:x86_64:*", + Format: AlpinePkgFormat, + FilesAvailable: PkgFilesIncluded, + Files: []FileRecord{ + { + Path: "lib/libssl.so.44.0.1", + }, + { + Path: "lib/libssl.so.44", + }, + { + Path: "usr/lib/libssl.so.44.0.1", + }, + { + Path: "usr/lib/libssl.so.44", + }, + }, } - assert.Contains(t, m, p, "libssl detected") - - p = packages.Package{ - Name: "apk-tools", - Version: "1515485577:2.8.2-r0", - Epoch: "1515485577", - Arch: "x86_64", - Description: "Alpine Package Keeper - package manager for alpine", - Origin: "apk-tools", - PUrl: "pkg:apk/alpine/apk-tools@1515485577%3A2.8.2-r0?arch=x86_64&distro=alpine-3.7.0&epoch=1515485577", - CPE: "cpe:2.3:a:apk-tools:apk-tools:1515485577:x86_64:*:*:*:*:x86_64:*", - Format: packages.AlpinePkgFormat, + assert.Equal(t, findPkg(m, p.Name), p, p.Name) + + p = Package{ + Name: "apk-tools", + Version: "1515485577:2.8.2-r0", + Epoch: "1515485577", + Arch: "x86_64", + Description: "Alpine Package Keeper - package manager for alpine", + Origin: "apk-tools", + PUrl: "pkg:apk/alpine/apk-tools@1515485577%3A2.8.2-r0?arch=x86_64&distro=alpine-3.7.0&epoch=1515485577", + CPE: "cpe:2.3:a:apk-tools:apk-tools:1515485577:x86_64:*:*:*:*:x86_64:*", + Format: AlpinePkgFormat, + FilesAvailable: PkgFilesIncluded, + Files: []FileRecord{ + { + Path: "sbin/apk", + }, + }, } - assert.Contains(t, m, p, "apk-tools detected") - - p = packages.Package{ - Name: "busybox", - Version: "1513075346:1.27.2-r7", - Epoch: "1513075346", - Arch: "x86_64", - Description: "Size optimized toolbox of many common UNIX utilities", - Origin: "busybox", - PUrl: "pkg:apk/alpine/busybox@1513075346%3A1.27.2-r7?arch=x86_64&distro=alpine-3.7.0&epoch=1513075346", - CPE: "cpe:2.3:a:busybox:busybox:1513075346:x86_64:*:*:*:*:x86_64:*", - Format: packages.AlpinePkgFormat, + assert.Equal(t, findPkg(m, p.Name), p, p.Name) + + p = Package{ + Name: "busybox", + Version: "1513075346:1.27.2-r7", + Epoch: "1513075346", + Arch: "x86_64", + Description: "Size optimized toolbox of many common UNIX utilities", + Origin: "busybox", + PUrl: "pkg:apk/alpine/busybox@1513075346%3A1.27.2-r7?arch=x86_64&distro=alpine-3.7.0&epoch=1513075346", + CPE: "cpe:2.3:a:busybox:busybox:1513075346:x86_64:*:*:*:*:x86_64:*", + Format: AlpinePkgFormat, + FilesAvailable: PkgFilesIncluded, + Files: []FileRecord{ + { + Path: "bin/busybox", + }, + { + Path: "bin/sh", + }, + { + Path: "etc/securetty", + }, + { + Path: "etc/udhcpd.conf", + }, + { + Path: "etc/logrotate.d/acpid", + }, + { + Path: "etc/network/if-up.d/dad", + }, + }, } - assert.Contains(t, m, p, "busybox detected") - - p = packages.Package{ - Name: "alpine-baselayout", - Version: "1510075862:3.0.5-r2", - Epoch: "1510075862", - Arch: "x86_64", - Description: "Alpine base dir structure and init scripts", - Origin: "alpine-baselayout", - PUrl: "pkg:apk/alpine/alpine-baselayout@1510075862%3A3.0.5-r2?arch=x86_64&distro=alpine-3.7.0&epoch=1510075862", - CPE: "cpe:2.3:a:alpine-baselayout:alpine-baselayout:1510075862:x86_64:*:*:*:*:x86_64:*", - Format: packages.AlpinePkgFormat, + assert.Equal(t, findPkg(m, p.Name), p, p.Name) + + p = Package{ + Name: "alpine-baselayout", + Version: "1510075862:3.0.5-r2", + Epoch: "1510075862", + Arch: "x86_64", + Description: "Alpine base dir structure and init scripts", + Origin: "alpine-baselayout", + PUrl: "pkg:apk/alpine/alpine-baselayout@1510075862%3A3.0.5-r2?arch=x86_64&distro=alpine-3.7.0&epoch=1510075862", + CPE: "cpe:2.3:a:alpine-baselayout:alpine-baselayout:1510075862:x86_64:*:*:*:*:x86_64:*", + Format: AlpinePkgFormat, + FilesAvailable: PkgFilesIncluded, + Files: []FileRecord{ + {Path: "etc/hosts"}, + {Path: "etc/sysctl.conf"}, + {Path: "etc/group"}, + {Path: "etc/protocols"}, + {Path: "etc/fstab"}, + {Path: "etc/mtab"}, + {Path: "etc/profile"}, + {Path: "etc/TZ"}, + {Path: "etc/shells"}, + {Path: "etc/motd"}, + {Path: "etc/inittab"}, + {Path: "etc/hostname"}, + {Path: "etc/modules"}, + {Path: "etc/services"}, + {Path: "etc/shadow"}, + {Path: "etc/passwd"}, + {Path: "etc/profile.d/color_prompt"}, + {Path: "etc/sysctl.d/00-alpine.conf"}, + {Path: "etc/modprobe.d/i386.conf"}, + {Path: "etc/modprobe.d/blacklist.conf"}, + {Path: "etc/modprobe.d/aliases.conf"}, + {Path: "etc/modprobe.d/kms.conf"}, + {Path: "etc/crontabs/root"}, + {Path: "sbin/mkmntdirs"}, + {Path: "var/run"}, + {Path: "var/spool/cron/crontabs"}, + }, } - assert.Contains(t, m, p, "alpine-baselayout detected") + assert.Equal(t, findPkg(m, p.Name), p, p.Name) } func TestApkUpdateParser(t *testing.T) { @@ -128,7 +229,7 @@ func TestApkUpdateParser(t *testing.T) { } assert.Nil(t, err) - m, err := packages.ParseApkUpdates(c.Stdout) + m, err := ParseApkUpdates(c.Stdout) assert.Nil(t, err) assert.Equal(t, 2, len(m), "detected the right amount of package updates") diff --git a/providers/os/resources/packages/cos_packages.go b/providers/os/resources/packages/cos_packages.go index b91660fad0..e58f79d191 100644 --- a/providers/os/resources/packages/cos_packages.go +++ b/providers/os/resources/packages/cos_packages.go @@ -51,7 +51,7 @@ func (cpm *CosPkgManager) List() ([]Package, error) { return ParseCosPackages(fr) } -func (mpm *CosPkgManager) Available() (map[string]PackageUpdate, error) { +func (cpm *CosPkgManager) Available() (map[string]PackageUpdate, error) { return nil, errors.New("cannot determine available packages for cos") } @@ -81,3 +81,8 @@ func ParseCosPackages(input io.Reader) ([]Package, error) { return pkgs, nil } + +func (cpm *CosPkgManager) Files(name string, version string, arch string) ([]FileRecord, error) { + // not yet implemented + return nil, nil +} diff --git a/providers/os/resources/packages/cos_packages_test.go b/providers/os/resources/packages/cos_packages_test.go index 46bac1c4b4..bc0e9bfd2e 100644 --- a/providers/os/resources/packages/cos_packages_test.go +++ b/providers/os/resources/packages/cos_packages_test.go @@ -19,8 +19,7 @@ func TestParseCosPackages(t *testing.T) { assert.Nil(t, err) assert.Equal(t, 3, len(m), "detected the right amount of packages") - var p Package - p = Package{ + p := Package{ Name: "zlib", Version: "1.2.11-r4", Arch: "", diff --git a/providers/os/resources/packages/dpkg_packages.go b/providers/os/resources/packages/dpkg_packages.go index ea5a370825..5a0c299277 100644 --- a/providers/os/resources/packages/dpkg_packages.go +++ b/providers/os/resources/packages/dpkg_packages.go @@ -57,7 +57,10 @@ func ParseDpkgPackages(pf *inventory.Platform, input io.Reader) ([]Package, erro // reset package definition once we reach a newline if len(line) == 0 { add(pkg) - pkg = Package{Format: DpkgPkgFormat} + pkg = Package{ + Format: DpkgPkgFormat, + FilesAvailable: PkgFilesAsync, + } } m := DPKG_REGEX.FindStringSubmatch(line) @@ -148,7 +151,7 @@ func (dpm *DebPkgManager) List() ([]Package, error) { // main pkg file for debian systems if fErr == nil { log.Debug().Str("file", dpkgStatusFile).Msg("parse dpkg status file") - fi, err := dpm.conn.FileSystem().Open(dpkgStatusFile) + fi, err := fs.Open(dpkgStatusFile) if err != nil { return nil, fmt.Errorf("could not read dpkg package list") } @@ -170,7 +173,7 @@ func (dpm *DebPkgManager) List() ([]Package, error) { } log.Debug().Str("path", path).Msg("walk file") - fi, err := dpm.conn.FileSystem().Open(path) + fi, err := fs.Open(path) if err != nil { log.Debug().Err(err).Str("path", path).Msg("could open file") return fmt.Errorf("could not read dpkg package list") @@ -210,3 +213,41 @@ func (dpm *DebPkgManager) Available() (map[string]PackageUpdate, error) { } return ParseDpkgUpdates(cmd.Stdout) } + +func (dpm *DebPkgManager) Files(name string, version string, arch string) ([]FileRecord, error) { + fs := dpm.conn.FileSystem() + + files := []string{ + "/var/lib/dpkg/info/" + name + ".list", + } + if arch != "" { + files = append(files, "/var/lib/dpkg/info/"+name+":"+arch+".list") + } + + fileRecords := []FileRecord{} + for i := range files { + file := files[i] + _, err := fs.Stat(file) + if err != nil { + continue + } + + fi, err := fs.Open(file) + if err != nil { + return nil, err + } + defer fi.Close() + + scanner := bufio.NewScanner(fi) + for scanner.Scan() { + line := scanner.Text() + fileRecords = append(fileRecords, FileRecord{ + Path: line, + }) + } + // we only need the first file that exists + break + } + + return fileRecords, nil +} diff --git a/providers/os/resources/packages/dpkg_packages_test.go b/providers/os/resources/packages/dpkg_packages_test.go index 0a2306155a..3b56f842bc 100644 --- a/providers/os/resources/packages/dpkg_packages_test.go +++ b/providers/os/resources/packages/dpkg_packages_test.go @@ -1,17 +1,15 @@ // Copyright (c) Mondoo, Inc. // SPDX-License-Identifier: BUSL-1.1 -package packages_test +package packages import ( "testing" - "go.mondoo.com/cnquery/v10/providers-sdk/v1/inventory" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "go.mondoo.com/cnquery/v10/providers-sdk/v1/inventory" "go.mondoo.com/cnquery/v10/providers/os/connection/mock" - "go.mondoo.com/cnquery/v10/providers/os/resources/packages" ) func TestDpkgParser(t *testing.T) { @@ -31,12 +29,11 @@ func TestDpkgParser(t *testing.T) { require.NoError(t, err) defer f.Close() - m, err := packages.ParseDpkgPackages(pf, f) + m, err := ParseDpkgPackages(pf, f) require.NoError(t, err) assert.Equal(t, 10, len(m), "detected the right amount of packages") - var p packages.Package - p = packages.Package{ + p := Package{ Name: "fdisk", Version: "2.31.1-0.4ubuntu3.1", Arch: "amd64", @@ -57,9 +54,9 @@ The sfdisk utility is mostly for automation and scripting uses.`, CPE: "cpe:2.3:a:fdisk:fdisk:2.31.1-0.4ubuntu3.1:amd64:*:*:*:*:*:*", Format: "deb", } - assert.Contains(t, m, p, "fdisk detected") + assert.Equal(t, findPkg(m, p.Name), p, p.Name) - p = packages.Package{ + p = Package{ Name: "libaudit1", Version: "1:2.4-1+b1", Arch: "amd64", @@ -69,11 +66,42 @@ The sfdisk utility is mostly for automation and scripting uses.`, The audit-libs package contains the dynamic libraries needed for applications to use the audit framework. It is used to monitor systems for security related events.`, - PUrl: "pkg:deb/ubuntu/libaudit1@1%3A2.4-1%2Bb1?arch=amd64&distro=ubuntu-18.04", - CPE: "cpe:2.3:a:libaudit1:libaudit1:1:amd64:*:*:*:*:*:*", - Format: "deb", + PUrl: "pkg:deb/ubuntu/libaudit1@1%3A2.4-1%2Bb1?arch=amd64&distro=ubuntu-18.04", + CPE: "cpe:2.3:a:libaudit1:libaudit1:1:amd64:*:*:*:*:*:*", + Format: "deb", + FilesAvailable: PkgFilesAsync, } - assert.Contains(t, m, p, "libaudit1 detected") + assert.Equal(t, findPkg(m, p.Name), p, p.Name) + + p = Package{ + Name: "libss2", + Version: "1.44.1-1", + Arch: "amd64", + Status: "install ok installed", + Origin: "e2fsprogs", + Description: `command-line interface parsing library +libss provides a simple command-line interface parser which will +accept input from the user, parse the command into an argv argument +vector, and then dispatch it to a handler function. +. +It was originally inspired by the Multics SubSystem library.`, + PUrl: "pkg:deb/ubuntu/libss2@1.44.1-1?arch=amd64&distro=ubuntu-18.04", + CPE: "cpe:2.3:a:libss2:libss2:1.44.1-1:amd64:*:*:*:*:*:*", + Format: "deb", + FilesAvailable: PkgFilesAsync, + } + assert.Equal(t, findPkg(m, p.Name), p, p.Name) + + // fetch package files + mgr := &DebPkgManager{ + conn: mock, + platform: pf, + } + pkgFiles, err := mgr.Files(p.Name, p.Version, p.Arch) + require.NoError(t, err) + assert.Equal(t, 11, len(pkgFiles), "detected the right amount of package files") + assert.Contains(t, pkgFiles, FileRecord{Path: "/lib/aarch64-linux-gnu/libss.so.2.0"}) + assert.Contains(t, pkgFiles, FileRecord{Path: "/lib/aarch64-linux-gnu/libss.so.2"}) } func TestDpkgParserStatusD(t *testing.T) { @@ -93,12 +121,11 @@ func TestDpkgParserStatusD(t *testing.T) { require.NoError(t, err) defer f.Close() - m, err := packages.ParseDpkgPackages(pf, f) + m, err := ParseDpkgPackages(pf, f) require.NoError(t, err) assert.Equal(t, 1, len(m), "detected the right amount of packages") - var p packages.Package - p = packages.Package{ + p := Package{ Name: "base-files", Version: "9.9+deb9u11", Arch: "amd64", @@ -121,7 +148,7 @@ func TestDpkgUpdateParser(t *testing.T) { require.NoError(t, err) assert.Nil(t, err) - m, err := packages.ParseDpkgUpdates(c.Stdout) + m, err := ParseDpkgUpdates(c.Stdout) assert.Nil(t, err) assert.Equal(t, 13, len(m), "detected the right amount of package updates") diff --git a/providers/os/resources/packages/freebsd_packages.go b/providers/os/resources/packages/freebsd_packages.go index 36f25e37ff..89440394d0 100644 --- a/providers/os/resources/packages/freebsd_packages.go +++ b/providers/os/resources/packages/freebsd_packages.go @@ -84,3 +84,8 @@ func (f *FreeBSDPkgManager) List() ([]Package, error) { func (f *FreeBSDPkgManager) Available() (map[string]PackageUpdate, error) { return map[string]PackageUpdate{}, nil } + +func (mpm *FreeBSDPkgManager) Files(name string, version string, arch string) ([]FileRecord, error) { + // not yet implemented + return nil, nil +} diff --git a/providers/os/resources/packages/macos_packages.go b/providers/os/resources/packages/macos_packages.go index 33977c534a..52af7d669a 100644 --- a/providers/os/resources/packages/macos_packages.go +++ b/providers/os/resources/packages/macos_packages.go @@ -34,6 +34,7 @@ func ParseMacOSPackages(input io.Reader) ([]Package, error) { type sysProfilerItems struct { Name string `plist:"_name"` Version string `plist:"version"` + Path string `plist:"path"` } type sysProfiler struct { @@ -56,6 +57,14 @@ func ParseMacOSPackages(input io.Reader) ([]Package, error) { pkgs[i].Name = entry.Name pkgs[i].Version = entry.Version pkgs[i].Format = MacosPkgFormat + pkgs[i].FilesAvailable = PkgFilesIncluded + if entry.Path != "" { + pkgs[i].Files = []FileRecord{ + { + Path: entry.Path, + }, + } + } } return pkgs, nil @@ -86,3 +95,8 @@ func (mpm *MacOSPkgManager) List() ([]Package, error) { func (mpm *MacOSPkgManager) Available() (map[string]PackageUpdate, error) { return nil, errors.New("cannot determine available packages for macOS") } + +func (mpm *MacOSPkgManager) Files(name string, version string, arch string) ([]FileRecord, error) { + // nothing extra to be done here since the list is already included in the package list + return nil, nil +} diff --git a/providers/os/resources/packages/macos_packages_test.go b/providers/os/resources/packages/macos_packages_test.go index b2a2019c7a..1dd6aa0b82 100644 --- a/providers/os/resources/packages/macos_packages_test.go +++ b/providers/os/resources/packages/macos_packages_test.go @@ -30,8 +30,12 @@ func TestMacOsXPackageParser(t *testing.T) { assert.Equal(t, "Preview", m[0].Name, "pkg name detected") assert.Equal(t, "10.0", m[0].Version, "pkg version detected") assert.Equal(t, packages.MacosPkgFormat, m[0].Format, "pkg format detected") + assert.Equal(t, packages.PkgFilesIncluded, m[0].FilesAvailable) + assert.Equal(t, []packages.FileRecord{{Path: "/Applications/Preview.app"}}, m[0].Files) assert.Equal(t, "Contacts", m[1].Name, "pkg name detected") assert.Equal(t, "11.0", m[1].Version, "pkg version detected") - assert.Equal(t, packages.MacosPkgFormat, m[0].Format, "pkg format detected") + assert.Equal(t, packages.MacosPkgFormat, m[1].Format, "pkg format detected") + assert.Equal(t, packages.PkgFilesIncluded, m[1].FilesAvailable) + assert.Equal(t, []packages.FileRecord{{Path: "/Applications/Contacts.app"}}, m[1].Files) } diff --git a/providers/os/resources/packages/opkg_packages.go b/providers/os/resources/packages/opkg_packages.go index 9c2dd88ad9..d4279ddf27 100644 --- a/providers/os/resources/packages/opkg_packages.go +++ b/providers/os/resources/packages/opkg_packages.go @@ -172,3 +172,8 @@ func (opkg *OpkgPkgManager) ListFromFile() ([]Package, error) { func (opkg *OpkgPkgManager) Available() (map[string]PackageUpdate, error) { return map[string]PackageUpdate{}, nil } + +func (opkg *OpkgPkgManager) Files(name string, version string, arch string) ([]FileRecord, error) { + // not yet implemented + return nil, nil +} diff --git a/providers/os/resources/packages/opkg_packages_test.go b/providers/os/resources/packages/opkg_packages_test.go index 2d65638105..2f660a5994 100644 --- a/providers/os/resources/packages/opkg_packages_test.go +++ b/providers/os/resources/packages/opkg_packages_test.go @@ -25,8 +25,7 @@ firewall - 2016-11-29-1` m := packages.ParseOpkgListPackagesCommand(strings.NewReader(pkgList)) assert.Equal(t, 5, len(m), "detected the right amount of packages") - var p packages.Package - p = packages.Package{ + p := packages.Package{ Name: "busybox", Version: "1.24.2-1", Format: packages.OpkgPkgFormat, diff --git a/providers/os/resources/packages/packages.go b/providers/os/resources/packages/packages.go index 3ae36dff8a..4b9566d39e 100644 --- a/providers/os/resources/packages/packages.go +++ b/providers/os/resources/packages/packages.go @@ -8,6 +8,20 @@ import ( "go.mondoo.com/cnquery/v10/providers/os/connection/shared" ) +type PkgFilesAvailable int + +const ( + // PkgFilesNotAvailable means that the package manager does not provide any file information about the packages. + // This is the default value. + PkgFilesNotAvailable PkgFilesAvailable = 0 + // PkgFilesIncluded means that the package manager includes the files in the package metadata and can be queried + // via the List function. + PkgFilesIncluded PkgFilesAvailable = 1 + // PkgFilesAsync means that the package manager does not include the files in the package metadata and needs to be + // queried asynchronously via the Files function. + PkgFilesAsync PkgFilesAvailable = 2 +) + type Package struct { Name string `json:"name"` Version string `json:"version"` @@ -27,6 +41,29 @@ type Package struct { // Package CPE CPE string `json:"cpe,omitempty"` + + // Package files (optional, only for some package managers) + FilesAvailable PkgFilesAvailable `json:"files_available,omitempty"` + Files []FileRecord `json:"files,omitempty"` +} + +type FileRecord struct { + Path string `json:"path"` + Digest PkgDigest `json:"digest"` + FileInfo PkgFileInfo `json:"permission"` +} + +type PkgDigest struct { + Value string `json:"value"` + Algorithm string `json:"type"` +} + +type PkgFileInfo struct { + Size int64 `json:"size"` + Mode uint16 `json:"mode"` + Flags int32 `json:"flags"` + Owner string `json:"owner"` + Group string `json:"group"` } // extends Package to store available version @@ -40,8 +77,12 @@ type PackageUpdate struct { type OperatingSystemPkgManager interface { Name() string + // List returns a list of Packages List() ([]Package, error) + // Available returns a map of available package updates from the perspective of the package manager Available() (map[string]PackageUpdate, error) + // Files returns a list of files on disk for a given package + Files(name string, version string, arch string) ([]FileRecord, error) } // this will find the right package manager for the operating system diff --git a/providers/os/resources/packages/packages_test.go b/providers/os/resources/packages/packages_test.go new file mode 100644 index 0000000000..56878c1044 --- /dev/null +++ b/providers/os/resources/packages/packages_test.go @@ -0,0 +1,13 @@ +// Copyright (c) Mondoo, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +package packages + +func findPkg(pkgs []Package, name string) Package { + for _, p := range pkgs { + if p.Name == name { + return p + } + } + panic("package not found: " + name) +} diff --git a/providers/os/resources/packages/pacman_packages.go b/providers/os/resources/packages/pacman_packages.go index 925623dabe..a37b4fd7e6 100644 --- a/providers/os/resources/packages/pacman_packages.go +++ b/providers/os/resources/packages/pacman_packages.go @@ -68,3 +68,8 @@ func (ppm *PacmanPkgManager) List() ([]Package, error) { func (ppm *PacmanPkgManager) Available() (map[string]PackageUpdate, error) { return nil, errors.New("Available() not implemented for pacman") } + +func (ppm *PacmanPkgManager) Files(name string, version string, arch string) ([]FileRecord, error) { + // not yet implemented + return nil, nil +} diff --git a/providers/os/resources/packages/pacman_packages_test.go b/providers/os/resources/packages/pacman_packages_test.go index 6cd0b89b91..d80296037c 100644 --- a/providers/os/resources/packages/pacman_packages_test.go +++ b/providers/os/resources/packages/pacman_packages_test.go @@ -35,8 +35,7 @@ zziplib 0.13.67-1` m := packages.ParsePacmanPackages(pf, strings.NewReader(pkgList)) assert.Equal(t, 8, len(m), "detected the right amount of packages") - var p packages.Package - p = packages.Package{ + p := packages.Package{ Name: "qpdfview", Version: "0.4.17beta1-4.1", PUrl: "pkg:alpm/arch/qpdfview@0.4.17beta1-4.1?distro=arch", diff --git a/providers/os/resources/packages/rpm_packages.go b/providers/os/resources/packages/rpm_packages.go index 1d5d040ecf..4d268cd962 100644 --- a/providers/os/resources/packages/rpm_packages.go +++ b/providers/os/resources/packages/rpm_packages.go @@ -5,7 +5,6 @@ package packages import ( "bufio" - "bytes" "fmt" "github.com/package-url/packageurl-go" "go.mondoo.com/cnquery/v10/providers/os/resources/cpe" @@ -58,26 +57,32 @@ func ParseRpmPackages(pf *inventory.Platform, input io.Reader) []Package { if arch == "(none)" { arch = "" } + pkg := newRpmPackage(pf, name, version, arch, epoch, m[5]) + pkg.FilesAvailable = PkgFilesAsync // when we use commands we need to fetch the files async + pkgs = append(pkgs, pkg) - // NOTE that we do not have the vendor of the package itself, we could try to parse it from the vendor - // but that will also not be reliable. We may incorporate the cpe dictionary in the future but that would - // increase the binary. - cpe, _ := cpe.NewPackage2Cpe(name, name, version, arch, epoch) - pkgs = append(pkgs, Package{ - Name: name, - Version: version, - Epoch: epoch, - Arch: arch, - Description: m[5], - Format: RpmPkgFormat, - PUrl: purl.NewPackageUrl(pf, name, version, arch, epoch, packageurl.TypeRPM), - CPE: cpe, - }) } } return pkgs } +func newRpmPackage(pf *inventory.Platform, name, version, arch, epoch, description string) Package { + // NOTE that we do not have the vendor of the package itself, we could try to parse it from the vendor + // but that will also not be reliable. We may incorporate the cpe dictionary in the future but that would + // increase the binary. + cpe, _ := cpe.NewPackage2Cpe(name, name, version, arch, epoch) + return Package{ + Name: name, + Version: version, + Epoch: epoch, + Arch: arch, + Description: description, + Format: RpmPkgFormat, + PUrl: purl.NewPackageUrl(pf, name, version, arch, epoch, packageurl.TypeRPM), + CPE: cpe, + } +} + // RpmPkgManager is the package manager for Redhat, CentOS, Oracle, Photon and Suse // it support two modes: runtime where the rpm command is available and static analysis for images (e.g. container tar) // If the RpmPkgManager is used in static mode, it extracts the rpm database from the system and copies it to the local @@ -240,8 +245,6 @@ func (rpm *RpmPkgManager) staticList() ([]Package, error) { } log.Debug().Str("rpmdb", rpmTmpDir).Msg("mql[packages]> cached rpm database locally") - - packages := bytes.Buffer{} db, err := rpmdb.Open(tmpRpmDBFile) if err != nil { return nil, err @@ -250,11 +253,39 @@ func (rpm *RpmPkgManager) staticList() ([]Package, error) { if err != nil { return nil, err } + + resultList := []Package{} for _, pkg := range pkgList { - packages.WriteString(fmt.Sprintf("%s %d:%s-%s %s %s\n", pkg.Name, pkg.EpochNum(), pkg.Version, pkg.Release, pkg.Arch, pkg.Summary)) + rpmPkg := newRpmPackage(rpm.platform, pkg.Name, pkg.Version, pkg.Arch, strconv.Itoa(pkg.EpochNum()), pkg.Summary) + + // determine all files attached + records := []FileRecord{} + files, err := pkg.InstalledFiles() + if err == nil { + for _, record := range files { + records = append(records, FileRecord{ + Path: record.Path, + Digest: PkgDigest{ + Value: record.Digest, + Algorithm: pkg.DigestAlgorithm.String(), + }, + FileInfo: PkgFileInfo{ + Mode: record.Mode, + Flags: int32(record.Flags), + Owner: record.Username, + Group: record.Groupname, + Size: int64(record.Size), + }, + }) + } + } + + rpmPkg.FilesAvailable = PkgFilesIncluded + rpmPkg.Files = records + resultList = append(resultList, rpmPkg) } - return ParseRpmPackages(rpm.platform, &packages), nil + return resultList, nil } // TODO: Available() not implemented for RpmFileSystemManager @@ -263,6 +294,28 @@ func (rpm *RpmPkgManager) staticAvailable() (map[string]PackageUpdate, error) { return map[string]PackageUpdate{}, nil } +func (rpm *RpmPkgManager) Files(name string, version string, arch string) ([]FileRecord, error) { + if rpm.isStaticAnalysis() { + // nothing to do since the data is already attached to the package + return nil, nil + } else { + // we need to fetch the files from the running system + cmd, err := rpm.conn.RunCommand("rpm -ql " + name) + if err != nil { + return nil, errors.Wrap(err, "could not read package files") + } + fileRecords := []FileRecord{} + scanner := bufio.NewScanner(cmd.Stdout) + for scanner.Scan() { + line := scanner.Text() + fileRecords = append(fileRecords, FileRecord{ + Path: line, + }) + } + return fileRecords, nil + } +} + // SusePkgManager overwrites the normal RPM handler type SusePkgManager struct { RpmPkgManager diff --git a/providers/os/resources/packages/rpm_packages_test.go b/providers/os/resources/packages/rpm_packages_test.go index c8355bbcc6..2e00988844 100644 --- a/providers/os/resources/packages/rpm_packages_test.go +++ b/providers/os/resources/packages/rpm_packages_test.go @@ -1,7 +1,7 @@ // Copyright (c) Mondoo, Inc. // SPDX-License-Identifier: BUSL-1.1 -package packages_test +package packages import ( "bytes" @@ -11,13 +11,11 @@ import ( "path/filepath" "testing" - "go.mondoo.com/cnquery/v10/providers-sdk/v1/inventory" - rpmdb "github.com/knqyf263/go-rpmdb/pkg" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "go.mondoo.com/cnquery/v10/providers-sdk/v1/inventory" "go.mondoo.com/cnquery/v10/providers/os/connection/mock" - "go.mondoo.com/cnquery/v10/providers/os/resources/packages" ) func TestRedhat7Parser(t *testing.T) { @@ -41,66 +39,94 @@ func TestRedhat7Parser(t *testing.T) { t.Fatal(err) } - m := packages.ParseRpmPackages(pf, c.Stdout) + m := ParseRpmPackages(pf, c.Stdout) assert.Equal(t, 144, len(m), "detected the right amount of packages") - var p packages.Package - p = packages.Package{ - Name: "ncurses-base", - Version: "5.9-14.20130511.el7_4", - Arch: "noarch", - Description: "Descriptions of common terminals", - PUrl: "pkg:rpm/rhel/ncurses-base@5.9-14.20130511.el7_4?arch=noarch&distro=rhel-7.4", - CPE: "cpe:2.3:a:ncurses-base:ncurses-base:5.9-14.20130511.el7_4:noarch:*:*:*:*:*:*", - Format: packages.RpmPkgFormat, + p := Package{ + Name: "ncurses-base", + Version: "5.9-14.20130511.el7_4", + Arch: "noarch", + Description: "Descriptions of common terminals", + PUrl: "pkg:rpm/rhel/ncurses-base@5.9-14.20130511.el7_4?arch=noarch&distro=rhel-7.4", + CPE: "cpe:2.3:a:ncurses-base:ncurses-base:5.9-14.20130511.el7_4:noarch:*:*:*:*:*:*", + Format: RpmPkgFormat, + FilesAvailable: PkgFilesAsync, + } + assert.Equal(t, findPkg(m, p.Name), p, p.Name) + + p = Package{ + Name: "libstdc++", + Version: "4.8.5-28.el7_5.1", + Arch: "x86_64", + Description: "GNU Standard C++ Library", + PUrl: "pkg:rpm/rhel/libstdc%2B%2B@4.8.5-28.el7_5.1?arch=x86_64&distro=rhel-7.4", + CPE: "cpe:2.3:a:libstdc++:libstdc\\+\\+:4.8.5-28.el7_5.1:x86_64:*:*:*:*:*:*", + Format: RpmPkgFormat, + FilesAvailable: PkgFilesAsync, } - assert.Contains(t, m, p, "ncurses-base") - - p = packages.Package{ - Name: "libstdc++", - Version: "4.8.5-28.el7_5.1", - Arch: "x86_64", - Description: "GNU Standard C++ Library", - PUrl: "pkg:rpm/rhel/libstdc%2B%2B@4.8.5-28.el7_5.1?arch=x86_64&distro=rhel-7.4", - CPE: "cpe:2.3:a:libstdc++:libstdc\\+\\+:4.8.5-28.el7_5.1:x86_64:*:*:*:*:*:*", - Format: packages.RpmPkgFormat, + assert.Equal(t, findPkg(m, p.Name), p, p.Name) + + p = Package{ + Name: "iputils", + Version: "20160308-10.el7", + Arch: "x86_64", + Description: "Network monitoring tools including ping", + PUrl: "pkg:rpm/rhel/iputils@20160308-10.el7?arch=x86_64&distro=rhel-7.4", + CPE: "cpe:2.3:a:iputils:iputils:20160308-10.el7:x86_64:*:*:*:*:*:*", + Format: RpmPkgFormat, + FilesAvailable: PkgFilesAsync, } - assert.Contains(t, m, p, "libstdc detected") - - p = packages.Package{ - Name: "iputils", - Version: "20160308-10.el7", - Arch: "x86_64", - Description: "Network monitoring tools including ping", - PUrl: "pkg:rpm/rhel/iputils@20160308-10.el7?arch=x86_64&distro=rhel-7.4", - CPE: "cpe:2.3:a:iputils:iputils:20160308-10.el7:x86_64:*:*:*:*:*:*", - Format: packages.RpmPkgFormat, + assert.Equal(t, findPkg(m, p.Name), p, p.Name) + + p = Package{ + Name: "openssl-libs", + Version: "1:1.0.2k-12.el7", + Epoch: "1", + Arch: "x86_64", + Description: "A general purpose cryptography library with TLS implementation", + PUrl: "pkg:rpm/rhel/openssl-libs@1%3A1.0.2k-12.el7?arch=x86_64&distro=rhel-7.4&epoch=1", + CPE: "cpe:2.3:a:openssl-libs:openssl-libs:1:x86_64:*:*:*:*:1:*", + Format: RpmPkgFormat, + FilesAvailable: PkgFilesAsync, } - assert.Contains(t, m, p, "gpg-pubkey detected") - - p = packages.Package{ - Name: "openssl-libs", - Version: "1:1.0.2k-12.el7", - Epoch: "1", - Arch: "x86_64", - Description: "A general purpose cryptography library with TLS implementation", - PUrl: "pkg:rpm/rhel/openssl-libs@1%3A1.0.2k-12.el7?arch=x86_64&distro=rhel-7.4&epoch=1", - CPE: "cpe:2.3:a:openssl-libs:openssl-libs:1:x86_64:*:*:*:*:1:*", - Format: packages.RpmPkgFormat, + assert.Equal(t, findPkg(m, p.Name), p, p.Name) + + p = Package{ + Name: "dbus-libs", + Version: "1:1.10.24-7.el7", + Epoch: "1", + Arch: "x86_64", + Description: "Libraries for accessing D-BUS", + PUrl: "pkg:rpm/rhel/dbus-libs@1%3A1.10.24-7.el7?arch=x86_64&distro=rhel-7.4&epoch=1", + CPE: "cpe:2.3:a:dbus-libs:dbus-libs:1:x86_64:*:*:*:*:1:*", + Format: RpmPkgFormat, + FilesAvailable: PkgFilesAsync, } - assert.Contains(t, m, p, "gpg-pubkey detected") - - p = packages.Package{ - Name: "dbus-libs", - Version: "1:1.10.24-7.el7", - Epoch: "1", - Arch: "x86_64", - Description: "Libraries for accessing D-BUS", - PUrl: "pkg:rpm/rhel/dbus-libs@1%3A1.10.24-7.el7?arch=x86_64&distro=rhel-7.4&epoch=1", - CPE: "cpe:2.3:a:dbus-libs:dbus-libs:1:x86_64:*:*:*:*:1:*", - Format: packages.RpmPkgFormat, + assert.Equal(t, findPkg(m, p.Name), p, p.Name) + + // fetch package files + p = Package{ + Name: "hostname", + Version: "3.13-3.el7", + Epoch: "", + Arch: "x86_64", + Description: "Utility to set/show the host name or domain name", + PUrl: "pkg:rpm/rhel/hostname@3.13-3.el7?arch=x86_64&distro=rhel-7.4", + CPE: "cpe:2.3:a:hostname:hostname:3.13-3.el7:x86_64:*:*:*:*:*:*", + Format: RpmPkgFormat, + FilesAvailable: PkgFilesAsync, } - assert.Contains(t, m, p, "gpg-pubkey detected") + assert.Equal(t, findPkg(m, p.Name), p, p.Name) + + mgr := &RpmPkgManager{ + conn: mock, + platform: pf, + } + pkgFiles, err := mgr.Files(p.Name, p.Version, p.Arch) + require.NoError(t, err) + assert.Equal(t, 4, len(pkgFiles), "detected the right amount of package files") + assert.Contains(t, pkgFiles, FileRecord{Path: "/usr/bin/dnsdomainname"}) + assert.Contains(t, pkgFiles, FileRecord{Path: "/usr/share/man/man1/ypdomainname.1.gz"}) } func TestRedhat6Parser(t *testing.T) { @@ -124,53 +150,56 @@ func TestRedhat6Parser(t *testing.T) { t.Fatal(err) } - m := packages.ParseRpmPackages(pf, c.Stdout) + m := ParseRpmPackages(pf, c.Stdout) assert.Equal(t, 8, len(m), "detected the right amount of packages") - var p packages.Package - p = packages.Package{ - Name: "ElectricFence", - Version: "2.1-3", - Arch: "i386", - Description: "A debugger which detects memory allocation violations.", - PUrl: "pkg:rpm/rhel/ElectricFence@2.1-3?arch=i386&distro=rhel-6.2", - CPE: "cpe:2.3:a:electricfence:electricfence:2.1-3:i386:*:*:*:*:*:*", - Format: packages.RpmPkgFormat, + p := Package{ + Name: "ElectricFence", + Version: "2.1-3", + Arch: "i386", + Description: "A debugger which detects memory allocation violations.", + PUrl: "pkg:rpm/rhel/ElectricFence@2.1-3?arch=i386&distro=rhel-6.2", + CPE: "cpe:2.3:a:electricfence:electricfence:2.1-3:i386:*:*:*:*:*:*", + Format: RpmPkgFormat, + FilesAvailable: PkgFilesAsync, } assert.Contains(t, m, p, "ElectricFence") - p = packages.Package{ - Name: "shadow-utils", - Version: "1:19990827-10", - Epoch: "1", - Arch: "i386", - Description: "Utilities for managing shadow password files and user/group accounts.", - PUrl: "pkg:rpm/rhel/shadow-utils@1%3A19990827-10?arch=i386&distro=rhel-6.2&epoch=1", - CPE: "cpe:2.3:a:shadow-utils:shadow-utils:1:i386:*:*:*:*:1:*", - Format: packages.RpmPkgFormat, + p = Package{ + Name: "shadow-utils", + Version: "1:19990827-10", + Epoch: "1", + Arch: "i386", + Description: "Utilities for managing shadow password files and user/group accounts.", + PUrl: "pkg:rpm/rhel/shadow-utils@1%3A19990827-10?arch=i386&distro=rhel-6.2&epoch=1", + CPE: "cpe:2.3:a:shadow-utils:shadow-utils:1:i386:*:*:*:*:1:*", + Format: RpmPkgFormat, + FilesAvailable: PkgFilesAsync, } assert.Contains(t, m, p, "shadow-utils") - p = packages.Package{ - Name: "arpwatch", - Version: "1:2.1a4-19", - Epoch: "1", - Arch: "i386", - Description: "Network monitoring tools for tracking IP addresses on a network.", - PUrl: "pkg:rpm/rhel/arpwatch@1%3A2.1a4-19?arch=i386&distro=rhel-6.2&epoch=1", - CPE: "cpe:2.3:a:arpwatch:arpwatch:1:i386:*:*:*:*:1:*", - Format: packages.RpmPkgFormat, + p = Package{ + Name: "arpwatch", + Version: "1:2.1a4-19", + Epoch: "1", + Arch: "i386", + Description: "Network monitoring tools for tracking IP addresses on a network.", + PUrl: "pkg:rpm/rhel/arpwatch@1%3A2.1a4-19?arch=i386&distro=rhel-6.2&epoch=1", + CPE: "cpe:2.3:a:arpwatch:arpwatch:1:i386:*:*:*:*:1:*", + Format: RpmPkgFormat, + FilesAvailable: PkgFilesAsync, } assert.Contains(t, m, p, "arpwatch") - p = packages.Package{ - Name: "bash", - Version: "1.14.7-22", - Arch: "i386", - Description: "The GNU Bourne Again shell (bash) version 1.14.", - PUrl: "pkg:rpm/rhel/bash@1.14.7-22?arch=i386&distro=rhel-6.2", - CPE: "cpe:2.3:a:bash:bash:1.14.7-22:i386:*:*:*:*:*:*", - Format: packages.RpmPkgFormat, + p = Package{ + Name: "bash", + Version: "1.14.7-22", + Arch: "i386", + Description: "The GNU Bourne Again shell (bash) version 1.14.", + PUrl: "pkg:rpm/rhel/bash@1.14.7-22?arch=i386&distro=rhel-6.2", + CPE: "cpe:2.3:a:bash:bash:1.14.7-22:i386:*:*:*:*:*:*", + Format: RpmPkgFormat, + FilesAvailable: PkgFilesAsync, } assert.Contains(t, m, p, "bash") } @@ -219,40 +248,42 @@ func TestPhoton4ImageParser(t *testing.T) { }, } - m := packages.ParseRpmPackages(pf, &packageList) + m := ParseRpmPackages(pf, &packageList) assert.Equal(t, 36, len(m), "detected the right amount of packages") - var p packages.Package - p = packages.Package{ - Name: "ncurses-libs", - Version: "6.2-6.ph4", - Arch: "x86_64", - Description: "Ncurses Libraries", - PUrl: "pkg:rpm/photon/ncurses-libs@6.2-6.ph4?arch=x86_64&distro=photon-3.0", - CPE: "cpe:2.3:a:ncurses-libs:ncurses-libs:6.2-6.ph4:x86_64:*:*:*:*:*:*", - Format: packages.RpmPkgFormat, + p := Package{ + Name: "ncurses-libs", + Version: "6.2-6.ph4", + Arch: "x86_64", + Description: "Ncurses Libraries", + PUrl: "pkg:rpm/photon/ncurses-libs@6.2-6.ph4?arch=x86_64&distro=photon-3.0", + CPE: "cpe:2.3:a:ncurses-libs:ncurses-libs:6.2-6.ph4:x86_64:*:*:*:*:*:*", + Format: RpmPkgFormat, + FilesAvailable: PkgFilesAsync, } assert.Contains(t, m, p, "ncurses-libs") - p = packages.Package{ - Name: "bash", - Version: "5.0-2.ph4", - Arch: "x86_64", - Description: "Bourne-Again SHell", - PUrl: "pkg:rpm/photon/bash@5.0-2.ph4?arch=x86_64&distro=photon-3.0", - CPE: "cpe:2.3:a:bash:bash:5.0-2.ph4:x86_64:*:*:*:*:*:*", - Format: packages.RpmPkgFormat, + p = Package{ + Name: "bash", + Version: "5.0-2.ph4", + Arch: "x86_64", + Description: "Bourne-Again SHell", + PUrl: "pkg:rpm/photon/bash@5.0-2.ph4?arch=x86_64&distro=photon-3.0", + CPE: "cpe:2.3:a:bash:bash:5.0-2.ph4:x86_64:*:*:*:*:*:*", + Format: RpmPkgFormat, + FilesAvailable: PkgFilesAsync, } assert.Contains(t, m, p, "bash") - p = packages.Package{ - Name: "sqlite-libs", - Version: "3.38.5-1.ph4", - Arch: "x86_64", - Description: "sqlite3 library", - PUrl: "pkg:rpm/photon/sqlite-libs@3.38.5-1.ph4?arch=x86_64&distro=photon-3.0", - CPE: "cpe:2.3:a:sqlite-libs:sqlite-libs:3.38.5-1.ph4:x86_64:*:*:*:*:*:*", - Format: packages.RpmPkgFormat, + p = Package{ + Name: "sqlite-libs", + Version: "3.38.5-1.ph4", + Arch: "x86_64", + Description: "sqlite3 library", + PUrl: "pkg:rpm/photon/sqlite-libs@3.38.5-1.ph4?arch=x86_64&distro=photon-3.0", + CPE: "cpe:2.3:a:sqlite-libs:sqlite-libs:3.38.5-1.ph4:x86_64:*:*:*:*:*:*", + Format: RpmPkgFormat, + FilesAvailable: PkgFilesAsync, } assert.Contains(t, m, p, "sqlite-libs") } diff --git a/providers/os/resources/packages/scratch.go b/providers/os/resources/packages/scratch.go index 3f93dcb154..575fb011e5 100644 --- a/providers/os/resources/packages/scratch.go +++ b/providers/os/resources/packages/scratch.go @@ -24,3 +24,8 @@ func (dpm *ScratchPkgManager) List() ([]Package, error) { func (dpm *ScratchPkgManager) Available() (map[string]PackageUpdate, error) { return map[string]PackageUpdate{}, nil } + +func (dpm *ScratchPkgManager) Files(nname string, version string, arch string) ([]FileRecord, error) { + // not yet implemented + return nil, nil +} diff --git a/providers/os/resources/packages/solaris_packages.go b/providers/os/resources/packages/solaris_packages.go index 222c847bfd..cfc8b0281c 100644 --- a/providers/os/resources/packages/solaris_packages.go +++ b/providers/os/resources/packages/solaris_packages.go @@ -100,3 +100,8 @@ func (s *SolarisPkgManager) List() ([]Package, error) { func (s *SolarisPkgManager) Available() (map[string]PackageUpdate, error) { return map[string]PackageUpdate{}, nil } + +func (s *SolarisPkgManager) Files(name string, version string, arch string) ([]FileRecord, error) { + // not yet implemented + return nil, nil +} diff --git a/providers/os/resources/packages/solaris_packages_test.go b/providers/os/resources/packages/solaris_packages_test.go index ebe8d28d0e..b71a42fc22 100644 --- a/providers/os/resources/packages/solaris_packages_test.go +++ b/providers/os/resources/packages/solaris_packages_test.go @@ -1,7 +1,7 @@ // Copyright (c) Mondoo, Inc. // SPDX-License-Identifier: BUSL-1.1 -package packages_test +package packages import ( "path/filepath" @@ -12,7 +12,6 @@ import ( "github.com/stretchr/testify/require" "go.mondoo.com/cnquery/v10/providers-sdk/v1/inventory" "go.mondoo.com/cnquery/v10/providers/os/connection/mock" - "go.mondoo.com/cnquery/v10/providers/os/resources/packages" ) func TestFmriParser(t *testing.T) { @@ -22,7 +21,7 @@ func TestFmriParser(t *testing.T) { // Version: 0.5.11 // Branch: 0.175.2.0.0.34.0 - sp, err := packages.ParseSolarisFmri("pkg://solaris/entire@0.5.11,5.11-0.175.1.0.0.24.2:20120919T190135Z") + sp, err := ParseSolarisFmri("pkg://solaris/entire@0.5.11,5.11-0.175.1.0.0.24.2:20120919T190135Z") require.NoError(t, err) assert.Equal(t, "entire", sp.Name) assert.Equal(t, "solaris", sp.Publisher) @@ -45,7 +44,7 @@ func TestFmriParser(t *testing.T) { // Size: 101.36 kB // FMRI: pkg://solaris/x11/library/libxscrnsaver@1.2.2,5.11-0.175.1.0.0.24.1317:20120904T180021Z - sp, err = packages.ParseSolarisFmri("pkg://solaris/x11/library/libxscrnsaver@1.2.2,5.11-0.175.1.0.0.24.1317:20120904T180021Z") + sp, err = ParseSolarisFmri("pkg://solaris/x11/library/libxscrnsaver@1.2.2,5.11-0.175.1.0.0.24.1317:20120904T180021Z") require.NoError(t, err) assert.Equal(t, "x11/library/libxscrnsaver", sp.Name) assert.Equal(t, "solaris", sp.Publisher) @@ -60,28 +59,27 @@ pkg://solaris/compress/bzip2@1.0.6,5.11-0.175.1.0.0.24.0:20120904T170602Z i-- pkg://solaris/compress/gzip@1.4,5.11-0.175.1.0.0.24.0:20120904T170603Z i-- pkg://solaris/compress/p7zip@9.20.1,5.11-0.175.1.0.0.24.0:20120904T170605Z i--` - m := packages.ParseSolarisPackages(strings.NewReader(pkgList)) + m := ParseSolarisPackages(strings.NewReader(pkgList)) assert.Equal(t, 4, len(m), "detected the right amount of packages") - var p packages.Package - p = packages.Package{ + p := Package{ Name: "archiver/gnu-tar", Version: "1.26", - Format: packages.SolarisPkgFormat, + Format: SolarisPkgFormat, } assert.Contains(t, m, p, "pkg detected") - p = packages.Package{ + p = Package{ Name: "compress/bzip2", Version: "1.0.6", - Format: packages.SolarisPkgFormat, + Format: SolarisPkgFormat, } assert.Contains(t, m, p, "pkg detected") - p = packages.Package{ + p = Package{ Name: "compress/p7zip", Version: "9.20.1", - Format: packages.SolarisPkgFormat, + Format: SolarisPkgFormat, } assert.Contains(t, m, p, "pkg detected") } @@ -95,17 +93,17 @@ func TestSolarisManager(t *testing.T) { }) require.NoError(t, err) - pkgManager, err := packages.ResolveSystemPkgManager(conn) + pkgManager, err := ResolveSystemPkgManager(conn) require.NoError(t, err) pkgList, err := pkgManager.List() require.NoError(t, err) assert.Equal(t, 146, len(pkgList)) - p := packages.Package{ + p := Package{ Name: "compress/p7zip", Version: "9.20.1", - Format: packages.SolarisPkgFormat, + Format: SolarisPkgFormat, } assert.Contains(t, pkgList, p, "pkg detected") } diff --git a/providers/os/resources/packages/testdata/packages_dpkg.toml b/providers/os/resources/packages/testdata/packages_dpkg.toml index f5cd93e07c..bb61e691c4 100644 --- a/providers/os/resources/packages/testdata/packages_dpkg.toml +++ b/providers/os/resources/packages/testdata/packages_dpkg.toml @@ -227,4 +227,19 @@ Description: Dynamic library for security auditing applications to use the audit framework. It is used to monitor systems for security related events. Homepage: http://people.redhat.com/sgrubb/audit/ -""" \ No newline at end of file +""" + +[files."/var/lib/dpkg/info/libss2:amd64.list"] +content=""" +/. +/lib +/lib/aarch64-linux-gnu +/lib/aarch64-linux-gnu/libss.so.2.0 +/usr +/usr/share +/usr/share/doc +/usr/share/doc/libss2 +/usr/share/doc/libss2/copyright +/lib/aarch64-linux-gnu/libss.so.2 +/usr/share/doc/libss2/changelog.Debian.gz +""" diff --git a/providers/os/resources/packages/testdata/packages_redhat7.toml b/providers/os/resources/packages/testdata/packages_redhat7.toml index 2ffb1b25b6..bb6c085cbc 100644 --- a/providers/os/resources/packages/testdata/packages_redhat7.toml +++ b/providers/os/resources/packages/testdata/packages_redhat7.toml @@ -1,3 +1,6 @@ +[commands."command -v rpm"] +stdout="/usr/bin/rpm" + [commands."rpm -qa --queryformat '%{NAME} %{EPOCHNUM}:%{VERSION}-%{RELEASE} %{ARCH} %{SUMMARY}\\n'"] stdout=""" tzdata 0:2018e-3.el7 noarch Timezone data @@ -144,4 +147,12 @@ yum 0:3.4.3-158.el7.centos noarch RPM package installer/updater/manager yum-utils 0:1.1.31-46.el7_5 noarch Utilities based around the yum package manager passwd 0:0.79-4.el7 x86_64 An utility for setting or changing passwords using PAM rootfiles 0:8.1-11.el7 noarch The basic required files for the root user's directory +""" + +[commands."rpm -ql hostname"] +stdout=""" +/usr/bin/dnsdomainname +/usr/bin/domainname +/usr/share/man/man1/nisdomainname.1.gz +/usr/share/man/man1/ypdomainname.1.gz """ \ No newline at end of file diff --git a/providers/os/resources/packages/windows_packages.go b/providers/os/resources/packages/windows_packages.go index 4442fe53be..1fec9b6c23 100644 --- a/providers/os/resources/packages/windows_packages.go +++ b/providers/os/resources/packages/windows_packages.go @@ -316,3 +316,8 @@ func ParseWindowsAppPackages(input io.Reader) ([]Package, error) { func (win *WinPkgManager) Available() (map[string]PackageUpdate, error) { return map[string]PackageUpdate{}, nil } + +func (win *WinPkgManager) Files(name string, version string, arch string) ([]FileRecord, error) { + // not yet implemented + return nil, nil +}