Skip to content

Commit

Permalink
Merge pull request #70 from aquasecurity/add-policy-tests
Browse files Browse the repository at this point in the history
chore(tests): Add policy based tests
  • Loading branch information
simar7 authored Feb 28, 2024
2 parents f887be5 + 76cc3bb commit 234eea7
Show file tree
Hide file tree
Showing 125 changed files with 1,543 additions and 47 deletions.
12 changes: 6 additions & 6 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,8 @@ module github.com/aquasecurity/trivy-policies

go 1.21

require github.com/aquasecurity/trivy v0.49.2-0.20240220065143-82214736a943

require (
github.com/aquasecurity/trivy v0.49.2-0.20240227072422-e1ea02c7b80d
github.com/docker/docker v25.0.2+incompatible
github.com/liamg/iamgo v0.0.9
github.com/liamg/memoryfs v1.6.0
Expand All @@ -22,11 +21,10 @@ require (
github.com/Microsoft/go-winio v0.6.1 // indirect
github.com/Microsoft/hcsshim v0.11.4 // indirect
github.com/OneOfOne/xxhash v1.2.8 // indirect
github.com/ProtonMail/go-crypto v0.0.0-20230923063757-afb1ddc0824c // indirect
github.com/ProtonMail/go-crypto v1.1.0-alpha.0 // indirect
github.com/agext/levenshtein v1.2.3 // indirect
github.com/agnivade/levenshtein v1.1.1 // indirect
github.com/alecthomas/chroma v0.10.0 // indirect
github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect
github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/bytecodealliance/wasmtime-go/v3 v3.0.2 // indirect
Expand All @@ -36,6 +34,7 @@ require (
github.com/cloudflare/circl v1.3.7 // indirect
github.com/containerd/containerd v1.7.12 // indirect
github.com/containerd/log v0.1.0 // indirect
github.com/containerd/typeurl/v2 v2.1.1 // indirect
github.com/cpuguy83/dockercfg v0.3.1 // indirect
github.com/cyphar/filepath-securejoin v0.2.4 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
Expand Down Expand Up @@ -78,6 +77,7 @@ require (
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect
github.com/mitchellh/go-wordwrap v1.0.1 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/moby/buildkit v0.12.5 // indirect
github.com/moby/locker v1.0.1 // indirect
github.com/moby/patternmatcher v0.6.0 // indirect
github.com/moby/sys/sequential v0.5.0 // indirect
Expand Down Expand Up @@ -113,7 +113,7 @@ require (
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/yashtewari/glob-intersection v0.2.0 // indirect
github.com/yusufpapurcu/wmi v1.2.3 // indirect
github.com/zclconf/go-cty v1.13.0 // indirect
github.com/zclconf/go-cty v1.14.1 // indirect
go.opencensus.io v0.24.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1 // indirect
go.opentelemetry.io/otel v1.23.1 // indirect
Expand All @@ -126,7 +126,7 @@ require (
go.uber.org/automaxprocs v1.5.3 // indirect
golang.org/x/crypto v0.18.0 // indirect
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa // indirect
golang.org/x/mod v0.14.0 // indirect
golang.org/x/mod v0.15.0 // indirect
golang.org/x/net v0.20.0 // indirect
golang.org/x/sync v0.6.0 // indirect
golang.org/x/sys v0.16.0 // indirect
Expand Down
56 changes: 15 additions & 41 deletions go.sum

Large diffs are not rendered by default.

152 changes: 152 additions & 0 deletions test/docker_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
package test

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/aquasecurity/trivy/pkg/iac/scanners/options"
"github.com/liamg/memoryfs"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

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])
}
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()

entries, err := os.ReadDir("./testdata/dockerfile")
require.NoError(t, err)

policiesPath, err := filepath.Abs("../checks/docker")
require.NoError(t, err)
scanner := dockerfile.NewScanner(
options.ScannerWithPolicyDirs(filepath.Base(policiesPath)),
options.ScannerWithEmbeddedLibraries(true),
)
memfs := memoryfs.New()
// add policies
err = addFilesToMemFS(memfs, true, policiesPath)
require.NoError(t, err)

// add test data
testDataPath, err := filepath.Abs("./testdata/dockerfile")
require.NoError(t, err)
err = addFilesToMemFS(memfs, false, testDataPath)
require.NoError(t, err)

results, err := scanner.ScanFS(context.TODO(), memfs, filepath.Base(testDataPath))
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 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(options.ScannerWithEmbeddedPolicies(true), options.ScannerWithEmbeddedLibraries(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
}
}
assert.True(t, matched)
})

})
}
}
130 changes: 130 additions & 0 deletions test/kubernetes_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
package test

