From dbb55c812c1a8ee940a068c6b2c62cecc20c0b0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Reigota?= Date: Wed, 27 Jan 2021 17:43:22 +0000 Subject: [PATCH] added unit tests to increase current code coverage #1790 (#1827) --- codecov.yml | 4 + internal/console/generate_id_test.go | 22 + internal/console/helpers_test.go | 131 ++++++ internal/console/version.go | 4 +- internal/console/version_test.go | 19 + internal/storage/memory_test.go | 262 +++++++++++ internal/tracker/ci_test.go | 74 +++ pkg/builder/engine/engine_test.go | 139 ++++++ pkg/builder/model/model_test.go | 144 ++++++ pkg/builder/parser/tag/tag_parser.go | 2 +- pkg/builder/parser/tag/tag_parser_test.go | 97 +++- pkg/builder/writer/rego_test.go | 420 +++++++++++++----- pkg/engine/inspector_test.go | 356 ++++++++++++++- pkg/engine/query/source.go | 10 +- pkg/engine/query/source_test.go | 305 +++++++++++++ pkg/engine/similarity_id_test.go | 2 + pkg/engine/vulnerability_builder_test.go | 313 +++++++++++++ pkg/kics/service_test.go | 114 +++++ pkg/model/model_test.go | 51 +++ pkg/model/summary_test.go | 88 ++++ pkg/parser/docker/parser_test.go | 3 + pkg/parser/json/parser_test.go | 3 + pkg/parser/parser_test.go | 26 +- .../terraform/converter/default_test.go | 260 +++++++++++ pkg/parser/terraform/terraform_test.go | 14 + pkg/parser/yaml/parser_test.go | 25 +- pkg/source/filesystem_test.go | 219 +++++++++ .../metadata.json | 8 + .../all_auth_users_get_read_access/query.rego | 15 + .../test/negative.tf | 9 + .../test/positive.tf | 9 + .../test/positive_expected_result.json | 7 + test/helpers.go | 73 +++ test/queries_content_test.go | 2 +- 34 files changed, 3080 insertions(+), 150 deletions(-) create mode 100644 internal/console/generate_id_test.go create mode 100644 internal/console/helpers_test.go create mode 100644 internal/console/version_test.go create mode 100644 internal/storage/memory_test.go create mode 100644 internal/tracker/ci_test.go create mode 100644 pkg/builder/engine/engine_test.go create mode 100644 pkg/builder/model/model_test.go create mode 100644 pkg/engine/query/source_test.go create mode 100644 pkg/kics/service_test.go create mode 100644 pkg/model/summary_test.go create mode 100644 pkg/parser/terraform/converter/default_test.go create mode 100644 pkg/source/filesystem_test.go create mode 100644 test/fixtures/all_auth_users_get_read_access/metadata.json create mode 100644 test/fixtures/all_auth_users_get_read_access/query.rego create mode 100644 test/fixtures/all_auth_users_get_read_access/test/negative.tf create mode 100644 test/fixtures/all_auth_users_get_read_access/test/positive.tf create mode 100644 test/fixtures/all_auth_users_get_read_access/test/positive_expected_result.json create mode 100644 test/helpers.go diff --git a/codecov.yml b/codecov.yml index 5649d9452ea..89df26b6f1f 100644 --- a/codecov.yml +++ b/codecov.yml @@ -18,3 +18,7 @@ ignore: - "*/**/*_test.go" - "**/*_test.go" - "**/mock/*.go" + - "**/*_easyjson.go" + - "cmd/" + - "internal/console/kics.go" + - "internal/console/scan.go" diff --git a/internal/console/generate_id_test.go b/internal/console/generate_id_test.go new file mode 100644 index 00000000000..09d6cf43006 --- /dev/null +++ b/internal/console/generate_id_test.go @@ -0,0 +1,22 @@ +package console + +import ( + "regexp" + "strings" + "testing" + + "github.com/Checkmarx/kics/test" + "github.com/stretchr/testify/require" +) + +// TestGenerateIDCommand tests kics generate ID command +func TestGenerateIDCommand(t *testing.T) { + t.Run("Tests if generates a valid uuid", func(t *testing.T) { + validUUID := regexp.MustCompile(test.ValidUUIDRegex) + + out, err := test.CaptureCommandOutput(generateIDCmd, nil) + + require.NoError(t, err) + require.True(t, validUUID.MatchString(strings.TrimSpace(out))) + }) +} diff --git a/internal/console/helpers_test.go b/internal/console/helpers_test.go new file mode 100644 index 00000000000..da36df763c7 --- /dev/null +++ b/internal/console/helpers_test.go @@ -0,0 +1,131 @@ +package console + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "os" + "testing" + + "github.com/Checkmarx/kics/pkg/model" + "github.com/Checkmarx/kics/test" + "github.com/stretchr/testify/require" +) + +var summary = model.Summary{ + Counters: model.Counters{ + ScannedFiles: 1, + ParsedFiles: 1, + FailedToScanFiles: 0, + TotalQueries: 1, + FailedToExecuteQueries: 0, + }, + Queries: []model.VulnerableQuery{ + { + QueryName: "ALB protocol is HTTP", + QueryID: "de7f5e83-da88-4046-871f-ea18504b1d43", + Severity: "HIGH", + Files: []model.VulnerableFile{ + { + FileName: "positive.tf", + Line: 25, + IssueType: "MissingAttribute", + SearchKey: "aws_alb_listener[front_end].default_action.redirect", + KeyExpectedValue: "'default_action.redirect.protocol' is equal 'HTTPS'", + KeyActualValue: "'default_action.redirect.protocol' is missing", + Value: nil, + }, + { + FileName: "positive.tf", + Line: 19, + IssueType: "IncorrectValue", + SearchKey: "aws_alb_listener[front_end].default_action.redirect", + KeyExpectedValue: "'default_action.redirect.protocol' is equal 'HTTPS'", + KeyActualValue: "'default_action.redirect.protocol' is equal 'HTTP'", + Value: nil, + }, + }, + }, + }, + SeveritySummary: model.SeveritySummary{ + ScanID: "console", + SeverityCounters: map[model.Severity]int{ + "INFO": 0, + "LOW": 0, + "MEDIUM": 0, + "HIGH": 2, + }, + TotalCounter: 2, + }, +} + +var printTests = []struct { + caseTest model.Summary + expectedResult string +}{ + { + caseTest: summary, + expectedResult: "Files scanned: 1\n" + + "Parsed files: 1\n" + + "Queries loaded: 1\n" + + "Queries failed to execute: 0\n\n" + + "ALB protocol is HTTP, Severity: HIGH, Results: 2\n" + + "\tpositive.tf:25\n" + + "\tpositive.tf:19\n\n" + + "Results Summary:\n" + + "HIGH: 2\n" + + "MEDIUM: 0\n" + + "LOW: 0\n" + + "INFO: 0\n" + + "TOTAL: 2\n\n", + }, +} + +type jsonCaseTest struct { + summary model.Summary + path string +} + +var jsonTests = []struct { + caseTest jsonCaseTest + expectedResult model.Summary +}{ + { + caseTest: jsonCaseTest{ + summary: summary, + path: "./testout.json", + }, + expectedResult: summary, + }, +} + +// TestPrintResult tests the functions [printResult()] and all the methods called by them +func TestPrintResult(t *testing.T) { + for idx, testCase := range printTests { + t.Run(fmt.Sprintf("Print test case %d", idx), func(t *testing.T) { + out, err := test.CaptureOutput(func() error { return printResult(&testCase.caseTest) }) + require.NoError(t, err) + require.Equal(t, testCase.expectedResult, out) + }) + } +} + +// TestPrintToJSONFile tests the functions [printToJSONFile()] and all the methods called by them +func TestPrintToJSONFile(t *testing.T) { + for idx, test := range jsonTests { + t.Run(fmt.Sprintf("JSON File test case %d", idx), func(t *testing.T) { + var err error + err = printToJSONFile(test.caseTest.path, test.caseTest.summary) + require.NoError(t, err) + require.FileExists(t, test.caseTest.path) + var jsonResult []byte + jsonResult, err = ioutil.ReadFile(test.caseTest.path) + require.NoError(t, err) + var resultSummary model.Summary + err = json.Unmarshal(jsonResult, &resultSummary) + require.NoError(t, err) + require.Equal(t, test.expectedResult, resultSummary) + os.Remove(test.caseTest.path) + }) + } +} diff --git a/internal/console/version.go b/internal/console/version.go index 4a940cf337c..6f5e022640b 100644 --- a/internal/console/version.go +++ b/internal/console/version.go @@ -6,6 +6,8 @@ import ( "github.com/spf13/cobra" ) +const currentVersion = "Keeping Infrastructure as Code Secure v1.1.1" + var versionCmd = &cobra.Command{ Use: "version", Short: "Displays the current version", @@ -16,5 +18,5 @@ var versionCmd = &cobra.Command{ } func getVersion() string { - return "Keeping Infrastructure as Code Secure v1.1.1" + return currentVersion } diff --git a/internal/console/version_test.go b/internal/console/version_test.go new file mode 100644 index 00000000000..ee9f942c858 --- /dev/null +++ b/internal/console/version_test.go @@ -0,0 +1,19 @@ +package console + +import ( + "fmt" + "testing" + + "github.com/Checkmarx/kics/test" + "github.com/stretchr/testify/require" +) + +// TestVersionCommand tests kics version command +func TestVersionCommand(t *testing.T) { + t.Run("Tests if prints current version", func(t *testing.T) { + out, err := test.CaptureCommandOutput(versionCmd, nil) + + require.NoError(t, err) + require.Equal(t, fmt.Sprintf("%s\n", currentVersion), out) + }) +} diff --git a/internal/storage/memory_test.go b/internal/storage/memory_test.go new file mode 100644 index 00000000000..ae704029168 --- /dev/null +++ b/internal/storage/memory_test.go @@ -0,0 +1,262 @@ +package storage + +import ( + "context" + "fmt" + "reflect" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/Checkmarx/kics/pkg/model" +) + +// TestMemoryStorage_SaveFile tests the functions [SaveFile()] +func TestMemoryStorage_SaveFile(t *testing.T) { + type fields struct { + vulnerabilities []model.Vulnerability + allFiles model.FileMetadatas + } + type args struct { + in0 context.Context + metadata *model.FileMetadata + } + tests := []struct { + name string + fields fields + args args + wantErr bool + }{ + { + name: "SaveFile", + fields: fields{ + vulnerabilities: []model.Vulnerability{}, + allFiles: model.FileMetadatas{}, + }, + args: args{ + in0: nil, + metadata: &model.FileMetadata{ + ID: "id", + ScanID: "scan_id", + OriginalData: "orig_data", + FileName: "file_name", + }, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + m := &MemoryStorage{ + vulnerabilities: tt.fields.vulnerabilities, + allFiles: tt.fields.allFiles, + } + if err := m.SaveFile(tt.args.in0, tt.args.metadata); (err != nil) != tt.wantErr { + t.Errorf("MemoryStorage.SaveFile() error = %v, wantErr %v", err, tt.wantErr) + } + require.Equal(t, *tt.args.metadata, m.allFiles[0]) + }) + } +} + +// TestMemoryStorage_SaveFile tests the functions [GetFiles(), GetVulnerabilities(), GetScanSummary()] +func TestMemoryStorage(t *testing.T) { // nolint + type fields struct { + vulnerabilities []model.Vulnerability + allFiles model.FileMetadatas + } + type args struct { + in0 context.Context + in1 string + in2 []string + } + type want struct { + metadata model.FileMetadatas + vulnerabilities []model.Vulnerability + scanSummary []model.SeveritySummary + } + tests := []struct { + name string + fields fields + args args + want want + wantErr bool + }{ + { + name: "test_case", + fields: fields{ + vulnerabilities: []model.Vulnerability{ + { + ID: 0, + ScanID: "scan_id", + FileID: "file_id", + FileName: "file_name", + QueryID: "query_id", + QueryName: "query_name", + Line: 1, + SearchKey: "search_key", + KeyExpectedValue: "key_expected_value", + KeyActualValue: "key_actual_value", + Output: "-", + }, + }, + allFiles: model.FileMetadatas{ + { + ID: "id", + ScanID: "scan_id", + OriginalData: "orig_data", + FileName: "file_name", + }, + }, + }, + args: args{ + in0: nil, + in1: "", + in2: []string{}, + }, + wantErr: false, + want: want{ + metadata: model.FileMetadatas{ + { + ID: "id", + ScanID: "scan_id", + OriginalData: "orig_data", + FileName: "file_name", + }, + }, + vulnerabilities: []model.Vulnerability{ + { + ID: 0, + ScanID: "scan_id", + FileID: "file_id", + FileName: "file_name", + QueryID: "query_id", + QueryName: "query_name", + Line: 1, + SearchKey: "search_key", + KeyExpectedValue: "key_expected_value", + KeyActualValue: "key_actual_value", + Output: "-", + }, + }, + scanSummary: nil, + }, + }, + } + for _, tt := range tests { + m := &MemoryStorage{ + vulnerabilities: tt.fields.vulnerabilities, + allFiles: tt.fields.allFiles, + } + t.Run(fmt.Sprintf(tt.name+"_GetFiles"), func(t *testing.T) { + got, err := m.GetFiles(tt.args.in0, tt.args.in1) + if (err != nil) != tt.wantErr { + t.Errorf("MemoryStorage.GetFiles() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want.metadata) { + t.Errorf("MemoryStorage.GetFiles() = %v, want %v", got, tt.want) + } + }) + t.Run(fmt.Sprintf(tt.name+"_GetVulnerabilities"), func(t *testing.T) { + got, err := m.GetVulnerabilities(tt.args.in0, tt.args.in1) + if (err != nil) != tt.wantErr { + t.Errorf("MemoryStorage.GetVulnerabilities() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want.vulnerabilities) { + t.Errorf("MemoryStorage.GetVulnerabilities() = %v, want %v", got, tt.want) + } + }) + t.Run(fmt.Sprintf(tt.name+"_GetScanSummary"), func(t *testing.T) { + got, err := m.GetScanSummary(tt.args.in0, tt.args.in2) + if (err != nil) != tt.wantErr { + t.Errorf("MemoryStorage.GetScanSummary() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want.scanSummary) { + t.Errorf("MemoryStorage.GetScanSummary() = %v, want %v", got, tt.want) + } + }) + } +} + +// TestMemoryStorage_SaveVulnerabilities tests the functions [SaveVulnerabilities()] +func TestMemoryStorage_SaveVulnerabilities(t *testing.T) { + type fields struct { + vulnerabilities []model.Vulnerability + allFiles model.FileMetadatas + } + type args struct { + in0 context.Context + vulnerabilities []model.Vulnerability + } + tests := []struct { + name string + fields fields + args args + wantErr bool + }{ + { + name: "SaveVulnerabilities", + fields: fields{ + vulnerabilities: []model.Vulnerability{}, + allFiles: model.FileMetadatas{}, + }, + args: args{ + in0: nil, + vulnerabilities: []model.Vulnerability{ + { + ID: 0, + ScanID: "scan_id", + FileID: "file_id", + FileName: "file_name", + QueryID: "query_id", + QueryName: "query_name", + Line: 1, + SearchKey: "search_key", + KeyExpectedValue: "key_expected_value", + KeyActualValue: "key_actual_value", + Output: "-", + }, + }, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + m := &MemoryStorage{ + vulnerabilities: tt.fields.vulnerabilities, + allFiles: tt.fields.allFiles, + } + if err := m.SaveVulnerabilities(tt.args.in0, tt.args.vulnerabilities); (err != nil) != tt.wantErr { + t.Errorf("MemoryStorage.SaveVulnerabilities() error = %v, wantErr %v", err, tt.wantErr) + } + require.Equal(t, tt.args.vulnerabilities, m.vulnerabilities) + }) + } +} + +// TestNewMemoryStorage tests the functions [NewMemoryStorage()] +func TestNewMemoryStorage(t *testing.T) { + tests := []struct { + name string + want *MemoryStorage + }{ + { + name: "new_memory_storage", + want: &MemoryStorage{ + allFiles: make(model.FileMetadatas, 0), + vulnerabilities: make([]model.Vulnerability, 0), + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := NewMemoryStorage(); !reflect.DeepEqual(got, tt.want) { + t.Errorf("NewMemoryStorage() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/internal/tracker/ci_test.go b/internal/tracker/ci_test.go new file mode 100644 index 00000000000..1ee146af9e0 --- /dev/null +++ b/internal/tracker/ci_test.go @@ -0,0 +1,74 @@ +package tracker + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/require" +) + +/* +TestCITracker tests the functions [TrackQueryLoad(),TrackQueryExecution(),TrackFileFound(), + TrackFileParse(),TrackFileParse(),FailedDetectLine(),FailedComputeSimilarityID()] +*/ +func TestCITracker(t *testing.T) { + type fields struct { + LoadedQueries int + ExecutedQueries int + FoundFiles int + ParsedFiles int + FailedSimilarityID int + } + tests := []struct { + name string + fields fields + }{ + { + name: "testing_case_1", + fields: fields{ + LoadedQueries: 0, + ExecutedQueries: 0, + FoundFiles: 0, + ParsedFiles: 0, + FailedSimilarityID: 0, + }, + }, + } + + for _, tt := range tests { + c := &CITracker{ + LoadedQueries: tt.fields.LoadedQueries, + ExecutedQueries: tt.fields.ExecutedQueries, + FoundFiles: tt.fields.FoundFiles, + ParsedFiles: tt.fields.ParsedFiles, + FailedSimilarityID: tt.fields.FailedSimilarityID, + } + t.Run(fmt.Sprintf(tt.name+"_LoadedQueries"), func(t *testing.T) { + c.TrackQueryLoad() + require.Equal(t, 1, c.LoadedQueries) + }) + + t.Run(fmt.Sprintf(tt.name+"_TrackQueryExecution"), func(t *testing.T) { + c.TrackQueryExecution() + require.Equal(t, 1, c.ExecutedQueries) + }) + + t.Run(fmt.Sprintf(tt.name+"_TrackFileFound"), func(t *testing.T) { + c.TrackFileFound() + require.Equal(t, 1, c.FoundFiles) + }) + + t.Run(fmt.Sprintf(tt.name+"_TrackFileParse"), func(t *testing.T) { + c.TrackFileParse() + require.Equal(t, 1, c.ParsedFiles) + }) + t.Run(fmt.Sprintf(tt.name+"_FailedDetectLine"), func(t *testing.T) { + c.FailedDetectLine() + require.Equal(t, 0, c.ExecutedQueries) + }) + t.Run(fmt.Sprintf(tt.name+"_FailedComputeSimilarityID"), func(t *testing.T) { + c.FailedComputeSimilarityID() + require.Equal(t, 1, c.FailedSimilarityID) + }) + } +} diff --git a/pkg/builder/engine/engine_test.go b/pkg/builder/engine/engine_test.go new file mode 100644 index 00000000000..6fe31370fc7 --- /dev/null +++ b/pkg/builder/engine/engine_test.go @@ -0,0 +1,139 @@ +package engine + +import ( + "reflect" + "strings" + "testing" + + build "github.com/Checkmarx/kics/pkg/builder/model" + "github.com/hashicorp/hcl/v2/hclsyntax" +) + +// TestRun tests the functions [Run()] and all the methods called by them +func TestRun(t *testing.T) { + type args struct { + src []byte + filename string + } + tests := []struct { + name string + args args + want []build.Rule + wantErr bool + }{ + { + name: "engine_run", + args: args{ + src: []byte(` + //some message in commit + resource "aws_s3_bucket" "b_website" { + bucket = "my-tf-test-bucket" + acl = "website" + + values = { + Name = "My bucket" + Environment = "Dev"//IncorrectValue:"resource=*,any_key" + } + + versioning { + enabled = true + } + } + `), + filename: "example.tf", + }, + want: []build.Rule{ + { + Conditions: []build.Condition{ + { + Line: 9, + IssueType: "IncorrectValue", + Path: []build.PathItem{ + { + Name: "resource", + Type: "RESOURCE", + }, + { + Name: "aws_s3_bucket", + Type: "RESOURCE_TYPE", + }, + { + Name: "resource", + Type: "RESOURCE_NAME", + }, + { + Name: "values", + Type: "DEFAULT", + }, + { + Name: "Environment", + Type: "DEFAULT", + }, + }, + Attributes: map[string]interface{}{ + "resource": "*", + "any_key": nil, + }, + }, + }, + }, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := Run(tt.args.src, tt.args.filename) + if (err != nil) != tt.wantErr { + t.Errorf("Run() error = %v, wantErr %v", err, tt.wantErr) + return + } + tt.want[0].Conditions[0].Value = got[0].Conditions[0].Value + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("Run() = %v, want %v", got, tt.want) + } + }) + } +} + +// TestEngine_BuildString tests the functions [buildString()] and all the methods called by them +func TestEngine_BuildString(t *testing.T) { + type args struct { + parts []hclsyntax.Expression + } + type fields struct { + Engine *Engine + } + tests := []struct { + name string + args args + fields fields + want strings.Builder + wantErr bool + }{ + { + name: "build_string", + fields: fields{ + Engine: &Engine{}, + }, + args: args{ + parts: []hclsyntax.Expression{}, + }, + want: strings.Builder{}, + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := tt.fields.Engine.buildString(tt.args.parts) + if (err != nil) != tt.wantErr { + t.Errorf("Run() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("Run() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/pkg/builder/model/model_test.go b/pkg/builder/model/model_test.go new file mode 100644 index 00000000000..d1cc9a5b144 --- /dev/null +++ b/pkg/builder/model/model_test.go @@ -0,0 +1,144 @@ +package model + +import ( + "reflect" + "testing" + + "github.com/Checkmarx/kics/pkg/model" +) + +type fields struct { + Line int + IssueType model.IssueType + Path []PathItem + Value interface{} + Attributes map[string]interface{} +} +type args struct { + name string +} + +type testCase struct { + name string + fields fields + args args + want interface{} + want1 bool +} + +var conditionAttrTestCase = testCase{ + name: "condition_attr", + fields: fields{ + Line: 1, + IssueType: "MissingAttribute", + Path: []PathItem{}, + Value: nil, + Attributes: map[string]interface{}{ + "test": "test2", + }, + }, + args: args{ + name: "test", + }, + want: "test2", + want1: true, +} + +// TestCondition_Attr tests the functions [Attr()] and all the methods called by them +func TestCondition_Attr(t *testing.T) { + tests := []testCase{ + conditionAttrTestCase, + { + name: "condition_attr_error", + fields: fields{ + Line: 1, + IssueType: "MissingAttribute", + Path: []PathItem{}, + Value: nil, + Attributes: map[string]interface{}{ + "test": "test2", + }, + }, + args: args{ + name: "test3", + }, + want: nil, + want1: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := getCondition(&tt) + got, got1 := c.Attr(tt.args.name) + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("Condition.Attr() got = %v, want %v", got, tt.want) + } + if got1 != tt.want1 { + t.Errorf("Condition.Attr() got1 = %v, want %v", got1, tt.want1) + } + }) + } +} + +// TestCondition_AttrAsString tests the functions [AttrAsString()] and all the methods called by them +func TestCondition_AttrAsString(t *testing.T) { + tests := []testCase{ + conditionAttrTestCase, + { + name: "condition_attr_error", + fields: fields{ + Line: 1, + IssueType: "MissingAttribute", + Path: []PathItem{}, + Value: nil, + Attributes: map[string]interface{}{ + "test": "test2", + }, + }, + args: args{ + name: "test3", + }, + want: "", + want1: false, + }, + { + name: "condition_attr_string_error", + fields: fields{ + Line: 1, + IssueType: "MissingAttribute", + Path: []PathItem{}, + Value: nil, + Attributes: map[string]interface{}{ + "test": nil, + }, + }, + args: args{ + name: "test", + }, + want: "", + want1: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := getCondition(&tt) + got, got1 := c.AttrAsString(tt.args.name) + if got != tt.want { + t.Errorf("Condition.AttrAsString() got = %v, want %v", got, tt.want) + } + if got1 != tt.want1 { + t.Errorf("Condition.AttrAsString() got1 = %v, want %v", got1, tt.want1) + } + }) + } +} + +func getCondition(tt *testCase) Condition { + return Condition{ + Line: tt.fields.Line, + IssueType: tt.fields.IssueType, + Path: tt.fields.Path, + Value: tt.fields.Value, + Attributes: tt.fields.Attributes, + } +} diff --git a/pkg/builder/parser/tag/tag_parser.go b/pkg/builder/parser/tag/tag_parser.go index 5767561e693..1dcc88fea18 100644 --- a/pkg/builder/parser/tag/tag_parser.go +++ b/pkg/builder/parser/tag/tag_parser.go @@ -181,7 +181,7 @@ func parseArgs(sc *scanner.Scanner) (map[string]interface{}, error) { } result[name] = value next := sc.Next() - if next == ')' { + if next == ']' { return result, nil } if next == ',' { diff --git a/pkg/builder/parser/tag/tag_parser_test.go b/pkg/builder/parser/tag/tag_parser_test.go index 9c4965765ee..e81688b80dc 100644 --- a/pkg/builder/parser/tag/tag_parser_test.go +++ b/pkg/builder/parser/tag/tag_parser_test.go @@ -6,7 +6,8 @@ import ( "github.com/stretchr/testify/require" ) -func TestParseTags(t *testing.T) { +// TestParseTags tests the functions [Parse()] and all the methods called by them (check for equal flags) +func TestParseTags(t *testing.T) { // nolint t.Run("empty", func(t *testing.T) { tags, err := Parse("", []string{"test2"}) require.NoError(t, err) @@ -43,7 +44,7 @@ func TestParseTags(t *testing.T) { }) t.Run("just_many_comments", func(t *testing.T) { - tags, err := Parse("// a:\"something,expected=private,test=false,iii=123.3,tt=['a','c']\" commentB a:\"test=1,b,c=!=\"", []string{"a", "commentB"}) // nolint:lll + tags, err := Parse("// a:\"something,expected=private,test=false,test2=true,iii=123.3,tt=['a','c']\" commentB a:\"test=1,b,c=!=\"", []string{"a", "commentB"}) // nolint:lll require.NoError(t, err) assertEqualTags(t, tags, []Tag{ { @@ -52,6 +53,7 @@ func TestParseTags(t *testing.T) { "something": nil, "expected": "private", "test": false, + "test2": true, "iii": 123.3, "tt": []interface{}{"a", "c"}, }, @@ -90,6 +92,97 @@ func TestParseTags(t *testing.T) { }, }) }) + + t.Run("parse_args", func(t *testing.T) { + tags, err := Parse("// Test:testArr[a=testA,b=testB]", []string{"Test"}) + require.NoError(t, err) + assertEqualTags(t, tags, []Tag{ + { + Name: "Test", + Attributes: map[string]interface{}{ + "testArr": map[string]interface{}{ + "a": "testA", + "b": "testB", + }, + }, + }, + }) + }) + + t.Run("parse_escape", func(t *testing.T) { + tags, err := Parse("// a:tt=['a\\a','b\\b','f\\f','n\\n','r\\r','t\\t','v\\v']", []string{"a"}) // nolint:lll + require.NoError(t, err) + assertEqualTags(t, tags, []Tag{ + { + Name: "a", + Attributes: map[string]interface{}{ + "tt": []interface{}{ + "a\a", + "b\b", + "f\f", + "n\n", + "r\r", + "t\t", + "v\v", + }, + }, + }, + }) + }) + + t.Run("special_escape_cases", func(t *testing.T) { + tags, err := Parse("// Test:t='\\\\',pel='\\'',asp='\\\"\\\"'", []string{"Test"}) + require.NoError(t, err) + assertEqualTags(t, tags, []Tag{ + { + Name: "Test", + Attributes: map[string]interface{}{ + "t": "\\", + "pel": "'", + "asp": "\"\"", + }, + }, + }) + }) +} + +// TestParseTags tests the functions [Parse()] and all the methods called by them (checks for errors) +func TestParseErrorTags(t *testing.T) { + t.Run("invalid_token", func(t *testing.T) { + tags, err := Parse("// Test:[error]", []string{"Test"}) + require.Error(t, err) + assertEqualTags(t, tags, nil) + }) + + t.Run("parse_args_error", func(t *testing.T) { + tags, err := Parse("// Test:testArr[testA]", []string{"Test"}) + require.Error(t, err) + assertEqualTags(t, tags, nil) + }) + + t.Run("invalid_value_error", func(t *testing.T) { + tags, err := Parse("// Test:t=!test", []string{"Test"}) + require.Error(t, err) + assertEqualTags(t, tags, nil) + }) + + t.Run("invalid_escape_sequence", func(t *testing.T) { + tags, err := Parse("// Test:t='\\k'", []string{"Test"}) + require.Error(t, err) + assertEqualTags(t, tags, nil) + }) + + t.Run("unterminated_string", func(t *testing.T) { + tags, err := Parse("// Test:t='\n'", []string{"Test"}) + require.Error(t, err) + assertEqualTags(t, tags, nil) + }) + + t.Run("']'_or_','_expected_error", func(t *testing.T) { + tags, err := Parse("// Test:t=['a')", []string{"Test"}) + require.Error(t, err) + assertEqualTags(t, tags, nil) + }) } func assertEqualTags(t *testing.T, actual, expected []Tag) { diff --git a/pkg/builder/writer/rego_test.go b/pkg/builder/writer/rego_test.go index 5c8167b008f..2fb4f087b90 100644 --- a/pkg/builder/writer/rego_test.go +++ b/pkg/builder/writer/rego_test.go @@ -2,12 +2,59 @@ package writer import ( "fmt" + "os" + "strings" "testing" build "github.com/Checkmarx/kics/pkg/builder/model" "github.com/stretchr/testify/require" ) +var blockElement = Block{ + Name: "resource", + All: true, + List: []string{"*"}, +} + +var pathElement = []build.PathItem{ + { + Name: "resource", + Type: "RESOURCE", + }, + { + Name: "aws_s3_bucket", + Type: "RESOURCE_TYPE", + }, + { + Name: "resource", + Type: "RESOURCE_NAME", + }, + { + Name: "values", + Type: "DEFAULT", + }, + { + Name: "Environment", + Type: "DEFAULT", + }, +} + +var ruleElement = build.Rule{ + Conditions: []build.Condition{ + { + Line: 8, + IssueType: "IncorrectValue", + Path: pathElement, + Value: "Dev", + Attributes: map[string]interface{}{ + "resource": "*", + "any_key": "", + }, + }, + }, +} + +// TestConditionKey tests the functions [conditionKey()] and all the methods called by them func TestConditionKey(t *testing.T) { values := []struct { block Block @@ -17,37 +64,12 @@ func TestConditionKey(t *testing.T) { expectedResult string }{ { - block: Block{ - Name: "resource", - All: true, - List: []string{"*"}, - }, + block: blockElement, c: build.Condition{ Line: 8, IssueType: "IncorrectValue", - Path: []build.PathItem{ - { - Name: "resource", - Type: "RESOURCE", - }, - { - Name: "aws_s3_bucket", - Type: "RESOURCE_TYPE", - }, - { - Name: "resource", - Type: "RESOURCE_NAME", - }, - { - Name: "values", - Type: "DEFAULT", - }, - { - Name: "Environment", - Type: "DEFAULT", - }, - }, - Value: "Dev", + Path: pathElement, + Value: "Dev", Attributes: map[string]interface{}{ "resource": "*", "any_key": "", @@ -66,52 +88,15 @@ func TestConditionKey(t *testing.T) { } } +// TestCreateBlock tests the functions [createBlock()] and all the methods called by them func TestCreateBlock(t *testing.T) { values := []struct { rules build.Rule expectedResult Block }{ { - expectedResult: Block{ - Name: "resource", - All: true, - List: []string{"*"}, - }, - rules: build.Rule{ - Conditions: []build.Condition{ - { - Line: 8, - IssueType: "IncorrectValue", - Path: []build.PathItem{ - { - Name: "resource", - Type: "RESOURCE", - }, - { - Name: "aws_s3_bucket", - Type: "RESOURCE_TYPE", - }, - { - Name: "resource", - Type: "RESOURCE_NAME", - }, - { - Name: "values", - Type: "DEFAULT", - }, - { - Name: "Environment", - Type: "DEFAULT", - }, - }, - Value: "Dev", - Attributes: map[string]interface{}{ - "resource": "*", - "any_key": "", - }, - }, - }, - }, + expectedResult: blockElement, + rules: ruleElement, }, } for idx, value := range values { @@ -122,6 +107,7 @@ func TestCreateBlock(t *testing.T) { } } +// TestResultName tests the functions [resultName()] and all the methods called by them func TestResultName(t *testing.T) { values := []struct { rules build.Rule @@ -135,41 +121,7 @@ func TestResultName(t *testing.T) { All: false, List: nil, }, - rules: build.Rule{ - Conditions: []build.Condition{ - { - Line: 8, - IssueType: "IncorrectValue", - Path: []build.PathItem{ - { - Name: "resource", - Type: "RESOURCE", - }, - { - Name: "aws_s3_bucket", - Type: "RESOURCE_TYPE", - }, - { - Name: "resource", - Type: "RESOURCE_NAME", - }, - { - Name: "values", - Type: "DEFAULT", - }, - { - Name: "Environment", - Type: "DEFAULT", - }, - }, - Value: "Dev", - Attributes: map[string]interface{}{ - "resource": "*", - "any_key": "", - }, - }, - }, - }, + rules: ruleElement, }, } for idx, value := range values { @@ -180,6 +132,7 @@ func TestResultName(t *testing.T) { } } +// TestSwitchFunction tests the functions [switchFunction()] and all the methods called by them func TestSwitchFunction(t *testing.T) { values := []struct { v interface{} @@ -205,6 +158,24 @@ func TestSwitchFunction(t *testing.T) { "*": {}, }, }, + { + result: Block{ + Name: "resource", + All: false, + List: nil, + }, + resources: map[string]struct{}{}, + v: []string{"first", "second"}, + expectedResult: Block{ + Name: "resource", + All: false, + List: nil, + }, + expectedResource: map[string]struct{}{ + "first": {}, + "second": {}, + }, + }, } for idx, value := range values { t.Run(fmt.Sprintf("switchFunction_%d", idx), func(t *testing.T) { @@ -214,3 +185,246 @@ func TestSwitchFunction(t *testing.T) { }) } } + +// TestRegoValueToString tests the functions [regoValueToString()] and all the methods called by them +func TestRegoValueToString(t *testing.T) { + var emptyStringPointer *string + var strPointer = new(string) + *strPointer = "testString" + + values := []struct { + testCase interface{} + expectedResult string + }{ + { + testCase: true, + expectedResult: "true", + }, + { + testCase: false, + expectedResult: "false", + }, + { + testCase: int64(9223372036854775806), + expectedResult: "9223372036854775806", + }, + { + testCase: int32(2147483647), + expectedResult: "2147483647", + }, + { + testCase: float64(123456.5), + expectedResult: "123456.500000", + }, + { + testCase: float32(123.5), + expectedResult: "123.500000", + }, + { + testCase: 0, + expectedResult: "0", + }, + { + testCase: "string", + expectedResult: "\"string\"", + }, + { + testCase: emptyStringPointer, + expectedResult: "\"\"", + }, + { + testCase: strPointer, + expectedResult: "\"testString\"", + }, + { + testCase: []string{"a", "b", "c"}, + expectedResult: "{\"a\", \"b\", \"c\"}", + }, + { + testCase: []int{1, 2, 3}, + expectedResult: "", + }, + } + + for idx, value := range values { + t.Run(fmt.Sprintf("regoValueToString_%d", idx), func(t *testing.T) { + result := regoValueToString(value.testCase) + require.Equal(t, value.expectedResult, result) + }) + } +} + +// TestCondition tests the functions [condition()] and all the methods called by them +func TestCondition(t *testing.T) { + values := []struct { + block Block + c build.Condition + expectedResult string + }{ + { + block: blockElement, + c: build.Condition{ + Line: 8, + IssueType: "IncorrectValue", + Path: pathElement, + Value: "DEV", + Attributes: map[string]interface{}{ + "resource": "*", + "any_key": "", + "upper": "", + }, + }, + expectedResult: "upper(block[blockType][name].values[key]) == \"DEV\"", + }, + { + block: blockElement, + c: build.Condition{ + Line: 8, + IssueType: "IncorrectValue", + Path: pathElement, + Attributes: map[string]interface{}{ + "resource": "*", + "any_key": "", + "lower": "", + "condition": "!=", + "val": "dev", + }, + }, + expectedResult: "lower(block[blockType][name].values[key]) != \"dev\"", + }, + { + block: blockElement, + c: build.Condition{ + Line: 8, + IssueType: "IncorrectValue", + Path: pathElement, + Value: "dev", + Attributes: map[string]interface{}{ + "resource": "*", + "any_key": "", + "regex": "\\w", + }, + }, + expectedResult: "re_match(\"\\w\", block[blockType][name].values[key])", + }, + { + block: blockElement, + c: build.Condition{ + Line: 8, + IssueType: "MissingAttribute", + Path: pathElement, + Attributes: map[string]interface{}{ + "resource": "*", + "any_key": "", + }, + }, + expectedResult: "not block[blockType][name].values[key]", + }, + { + block: blockElement, + c: build.Condition{ + Line: 8, + IssueType: "RedundantAttribute", + Path: pathElement, + Attributes: map[string]interface{}{ + "resource": "*", + "any_key": "", + }, + }, + expectedResult: "block[blockType][name].values[key]", + }, + } + for idx, value := range values { + t.Run(fmt.Sprintf("condition_%d", idx), func(t *testing.T) { + key := condition(value.block, value.c) + require.Equal(t, value.expectedResult, key) + }) + } +} + +// TestFormat tests the functions [format()] and all the methods called by them +func TestFormat(t *testing.T) { + values := []struct { + rule build.Rule + expectedResult RegoRule + }{ + { + rule: ruleElement, + expectedResult: RegoRule{ + Block: blockElement, + Rule: ruleElement, + }, + }, + } + for idx, value := range values { + t.Run(fmt.Sprintf("format_%d", idx), func(t *testing.T) { + result := format([]build.Rule{ruleElement}) + require.Equal(t, []RegoRule{value.expectedResult}, result) + }) + } +} + +// TestRender tests the functions [Render()] and all the methods called by them +func TestRender(t *testing.T) { + for currentDir, err := os.Getwd(); getCurrentDirName(currentDir) != "kics"; currentDir, err = os.Getwd() { + if err == nil { + if dirErr := os.Chdir(".."); dirErr != nil { // this is necessary to load template file on RegoWriter + t.Fatal(dirErr) + } + } else { + t.Fatal(err) + } + } + regoWriter, err := NewRegoWriter() + if err != nil { + t.Fatal(err) + } + values := []struct { + rules []build.Rule + expectedResult string + }{ + { + rules: []build.Rule{ruleElement}, + expectedResult: `package Cx + + CxPolicy [ result ] { + document := input.document[i] + block := document.resource + + block[blockType][name].values[key] == "Dev" + + result := { + "documentId": document.id, + "searchKey": sprintf("%s[%s].values.%s", [blockType, name, key]), + "issueType": "IncorrectValue", + "keyExpectedValue": "'values' should be valid", + "keyActualValue": "'values' is invalid" + } + }`, + }, + } + for idx, value := range values { + t.Run(fmt.Sprintf("format_%d", idx), func(t *testing.T) { + results, err := regoWriter.Render(value.rules) + require.NoError(t, err) + require.Equal(t, removeWhiteSpaces(string(results)), removeWhiteSpaces(value.expectedResult)) + }) + } +} + +func getCurrentDirName(path string) string { + dirs := strings.Split(path, string(os.PathSeparator)) + if dirs[len(dirs)-1] == "" && len(dirs) > 1 { + return dirs[len(dirs)-2] + } + return dirs[len(dirs)-1] +} + +func removeWhiteSpaces(str string) string { + str = strings.TrimSpace(str) + whitespaces := []string{"\r\n", "\n", " ", "\t"} + for _, removeSpaceChar := range whitespaces { + str = strings.ReplaceAll(str, removeSpaceChar, "") + } + return str +} diff --git a/pkg/engine/inspector_test.go b/pkg/engine/inspector_test.go index a0714202e10..2f0ac3abb61 100644 --- a/pkg/engine/inspector_test.go +++ b/pkg/engine/inspector_test.go @@ -1,44 +1,356 @@ package engine import ( - "fmt" + "context" + "reflect" "testing" "github.com/stretchr/testify/require" + + "github.com/Checkmarx/kics/internal/tracker" + "github.com/Checkmarx/kics/pkg/engine/query" + "github.com/Checkmarx/kics/pkg/model" + "github.com/Checkmarx/kics/test" + "github.com/open-policy-agent/opa/cover" + "github.com/open-policy-agent/opa/rego" ) -func TestMapKeyToString(t *testing.T) { - testCases := []struct { - payload interface{} - expected string +// TestInspector_EnableCoverageReport tests the functions [EnableCoverageReport()] and all the methods called by them +func TestInspector_EnableCoverageReport(t *testing.T) { + type fields struct { + queries []*preparedQuery + vb VulnerabilityBuilder + tracker Tracker + enableCoverageReport bool + coverageReport cover.Report + } + tests := []struct { + name string + fields fields + want bool }{ { - payload: "test", - expected: "test", + name: "enable_coverage_report_1", + fields: fields{ + queries: []*preparedQuery{}, + vb: DefaultVulnerabilityBuilder, + tracker: &tracker.CITracker{}, + enableCoverageReport: false, + coverageReport: cover.Report{}, + }, + want: true, }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &Inspector{ + queries: tt.fields.queries, + vb: tt.fields.vb, + tracker: tt.fields.tracker, + enableCoverageReport: tt.fields.enableCoverageReport, + coverageReport: tt.fields.coverageReport, + } + c.EnableCoverageReport() + if !reflect.DeepEqual(c.enableCoverageReport, tt.want) { + t.Errorf("Inspector.enableCoverageReport() = %v, want %v", c.enableCoverageReport, tt.want) + } + }) + } +} + +// TestInspector_GetCoverageReport tests the functions [GetCoverageReport()] and all the methods called by them +func TestInspector_GetCoverageReport(t *testing.T) { + coverageReports := cover.Report{ + Coverage: 75.5, + Files: map[string]*cover.FileReport{}, + } + + type fields struct { + queries []*preparedQuery + vb VulnerabilityBuilder + tracker Tracker + enableCoverageReport bool + coverageReport cover.Report + } + tests := []struct { + name string + fields fields + want cover.Report + }{ { - payload: 123, - expected: "123", + name: "get_coverage_report_1", + fields: fields{ + queries: []*preparedQuery{}, + vb: DefaultVulnerabilityBuilder, + tracker: &tracker.CITracker{}, + enableCoverageReport: false, + coverageReport: coverageReports, + }, + want: coverageReports, }, - { - payload: 0.123, - expected: "0.123", + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &Inspector{ + queries: tt.fields.queries, + vb: tt.fields.vb, + tracker: tt.fields.tracker, + enableCoverageReport: tt.fields.enableCoverageReport, + coverageReport: tt.fields.coverageReport, + } + if got := c.GetCoverageReport(); !reflect.DeepEqual(got, tt.want) { + t.Errorf("Inspector.GetCoverageReport() = %v, want %v", got, tt.want) + } + }) + } +} + +// TestInspect tests the functions [Inspect()] and all the methods called by them +func TestInspect(t *testing.T) { //nolint + ctx := context.Background() + opaQuery, _ := rego.New( + rego.Query(regoQuery), + rego.Module("add_instead_of_copy", `package Cx + + CxPolicy [ result ] { + resource := input.document[i].command[name][_] + resource.Cmd == "add" + not tarfileChecker(resource.Value, ".tar") + not tarfileChecker(resource.Value, ".tar.") + + result := { + "documentId": input.document[i].id, + "searchKey": sprintf("{{%s}}", [resource.Original]), + "issueType": "IncorrectValue", + "keyExpectedValue": sprintf("'COPY' %s", [resource.Value[0]]), + "keyActualValue": sprintf("'ADD' %s", [resource.Value[0]]) + } + } + + tarfileChecker(cmdValue, elem) { + contains(cmdValue[_], elem) + }`), + rego.UnsafeBuiltins(unsafeRegoFunctions), + ).PrepareForEval(ctx) + + opaQueries := make([]*preparedQuery, 0, 1) + opaQueries = append(opaQueries, &preparedQuery{ + opaQuery: opaQuery, + metadata: model.QueryMetadata{ + Query: "add_instead_of_copy", + Content: `package Cx + + CxPolicy [ result ] { + resource := input.document[i].command[name][_] + resource.Cmd == "add" + not tarfileChecker(resource.Value, ".tar") + not tarfileChecker(resource.Value, ".tar.") + + result := { + "documentId": input.document[i].id, + "searchKey": sprintf("{{%s}}", [resource.Original]), + "issueType": "IncorrectValue", + "keyExpectedValue": sprintf("'COPY' %s", [resource.Value[0]]), + "keyActualValue": sprintf("'ADD' %s", [resource.Value[0]]) + } + } + + tarfileChecker(cmdValue, elem) { + contains(cmdValue[_], elem) + }`, }, + }) + + type fields struct { + queries []*preparedQuery + vb VulnerabilityBuilder + tracker Tracker + enableCoverageReport bool + coverageReport cover.Report + } + type args struct { + ctx context.Context + scanID string + files model.FileMetadatas + } + tests := []struct { + name string + fields fields + args args + want []model.Vulnerability + wantErr bool + }{ { - payload: false, - expected: "false", + name: "Test", + fields: fields{ + queries: opaQueries, + vb: DefaultVulnerabilityBuilder, + tracker: &tracker.CITracker{}, + enableCoverageReport: true, + coverageReport: cover.Report{}, + }, + args: args{ + ctx: ctx, + scanID: "scanID", + files: model.FileMetadatas{ + { + ID: "3a3be8f7-896e-4ef8-9db3-d6c19e60510b", + ScanID: "scanID", + Document: map[string]interface{}{ + "id": nil, + "file": nil, + "command": map[string]interface{}{ + "openjdk:10-jdk": []map[string]interface{}{ + { + "Cmd": "add", + "EndLine": 8, + "JSON": false, + "Original": "ADD ${JAR_FILE} app.jar", + "StartLine": 8, + "SubCmd": "", + "Value": []string{ + "app.jar", + }, + }, + }, + }, + }, + OriginalData: "orig_data", + Kind: "DOCKERFILE", + FileName: "assets/queries/dockerfile/add_instead_of_copy/test/positive.dockerfile", + }, + }, + }, + want: []model.Vulnerability{ + { + ID: 0, + SimilarityID: "b84570a546f2064d483b5916d3bf3c6949c8cfc227a8c61fce22220b2f5d77bd", + ScanID: "scanID", + FileID: "3a3be8f7-896e-4ef8-9db3-d6c19e60510b", + FileName: "assets/queries/dockerfile/add_instead_of_copy/test/positive.dockerfile", + QueryID: "Undefined", + QueryName: "Anonymous", + Severity: "INFO", + Line: -1, + IssueType: "IncorrectValue", + SearchKey: "{{ADD ${JAR_FILE} app.jar}}", + KeyExpectedValue: "'COPY' app.jar", + KeyActualValue: "'ADD' app.jar", + Value: nil, + Output: `{"documentId":"3a3be8f7-896e-4ef8-9db3-d6c19e60510b","issueType":"IncorrectValue","keyActualValue":"'ADD' app.jar","keyExpectedValue":"'COPY' app.jar","searchKey":"{{ADD ${JAR_FILE} app.jar}}"}`, // nolint + }, + }, + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &Inspector{ + queries: tt.fields.queries, + vb: tt.fields.vb, + tracker: tt.fields.tracker, + enableCoverageReport: tt.fields.enableCoverageReport, + coverageReport: tt.fields.coverageReport, + } + got, err := c.Inspect(tt.args.ctx, tt.args.scanID, tt.args.files) + if tt.wantErr { + if err == nil { + t.Errorf("Inspector.GetCoverageReport() = %v, want %v", err, tt.want) + } + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("Inspector.GetCoverageReport() = %v, want %v", got, tt.want) + } + }) + } +} + +// TestNewInspector tests the functions [NewInspector()] and all the methods called by them +func TestNewInspector(t *testing.T) { // nolint + if err := test.ChangeCurrentDir("kics"); err != nil { + t.Fatal(err) + } + track := &tracker.CITracker{} + sources := &query.FilesystemSource{ + Source: "./test/fixtures/all_auth_users_get_read_access", + } + vbs := DefaultVulnerabilityBuilder + opaQueries := make([]*preparedQuery, 0, 1) + opaQueries = append(opaQueries, &preparedQuery{ + opaQuery: rego.PreparedEvalQuery{}, + metadata: model.QueryMetadata{ + Query: "all_auth_users_get_read_access", + Content: `package Cx + +CxPolicy [ result ] { + resource := input.document[i].resource.aws_s3_bucket[name] + role = "authenticated-read" + resource.acl == role + + result := { + "documentId": input.document[i].id, + "searchKey": sprintf("aws_s3_bucket[%s].acl", [name]), + "issueType": "IncorrectValue", + "keyExpectedValue": sprintf("aws_s3_bucket[%s].acl is private", [name]), + "keyActualValue": sprintf("aws_s3_bucket[%s].acl is %s", [name, role]) + } +} +`, + Platform: "unknown", + Metadata: map[string]interface{}{ + "id": "57b9893d-33b1-4419-bcea-a717ea87e139", + "queryName": "All Auth Users Get Read Access", + "severity": "HIGH", + "category": "Identity and Access Management", + "descriptionText": "Misconfigured S3 buckets can leak private information to the entire internet or allow unauthorized data tampering / deletion", // nolint + "descriptionUrl": "https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket#acl", + }, }, + }) + + type args struct { + ctx context.Context + source QueriesSource + vb VulnerabilityBuilder + tracker Tracker + } + tests := []struct { + name string + args args + want *Inspector + wantErr bool + }{ { - payload: nil, - expected: "null", + name: "test_new_inspector", + args: args{ + ctx: context.Background(), + vb: vbs, + tracker: track, + source: sources, + }, + want: &Inspector{ + vb: vbs, + tracker: track, + queries: opaQueries, + }, + wantErr: false, }, } - - for i, testCase := range testCases { - t.Run(fmt.Sprintf("mapKeyToString-%d", i), func(t *testing.T) { - v, err := mapKeyToString(map[string]interface{}{"key": testCase.payload}, "key", false) - require.Nil(t, err) - require.Equal(t, testCase.expected, *v) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := NewInspector(tt.args.ctx, tt.args.source, tt.args.vb, tt.args.tracker) + if (err != nil) != tt.wantErr { + t.Errorf("NewInspector() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got.queries[0].metadata, tt.want.queries[0].metadata) { + t.Errorf("NewInspector() metadata = %v, want %v", got.queries[0].metadata, tt.want.queries[0].metadata) + } + if !reflect.DeepEqual(got.tracker, tt.want.tracker) { + t.Errorf("NewInspector() tracker = %v, want %v", got.tracker, tt.want.tracker) + } + require.NotNil(t, got.vb) }) } } diff --git a/pkg/engine/query/source.go b/pkg/engine/query/source.go index c3749b06c87..e51c92bfce4 100644 --- a/pkg/engine/query/source.go +++ b/pkg/engine/query/source.go @@ -34,15 +34,15 @@ const ( func GetPathToLibrary(platform, relativeBasePath string) string { libraryPath := filepath.Join(relativeBasePath, LibrariesBasePath) - if strings.Contains(platform, "ansible") { + if strings.Contains(strings.ToUpper(platform), strings.ToUpper("ansible")) { return filepath.FromSlash(libraryPath + "/ansible/" + LibraryFileName) - } else if strings.Contains(platform, "cloudformation") { + } else if strings.Contains(strings.ToUpper(platform), strings.ToUpper("cloudFormation")) { return filepath.FromSlash(libraryPath + "/cloudformation/" + LibraryFileName) - } else if strings.Contains(platform, "dockerfile") { + } else if strings.Contains(strings.ToUpper(platform), strings.ToUpper("dockerfile")) { return filepath.FromSlash(libraryPath + "/dockerfile/" + LibraryFileName) - } else if strings.Contains(platform, "k8s") { + } else if strings.Contains(strings.ToUpper(platform), strings.ToUpper("k8s")) { return filepath.FromSlash(libraryPath + "/k8s/" + LibraryFileName) - } else if strings.Contains(platform, "terraform") { + } else if strings.Contains(strings.ToUpper(platform), strings.ToUpper("terraform")) { return filepath.FromSlash(libraryPath + "/terraform/" + LibraryFileName) } diff --git a/pkg/engine/query/source_test.go b/pkg/engine/query/source_test.go new file mode 100644 index 00000000000..813aade872b --- /dev/null +++ b/pkg/engine/query/source_test.go @@ -0,0 +1,305 @@ +package query + +import ( + "reflect" + "strings" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/Checkmarx/kics/pkg/model" + "github.com/Checkmarx/kics/test" +) + +// TestFilesystemSource_GetGenericQuery tests the functions [GetGenericQuery()] and all the methods called by them +func TestFilesystemSource_GetGenericQuery(t *testing.T) { // nolint + if err := test.ChangeCurrentDir("kics"); err != nil { + t.Fatal(err) + } + type fields struct { + Source string + } + type args struct { + platform string + } + tests := []struct { + name string + fields fields + args args + contains string + wantErr bool + }{ + { + name: "get_generic_query_terraform", + fields: fields{ + Source: "./assets/queries/template", + }, + args: args{ + platform: "terraform", + }, + contains: "generic.terraform", + wantErr: false, + }, + { + name: "get_generic_query_common", + fields: fields{ + Source: "./assets/queries/template", + }, + args: args{ + platform: "common", + }, + contains: "generic.common", + wantErr: false, + }, + { + name: "get_generic_query_cloudformation", + fields: fields{ + Source: "./assets/queries/template", + }, + args: args{ + platform: "cloudFormation", + }, + contains: "generic.cloudformation", + wantErr: false, + }, + { + name: "get_generic_query_ansible", + fields: fields{ + Source: "./assets/queries/template", + }, + args: args{ + platform: "ansible", + }, + contains: "generic.ansible", + wantErr: false, + }, + { + name: "get_generic_query_dockerfile", + fields: fields{ + Source: "./assets/queries/template", + }, + args: args{ + platform: "dockerfile", + }, + contains: "generic.dockerfile", + wantErr: false, + }, + { + name: "get_generic_query_k8s", + fields: fields{ + Source: "./assets/queries/template", + }, + args: args{ + platform: "k8s", + }, + contains: "generic.k8s", + wantErr: false, + }, + { + name: "get_generic_query_unknown", + fields: fields{ + Source: "./assets/queries/template", + }, + args: args{ + platform: "unknown", + }, + contains: "generic.common", + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s := &FilesystemSource{ + Source: tt.fields.Source, + } + got, err := s.GetGenericQuery(tt.args.platform) + if (err != nil) != tt.wantErr { + t.Errorf("FilesystemSource.GetGenericQuery() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !strings.Contains(got, tt.contains) { + t.Errorf("FilesystemSource.GetGenericQuery() = %v, doesn't contains %v", got, tt.contains) + } + }) + } +} + +// TestFilesystemSource_GetQueries tests the functions [GetQueries()] and all the methods called by them +func TestFilesystemSource_GetQueries(t *testing.T) { + if err := test.ChangeCurrentDir("kics"); err != nil { + t.Fatal(err) + } + + type fields struct { + Source string + } + tests := []struct { + name string + fields fields + want []model.QueryMetadata + wantErr bool + }{ + { + name: "get_queries_1", + fields: fields{ + Source: "./assets/queries/template", + }, + want: []model.QueryMetadata{ + { + Query: "template", + Content: `package Cx + +CxPolicy [ result ] { + resource := input.document[i].resource + resource == "" + + result := { + "documentId": input.document[i].id, + "searchKey": sprintf("%s", [resource]), + "issueType": "IncorrectValue", #"MissingAttribute" / "RedundantAttribute" + "keyExpectedValue": "", + "keyActualValue": resource + } +}`, + Metadata: map[string]interface{}{ + "category": nil, + "descriptionText": "", + "descriptionUrl": "#", + "id": "", + "queryName": "", + "severity": "HIGH", + }, + Platform: "unknown", + }, + }, + wantErr: false, + }, + { + name: "get_queries_error", + fields: fields{ + Source: "../no-path", + }, + want: nil, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s := &FilesystemSource{ + Source: tt.fields.Source, + } + got, err := s.GetQueries() + if (err != nil) != tt.wantErr { + t.Errorf("FilesystemSource.GetQueries() error = %v, wantErr %v", err, tt.wantErr) + return + } + require.Equal(t, tt.want, got) + }) + } +} + +// Test_ReadMetadata tests the functions [ReadMetadata()] and all the methods called by them +func Test_ReadMetadata(t *testing.T) { + if err := test.ChangeCurrentDir("kics"); err != nil { + t.Fatal(err) + } + type args struct { + queryDir string + } + tests := []struct { + name string + args args + want map[string]interface{} + }{ + { + name: "read_metadata_error", + args: args{ + queryDir: "error-path", + }, + want: nil, + }, + { + name: "read_metadata_template", + args: args{ + queryDir: "./assets/queries/template", + }, + want: map[string]interface{}{ + "category": nil, + "descriptionText": "", + "descriptionUrl": "#", + "id": "", + "queryName": "", + "severity": "HIGH", + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := ReadMetadata(tt.args.queryDir); !reflect.DeepEqual(got, tt.want) { + t.Errorf("readMetadata() = %v, want %v", got, tt.want) + } + }) + } +} + +// Test_getPlatform tests the functions [getPlatform()] and all the methods called by them +func Test_getPlatform(t *testing.T) { + type args struct { + queryPath string + } + tests := []struct { + name string + args args + want string + }{ + { + name: "get_platform_commonQuery", + args: args{ + queryPath: "../test/commonQuery/test", + }, + want: "commonQuery", + }, + { + name: "get_platform_ansible", + args: args{ + queryPath: "../test/ansible/test", + }, + want: "ansible", + }, + { + name: "get_platform_cloudFormation", + args: args{ + queryPath: "../test/cloudFormation/test", + }, + want: "cloudFormation", + }, + { + name: "get_platform_dockerfile", + args: args{ + queryPath: "../test/dockerfile/test", + }, + want: "dockerfile", + }, + { + name: "get_platform_k8s", + args: args{ + queryPath: "../test/k8s/test", + }, + want: "k8s", + }, + { + name: "get_platform_terraform", + args: args{ + queryPath: "../test/terraform/test", + }, + want: "terraform", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := getPlatform(tt.args.queryPath); got != tt.want { + t.Errorf("getPlatform() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/pkg/engine/similarity_id_test.go b/pkg/engine/similarity_id_test.go index c43fac89935..4617be1ae64 100644 --- a/pkg/engine/similarity_id_test.go +++ b/pkg/engine/similarity_id_test.go @@ -166,6 +166,7 @@ var ( } ) +// TestComputeSimilarityID tests the functions [ComputeSimilarityID()] and all the methods called by them func TestComputeSimilarityID(t *testing.T) { for _, tc := range similarityIDTests { t.Run(tc.name, func(tt *testing.T) { @@ -181,6 +182,7 @@ func TestComputeSimilarityID(t *testing.T) { } } +// TestStandardizeFilePathEquals tests the functions [standardizeFilePath()] and all the methods called by them func TestStandardizeFilePathEquals(t *testing.T) { tests := []struct { name string diff --git a/pkg/engine/vulnerability_builder_test.go b/pkg/engine/vulnerability_builder_test.go index 64e19aadd6e..a5b462ed059 100644 --- a/pkg/engine/vulnerability_builder_test.go +++ b/pkg/engine/vulnerability_builder_test.go @@ -2,13 +2,16 @@ package engine import ( "fmt" + "reflect" "testing" + "github.com/Checkmarx/kics/internal/tracker" "github.com/Checkmarx/kics/pkg/model" "github.com/rs/zerolog" "github.com/stretchr/testify/require" ) +// TestDetectDockerLine tests the functions [DetectDockerLine()] and all the methods called by them func TestDetectDockerLine(t *testing.T) { testCases := []struct { expected int @@ -100,6 +103,7 @@ ENTRYPOINT ["kubectl"] } } +// TestSelectLineWithMinimumDistance tests the functions [SelectLineWithMinimumDistance()] and all the methods called by them func TestSelectLineWithMinimumDistance(t *testing.T) { values := []struct { distances map[int]int @@ -139,3 +143,312 @@ func TestSelectLineWithMinimumDistance(t *testing.T) { }) } } + +// TestMapKeyToString tests the functions [MapKeyToString()] and all the methods called by them +func TestMapKeyToString(t *testing.T) { + testCases := []struct { + payload interface{} + expected string + }{ + { + payload: "test", + expected: "test", + }, + { + payload: 123, + expected: "123", + }, + { + payload: 0.123, + expected: "0.123", + }, + { + payload: false, + expected: "false", + }, + { + payload: nil, + expected: "null", + }, + } + + for i, testCase := range testCases { + t.Run(fmt.Sprintf("mapKeyToString-%d", i), func(t *testing.T) { + v, err := mapKeyToString(map[string]interface{}{"key": testCase.payload}, "key", false) + require.Nil(t, err) + require.Equal(t, testCase.expected, *v) + }) + } + for i, testCase := range testCases { + t.Run(fmt.Sprintf("mapKeyToString-%d", i), func(t *testing.T) { + _, err := mapKeyToString(map[string]interface{}{"t": testCase.payload}, "key", false) + require.Error(t, err) + }) + } +} + +// Test_mergeWithMetadata tests the functions [mergeWithMetadata()] and all the methods called by them +func Test_mergeWithMetadata(t *testing.T) { + type args struct { + base map[string]interface{} + additional map[string]interface{} + } + tests := []struct { + name string + args args + want map[string]interface{} + }{ + { + name: "mergeWithMetadata", + args: args{ + base: map[string]interface{}{ + "key": "123", + }, + additional: map[string]interface{}{ + "key": "teste", + }, + }, + want: map[string]interface{}{ + "key": "123", + }, + }, + { + name: "mergeWithMetadata_2", + args: args{ + base: map[string]interface{}{ + "key": "123", + }, + additional: map[string]interface{}{ + "r": "teste2", + }, + }, + want: map[string]interface{}{ + "key": "123", + "r": "teste2", + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := mergeWithMetadata(tt.args.base, tt.args.additional); !reflect.DeepEqual(got, tt.want) { + t.Errorf("mergeWithMetadata() = %v, want %v", got, tt.want) + } + }) + } +} + +// Test_detectLine tests the functions [detectLine()] and all the methods called by them +func Test_detectLine(t *testing.T) { + type args struct { + ctx QueryContext + file *model.FileMetadata + searchKey string + } + tests := []struct { + name string + args args + want int + }{ + { + name: "detect_line", + args: args{ + ctx: QueryContext{ + scanID: "scanID", + }, + file: &model.FileMetadata{ + ScanID: "scanID", + ID: "Test", + Kind: model.KindTerraform, + OriginalData: `resource "aws_s3_bucket" "b" { + bucket = "my-tf-test-bucket" + acl = "authenticated-read" + + tags = { + Name = "My bucket" + Environment = "Dev" + } + } + `, + }, + searchKey: "aws_s3_bucket[b].acl", + }, + want: 3, + }, + { + name: "detect_line_with_curly_brackets", + args: args{ + ctx: QueryContext{ + scanID: "scanID", + }, + file: &model.FileMetadata{ + ScanID: "scanID", + ID: "Test", + Kind: model.KindTerraform, + OriginalData: `resource "aws_s3_bucket" "b" { + bucket = "my-tf-test-bucket" + acl = "authenticated-read" + + tags = { + Name = "My bucket" + Environment = "Dev.123" + Environment = "test" + } + } + `, + }, + searchKey: "aws_s3_bucket[b].Environment={{Dev.123}}", + }, + want: 7, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := detectLine(tt.args.file, tt.args.searchKey, &zerolog.Logger{}); got != tt.want { + t.Errorf("detectLine() = %v, want %v", got, tt.want) + } + }) + } +} + +// Test_mustMapKeyToString tests the functions [mustMapKeyToString()] and all the methods called by them +func Test_mustMapKeyToString(t *testing.T) { + type args struct { + m map[string]interface{} + key string + } + tests := []struct { + name string + args args + want string + }{ + { + name: "mustMapKeyToString", + args: args{ + m: map[string]interface{}{ + "key": 123, + }, + key: "key", + }, + want: "123", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := mustMapKeyToString(tt.args.m, tt.args.key) + require.Equal(t, tt.want, *got) + }) + } +} + +// Test_ptrStringToString tests the functions [ptrStringToString()] and all the methods called by them +func Test_ptrStringToString(t *testing.T) { + type args struct { + v string + } + tests := []struct { + name string + args args + want string + }{ + { + name: "ptrStringToString", + args: args{ + v: "123", + }, + want: "123", + }, + { + name: "ptrStringToString_empty", + args: args{ + v: "nil", + }, + want: "", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.args.v == "nil" { + if got := ptrStringToString(nil); got != tt.want { + t.Errorf("ptrStringToString() = %v, want %v", got, tt.want) + } + } else { + if got := ptrStringToString(&tt.args.v); got != tt.want { + t.Errorf("ptrStringToString() = %v, want %v", got, tt.want) + } + } + }) + } +} + +// TestDefaultVulnerabilityBuilder tests the functions [DefaultVulnerabilityBuilder] and all the methods called by them +func TestDefaultVulnerabilityBuilder(t *testing.T) { + type args struct { + ctx QueryContext + v interface{} + tracker Tracker + } + tests := []struct { + name string + args args + want model.Vulnerability + wantErr bool + }{ + { + name: "DefaultVulnerabilityBuilder", + args: args{ + tracker: &tracker.CITracker{}, + ctx: QueryContext{ + scanID: "ScanID", + query: &preparedQuery{ + metadata: model.QueryMetadata{ + Metadata: map[string]interface{}{ + "key": "123", + "severity": "INFO", + "issueType": "IncorrectValue", + "searchKey": "testSearchKey", + }, + Query: "TestQuery", + }, + }, + files: map[string]model.FileMetadata{ + "testV": {}, + }, + }, + v: map[string]interface{}{ + "documentId": "testV", + }, + }, + want: model.Vulnerability{ + ID: 0, + SimilarityID: "2fefa27cc667decf203d10f103b7ffdec232e9af16e361f47d626e72c72b8d63", + ScanID: "ScanID", + FileID: "", + FileName: "", + QueryID: "Undefined", + QueryName: "Anonymous", + Severity: "INFO", + Line: -1, + IssueType: "IncorrectValue", + SearchKey: "testSearchKey", + KeyActualValue: "", + KeyExpectedValue: "", + Value: nil, + Output: `{"documentId":"testV","issueType":"IncorrectValue","key":"123","searchKey":"testSearchKey","severity":"INFO"}`, + }, + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := DefaultVulnerabilityBuilder(tt.args.ctx, tt.args.tracker, tt.args.v) + if (err != nil) != tt.wantErr { + t.Errorf("DefaultVulnerabilityBuilder() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("DefaultVulnerabilityBuilder() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/pkg/kics/service_test.go b/pkg/kics/service_test.go new file mode 100644 index 00000000000..330a205d133 --- /dev/null +++ b/pkg/kics/service_test.go @@ -0,0 +1,114 @@ +package kics + +import ( + "context" + "fmt" + "reflect" + "testing" + + "github.com/Checkmarx/kics/internal/storage" + "github.com/Checkmarx/kics/internal/tracker" + "github.com/Checkmarx/kics/pkg/engine" + "github.com/Checkmarx/kics/pkg/model" + "github.com/Checkmarx/kics/pkg/parser" + dockerParser "github.com/Checkmarx/kics/pkg/parser/docker" + terraformParser "github.com/Checkmarx/kics/pkg/parser/terraform" + yamlParser "github.com/Checkmarx/kics/pkg/parser/yaml" + "github.com/Checkmarx/kics/pkg/source" +) + +// TestService tests the functions [GetVulnerabilities(), GetScanSummary(),StartScan()] and all the methods called by them +func TestService(t *testing.T) { + mockParser, mockFilesSource := createParserSourceProvider("../../assets/queries/template") + + type fields struct { + SourceProvider SourceProvider + Storage Storage + Parser *parser.Parser + Inspector *engine.Inspector + Tracker Tracker + } + type args struct { + ctx context.Context + scanID string + scanIDs []string + } + type want struct { + vulnerabilities []model.Vulnerability + severitySummary []model.SeveritySummary + } + tests := []struct { + name string + fields fields + args args + want want + wantErr bool + }{ + { + name: "service", + fields: fields{ + Inspector: &engine.Inspector{}, + Parser: mockParser, + Tracker: &tracker.CITracker{}, + Storage: storage.NewMemoryStorage(), + SourceProvider: mockFilesSource, + }, + args: args{ + ctx: nil, + scanID: "scanID", + scanIDs: []string{"scanID"}, + }, + wantErr: false, + want: want{ + vulnerabilities: []model.Vulnerability{}, + severitySummary: nil, + }, + }, + } + for _, tt := range tests { + s := &Service{ + SourceProvider: tt.fields.SourceProvider, + Storage: tt.fields.Storage, + Parser: tt.fields.Parser, + Inspector: tt.fields.Inspector, + Tracker: tt.fields.Tracker, + } + t.Run(fmt.Sprintf(tt.name+"_get_vulnerabilities"), func(t *testing.T) { + got, err := s.GetVulnerabilities(tt.args.ctx, tt.args.scanID) + if (err != nil) != tt.wantErr { + t.Errorf("Service.GetVulnerabilities() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want.vulnerabilities) { + t.Errorf("Service.GetVulnerabilities() = %v, want %v", got, tt.want) + } + }) + t.Run(fmt.Sprintf(tt.name+"_get_scan_summary"), func(t *testing.T) { + got, err := s.GetScanSummary(tt.args.ctx, tt.args.scanIDs) + if (err != nil) != tt.wantErr { + t.Errorf("Service.GetScanSummary() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want.severitySummary) { + t.Errorf("Service.GetScanSummary() = %v, want %v", got, tt.want) + } + }) + t.Run(fmt.Sprintf(tt.name+"_start_scan"), func(t *testing.T) { + if err := s.StartScan(tt.args.ctx, tt.args.scanID); (err != nil) != tt.wantErr { + t.Errorf("Service.StartScan() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func createParserSourceProvider(path string) (*parser.Parser, *source.FileSystemSourceProvider) { + mockParser := parser.NewBuilder(). + Add(&yamlParser.Parser{}). + Add(terraformParser.NewDefault()). + Add(&dockerParser.Parser{}). + Build() + + mockFilesSource, _ := source.NewFileSystemSourceProvider(path, []string{}) + + return mockParser, mockFilesSource +} diff --git a/pkg/model/model_test.go b/pkg/model/model_test.go index f198eae4fe8..1ec7ba2b8f1 100644 --- a/pkg/model/model_test.go +++ b/pkg/model/model_test.go @@ -6,6 +6,7 @@ import ( "github.com/stretchr/testify/require" ) +// TestExtensions_MatchedFilesRegex tests the functions [MatchedFilesRegex()] and all the methods called by them func TestExtensions_MatchedFilesRegex(t *testing.T) { var e Extensions require.Equal(t, "NO_MATCHED_FILES", e.MatchedFilesRegex()) @@ -24,3 +25,53 @@ func TestExtensions_MatchedFilesRegex(t *testing.T) { } require.Equal(t, "(.*)(\\.tf|\\.txt)$", e.MatchedFilesRegex()) } + +// TestInclude tests the functions [Include()] and all the methods called by them +func TestInclude(t *testing.T) { + e := Extensions{ + ".txt": struct{}{}, + ".tf": struct{}{}, + } + require.Equal(t, true, e.Include(".txt")) +} + +// TestFileMetadatas tests the functions [Combine(),ToMap()] and all the methods called by them +func TestFileMetadatas(t *testing.T) { + m := FileMetadatas{ + { + ID: "id", + ScanID: "scan_id", + OriginalData: "orig_data", + FileName: "file_name", + Document: Document{ + "id": "", + }, + }, + } + + mEmptyDocuments := FileMetadatas{ + { + ID: "id", + ScanID: "scan_id", + OriginalData: "orig_data", + FileName: "file_name", + Document: nil, + }, + } + + t.Run("to_map", func(t *testing.T) { + result := m.ToMap() + require.Len(t, result, 1) + require.Equal(t, m[0], result["id"]) + }) + + t.Run("combine", func(t *testing.T) { + result := m.Combine() + require.Equal(t, Documents{Documents: []Document{{"file": "file_name", "id": "id"}}}, result) + }) + + t.Run("combine_empty_documents", func(t *testing.T) { + result := mEmptyDocuments.Combine() + require.Equal(t, Documents{Documents: []Document{}}, result) + }) +} diff --git a/pkg/model/summary_test.go b/pkg/model/summary_test.go new file mode 100644 index 00000000000..576a41fc3ef --- /dev/null +++ b/pkg/model/summary_test.go @@ -0,0 +1,88 @@ +package model + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +// TestCreateSummary tests the functions [CreateSummary()] and all the methods called by them +func TestCreateSummary(t *testing.T) { + vulnerabilities := []Vulnerability{ + { + ID: 1, + ScanID: "scanID", + FileID: "fileId", + FileName: "fileName", + QueryID: "QueryID", + QueryName: "query_name", + Severity: SeverityHigh, + Line: 1, + IssueType: IssueTypeMissingAttribute, + SearchKey: "searchKey", + KeyExpectedValue: "key_expected_value", + KeyActualValue: "key_actual_value", + Output: "-", + }, + } + + counter := Counters{ + ScannedFiles: 2, + ParsedFiles: 3, + FailedToExecuteQueries: 0, + TotalQueries: 0, + FailedToScanFiles: 0, + } + + t.Run("create_summary_empty", func(t *testing.T) { + summary := CreateSummary(counter, []Vulnerability{}, "scanID") + require.Equal(t, summary, Summary{ + Counters: counter, + SeveritySummary: SeveritySummary{ + ScanID: "scanID", + SeverityCounters: map[Severity]int{ + "INFO": 0, + "LOW": 0, + "MEDIUM": 0, + "HIGH": 0, + }, + }, + Queries: []VulnerableQuery{}, + }) + }) + + t.Run("create_summary", func(t *testing.T) { + summary := CreateSummary(counter, vulnerabilities, "scanID") + require.Equal(t, summary, Summary{ + Counters: counter, + SeveritySummary: SeveritySummary{ + ScanID: "scanID", + SeverityCounters: map[Severity]int{ + "INFO": 0, + "LOW": 0, + "MEDIUM": 0, + "HIGH": 1, + }, + TotalCounter: 1, + }, + Queries: []VulnerableQuery{ + { + QueryName: "query_name", + QueryID: "QueryID", + Severity: SeverityHigh, + Files: []VulnerableFile{ + { + FileName: "fileName", + Line: 1, + IssueType: IssueTypeMissingAttribute, + SearchKey: "searchKey", + KeyExpectedValue: "key_expected_value", + KeyActualValue: "key_actual_value", + Value: nil, + }, + }, + }, + }, + }) + }) +} diff --git a/pkg/parser/docker/parser_test.go b/pkg/parser/docker/parser_test.go index 475a239e049..fbf69c32bfa 100644 --- a/pkg/parser/docker/parser_test.go +++ b/pkg/parser/docker/parser_test.go @@ -7,16 +7,19 @@ import ( "github.com/stretchr/testify/require" ) +// TestParser_GetKind tests the functions [GetKind()] and all the methods called by them func TestParser_GetKind(t *testing.T) { p := &Parser{} require.Equal(t, model.KindDOCKER, p.GetKind()) } +// TestParser_SupportedExtensions tests the functions [SupportedExtensions()] and all the methods called by them func TestParser_SupportedExtensions(t *testing.T) { p := &Parser{} require.Equal(t, []string{"Dockerfile", ".dockerfile"}, p.SupportedExtensions()) } +// TestParser_Parse tests the functions [Parse()] and all the methods called by them func TestParser_Parse(t *testing.T) { p := &Parser{} sample := []string{ diff --git a/pkg/parser/json/parser_test.go b/pkg/parser/json/parser_test.go index 00ef2090c98..e8c2e8a44cb 100644 --- a/pkg/parser/json/parser_test.go +++ b/pkg/parser/json/parser_test.go @@ -7,16 +7,19 @@ import ( "github.com/stretchr/testify/require" ) +// TestParser_GetKind tests the functions [GetKind()] and all the methods called by them func TestParser_GetKind(t *testing.T) { p := &Parser{} require.Equal(t, model.KindJSON, p.GetKind()) } +// TestParser_SupportedExtensions tests the functions [SupportedExtensions()] and all the methods called by them func TestParser_SupportedExtensions(t *testing.T) { p := &Parser{} require.Equal(t, []string{".json"}, p.SupportedExtensions()) } +// TestParser_Parse tests the functions [Parse()] and all the methods called by them func TestParser_Parse(t *testing.T) { p := &Parser{} have := ` diff --git a/pkg/parser/parser_test.go b/pkg/parser/parser_test.go index ee82e6c0e5d..6470d1edd6f 100644 --- a/pkg/parser/parser_test.go +++ b/pkg/parser/parser_test.go @@ -11,13 +11,9 @@ import ( "github.com/stretchr/testify/require" ) +// TestParser_Parse tests the functions [Parse()] and all the methods called by them func TestParser_Parse(t *testing.T) { - p := NewBuilder(). - Add(&jsonParser.Parser{}). - Add(&yamlParser.Parser{}). - Add(terraformParser.NewDefault()). - Add(&dockerParser.Parser{}). - Build() + p := initilizeBuilder() docs, kind, err := p.Parse("test.json", []byte(` { @@ -51,6 +47,7 @@ martin: require.Equal(t, model.KindDOCKER, kind) } +// TestParser_Empty tests the functions [Parse()] and all the methods called by them (tests an empty parser) func TestParser_Empty(t *testing.T) { p := NewBuilder(). Build() @@ -62,13 +59,9 @@ func TestParser_Empty(t *testing.T) { require.Equal(t, ErrNotSupportedFile, err) } +// TestParser_SupportedExtensions tests the functions [SupportedExtensions()] and all the methods called by them func TestParser_SupportedExtensions(t *testing.T) { - p := NewBuilder(). - Add(&jsonParser.Parser{}). - Add(&yamlParser.Parser{}). - Add(terraformParser.NewDefault()). - Add(&dockerParser.Parser{}). - Build() + p := initilizeBuilder() extensions := p.SupportedExtensions() require.NotNil(t, extensions) @@ -78,3 +71,12 @@ func TestParser_SupportedExtensions(t *testing.T) { require.Contains(t, extensions, ".dockerfile") require.Contains(t, extensions, "Dockerfile") } + +func initilizeBuilder() *Parser { + return NewBuilder(). + Add(&jsonParser.Parser{}). + Add(&yamlParser.Parser{}). + Add(terraformParser.NewDefault()). + Add(&dockerParser.Parser{}). + Build() +} diff --git a/pkg/parser/terraform/converter/default_test.go b/pkg/parser/terraform/converter/default_test.go new file mode 100644 index 00000000000..875bbd40c37 --- /dev/null +++ b/pkg/parser/terraform/converter/default_test.go @@ -0,0 +1,260 @@ +package converter + +import ( + "bytes" + "encoding/json" + "testing" + + "github.com/Checkmarx/kics/pkg/model" + "github.com/hashicorp/hcl/v2" + "github.com/hashicorp/hcl/v2/hclsyntax" + "github.com/stretchr/testify/require" +) + +// TestLabelsWithNestedBlock tests the functions [DefaultConverted] and all the methods called by them (test with nested block) +func TestLabelsWithNestedBlock(t *testing.T) { + input := ` +block "label_one" "label_two" { + nested_block { } +}` + + expected := `{ + "block": { + "label_one": { + "label_two": { + "nested_block": {} + } + } + } +}` + + file, _ := hclsyntax.ParseConfig([]byte(input), "testFileName", hcl.Pos{Byte: 0, Line: 1, Column: 1}) + + body, _, err := DefaultConverted(file) + if err != nil { + t.Fatal("parse bytes:", err) + } + inputMarsheld, err := json.Marshal(body) + if err != nil { + t.Errorf("Error Marshling: %s", err) + } + compareTest(t, inputMarsheld, expected) +} + +// TestLabelsWithNestedBlock tests the functions [DefaultConverted] and all the methods called by them (test with single block) +func TestSingleBlock(t *testing.T) { + input := ` +block "label_one" { + attribute = "value" +} +` + + expected := `{ + "block": { + "label_one": { + "attribute": "value" + } + } +}` + + file, _ := hclsyntax.ParseConfig([]byte(input), "testFileName", hcl.Pos{Byte: 0, Line: 1, Column: 1}) + + body, _, err := DefaultConverted(file) + if err != nil { + t.Fatal("parse bytes:", err) + } + inputMarsheld, err := json.Marshal(body) + if err != nil { + t.Errorf("Error Marshling: %s", err) + } + compareTest(t, inputMarsheld, expected) +} + +// TestMultipleBlocks tests the functions [DefaultConverted] and all the methods called by them (test with multiple blocks) +func TestMultipleBlocks(t *testing.T) { + input := ` +block "label_one" { + attribute = "value" +} +block "label_one" { + attribute = "value_two" +} +` + + expected := `{ + "block": { + "label_one": [ + { + "attribute": "value" + }, + { + "attribute": "value_two" + } + ] + } +}` + + file, _ := hclsyntax.ParseConfig([]byte(input), "testFileName", hcl.Pos{Byte: 0, Line: 1, Column: 1}) + + body, _, err := DefaultConverted(file) + if err != nil { + t.Fatal("parse bytes:", err) + } + inputMarsheld, err := json.Marshal(body) + if err != nil { + t.Errorf("Error Marshling: %s", err) + } + compareTest(t, inputMarsheld, expected) +} + +// TestLabelsWithNestedBlock tests the functions [DefaultConverted] and all the methods called by them +func TestConversion(t *testing.T) { // nolint + const input = ` +locals { + test3 = 1 + 2 + test1 = "hello" + test2 = 5 + arr = [1, 2, 3, 4] + hyphen-test = 3 + temp = "${1 + 2} %{if local.test2 < 3}\"4\n\"%{endif}" + temp2 = "${"hi"} there" + quoted = "\"quoted\"" + squoted = "'quoted'" + x = -10 + y = -x + z = -(1 + 4) +} +locals { + other = { + num = local.test2 + 5 + thing = [for x in local.arr: x * 2] + "${local.test3}" = 4 + 3 = 1 + "local.test1" = 89 + "a.b.c[\"hi\"][3].*" = 3 + loop = "This has a for loop: %{for x in local.arr}x,%{endfor}" + a.b.c = "True" + } +} +locals { + heredoc = <<-EOF + This is a heredoc template. + It references ${local.other.3} + EOF + simple = "${4 - 2}" + cond = test3 > 2 ? 1: 0 + heredoc2 = < 0 { + cmd.SetArgs(args) + } + + return CaptureOutput(cmd.Execute) +} + +// ChangeCurrentDir gets current working directory and changes to its parent until finds the desired directory +// or fail +func ChangeCurrentDir(desiredDir string) error { + for currentDir, err := os.Getwd(); GetCurrentDirName(currentDir) != desiredDir; currentDir, err = os.Getwd() { + if err == nil { + if err = os.Chdir(".."); err != nil { + fmt.Printf("change path error = %v", err) + return fmt.Errorf("change path error = %v", err) + } + } else { + return fmt.Errorf("change path error = %v", err) + } + } + return nil +} + +// GetCurrentDirName returns current working directory +func GetCurrentDirName(path string) string { + dirs := strings.Split(path, string(os.PathSeparator)) + if dirs[len(dirs)-1] == "" && len(dirs) > 1 { + return dirs[len(dirs)-2] + } + return dirs[len(dirs)-1] +} diff --git a/test/queries_content_test.go b/test/queries_content_test.go index 118492be31f..d04676e681a 100644 --- a/test/queries_content_test.go +++ b/test/queries_content_test.go @@ -27,7 +27,7 @@ const ( ) var ( - validUUID = regexp.MustCompile(`(?i)^[0-9a-f]{8}-[0-9a-f]{4}-[0-5][0-9a-f]{3}-[089ab][0-9a-f]{3}-[0-9a-f]{12}$`) + validUUID = regexp.MustCompile(ValidUUIDRegex) severityList = []string{"HIGH", "MEDIUM", "LOW", "INFO"} requiredQueryResultProperties = []string{