Skip to content

Commit

Permalink
feat(misconf): resolve tf module from OpenTofu compatible registry
Browse files Browse the repository at this point in the history
  • Loading branch information
nikpivkin committed May 22, 2024
1 parent 48bdc6e commit 7d9f9b7
Show file tree
Hide file tree
Showing 3 changed files with 57 additions and 9 deletions.
14 changes: 7 additions & 7 deletions pkg/iac/scanners/terraform/parser/module_retrieval.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,21 @@ import (
"fmt"
"io/fs"

resolvers2 "github.com/aquasecurity/trivy/pkg/iac/scanners/terraform/parser/resolvers"
"github.com/aquasecurity/trivy/pkg/iac/scanners/terraform/parser/resolvers"
)

type ModuleResolver interface {
Resolve(context.Context, fs.FS, resolvers2.Options) (filesystem fs.FS, prefix string, downloadPath string, applies bool, err error)
Resolve(context.Context, fs.FS, resolvers.Options) (filesystem fs.FS, prefix string, downloadPath string, applies bool, err error)
}

var defaultResolvers = []ModuleResolver{
resolvers2.Cache,
resolvers2.Local,
resolvers2.Remote,
resolvers2.Registry,
resolvers.Cache,
resolvers.Local,
resolvers.Remote,
resolvers.Registry,
}

func resolveModule(ctx context.Context, current fs.FS, opt resolvers2.Options) (filesystem fs.FS, sourcePrefix, downloadPath string, err error) {
func resolveModule(ctx context.Context, current fs.FS, opt resolvers.Options) (filesystem fs.FS, sourcePrefix, downloadPath string, err error) {
opt.Debug("Resolving module '%s' with source: '%s'...", opt.Name, opt.Source)
for _, resolver := range defaultResolvers {
if filesystem, prefix, path, applies, err := resolver.Resolve(ctx, current, opt); err != nil {
Expand Down
22 changes: 20 additions & 2 deletions pkg/iac/scanners/terraform/parser/resolvers/registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,11 +122,29 @@ func (r *registryResolver) Resolve(ctx context.Context, target fs.FS, opt Option
return nil, "", "", true, err
}
defer func() { _ = resp.Body.Close() }()
if resp.StatusCode != http.StatusNoContent {

// OpenTofu may return 200 with body
switch resp.StatusCode {
case http.StatusOK:
// https://opentofu.org/docs/internals/module-registry-protocol/#sample-response-1
var downloadResponse struct {
Location string `json:"location"`
}
if err := json.NewDecoder(resp.Body).Decode(&downloadResponse); err != nil {
return nil, "", "", true, fmt.Errorf("failed to decode download response: %w", err)
}

opt.Source = downloadResponse.Location
case http.StatusNoContent:
opt.Source = resp.Header.Get("X-Terraform-Get")
default:
return nil, "", "", true, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
}

opt.Source = resp.Header.Get("X-Terraform-Get")
if opt.Source == "" {
return nil, "", "", true, fmt.Errorf("no source was found for the registry at %s", hostname)
}

opt.Debug("Module '%s' resolved via registry to new source: '%s'", opt.Name, opt.Source)
opt.RelativePath = relativePath
filesystem, prefix, downloadPath, _, err = Remote.Resolve(ctx, target, opt)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package resolvers_test

import (
"context"
"io/fs"
"path/filepath"
"testing"

"github.com/aquasecurity/trivy/pkg/iac/scanners/terraform/parser/resolvers"
"github.com/stretchr/testify/require"
)

func TestResolveModuleFromOpenTofuRegistry(t *testing.T) {
if testing.Short() {
t.Skip("skipping integration test in short mode")
}

fsys, _, path, _, err := resolvers.Registry.Resolve(context.Background(), nil, resolvers.Options{
Source: "registry.opentofu.org/terraform-aws-modules/s3-bucket/aws",
RelativePath: "test",
Name: "bucket",
Version: "4.1.2",
AllowDownloads: true,
SkipCache: true,
})
require.NoError(t, err)

_, err = fs.Stat(fsys, filepath.Join(path, "main.tf"))
require.NoError(t, err)
}

0 comments on commit 7d9f9b7

Please sign in to comment.