Skip to content

Commit 3466ef5

Browse files
authored
fix(terraform_plan): Correctly handle complex types for after_unknown (#7333)
* Correctly handle complex types for after_unknown * Fixed typo
1 parent 35d4f4d commit 3466ef5

File tree

2 files changed

+68
-11
lines changed

2 files changed

+68
-11
lines changed

checkov/terraform/plan_parser.py

Lines changed: 49 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -265,20 +265,60 @@ def _handle_complex_after_unknown(k: str, resource_conf: dict[str, Any], v: Any)
265265
if inner_key not in resource_conf_value and isinstance(resource_conf_value, list):
266266
for i in range(len(resource_conf_value)):
267267
if isinstance(resource_conf_value[i], dict):
268-
if _validate_after_unknown_list_not_empty(inner_key, k, resource_conf_value[i]):
269-
resource_conf_value[i][inner_key] = _clean_simple_type_list([TRUE_AFTER_UNKNOWN])
268+
_update_after_unknown_in_complex_types(inner_key, resource_conf_value[i])
270269
elif isinstance(resource_conf_value[i], list) and isinstance(resource_conf_value[i][0], dict):
271-
if _validate_after_unknown_list_not_empty(inner_key, k, resource_conf_value[i][0]):
272-
resource_conf_value[i][0][inner_key] = _clean_simple_type_list([TRUE_AFTER_UNKNOWN])
270+
_update_after_unknown_in_complex_types(inner_key, resource_conf_value[i][0])
273271

274272

275-
def _validate_after_unknown_list_not_empty(inner_key: str, k: str, value: dict[str, Any]) -> bool:
273+
def _update_after_unknown_in_complex_types(inner_key: str, value: dict[str, Any]) -> None:
276274
"""
277-
If the inner key is a list - we want to check it's not empty, if not we handle it in the original way.
275+
Based on terraform docs, in complex types like list/dict some values might be known while others are not.
276+
So when trying to update the info shared from the `after_unknown`, we only want to update the specific items in
277+
those objects which are unknown.
278+
For example, in the conf:
279+
```
280+
"after": {
281+
"outer": [
282+
{"tag1": 1}
283+
]
284+
},
285+
"after_unknown": {
286+
"outer": [
287+
{}, -> the value is known from the "after" section, we don't want to touch it
288+
true -> the value is unknown, we want to replace it with `TRUE_AFTER_UNKNOWN`
289+
]
290+
}.
291+
292+
Full result for resource conf:
293+
```
294+
"outer": [{"tag1": 1}, `TRUE_AFTER_UNKNOWN`]
295+
```
296+
```
278297
"""
279-
return ((inner_key not in value) or
280-
(inner_key in value and not isinstance(value[inner_key], list)) or
281-
(isinstance(value[inner_key], list) and value[inner_key] != [] and value[inner_key][0] != []))
298+
if inner_key not in value:
299+
value[inner_key] = _clean_simple_type_list([TRUE_AFTER_UNKNOWN])
300+
return
301+
inner_value = value[inner_key]
302+
if isinstance(inner_value, str) and inner_value.lower() == "true":
303+
value[inner_key] = _clean_simple_type_list([TRUE_AFTER_UNKNOWN])
304+
if isinstance(inner_value, list):
305+
for i, v in enumerate(inner_value):
306+
if isinstance(v, str) and v.lower() == "true":
307+
inner_value[i] = _clean_simple_type_list([TRUE_AFTER_UNKNOWN])
308+
if isinstance(v, dict):
309+
_handle_after_unknown_dict(v)
310+
if isinstance(inner_value, dict):
311+
for k, v in inner_value.items():
312+
if isinstance(v, str) and v.lower() == "true":
313+
inner_value[k] = _clean_simple_type_list([TRUE_AFTER_UNKNOWN])
314+
if isinstance(v, dict):
315+
_handle_after_unknown_dict(v)
316+
return
317+
318+
319+
def _handle_after_unknown_dict(v: dict[str, Any]) -> None:
320+
for k in v.keys():
321+
_update_after_unknown_in_complex_types(k, v)
282322

283323

284324
def _find_child_modules(

tests/terraform/parser/test_plan_parser.py

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
from checkov.common.util.consts import TRUE_AFTER_UNKNOWN
1212
from checkov.terraform.plan_parser import parse_tf_plan, _sanitize_count_from_name, _handle_complex_after_unknown, \
13-
_validate_after_unknown_list_not_empty
13+
_update_after_unknown_in_complex_types
1414
from checkov.common.parsers.node import StrNode
1515

1616

@@ -148,7 +148,7 @@ def test_handle_complex_after_unknown(self):
148148
}
149149
]
150150
_handle_complex_after_unknown(key, resource, value)
151-
assert resource == {'tags': [[{'custom_tags': ['true_after_unknown']}]]}
151+
assert resource["tags"] == [value]
152152

153153
def test_handle_complex_after_unknown_with_empty_list(self):
154154
resource = {"network_configuration": [
@@ -162,6 +162,23 @@ def test_handle_complex_after_unknown_with_empty_list(self):
162162
_handle_complex_after_unknown(key, resource, value)
163163
assert resource == {'network_configuration': [{"endpoint_configuration": []}]}
164164

165+
def test_handle_complex_after_unknown_with_some_known_values(self):
166+
original_resource = {
167+
"tags": [
168+
{"tag1": "my_tag"},
169+
{"tag2": "true"},
170+
]
171+
}
172+
_update_after_unknown_in_complex_types("tags", original_resource)
173+
assert original_resource == {
174+
"tags": [
175+
{"tag1": "my_tag"},
176+
{"tag2": ["true_after_unknown"]},
177+
]
178+
}
179+
180+
181+
165182
@pytest.mark.parametrize("inner_key, k, is_inner_list", [
166183
("endpoint_configuration", "network_configuration", False),
167184
("endpoint_configuration", "network_configuration", True)

0 commit comments

Comments
 (0)