Repository: bridgecrewio/checkov
Pull request: Ensure no security groups rules allow ingress from 0.0.0.0:0 to port …
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
resolved #1973

OCI security group doesn't allow unrestricted ingress access to port 22

Problem statement:
Add OCI TF check - OCI security group allows unrestricted ingress access to port 22
Equivalent RQL query 
config from cloud.resource where cloud.type = 'oci' AND api.name = 'oci-networking-nsg' AND json.rule = (securityRules[?any((((*.destinationPortRange.min == 22 or *.destinationPortRange.max == 22) or (*.destinationPortRange.min < 22 and *.destinationPortRange.max > 22)) or (protocol equals "all") or ((tcpOptions does not exist) and (udpOptions does not exist) and (protocol does not equal 1))) and (source equals 0.0.0.0/0 and direction equals INGRESS))] exists)

Hints from issue discussion:
Note that RQL queries are spotting the error while “Checkov logic wish to find the Desired state” use the referance below in order to get more data for the check needed -

_Note that RQL queries are spotting the error while “Checkov logic wish to find the Desired state” use the referance below in order to get more data for the check needed -

_This policy identifies OCI Security groups that allow unrestricted ingress access to port 22. It is recommended that no security group allows unrestricted ingress access to port 22. As a best practice, remove unfettered connectivity to remote console services, such as Secure Shell (SSH), to reduce server's exposure to risk.._

https://github.com/PaloAltoNetworks/prisma-cloud-policies/blob/master/policies/OCI-security-group-allows-unrestricted-ingress-access-to-port-22.json

Commit message: Update checkov/terraform/checks/resource/oci/AbsSecurityGroupUnrestrictedIngress.py

Co-authored-by: Anton Grübel <anton.gruebel@gmail.com>

