Skip to content

Commit 9d7264a

Browse files
authored
fix(misconf): do not filter Terraform plan JSON by name (#7406)
Signed-off-by: nikpivkin <nikita.pivkin@smartforce.io>
1 parent 44e4686 commit 9d7264a

File tree

5 files changed

+71
-79
lines changed

5 files changed

+71
-79
lines changed

docs/docs/coverage/iac/index.md

+11-11
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,17 @@ Trivy scans Infrastructure as Code (IaC) files for
88

99
## Supported configurations
1010

11-
| Config type | File patterns |
12-
|-------------------------------------|-----------------------------------------------|
13-
| [Kubernetes](kubernetes.md) | \*.yml, \*.yaml, \*.json |
14-
| [Docker](docker.md) | Dockerfile, Containerfile |
15-
| [Terraform](terraform.md) | \*.tf, \*.tf.json, \*.tfvars |
16-
| [Terraform Plan](terraform.md) | tfplan, \*.tfplan, \*.tfplan.json, \*.tf.json |
17-
| [CloudFormation](cloudformation.md) | \*.yml, \*.yaml, \*.json |
18-
| [Azure ARM Template](azure-arm.md) | \*.json |
19-
| [Helm](helm.md) | \*.yaml, \*.tpl, \*.tar.gz, etc. |
20-
| [YAML][json-and-yaml] | \*.yaml, \*.yml |
21-
| [JSON][json-and-yaml] | \*.json |
11+
| Config type | File patterns |
12+
|-------------------------------------|----------------------------------|
13+
| [Kubernetes](kubernetes.md) | \*.yml, \*.yaml, \*.json |
14+
| [Docker](docker.md) | Dockerfile, Containerfile |
15+
| [Terraform](terraform.md) | \*.tf, \*.tf.json, \*.tfvars |
16+
| [Terraform Plan](terraform.md) | tfplan, \*.tfplan, \*.json |
17+
| [CloudFormation](cloudformation.md) | \*.yml, \*.yaml, \*.json |
18+
| [Azure ARM Template](azure-arm.md) | \*.json |
19+
| [Helm](helm.md) | \*.yaml, \*.tpl, \*.tar.gz, etc. |
20+
| [YAML][json-and-yaml] | \*.yaml, \*.yml |
21+
| [JSON][json-and-yaml] | \*.json |
2222

2323
[misconf]: ../../scanner/misconfiguration/index.md
2424
[secret]: ../../scanner/secret.md

pkg/iac/detection/detect.go

+10-6
Original file line numberDiff line numberDiff line change
@@ -88,12 +88,17 @@ func init() {
8888

8989
contents := make(map[string]any)
9090
err := json.NewDecoder(r).Decode(&contents)
91-
if err == nil {
92-
if _, ok := contents["terraform_version"]; ok {
93-
_, stillOk := contents["format_version"]
94-
return stillOk
91+
if err != nil {
92+
return false
93+
}
94+
95+
for _, k := range []string{"terraform_version", "format_version"} {
96+
if _, ok := contents[k]; !ok {
97+
return false
9598
}
9699
}
100+
101+
return true
97102
}
98103
return false
99104
}
@@ -150,8 +155,7 @@ func init() {
150155
return false
151156
}
152157

153-
return (sniff.Parameters != nil && len(sniff.Parameters) > 0) ||
154-
(sniff.Resources != nil && len(sniff.Resources) > 0)
158+
return len(sniff.Parameters) > 0 || len(sniff.Resources) > 0
155159
}
156160

157161
matchers[FileTypeDockerfile] = func(name string, _ io.ReadSeeker) bool {

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

+19-19
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@ import (
66
"io"
77
"io/fs"
88

9-
"github.com/bmatcuk/doublestar/v4"
10-
119
"github.com/aquasecurity/trivy/pkg/iac/framework"
1210
"github.com/aquasecurity/trivy/pkg/iac/scan"
1311
"github.com/aquasecurity/trivy/pkg/iac/scanners/options"
@@ -17,11 +15,6 @@ import (
1715
"github.com/aquasecurity/trivy/pkg/log"
1816
)
1917

20-
var tfPlanExts = []string{
21-
"**/*tfplan.json",
22-
"**/*tf.json",
23-
}
24-
2518
type Scanner struct {
2619
parser *parser.Parser
2720
logger *log.Logger
@@ -93,25 +86,32 @@ func (s *Scanner) Name() string {
9386
return "Terraform Plan JSON"
9487
}
9588

96-
func (s *Scanner) ScanFS(ctx context.Context, inputFS fs.FS, dir string) (scan.Results, error) {
97-
var filesFound []string
89+
func (s *Scanner) ScanFS(ctx context.Context, fsys fs.FS, dir string) (scan.Results, error) {
90+
91+
var results scan.Results
9892

99-
for _, ext := range tfPlanExts {
100-
files, err := doublestar.Glob(inputFS, ext, doublestar.WithFilesOnly())
93+
walkFn := func(path string, d fs.DirEntry, err error) error {
10194
if err != nil {
102-
return nil, fmt.Errorf("unable to scan for terraform plan files: %w", err)
95+
return err
10396
}
104-
filesFound = append(filesFound, files...)
105-
}
10697

107-
var results scan.Results
108-
for _, f := range filesFound {
109-
res, err := s.ScanFile(f, inputFS)
98+
if d.IsDir() {
99+
return nil
100+
}
101+
102+
res, err := s.ScanFile(path, fsys)
110103
if err != nil {
111-
return nil, err
104+
return fmt.Errorf("failed to scan %s: %w", path, err)
112105
}
106+
113107
results = append(results, res...)
108+
return nil
114109
}
110+
111+
if err := fs.WalkDir(fsys, dir, walkFn); err != nil {
112+
return nil, err
113+
}
114+
115115
return results, nil
116116
}
117117

@@ -148,7 +148,7 @@ func (s *Scanner) Scan(reader io.Reader) (scan.Results, error) {
148148

149149
planFS, err := planFile.ToFS()
150150
if err != nil {
151-
return nil, err
151+
return nil, fmt.Errorf("failed to convert plan to FS: %w", err)
152152
}
153153

154154
scanner := terraformScanner.New(s.options...)

pkg/iac/scanners/terraformplan/tfjson/scanner_test.go

+30-43
Original file line numberDiff line numberDiff line change
@@ -12,20 +12,7 @@ import (
1212
"github.com/aquasecurity/trivy/pkg/iac/scanners/options"
1313
)
1414

15-
func Test_TerraformScanner(t *testing.T) {
16-
t.Parallel()
17-
18-
testCases := []struct {
19-
name string
20-
inputFile string
21-
inputRego string
22-
options []options.ScannerOption
23-
}{
24-
{
25-
name: "old rego metadata",
26-
inputFile: "test/testdata/plan.json",
27-
inputRego: `
28-
package defsec.abcdefg
15+
const defaultCheck = `package defsec.abcdefg
2916
3017
__rego_metadata__ := {
3118
"id": "TEST123",
@@ -48,48 +35,40 @@ deny[cause] {
4835
bucket := input.aws.s3.buckets[_]
4936
bucket.name.value == "tfsec-plan-testing"
5037
cause := bucket.name
51-
}
52-
`,
38+
}`
39+
40+
func Test_TerraformScanner(t *testing.T) {
41+
t.Parallel()
42+
43+
testCases := []struct {
44+
name string
45+
inputFile string
46+
check string
47+
options []options.ScannerOption
48+
}{
49+
{
50+
name: "old rego metadata",
51+
inputFile: "test/testdata/plan.json",
52+
check: defaultCheck,
5353
options: []options.ScannerOption{
5454
options.ScannerWithPolicyDirs("rules"),
5555
options.ScannerWithRegoOnly(true),
56-
options.ScannerWithEmbeddedPolicies(false)},
56+
},
5757
},
5858
{
5959
name: "with user namespace",
6060
inputFile: "test/testdata/plan.json",
61-
inputRego: `
62-
# METADATA
63-
# title: Bad buckets are bad
64-
# description: Bad buckets are bad because they are not good.
65-
# scope: package
66-
# schemas:
67-
# - input: schema["input"]
68-
# custom:
69-
# avd_id: AVD-TEST-0123
70-
# severity: CRITICAL
71-
# short_code: very-bad-misconfig
72-
# recommended_action: "Fix the s3 bucket"
73-
74-
package user.foobar.ABC001
75-
76-
deny[cause] {
77-
bucket := input.aws.s3.buckets[_]
78-
bucket.name.value == "tfsec-plan-testing"
79-
cause := bucket.name
80-
}
81-
`,
61+
check: defaultCheck,
8262
options: []options.ScannerOption{
8363
options.ScannerWithPolicyDirs("rules"),
8464
options.ScannerWithRegoOnly(true),
85-
options.ScannerWithEmbeddedPolicies(false),
8665
options.ScannerWithPolicyNamespaces("user"),
8766
},
8867
},
8968
{
9069
name: "with templated plan json",
9170
inputFile: "test/testdata/plan_with_template.json",
92-
inputRego: `
71+
check: `
9372
# METADATA
9473
# title: Bad buckets are bad
9574
# description: Bad buckets are bad because they are not good.
@@ -113,19 +92,27 @@ deny[cause] {
11392
options: []options.ScannerOption{
11493
options.ScannerWithPolicyDirs("rules"),
11594
options.ScannerWithRegoOnly(true),
116-
options.ScannerWithEmbeddedPolicies(false),
95+
options.ScannerWithPolicyNamespaces("user"),
96+
},
97+
},
98+
{
99+
name: "plan with arbitrary name",
100+
inputFile: "test/testdata/arbitrary_name.json",
101+
check: defaultCheck,
102+
options: []options.ScannerOption{
103+
options.ScannerWithPolicyDirs("rules"),
104+
options.ScannerWithRegoOnly(true),
117105
options.ScannerWithPolicyNamespaces("user"),
118106
},
119107
},
120108
}
121109

122110
for _, tc := range testCases {
123-
tc := tc
124111
t.Run(tc.name, func(t *testing.T) {
125112
b, _ := os.ReadFile(tc.inputFile)
126113
fs := testutil.CreateFS(t, map[string]string{
127114
"/code/main.tfplan.json": string(b),
128-
"/rules/test.rego": tc.inputRego,
115+
"/rules/test.rego": tc.check,
129116
})
130117

131118
so := append(tc.options, options.ScannerWithPolicyFilesystem(fs))
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"format_version":"1.2","terraform_version":"1.8.1","planned_values":{"root_module":{"resources":[{"address":"aws_s3_bucket.this","mode":"managed","type":"aws_s3_bucket","name":"this","provider_name":"registry.opentofu.org/hashicorp/aws","schema_version":0,"values":{"bucket":"tfsec-plan-testing","force_destroy":false,"tags":null,"timeouts":null},"sensitive_values":{"cors_rule":[],"grant":[],"lifecycle_rule":[],"logging":[],"object_lock_configuration":[],"replication_configuration":[],"server_side_encryption_configuration":[],"tags_all":{},"versioning":[],"website":[]}}]}},"resource_drift":[{"address":"aws_security_group.this","mode":"managed","type":"aws_security_group","name":"this","provider_name":"registry.opentofu.org/hashicorp/aws","change":{"actions":["delete"],"before":{"arn":"arn:aws:ec2:us-west-1:145167242158:security-group/sg-0f3ac859864629452","description":"Managed by Terraform","egress":[{"cidr_blocks":[],"description":"","from_port":0,"ipv6_cidr_blocks":[],"prefix_list_ids":[],"protocol":"-1","security_groups":[],"self":false,"to_port":0}],"id":"sg-0f3ac859864629452","ingress":[],"name":"test_sg","name_prefix":"","owner_id":"145167242158","revoke_rules_on_delete":false,"tags":null,"tags_all":{},"timeouts":null,"vpc_id":"vpc-06165c55f5a70fdfa"},"after":null,"after_unknown":{},"before_sensitive":{"egress":[{"cidr_blocks":[],"ipv6_cidr_blocks":[],"prefix_list_ids":[],"security_groups":[]}],"ingress":[],"tags_all":{}},"after_sensitive":false}}],"resource_changes":[{"address":"aws_s3_bucket.this","mode":"managed","type":"aws_s3_bucket","name":"this","provider_name":"registry.opentofu.org/hashicorp/aws","change":{"actions":["create"],"before":null,"after":{"bucket":"tfsec-plan-testing","force_destroy":false,"tags":null,"timeouts":null},"after_unknown":{"acceleration_status":true,"acl":true,"arn":true,"bucket_domain_name":true,"bucket_prefix":true,"bucket_regional_domain_name":true,"cors_rule":true,"grant":true,"hosted_zone_id":true,"id":true,"lifecycle_rule":true,"logging":true,"object_lock_configuration":true,"object_lock_enabled":true,"policy":true,"region":true,"replication_configuration":true,"request_payer":true,"server_side_encryption_configuration":true,"tags_all":true,"versioning":true,"website":true,"website_domain":true,"website_endpoint":true},"before_sensitive":false,"after_sensitive":{"cors_rule":[],"grant":[],"lifecycle_rule":[],"logging":[],"object_lock_configuration":[],"replication_configuration":[],"server_side_encryption_configuration":[],"tags_all":{},"versioning":[],"website":[]}}}],"configuration":{"provider_config":{"aws":{"name":"aws","full_name":"registry.opentofu.org/hashicorp/aws"}},"root_module":{"resources":[{"address":"aws_s3_bucket.this","mode":"managed","type":"aws_s3_bucket","name":"this","provider_config_key":"aws","expressions":{"bucket":{"constant_value":"tfsec-plan-testing"}},"schema_version":0}]}},"timestamp":"2024-08-28T04:27:38Z","errored":false}

0 commit comments

Comments
 (0)