Repository: bridgecrewio/checkov

Issue:
CKV_OPENAPI_20 incorrectly flags API keys via HTTPS
**Describe the issue**

#5253 added CKV_OPENAPI_20 with the message "Ensure that API keys are not sent over cleartext", but the [check](https://github.com/bridgecrewio/checkov/blob/main/checkov/openapi/checks/resource/generic/ClearTextAPIKey.py) does not check the API's supported schemes.

If the intent of this check is to prevent cleartext use of API keys, then if the root level [`schemes`](https://swagger.io/docs/specification/2-0/api-host-and-base-path/) key in OpenAPI 2.0 or [`servers`](https://swagger.io/docs/specification/api-host-and-base-path/) key in OpenAPI 3.0 specifies only `https` (2.0) or only `url`s which are HTTPS (3.0), this check should pass.

**Examples**

[fail2.json](https://github.com/bridgecrewio/checkov/blob/main/tests/openapi/checks/resource/generic/example_ClearTextAPIKey/fail2.json) and its YAML counterpart should fail, but if they specified `"schemes": ["https"]`, they should not.

Ditto for the OpenAPI 3.0 version of this example.

**Version (please complete the following information):**

2.3.312

**Additional context**

It may be that the message is wrong and you actually intend to flag all use of API keys, but if that's the case, the message should convey that. I also would argue that header API keys should not be scrutinized the same way as query parameter API keys, since the risk of leaking the API key unintentionally is higher with the latter.


Discussion:
hey @ziggythehamster thanks for reaching out.

Yeah, the part about sending the token via header shouldn't be flagged, will check it with the creator of the check @tsmithv11 

Resolving patch:
diff --git a/checkov/openapi/checks/resource/generic/ClearTextAPIKey.py b/checkov/openapi/checks/resource/generic/ClearTextAPIKey.py
--- a/checkov/openapi/checks/resource/generic/ClearTextAPIKey.py
+++ b/checkov/openapi/checks/resource/generic/ClearTextAPIKey.py
@@ -17,6 +17,16 @@ def __init__(self) -> None:
                          block_type=BlockType.DOCUMENT)
 
     def scan_entity_conf(self, conf: dict[str, Any], entity_type: str) -> tuple[CheckResult, dict[str, Any]]:  # type:ignore[override]  # return type is different than the base class
+        schemes = conf.get("schemes")
+        if schemes and isinstance(schemes, list):
+            if "http" not in schemes and "wp" not in schemes:
+                return CheckResult.PASSED, conf
+
+        servers = conf.get("servers")
+        if servers and isinstance(servers, list):
+            if not any(server['url'].startswith('http://') for server in servers):
+                return CheckResult.PASSED, conf
+
         components = conf.get("components")
         security_def = conf.get("securityDefinitions")
         if components and isinstance(components, dict):


Test patch:
diff --git a/tests/openapi/checks/resource/generic/example_ClearTextAPIKey/fail3.json b/tests/openapi/checks/resource/generic/example_ClearTextAPIKey/fail3.json
new file mode 100644
--- /dev/null
+++ b/tests/openapi/checks/resource/generic/example_ClearTextAPIKey/fail3.json
@@ -0,0 +1,42 @@
+{
+  "swagger": "2.0",
+  "info": {
+    "title": "Simple API overview",
+    "version": "1.0.0"
+  },
+  "schemes": [
+    "https",
+    "http"
+  ],
+  "paths": {
+    "/pets": {
+      "post": {
+        "description": "Creates a new pet in the store",
+        "responses": {
+          "200": {
+            "description": "200 response"
+          }
+        },
+        "operationId": "addPet",
+        "security": [
+          {
+            "apiKey1": [],
+            "apiKey3": []
+          }
+        ]
+      }
+    }
+  },
+  "securityDefinitions": {
+    "apiKey1": {
+      "type": "apiKey",
+      "name": "X-API-Key",
+      "in": "header"
+    },
+    "apiKey3": {
+      "type": "apiKey",
+      "name": "X-API-Key",
+      "in": "query"
+    }
+  }
+}
\ No newline at end of file
diff --git a/tests/openapi/checks/resource/generic/example_ClearTextAPIKey/fail3.yaml b/tests/openapi/checks/resource/generic/example_ClearTextAPIKey/fail3.yaml
new file mode 100644
--- /dev/null
+++ b/tests/openapi/checks/resource/generic/example_ClearTextAPIKey/fail3.yaml
@@ -0,0 +1,27 @@
+swagger: "2.0"
+info:
+  title: Simple API overview
+  version: 1.0.0
+schemes:
+  - https
+  - http
+paths:
+  /pets:
+    post:
+      description: Creates a new pet in the store
+      responses:
+        "200":
+          description: 200 response
+      operationId: addPet
+      security:
+        - apiKey1: []
+          apiKey3: []
+securityDefinitions:
+  apiKey1:
+    type: apiKey
+    name: X-API-Key
+    in: header
+  apiKey3:
+    type: apiKey
+    name: X-API-Key
+    in: query
diff --git a/tests/openapi/checks/resource/generic/example_ClearTextAPIKey/fail4.json b/tests/openapi/checks/resource/generic/example_ClearTextAPIKey/fail4.json
new file mode 100644
--- /dev/null
+++ b/tests/openapi/checks/resource/generic/example_ClearTextAPIKey/fail4.json
@@ -0,0 +1,56 @@
+{
+  "openapi": "3.0.0",
+  "info": {
+    "title": "Simple API overview",
+    "version": "1.0.0"
+  },
+  "servers": [
+    {
+      "url": "https://localhost:8000",
+      "description": "Local server"
+    },
+    {
+      "url": "http://example.com",
+      "description": "Example"
+    }
+  ],
+  "paths": {
+    "/pets": {
+      "post": {
+        "description": "Creates a new pet in the store",
+        "responses": {
+          "200": {
+            "description": "200 response"
+          }
+        },
+        "operationId": "addPet",
+        "security": [
+          {
+            "apiKey1": [],
+            "apiKey2": [],
+            "apiKey3": []
+          }
+        ]
+      }
+    }
+  },
+  "components": {
+    "securitySchemes": {
+      "apiKey1": {
+        "type": "apiKey",
+        "name": "X-API-Key",
+        "in": "header"
+      },
+      "apiKey2": {
+        "type": "apiKey",
+        "name": "X-API-Key",
+        "in": "cookie"
+      },
+      "apiKey3": {
+        "type": "apiKey",
+        "name": "X-API-Key",
+        "in": "query"
+      }
+    }
+  }
+}
diff --git a/tests/openapi/checks/resource/generic/example_ClearTextAPIKey/fail4.yaml b/tests/openapi/checks/resource/generic/example_ClearTextAPIKey/fail4.yaml
new file mode 100644
--- /dev/null
+++ b/tests/openapi/checks/resource/generic/example_ClearTextAPIKey/fail4.yaml
@@ -0,0 +1,35 @@
+openapi: 3.0.0
+info:
+  title: Simple API overview
+  version: 1.0.0
+servers:
+  - url: https://localhost:8000
+    description: Local server
+  - url: http://example.com
+    description: example
+paths:
+  /pets:
+    post:
+      description: Creates a new pet in the store
+      responses:
+        '200':
+          description: 200 response
+      operationId: addPet
+      security:
+        - apiKey1: []
+          apiKey2: []
+          apiKey3: []
+components:
+  securitySchemes:
+    apiKey1:
+      type: apiKey
+      name: X-API-Key
+      in: header
+    apiKey2:
+      type: apiKey
+      name: X-API-Key
+      in: cookie
+    apiKey3:
+      type: apiKey
+      name: X-API-Key
+      in: query
diff --git a/tests/openapi/checks/resource/generic/example_ClearTextAPIKey/pass3.json b/tests/openapi/checks/resource/generic/example_ClearTextAPIKey/pass3.json
new file mode 100644
--- /dev/null
+++ b/tests/openapi/checks/resource/generic/example_ClearTextAPIKey/pass3.json
@@ -0,0 +1,41 @@
+{
+  "swagger": "2.0",
+  "info": {
+    "title": "Simple API overview",
+    "version": "1.0.0"
+  },
+  "schemes": [
+    "https"
+  ],
+  "paths": {
+    "/pets": {
+      "post": {
+        "description": "Creates a new pet in the store",
+        "responses": {
+          "200": {
+            "description": "200 response"
+          }
+        },
+        "operationId": "addPet",
+        "security": [
+          {
+            "apiKey1": [],
+            "apiKey3": []
+          }
+        ]
+      }
+    }
+  },
+  "securityDefinitions": {
+    "apiKey1": {
+      "type": "apiKey",
+      "name": "X-API-Key",
+      "in": "header"
+    },
+    "apiKey3": {
+      "type": "apiKey",
+      "name": "X-API-Key",
+      "in": "query"
+    }
+  }
+}
\ No newline at end of file
diff --git a/tests/openapi/checks/resource/generic/example_ClearTextAPIKey/pass3.yaml b/tests/openapi/checks/resource/generic/example_ClearTextAPIKey/pass3.yaml
new file mode 100644
--- /dev/null
+++ b/tests/openapi/checks/resource/generic/example_ClearTextAPIKey/pass3.yaml
@@ -0,0 +1,26 @@
+swagger: "2.0"
+info:
+  title: Simple API overview
+  version: 1.0.0
+schemes:
+  - https
+paths:
+  /pets:
+    post:
+      description: Creates a new pet in the store
+      responses:
+        "200":
+          description: 200 response
+      operationId: addPet
+      security:
+        - apiKey1: []
+          apiKey3: []
+securityDefinitions:
+  apiKey1:
+    type: apiKey
+    name: X-API-Key
+    in: header
+  apiKey3:
+    type: apiKey
+    name: X-API-Key
+    in: query
diff --git a/tests/openapi/checks/resource/generic/example_ClearTextAPIKey/pass4.json b/tests/openapi/checks/resource/generic/example_ClearTextAPIKey/pass4.json
new file mode 100644
--- /dev/null
+++ b/tests/openapi/checks/resource/generic/example_ClearTextAPIKey/pass4.json
@@ -0,0 +1,52 @@
+{
+  "openapi": "3.0.0",
+  "info": {
+    "title": "Simple API overview",
+    "version": "1.0.0"
+  },
+  "servers": [
+    {
+      "url": "https://localhost:8000",
+      "description": "Local server"
+    }
+  ],
+  "paths": {
+    "/pets": {
+      "post": {
+        "description": "Creates a new pet in the store",
+        "responses": {
+          "200": {
+            "description": "200 response"
+          }
+        },
+        "operationId": "addPet",
+        "security": [
+          {
+            "apiKey1": [],
+            "apiKey2": [],
+            "apiKey3": []
+          }
+        ]
+      }
+    }
+  },
+  "components": {
+    "securitySchemes": {
+      "apiKey1": {
+        "type": "apiKey",
+        "name": "X-API-Key",
+        "in": "header"
+      },
+      "apiKey2": {
+        "type": "apiKey",
+        "name": "X-API-Key",
+        "in": "cookie"
+      },
+      "apiKey3": {
+        "type": "apiKey",
+        "name": "X-API-Key",
+        "in": "query"
+      }
+    }
+  }
+}
diff --git a/tests/openapi/checks/resource/generic/example_ClearTextAPIKey/pass4.yaml b/tests/openapi/checks/resource/generic/example_ClearTextAPIKey/pass4.yaml
new file mode 100644
--- /dev/null
+++ b/tests/openapi/checks/resource/generic/example_ClearTextAPIKey/pass4.yaml
@@ -0,0 +1,33 @@
+openapi: 3.0.0
+info:
+  title: Simple API overview
+  version: 1.0.0
+servers:
+  - url: https://localhost:8000
+    description: Local server
+paths:
+  /pets:
+    post:
+      description: Creates a new pet in the store
+      responses:
+        '200':
+          description: 200 response
+      operationId: addPet
+      security:
+        - apiKey1: []
+          apiKey2: []
+          apiKey3: []
+components:
+  securitySchemes:
+    apiKey1:
+      type: apiKey
+      name: X-API-Key
+      in: header
+    apiKey2:
+      type: apiKey
+      name: X-API-Key
+      in: cookie
+    apiKey3:
+      type: apiKey
+      name: X-API-Key
+      in: query
diff --git a/tests/openapi/checks/resource/generic/test_ClearTextAPIKey.py b/tests/openapi/checks/resource/generic/test_ClearTextAPIKey.py
--- a/tests/openapi/checks/resource/generic/test_ClearTextAPIKey.py
+++ b/tests/openapi/checks/resource/generic/test_ClearTextAPIKey.py
@@ -23,12 +23,20 @@ def test_summary(self):
             "/pass.json",
             "/pass2.yaml",
             "/pass2.json",
+            "/pass3.yaml",
+            "/pass3.json",
+            "/pass4.yaml",
+            "/pass4.json",
         }
         failing_resources = {
             "/fail.yaml",
             "/fail.json",
             "/fail2.yaml",
             "/fail2.json",
+            "/fail3.yaml",
+            "/fail3.json",
+            "/fail4.yaml",
+            "/fail4.json",
         }
 
         passed_check_resources = {c.file_path for c in report.passed_checks}