From fb201aa9c57a6dec5bb9efdd601b9d8ee7833135 Mon Sep 17 00:00:00 2001 From: Salim Afiune Maya Date: Wed, 4 Dec 2024 15:28:34 -0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=8E=89=20add=20integration=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Salim Afiune Maya --- cli/reporter/cnspec_report.pb.go | 4 +- policy/cnspec_policy.pb.go | 2 +- policy/scan/scan.pb.go | 4 +- test/providers/os_test.go | 317 ++++++++++++++++++ test/providers/testdata/fs/etc/debian_version | 1 + test/providers/testdata/fs/etc/hostname | 1 + test/providers/testdata/fs/etc/os-release | 9 + .../providers/testdata/fs/var/lib/dpkg/status | 49 +++ 8 files changed, 382 insertions(+), 5 deletions(-) create mode 100644 test/providers/os_test.go create mode 100644 test/providers/testdata/fs/etc/debian_version create mode 100644 test/providers/testdata/fs/etc/hostname create mode 100644 test/providers/testdata/fs/etc/os-release create mode 100644 test/providers/testdata/fs/var/lib/dpkg/status diff --git a/cli/reporter/cnspec_report.pb.go b/cli/reporter/cnspec_report.pb.go index cbc1db0c..c84d3391 100644 --- a/cli/reporter/cnspec_report.pb.go +++ b/cli/reporter/cnspec_report.pb.go @@ -3,8 +3,8 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.35.1 -// protoc v5.28.2 +// protoc-gen-go v1.35.2 +// protoc v5.29.0 // source: cnspec_report.proto package reporter diff --git a/policy/cnspec_policy.pb.go b/policy/cnspec_policy.pb.go index 9008e700..ab889eae 100644 --- a/policy/cnspec_policy.pb.go +++ b/policy/cnspec_policy.pb.go @@ -4,7 +4,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.35.2 -// protoc v5.28.3 +// protoc v5.29.0 // source: cnspec_policy.proto package policy diff --git a/policy/scan/scan.pb.go b/policy/scan/scan.pb.go index a0e590df..7c7fa79b 100644 --- a/policy/scan/scan.pb.go +++ b/policy/scan/scan.pb.go @@ -3,8 +3,8 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.35.1 -// protoc v5.28.3 +// protoc-gen-go v1.35.2 +// protoc v5.29.0 // source: scan.proto package scan diff --git a/test/providers/os_test.go b/test/providers/os_test.go new file mode 100644 index 00000000..6b0d9fd0 --- /dev/null +++ b/test/providers/os_test.go @@ -0,0 +1,317 @@ +// Copyright (c) Mondoo, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +package providers + +import ( + "log" + "os" + "os/exec" + "path/filepath" + "sync" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.mondoo.com/cnquery/v11/test" + "go.mondoo.com/cnspec/v11/policy" +) + +var once sync.Once + +// setup builds cnspec locally +func setup() { + // build cnspec + if err := exec.Command("go", "build", "../../apps/cnspec/cnspec.go").Run(); err != nil { + log.Fatalf("building cnspec: %v", err) + } + + // // install local provider + // if err := exec.Command("bash", "-c", "cd ../.. && make providers/build/os providers/install/os").Run(); err != nil { + // log.Fatalf("building os provider: %v", err) + // } + + providersPATH := os.Getenv("PROVIDERS_PATH") + if providersPATH != "" { + + // provider install places the provider in the "$(HOME)/.config/mondoo/providers/${$@_NAME}") but we + // want to test it in isolation. Therefore, we copy the provider to the current directory .providers + osProviderPath := filepath.Join(providersPATH, "os") + if err := os.MkdirAll(osProviderPath, 0755); err != nil { + log.Fatalf("creating directory: %v", err) + } + + if err := exec.Command("cp", "-r", os.ExpandEnv("../../providers/os/dist"), osProviderPath).Run(); err != nil { + log.Fatalf("copying provider: %v", err) + } + } +} + +const mqlPackagesQuery = "packages" + +type mqlPackages []struct { + Packages []struct { + Name string `json:"name,omitempty"` + Version string `json:"version,omitempty"` + } `json:"packages.list,omitempty"` +} + +const mqlPlatformQuery = "asset.platform" + +type mqlPlatform []struct { + Platform string `json:"asset.platform,omitempty"` +} + +type connections []struct { + name string + binary string + args []string + tests []mqlTest +} + +type mqlTest struct { + query string + expected func(*testing.T, test.Runner) +} + +func TestOsProviderSharedTests(t *testing.T) { + once.Do(setup) + + connections := connections{ + { + name: "local", + binary: "./cnspec", + args: []string{"run", "local"}, + tests: []mqlTest{ + { + mqlPackagesQuery, + func(t *testing.T, r test.Runner) { + var c mqlPackages + err := r.Json(&c) + assert.NoError(t, err) + + x := c[0] + assert.NotNil(t, x.Packages) + assert.True(t, len(x.Packages) > 0) + }, + }, + { + mqlPlatformQuery, + func(t *testing.T, r test.Runner) { + var c mqlPlatform + err := r.Json(&c) + assert.NoError(t, err) + + x := c[0] + assert.True(t, len(x.Platform) > 0) + }, + }, + }, + }, + { + name: "fs", + binary: "./cnspec", + args: []string{"run", "fs", "--path", "./testdata/fs"}, + tests: []mqlTest{ + { + mqlPackagesQuery, + func(t *testing.T, r test.Runner) { + var c mqlPackages + err := r.Json(&c) + assert.NoError(t, err) + + x := c[0] + assert.NotNil(t, x.Packages) + assert.True(t, len(x.Packages) > 0) + }, + }, + { + mqlPlatformQuery, + func(t *testing.T, r test.Runner) { + var c mqlPlatform + err := r.Json(&c) + assert.NoError(t, err) + + x := c[0] + assert.Equal(t, "debian", x.Platform) + }, + }, + }, + }, + { + name: "docker", + binary: "./cnspec", + args: []string{"run", "docker", "alpine:latest"}, + tests: []mqlTest{ + { + mqlPackagesQuery, + func(t *testing.T, r test.Runner) { + var c mqlPackages + err := r.Json(&c) + assert.NoError(t, err) + + x := c[0] + assert.NotNil(t, x.Packages) + assert.True(t, len(x.Packages) > 0) + }, + }, + { + mqlPlatformQuery, + func(t *testing.T, r test.Runner) { + var c mqlPlatform + err := r.Json(&c) + assert.NoError(t, err) + + x := c[0] + assert.Equal(t, "alpine", x.Platform) + }, + }, + }, + }, + } + + // iterate over all tests for all connections + for _, cc := range connections { + for _, tt := range cc.tests { + + t.Run(cc.name+"/"+tt.query, func(t *testing.T) { + r := test.NewCliTestRunner(cc.binary, append(cc.args, "-c", tt.query, "-j")...) + err := r.Run() + require.NoError(t, err) + assert.Equal(t, 0, r.ExitCode()) + assert.NotNil(t, r.Stdout()) + assert.NotNil(t, r.Stderr()) + + tt.expected(t, r) + }) + } + } +} + +func TestProvidersEnvVarsLoading(t *testing.T) { + t.Run("command WITHOUT path should not find any package", func(t *testing.T) { + r := test.NewCliTestRunner("./cnspec", "run", "fs", "-c", mqlPackagesQuery, "-j") + err := r.Run() + require.NoError(t, err) + assert.Equal(t, 0, r.ExitCode()) + assert.NotNil(t, r.Stdout()) + assert.NotNil(t, r.Stderr()) + + var c mqlPackages + err = r.Json(&c) + assert.NoError(t, err) + + // No packages + assert.Empty(t, c) + }) + t.Run("command WITH path should find packages", func(t *testing.T) { + os.Setenv("MONDOO_PATH", "./testdata/fs") + defer os.Unsetenv("MONDOO_PATH") + // Note we are not passing the flag "--path ./testdata/fs" + r := test.NewCliTestRunner("./cnspec", "run", "fs", "-c", mqlPackagesQuery, "-j") + err := r.Run() + require.NoError(t, err) + assert.Equal(t, 0, r.ExitCode()) + assert.NotNil(t, r.Stdout()) + assert.NotNil(t, r.Stderr()) + + var c mqlPackages + err = r.Json(&c) + assert.NoError(t, err) + + // Should have packages + if assert.NotEmpty(t, c) { + x := c[0] + assert.NotNil(t, x.Packages) + assert.True(t, len(x.Packages) > 0) + } + }) + + t.Run("command with flags set to not bind to config (ConfigEntry=\"-\")", func(t *testing.T) { + t.Run("should work via direct flag", func(t *testing.T) { + r := test.NewCliTestRunner("./cnspec", "run", "ssh", "localhost", "-c", "ls", "-p", "test", "-v") + err := r.Run() + require.NoError(t, err) + assert.Equal(t, 0, r.ExitCode()) + assert.NotNil(t, r.Stdout()) + if assert.NotNil(t, r.Stderr()) { + assert.Contains(t, string(r.Stderr()), "skipping config binding for password") + assert.Contains(t, string(r.Stderr()), "enabled ssh password authentication") + } + }) + t.Run("should NOT work via config/env-vars", func(t *testing.T) { + os.Setenv("MONDOO_PASSWORD", "test") + defer os.Unsetenv("MONDOO_PASSWORD") + r := test.NewCliTestRunner("./cnspec", "run", "ssh", "localhost", "-c", "ls", "-v") + err := r.Run() + require.NoError(t, err) + assert.Equal(t, 0, r.ExitCode()) + assert.NotNil(t, r.Stdout()) + if assert.NotNil(t, r.Stderr()) { + assert.Contains(t, string(r.Stderr()), "skipping config binding for password") + assert.NotContains(t, string(r.Stderr()), "enabled ssh password authentication") + } + }) + }) +} + +func TestScanFlags(t *testing.T) { + t.Run("successful scan without flags", func(t *testing.T) { + r := test.NewCliTestRunner("./cnspec", "scan", "docker", "alpine:latest", "--json") + err := r.Run() + require.NoError(t, err) + assert.Equal(t, 0, r.ExitCode()) + assert.NotNil(t, r.Stdout()) + assert.NotNil(t, r.Stderr()) + + var c policy.ReportCollection + err = r.Json(&c) + assert.NoError(t, err) + + // Assest must be found + assert.NotEmpty(t, c.Assets) + }) + t.Run("github scan WITHOUT flags", func(t *testing.T) { + // NOTE this will fail but, it will load the flags and fail with the right message + r := test.NewCliTestRunner("./cnspec", "scan", "github", "repo", "foo") + err := r.Run() + require.NoError(t, err) + assert.Equal(t, 0, r.ExitCode()) + assert.NotNil(t, r.Stdout()) + assert.NotNil(t, r.Stderr()) + + assert.Contains(t, string(r.Stderr()), + "a valid GitHub authentication is required", + ) + }) + t.Run("github scan WITH flags but missing app auth key", func(t *testing.T) { + // NOTE this will fail but, it will load the flags and fail with the right message + r := test.NewCliTestRunner("./cnspec", "scan", "github", "repo", "foo", + "--app-id", "123", "--app-installation-id", "456", + ) + err := r.Run() + require.NoError(t, err) + assert.Equal(t, 1, r.ExitCode()) + assert.NotNil(t, r.Stdout()) + assert.NotNil(t, r.Stderr()) + + assert.Contains(t, string(r.Stderr()), + "app-private-key is required for GitHub App authentication", // expected! it means we loaded the flags + ) + }) + t.Run("github scan WITH all required flags for app auth", func(t *testing.T) { + // NOTE this will fail but, it will load the flags and fail with the right message + r := test.NewCliTestRunner("./cnspec", "scan", "github", "repo", "foo", + "--app-id", "123", "--app-installation-id", "456", "--app-private-key", "private-key.pem", + ) + err := r.Run() + require.NoError(t, err) + assert.Equal(t, 1, r.ExitCode()) + assert.NotNil(t, r.Stdout()) + assert.NotNil(t, r.Stderr()) + + assert.Contains(t, string(r.Stderr()), + "could not read private key", // expected! it means we loaded the flags + ) + }) +} diff --git a/test/providers/testdata/fs/etc/debian_version b/test/providers/testdata/fs/etc/debian_version new file mode 100644 index 00000000..7ef6ffee --- /dev/null +++ b/test/providers/testdata/fs/etc/debian_version @@ -0,0 +1 @@ +12.5 \ No newline at end of file diff --git a/test/providers/testdata/fs/etc/hostname b/test/providers/testdata/fs/etc/hostname new file mode 100644 index 00000000..c51793b9 --- /dev/null +++ b/test/providers/testdata/fs/etc/hostname @@ -0,0 +1 @@ +debianfs \ No newline at end of file diff --git a/test/providers/testdata/fs/etc/os-release b/test/providers/testdata/fs/etc/os-release new file mode 100644 index 00000000..6c12857c --- /dev/null +++ b/test/providers/testdata/fs/etc/os-release @@ -0,0 +1,9 @@ +PRETTY_NAME="Debian GNU/Linux 12 (bookworm)" +NAME="Debian GNU/Linux" +VERSION_ID="12" +VERSION="12 (bookworm)" +VERSION_CODENAME=bookworm +ID=debian +HOME_URL="https://www.debian.org/" +SUPPORT_URL="https://www.debian.org/support" +BUG_REPORT_URL="https://bugs.debian.org/" \ No newline at end of file diff --git a/test/providers/testdata/fs/var/lib/dpkg/status b/test/providers/testdata/fs/var/lib/dpkg/status new file mode 100644 index 00000000..f9eca6cb --- /dev/null +++ b/test/providers/testdata/fs/var/lib/dpkg/status @@ -0,0 +1,49 @@ +Package: fdisk +Status: install ok installed +Priority: important +Section: utils +Installed-Size: 427 +Maintainer: Ubuntu Developers +Architecture: amd64 +Multi-Arch: foreign +Source: util-linux +Version: 2.31.1-0.4ubuntu3.1 +Replaces: util-linux (<< 2.30.1-0ubuntu4~) +Depends: libc6 (>= 2.14), libfdisk1 (>= 2.31.1), libmount1 (>= 2.24.2), libncursesw5 (>= 6), libsmartcols1 (>= 2.28~rc1), libtinfo5 (>= 6) +Breaks: util-linux (<< 2.30.1-0ubuntu4~) +Description: collection of partitioning utilities + This package contains the classic fdisk, sfdisk and cfdisk partitioning + utilities from the util-linux suite. + . + The utilities included in this package allow you to partition + your hard disk. The utilities supports both modern and legacy + partition tables (eg. GPT, MBR, etc). + . + The fdisk utility is the classical text-mode utility. + The cfdisk utilitity gives a more userfriendly curses based interface. + The sfdisk utility is mostly for automation and scripting uses. +Important: yes +Original-Maintainer: LaMont Jones + +Package: libpam-runtime +Status: install ok installed +Priority: required +Section: admin +Installed-Size: 300 +Maintainer: Ubuntu Developers +Architecture: all +Multi-Arch: foreign +Source: pam +Version: 1.1.8-3.6ubuntu2 +Replaces: libpam0g-dev, libpam0g-util +Depends: debconf (>= 0.5) | debconf-2.0, debconf (>= 1.5.19) | cdebconf, libpam-modules (>= 1.0.1-6) +Conflicts: libpam0g-util +Conffiles: + /etc/pam.conf 87fc76f18e98ee7d3848f6b81b3391e5 + /etc/pam.d/other 31aa7f2181889ffb00b87df4126d1701 +Description: Runtime support for the PAM library + Contains configuration files and directories required for + authentication to work on Debian systems. This package is required + on almost all installations. +Homepage: http://www.linux-pam.org/ +Original-Maintainer: Steve Langasek \ No newline at end of file