mirror of
https://github.com/aquasecurity/trivy.git
synced 2025-12-07 21:30:46 -08:00
fix(misconf): do not filter Terraform plan JSON by name (#7406)
Signed-off-by: nikpivkin <nikita.pivkin@smartforce.io>
This commit is contained in:
@@ -8,17 +8,17 @@ Trivy scans Infrastructure as Code (IaC) files for
|
||||
|
||||
## Supported configurations
|
||||
|
||||
| Config type | File patterns |
|
||||
|-------------------------------------|-----------------------------------------------|
|
||||
| [Kubernetes](kubernetes.md) | \*.yml, \*.yaml, \*.json |
|
||||
| [Docker](docker.md) | Dockerfile, Containerfile |
|
||||
| [Terraform](terraform.md) | \*.tf, \*.tf.json, \*.tfvars |
|
||||
| [Terraform Plan](terraform.md) | tfplan, \*.tfplan, \*.tfplan.json, \*.tf.json |
|
||||
| [CloudFormation](cloudformation.md) | \*.yml, \*.yaml, \*.json |
|
||||
| [Azure ARM Template](azure-arm.md) | \*.json |
|
||||
| [Helm](helm.md) | \*.yaml, \*.tpl, \*.tar.gz, etc. |
|
||||
| [YAML][json-and-yaml] | \*.yaml, \*.yml |
|
||||
| [JSON][json-and-yaml] | \*.json |
|
||||
| Config type | File patterns |
|
||||
|-------------------------------------|----------------------------------|
|
||||
| [Kubernetes](kubernetes.md) | \*.yml, \*.yaml, \*.json |
|
||||
| [Docker](docker.md) | Dockerfile, Containerfile |
|
||||
| [Terraform](terraform.md) | \*.tf, \*.tf.json, \*.tfvars |
|
||||
| [Terraform Plan](terraform.md) | tfplan, \*.tfplan, \*.json |
|
||||
| [CloudFormation](cloudformation.md) | \*.yml, \*.yaml, \*.json |
|
||||
| [Azure ARM Template](azure-arm.md) | \*.json |
|
||||
| [Helm](helm.md) | \*.yaml, \*.tpl, \*.tar.gz, etc. |
|
||||
| [YAML][json-and-yaml] | \*.yaml, \*.yml |
|
||||
| [JSON][json-and-yaml] | \*.json |
|
||||
|
||||
[misconf]: ../../scanner/misconfiguration/index.md
|
||||
[secret]: ../../scanner/secret.md
|
||||
|
||||
@@ -88,12 +88,17 @@ func init() {
|
||||
|
||||
contents := make(map[string]any)
|
||||
err := json.NewDecoder(r).Decode(&contents)
|
||||
if err == nil {
|
||||
if _, ok := contents["terraform_version"]; ok {
|
||||
_, stillOk := contents["format_version"]
|
||||
return stillOk
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, k := range []string{"terraform_version", "format_version"} {
|
||||
if _, ok := contents[k]; !ok {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -150,8 +155,7 @@ func init() {
|
||||
return false
|
||||
}
|
||||
|
||||
return (sniff.Parameters != nil && len(sniff.Parameters) > 0) ||
|
||||
(sniff.Resources != nil && len(sniff.Resources) > 0)
|
||||
return len(sniff.Parameters) > 0 || len(sniff.Resources) > 0
|
||||
}
|
||||
|
||||
matchers[FileTypeDockerfile] = func(name string, _ io.ReadSeeker) bool {
|
||||
|
||||
@@ -6,8 +6,6 @@ import (
|
||||
"io"
|
||||
"io/fs"
|
||||
|
||||
"github.com/bmatcuk/doublestar/v4"
|
||||
|
||||
"github.com/aquasecurity/trivy/pkg/iac/framework"
|
||||
"github.com/aquasecurity/trivy/pkg/iac/scan"
|
||||
"github.com/aquasecurity/trivy/pkg/iac/scanners/options"
|
||||
@@ -17,11 +15,6 @@ import (
|
||||
"github.com/aquasecurity/trivy/pkg/log"
|
||||
)
|
||||
|
||||
var tfPlanExts = []string{
|
||||
"**/*tfplan.json",
|
||||
"**/*tf.json",
|
||||
}
|
||||
|
||||
type Scanner struct {
|
||||
parser *parser.Parser
|
||||
logger *log.Logger
|
||||
@@ -93,25 +86,32 @@ func (s *Scanner) Name() string {
|
||||
return "Terraform Plan JSON"
|
||||
}
|
||||
|
||||
func (s *Scanner) ScanFS(ctx context.Context, inputFS fs.FS, dir string) (scan.Results, error) {
|
||||
var filesFound []string
|
||||
|
||||
for _, ext := range tfPlanExts {
|
||||
files, err := doublestar.Glob(inputFS, ext, doublestar.WithFilesOnly())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to scan for terraform plan files: %w", err)
|
||||
}
|
||||
filesFound = append(filesFound, files...)
|
||||
}
|
||||
func (s *Scanner) ScanFS(ctx context.Context, fsys fs.FS, dir string) (scan.Results, error) {
|
||||
|
||||
var results scan.Results
|
||||
for _, f := range filesFound {
|
||||
res, err := s.ScanFile(f, inputFS)
|
||||
|
||||
walkFn := func(path string, d fs.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return err
|
||||
}
|
||||
|
||||
if d.IsDir() {
|
||||
return nil
|
||||
}
|
||||
|
||||
res, err := s.ScanFile(path, fsys)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to scan %s: %w", path, err)
|
||||
}
|
||||
|
||||
results = append(results, res...)
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := fs.WalkDir(fsys, dir, walkFn); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
@@ -148,7 +148,7 @@ func (s *Scanner) Scan(reader io.Reader) (scan.Results, error) {
|
||||
|
||||
planFS, err := planFile.ToFS()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("failed to convert plan to FS: %w", err)
|
||||
}
|
||||
|
||||
scanner := terraformScanner.New(s.options...)
|
||||
|
||||
@@ -12,20 +12,7 @@ import (
|
||||
"github.com/aquasecurity/trivy/pkg/iac/scanners/options"
|
||||
)
|
||||
|
||||
func Test_TerraformScanner(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
inputFile string
|
||||
inputRego string
|
||||
options []options.ScannerOption
|
||||
}{
|
||||
{
|
||||
name: "old rego metadata",
|
||||
inputFile: "test/testdata/plan.json",
|
||||
inputRego: `
|
||||
package defsec.abcdefg
|
||||
const defaultCheck = `package defsec.abcdefg
|
||||
|
||||
__rego_metadata__ := {
|
||||
"id": "TEST123",
|
||||
@@ -48,48 +35,40 @@ deny[cause] {
|
||||
bucket := input.aws.s3.buckets[_]
|
||||
bucket.name.value == "tfsec-plan-testing"
|
||||
cause := bucket.name
|
||||
}
|
||||
`,
|
||||
}`
|
||||
|
||||
func Test_TerraformScanner(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
inputFile string
|
||||
check string
|
||||
options []options.ScannerOption
|
||||
}{
|
||||
{
|
||||
name: "old rego metadata",
|
||||
inputFile: "test/testdata/plan.json",
|
||||
check: defaultCheck,
|
||||
options: []options.ScannerOption{
|
||||
options.ScannerWithPolicyDirs("rules"),
|
||||
options.ScannerWithRegoOnly(true),
|
||||
options.ScannerWithEmbeddedPolicies(false)},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "with user namespace",
|
||||
inputFile: "test/testdata/plan.json",
|
||||
inputRego: `
|
||||
# METADATA
|
||||
# title: Bad buckets are bad
|
||||
# description: Bad buckets are bad because they are not good.
|
||||
# scope: package
|
||||
# schemas:
|
||||
# - input: schema["input"]
|
||||
# custom:
|
||||
# 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 == "tfsec-plan-testing"
|
||||
cause := bucket.name
|
||||
}
|
||||
`,
|
||||
check: defaultCheck,
|
||||
options: []options.ScannerOption{
|
||||
options.ScannerWithPolicyDirs("rules"),
|
||||
options.ScannerWithRegoOnly(true),
|
||||
options.ScannerWithEmbeddedPolicies(false),
|
||||
options.ScannerWithPolicyNamespaces("user"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "with templated plan json",
|
||||
inputFile: "test/testdata/plan_with_template.json",
|
||||
inputRego: `
|
||||
check: `
|
||||
# METADATA
|
||||
# title: Bad buckets are bad
|
||||
# description: Bad buckets are bad because they are not good.
|
||||
@@ -113,19 +92,27 @@ deny[cause] {
|
||||
options: []options.ScannerOption{
|
||||
options.ScannerWithPolicyDirs("rules"),
|
||||
options.ScannerWithRegoOnly(true),
|
||||
options.ScannerWithEmbeddedPolicies(false),
|
||||
options.ScannerWithPolicyNamespaces("user"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "plan with arbitrary name",
|
||||
inputFile: "test/testdata/arbitrary_name.json",
|
||||
check: defaultCheck,
|
||||
options: []options.ScannerOption{
|
||||
options.ScannerWithPolicyDirs("rules"),
|
||||
options.ScannerWithRegoOnly(true),
|
||||
options.ScannerWithPolicyNamespaces("user"),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
tc := tc
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
b, _ := os.ReadFile(tc.inputFile)
|
||||
fs := testutil.CreateFS(t, map[string]string{
|
||||
"/code/main.tfplan.json": string(b),
|
||||
"/rules/test.rego": tc.inputRego,
|
||||
"/rules/test.rego": tc.check,
|
||||
})
|
||||
|
||||
so := append(tc.options, options.ScannerWithPolicyFilesystem(fs))
|
||||
|
||||
1
pkg/iac/scanners/terraformplan/tfjson/test/testdata/arbitrary_name.json
vendored
Normal file
1
pkg/iac/scanners/terraformplan/tfjson/test/testdata/arbitrary_name.json
vendored
Normal file
@@ -0,0 +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}
|
||||
Reference in New Issue
Block a user