diff --git a/pkg/iac-providers/terraform/commons/load-dir.go b/pkg/iac-providers/terraform/commons/load-dir.go index 74d8bb2df..18f4c3270 100644 --- a/pkg/iac-providers/terraform/commons/load-dir.go +++ b/pkg/iac-providers/terraform/commons/load-dir.go @@ -20,7 +20,6 @@ import ( "fmt" "os" "path/filepath" - "strings" "github.com/accurics/terrascan/pkg/downloader" "github.com/accurics/terrascan/pkg/iac-providers/output" @@ -52,9 +51,6 @@ type ModuleConfig struct { // resources present in rootDir and descendant modules func LoadIacDir(absRootDir string) (allResourcesConfig output.AllResourceConfigs, err error) { - // map to hold the download paths of remote modules - remoteModPaths := make(map[string]string) - // create a new config parser parser := hclConfigs.NewParser(afero.NewOsFs()) @@ -89,13 +85,13 @@ func LoadIacDir(absRootDir string) (allResourcesConfig output.AllResourceConfigs var pathToModule string if downloader.IsLocalSourceAddr(req.SourceAddr) { - pathToModule = processLocalSource(req, remoteModPaths, absRootDir) + pathToModule = processLocalSource(req) zap.S().Debugf("processing local module %q", pathToModule) } else if downloader.IsRegistrySourceAddr(req.SourceAddr) { // temp dir to download the remote repo tempDir := generateTempDir() - pathToModule, err = processTerraformRegistrySource(req, remoteModPaths, tempDir, r) + pathToModule, err = processTerraformRegistrySource(req, tempDir, r) if err != nil { zap.S().Errorf("failed to download remote module %q. error: '%v'", req.SourceAddr, err) } @@ -194,35 +190,21 @@ func generateTempDir() string { return filepath.Join(os.TempDir(), utils.GenRandomString(6)) } -func processLocalSource(req *hclConfigs.ModuleRequest, remoteModPaths map[string]string, absRootDir string) string { +func processLocalSource(req *hclConfigs.ModuleRequest) string { // determine the absolute path from root module to the sub module - // since we start at the end of the path, we need to assemble - // the parts in reverse order - pathToModule := req.SourceAddr - for p := req.Parent; p != nil; p = p.Parent { - pathToModule = filepath.Join(p.SourceAddr, pathToModule) - } + // while building the unified config, recursive calls are made for all the paths resolved, + // the source address in a tf file is relative while this func is called, hence, we get the + // path of caller dir, and join the source address of current module request to get the path to module - // check if pathToModule consists of a remote module downloaded - // if yes, then update the module path, with the remote module - // download path - keyFound := false - for remoteSourceAddr, downloadPath := range remoteModPaths { - if strings.Contains(pathToModule, remoteSourceAddr) { - pathToModule = strings.Replace(pathToModule, remoteSourceAddr, "", 1) - pathToModule = filepath.Join(downloadPath, pathToModule) - keyFound = true - break - } - } - if !keyFound { - pathToModule = filepath.Join(absRootDir, pathToModule) - } - return pathToModule + // get the caller dir path + callDirPath := filepath.Dir(req.CallRange.Filename) + + // join source address to the caller dir + return filepath.Join(callDirPath, req.SourceAddr) } -func processTerraformRegistrySource(req *hclConfigs.ModuleRequest, remoteModPaths map[string]string, tempDir string, m downloader.ModuleDownloader) (string, error) { +func processTerraformRegistrySource(req *hclConfigs.ModuleRequest, tempDir string, m downloader.ModuleDownloader) (string, error) { // regsrc.ParseModuleSource func returns a terraform registry module source // error check is not required as the source address is already validated module, _ := regsrc.ParseModuleSource(req.SourceAddr) @@ -232,7 +214,5 @@ func processTerraformRegistrySource(req *hclConfigs.ModuleRequest, remoteModPath return pathToModule, err } - // add the entry of remote module's source address to the map - remoteModPaths[req.SourceAddr] = pathToModule return pathToModule, nil } diff --git a/pkg/iac-providers/terraform/commons/load-dir_test.go b/pkg/iac-providers/terraform/commons/load-dir_test.go index 0914566e4..b2ffc79f9 100644 --- a/pkg/iac-providers/terraform/commons/load-dir_test.go +++ b/pkg/iac-providers/terraform/commons/load-dir_test.go @@ -18,9 +18,11 @@ package commons import ( "os" + "path/filepath" "testing" "github.com/accurics/terrascan/pkg/downloader" + "github.com/hashicorp/hcl/v2" hclConfigs "github.com/hashicorp/terraform/configs" ) @@ -28,28 +30,19 @@ import ( var ( testLocalSourceAddr = "./someModule" testRemoteSourceAddr = "terraform-aws-modules/eks/aws" + testDirPath = filepath.Join("root", "test") + testFileNamePath = filepath.Join(testDirPath, "main.tf") testModuleReqA = &hclConfigs.ModuleRequest{ SourceAddr: testLocalSourceAddr, - Parent: &hclConfigs.Config{ - SourceAddr: "./eks/aws", - }, - } - - testModuleReqB = &hclConfigs.ModuleRequest{ - SourceAddr: testLocalSourceAddr, - Parent: &hclConfigs.Config{ - SourceAddr: testRemoteSourceAddr, - }, + CallRange: hcl.Range{Filename: testFileNamePath}, } ) func TestProcessLocalSource(t *testing.T) { type args struct { - req *hclConfigs.ModuleRequest - remoteModPaths map[string]string - absRootDir string + req *hclConfigs.ModuleRequest } tests := []struct { name string @@ -59,25 +52,14 @@ func TestProcessLocalSource(t *testing.T) { { name: "no remote module", args: args{ - req: testModuleReqA, - absRootDir: "/home/somedir", - }, - want: "/home/somedir/eks/aws/someModule", - }, - { - name: "with remote module", - args: args{ - req: testModuleReqB, - remoteModPaths: map[string]string{ - testRemoteSourceAddr: "/var/temp/testDir", - }, + req: testModuleReqA, }, - want: "/var/temp/testDir/someModule", + want: filepath.Join(testDirPath, "someModule"), }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if got := processLocalSource(tt.args.req, tt.args.remoteModPaths, tt.args.absRootDir); got != tt.want { + if got := processLocalSource(tt.args.req); got != tt.want { t.Errorf("processLocalSource() got = %v, want = %v", got, tt.want) } }) @@ -127,7 +109,7 @@ func TestProcessTerraformRegistrySource(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { defer os.RemoveAll(tt.args.tempDir) - got, err := processTerraformRegistrySource(tt.args.req, tt.args.remoteModPaths, tt.args.tempDir, tt.args.m) + got, err := processTerraformRegistrySource(tt.args.req, tt.args.tempDir, tt.args.m) if (err != nil) != tt.wantErr { t.Errorf("processTerraformRegistrySource() got error = %v, wantErr = %v", err, tt.wantErr) return diff --git a/test/e2e/scan/scan_remote_test.go b/test/e2e/scan/scan_remote_test.go index 88b5a3ec8..e12fd30b7 100644 --- a/test/e2e/scan/scan_remote_test.go +++ b/test/e2e/scan/scan_remote_test.go @@ -180,13 +180,7 @@ var _ = Describe("Scan Command using remote types", func() { }) When("terraform registry remote url has a version", func() { - oldRemoteURL := remoteURL - JustBeforeEach(func() { - remoteURL = "terraform-aws-modules/vpc/aws:2.22.0" - }) - JustAfterEach(func() { - remoteURL = oldRemoteURL - }) + remoteURL = "terraform-aws-modules/vpc/aws:2.22.0" It("should download the remote registry and generate scan results", func() { scanArgs := []string{scanUtils.ScanCommand, "-r", "terraform-registry", "--remote-url", remoteURL} session = helper.RunCommand(terrascanBinaryPath, outWriter, errWriter, scanArgs...) @@ -195,14 +189,30 @@ var _ = Describe("Scan Command using remote types", func() { }) }) - When("terraform registry remote url has a invalid version", func() { - oldRemoteURL := remoteURL - JustBeforeEach(func() { - remoteURL = "terraform-aws-modules/vpc/aws:blah" + Context("remote modules has reference to its local modules", func() { + When("remote type is terraform registry and remote url has a subdirectory", func() { + remoteURL := "terraform-aws-modules/security-group/aws//modules/http-80" + It("should download the remote registry and generate scan results", func() { + scanArgs := []string{scanUtils.ScanCommand, "-r", "terraform-registry", "--remote-url", remoteURL} + session = helper.RunCommand(terrascanBinaryPath, outWriter, errWriter, scanArgs...) + // has a OR condition because we don't know if there would be violations or not + Eventually(session, scanUtils.RemoteScanTimeout).Should(Or(gexec.Exit(helper.ExitCodeThree), gexec.Exit(helper.ExitCodeZero))) + }) }) - JustAfterEach(func() { - remoteURL = oldRemoteURL + + When("remote type is git and remote url has a subdirectory", func() { + remoteURL := "github.com/terraform-aws-modules/terraform-aws-security-group//modules/http-80" + It("should download the remote registry and generate scan results", func() { + scanArgs := []string{scanUtils.ScanCommand, "-r", "git", "--remote-url", remoteURL} + session = helper.RunCommand(terrascanBinaryPath, outWriter, errWriter, scanArgs...) + // has a OR condition because we don't know if there would be violations or not + Eventually(session, scanUtils.RemoteScanTimeout).Should(Or(gexec.Exit(helper.ExitCodeThree), gexec.Exit(helper.ExitCodeZero))) + }) }) + }) + + When("terraform registry remote url has a invalid version", func() { + remoteURL := "terraform-aws-modules/vpc/aws:blah" It("should error out and exit with status code 1", func() { scanArgs := []string{scanUtils.ScanCommand, "-r", "terraform-registry", "--remote-url", remoteURL} session = helper.RunCommand(terrascanBinaryPath, outWriter, errWriter, scanArgs...)