import (
"context"
"fmt"
"os"
"strings"
"testing"

"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()

entries, err := os.ReadDir("./testdata/kubernetes")
require.NoError(t, err)

scanner := kubernetes.NewScanner(
options.ScannerWithPerResultTracing(true),
options.ScannerWithEmbeddedPolicies(true),
options.ScannerWithEmbeddedLibraries(true),
)

srcFS := os.DirFS("../")

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" {
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)
}
}
}
}
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()

entries, err := os.ReadDir("./testdata/kubernetes")
require.NoError(t, err)

scanner := kubernetes.NewScanner(options.ScannerWithEmbeddedPolicies(true), options.ScannerWithEmbeddedLibraries(true), options.ScannerWithEmbeddedLibraries(true))

srcFS := os.DirFS("../")

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" {
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)
}
}
}
}
assert.True(t, matched, "Neither a pass or fail result was found for %s - did you add example code for it?", entry.Name())
})
}
}
24 changes: 24 additions & 0 deletions test/rules_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package test

import (
"testing"

"github.com/aquasecurity/trivy/pkg/iac/framework"
"github.com/aquasecurity/trivy/pkg/iac/rules"
)

func TestAVDIDs(t *testing.T) {
existing := make(map[string]struct{})
for _, rule := range rules.GetRegistered(framework.ALL) {
t.Run(rule.LongID(), func(t *testing.T) {
if rule.GetRule().AVDID == "" {
t.Errorf("Rule has no AVD ID: %#v", rule)
return
}
if _, ok := existing[rule.GetRule().AVDID]; ok {
t.Errorf("Rule detected with duplicate AVD ID: %s", rule.GetRule().AVDID)
}
})
existing[rule.GetRule().AVDID] = struct{}{}
}
}
3 changes: 3 additions & 0 deletions test/testdata/dockerfile/DS001/Dockerfile.allowed
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
FROM debian:9
RUN apt-get update && apt-get -y install vim && apt-get clean
USER foo
3 changes: 3 additions & 0 deletions test/testdata/dockerfile/DS001/Dockerfile.denied
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
FROM debian:latest
RUN apt-get update && apt-get -y install vim && apt-get clean
USER foo
3 changes: 3 additions & 0 deletions test/testdata/dockerfile/DS002/Dockerfile.allowed
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
FROM debian:9
RUN apt-get update && apt-get -y install vim && apt-get clean
USER foo
2 changes: 2 additions & 0 deletions test/testdata/dockerfile/DS002/Dockerfile.denied
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
FROM debian:9
RUN apt-get update && apt-get -y install vim && apt-get clean
3 changes: 3 additions & 0 deletions test/testdata/dockerfile/DS004/Dockerfile.allowed
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
FROM alpine:3.13
USER mike
EXPOSE 8080
3 changes: 3 additions & 0 deletions test/testdata/dockerfile/DS004/Dockerfile.denied
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
FROM alpine:3.13
USER mike
EXPOSE 22
3 changes: 3 additions & 0 deletions test/testdata/dockerfile/DS005/Dockerfile.allowed
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
FROM alpine:3.13
USER mike
ADD "/target/resources.tar.gz" "resources"
Loading

0 comments on commit 234eea7

Please sign in to comment.