Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(terraform): support cross-modules edges #3909

Merged
merged 5 commits into from
Nov 22, 2022
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
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))
tronxd marked this conversation as resolved.
Show resolved Hide resolved
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
}