Skip to content
This repository has been archived by the owner on Jan 22, 2025. It is now read-only.

fix(terraform): convert input variables to expected type #40

Merged
merged 1 commit into from
Oct 30, 2023
Merged
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
54 changes: 43 additions & 11 deletions pkg/scanners/terraform/parser/evaluator.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package parser

import (
"context"
"fmt"
"errors"
"io/fs"
"reflect"
"time"
Expand All @@ -12,7 +12,9 @@ import (
tfcontext "github.com/aquasecurity/defsec/pkg/terraform/context"
"github.com/aquasecurity/defsec/pkg/types"
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/ext/typeexpr"
"github.com/zclconf/go-cty/cty"
"github.com/zclconf/go-cty/cty/convert"
"github.com/zclconf/go-cty/cty/gocty"
)

Expand Down Expand Up @@ -334,29 +336,59 @@ func (e *evaluator) copyVariables(from, to *terraform.Block) {

func (e *evaluator) evaluateVariable(b *terraform.Block) (cty.Value, error) {
if b.Label() == "" {
return cty.NilVal, fmt.Errorf("empty label - cannot resolve")
}
if override, exists := e.inputVars[b.Label()]; exists {
return override, nil
return cty.NilVal, errors.New("empty label - cannot resolve")
}

attributes := b.Attributes()
if attributes == nil {
return cty.NilVal, fmt.Errorf("cannot resolve variable with no attributes")
return cty.NilVal, errors.New("cannot resolve variable with no attributes")
}

var valType cty.Type
var defaults *typeexpr.Defaults
if typeAttr, exists := attributes["type"]; exists {
ty, def, err := typeAttr.DecodeVarType()
if err != nil {
return cty.NilVal, err
}
valType = ty
defaults = def
}

var val cty.Value

if override, exists := e.inputVars[b.Label()]; exists {
val = override
} else if def, exists := attributes["default"]; exists {
val = def.NullableValue()
} else {
return cty.NilVal, errors.New("no value found")
}
if def, exists := attributes["default"]; exists {
return def.NullableValue(), nil

if valType != cty.NilType {
if defaults != nil {
val = defaults.Apply(val)
}

typedVal, err := convert.Convert(val, valType)
if err != nil {
return cty.NilVal, err
}
return typedVal, nil
}
return cty.NilVal, fmt.Errorf("no value found")

return val, nil

}

func (e *evaluator) evaluateOutput(b *terraform.Block) (cty.Value, error) {
if b.Label() == "" {
return cty.NilVal, fmt.Errorf("empty label - cannot resolve")
return cty.NilVal, errors.New("empty label - cannot resolve")
}

attribute := b.GetAttribute("value")
if attribute.IsNil() {
return cty.NilVal, fmt.Errorf("cannot resolve variable with no attributes")
return cty.NilVal, errors.New("cannot resolve output with no attributes")
}
return attribute.Value(), nil
}
Expand Down
129 changes: 129 additions & 0 deletions pkg/scanners/terraform/parser/parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -759,3 +759,132 @@ resource "aws_s3_bucket" "main" {

assert.Equal(t, "test_bucket", block.GetAttribute("bucket").AsStringValueOrDefault("", block).Value())
}

func Test_ForEachRefToLocals(t *testing.T) {
fs := testutil.CreateFS(t, map[string]string{
"main.tf": `
locals {
buckets = toset([
"foo",
"bar",
])
}
resource "aws_s3_bucket" "this" {
for_each = local.buckets
bucket = each.key
}
`,
})

parser := New(fs, "", OptionStopOnHCLError(true))
if err := parser.ParseFS(context.TODO(), "."); err != nil {
t.Fatal(err)
}
modules, _, err := parser.EvaluateAll(context.TODO())
assert.NoError(t, err)
assert.Len(t, modules, 1)

rootModule := modules[0]

blocks := rootModule.GetResourcesByType("aws_s3_bucket")
assert.Len(t, blocks, 2)

for _, block := range blocks {
attr := block.GetAttribute("bucket")
require.NotNil(t, attr)
assert.Contains(t, []string{"foo", "bar"}, attr.AsStringValueOrDefault("", block).Value())
}
}

func Test_ForEachRefToVariableWithDefault(t *testing.T) {
fs := testutil.CreateFS(t, map[string]string{
"main.tf": `
variable "buckets" {
type = set(string)
default = ["foo", "bar"]
}
resource "aws_s3_bucket" "this" {
for_each = var.buckets
bucket = each.key
}
`,
})

parser := New(fs, "", OptionStopOnHCLError(true))
if err := parser.ParseFS(context.TODO(), "."); err != nil {
t.Fatal(err)
}
modules, _, err := parser.EvaluateAll(context.TODO())
assert.NoError(t, err)
assert.Len(t, modules, 1)

rootModule := modules[0]

blocks := rootModule.GetResourcesByType("aws_s3_bucket")
assert.Len(t, blocks, 2)

for _, block := range blocks {
attr := block.GetAttribute("bucket")
require.NotNil(t, attr)
assert.Contains(t, []string{"foo", "bar"}, attr.AsStringValueOrDefault("", block).Value())
}
}

func Test_ForEachRefToVariableFromFile(t *testing.T) {
fs := testutil.CreateFS(t, map[string]string{
"main.tf": `
variable "policy_rules" {
type = object({
secure_tags = optional(map(object({
session_matcher = optional(string)
priority = number
enabled = optional(bool, true)
})), {})
})
}
resource "google_network_security_gateway_security_policy_rule" "secure_tag_rules" {
for_each = var.policy_rules.secure_tags
provider = google-beta
project = "test"
name = each.key
enabled = each.value.enabled
priority = each.value.priority
session_matcher = each.value.session_matcher
}
`,
"main.tfvars": `
policy_rules = {
secure_tags = {
secure-tag-1 = {
session_matcher = "host() != 'google.com'"
priority = 1001
}
}
}
`,
})

parser := New(fs, "", OptionStopOnHCLError(true))
parser.SetTFVarsPaths("main.tfvars")
if err := parser.ParseFS(context.TODO(), "."); err != nil {
t.Fatal(err)
}
modules, _, err := parser.EvaluateAll(context.TODO())
assert.NoError(t, err)
assert.Len(t, modules, 1)

rootModule := modules[0]

blocks := rootModule.GetResourcesByType("google_network_security_gateway_security_policy_rule")
assert.Len(t, blocks, 1)

block := blocks[0]

assert.Equal(t, "secure-tag-1", block.GetAttribute("name").AsStringValueOrDefault("", block).Value())
assert.Equal(t, true, block.GetAttribute("enabled").AsBoolValueOrDefault(false, block).Value())
assert.Equal(t, "host() != 'google.com'", block.GetAttribute("session_matcher").AsStringValueOrDefault("", block).Value())
assert.Equal(t, 1001, block.GetAttribute("priority").AsIntValueOrDefault(0, block).Value())
}
2 changes: 1 addition & 1 deletion test/module_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -596,7 +596,7 @@ module "something" {
`,
"modules/a/main.tf": `
variable "group" {
type = "string"
type = string
}
resource aws_iam_group_policy mfa {
Expand Down