fix(misconf): do not filter Terraform plan JSON by name (#7406)

Signed-off-by: nikpivkin <nikita.pivkin@smartforce.io>
This commit is contained in:
Nikita Pivkin
2024-08-29 05:51:25 +06:00
committed by GitHub
parent 44e468603d
commit 9d7264af8e
5 changed files with 73 additions and 81 deletions

View File

@@ -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

View File

@@ -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 {

View File

@@ -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...)

View File

@@ -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))

View 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}