Patch to review:
diff --git a/checkov/terraform/checks/resource/oci/AbsSecurityGroupUnrestrictedIngress.py b/checkov/terraform/checks/resource/oci/AbsSecurityGroupUnrestrictedIngress.py
new file mode 100644
index 0000000000..22a98bca00
--- /dev/null
+++ b/checkov/terraform/checks/resource/oci/AbsSecurityGroupUnrestrictedIngress.py
@@ -0,0 +1,33 @@
+from checkov.common.models.enums import CheckResult, CheckCategories
+from checkov.terraform.checks.resource.base_resource_check import BaseResourceCheck
+
+
+class AbsSecurityGroupUnrestrictedIngress(BaseResourceCheck):
+    def __init__(self, check_id: str, port: int) -> None:
+        name = f"Ensure no security groups rules allow ingress from 0.0.0.0/0 to port {port}"
+        supported_resources = ['oci_core_network_security_group_security_rule']
+        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):
+        direction = conf.get('direction')
+        source = conf.get('source')
+        protocol = conf.get('protocol')
+        tcp_options = conf.get('tcp_options')
+        if direction and direction[0] != 'INGRESS':
+            return CheckResult.PASSED
+        if source and source[0] != "0.0.0.0/0":
+            return CheckResult.PASSED
+        elif (tcp_options is None and (protocol[0] == 'all' or protocol[0] == '6')) \
+                or tcp_options and self.scan_protocol_conf(tcp_options) is False:
+            return CheckResult.FAILED
+        return CheckResult.PASSED
+
+    def scan_protocol_conf(self, protocol_name):
+        """ scan tcp_options configuration"""
+        max_port = protocol_name[0]['source_port_range'][0]['max'][0]
+        min_port = protocol_name[0]['source_port_range'][0]['min'][0]
+        if min_port <= self.port <= max_port:
+            return False
+        return True
diff --git a/checkov/terraform/checks/resource/oci/SecurityGroupUnrestrictedIngress22.py b/checkov/terraform/checks/resource/oci/SecurityGroupUnrestrictedIngress22.py
new file mode 100644
index 0000000000..a3d31968c9
--- /dev/null
+++ b/checkov/terraform/checks/resource/oci/SecurityGroupUnrestrictedIngress22.py
@@ -0,0 +1,10 @@
+from checkov.terraform.checks.resource.oci.AbsSecurityGroupUnrestrictedIngress import \
+    AbsSecurityGroupUnrestrictedIngress
+
+
+class SecurityGroupUnrestrictedIngress22(AbsSecurityGroupUnrestrictedIngress):
+    def __init__(self):
+        super().__init__(check_id="CKV_OCI_22", port=22)
+
+
+check = AbsSecurityGroupUnrestrictedIngress("CKV_OCI_22", 22)
diff --git a/tests/terraform/checks/resource/oci/example_SecurityGroupUnrestrictedIngress22/main.tf b/tests/terraform/checks/resource/oci/example_SecurityGroupUnrestrictedIngress22/main.tf
new file mode 100644
index 0000000000..bd27cf0f89
--- /dev/null
+++ b/tests/terraform/checks/resource/oci/example_SecurityGroupUnrestrictedIngress22/main.tf
@@ -0,0 +1,90 @@
+
+resource "oci_core_network_security_group_security_rule" "pass" {
+    network_security_group_id = oci_core_network_security_group.sg.id
+    direction = "EGRESS"
+    protocol = "all"
+    source = "0.0.0.0/0"
+
+    tcp_options {
+        source_port_range {
+            max = 22
+            min = 22
+        }
+    }
+}
+
+resource "oci_core_network_security_group_security_rule" "pass1" {
+    network_security_group_id = oci_core_network_security_group.sg.id
+    direction = "EGRESS"
+    protocol = "all"
+    source = "0.0.0.0/0"
+}
+
+resource "oci_core_network_security_group_security_rule" "pass2" {
+    network_security_group_id = oci_core_network_security_group.sg.id
+    direction = "INGRESS"
+    protocol = "all"
+    source = "0.0.0.0/0"
+    source_type = "CIDR_BLOCK"
+
+    tcp_options {
+        source_port_range {
+            max = 25
+            min = 25
+        }
+    }
+}
+
+resource "oci_core_network_security_group_security_rule" "pass3" {
+    network_security_group_id = oci_core_network_security_group.sg.id
+    direction = "INGRESS"
+    protocol = "all"
+    source = "0.0.0.0/0"
+    source_type = "CIDR_BLOCK"
+
+    tcp_options {
+        source_port_range {
+            max = 21
+            min = 1
+        }
+    }
+}
+
+
+resource "oci_core_network_security_group_security_rule" "fail" {
+    network_security_group_id = oci_core_network_security_group.sg.id
+    direction = "INGRESS"
+    protocol = "all"
+    source = "0.0.0.0/0"
+    source_type = "CIDR_BLOCK"
+
+    tcp_options {
+        source_port_range {
+            max = 22
+            min = 22
+        }
+    }
+}
+
+resource "oci_core_network_security_group_security_rule" "fail1" {
+    network_security_group_id = oci_core_network_security_group.sg.id
+    direction = "INGRESS"
+    protocol = "all"
+    source = "0.0.0.0/0"
+    source_type = "CIDR_BLOCK"
+
+    tcp_options {
+        source_port_range {
+            max = 25
+            min = 21
+        }
+    }
+}
+
+resource "oci_core_network_security_group_security_rule" "fail2" {
+    network_security_group_id = oci_core_network_security_group.sg.id
+    direction = "INGRESS"
+    protocol = "all"
+    source = "0.0.0.0/0"
+    source_type = "CIDR_BLOCK"
+}
diff --git a/tests/terraform/checks/resource/oci/test_SecurityGroupUnrestrictedIngress22.py b/tests/terraform/checks/resource/oci/test_SecurityGroupUnrestrictedIngress22.py
new file mode 100644
index 0000000000..80ba6ed8d3
--- /dev/null
+++ b/tests/terraform/checks/resource/oci/test_SecurityGroupUnrestrictedIngress22.py
@@ -0,0 +1,44 @@
+import os
+import unittest
+
+from checkov.runner_filter import RunnerFilter
+from checkov.terraform.checks.resource.oci.SecurityGroupUnrestrictedIngress22 import check
+from checkov.terraform.runner import Runner
+
+
+class TestSecurityGroupUnrestrictedIngress22(unittest.TestCase):
+    def test(self):
+        runner = Runner()
+        current_dir = os.path.dirname(os.path.realpath(__file__))
+
+        test_files_dir = current_dir + "/example_SecurityGroupUnrestrictedIngress22"
+        report = runner.run(root_folder=test_files_dir, runner_filter=RunnerFilter(checks=[check.id]))
+        summary = report.get_summary()
+
+        passing_resources = {
+            "oci_core_network_security_group_security_rule.pass",
+            "oci_core_network_security_group_security_rule.pass1",
+            "oci_core_network_security_group_security_rule.pass2",
+            "oci_core_network_security_group_security_rule.pass3",
+
+        }
+        failing_resources = {
+            "oci_core_network_security_group_security_rule.fail",
+            "oci_core_network_security_group_security_rule.fail1",
+            "oci_core_network_security_group_security_rule.fail2",
+        }
+
+        passed_check_resources = set([c.resource for c in report.passed_checks])
+        failed_check_resources = set([c.resource for c in report.failed_checks])
+
+        self.assertEqual(summary["passed"], 4)
+        self.assertEqual(summary["failed"], 3)
+        self.assertEqual(summary["skipped"], 0)
+        self.assertEqual(summary["parsing_errors"], 0)
+
+        self.assertEqual(passing_resources, passed_check_resources)
+        self.assertEqual(failing_resources, failed_check_resources)
+
+
+if __name__ == "__main__":
+    unittest.main()


