Repository: bridgecrewio/checkov

Issue:
False negative for CKV_AWS_24 (unrestricted SSH ingress)
**Describe the bug**

The CKV_AWS_24 rule appears to have stopped working at some point.

**To Reproduce**
Run checkov — e.g. `docker run -it --rm -v $(pwd):/code bridgecrew/checkov -d /code` — using this Terraform file:

<details>

```hcl
resource "aws_security_group" "inline_rules" {
  description = "SG with inline rules"
  ingress = [
    {
      cidr_blocks      = ["0.0.0.0/0"]
      description      = "Wide Open"
      from_port        = 0
      ipv6_cidr_blocks = []
      prefix_list_ids  = []
      security_groups  = []
      protocol         = "-1"
      self             = false
      to_port          = 65535
    }
  ]
}

resource "aws_security_group" "separate_rules" {
  description = "SG with separate rules"
}

resource "aws_security_group_rule" "bad" {
  description       = "Allow inbound SSH from the world!"
  type              = "ingress"
  security_group_id = aws_security_group.separate_rules.id
  protocol          = "tcp"
  from_port         = 22
  to_port           = 22
  cidr_blocks       = ["0.0.0.0/0"]
}

resource "aws_security_group_rule" "worse" {
  description       = "Allow everything!"
  type              = "ingress"
  security_group_id = aws_security_group.separate_rules.id
  protocol          = "-1"
  from_port         = 0
  to_port           = 65535
  cidr_blocks       = ["0.0.0.0/0"]
}
```

</details>

**Expected behavior**

The security groups should be flagged

**Screenshots**
If applicable, add screenshots to help explain your problem.

**Desktop (please complete the following information):**
 - OS: MacOS using Docker.app
 - Checkov Version: version: 1.0.290 

```
Using default tag: latest
latest: Pulling from bridgecrew/checkov
Digest: sha256:8ad4123715f6c75df75810aecea0b861e02dbe37dee823f4d2514147d7dd64d7
Status: Image is up to date for bridgecrew/checkov:latest
docker.io/bridgecrew/checkov:latest
```

**Additional context**

<details>

```
docker run -it --rm -v (pwd):/code bridgecrew/checkov -d /code

       _               _              
   ___| |__   ___  ___| | _______   __
  / __| '_ \ / _ \/ __| |/ / _ \ \ / /
 | (__| | | |  __/ (__|   < (_) \ V / 
  \___|_| |_|\___|\___|_|\_\___/ \_/  
                                      
version: 1.0.290 

terraform scan results:

Passed checks: 8, Failed checks: 0, Skipped checks: 0

Check: CKV_AWS_25: "Ensure no security groups allow ingress from 0.0.0.0:0 to port 3389"
	PASSED for resource: aws_security_group.inline_rules
	File: /security-groups.tf:1-16


Check: CKV_AWS_23: "Ensure every security groups rule has a description"
	PASSED for resource: aws_security_group.inline_rules
	File: /security-groups.tf:1-16


Check: CKV_AWS_24: "Ensure no security groups allow ingress from 0.0.0.0:0 to port 22"
	PASSED for resource: aws_security_group.inline_rules
	File: /security-groups.tf:1-16


Check: CKV_AWS_25: "Ensure no security groups allow ingress from 0.0.0.0:0 to port 3389"
	PASSED for resource: aws_security_group.separate_rules
	File: /security-groups.tf:18-20


Check: CKV_AWS_23: "Ensure every security groups rule has a description"
	PASSED for resource: aws_security_group.separate_rules
	File: /security-groups.tf:18-20


Check: CKV_AWS_24: "Ensure no security groups allow ingress from 0.0.0.0:0 to port 22"
	PASSED for resource: aws_security_group.separate_rules
	File: /security-groups.tf:18-20


Check: CKV_AWS_23: "Ensure every security groups rule has a description"
	PASSED for resource: aws_security_group_rule.bad
	File: /security-groups.tf:22-30


Check: CKV_AWS_23: "Ensure every security groups rule has a description"
	PASSED for resource: aws_security_group_rule.worse
	File: /security-groups.tf:33-41
```
</details>


