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:
I tried exactly your code and there was no issue on my side, my checkov version is a bit higher than yours, but I don't think that could be the problem

```shell

       _               _              
   ___| |__   ___  ___| | _______   __
  / __| '_ \ / _ \/ __| |/ / _ \ \ / /
 | (__| | | |  __/ (__|   < (_) \ V / 
  \___|_| |_|\___|\___|_|\_\___/ \_/  
                                      
By bridgecrew.io | version: 2.0.87 

terraform scan results:

Passed checks: 0, Failed checks: 0, Skipped checks: 1

Check: CKV_AWS_130: "Ensure VPC subnets do not assign public IP by default"
        SKIPPED for resource: aws_subnet.pub_sub
        Suppress comment: Public IP required in public subnet
        File: /main.tf:5-14

```
Where could I be possibly going wrong then? Anything from the screenshot that can help you identify the issue or is there anything I can provide you for help because I'm using other skip statements too in the same main.tf file and they all work. This is the only check that is failing.

<img width="643" alt="Screenshot 2021-04-29 at 9 59 28 PM" src="https://user-images.githubusercontent.com/34170345/116585589-2dffd080-a936-11eb-9136-8bd0db7cd8be.png">

One thing I noticed while running the scan in DEBUG mode is that the evaluation key is present for the public IP check for public subnet resource whereas there isn't any evaluation key for S3 MFA delete or any other checks I'm skipping.
<img width="1420" alt="Screenshot 2021-04-29 at 10 18 18 PM" src="https://user-images.githubusercontent.com/34170345/116588622-67860b00-a939-11eb-857d-3ad8925f37f8.png">

<img width="1413" alt="Screenshot 2021-04-29 at 10 18 44 PM" src="https://user-images.githubusercontent.com/34170345/116588637-69e86500-a939-11eb-952d-6ffe847b0b28.png">

@paliwalvimal Hey!
looking at your output - is it just me or are the line numbers off?
That might affect the supression
Sorry, @nimrodkor. I didn't get your point.
@paliwalvimal 

I think the problem is that you are not using quoation marks. This is unclear in the docs, so I've updated it to specify that quotation marks are needed. Try:
`#checkov:skip=CKV_AWS_130:"Public IP required in public subnet"`

I don't see this issue with 2.0.344:

```
resource "aws_subnet" "pub_sub" {
	# checkov:skip=CKV_AWS_130: no reason
    count = length(var.azs)
    vpc_id= aws_vpc.vpc.vpc_id
    map_public_ip_on_launch=true
    cidr_block = cidrsubnet(
        var.cidr_block,
        var.pub_subnet_mask - local.vpc_mask,
        count.index
    )
    availability_zone=element(var.azs, count.index)
}
```

Is suppressed just fine.

```
> checkov -f subnet.tf

       _               _              
   ___| |__   ___  ___| | _______   __
  / __| '_ \ / _ \/ __| |/ / _ \ \ / /
 | (__| | | |  __/ (__|   < (_) \ V / 
  \___|_| |_|\___|\___|_|\_\___/ \_/  
                                      
By bridgecrew.io | version: 2.0.344 

terraform scan results:

Passed checks: 0, Failed checks: 0, Skipped checks: 1

Check: CKV_AWS_130: "Ensure VPC subnets do not assign public IP by default"
        SKIPPED for resource: aws_subnet.pub_sub
        Suppress comment:  no reason
        File: /subnet.tf:1-12
```

@paliwalvimal can you verify with the latest checkov pls.
@JamesWoolfenden weird. it still fails for me even on the latest version

<img width="817" alt="Screenshot 2021-09-07 at 5 36 52 PM" src="https://user-images.githubusercontent.com/34170345/132341818-6ec94174-606d-402b-8d29-30dc2d0099d7.png">
<img width="453" alt="Screenshot 2021-09-07 at 5 37 27 PM" src="https://user-images.githubusercontent.com/34170345/132341873-edbd00a3-6217-4bc9-bf7e-60e8c6e0910a.png">
<img width="778" alt="Screenshot 2021-09-07 at 5 37 51 PM" src="https://user-images.githubusercontent.com/34170345/132341930-7548e287-75ca-404c-b110-deb7b87ca4aa.png">

@paliwalvimal  if you post your code rather than an image, i could verify?
@JamesWoolfenden sure
```
resource "aws_subnet" "pub_sub" {
  # checkov:skip=CKV_AWS_130: Public IP required in public subnet
  count                   = length(var.azs)
  vpc_id                  = aws_vpc.vpc.id
  map_public_ip_on_launch = var.map_public_ip_for_public_subnet
  cidr_block = cidrsubnet(
    var.cidr_block,
    var.pub_subnet_mask - local.vpc_mask,
    count.index,
  )
  availability_zone = element(var.azs, count.index)

  tags = merge({
    Name = "${var.network_name}-pub-sub-${element(var.azs, count.index)}"
    Tier = "public"
  }, var.tags, var.add_eks_tags ? { "kubernetes.io/role/elb" : "1" } : {})
}
```

Resolving patch:
diff --git a/checkov/common/bridgecrew/platform_integration.py b/checkov/common/bridgecrew/platform_integration.py
--- a/checkov/common/bridgecrew/platform_integration.py
+++ b/checkov/common/bridgecrew/platform_integration.py
@@ -7,7 +7,7 @@
 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 @@ def get_guidelines(self) -> dict:
             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
--- 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 @@ def _trim_whitespaces_linebreaks(text: str) -> str:
     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 @@ def _collect_skip_comments(self, definition_blocks: List[Dict[str, Any]]) -> Dic
         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 @@ def _collect_skip_comments(self, definition_blocks: List[Dict[str, Any]]) -> Dic
                     "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 @@ def _collect_skip_comments(self, definition_blocks: List[Dict[str, Any]]) -> Dic
         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 @@ def get_block_type(self) -> str:
         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 @@ def enrich_definition_block(self, definition_blocks: List[Dict[str, Any]]) -> Di
                     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
--- a/tests/terraform/context_parsers/test_base_parser.py
+++ b/tests/terraform/context_parsers/test_base_parser.py
@@ -4,28 +4,41 @@
 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()