Repository: bridgecrewio/checkov

Issue:
CKV_DOCKER_1 rule not failed when using EXPOSE 22/TCP
**Issue**
If it is related to an existing check, CKV_DOCKER_1.
Dockerfile EXPOSE can accept port using tcp proto by defaut if not specified or you can also specify the protocol with /tcp or /udp.

When using "EXPOSE 22/tcp" the rule does not detect the SSH port. something like '22/tcp'.split('/')[0] will return port without protocol or we can test both '22' or '22/tcp' in the rule.

**Examples**
Issue can be replicated with a Dockerfile which uses "EXPOSE 22/tcp".

**Version**
 - Checkov Version 2.1.87

**Specification**
EXPOSE specification https://docs.docker.com/engine/reference/builder/#expose



Discussion:
hey @pitfallincode thanks for reaching out. Nice catch, you are right, we just check, if there is a port `22` mentioned without the protocol part. I like the idea of testing for both `22` and `22/tcp` 🥇 

Are you interested in contributing the needed change? If yes, you can find the check and the related tests here
https://github.com/bridgecrewio/checkov/blob/master/checkov/dockerfile/checks/ExposePort22.py
https://github.com/bridgecrewio/checkov/blob/fe902170cfa620271bff7b124ffc6b1309c0aacc/tests/dockerfile/test_runner.py#L72-L82

Resolving patch:
diff --git a/checkov/dockerfile/checks/ExposePort22.py b/checkov/dockerfile/checks/ExposePort22.py
--- a/checkov/dockerfile/checks/ExposePort22.py
+++ b/checkov/dockerfile/checks/ExposePort22.py
@@ -19,7 +19,7 @@ def __init__(self) -> None:
 
     def scan_resource_conf(self, conf: list[_Instruction]) -> tuple[CheckResult, list[_Instruction] | None]:
         for expose in conf:
-            if "22" in expose["value"].split(" "):
+            if any(port in expose["value"].split(" ") for port in ("22", "22/tcp")):
                 return CheckResult.FAILED, [expose]
 
         return CheckResult.PASSED, None


Test patch:
diff --git a/tests/dockerfile/checks/example_ExposePort22/failure/Dockerfile b/tests/dockerfile/checks/example_ExposePort22/failure/Dockerfile
new file mode 100644
--- /dev/null
+++ b/tests/dockerfile/checks/example_ExposePort22/failure/Dockerfile
@@ -0,0 +1,3 @@
+FROM busybox
+
+EXPOSE 8080 22
diff --git a/tests/dockerfile/checks/example_ExposePort22/failure_tcp/Dockerfile b/tests/dockerfile/checks/example_ExposePort22/failure_tcp/Dockerfile
new file mode 100644
--- /dev/null
+++ b/tests/dockerfile/checks/example_ExposePort22/failure_tcp/Dockerfile
@@ -0,0 +1,3 @@
+FROM busybox
+
+EXPOSE 22/tcp
diff --git a/tests/dockerfile/checks/example_ExposePort22/success/Dockerfile b/tests/dockerfile/checks/example_ExposePort22/success/Dockerfile
new file mode 100644
--- /dev/null
+++ b/tests/dockerfile/checks/example_ExposePort22/success/Dockerfile
@@ -0,0 +1,3 @@
+FROM busybox
+
+EXPOSE 8080
diff --git a/tests/dockerfile/checks/test_ExposePort22.py b/tests/dockerfile/checks/test_ExposePort22.py
new file mode 100644
--- /dev/null
+++ b/tests/dockerfile/checks/test_ExposePort22.py
@@ -0,0 +1,41 @@
+import unittest
+from pathlib import Path
+
+from checkov.dockerfile.checks.ExposePort22 import check
+from checkov.dockerfile.runner import Runner
+from checkov.runner_filter import RunnerFilter
+
+
+class TestExposePort22(unittest.TestCase):
+    def test(self):
+        #  given
+        test_files_dir = Path(__file__).parent / "example_ExposePort22"
+
+        # when
+        report = Runner().run(root_folder=str(test_files_dir), runner_filter=RunnerFilter(checks=[check.id]))
+
+        # then
+        summary = report.get_summary()
+
+        passing_resources = {
+            "/success/Dockerfile.",
+        }
+        failing_resources = {
+            "/failure/Dockerfile.EXPOSE",
+            "/failure_tcp/Dockerfile.EXPOSE",
+        }
+
+        passed_check_resources = {c.resource for c in report.passed_checks}
+        failed_check_resources = {c.resource for c in report.failed_checks}
+
+        self.assertEqual(summary["passed"], len(passing_resources))
+        self.assertEqual(summary["failed"], len(failing_resources))
+        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()