Discussion:
A quick pass through pdb revealed 3 problems:

1. The type check on https://github.com/bridgecrewio/checkov/blob/5658c3cb762ef6a0bf51903466f94e50754de019/checkov/terraform/checks/resource/aws/SecurityGroupUnrestrictedIngress22.py#L26 is expecting a dictionary but receives a list of dictionaries. It proceeds further if you do something hacky like `exec("rule = rule[0]")`.
2. The type check fails silently — it should probably either just assume a dictionary and fail with an exception if given something else or have an else path which raises a `TypeError`.
3. The port check similarly hits a changed type signature causing a silent failure: it's comparing an integer to a list containing an integer.
4. The port check only catches exact matches. It should be doing a numeric test to catch cases like from_port = 0 / to_port = 65535
Hi @acdha , Can you please provide an example terraform code of something that fails currently?

Resolving patch:
diff --git a/checkov/terraform/checks/resource/aws/AbsSecurityGroupUnrestrictedIngress.py b/checkov/terraform/checks/resource/aws/AbsSecurityGroupUnrestrictedIngress.py
new file mode 100644
--- /dev/null
+++ b/checkov/terraform/checks/resource/aws/AbsSecurityGroupUnrestrictedIngress.py
@@ -0,0 +1,40 @@
+from checkov.common.models.enums import CheckResult, CheckCategories
+from checkov.terraform.checks.resource.base_resource_check import BaseResourceCheck
+from checkov.common.util.type_forcers import force_list
+from checkov.common.util.type_forcers import force_int
+
+
+class AbsSecurityGroupUnrestrictedIngress(BaseResourceCheck):
+    def __init__(self, check_id, port):
+        name = "Ensure no security groups allow ingress from 0.0.0.0:0 to port %d" % port
+        supported_resources = ['aws_security_group']
+        categories = [CheckCategories.NETWORKING]
+        super().__init__(name=name, id=check_id, categories=categories, supported_resources=supported_resources)
+        self.port = port
+
+    def scan_resource_conf(self, conf):
+        """
+            Looks for configuration at security group ingress rules :
+            https://www.terraform.io/docs/providers/aws/r/security_group.html
+        :param conf: aws_security_group configuration
+        :return: <CheckResult>
+        """
+        if 'ingress' in conf:
+            ingress_conf = conf['ingress']
+            for ingress_rule in ingress_conf:
+                ingress_rules = force_list(ingress_rule)
+                for rule in ingress_rules:
+                    if isinstance(rule, dict):
+                        from_port = force_int(force_list(rule['from_port'])[0])
+                        to_port = force_int(force_list(rule['to_port'])[0])
+
+                        if from_port <= self.port <= to_port:
+                            # It's not clear whether these can ever be a type other
+                            # than an empty list but just in case…
+                            cidr_blocks = force_list(rule.get('cidr_blocks', [[]])[0])
+                            security_groups = rule.get('security_groups', [])
+
+                            if "0.0.0.0/0" in cidr_blocks and not security_groups:
+                                return CheckResult.FAILED
+
+        return CheckResult.PASSED
diff --git a/checkov/terraform/checks/resource/aws/SecurityGroupUnrestrictedIngress22.py b/checkov/terraform/checks/resource/aws/SecurityGroupUnrestrictedIngress22.py
--- a/checkov/terraform/checks/resource/aws/SecurityGroupUnrestrictedIngress22.py
+++ b/checkov/terraform/checks/resource/aws/SecurityGroupUnrestrictedIngress22.py
@@ -1,36 +1,9 @@
-from checkov.common.models.enums import CheckResult, CheckCategories
-from checkov.terraform.checks.resource.base_resource_check import BaseResourceCheck
-from checkov.common.util.type_forcers import force_list
+from checkov.terraform.checks.resource.aws.AbsSecurityGroupUnrestrictedIngress import AbsSecurityGroupUnrestrictedIngress
 
-PORT = 22
 
-
-class SecurityGroupUnrestrictedIngress22(BaseResourceCheck):
+class SecurityGroupUnrestrictedIngress22(AbsSecurityGroupUnrestrictedIngress):
     def __init__(self):
