From 96ce13a6ef1b5c3e7874cf7231174ef28554393a Mon Sep 17 00:00:00 2001 From: Nikita Pivkin Date: Thu, 28 Nov 2024 11:46:49 +0600 Subject: [PATCH] test: use testing/fstest instead of memoryfs (#264) Signed-off-by: Nikita Pivkin --- go.mod | 2 +- internal/testutil/util.go | 114 ------------------------------------ pkg/scanner/scanner_test.go | 84 ++++++++++++++------------ 3 files changed, 47 insertions(+), 153 deletions(-) delete mode 100644 internal/testutil/util.go diff --git a/go.mod b/go.mod index 85aded2..39d7073 100644 --- a/go.mod +++ b/go.mod @@ -50,7 +50,6 @@ require ( github.com/aws/aws-sdk-go-v2/service/sts v1.33.1 github.com/aws/aws-sdk-go-v2/service/workspaces v1.50.1 github.com/liamg/iamgo v0.0.9 - github.com/liamg/memoryfs v1.6.0 github.com/spf13/cobra v1.8.1 github.com/spf13/viper v1.19.0 github.com/stretchr/testify v1.10.0 @@ -235,6 +234,7 @@ require ( github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect github.com/letsencrypt/boulder v0.0.0-20231026200631-000cd05d5491 // indirect github.com/liamg/jfather v0.0.7 // indirect + github.com/liamg/memoryfs v1.6.0 // indirect github.com/lib/pq v1.10.9 // indirect github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect github.com/lufia/plan9stats v0.0.0-20240226150601-1dcf7310316a // indirect diff --git a/internal/testutil/util.go b/internal/testutil/util.go deleted file mode 100644 index f16b95a..0000000 --- a/internal/testutil/util.go +++ /dev/null @@ -1,114 +0,0 @@ -package testutil - -import ( - "encoding/json" - "io/fs" - "path/filepath" - "strings" - "testing" - - "github.com/liamg/memoryfs" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/aquasecurity/trivy/pkg/iac/scan" -) - -func AssertRuleFound(t *testing.T, ruleID string, results scan.Results, message string, args ...interface{}) { - found := ruleIDInResults(ruleID, results.GetFailed()) - assert.True(t, found, append([]interface{}{message}, args...)...) - for _, result := range results.GetFailed() { - if result.Rule().LongID() == ruleID { - m := result.Metadata() - meta := &m - for meta != nil { - assert.NotNil(t, meta.Range(), 0) - assert.Greater(t, meta.Range().GetStartLine(), 0) - assert.Greater(t, meta.Range().GetEndLine(), 0) - meta = meta.Parent() - } - } - } -} - -func AssertRuleNotFound(t *testing.T, ruleID string, results scan.Results, message string, args ...interface{}) { - found := ruleIDInResults(ruleID, results.GetFailed()) - assert.False(t, found, append([]interface{}{message}, args...)...) -} - -func ruleIDInResults(ruleID string, results scan.Results) bool { - for _, res := range results { - if res.Rule().LongID() == ruleID { - return true - } - } - return false -} - -func CreateFS(t *testing.T, files map[string]string) fs.FS { - memfs := memoryfs.New() - for name, content := range files { - name := strings.TrimPrefix(name, "/") - err := memfs.MkdirAll(filepath.Dir(name), 0o700) - require.NoError(t, err) - err = memfs.WriteFile(name, []byte(content), 0o644) - require.NoError(t, err) - } - return memfs -} - -func AssertDefsecEqual(t *testing.T, expected, actual interface{}) { - expectedJson, err := json.MarshalIndent(expected, "", "\t") - require.NoError(t, err) - actualJson, err := json.MarshalIndent(actual, "", "\t") - require.NoError(t, err) - - if expectedJson[0] == '[' { - var expectedSlice []map[string]interface{} - require.NoError(t, json.Unmarshal(expectedJson, &expectedSlice)) - var actualSlice []map[string]interface{} - require.NoError(t, json.Unmarshal(actualJson, &actualSlice)) - expectedSlice = purgeMetadataSlice(expectedSlice) - actualSlice = purgeMetadataSlice(actualSlice) - assert.Equal(t, expectedSlice, actualSlice, "defsec adapted and expected values do not match") - } else { - var expectedMap map[string]interface{} - require.NoError(t, json.Unmarshal(expectedJson, &expectedMap)) - var actualMap map[string]interface{} - require.NoError(t, json.Unmarshal(actualJson, &actualMap)) - expectedMap = purgeMetadata(expectedMap) - actualMap = purgeMetadata(actualMap) - assert.Equal(t, expectedMap, actualMap, "defsec adapted and expected values do not match") - } -} - -func purgeMetadata(input map[string]interface{}) map[string]interface{} { - for k, v := range input { - if k == "metadata" || k == "Metadata" { - delete(input, k) - continue - } - if v, ok := v.(map[string]interface{}); ok { - input[k] = purgeMetadata(v) - } - if v, ok := v.([]interface{}); ok { - if len(v) > 0 { - if _, ok := v[0].(map[string]interface{}); ok { - maps := make([]map[string]interface{}, len(v)) - for i := range v { - maps[i] = v[i].(map[string]interface{}) - } - input[k] = purgeMetadataSlice(maps) - } - } - } - } - return input -} - -func purgeMetadataSlice(input []map[string]interface{}) []map[string]interface{} { - for i := range input { - input[i] = purgeMetadata(input[i]) - } - return input -} diff --git a/pkg/scanner/scanner_test.go b/pkg/scanner/scanner_test.go index fbc9054..c8b4210 100644 --- a/pkg/scanner/scanner_test.go +++ b/pkg/scanner/scanner_test.go @@ -4,11 +4,11 @@ import ( "context" "io/fs" "testing" + "testing/fstest" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/aquasecurity/trivy-aws/internal/testutil" "github.com/aquasecurity/trivy/pkg/iac/framework" "github.com/aquasecurity/trivy/pkg/iac/providers/aws" "github.com/aquasecurity/trivy/pkg/iac/providers/aws/iam" @@ -16,10 +16,10 @@ import ( "github.com/aquasecurity/trivy/pkg/iac/providers/azure" "github.com/aquasecurity/trivy/pkg/iac/providers/azure/authorization" "github.com/aquasecurity/trivy/pkg/iac/rego" + "github.com/aquasecurity/trivy/pkg/iac/scan" "github.com/aquasecurity/trivy/pkg/iac/scanners/options" "github.com/aquasecurity/trivy/pkg/iac/state" - trivyTypes "github.com/aquasecurity/trivy/pkg/iac/types" - defsecRules "github.com/aquasecurity/trivy/pkg/iac/types/rules" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" ) type testStruct struct { @@ -62,17 +62,17 @@ func TestScanner_GetRegisteredRules(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { for _, r := range tc.scanner.getRules() { - assertRules(t, r, tc) + assertRules(t, r.Rule, tc) } }) } } -func assertRules(t *testing.T, r defsecRules.RegisteredRule, tc testStruct) { +func assertRules(t *testing.T, r scan.Rule, tc testStruct) { t.Helper() - if _, ok := r.Rule.Frameworks[tc.fwApplied]; !ok { - assert.FailNowf(t, "unexpected rule found", "rule: %s in test case: %s", r.Rule.AVDID, tc.name) + if _, ok := r.Frameworks[tc.fwApplied]; !ok { + assert.FailNowf(t, "unexpected rule found", "rule: %s in test case: %s", r.AVDID, tc.name) } } @@ -89,7 +89,7 @@ func Test_AWSInputSelectors(t *testing.T) { }{ { name: "selector is not defined", - srcFS: testutil.CreateFS(t, map[string]string{ + srcFS: createFS(map[string]string{ "policies/rds_policy.rego": `# METADATA # title: "RDS Publicly Accessible" # custom: @@ -114,8 +114,8 @@ deny[res] { state: state.State{AWS: aws.AWS{ RDS: rds.RDS{ Instances: []rds.Instance{ - {Metadata: trivyTypes.Metadata{}, - PublicAccess: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), + {Metadata: iacTypes.Metadata{}, + PublicAccess: iacTypes.Bool(true, iacTypes.NewTestMetadata()), }, }, }, @@ -128,7 +128,7 @@ deny[res] { }, { name: "selector is empty", - srcFS: testutil.CreateFS(t, map[string]string{ + srcFS: createFS(map[string]string{ "policies/rds_policy.rego": `# METADATA # title: "RDS Publicly Accessible" # custom: @@ -156,8 +156,8 @@ deny[res] { state: state.State{AWS: aws.AWS{ RDS: rds.RDS{ Instances: []rds.Instance{ - {Metadata: trivyTypes.Metadata{}, - PublicAccess: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), + {Metadata: iacTypes.Metadata{}, + PublicAccess: iacTypes.Bool(true, iacTypes.NewTestMetadata()), }, }, }, @@ -169,7 +169,7 @@ deny[res] { }, { name: "selector without subtype", - srcFS: testutil.CreateFS(t, map[string]string{ + srcFS: createFS(map[string]string{ "policies/rds_policy.rego": `# METADATA # title: "RDS Publicly Accessible" # custom: @@ -198,8 +198,8 @@ deny[res] { state: state.State{AWS: aws.AWS{ RDS: rds.RDS{ Instances: []rds.Instance{ - {Metadata: trivyTypes.Metadata{}, - PublicAccess: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), + {Metadata: iacTypes.Metadata{}, + PublicAccess: iacTypes.Bool(true, iacTypes.NewTestMetadata()), }, }, }, @@ -212,7 +212,7 @@ deny[res] { }, { name: "conflicting selectors", - srcFS: testutil.CreateFS(t, map[string]string{ + srcFS: createFS(map[string]string{ "policies/rds_policy.rego": `# METADATA # title: "RDS Publicly Accessible" # custom: @@ -235,8 +235,8 @@ deny[res] { state: state.State{AWS: aws.AWS{ RDS: rds.RDS{ Instances: []rds.Instance{ - {Metadata: trivyTypes.Metadata{}, - PublicAccess: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), + {Metadata: iacTypes.Metadata{}, + PublicAccess: iacTypes.Bool(true, iacTypes.NewTestMetadata()), }, }, }, @@ -248,7 +248,7 @@ deny[res] { }, { name: "selector is defined with empty subtype", - srcFS: testutil.CreateFS(t, map[string]string{ + srcFS: createFS(map[string]string{ "policies/rds_policy.rego": `# METADATA # title: "RDS Publicly Accessible" # custom: @@ -280,8 +280,8 @@ deny[res] { state: state.State{AWS: aws.AWS{ RDS: rds.RDS{ Instances: []rds.Instance{ - {Metadata: trivyTypes.Metadata{}, - PublicAccess: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), + {Metadata: iacTypes.Metadata{}, + PublicAccess: iacTypes.Bool(true, iacTypes.NewTestMetadata()), }, }, }, @@ -294,7 +294,7 @@ deny[res] { }, { name: "single cloud, single selector", - srcFS: testutil.CreateFS(t, map[string]string{ + srcFS: createFS(map[string]string{ "policies/rds_policy.rego": `# METADATA # title: "RDS Publicly Accessible" # description: "Ensures RDS instances are not launched into the public cloud." @@ -360,8 +360,8 @@ deny[res] { state: state.State{AWS: aws.AWS{ RDS: rds.RDS{ Instances: []rds.Instance{ - {Metadata: trivyTypes.Metadata{}, - PublicAccess: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), + {Metadata: iacTypes.Metadata{}, + PublicAccess: iacTypes.Bool(true, iacTypes.NewTestMetadata()), }, }, }, @@ -374,7 +374,7 @@ deny[res] { }, { name: "multi cloud, single selector, same named service", - srcFS: testutil.CreateFS(t, map[string]string{ + srcFS: createFS(map[string]string{ "policies/azure_iam_policy.rego": `# METADATA # title: "Azure IAM Policy" # custom: @@ -410,23 +410,23 @@ deny[res] { AWS: aws.AWS{ IAM: iam.IAM{ PasswordPolicy: iam.PasswordPolicy{ - MinimumLength: trivyTypes.Int(1, trivyTypes.NewTestMetadata()), + MinimumLength: iacTypes.Int(1, iacTypes.NewTestMetadata()), }}, }, Azure: azure.Azure{ Authorization: authorization.Authorization{ RoleDefinitions: []authorization.RoleDefinition{{ - Metadata: trivyTypes.NewTestMetadata(), + Metadata: iacTypes.NewTestMetadata(), Permissions: []authorization.Permission{ { - Metadata: trivyTypes.NewTestMetadata(), - Actions: []trivyTypes.StringValue{ - trivyTypes.String("*", trivyTypes.NewTestMetadata()), + Metadata: iacTypes.NewTestMetadata(), + Actions: []iacTypes.StringValue{ + iacTypes.String("*", iacTypes.NewTestMetadata()), }, }, }, - AssignableScopes: []trivyTypes.StringValue{ - trivyTypes.StringUnresolvable(trivyTypes.NewTestMetadata()), + AssignableScopes: []iacTypes.StringValue{ + iacTypes.StringUnresolvable(iacTypes.NewTestMetadata()), }}, }}, }, @@ -439,7 +439,7 @@ deny[res] { }, { name: "single cloud, single selector with config data", - srcFS: testutil.CreateFS(t, map[string]string{ + srcFS: createFS(map[string]string{ "policies/rds_policy.rego": `# METADATA # title: "RDS Publicly Accessible" # description: "Ensures RDS instances are not launched into the public cloud." @@ -500,7 +500,7 @@ deny[res] { } `, }), - dataFS: testutil.CreateFS(t, map[string]string{ + dataFS: createFS(map[string]string{ "config-data/data.json": `{ "settings": { "DS0999": { @@ -516,8 +516,8 @@ deny[res] { state: state.State{AWS: aws.AWS{ RDS: rds.RDS{ Instances: []rds.Instance{ - {Metadata: trivyTypes.Metadata{}, - PublicAccess: trivyTypes.Bool(false, trivyTypes.NewTestMetadata()), + {Metadata: iacTypes.Metadata{}, + PublicAccess: iacTypes.Bool(false, iacTypes.NewTestMetadata()), }, }, }, @@ -539,7 +539,7 @@ deny[res] { scannerOpts = append(scannerOpts, rego.WithEmbeddedPolicies(false)) scannerOpts = append(scannerOpts, rego.WithPolicyFilesystem(tc.srcFS)) scannerOpts = append(scannerOpts, options.ScannerWithRegoOnly(true)) - scannerOpts = append(scannerOpts, rego.WithPolicyDirs("policies/")) + scannerOpts = append(scannerOpts, rego.WithPolicyDirs("policies")) scanner := New(scannerOpts...) results, err := scanner.Scan(context.TODO(), &tc.state) @@ -551,3 +551,11 @@ deny[res] { }) } } + +func createFS(files map[string]string) fs.FS { + fsys := make(fstest.MapFS) + for path, content := range files { + fsys[path] = &fstest.MapFile{Data: []byte(content)} + } + return fsys +}