Repository: bridgecrewio/checkov

Issue:
Add OCI TF check - OCI security lists allows unrestricted ingress access to port 3389
Equivalent RQL query 

config from cloud.resource where cloud.type = 'oci' AND api.name = 'oci-networking-security-list' AND json.rule = (ingressSecurityRules[?any((source equals 0.0.0.0/0) and (((*.destinationPortRange.min == 3389 or *.destinationPortRange.max == 3389) or (*.destinationPortRange.min < 3389 and *.destinationPortRange.max > 3389)) or (protocol equals "all") or ((tcpOptions does not exist) and (udpOptions does not exist) and (protocol does not equal 1))))] exists)
Add OCI TF check - OCI Security List allows all traffic on SSH port (22)
Equivalent RQL query

config from cloud.resource where cloud.type = 'oci' AND api.name = 'oci-networking-security-list' AND json.rule = (ingressSecurityRules[?any((source equals 0.0.0.0/0) and (((*.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))))] exists)


Discussion:

@GiladMark if the "source" is not equal to  0.0.0.0/0, should the check skip or fail?

Resolving patch:
diff --git a/checkov/terraform/checks/resource/oci/AbsSecurityListUnrestrictedIngress.py b/checkov/terraform/checks/resource/oci/AbsSecurityListUnrestrictedIngress.py
new file mode 100644
--- /dev/null
+++ b/checkov/terraform/checks/resource/oci/AbsSecurityListUnrestrictedIngress.py
@@ -0,0 +1,41 @@
+from checkov.common.models.enums import CheckCategories, CheckResult
+from checkov.terraform.checks.resource.base_resource_check import BaseResourceCheck
+
+
+class AbsSecurityListUnrestrictedIngress(BaseResourceCheck):
+    def __init__(self, check_id, port, is_exposed_by_default):
+        name = f"Ensure no security list allow ingress from 0.0.0.0:0 to port {port}."
+        supported_resources = ['oci_core_security_list']
+        categories = [CheckCategories.NETWORKING]
+        super().__init__(name=name, id=check_id, categories=categories, supported_resources=supported_resources)
+        self.port = port
+        self.is_exposed_by_default = is_exposed_by_default
+
+    def scan_resource_conf(self, conf):
+        if 'ingress_security_rules' in conf:
+            self.evaluated_keys = ['ingress_security_rules']
+            rules = conf.get("ingress_security_rules", [])
+
+            for idx, rule in enumerate(rules):
+                if "0.0.0.0/0" in rule['source'][0] \
+                        and (
+                        (rule['protocol'][0] != '1' and ('udp_options' not in rule) and ('tcp_options' not in rule))
+                        or self.scan_protocol_conf(rule, 'tcp_options', idx) != CheckResult.FAILED
+                        or self.scan_protocol_conf(rule, 'udp_options', idx) != CheckResult.FAILED
+                        or rule['protocol'][0] == 'all'):
+                    self.evaluated_keys = [f'ingress_security_rules/[0]/[{idx}]']
+                    return CheckResult.FAILED
+
+            return CheckResult.PASSED
+
+        return CheckResult.FAILED if self.is_exposed_by_default else CheckResult.PASSED
+
+    def scan_protocol_conf(self, rule, protocol_name, idx):
+        """ scan udp/tcp_options configuration"""
+        if protocol_name in rule:
+            max_port = rule[protocol_name][0]['max'][0]
+            min_port = rule[protocol_name][0]['min'][0]
+            if min_port <= self.port and max_port >= self.port:
+                return CheckResult.SKIPPED
+        self.evaluated_keys = [f'ingress_security_rules/[0]/[{idx}]/protocol/[0]/{protocol_name}']
+        return CheckResult.FAILED
diff --git a/checkov/terraform/checks/resource/oci/SecurityListUnrestrictedIngress22.py b/checkov/terraform/checks/resource/oci/SecurityListUnrestrictedIngress22.py
new file mode 100644
--- /dev/null
+++ b/checkov/terraform/checks/resource/oci/SecurityListUnrestrictedIngress22.py
@@ -0,0 +1,9 @@
+from checkov.terraform.checks.resource.oci.AbsSecurityListUnrestrictedIngress import AbsSecurityListUnrestrictedIngress
+
+
+class SecurityListUnrestrictedIngress22(AbsSecurityListUnrestrictedIngress):
+    def __init__(self):
+        super().__init__(check_id="CKV_OCI_19", port=22, is_exposed_by_default=True)
+
+
+check = SecurityListUnrestrictedIngress22()
diff --git a/checkov/terraform/checks/resource/oci/SecurityListUnrestrictedIngress3389.py b/checkov/terraform/checks/resource/oci/SecurityListUnrestrictedIngress3389.py
new file mode 100644
--- /dev/null
+++ b/checkov/terraform/checks/resource/oci/SecurityListUnrestrictedIngress3389.py
@@ -0,0 +1,9 @@
+from checkov.terraform.checks.resource.oci.AbsSecurityListUnrestrictedIngress import AbsSecurityListUnrestrictedIngress
+
+
+class SecurityListUnrestrictedIngress3389(AbsSecurityListUnrestrictedIngress):
+    def __init__(self):
+        super().__init__(check_id="CKV_OCI_20", port=3389, is_exposed_by_default=False)
+
+
+check = SecurityListUnrestrictedIngress3389()


