From 7a25dadb44a57a1099227cde44e1732f25409cea Mon Sep 17 00:00:00 2001 From: Nikita Pivkin Date: Sat, 4 May 2024 10:24:39 +0600 Subject: [PATCH] fix(misconf): load cached tf modules (#6607) --- .../config/terraform/terraform_test.go | 5 ++++ pkg/iac/detection/detect.go | 4 +++ .../terraform/parser/load_module_metadata.go | 8 +++--- pkg/iac/scanners/terraform/parser/modules.go | 4 +++ .../scanners/terraform/parser/parser_test.go | 25 +++++++++++++++++++ .../.terraform/modules/modules.json | 1 + .../.terraform/modules/s3-bucket/main.tf | 7 ++++++ .../parser/testdata/cached-modules/main.tf | 5 ++++ 8 files changed, 56 insertions(+), 3 deletions(-) create mode 100644 pkg/iac/scanners/terraform/parser/testdata/cached-modules/.terraform/modules/modules.json create mode 100644 pkg/iac/scanners/terraform/parser/testdata/cached-modules/.terraform/modules/s3-bucket/main.tf create mode 100644 pkg/iac/scanners/terraform/parser/testdata/cached-modules/main.tf diff --git a/pkg/fanal/analyzer/config/terraform/terraform_test.go b/pkg/fanal/analyzer/config/terraform/terraform_test.go index 9096ee062a..988a857777 100644 --- a/pkg/fanal/analyzer/config/terraform/terraform_test.go +++ b/pkg/fanal/analyzer/config/terraform/terraform_test.go @@ -42,6 +42,11 @@ func TestConfigAnalyzer_Required(t *testing.T) { filePath: "deployment.yaml", want: false, }, + { + name: "manifest snapshot file", + filePath: ".terraform/modules/modules.json", + want: true, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/pkg/iac/detection/detect.go b/pkg/iac/detection/detect.go index 6d1e627d11..87c16582ca 100644 --- a/pkg/iac/detection/detect.go +++ b/pkg/iac/detection/detect.go @@ -244,6 +244,10 @@ func init() { } func IsTerraformFile(path string) bool { + if strings.HasSuffix(path, filepath.ToSlash(".terraform/modules/modules.json")) { + return true + } + for _, ext := range []string{".tf", ".tf.json", ".tfvars"} { if strings.HasSuffix(path, ext) { return true diff --git a/pkg/iac/scanners/terraform/parser/load_module_metadata.go b/pkg/iac/scanners/terraform/parser/load_module_metadata.go index 7b316f8d66..9648e6475f 100644 --- a/pkg/iac/scanners/terraform/parser/load_module_metadata.go +++ b/pkg/iac/scanners/terraform/parser/load_module_metadata.go @@ -3,9 +3,11 @@ package parser import ( "encoding/json" "io/fs" - "path/filepath" + "path" ) +const manifestSnapshotFile = ".terraform/modules/modules.json" + type modulesMetadata struct { Modules []struct { Key string `json:"Key"` @@ -16,13 +18,13 @@ type modulesMetadata struct { } func loadModuleMetadata(target fs.FS, fullPath string) (*modulesMetadata, string, error) { - metadataPath := filepath.Join(fullPath, ".terraform/modules/modules.json") // nolint: gocritic + metadataPath := path.Join(fullPath, manifestSnapshotFile) f, err := target.Open(metadataPath) if err != nil { return nil, metadataPath, err } - defer func() { _ = f.Close() }() + defer f.Close() var metadata modulesMetadata if err := json.NewDecoder(f).Decode(&metadata); err != nil { diff --git a/pkg/iac/scanners/terraform/parser/modules.go b/pkg/iac/scanners/terraform/parser/modules.go index 499fc2fb96..415f52a117 100644 --- a/pkg/iac/scanners/terraform/parser/modules.go +++ b/pkg/iac/scanners/terraform/parser/modules.go @@ -16,6 +16,10 @@ import ( // It builds a graph based on the module dependencies and determines the modules that have no incoming dependencies, // considering them as root modules. func (p *Parser) FindRootModules(ctx context.Context, dirs []string) ([]string, error) { + // skip cached terraform modules as they cannot be root modules + dirs = lo.Filter(dirs, func(dir string, _ int) bool { + return !strings.Contains(dir, ".terraform/modules/") + }) for _, dir := range dirs { if err := p.ParseFS(ctx, dir); err != nil { return nil, err diff --git a/pkg/iac/scanners/terraform/parser/parser_test.go b/pkg/iac/scanners/terraform/parser/parser_test.go index 554a8b0fb3..48181eee63 100644 --- a/pkg/iac/scanners/terraform/parser/parser_test.go +++ b/pkg/iac/scanners/terraform/parser/parser_test.go @@ -3,6 +3,7 @@ package parser import ( "context" "os" + "path/filepath" "sort" "testing" @@ -1699,3 +1700,27 @@ resource "test" "values" { "a": cty.NumberIntVal(1), "b": cty.NumberIntVal(2), }))) } + +func Test_LoadLocalCachedModule(t *testing.T) { + fsys := os.DirFS(filepath.Join("testdata", "cached-modules")) + + parser := New( + fsys, "", + OptionStopOnHCLError(true), + OptionWithDownloads(false), + ) + require.NoError(t, parser.ParseFS(context.TODO(), ".")) + + modules, _, err := parser.EvaluateAll(context.TODO()) + require.NoError(t, err) + + assert.Len(t, modules, 2) + + buckets := modules.GetResourcesByType("aws_s3_bucket") + assert.Len(t, buckets, 1) + + assert.Equal(t, "my-private-module/s3-bucket/aws/.terraform/modules/s3-bucket/main.tf", buckets[0].GetMetadata().Range().GetFilename()) + + bucketName := buckets[0].GetAttribute("bucket").Value().AsString() + assert.Equal(t, "my-s3-bucket", bucketName) +} diff --git a/pkg/iac/scanners/terraform/parser/testdata/cached-modules/.terraform/modules/modules.json b/pkg/iac/scanners/terraform/parser/testdata/cached-modules/.terraform/modules/modules.json new file mode 100644 index 0000000000..76be610c13 --- /dev/null +++ b/pkg/iac/scanners/terraform/parser/testdata/cached-modules/.terraform/modules/modules.json @@ -0,0 +1 @@ +{"Modules":[{"Key":"","Source":"","Dir":"."},{"Key":"s3-bucket","Source":"registry.myregistry.org/my-private-module/s3-bucket/aws","Version":"1.0.0","Dir":".terraform/modules/s3-bucket"}]} \ No newline at end of file diff --git a/pkg/iac/scanners/terraform/parser/testdata/cached-modules/.terraform/modules/s3-bucket/main.tf b/pkg/iac/scanners/terraform/parser/testdata/cached-modules/.terraform/modules/s3-bucket/main.tf new file mode 100644 index 0000000000..68506b9930 --- /dev/null +++ b/pkg/iac/scanners/terraform/parser/testdata/cached-modules/.terraform/modules/s3-bucket/main.tf @@ -0,0 +1,7 @@ +variable "bucket" { + type = string +} + +resource "aws_s3_bucket" "this" { + bucket = var.bucket +} \ No newline at end of file diff --git a/pkg/iac/scanners/terraform/parser/testdata/cached-modules/main.tf b/pkg/iac/scanners/terraform/parser/testdata/cached-modules/main.tf new file mode 100644 index 0000000000..231058b26e --- /dev/null +++ b/pkg/iac/scanners/terraform/parser/testdata/cached-modules/main.tf @@ -0,0 +1,5 @@ +module "s3-bucket" { + source = "my-private-module/s3-bucket/aws" + version = "1.0.0" + bucket = "my-s3-bucket" +} \ No newline at end of file