diff --git a/checkov/terraform/plan_parser.py b/checkov/terraform/plan_parser.py index 041b442cb5f..d37065cc6d4 100644 --- a/checkov/terraform/plan_parser.py +++ b/checkov/terraform/plan_parser.py @@ -234,6 +234,31 @@ def _eval_after_unknown(changes: dict[str, Any], resource_conf: dict[str, Any]) # In these cases, policies checking the existence of a value will succeed, # but policies checking for concrete values will fail resource_conf[k] = _clean_simple_type_list([TRUE_AFTER_UNKNOWN]) + elif isinstance(v, list) and len(v) == 1 and isinstance(v[0], dict): + _handle_complex_after_unknown(k, resource_conf, v) + + +def _handle_complex_after_unknown(k: str, resource_conf: dict[str, Any], v: Any) -> None: + """ + Handles a case of an inner key generated with "after_unknown" value. + Example: + ` + after_unknown: { + "logging_config": [ + { + "bucket": true + } + ], + } + ` + """ + inner_keys = list(v[0].keys()) + for inner_key in inner_keys: + if inner_key in (START_LINE, END_LINE): + # skip inner checkov keys + continue + if inner_key not in resource_conf[k]: + resource_conf[k][0][inner_key] = _clean_simple_type_list([TRUE_AFTER_UNKNOWN]) def _find_child_modules( diff --git a/tests/terraform/parser/resources/plan_after_unknown/tfplan.json b/tests/terraform/parser/resources/plan_after_unknown/tfplan.json new file mode 100644 index 00000000000..92851fa94c9 --- /dev/null +++ b/tests/terraform/parser/resources/plan_after_unknown/tfplan.json @@ -0,0 +1,49 @@ +{ + "format_version": "1.0", + "terraform_version": "1.1.3", + "planned_values": { + "root_module": { + "resources": [ + { + "address": "aws_cloudfront_distribution.cloudfront", + "mode": "managed", + "type": "aws_cloudfront_distribution", + "name": "cloudfront", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 1, + "values": { + "logging_config": [ + { + "include_cookies": false, + "prefix": "cloudfront" + } + ] + } + } + ] + } + }, + "resource_changes": [ + { + "address": "aws_cloudfront_distribution.cloudfront", + "mode": "managed", + "type": "aws_cloudfront_distribution", + "name": "cloudfront", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": null, + "after_unknown": { + "logging_config": [ + { + "bucket": true + } + ] + } + } + } + ] +} diff --git a/tests/terraform/parser/test_plan_parser.py b/tests/terraform/parser/test_plan_parser.py index ad7e343c2c0..be193d981dc 100644 --- a/tests/terraform/parser/test_plan_parser.py +++ b/tests/terraform/parser/test_plan_parser.py @@ -1,9 +1,11 @@ import os import unittest from pathlib import Path +from unittest import mock from pytest_mock import MockerFixture +from checkov.common.util.consts import TRUE_AFTER_UNKNOWN from checkov.terraform.plan_parser import parse_tf_plan from checkov.common.parsers.node import StrNode @@ -80,6 +82,16 @@ def test_module_with_connected_resources(self): resource_attributes = next(iter(resource_definition.values())) self.assertTrue(resource_attributes['references_']) + @mock.patch.dict(os.environ, {"EVAL_TF_PLAN_AFTER_UNKNOWN": "True"}) + def test_after_unknown_handling(self): + current_dir = os.path.dirname(os.path.realpath(__file__)) + valid_plan_path = current_dir + "/resources/plan_after_unknown/tfplan.json" + tf_definition, _ = parse_tf_plan(valid_plan_path, {}) + file_resource_definition = tf_definition['resource'][0] + resource_definition = next(iter(file_resource_definition.values())) + resource_attributes = next(iter(resource_definition.values())) + self.assertEqual(resource_attributes['logging_config'][0]["bucket"], [TRUE_AFTER_UNKNOWN]) + def test_large_file(mocker: MockerFixture): # given test_file = Path(__file__).parent / "resources/plan_encodings/tfplan_mac_utf8.json"