-        name = "Ensure no security groups allow ingress from 0.0.0.0:0 to port %d" % PORT
-        id = "CKV_AWS_24"
-        supported_resources = ['aws_security_group']
-        categories = [CheckCategories.NETWORKING]
-        super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
-
-    def scan_resource_conf(self, conf):
-        """
-            Looks for configuration at security group ingress rules :
-            https://www.terraform.io/docs/providers/aws/r/security_group.html
-        :param conf: aws_security_group configuration
-        :return: <CheckResult>
-        """
-        if 'ingress' in conf.keys():
-            ingress_conf = conf['ingress']
-            for rule in ingress_conf:
-                if isinstance(rule, dict):
-                    if isinstance(force_list(rule['from_port'])[0],int) and isinstance(force_list(rule['to_port'])[0],int):
-                        if rule['from_port'] == [PORT] and rule['to_port'] == [PORT]:
-                            if 'cidr_blocks' in rule.keys():
-                                if rule['cidr_blocks'] == [["0.0.0.0/0"]] and 'security_groups' not in rule.keys():
-                                    return CheckResult.FAILED
-
-        return CheckResult.PASSED
+        super().__init__(check_id="CKV_AWS_24", port=22)
 
 
 check = SecurityGroupUnrestrictedIngress22()
diff --git a/checkov/terraform/checks/resource/aws/SecurityGroupUnrestrictedIngress3389.py b/checkov/terraform/checks/resource/aws/SecurityGroupUnrestrictedIngress3389.py
--- a/checkov/terraform/checks/resource/aws/SecurityGroupUnrestrictedIngress3389.py
+++ b/checkov/terraform/checks/resource/aws/SecurityGroupUnrestrictedIngress3389.py
@@ -1,37 +1,9 @@
-from checkov.common.models.enums import CheckResult, CheckCategories
-from checkov.terraform.checks.resource.base_resource_check import BaseResourceCheck
-from checkov.common.util.type_forcers import force_list
+from checkov.terraform.checks.resource.aws.AbsSecurityGroupUnrestrictedIngress import AbsSecurityGroupUnrestrictedIngress
 
-PORT = 3389
 
-
-class SecurityGroupUnrestrictedIngress3389(BaseResourceCheck):
+class SecurityGroupUnrestrictedIngress3389(AbsSecurityGroupUnrestrictedIngress):
     def __init__(self):
-        name = "Ensure no security groups allow ingress from 0.0.0.0:0 to port %d" % PORT
-        id = "CKV_AWS_25"
-        supported_resources = ['aws_security_group']
-        categories = [CheckCategories.NETWORKING]
-        super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
-
-    def scan_resource_conf(self, conf):
-        """
-            Looks for configuration at security group ingress rules :
-            https://www.terraform.io/docs/providers/aws/r/security_group.html
-        :param conf: aws_security_group configuration
-        :return: <CheckResult>
-        """
-        if 'ingress' in conf.keys():
-            ingress_conf = conf['ingress']
-            for rule in ingress_conf:
-                if isinstance(rule, dict):
-                    if isinstance(force_list(rule['from_port'])[0], int) and isinstance(force_list(rule['to_port'])[0], int):
-                        if rule['from_port'] == [PORT] and rule['to_port'] == [PORT]:
-                            if 'cidr_blocks' in rule.keys():
-                                if rule['cidr_blocks'] == [["0.0.0.0/0"]] and 'security_groups' not in rule.keys():
-                                    return CheckResult.FAILED
-
-
-        return CheckResult.PASSED
+        super().__init__(check_id="CKV_AWS_25", port=3389)
 
 
 check = SecurityGroupUnrestrictedIngress3389()


Test patch:
diff --git a/tests/terraform/checks/resource/aws/test_SecurityGroupUnrestrictedIngress22.py b/tests/terraform/checks/resource/aws/test_SecurityGroupUnrestrictedIngress22.py
--- a/tests/terraform/checks/resource/aws/test_SecurityGroupUnrestrictedIngress22.py
+++ b/tests/terraform/checks/resource/aws/test_SecurityGroupUnrestrictedIngress22.py
@@ -1,35 +1,88 @@
 import unittest
 
