Skip to content

Commit

Permalink
feat(terraform): support cross-modules edges (#3909)
Browse files Browse the repository at this point in the history
* support cross-modules edges

* fix getting the source modules

* fix test

* add UT
  • Loading branch information
YaaraVerner committed Nov 22, 2022
1 parent f61ced9 commit 59692f6
Show file tree
Hide file tree
Showing 11 changed files with 119 additions and 10 deletions.
35 changes: 26 additions & 9 deletions checkov/terraform/graph_builder/local_graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,9 @@ def _build_edges(self) -> None:
for origin_node_index, vertex in enumerate(self.vertices):
self._build_edges_for_vertex(origin_node_index, vertex, aliases, resources_types)

def _build_edges_for_vertex(self, origin_node_index: int, vertex: TerraformBlock, aliases: Dict[str, Dict[str, BlockType]], resources_types: List[str], cross_variable_edges: bool = False):
def _build_edges_for_vertex(self, origin_node_index: int, vertex: TerraformBlock, aliases: Dict[str, Dict[str, BlockType]], resources_types: List[str], cross_variable_edges: bool = False, referenced_module: Optional[Dict[str, Any]] = None):
referenced_module_idx = referenced_module.get("idx") if referenced_module else None
referenced_module_path = referenced_module.get("path") if referenced_module else None
for attribute_key, attribute_value in vertex.attributes.items():
if attribute_key in reserved_attribute_names or attribute_has_nested_attributes(
attribute_key, vertex.attributes
Expand All @@ -198,7 +200,12 @@ def _build_edges_for_vertex(self, origin_node_index: int, vertex: TerraformBlock
sub_values = [remove_index_pattern_from_str(sub_value) for sub_value in vertex_reference.sub_parts]
for i in range(len(sub_values)):
reference_name = join_trimmed_strings(char_to_join=".", str_lst=sub_values, num_to_trim=i)
if vertex.module_dependency:
if referenced_module is not None:
dest_node_index = self._find_vertex_index_relative_to_path(
vertex_reference.block_type, reference_name, referenced_module_path, vertex.module_dependency,
vertex.module_dependency_num, referenced_module_idx
)
elif vertex.module_dependency:
dest_node_index = self._find_vertex_index_relative_to_path(
vertex_reference.block_type, reference_name, vertex.path, vertex.module_dependency,
vertex.module_dependency_num
Expand Down Expand Up @@ -270,6 +277,9 @@ def _build_cross_variable_edges(self):
for origin_node_index in target_nodes_indexes:
vertex = self.vertices[origin_node_index]
self._build_edges_for_vertex(origin_node_index, vertex, aliases, resources_types, True)
modules = vertex.breadcrumbs.get(CustomAttributes.SOURCE_MODULE, [])
for module in modules:
self._build_edges_for_vertex(origin_node_index, vertex, aliases, resources_types, True, module)

def _create_edge(self, origin_vertex_index: int, dest_vertex_index: int, label: str,
cross_variable_edges: bool = False) -> bool:
Expand Down Expand Up @@ -352,10 +362,14 @@ def _get_dest_module_path(self, curr_module_dir: str, dest_module_source: str, d
return os.path.realpath(dest_module_path)

def _find_vertex_index_relative_to_path(
self, block_type: BlockType, name: str, block_path: str, module_path: str, module_num: str
self, block_type: BlockType, name: str, block_path: str, module_path: str, module_num: str, relative_module_idx: Optional[int] = None
) -> int:
relative_vertices = []
possible_vertices = self.vertices_by_module_dependency_by_name.get((module_path, module_num), {}).get(block_type, {}).get(name, [])
if relative_module_idx is not None:
module_dependency_by_name_key = next(k for k, v in self.vertices_by_module_dependency.items() if v.get(BlockType.MODULE, []).__contains__(relative_module_idx))
else:
module_dependency_by_name_key = (module_path, module_num)
possible_vertices = self.vertices_by_module_dependency_by_name.get(module_dependency_by_name_key, {}).get(block_type, {}).get(name, [])
for vertex_index in possible_vertices:
vertex = self.vertices[vertex_index]
if self.get_dirname(vertex.path) == self.get_dirname(block_path):
Expand Down Expand Up @@ -466,11 +480,14 @@ def update_vertices_breadcrumbs_and_module_connections(self) -> None:
hash_breadcrumbs.append(breadcrumb)
vertex.breadcrumbs[attribute_key] = hash_breadcrumbs
if len(vertex.source_module) == 1:
m = self.vertices[list(vertex.source_module)[0]]
source_module_data = [m.get_export_data()]
while len(m.source_module) == 1:
m = self.vertices[list(m.source_module)[0]]
source_module_data.append(m.get_export_data())
v = vertex
source_module_data = []
while len(v.source_module) == 1:
idx = list(v.source_module)[0]
v = self.vertices[idx]
module_data = v.get_export_data()
module_data["idx"] = idx
source_module_data.append(module_data)
source_module_data.reverse()
vertex.breadcrumbs[CustomAttributes.SOURCE_MODULE] = source_module_data

Expand Down
32 changes: 31 additions & 1 deletion tests/terraform/graph/graph_builder/test_graph_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,36 @@ def test_build_graph_with_cross_variables_connections_from_module(self):
self.check_edge(local_graph, node_from=var_bucket_resource, node_to=bucket_resource,
expected_label="[cross-variable] bucket")

@mock.patch.dict(os.environ, {"CHECKOV_EXPERIMENTAL_CROSS_VARIABLE_EDGES": "True"})
def test_build_graph_with_cross_modules_connections(self):
resources_dir = os.path.realpath(os.path.join(TEST_DIRNAME, '../resources/cross_modules'))

graph_manager = TerraformGraphManager(NetworkxConnector())
local_graph, _ = graph_manager.build_graph_from_source_directory(resources_dir, render_variables=True)

var_bucket_resource = self.get_vertex_by_name_and_type(local_graph, BlockType.RESOURCE,
'aws_s3_bucket_public_access_block.var_bucket')
bucket_resource = self.get_vertex_by_name_and_type(local_graph, BlockType.RESOURCE, 'aws_s3_bucket.example')

self.assertEqual(len(local_graph.edges), 5)
self.check_edge(local_graph, node_from=var_bucket_resource, node_to=bucket_resource,
expected_label="[cross-variable] bucket")

@mock.patch.dict(os.environ, {"CHECKOV_EXPERIMENTAL_CROSS_VARIABLE_EDGES": "True"})
def test_build_graph_with_cross_nested_modules_connections(self):
resources_dir = os.path.realpath(os.path.join(TEST_DIRNAME, '../resources/cross_modules2'))

graph_manager = TerraformGraphManager(NetworkxConnector())
local_graph, _ = graph_manager.build_graph_from_source_directory(resources_dir, render_variables=True)

var_bucket_resource = self.get_vertex_by_name_and_type(local_graph, BlockType.RESOURCE,
'aws_s3_bucket_public_access_block.var_bucket')
bucket_resource = self.get_vertex_by_name_and_type(local_graph, BlockType.RESOURCE, 'aws_s3_bucket.example')

self.assertEqual(len(local_graph.edges), 8)
self.check_edge(local_graph, node_from=var_bucket_resource, node_to=bucket_resource,
expected_label="[cross-variable] bucket")

def test_nested_modules_address_attribute(self):
resources_dir = os.path.realpath(os.path.join(TEST_DIRNAME, '../resources/nested_modules_address'))
graph_manager = TerraformGraphManager(NetworkxConnector())
Expand All @@ -288,4 +318,4 @@ def test_nested_modules_address_attribute(self):
resource_1 = self.get_vertex_by_name_and_type(local_graph, BlockType.RESOURCE, 'aws_s3_bucket_public_access_block.var_bucket')
assert resource_1.attributes.get(CustomAttributes.TF_RESOURCE_ADDRESS) == 'module.s3_module.module.inner_s3_module.aws_s3_bucket_public_access_block.var_bucket'
resource_2 = self.get_vertex_by_name_and_type(local_graph, BlockType.RESOURCE, 'aws_s3_bucket.example')
assert resource_2.attributes.get(CustomAttributes.TF_RESOURCE_ADDRESS) == 'aws_s3_bucket.example'
assert resource_2.attributes.get(CustomAttributes.TF_RESOURCE_ADDRESS) == 'aws_s3_bucket.example'
7 changes: 7 additions & 0 deletions tests/terraform/graph/graph_builder/test_local_graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -212,16 +212,19 @@ def test_vertices_from_local_graph_module(self):
"type": "module",
"name": "sub-module",
"path": str(parent_dir / "resources/modules/stacks/prod/main.tf"),
"idx": 0
},
{
"type": "module",
"name": "s3",
"path": str(parent_dir / "resources/modules/stacks/prod/sub-prod/main.tf"),
"idx": 3
},
{
"type": "module",
"name": "inner_module_call",
"path": str(parent_dir / "resources/modules/s3_inner_modules/main.tf"),
"idx": 4
},
],
},
Expand Down Expand Up @@ -249,11 +252,13 @@ def test_vertices_from_local_graph_module(self):
"type": "module",
"name": "s3",
"path": str(parent_dir / "resources/modules/stacks/stage/main.tf"),
"idx": 1
},
{
"type": "module",
"name": "inner_module_call",
"path": str(parent_dir / "resources/modules/s3_inner_modules/main.tf"),
"idx": 5
},
],
},
Expand Down Expand Up @@ -281,11 +286,13 @@ def test_vertices_from_local_graph_module(self):
"type": "module",
"name": "s3",
"path": str(parent_dir / "resources/modules/stacks/test/main.tf"),
"idx": 2
},
{
"type": "module",
"name": "inner_module_call",
"path": str(parent_dir / "resources/modules/s3_inner_modules/main.tf"),
"idx": 6
},
],
},
Expand Down
8 changes: 8 additions & 0 deletions tests/terraform/graph/resources/cross_modules/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
module "test" {
source = "./module"
bucket = aws_s3_bucket.example.id
}

resource "aws_s3_bucket" "example" {
bucket = "example"
}
11 changes: 11 additions & 0 deletions tests/terraform/graph/resources/cross_modules/module/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
locals {
bucket = var.bucket
}

resource "aws_s3_bucket_public_access_block" "var_bucket" {
bucket = local.bucket
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
variable "bucket" {
type = string
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
locals {
bucket = var.bucket
}

resource "aws_s3_bucket_public_access_block" "var_bucket" {
bucket = local.bucket
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
variable "bucket" {
type = string
}
8 changes: 8 additions & 0 deletions tests/terraform/graph/resources/cross_modules2/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
module "test" {
source = "./module"
bucket = aws_s3_bucket.example.id
}

resource "aws_s3_bucket" "example" {
bucket = "example"
}
8 changes: 8 additions & 0 deletions tests/terraform/graph/resources/cross_modules2/module/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
locals {
bucket = var.bucket
}

module "inner_module" {
source = "../inner_module"
bucket = local.bucket
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
variable "bucket" {
type = string
}

0 comments on commit 59692f6

Please sign in to comment.