Skip to content
Draft
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
349 changes: 248 additions & 101 deletions pkg/iac/scanners/terraformplan/tfjson/parser/parser.go

Large diffs are not rendered by default.

75 changes: 75 additions & 0 deletions pkg/iac/scanners/terraformplan/tfjson/parser/parser_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package parser_test

import (
"io/fs"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/aquasecurity/trivy/pkg/iac/scanners/terraformplan/tfjson/parser"
)

func TestParser_ParseFile(t *testing.T) {
planFile, err := parser.New().ParseFile("../testdata/plan.json")
require.NoError(t, err)
assert.NotNil(t, planFile)

fsys, err := planFile.ToFS()
require.NoError(t, err)
assert.NotNil(t, fsys)

b, err := fs.ReadFile(fsys, parser.TerraformMainFile)
require.NoError(t, err)

expected := `resource "aws_s3_bucket" "planbucket" {
bucket = "tfsec-plan-testing"
force_destroy = false
logging {
target_bucket = "arn:aws:s3:::iac-tfsec-dev"
}
versioning {
enabled = true
mfa_delete = false
}
}

resource "aws_s3_bucket_server_side_encryption_configuration" "example" {
bucket = aws_s3_bucket.planbucket.id
rule {
bucket_key_enabled = true
apply_server_side_encryption_by_default {
kms_master_key_id = ""
sse_algorithm = "AES256"
}
}
}

resource "aws_security_group" "sg" {
description = "Managed by Terraform"
name = "sg"
revoke_rules_on_delete = false
tags = {
Name = "blah"
}
tags_all = {
Name = "blah"
}
ingress {
cidr_blocks = [
"0.0.0.0/0",
]
description = ""
from_port = 80
ipv6_cidr_blocks = []
prefix_list_ids = []
protocol = "tcp"
security_groups = []
self = false
to_port = 80
}
}
`

assert.Equal(t, expected, string(b))
}
11 changes: 10 additions & 1 deletion pkg/iac/scanners/terraformplan/tfjson/parser/plan_file.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,23 @@ type Resource struct {
SchemaVersion int `json:"schema_version"`
}

func (r Resource) BlockType() string {
if r.Mode == "managed" {
return "resource"
}
return r.Mode
}

type ResourceChange struct {
Resource
Change `json:"change"`
}

type ResourceExpressions map[string]any

type ConfigurationResource struct {
Resource
Expressions map[string]any `json:"expressions"`
Expressions ResourceExpressions `json:"expressions"`
}

