Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix - terrascan not able to read modules within a subdirectory #641

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 12 additions & 32 deletions pkg/iac-providers/terraform/commons/load-dir.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import (
"fmt"
"os"
"path/filepath"
"strings"

"github.com/accurics/terrascan/pkg/downloader"
"github.com/accurics/terrascan/pkg/iac-providers/output"
Expand Down Expand Up @@ -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())

Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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)
Expand All @@ -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
}
38 changes: 10 additions & 28 deletions pkg/iac-providers/terraform/commons/load-dir_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,38 +18,31 @@ package commons

import (
"os"
"path/filepath"
"testing"

"github.com/accurics/terrascan/pkg/downloader"
"github.com/hashicorp/hcl/v2"
hclConfigs "github.com/hashicorp/terraform/configs"
)

// test data
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
Expand All @@ -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)
}
})
Expand Down Expand Up @@ -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
Expand Down
36 changes: 23 additions & 13 deletions test/e2e/scan/scan_remote_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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...)
Expand All @@ -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...)
Expand Down