Repository: bridgecrewio/checkov

Issue:
Unable to skip CKV_AWS_130
**Describe the bug**
I have a two-tier network infrastructure template written in terraform which basically sets up a VPC network. In there, I have a public subnet block which has parameter `map_public_ip_on_launch` set to **true**. Even after adding `#checkov:skip=CKV_AWS_130:Public IP required in public subnet` within the resource check still fails. I do have other check skips but just the `CKV_AWS_130` is failing

**To Reproduce**
Steps to reproduce the behavior:
1. Create an aws subnet resource block
2. Add the skip statement
3. Test it and see the error

**Expected behavior**
It should be shown as skipped check

**Screenshots**
<img width="723" alt="Screenshot 2021-04-29 at 9 08 22 PM" src="https://user-images.githubusercontent.com/34170345/116578775-60f29600-a92f-11eb-83cb-aedc04daf72c.png">

<img width="610" alt="Screenshot 2021-04-29 at 9 08 34 PM" src="https://user-images.githubusercontent.com/34170345/116578819-69e36780-a92f-11eb-8868-213a2e183b45.png">

**Desktop (please complete the following information):**
 - OS: MacOS Big Sur
 - Checkov Version: 2.0.84

**Additional context**
Add any other context about the problem here (e.g. code snippets).


Discussion:

Patch:
diff --git a/checkov/common/bridgecrew/platform_integration.py b/checkov/common/bridgecrew/platform_integration.py
index 66e6f4508..c036a1942 100644
--- a/checkov/common/bridgecrew/platform_integration.py
+++ b/checkov/common/bridgecrew/platform_integration.py
@@ -7,7 +7,7 @@ from concurrent import futures
 from json import JSONDecodeError
 from os import path
 from time import sleep
-from typing import Optional
+from typing import Optional, Dict
 
 import boto3
 import dpath.util
@@ -346,17 +346,17 @@ class BcPlatformIntegration(object):
             self.get_checkov_mapping_metadata()
         return self.guidelines
 
-    def get_id_mapping(self) -> dict:
+    def get_id_mapping(self) -> Dict[str, str]:
         if not self.bc_id_mapping:
             self.get_checkov_mapping_metadata()
         return self.bc_id_mapping
 
-    def get_ckv_to_bc_id_mapping(self) -> dict:
+    def get_ckv_to_bc_id_mapping(self) -> Dict[str, str]:
         if not self.ckv_to_bc_id_mapping:
             self.get_checkov_mapping_metadata()
         return self.ckv_to_bc_id_mapping
 
-    def get_checkov_mapping_metadata(self) -> Optional[dict]:
+    def get_checkov_mapping_metadata(self) -> None:
         if self.bc_skip_mapping is True:
             logging.debug("Skipped mapping API call")
             self.ckv_to_bc_id_mapping = {}
diff --git a/checkov/terraform/context_parsers/base_parser.py b/checkov/terraform/context_parsers/base_parser.py
index e7f2af088..bfa36283a 100644
--- a/checkov/terraform/context_parsers/base_parser.py
+++ b/checkov/terraform/context_parsers/base_parser.py
@@ -22,6 +22,7 @@ class BaseContextParser(ABC):
     tf_file_path: Optional[Path] = None
     file_lines: List[Tuple[int, str]] = []
     filtered_lines: List[Tuple[int, str]] = []
+    filtered_line_numbers: List[int] = []
     context: Dict[str, Any] = {}
 
     def __init__(self, definition_type: str) -> None:
@@ -60,18 +61,19 @@ class BaseContextParser(ABC):
     def _filter_file_lines(self) -> List[Tuple[int, str]]:
         parsed_file_lines = [(ind, self._trim_whitespaces_linebreaks(line)) for (ind, line) in self.file_lines]
         self.filtered_lines = [(ind, line) for (ind, line) in parsed_file_lines if line]
+        self.filtered_line_numbers = [ind for ind, _ in self.filtered_lines]
         return self.filtered_lines
 
     def _read_file_lines(self) -> List[Tuple[int, str]]:
-        with (open(self.tf_file, "r")) as file:
+        with open(self.tf_file, "r") as file:
             file.seek(0)
-            file_lines = [(ind + 1, line) for (ind, line) in list(enumerate(file.readlines()))]
+            file_lines = [(ind + 1, line) for ind, line in enumerate(file.readlines())]
             return file_lines
 
     @staticmethod
     def is_optional_comment_line(line: str) -> bool:
-        line_without_whitespace = "".join(line.split())
-        return 'checkov:skip=' in line_without_whitespace or 'bridgecrew:skip=' in line_without_whitespace
+        line_without_whitespace = line.replace(" ", "")
+        return "checkov:skip=" in line_without_whitespace or "bridgecrew:skip=" in line_without_whitespace
 
     def _collect_skip_comments(self, definition_blocks: List[Dict[str, Any]]) -> Dict[str, Any]:
         """
@@ -82,10 +84,6 @@ class BaseContextParser(ABC):
         bc_id_mapping = bc_integration.get_id_mapping()
         ckv_to_bc_id_mapping = bc_integration.get_ckv_to_bc_id_mapping()
         parsed_file_lines = self.filtered_lines
-        optional_comment_lines = [
-            line for line in parsed_file_lines
-            if self.is_optional_comment_line(line[1])
-        ]
         comments = [
             (
                 line_num,
@@ -94,7 +92,8 @@ class BaseContextParser(ABC):
                     "suppress_comment": match.group(3)[1:] if match.group(3) else "No comment provided",
                 },
             )
-            for (line_num, x) in optional_comment_lines
+            for (line_num, x) in parsed_file_lines
+            if self.is_optional_comment_line(x)
             for match in [re.search(COMMENT_REGEX, x)]
             if match
         ]
@@ -125,26 +124,26 @@ class BaseContextParser(ABC):
         return self.context
 
     def _compute_definition_end_line(self, start_line_num: int) -> int:
-        """ Given the code block's start line, compute the block's end line
+        """Given the code block's start line, compute the block's end line
         :param start_line_num: code block's first line number (the signature line)
         :return: the code block's last line number
         """
         parsed_file_lines = self.filtered_lines
