Skip to content

Commit

Permalink
fix(terraform): Handled nested unrendered vars (#3853)
Browse files Browse the repository at this point in the history
* handle nested attributes when checking for unrendered vars

* add dedicated unrendered vars tests

* remove unused var
  • Loading branch information
mikeurbanski1 authored Nov 13, 2022
1 parent ca93b14 commit 2003ad7
Show file tree
Hide file tree
Showing 8 changed files with 152 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -53,13 +53,24 @@ def run(self, graph_connector: DiGraph) -> Tuple[List[Dict[str, Any]], List[Dict
return passed_vertices, failed_vertices, unknown_vertices

def get_operation(self, vertex: Dict[str, Any]) -> Optional[bool]:
attr_val = vertex.get(self.attribute) # type:ignore[arg-type] # due to attribute can be None
# if this value contains an underendered variable, then we cannot evaluate value checks,
# and will return None (for UNKNOWN)
# handle edge cases in some policies that explicitly look for blank values
if self.is_value_attribute_check and self._is_variable_dependant(attr_val, vertex['source_']) \
and self.value != '':
return None
# we also need to check the attribute stack - e.g., if they are looking for tags.component, but tags = local.tags,
# then we actually need to see if tags is variable dependent as well
attr_parts = self.attribute.split('.') # type:ignore[union-attr] # due to attribute can be None (but not really)
attr_to_check = None
for attr in attr_parts:
attr_to_check = f'{attr_to_check}.{attr}' if attr_to_check else attr
value_to_check = vertex.get(attr_to_check)

# we can only check is_attribute_value_check when evaluating the full attribute
# for example, if we have a policy that says "tags.component exists", and tags = local.tags, then
# we need to check if tags is variable dependent even though this is a not value_attribute check
if (attr_to_check != self.attribute or self.is_value_attribute_check) \
and self._is_variable_dependant(value_to_check, vertex['source_']) \
and self.value != '':
return None

if self.attribute and (self.is_jsonpath_check or re.match(WILDCARD_PATTERN, self.attribute)):
attribute_matches = self.get_attribute_matches(vertex)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
metadata:
id: "BUCKET_EQUALS"
name: "Ensure S3 bucket name is xyz"
category: "general"
definition:
cond_type: "attribute"
resource_types:
- "aws_s3_bucket"
attribute: "bucket"
operator: "equals"
value: "xyz"
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
metadata:
id: "BUCKET_EXISTS"
name: "Ensure S3 bucket name is present"
category: "general"
definition:
cond_type: "attribute"
resource_types:
- "aws_s3_bucket"
attribute: "bucket"
operator: "exists"
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
metadata:
id: "COMPONENT_EQUALS"
name: "Ensure S3 bucket has a component tag"
category: "general"
definition:
cond_type: "attribute"
resource_types:
- "aws_s3_bucket"
attribute: "tags.component"
operator: "equals"
value: "xyz"
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
metadata:
id: "COMPONENT_EXISTS"
name: "Ensure S3 bucket has a component tag"
category: "general"
definition:
cond_type: "attribute"
resource_types:
- "aws_s3_bucket"
attribute: "tags.component"
operator: "exists"
40 changes: 40 additions & 0 deletions tests/terraform/runner/resources/unrendered_vars/nested.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@

variable "tags_without_component" {
default = {
something = "something"
}
}

variable "tags_with_component" {
default = {
component = "xyz"
}
}

variable "component" {
default = "xyz"
}

resource "aws_s3_bucket" "unknown_nested_unknown" {
tags = var.unknown_tags
}

resource "aws_s3_bucket" "unknown_nested_2_pass" {
tags = {
component = var.unknown_component
}
}

resource "aws_s3_bucket" "known_nested_pass" {
tags = var.tags_with_component
}

resource "aws_s3_bucket" "known_nested_2_pass" {
tags = {
component = var.component
}
}

resource "aws_s3_bucket" "known_nested_fail" {
tags = var.tags_without_component
}
11 changes: 11 additions & 0 deletions tests/terraform/runner/resources/unrendered_vars/simple.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
variable "bucket" {
default = "xyz"
}

resource "aws_s3_bucket" "unknown_simple" {
bucket = var.unknown_bucket
}

resource "aws_s3_bucket" "known_simple_pass" {
bucket = var.bucket
}
44 changes: 44 additions & 0 deletions tests/terraform/runner/test_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -1273,6 +1273,50 @@ def test_resource_negative_values_do_exist(self):
self.assertEqual(len(report.passed_checks), 3)
self.assertEqual(len(report.failed_checks), 3)

def test_unrendered_simple_var(self):
resources_dir = os.path.join(
os.path.dirname(os.path.realpath(__file__)), "resources", "unrendered_vars")
file_to_scan = os.path.join(resources_dir, "simple.tf")
checks = ['BUCKET_EQUALS', 'BUCKET_EXISTS']

runner = Runner()
runner_filter = RunnerFilter(framework=['terraform'], checks=checks)
report = runner.run(root_folder=None, files=[file_to_scan], external_checks_dir=[resources_dir], runner_filter=runner_filter)

# plus 1 unknown
self.assertEqual(len(report.passed_checks), 3)
self.assertEqual(len(report.failed_checks), 0)

self.assertTrue(any(r.check_id == 'BUCKET_EXISTS' and r.resource == 'aws_s3_bucket.known_simple_pass' for r in report.passed_checks))
self.assertTrue(any(r.check_id == 'BUCKET_EQUALS' and r.resource == 'aws_s3_bucket.known_simple_pass' for r in report.passed_checks))

self.assertTrue(any(r.check_id == 'BUCKET_EXISTS' and r.resource == 'aws_s3_bucket.unknown_simple' for r in report.passed_checks))

def test_unrendered_nested_var(self):
resources_dir = os.path.join(
os.path.dirname(os.path.realpath(__file__)), "resources", "unrendered_vars")
file_to_scan = os.path.join(resources_dir, "nested.tf")
checks = ['COMPONENT_EQUALS', 'COMPONENT_EXISTS']

runner = Runner()
runner_filter = RunnerFilter(framework=['terraform'], checks=checks)
report = runner.run(root_folder=None, files=[file_to_scan], external_checks_dir=[resources_dir], runner_filter=runner_filter)

# plus 3 unknown
self.assertEqual(len(report.passed_checks), 5)
self.assertEqual(len(report.failed_checks), 2)

self.assertTrue(any(r.check_id == 'COMPONENT_EXISTS' and r.resource == 'aws_s3_bucket.unknown_nested_2_pass' for r in report.passed_checks))

self.assertTrue(any(r.check_id == 'COMPONENT_EXISTS' and r.resource == 'aws_s3_bucket.known_nested_pass' for r in report.passed_checks))
self.assertTrue(any(r.check_id == 'COMPONENT_EQUALS' and r.resource == 'aws_s3_bucket.known_nested_pass' for r in report.passed_checks))

self.assertTrue(any(r.check_id == 'COMPONENT_EXISTS' and r.resource == 'aws_s3_bucket.known_nested_2_pass' for r in report.passed_checks))
self.assertTrue(any(r.check_id == 'COMPONENT_EQUALS' and r.resource == 'aws_s3_bucket.known_nested_2_pass' for r in report.passed_checks))

self.assertTrue(any(r.check_id == 'COMPONENT_EXISTS' and r.resource == 'aws_s3_bucket.known_nested_fail' for r in report.failed_checks))
self.assertTrue(any(r.check_id == 'COMPONENT_EQUALS' and r.resource == 'aws_s3_bucket.known_nested_fail' for r in report.failed_checks))

def test_no_duplicate_results(self):
resources_path = os.path.join(
os.path.dirname(os.path.realpath(__file__)), "resources", "duplicate_violations")
Expand Down

0 comments on commit 2003ad7

Please sign in to comment.