From c4eec38c78da667f38ac3be61a6fd3292ac53ed3 Mon Sep 17 00:00:00 2001 From: Nikita Pivkin Date: Fri, 1 Nov 2024 13:17:15 +0600 Subject: [PATCH] test: unify docker and k8s tests Signed-off-by: Nikita Pivkin --- test/docker_test.go | 167 ++++++++++--------------------------- test/kubernetes_test.go | 177 ++++++++++++++++++---------------------- 2 files changed, 123 insertions(+), 221 deletions(-) diff --git a/test/docker_test.go b/test/docker_test.go index d0d4e9f0..cc19bdb6 100644 --- a/test/docker_test.go +++ b/test/docker_test.go @@ -4,16 +4,11 @@ import ( "context" "fmt" "os" - "path" - "path/filepath" - "strings" "testing" "github.com/aquasecurity/trivy/pkg/iac/rego" - "github.com/aquasecurity/trivy/pkg/iac/scan" "github.com/aquasecurity/trivy/pkg/iac/scanners/dockerfile" - "github.com/liamg/memoryfs" - "github.com/stretchr/testify/assert" + "github.com/aquasecurity/trivy/pkg/iac/scanners/options" "github.com/stretchr/testify/require" builtinrego "github.com/aquasecurity/trivy-checks/pkg/rego" @@ -23,135 +18,61 @@ func init() { builtinrego.RegisterBuiltins() } -func getFileName(fpath string, info os.FileInfo, typePolicy bool) string { - pathParts := strings.Split(fpath, filepath.FromSlash("/")) - fileName := info.Name() - // append test data folder to input file name example Dockerfile.allowed_DS001 - if len(pathParts) > 2 && !typePolicy { - fileName = fmt.Sprintf("%s_%s", fileName, pathParts[len(pathParts)-2]) +func Test_Dockerfile(t *testing.T) { + tests := []struct { + name string + opts []options.ScannerOption + }{ + { + name: "checks from disk", + opts: []options.ScannerOption{ + rego.WithPolicyFilesystem(os.DirFS("../checks/docker")), + rego.WithPolicyDirs("."), + }, + }, + { + name: "embedded checks", + opts: []options.ScannerOption{ + rego.WithEmbeddedPolicies(true), + }, + }, } - return fileName -} - -func addFilesToMemFS(memfs *memoryfs.FS, typePolicy bool, folderName string) error { - base := filepath.Base(folderName) - if err := memfs.MkdirAll(base, 0o700); err != nil { - return err - } - err := filepath.Walk(filepath.FromSlash(folderName), - func(fpath string, info os.FileInfo, err error) error { - if err != nil { - return err - } - if info.IsDir() { - return nil - } - if typePolicy && !rego.IsRegoFile(info.Name()) { - return nil - } - data, err := os.ReadFile(fpath) - if err != nil { - return err - } - fileName := getFileName(fpath, info, typePolicy) - if err := memfs.WriteFile(path.Join(base, fileName), data, 0o644); err != nil { - return err - } - return nil - }) - - if err != nil { - return err - } - return nil -} -func Test_Docker_RegoPoliciesFromDisk(t *testing.T) { - t.Parallel() + testdata := "./testdata/dockerfile" - entries, err := os.ReadDir("./testdata/dockerfile") + entries, err := os.ReadDir(testdata) require.NoError(t, err) - policiesPath, err := filepath.Abs("../checks/docker") - require.NoError(t, err) - scanner := dockerfile.NewScanner( - rego.WithPolicyDirs(filepath.Base(policiesPath)), - rego.WithEmbeddedLibraries(true), - ) - memfs := memoryfs.New() - // add policies - err = addFilesToMemFS(memfs, true, policiesPath) - require.NoError(t, err) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() - // add test data - testDataPath, err := filepath.Abs("./testdata/dockerfile") - require.NoError(t, err) - err = addFilesToMemFS(memfs, false, testDataPath) - require.NoError(t, err) + opts := []options.ScannerOption{ + rego.WithPerResultTracing(true), + rego.WithEmbeddedLibraries(true), + } + opts = append(opts, tt.opts...) - results, err := scanner.ScanFS(context.TODO(), memfs, filepath.Base(testDataPath)) - require.NoError(t, err) + scanner := dockerfile.NewScanner(opts...) - for _, entry := range entries { - if !entry.IsDir() { - continue - } - t.Run(entry.Name(), func(t *testing.T) { + results, err := scanner.ScanFS(context.TODO(), os.DirFS(testdata), ".") require.NoError(t, err) - t.Run(entry.Name(), func(t *testing.T) { - var matched int - for _, result := range results { - if result.Rule().HasID(entry.Name()) && result.Status() == scan.StatusFailed { - if result.Description() != "Specify at least 1 USER command in Dockerfile with non-root user as argument" { - assert.Greater(t, result.Range().GetStartLine(), 0) - assert.Greater(t, result.Range().GetEndLine(), 0) - } - if !strings.HasSuffix(result.Range().GetFilename(), entry.Name()) { - continue - } - matched++ - } - } - assert.Equal(t, 1, matched, "Rule should be matched once") - }) - - }) - } -} - -func Test_Docker_RegoPoliciesEmbedded(t *testing.T) { - t.Parallel() - entries, err := os.ReadDir("./testdata/dockerfile") - require.NoError(t, err) - - scanner := dockerfile.NewScanner(rego.WithEmbeddedPolicies(true), rego.WithEmbeddedLibraries(true)) - srcFS := os.DirFS("../") - - results, err := scanner.ScanFS(context.TODO(), srcFS, "test/testdata/dockerfile") - require.NoError(t, err) - - for _, entry := range entries { - if !entry.IsDir() { - continue - } - t.Run(entry.Name(), func(t *testing.T) { - require.NoError(t, err) - t.Run(entry.Name(), func(t *testing.T) { - var matched bool - for _, result := range results { - if result.Rule().HasID(entry.Name()) && result.Status() == scan.StatusFailed { - if result.Description() != "Specify at least 1 USER command in Dockerfile with non-root user as argument" { - assert.Greater(t, result.Range().GetStartLine(), 0) - assert.Greater(t, result.Range().GetEndLine(), 0) - } - assert.Equal(t, fmt.Sprintf("test/testdata/dockerfile/%s/Dockerfile.denied", entry.Name()), result.Range().GetFilename()) - matched = true - } + for _, entry := range entries { + if !entry.IsDir() { + continue } - assert.True(t, matched) - }) + dirName := entry.Name() + + t.Run(entry.Name(), func(t *testing.T) { + assertChecks(t, dirName, + fmt.Sprintf("%s/Dockerfile.denied", dirName), + fmt.Sprintf("%s/Dockerfile.allowed", dirName), + results, + ) + }) + } }) } } diff --git a/test/kubernetes_test.go b/test/kubernetes_test.go index 5ebd50b6..7a126661 100644 --- a/test/kubernetes_test.go +++ b/test/kubernetes_test.go @@ -4,131 +4,112 @@ import ( "context" "fmt" "os" - "strings" "testing" "github.com/aquasecurity/trivy/pkg/iac/rego" "github.com/aquasecurity/trivy/pkg/iac/scan" "github.com/aquasecurity/trivy/pkg/iac/scanners/kubernetes" + "github.com/aquasecurity/trivy/pkg/iac/scanners/options" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -func Test_Kubernetes_RegoPoliciesFromDisk(t *testing.T) { - t.Parallel() +func Test_Kubenetes(t *testing.T) { + tests := []struct { + name string + opts []options.ScannerOption + }{ + { + name: "checks from disk", + opts: []options.ScannerOption{ + rego.WithPolicyFilesystem(os.DirFS("../checks/kubernetes")), + rego.WithPolicyDirs("."), + }, + }, + { + name: "embedded checks", + opts: []options.ScannerOption{ + rego.WithEmbeddedPolicies(true), + }, + }, + } + + testdata := "./testdata/kubernetes" - entries, err := os.ReadDir("./testdata/kubernetes") + entries, err := os.ReadDir(testdata) require.NoError(t, err) - scanner := kubernetes.NewScanner( - rego.WithPerResultTracing(true), - rego.WithEmbeddedPolicies(true), - rego.WithEmbeddedLibraries(true), - ) + for _, tt := range tests { + t.Run(t.Name(), func(t *testing.T) { + t.Parallel() - srcFS := os.DirFS("../") + opts := []options.ScannerOption{ + rego.WithPerResultTracing(true), + rego.WithEmbeddedLibraries(true), + } + opts = append(opts, tt.opts...) - results, err := scanner.ScanFS(context.TODO(), srcFS, "test/testdata/kubernetes") - require.NoError(t, err) + scanner := kubernetes.NewScanner(opts...) - for _, entry := range entries { - if !entry.IsDir() { - continue - } - if entry.Name() == "optional" { - continue - } - t.Run(entry.Name(), func(t *testing.T) { - var matched bool - for _, result := range results { - if result.Rule().HasID(entry.Name()) { - - failCase := fmt.Sprintf("test/testdata/kubernetes/%s/denied.yaml", entry.Name()) - passCase := fmt.Sprintf("test/testdata/kubernetes/%s/allowed.yaml", entry.Name()) - - switch result.Range().GetFilename() { - case failCase: - assert.Equal(t, scan.StatusFailed, result.Status(), "Rule should have failed, but didn't.") - assert.Greater(t, result.Range().GetStartLine(), 0, "We should have line numbers for a failure") - assert.Greater(t, result.Range().GetEndLine(), 0, "We should have line numbers for a failure") - matched = true - case passCase: - assert.Equal(t, scan.StatusPassed, result.Status(), "Rule should have passed, but didn't.") - matched = true - default: - if strings.Contains(result.Range().GetFilename(), entry.Name()) { - t.Fatal(result.Range().GetFilename()) - } - continue - } - - if t.Failed() { - fmt.Println("Test failed - rego trace follows:") - for _, trace := range result.Traces() { - fmt.Println(trace) - } - } + results, err := scanner.ScanFS(context.TODO(), os.DirFS(testdata), ".") + require.NoError(t, err) + + for _, entry := range entries { + if !entry.IsDir() { + continue } + if entry.Name() == "optional" { + continue + } + + dirName := entry.Name() + + t.Run(entry.Name(), func(t *testing.T) { + assertChecks(t, dirName, + fmt.Sprintf("%s/denied.yaml", dirName), + fmt.Sprintf("%s/allowed.yaml", dirName), + results, + ) + }) } - assert.True(t, matched, "Neither a pass or fail result was found for %s - did you add example code for it?", entry.Name()) }) } } -func Test_Kubernetes_RegoPoliciesEmbedded(t *testing.T) { - t.Parallel() +func assertChecks(t *testing.T, fileName, failCase, passCase string, results scan.Results) { + t.Helper() - entries, err := os.ReadDir("./testdata/kubernetes") - require.NoError(t, err) - - scanner := kubernetes.NewScanner( - rego.WithEmbeddedPolicies(true), - rego.WithEmbeddedLibraries(true), - rego.WithEmbeddedLibraries(true), - ) - - srcFS := os.DirFS("../") + var matched bool - results, err := scanner.ScanFS(context.TODO(), srcFS, "test/testdata/kubernetes") - require.NoError(t, err) - - for _, entry := range entries { - if !entry.IsDir() { - continue - } - if entry.Name() == "optional" { + for _, result := range results { + if !result.Rule().HasID(fileName) { continue } - t.Run(entry.Name(), func(t *testing.T) { - var matched bool - for _, result := range results { - if result.Rule().HasID(entry.Name()) { - - failCase := fmt.Sprintf("test/testdata/kubernetes/%s/denied.yaml", entry.Name()) - passCase := fmt.Sprintf("test/testdata/kubernetes/%s/allowed.yaml", entry.Name()) - - switch result.Range().GetFilename() { - case failCase: - assert.Equal(t, scan.StatusFailed, result.Status(), "Rule should have failed, but didn't.") - assert.Greater(t, result.Range().GetStartLine(), 0, "We should have line numbers for a failure") - assert.Greater(t, result.Range().GetEndLine(), 0, "We should have line numbers for a failure") - matched = true - case passCase: - assert.Equal(t, scan.StatusPassed, result.Status(), "Rule should have passed, but didn't.") - matched = true - default: - continue - } - - if t.Failed() { - fmt.Println("Test failed - rego trace follows:") - for _, trace := range result.Traces() { - fmt.Println(trace) - } - } + + t.Run(result.Rule().AVDID, func(t *testing.T) { + switch result.Range().GetFilename() { + case failCase: + assert.Equal(t, scan.StatusFailed, result.Status(), "Rule should have failed, but didn't.") + if result.Rule().AVDID != "AVD-DS-0002" { + assert.Greater(t, result.Range().GetStartLine(), 0, "We should have line numbers for a failure") + assert.Greater(t, result.Range().GetEndLine(), 0, "We should have line numbers for a failure") + } + matched = true + case passCase: + assert.Equal(t, scan.StatusPassed, result.Status(), "Rule should have passed, but didn't.") + matched = true + default: + return + } + + if t.Failed() { + fmt.Println("Test failed - rego trace follows:") + for _, trace := range result.Traces() { + fmt.Println(trace) } } - assert.True(t, matched, "Neither a pass or fail result was found for %s - did you add example code for it?", entry.Name()) }) } + + assert.True(t, matched, "Rule should be matched once") }