-        start_line_idx = [line_num for (line_num, _) in parsed_file_lines].index(start_line_num)
+        start_line_idx = self.filtered_line_numbers.index(start_line_num)
         i = 0
         end_line_num = 0
         for (line_num, line) in islice(parsed_file_lines, start_line_idx, None):
             if OPEN_CURLY in line:
-                i = i + 1
+                i += line.count(OPEN_CURLY)
             if CLOSE_CURLY in line:
-                i = i - 1
+                i -= line.count(CLOSE_CURLY)
                 if i == 0:
                     end_line_num = line_num
                     break
         return end_line_num
 
     def run(
-            self, tf_file: str, definition_blocks: List[Dict[str, Any]], collect_skip_comments: bool = True
+        self, tf_file: str, definition_blocks: List[Dict[str, Any]], collect_skip_comments: bool = True
     ) -> Dict[str, Any]:
         # TF files for loaded modules have this formation:  <file>[<referrer>#<index>]
         # Chop off everything after the file name for our purposes here
@@ -165,10 +164,10 @@ class BaseContextParser(ABC):
         return self.definition_type
 
     @staticmethod
-    def _clean_line(line: str):
-        res = line.replace('"', ' ')
+    def _clean_line(line: str) -> str:
+        res = line.replace('"', " ")
         if '"{' in res:
-            res = res.split('{')[0]
+            res = res.split("{")[0]
         return res
 
     def enrich_definition_block(self, definition_blocks: List[Dict[str, Any]]) -> Dict[str, Any]:
@@ -193,7 +192,7 @@ class BaseContextParser(ABC):
                     dpath.new(self.context, entity_context_path + ["start_line"], start_line)
                     dpath.new(self.context, entity_context_path + ["end_line"], end_line)
                     dpath.new(
-                        self.context, entity_context_path + ["code_lines"], self.file_lines[start_line - 1: end_line]
+                        self.context, entity_context_path + ["code_lines"], self.file_lines[start_line - 1 : end_line]
                     )
                     potential_block_start_lines.remove((line_num, line))
                     break


Test patch:
diff --git a/tests/terraform/context_parsers/test_base_parser.py b/tests/terraform/context_parsers/test_base_parser.py
index bc3f56d5d..00b715cfc 100644
--- a/tests/terraform/context_parsers/test_base_parser.py
+++ b/tests/terraform/context_parsers/test_base_parser.py
@@ -4,28 +4,41 @@ import unittest
 from checkov.terraform.context_parsers.registry import parser_registry
 from tests.terraform.context_parsers.mock_context_parser import MockContextParser
 
-mock_tf_file = os.path.dirname(os.path.realpath(__file__)) + '/mock_tf_files/mock.tf'
-mock_definition = (mock_tf_file, {'mock': [
-    {
-        'mock_type': {
-            'mock_name': {
-                'value': [
-                    'mock_value']}}}
-]})
+mock_tf_file = os.path.dirname(os.path.realpath(__file__)) + "/mock_tf_files/mock.tf"
+mock_definition = (mock_tf_file, {"mock": [{"mock_type": {"mock_name": {"value": ["mock_value"]}}}]})
 
 
 class TestBaseParser(unittest.TestCase):
-
     def test_enrich_definition_block(self):
         mock_parser = MockContextParser()
         parser_registry.register(mock_parser)
         definition_context = parser_registry.enrich_definitions_context(mock_definition)
-        skipped_checks = definition_context[mock_tf_file]['mock']['mock_type']['mock_name'].get('skipped_checks')
+        skipped_checks = definition_context[mock_tf_file]["mock"]["mock_type"]["mock_name"].get("skipped_checks")
         self.assertIsNotNone(skipped_checks)
         self.assertEqual(len(skipped_checks), 3)
         # Ensure checkov IDs are mapped to BC IDs
-        self.assertEqual(skipped_checks[2]['id'], 'CKV_AWS_15')
+        self.assertEqual(skipped_checks[2]["id"], "CKV_AWS_15")
+
+    def test__compute_definition_end_line_with_multi_curly_brackets(self):
+        # given
+        mock_parser = MockContextParser()
+        mock_parser.filtered_lines = [
+            (1, '#data "aws_iam_policy_document" "null" {}'),
+            (3, 'resource "aws_subnet" "pub_sub" {'),
+            (4, "tags = merge({"),
+            (5, 'Name = "${var.network_name}-pub-sub-${element(var.azs, count.index)}"'),
+            (6, 'Tier = "public"'),
+            (7, '}, var.tags, var.add_eks_tags ? { "kubernetes.io/role/elb" : "1" } : {})'),
+            (8, "}"),
+        ]
+        mock_parser.filtered_line_numbers = [1, 3, 4, 5, 6, 7, 8]
+
+        # when
+        end_line_num = mock_parser._compute_definition_end_line(3)
+
+        # then
+        self.assertEqual(8, end_line_num)
 
 
-if __name__ == '__main__':
+if __name__ == "__main__":
     unittest.main()