type Change struct {
Expand Down
13 changes: 6 additions & 7 deletions pkg/iac/scanners/terraformplan/tfjson/scanner.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ func (s *Scanner) Name() string {
return "Terraform Plan JSON"
}

func (s *Scanner) ScanFS(_ context.Context, fsys fs.FS, dir string) (scan.Results, error) {
func (s *Scanner) ScanFS(ctx context.Context, fsys fs.FS, dir string) (scan.Results, error) {

var results scan.Results

Expand All @@ -37,7 +37,7 @@ func (s *Scanner) ScanFS(_ context.Context, fsys fs.FS, dir string) (scan.Result
return nil
}

res, err := s.ScanFile(path, fsys)
res, err := s.ScanFile(ctx, path, fsys)
if err != nil {
return fmt.Errorf("failed to scan %s: %w", path, err)
}
Expand Down Expand Up @@ -66,19 +66,18 @@ func New(opts ...options.ScannerOption) *Scanner {
return scanner
}

func (s *Scanner) ScanFile(filepath string, fsys fs.FS) (scan.Results, error) {
func (s *Scanner) ScanFile(ctx context.Context, filepath string, fsys fs.FS) (scan.Results, error) {

s.logger.Debug("Scanning file", log.FilePath(filepath))
file, err := fsys.Open(filepath)
if err != nil {
return nil, err
}
defer file.Close()
return s.Scan(file)
return s.Scan(ctx, file)
}

func (s *Scanner) Scan(reader io.Reader) (scan.Results, error) {

func (s *Scanner) Scan(ctx context.Context, reader io.Reader) (scan.Results, error) {
planFile, err := s.parser.Parse(reader)
if err != nil {
return nil, err
Expand All @@ -89,5 +88,5 @@ func (s *Scanner) Scan(reader io.Reader) (scan.Results, error) {
return nil, fmt.Errorf("failed to convert plan to FS: %w", err)
}

return s.inner.ScanFS(context.TODO(), planFS, ".")
return s.inner.ScanFS(ctx, planFS, ".")
}
96 changes: 50 additions & 46 deletions pkg/iac/scanners/terraformplan/tfjson/scanner_test.go
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
package tfjson
package tfjson_test

import (
"os"
"strings"
"testing"
"testing/fstest"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/aquasecurity/trivy/internal/testutil"
"github.com/aquasecurity/trivy/pkg/iac/rego"
"github.com/aquasecurity/trivy/pkg/iac/scanners/options"
"github.com/aquasecurity/trivy/pkg/iac/scanners/terraformplan/tfjson"
)

const defaultCheck = `package defsec.abcdefg
Expand Down Expand Up @@ -37,91 +39,93 @@ deny[cause] {
cause := bucket.name
}`

func Test_TerraformScanner(t *testing.T) {
func TestScanner_ScanFS(t *testing.T) {
t.Parallel()

testCases := []struct {
name string
inputFile string
check string
options []options.ScannerOption
name string
input string
options []options.ScannerOption
expected []string
}{
{
name: "old rego metadata",
inputFile: "test/testdata/plan.json",
check: defaultCheck,
name: "use builtin checks",
input: "testdata/plan.json",
options: []options.ScannerOption{
rego.WithPolicyDirs("rules"),
rego.WithEmbeddedPolicies(true),
rego.WithEmbeddedLibraries(true),
},
expected: []string{
"AVD-AWS-0093",
"AVD-AWS-0086",
"AVD-AWS-0132",
"AVD-AWS-0094",
"AVD-AWS-0087",
"AVD-AWS-0091",
"AVD-AWS-0099",
"AVD-AWS-0124",
},
},
{
name: "with user namespace",
inputFile: "test/testdata/plan.json",
check: defaultCheck,
name: "with user namespace",
input: "testdata/plan.json",
options: []options.ScannerOption{
rego.WithPolicyDirs("rules"),
rego.WithPolicyReader(strings.NewReader(defaultCheck)),
rego.WithPolicyNamespaces("user"),
},
expected: []string{"TEST123"},
},
{
name: "with templated plan json",
inputFile: "test/testdata/plan_with_template.json",
check: `
# METADATA
# title: Bad buckets are bad
# description: Bad buckets are bad because they are not good.
# scope: package
name: "with templated plan json",
input: "testdata/plan_with_template.json",
options: []options.ScannerOption{
rego.WithPolicyReader(strings.NewReader(`# METADATA
# schemas:
# - input: schema["cloud"]
# custom:
# id: TEST123
# avd_id: AVD-TEST-0123
# severity: CRITICAL
# short_code: very-bad-misconfig
# recommended_action: "Fix the s3 bucket"

package user.foobar.ABC001

deny[cause] {
bucket := input.aws.s3.buckets[_]
bucket.name.value == "${template-name-is-$evil}"
cause := bucket.name
}
`,
options: []options.ScannerOption{
rego.WithPolicyDirs("rules"),
}`)),
rego.WithPolicyNamespaces("user"),
},
expected: []string{"TEST123"},
},
{
name: "plan with arbitrary name",
inputFile: "test/testdata/arbitrary_name.json",
check: defaultCheck,
name: "plan with arbitrary name",
input: "testdata/arbitrary_name.json",
options: []options.ScannerOption{
rego.WithPolicyDirs("rules"),
rego.WithPolicyReader(strings.NewReader(defaultCheck)),
rego.WithPolicyNamespaces("user"),
},
expected: []string{"TEST123"},
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
b, _ := os.ReadFile(tc.inputFile)
fs := testutil.CreateFS(map[string]string{
"/code/main.tfplan.json": string(b),
"/rules/test.rego": tc.check,
})
b, err := os.ReadFile(tc.input)
require.NoError(t, err)

so := append(tc.options, rego.WithPolicyFilesystem(fs))
scanner := New(so...)
fsys := fstest.MapFS{
"main.tfplan.json": {Data: b},
}

results, err := scanner.ScanFS(t.Context(), fs, "code")
scanner := tfjson.New(tc.options...)
results, err := scanner.ScanFS(t.Context(), fsys, ".")
require.NoError(t, err)

require.Len(t, results.GetFailed(), 1)

failure := results.GetFailed()[0]
var got []string
for _, r := range results.GetFailed() {
got = append(got, r.Rule().ID)
}

assert.Equal(t, "AVD-TEST-0123", failure.Rule().AVDID)
assert.ElementsMatch(t, tc.expected, got)
})
}
}
21 changes: 0 additions & 21 deletions pkg/iac/scanners/terraformplan/tfjson/test/parser_test.go

This file was deleted.

39 changes: 0 additions & 39 deletions pkg/iac/scanners/terraformplan/tfjson/test/scanner_test.go

This file was deleted.

Loading
Loading