Code review comments:
diff_hunk: @@ -0,0 +1,33 @@
+from checkov.common.models.enums import CheckResult, CheckCategories
+from checkov.terraform.checks.resource.base_resource_check import BaseResourceCheck
+
+
+class AbsSecurityGroupUnrestrictedIngress(BaseResourceCheck):
+    def __init__(self, check_id: str, port: int) -> None:
+        name = f"Ensure no security groups rules allow ingress from 0.0.0.0/0 to port {port}"
+        supported_resources = ['oci_core_network_security_group_security_rule']
+        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):
+        direction = conf.get('direction')
+        source = conf.get('source')
+        protocol = conf.get('protocol')
+        tcp_options = conf.get('tcp_options')
+        if direction and direction[0] != 'INGRESS':
+            return CheckResult.PASSED
+        if source and source[0] != "0.0.0.0/0":
path: checkov/terraform/checks/resource/oci/AbsSecurityGroupUnrestrictedIngress.py
text: @user1:
```suggestion
        self.evaluated_keys.append("source")
        if source and source[0] != "0.0.0.0/0":
```

diff_hunk: @@ -0,0 +1,33 @@
+from checkov.common.models.enums import CheckResult, CheckCategories
+from checkov.terraform.checks.resource.base_resource_check import BaseResourceCheck
+
+
+class AbsSecurityGroupUnrestrictedIngress(BaseResourceCheck):
+    def __init__(self, check_id: str, port: int) -> None:
+        name = f"Ensure no security groups rules allow ingress from 0.0.0.0/0 to port {port}"
+        supported_resources = ['oci_core_network_security_group_security_rule']
+        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):
+        direction = conf.get('direction')
+        source = conf.get('source')
+        protocol = conf.get('protocol')
+        tcp_options = conf.get('tcp_options')
+        if direction and direction[0] != 'INGRESS':
path: checkov/terraform/checks/resource/oci/AbsSecurityGroupUnrestrictedIngress.py
text: @user1:
```suggestion
        tcp_options = conf.get('tcp_options')
        self.evaluated_keys = ["direction"]
        if direction and direction[0] != 'INGRESS':
```