Test patch:
diff --git a/tests/terraform/checks/resource/oci/example_SecurityListUnrestrictedIngress22/main.tf b/tests/terraform/checks/resource/oci/example_SecurityListUnrestrictedIngress22/main.tf
new file mode 100644
--- /dev/null
+++ b/tests/terraform/checks/resource/oci/example_SecurityListUnrestrictedIngress22/main.tf
@@ -0,0 +1,138 @@
+resource "oci_core_security_list" "fail1" {
+    compartment_id = "var.compartment_id"
+    vcn_id = "oci_core_vcn.test_vcn.id"
+
+    ingress_security_rules {
+        protocol = "var.security_list_ingress_security_rules_protocol"
+        source = "0.0.0.0/0"
+
+        tcp_options {
+            max = 22
+            min = 22
+            source_port_range {
+                max = "var.security_list_ingress_security_rules_tcp_options_source_port_range_max"
+                min = "var.security_list_ingress_security_rules_tcp_options_source_port_range_min"
+            }
+        }
+        udp_options {
+            max = 900
+            min = 7
+            source_port_range {
+                max = "var.security_list_ingress_security_rules_udp_options_source_port_range_max"
+                min = "var.security_list_ingress_security_rules_udp_options_source_port_range_min"
+            }
+        }
+    }
+}
+
+resource "oci_core_security_list" "fail" {
+    compartment_id = "var.compartment_id"
+    vcn_id = "oci_core_vcn.test_vcn.id"
+
+    ingress_security_rules {
+        protocol = "var.security_list_ingress_security_rules_protocol"
+        source = "0.0.0.0/0"
+
+        tcp_options {
+            max = 25
+            min = 25
+            source_port_range {
+                max = "var.security_list_ingress_security_rules_tcp_options_source_port_range_max"
+                min = "var.security_list_ingress_security_rules_tcp_options_source_port_range_min"
+            }
+        }
+        udp_options {
+            max = 22
+            min = 22
+            source_port_range {
+                max = "var.security_list_ingress_security_rules_udp_options_source_port_range_max"
+                min = "var.security_list_ingress_security_rules_udp_options_source_port_range_min"
+            }
+        }
+    }
+}
+
+resource "oci_core_security_list" "pass0" {
+    compartment_id = "var.compartment_id"
+    vcn_id = "oci_core_vcn.test_vcn.id"
+
+    ingress_security_rules {
+        protocol = "var.security_list_ingress_security_rules_protocol"
+        source = "0.0.0.0/0"
+
+        tcp_options {
+            max = 25
+            min = 25
+            source_port_range {
+                max = "var.security_list_ingress_security_rules_tcp_options_source_port_range_max"
+                min = "var.security_list_ingress_security_rules_tcp_options_source_port_range_min"
+            }
+        }
+        udp_options {
+            max = 21
+            min = 20
+            source_port_range {
+                max = "var.security_list_ingress_security_rules_udp_options_source_port_range_max"
+                min = "var.security_list_ingress_security_rules_udp_options_source_port_range_min"
+            }
+        }
+    }
+}
+
+resource "oci_core_security_list" "fail2" {
+    compartment_id = "var.compartment_id"
+    vcn_id = "oci_core_vcn.test_vcn.id"
+
+    ingress_security_rules {
+        protocol = "var.security_list_ingress_security_rules_protocol"
+        source = "0.0.0.0/0"
+
+        tcp_options {
+            max = 22
+            min = 21
+            source_port_range {
+                max = "var.security_list_ingress_security_rules_tcp_options_source_port_range_max"
+                min = "var.security_list_ingress_security_rules_tcp_options_source_port_range_min"
+            }
+        }
+        udp_options {
+            max = 23
+            min = 20
+            source_port_range {
+                max = "var.security_list_ingress_security_rules_udp_options_source_port_range_max"
+                min = "var.security_list_ingress_security_rules_udp_options_source_port_range_min"
+            }
+        }
+    }
+}
+resource "oci_core_security_list" "fail3" {
+    compartment_id = "var.compartment_id"
+    vcn_id = "oci_core_vcn.test_vcn.id"
+
+    ingress_security_rules {
+        protocol = "all"
+        source = "0.0.0.0/0"
+    }
+}
+resource "oci_core_security_list" "pass1" {
+    compartment_id = "var.compartment_id"
+    vcn_id = "oci_core_vcn.test_vcn.id"
+
+    ingress_security_rules {
+        protocol = "1"
+        source = "0.0.0.0/0"
+    }
+}
+resource "oci_core_security_list" "pass4" {
+    compartment_id = "var.compartment_id"
+    vcn_id = "oci_core_vcn.test_vcn.id"
+
+    ingress_security_rules {
+        protocol = "all"
+        source = "0.0.0.1/0"
+    }
+}
+resource "oci_core_security_list" "fail5" {
+    compartment_id = "var.compartment_id"
+    vcn_id = "oci_core_vcn.test_vcn.id"
+}
\ No newline at end of file
diff --git a/tests/terraform/checks/resource/oci/example_SecurityListUnrestrictedIngress3389/main.tf b/tests/terraform/checks/resource/oci/example_SecurityListUnrestrictedIngress3389/main.tf
new file mode 100644
--- /dev/null
+++ b/tests/terraform/checks/resource/oci/example_SecurityListUnrestrictedIngress3389/main.tf
@@ -0,0 +1,138 @@
+resource "oci_core_security_list" "fail1" {
+    compartment_id = "var.compartment_id"
+    vcn_id = "oci_core_vcn.test_vcn.id"
+
+    ingress_security_rules {
+        protocol = "var.security_list_ingress_security_rules_protocol"
+        source = "0.0.0.0/0"
+
+        tcp_options {
+            max = 3389
+            min = 3389
+            source_port_range {
+                max = "var.security_list_ingress_security_rules_tcp_options_source_port_range_max"
+                min = "var.security_list_ingress_security_rules_tcp_options_source_port_range_min"
+            }
+        }
+        udp_options {
+            max = 4000
+            min = 3388
+            source_port_range {
+                max = "var.security_list_ingress_security_rules_udp_options_source_port_range_max"
+                min = "var.security_list_ingress_security_rules_udp_options_source_port_range_min"
+            }
+        }
+    }
+}
+
+resource "oci_core_security_list" "fail" {
+    compartment_id = "var.compartment_id"
+    vcn_id = "oci_core_vcn.test_vcn.id"
+
+    ingress_security_rules {
+        protocol = "var.security_list_ingress_security_rules_protocol"
+        source = "0.0.0.0/0"
+
+        tcp_options {
+            max = 25
+            min = 25
+            source_port_range {
+                max = "var.security_list_ingress_security_rules_tcp_options_source_port_range_max"
+                min = "var.security_list_ingress_security_rules_tcp_options_source_port_range_min"
+            }
+        }
+        udp_options {
+            max = 3389
+            min = 3389
+            source_port_range {
+                max = "var.security_list_ingress_security_rules_udp_options_source_port_range_max"
+                min = "var.security_list_ingress_security_rules_udp_options_source_port_range_min"
+            }
+        }
+    }
+}
+
+resource "oci_core_security_list" "pass0" {
+    compartment_id = "var.compartment_id"
+    vcn_id = "oci_core_vcn.test_vcn.id"
+
+    ingress_security_rules {
+        protocol = "var.security_list_ingress_security_rules_protocol"
+        source = "0.0.0.0/0"
+
+        tcp_options {
+            max = 4000
+            min = 3390
+            source_port_range {
+                max = "var.security_list_ingress_security_rules_tcp_options_source_port_range_max"
+                min = "var.security_list_ingress_security_rules_tcp_options_source_port_range_min"
+            }
+        }
+        udp_options {
+            max = 21
+            min = 20
+            source_port_range {
+                max = "var.security_list_ingress_security_rules_udp_options_source_port_range_max"
+                min = "var.security_list_ingress_security_rules_udp_options_source_port_range_min"
+            }
+        }
+    }
+}
+
+resource "oci_core_security_list" "fail2" {
+    compartment_id = "var.compartment_id"
+    vcn_id = "oci_core_vcn.test_vcn.id"
+
+    ingress_security_rules {
+        protocol = "var.security_list_ingress_security_rules_protocol"
+        source = "0.0.0.0/0"
+
+        tcp_options {
+            max = 3389
+            min = 3388
+            source_port_range {
+                max = "var.security_list_ingress_security_rules_tcp_options_source_port_range_max"
+                min = "var.security_list_ingress_security_rules_tcp_options_source_port_range_min"
+            }
+        }
+        udp_options {
+            max = 3390
+            min = 3386
+            source_port_range {
+                max = "var.security_list_ingress_security_rules_udp_options_source_port_range_max"
+                min = "var.security_list_ingress_security_rules_udp_options_source_port_range_min"
+            }
+        }
+    }
+}
+resource "oci_core_security_list" "fail3" {
+    compartment_id = "var.compartment_id"
+    vcn_id = "oci_core_vcn.test_vcn.id"
+
+    ingress_security_rules {
+        protocol = "all"
+        source = "0.0.0.0/0"
+    }
+}
+resource "oci_core_security_list" "pass1" {
+    compartment_id = "var.compartment_id"
+    vcn_id = "oci_core_vcn.test_vcn.id"
+
+    ingress_security_rules {
+        protocol = "1"
+        source = "0.0.0.0/0"
+    }
+}
+resource "oci_core_security_list" "pass4" {
+    compartment_id = "var.compartment_id"
+    vcn_id = "oci_core_vcn.test_vcn.id"
+
+    ingress_security_rules {
+        protocol = "all"
+        source = "0.0.0.1/0"
+    }
+}
+resource "oci_core_security_list" "pass5" {
+    compartment_id = "var.compartment_id"
+    vcn_id = "oci_core_vcn.test_vcn.id"
+}
\ No newline at end of file
diff --git a/tests/terraform/checks/resource/oci/test_SecurityListUnrestrictedIngress22.py b/tests/terraform/checks/resource/oci/test_SecurityListUnrestrictedIngress22.py
new file mode 100644
--- /dev/null
+++ b/tests/terraform/checks/resource/oci/test_SecurityListUnrestrictedIngress22.py
@@ -0,0 +1,44 @@
+import os
+import unittest
+
+from checkov.runner_filter import RunnerFilter
+from checkov.terraform.checks.resource.oci.SecurityListUnrestrictedIngress22 import check
+from checkov.terraform.runner import Runner
+
+
+class TestSecurityListUnrestrictedIngress22(unittest.TestCase):
+    def test(self):
+        runner = Runner()
+        current_dir = os.path.dirname(os.path.realpath(__file__))
+
+        test_files_dir = current_dir + "/example_SecurityListUnrestrictedIngress22"
+        report = runner.run(root_folder=test_files_dir, runner_filter=RunnerFilter(checks=[check.id]))
+        summary = report.get_summary()
+
+        passing_resources = {
+            "oci_core_security_list.pass0",
+            "oci_core_security_list.pass1",
+            "oci_core_security_list.pass4",
+        }
+        failing_resources = {
+            "oci_core_security_list.fail",
+            "oci_core_security_list.fail1",
+            "oci_core_security_list.fail2",
+            "oci_core_security_list.fail3",
+            "oci_core_security_list.fail5",
+        }
+
+        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"], 3)
+        self.assertEqual(summary["failed"], 5)
+        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()
diff --git a/tests/terraform/checks/resource/oci/test_SecurityListUnrestrictedIngress3389.py b/tests/terraform/checks/resource/oci/test_SecurityListUnrestrictedIngress3389.py
new file mode 100644
--- /dev/null
+++ b/tests/terraform/checks/resource/oci/test_SecurityListUnrestrictedIngress3389.py
@@ -0,0 +1,44 @@
+import os
+import unittest
+
+from checkov.runner_filter import RunnerFilter
+from checkov.terraform.checks.resource.oci.SecurityListUnrestrictedIngress3389 import check
+from checkov.terraform.runner import Runner
+
+
+class TestSecurityListUnrestrictedIngress3389(unittest.TestCase):
+    def test(self):
+        runner = Runner()
+        current_dir = os.path.dirname(os.path.realpath(__file__))
+
+        test_files_dir = current_dir + "/example_SecurityListUnrestrictedIngress3389"
+        report = runner.run(root_folder=test_files_dir, runner_filter=RunnerFilter(checks=[check.id]))
+        summary = report.get_summary()
+
+        passing_resources = {
+            "oci_core_security_list.pass0",
+            "oci_core_security_list.pass1",
+            "oci_core_security_list.pass4",
+            "oci_core_security_list.pass5",
+        }
+        failing_resources = {
+            "oci_core_security_list.fail",
+            "oci_core_security_list.fail1",
+            "oci_core_security_list.fail2",
+            "oci_core_security_list.fail3",
+        }
+
+        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"], 4)
+        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()