Skip to content

Commit fbc42a0

Browse files
authoredNov 25, 2024··
fix(misconf): load full Terraform module (#7925)
Signed-off-by: nikpivkin <nikita.pivkin@smartforce.io>
1 parent fe3a897 commit fbc42a0

File tree

8 files changed

+125
-53
lines changed

8 files changed

+125
-53
lines changed
 
Original file line numberDiff line numberDiff line change
@@ -1,119 +1,172 @@
1+
//go:build unix
2+
13
package resolvers_test
24

35
import (
46
"context"
7+
"crypto/tls"
58
"io/fs"
9+
"net/http"
10+
"net/http/httptest"
11+
"path"
12+
"strings"
613
"testing"
714

815
"github.com/stretchr/testify/assert"
916
"github.com/stretchr/testify/require"
1017

18+
"github.com/aquasecurity/trivy/internal/gittest"
1119
"github.com/aquasecurity/trivy/pkg/iac/scanners/terraform/parser/resolvers"
20+
"github.com/aquasecurity/trivy/pkg/log"
1221
)
1322

1423
type moduleResolver interface {
1524
Resolve(context.Context, fs.FS, resolvers.Options) (fs.FS, string, string, bool, error)
1625
}
1726

18-
func TestResolveModuleFromCache(t *testing.T) {
19-
if testing.Short() {
20-
t.Skip("skipping integration test in short mode")
27+
func testOptions(t *testing.T, source string) resolvers.Options {
28+
return resolvers.Options{
29+
Source: source,
30+
OriginalSource: source,
31+
Version: "",
32+
OriginalVersion: "",
33+
AllowDownloads: true,
34+
CacheDir: t.TempDir(),
35+
Logger: log.WithPrefix("test"),
2136
}
37+
}
38+
39+
func newRegistry(repoURL string) *httptest.Server {
40+
mux := http.NewServeMux()
41+
mux.HandleFunc("/v1/modules/terraform-aws-modules/s3-bucket/aws/download", func(w http.ResponseWriter, r *http.Request) {
42+
w.Header().Set("X-Terraform-Get", repoURL)
43+
w.WriteHeader(http.StatusNoContent)
44+
})
45+
46+
return httptest.NewTLSServer(mux)
47+
}
48+
49+
func buildGitSource(repoURL string) string { return "git::" + repoURL }
50+
51+
func TestResolveModuleFromCache(t *testing.T) {
52+
53+
repo := "terraform-aws-s3-bucket"
54+
gs := gittest.NewServer(t, repo, "testdata/terraform-aws-s3-bucket")
55+
defer gs.Close()
56+
57+
repoURL := gs.URL + "/" + repo + ".git"
58+
59+
registry := newRegistry(buildGitSource(repoURL))
60+
defer registry.Close()
61+
62+
registryAddress := strings.TrimPrefix(registry.URL, "https://")
2263

2364
tests := []struct {
2465
name string
2566
opts resolvers.Options
2667
firstResolver moduleResolver
2768
expectedSubdir string
69+
expectedString string
2870
}{
2971
{
3072
name: "registry",
3173
opts: resolvers.Options{
32-
Name: "bucket",
33-
Source: "terraform-aws-modules/s3-bucket/aws",
34-
Version: "4.1.2",
74+
Source: registryAddress + "/terraform-aws-modules/s3-bucket/aws",
75+
Client: &http.Client{
76+
Transport: &http.Transport{
77+
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
78+
},
79+
},
3580
},
3681
firstResolver: resolvers.Registry,
3782
expectedSubdir: ".",
83+
expectedString: "# AWS S3 bucket Terraform module",
3884
},
3985
{
4086
name: "registry with subdir",
4187
opts: resolvers.Options{
42-
Name: "object",
43-
Source: "terraform-aws-modules/s3-bucket/aws//modules/object",
44-
Version: "4.1.2",
88+
Source: registryAddress + "/terraform-aws-modules/s3-bucket/aws//modules/object",
89+
Client: &http.Client{
90+
Transport: &http.Transport{
91+
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
92+
},
93+
},
4594
},
4695
firstResolver: resolvers.Registry,
4796
expectedSubdir: "modules/object",
97+
expectedString: "# S3 bucket object",
4898
},
4999
{
50100
name: "remote",
51101
opts: resolvers.Options{
52-
Name: "bucket",
53-
Source: "git::https://github.com/terraform-aws-modules/terraform-aws-s3-bucket.git?ref=v4.1.2",
102+
Source: buildGitSource(repoURL),
54103
},
55104
firstResolver: resolvers.Remote,
56105
expectedSubdir: ".",
106+
expectedString: "# AWS S3 bucket Terraform module",
57107
},
58108
{
59109
name: "remote with subdir",
60110
opts: resolvers.Options{
61-
Name: "object",
62-
Source: "git::https://github.com/terraform-aws-modules/terraform-aws-s3-bucket.git//modules/object?ref=v4.1.2",
111+
Source: buildGitSource(repoURL) + "//modules/object",
63112
},
64113
firstResolver: resolvers.Remote,
65114
expectedSubdir: "modules/object",
115+
expectedString: "# S3 bucket object",
66116
},
67117
}
68118

69119
for _, tt := range tests {
70120
t.Run(tt.name, func(t *testing.T) {
71121

72-
tt.opts.AllowDownloads = true
73122
tt.opts.OriginalSource = tt.opts.Source
74-
tt.opts.OriginalVersion = tt.opts.Version
123+
tt.opts.AllowDownloads = true
75124
tt.opts.CacheDir = t.TempDir()
125+
tt.opts.Logger = log.WithPrefix("test")
126+
127+
fsys, _, dir, _, err := tt.firstResolver.Resolve(context.Background(), nil, tt.opts)
128+
require.NoError(t, err)
129+
assert.Equal(t, tt.expectedSubdir, dir)
76130

77-
fsys, _, _, applies, err := tt.firstResolver.Resolve(context.Background(), nil, tt.opts)
131+
b, err := fs.ReadFile(fsys, path.Join(dir, "README.md"))
78132
require.NoError(t, err)
79-
assert.True(t, applies)
133+
assert.Equal(t, tt.expectedString, string(b))
80134

81-
_, err = fs.Stat(fsys, "main.tf")
135+
_, _, dir, _, err = resolvers.Cache.Resolve(context.Background(), fsys, tt.opts)
82136
require.NoError(t, err)
137+
assert.Equal(t, tt.expectedSubdir, dir)
83138

84-
_, _, _, applies, err = resolvers.Cache.Resolve(context.Background(), fsys, tt.opts)
139+
b, err = fs.ReadFile(fsys, path.Join(dir, "README.md"))
85140
require.NoError(t, err)
86-
assert.True(t, applies)
141+
assert.Equal(t, tt.expectedString, string(b))
87142
})
88143
}
89144
}
90145

91146
func TestResolveModuleFromCacheWithDifferentSubdir(t *testing.T) {
92-
if testing.Short() {
93-
t.Skip("skipping integration test in short mode")
94-
}
147+
repo := "terraform-aws-s3-bucket"
148+
gs := gittest.NewServer(t, repo, "testdata/terraform-aws-s3-bucket")
149+
defer gs.Close()
95150

96-
cacheDir := t.TempDir()
151+
repoURL := gs.URL + "/" + repo + ".git"
97152

98-
fsys, _, _, applies, err := resolvers.Remote.Resolve(context.Background(), nil, resolvers.Options{
99-
Name: "object",
100-
Source: "git::https://github.com/terraform-aws-modules/terraform-aws-s3-bucket.git//modules/object?ref=v4.1.2",
101-
OriginalSource: "git::https://github.com/terraform-aws-modules/terraform-aws-s3-bucket.git//modules/object?ref=v4.1.2",
102-
AllowDownloads: true,
103-
CacheDir: cacheDir,
104-
})
153+
fsys, _, dir, _, err := resolvers.Remote.Resolve(
154+
context.Background(), nil,
155+
testOptions(t, "git::"+repoURL+"//modules/object"),
156+
)
105157
require.NoError(t, err)
106-
assert.True(t, applies)
107158

108-
_, err = fs.Stat(fsys, "main.tf")
159+
b, err := fs.ReadFile(fsys, path.Join(dir, "README.md"))
109160
require.NoError(t, err)
161+
assert.Equal(t, "# S3 bucket object", string(b))
110162

111-
_, _, _, applies, err = resolvers.Cache.Resolve(context.Background(), nil, resolvers.Options{
112-
Name: "notification",
113-
Source: "git::https://github.com/terraform-aws-modules/terraform-aws-s3-bucket.git//modules/notification?ref=v4.1.2",
114-
OriginalSource: "git::https://github.com/terraform-aws-modules/terraform-aws-s3-bucket.git//modules/notification?ref=v4.1.2",
115-
CacheDir: cacheDir,
116-
})
163+
fsys, _, dir, _, err = resolvers.Remote.Resolve(
164+
context.Background(), nil,
165+
testOptions(t, "git::"+repoURL+"//modules/notification"),
166+
)
167+
require.NoError(t, err)
168+
169+
b, err = fs.ReadFile(fsys, path.Join(dir, "README.md"))
117170
require.NoError(t, err)
118-
assert.True(t, applies)
171+
assert.Equal(t, "# S3 bucket notification", string(b))
119172
}

‎pkg/iac/scanners/terraform/parser/resolvers/options.go

+2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package resolvers
22

33
import (
4+
"net/http"
45
"strings"
56

67
"github.com/aquasecurity/trivy/pkg/log"
@@ -13,6 +14,7 @@ type Options struct {
1314
SkipCache bool
1415
RelativePath string
1516
CacheDir string
17+
Client *http.Client
1618
}
1719

1820
func (o *Options) hasPrefix(prefixes ...string) bool {

‎pkg/iac/scanners/terraform/parser/resolvers/registry.go

+8-3
Original file line numberDiff line numberDiff line change
@@ -41,12 +41,17 @@ const registryHostname = "registry.terraform.io"
4141
// nolint
4242
func (r *registryResolver) Resolve(ctx context.Context, target fs.FS, opt Options) (filesystem fs.FS, prefix string, downloadPath string, applies bool, err error) {
4343

44+
client := r.client
45+
if opt.Client != nil {
46+
client = opt.Client
47+
}
48+
4449
if !opt.AllowDownloads {
4550
return
4651
}
4752

4853
inputVersion := opt.Version
49-
source, _ := splitPackageSubdirRaw(opt.Source)
54+
source, _ := splitPackageSubdirRaw(opt.OriginalSource)
5055
parts := strings.Split(source, "/")
5156
if len(parts) < 3 || len(parts) > 4 {
5257
return
@@ -81,7 +86,7 @@ func (r *registryResolver) Resolve(ctx context.Context, target fs.FS, opt Option
8186
if token != "" {
8287
req.Header.Set("Authorization", "Bearer "+token)
8388
}
84-
resp, err := r.client.Do(req)
89+
resp, err := client.Do(req)
8590
if err != nil {
8691
return nil, "", "", true, err
8792
}
@@ -122,7 +127,7 @@ func (r *registryResolver) Resolve(ctx context.Context, target fs.FS, opt Option
122127
req.Header.Set("X-Terraform-Version", opt.Version)
123128
}
124129

125-
resp, err := r.client.Do(req)
130+
resp, err := client.Do(req)
126131
if err != nil {
127132
return nil, "", "", true, err
128133
}

‎pkg/iac/scanners/terraform/parser/resolvers/registry_integration_test.go

+10-6
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"github.com/stretchr/testify/require"
1010

1111
"github.com/aquasecurity/trivy/pkg/iac/scanners/terraform/parser/resolvers"
12+
"github.com/aquasecurity/trivy/pkg/log"
1213
)
1314

1415
func TestResolveModuleFromOpenTofuRegistry(t *testing.T) {
@@ -17,12 +18,15 @@ func TestResolveModuleFromOpenTofuRegistry(t *testing.T) {
1718
}
1819

1920
fsys, _, path, _, err := resolvers.Registry.Resolve(context.Background(), nil, resolvers.Options{
20-
Source: "registry.opentofu.org/terraform-aws-modules/s3-bucket/aws",
21-
RelativePath: "test",
22-
Name: "bucket",
23-
Version: "4.1.2",
24-
AllowDownloads: true,
25-
SkipCache: true,
21+
Source: "registry.opentofu.org/terraform-aws-modules/s3-bucket/aws",
22+
OriginalSource: "registry.opentofu.org/terraform-aws-modules/s3-bucket/aws",
23+
RelativePath: "test",
24+
Name: "bucket",
25+
Version: "4.1.2",
26+
OriginalVersion: "4.1.2",
27+
AllowDownloads: true,
28+
SkipCache: true,
29+
Logger: log.WithPrefix("test"),
2630
})
2731
require.NoError(t, err)
2832

‎pkg/iac/scanners/terraform/parser/resolvers/remote.go

+9-4
Original file line numberDiff line numberDiff line change
@@ -40,25 +40,30 @@ func (r *remoteResolver) Resolve(ctx context.Context, _ fs.FS, opt Options) (fil
4040
return nil, "", "", false, nil
4141
}
4242

43-
src, subdir := splitPackageSubdirRaw(opt.OriginalSource)
44-
key := cacheKey(src, opt.OriginalVersion)
43+
origSrc, subdir := splitPackageSubdirRaw(opt.OriginalSource)
44+
key := cacheKey(origSrc, opt.OriginalVersion)
4545
opt.Logger.Debug("Caching module", log.String("key", key))
4646

4747
baseCacheDir, err := locateCacheDir(opt.CacheDir)
4848
if err != nil {
4949
return nil, "", "", true, fmt.Errorf("failed to locate cache directory: %w", err)
5050
}
51+
5152
cacheDir := filepath.Join(baseCacheDir, key)
53+
54+
src, _ := splitPackageSubdirRaw(opt.Source)
55+
56+
opt.Source = src
5257
if err := r.download(ctx, opt, cacheDir); err != nil {
5358
return nil, "", "", true, err
5459
}
5560

5661
r.incrementCount(opt)
5762
opt.Logger.Debug("Successfully resolve module via remote download",
5863
log.String("name", opt.Name),
59-
log.String("source", opt.Source),
64+
log.String("source", opt.OriginalSource),
6065
)
61-
return os.DirFS(cacheDir), opt.Source, subdir, true, nil
66+
return os.DirFS(cacheDir), opt.OriginalSource, subdir, true, nil
6267
}
6368

6469
func (r *remoteResolver) download(ctx context.Context, opt Options, dst string) error {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# AWS S3 bucket Terraform module
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# S3 bucket notification
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# S3 bucket object

0 commit comments

Comments
 (0)
Please sign in to comment.