Skip to content

Commit 298a994

Browse files
authored
feat(terraform): use .terraform cache for remote modules in plan scanning (aquasecurity#9277)
Signed-off-by: nikpivkin <nikita.pivkin@smartforce.io>
1 parent c9cb3d1 commit 298a994

File tree

5 files changed

+73
-40
lines changed

5 files changed

+73
-40
lines changed

pkg/iac/scanners/terraform/parser/evaluator.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ type evaluator struct {
3131
ctx *tfcontext.Context
3232
blocks terraform.Blocks
3333
inputVars map[string]cty.Value
34-
moduleMetadata *modulesMetadata
34+
moduleMetadata *ModulesMetadata
3535
projectRootPath string // root of the current scan
3636
modulePath string
3737
moduleName string
@@ -50,7 +50,7 @@ func newEvaluator(
5050
moduleName string,
5151
blocks terraform.Blocks,
5252
inputVars map[string]cty.Value,
53-
moduleMetadata *modulesMetadata,
53+
moduleMetadata *ModulesMetadata,
5454
workspace string,
5555
ignores ignore.Rules,
5656
logger *log.Logger,

pkg/iac/scanners/terraform/parser/load_module_metadata.go

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,27 +6,29 @@ import (
66
"path"
77
)
88

9-
const manifestSnapshotFile = ".terraform/modules/modules.json"
10-
11-
type modulesMetadata struct {
12-
Modules []struct {
13-
Key string `json:"Key"`
14-
Source string `json:"Source"`
15-
Version string `json:"Version"`
16-
Dir string `json:"Dir"`
17-
} `json:"Modules"`
9+
const ManifestSnapshotFile = ".terraform/modules/modules.json"
10+
11+
type ModulesMetadata struct {
12+
Modules []ModuleMetadata `json:"Modules"`
13+
}
14+
15+
type ModuleMetadata struct {
16+
Key string `json:"Key"`
17+
Source string `json:"Source"`
18+
Version string `json:"Version"`
19+
Dir string `json:"Dir"`
1820
}
1921

20-
func loadModuleMetadata(target fs.FS, fullPath string) (*modulesMetadata, string, error) {
21-
metadataPath := path.Join(fullPath, manifestSnapshotFile)
22+
func loadModuleMetadata(target fs.FS, fullPath string) (*ModulesMetadata, string, error) {
23+
metadataPath := path.Join(fullPath, ManifestSnapshotFile)
2224

2325
f, err := target.Open(metadataPath)
2426
if err != nil {
2527
return nil, metadataPath, err
2628
}
2729
defer f.Close()
2830

29-
var metadata modulesMetadata
31+
var metadata ModulesMetadata
3032
if err := json.NewDecoder(f).Decode(&metadata); err != nil {
3133
return nil, metadataPath, err
3234
}

pkg/iac/scanners/terraformplan/snapshot/scanner.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ func (s *Scanner) scan(ctx context.Context, reader io.Reader) (scan.Results, err
7878

7979
s.inner.AddParserOptions(
8080
tfparser.OptionsWithTfVars(snap.inputVariables),
81+
tfparser.OptionWithDownloads(false),
8182
)
8283
return s.inner.ScanFS(ctx, fsys, ".")
8384
}

pkg/iac/scanners/terraformplan/snapshot/snapshot.go

Lines changed: 26 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ import (
1515
"github.com/liamg/memoryfs"
1616
"github.com/zclconf/go-cty/cty"
1717

18+
"github.com/aquasecurity/trivy/pkg/iac/scanners/terraform/parser"
19+
"github.com/aquasecurity/trivy/pkg/log"
1820
iox "github.com/aquasecurity/trivy/pkg/x/io"
1921
)
2022

@@ -27,13 +29,7 @@ const (
2729
)
2830

2931
type (
30-
configSnapshotModuleRecord struct {
31-
Key string `json:"Key"`
32-
SourceAddr string `json:"Source,omitempty"`
33-
Dir string `json:"Dir"`
34-
}
35-
36-
configSnapshotModuleManifest []configSnapshotModuleRecord
32+
configSnapshotModuleManifest []parser.ModuleMetadata
3733
)
3834

3935
var errNoTerraformPlan = errors.New("no terraform plan file")
@@ -79,13 +75,11 @@ func parseSnapshot(r io.Reader) (*snapshot, error) {
7975
inputVariables: make(map[string]cty.Value),
8076
}
8177

82-
var moduleManifest configSnapshotModuleManifest
83-
8478
for _, file := range zr.File {
8579
switch {
8680
case file.Name == configSnapshotManifestFile:
8781
var err error
88-
moduleManifest, err = readModuleManifest(file)
82+
snap.moduleManifest, err = readModuleManifest(file)
8983
if err != nil {
9084
return nil, err
9185
}
@@ -113,12 +107,7 @@ func parseSnapshot(r io.Reader) (*snapshot, error) {
113107
}
114108
}
115109

116-
for _, record := range moduleManifest {
117-
// skip non-local modules
118-
if record.Dir != "." && !strings.HasPrefix(record.SourceAddr, ".") {
119-
delete(snap.modules, record.Key)
120-
continue
121-
}
110+
for _, record := range snap.moduleManifest {
122111
modSnap := snap.getOrCreateModuleSnapshot(record.Key)
123112
modSnap.dir = record.Dir
124113
}
@@ -159,6 +148,7 @@ type (
159148
}
160149

161150
snapshot struct {
151+
moduleManifest configSnapshotModuleManifest
162152
modules map[string]*snapshotModule
163153
inputVariables map[string]cty.Value
164154
}
@@ -202,6 +192,10 @@ func (s *snapshot) getOrCreateModuleSnapshot(key string) *snapshotModule {
202192
func (s *snapshot) toFS() (fs.FS, error) {
203193
fsys := memoryfs.New()
204194

195+
if err := s.writeManifest(fsys); err != nil {
196+
log.WithPrefix(log.PrefixMisconfiguration).Error("Failed to write manifest file", log.Err(err))
197+
}
198+
205199
for _, module := range s.modules {
206200
if err := fsys.MkdirAll(module.dir, fs.ModePerm); err != nil && !errors.Is(err, os.ErrExist) {
207201
return nil, err
@@ -218,3 +212,19 @@ func (s *snapshot) toFS() (fs.FS, error) {
218212
}
219213
return fsys, nil
220214
}
215+
216+
func (s *snapshot) writeManifest(fsys *memoryfs.FS) error {
217+
if err := fsys.MkdirAll(path.Dir(parser.ManifestSnapshotFile), fs.ModePerm); err != nil {
218+
return fmt.Errorf("create manifest directory: %w", err)
219+
}
220+
221+
b, err := json.Marshal(parser.ModulesMetadata{Modules: s.moduleManifest})
222+
if err != nil {
223+
return fmt.Errorf("marshal manifest snapshot: %w", err)
224+
}
225+
226+
if err := fsys.WriteFile(parser.ManifestSnapshotFile, b, fs.ModePerm); err != nil {
227+
return fmt.Errorf("write manifest snapshot: %w", err)
228+
}
229+
return nil
230+
}

pkg/iac/scanners/terraformplan/snapshot/snapshot_test.go

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import (
66
"io/fs"
77
"os"
88
"path/filepath"
9-
"sort"
109
"testing"
1110

1211
"github.com/stretchr/testify/assert"
@@ -21,23 +20,46 @@ func TestReadSnapshot(t *testing.T) {
2120
expectedFiles []string
2221
}{
2322
{
24-
name: "just resource",
25-
dir: "just-resource",
26-
expectedFiles: []string{"main.tf", "terraform.tf"},
23+
name: "just resource",
24+
dir: "just-resource",
25+
expectedFiles: []string{
26+
"main.tf",
27+
"terraform.tf",
28+
".terraform/modules/modules.json",
29+
},
2730
},
2831
{
29-
name: "with local module",
30-
dir: "with-local-module",
31-
expectedFiles: []string{"main.tf", "modules/ec2/main.tf", "terraform.tf"},
32+
name: "with local module",
33+
dir: "with-local-module",
34+
expectedFiles: []string{
35+
"main.tf",
36+
"terraform.tf",
37+
"modules/ec2/main.tf",
38+
".terraform/modules/modules.json",
39+
},
3240
},
3341
{
3442
name: "with nested modules",
3543
dir: "nested-modules",
3644
expectedFiles: []string{
3745
"main.tf",
46+
"terraform.tf",
3847
"modules/s3/main.tf",
3948
"modules/s3/modules/logging/main.tf",
49+
".terraform/modules/modules.json",
50+
},
51+
},
52+
{
53+
name: "with remote module",
54+
dir: "with-remote-module",
55+
expectedFiles: []string{
56+
"main.tf",
4057
"terraform.tf",
58+
".terraform/modules/modules.json",
59+
".terraform/modules/s3_bucket/main.tf",
60+
".terraform/modules/s3_bucket/outputs.tf",
61+
".terraform/modules/s3_bucket/variables.tf",
62+
".terraform/modules/s3_bucket/versions.tf",
4163
},
4264
},
4365
}
@@ -58,7 +80,7 @@ func TestReadSnapshot(t *testing.T) {
5880
files, err := getAllfiles(fsys)
5981
require.NoError(t, err)
6082

61-
assert.Equal(t, tt.expectedFiles, files)
83+
assert.ElementsMatch(t, tt.expectedFiles, files)
6284
})
6385
}
6486
}
@@ -80,8 +102,6 @@ func getAllfiles(fsys fs.FS) ([]string, error) {
80102
if err := fs.WalkDir(fsys, ".", walkFn); err != nil {
81103
return nil, err
82104
}
83-
84-
sort.Strings(files)
85105
return files, nil
86106
}
87107

0 commit comments

Comments
 (0)