-from checkov.terraform.checks.resource.aws.SecurityGroupUnrestrictedIngress22 import check
+import hcl2
+
 from checkov.common.models.enums import CheckResult
+from checkov.terraform.checks.resource.aws.SecurityGroupUnrestrictedIngress22 import check
 
 
 class TestSecurityGroupUnrestrictedIngress22(unittest.TestCase):
 
     def test_failure(self):
-        resource_conf = {'name': ['foo'],
-                         'vpc_id': ['${var.vpc_id}'], 'ingress': [
-                {'from_port': [22], 'to_port': [22], 'protocol': ['TCP'], 'cidr_blocks': [['0.0.0.0/0']]},
-                {'from_port': [443], 'to_port': [443], 'protocol': ['TCP'], 'cidr_blocks': [['0.0.0.0/0']]}],
-                         'egress': [
-                             {'from_port': [0], 'to_port': [0], 'protocol': ['-1'], 'cidr_blocks': [['0.0.0.0/0']]}]
-                        }
+        hcl_res = hcl2.loads("""
+resource "aws_security_group" "bar-sg" {
+  name   = "sg-bar"
+  vpc_id = aws_vpc.main.id
+
+  ingress {
+    from_port = 22
+    to_port   = 22
+    protocol  = "tcp"
+    cidr_blocks = ["0.0.0.0/0"]
+    description = "foo"
+  }
 
+  egress {
+    from_port = 0
+    to_port   = 0
+    protocol  = "-1"
+    cidr_blocks = ["0.0.0.0/0"]
+  }
+}  
+        """)
+        resource_conf = hcl_res['resource'][0]['aws_security_group']['bar-sg']
         scan_result = check.scan_resource_conf(conf=resource_conf)
         self.assertEqual(CheckResult.FAILED, scan_result)
 
     def test_success(self):
-        resource_conf =  {'name': ['foo'],
-                          'vpc_id': ['${var.vpc_id}'], 'ingress': [
-                {'from_port': [80], 'to_port': [80], 'protocol': ['TCP'], 'cidr_blocks': [['0.0.0.0/0']]},
-                {'from_port': [443], 'to_port': [443], 'protocol': ['TCP'], 'cidr_blocks': [['0.0.0.0/0']]}],
-                          'egress': [
-                              {'from_port': [0], 'to_port': [0], 'protocol': ['-1'], 'cidr_blocks': [['0.0.0.0/0']]}],
-                          'tags': [{'kubernetes.io/cluster/${var.cluster_name}': 'owned',
-                                    'kubernetes:application': '${local.name}'}]}
+        hcl_res = hcl2.loads("""
+resource "aws_security_group" "bar-sg" {
+  name   = "sg-bar"
+  vpc_id = aws_vpc.main.id
+
+  ingress {
+    from_port = 22
+    to_port   = 22
+    protocol  = "tcp"
+    security_groups = [aws_security_group.foo-sg.id]
+    description = "foo"
+  }
+
+  egress {
+    from_port = 0
+    to_port   = 0
+    protocol  = "-1"
+    cidr_blocks = ["0.0.0.0/0"]
+  }
+}
+        """)
+        resource_conf = hcl_res['resource'][0]['aws_security_group']['bar-sg']
         scan_result = check.scan_resource_conf(conf=resource_conf)
         self.assertEqual(CheckResult.PASSED, scan_result)
 
+    def test_ingress_rules_list(self):
+        hcl_res = hcl2.loads("""
+resource "aws_security_group" "inline_rules" {
+  description = "SG with inline rules"
+  ingress = [
+    {
+      cidr_blocks      = ["0.0.0.0/0"]
+      description      = "Wide Open"
+      from_port        = 0
+      ipv6_cidr_blocks = []
+      prefix_list_ids  = []
+      security_groups  = []
+      protocol         = "-1"
+      self             = false
+      to_port          = 65535
+    }
+  ]
+}
+""")
+        conf = hcl_res['resource'][0]['aws_security_group']['inline_rules']
+        result = check.scan_resource_conf(conf)
+        self.assertEqual(result, CheckResult.FAILED)
+
 
 if __name__ == '__main__':
     unittest.main()