Repository: bridgecrewio/checkov

Issue:
CKV2_K8S_6: Cannot skip test
I am receiving a failure on `CKV2_K8S_6`, and I am unable to skip the test when running Checkov scans.

Here's my `Deployment` manifest where I'm seeing the issue. Checkov is successfully skipping `CKV_K8S_38`.

```
annotations:
    "checkov.io/skip1": "CKV_K8S_38=service account is necessary for S3 export bucket upload permissions"
    "checkov.io/skip2": "CKV2_K8S_6=on roadmap"
```

Results when running a Checkov scan. Output has been omitted or obfuscated.

```
checkov -f deployment.yaml --framework kubernetes

...

Check: CKV2_K8S_6: "Minimize the admission of pods which lack an associated NetworkPolicy"
	FAILED for resource: Pod.default...........

...
```


Discussion:
I can skip the test if I pass it as a command-line argument.

```
checkov -f deployment.yaml --framework kubernetes --skip-check CKV2_K8S_6
```

Though, interestingly, the test does not show up at all in the skipped section of the CLI output if I skip it this way.

```
Check: CKV_K8S_38: "Ensure that Service Account Tokens are only mounted where necessary"
	SKIPPED for resource: Deployment.test.....
	Suppress comment: service account is necessary for S3 export bucket upload permissions
	File: /deployment.yaml:26-109
	Guide: https://docs.bridgecrew.io/docs/bc_k8s_35
Check: CKV_K8S_38: "Ensure that Service Account Tokens are only mounted where necessary"
	SKIPPED for resource: Deployment.test.....
	Suppress comment: service account is necessary for S3 export bucket upload permissions
	File: /deployment.yaml:111-198
	Guide: https://docs.bridgecrew.io/docs/bc_k8s_35
```
This is occurring on Checkov version `2.2.302`.

```
checkov --version
2.2.302
```
Here is a sanitized version of the `Deployment` on which this test cannot be skipped:

```
apiVersion: apps/v1
kind: Deployment
metadata:
  name: release-name-microservice
  namespace: test
  annotations:
    "checkov.io/skip1": "CKV_K8S_14=ignored as digests are used"
    "checkov.io/skip2": "CKV_K8S_43=ignored as digests are used"
    "checkov.io/skip3": "CKV2_K8S_6=on roadmap"
  labels:
    helm.sh/chart: microservice-1.1.0
    app.kubernetes.io/name: microservice
    app.kubernetes.io/instance: release-name
    app.kubernetes.io/managed-by: Helm
spec:
  replicas: 1
  strategy:
    type: RollingUpdate
  selector:
    matchLabels:
      app.kubernetes.io/name: microservice
      app.kubernetes.io/instance: release-name
  template:
    metadata:
      labels:
        app.kubernetes.io/name: microservice
        app.kubernetes.io/instance: release-name
    spec:
      serviceAccountName: release-name-microservice
      automountServiceAccountToken: false
      securityContext:
        seccompProfile:
          type: RuntimeDefault
      terminationGracePeriodSeconds: 30
      volumes:
        - name: microservice-tmp-dir
          emptyDir: {}
      containers:
        - name: release-name
          securityContext:
            allowPrivilegeEscalation: false
            capabilities:
              drop:
              - NET_RAW
              - ALL
            readOnlyRootFilesystem: true
            runAsUser: 12000
          image: "RECACTED"
          imagePullPolicy: Always
          volumeMounts:
            - name: microservice-tmp-dir
              mountPath: /tmp
          ports:
            - containerPort: 80
              name: http
              protocol: TCP
          livenessProbe:
            httpGet:
              path: /
              port: http
          readinessProbe:
            httpGet:
              path: /
              port: http
          resources:
            limits:
              cpu: 100m
              memory: 128Mi
            requests:
              cpu: 100m
              memory: 128Mi
```
This is also occurring on `StatefulSet` Helm charts I've written for `$EMPLOYER`.
hey @ScottKinder thanks for reaching out.
yeah, it looks like we are not considering skip annotations for our graph checks (CKV2_K8S_x). I will see what I can do about it 🧐 

Resolving patch:
diff --git a/checkov/kubernetes/kubernetes_utils.py b/checkov/kubernetes/kubernetes_utils.py
--- a/checkov/kubernetes/kubernetes_utils.py
+++ b/checkov/kubernetes/kubernetes_utils.py
@@ -3,11 +3,11 @@
 import logging
 import os
 from copy import deepcopy
-from typing import Dict, Any
+from typing import Dict, Any, TYPE_CHECKING
 
 import dpath
 
-from checkov.common.typing import _SkippedCheck
+from checkov.common.models.enums import CheckResult
 from checkov.runner_filter import RunnerFilter
 from checkov.common.bridgecrew.integration_features.features.policy_metadata_integration import integration as metadata_integration
 from checkov.common.models.consts import YAML_COMMENT_MARK
@@ -16,6 +16,9 @@
 from checkov.common.util.type_forcers import force_list
 from checkov.kubernetes.parser.parser import parse
 
+if TYPE_CHECKING:
+    from checkov.common.typing import _SkippedCheck, _CheckResult, _EntityContext
+
 K8_POSSIBLE_ENDINGS = {".yaml", ".yml", ".json"}
 DEFAULT_NESTED_RESOURCE_TYPE = "Pod"
 SUPPORTED_POD_CONTAINERS_TYPES = {"Deployment", "DeploymentConfig", "DaemonSet", "Job", "ReplicaSet", "ReplicationController", "StatefulSet"}
@@ -239,3 +242,20 @@ def remove_metadata_from_attribute(attribute: dict[str, Any] | None) -> None:
     if isinstance(attribute, dict):
         attribute.pop("__startline__", None)
         attribute.pop("__endline__", None)
+
+
+def create_check_result(check_result: _CheckResult, entity_context: _EntityContext, check_id: str) -> _CheckResult:
+    """Creates a cleaned version of check_result for further usage"""
+
+    clean_check_result: _CheckResult = {
+        "result": check_result["result"],
+        "evaluated_keys": check_result["evaluated_keys"],
+    }
+
+    for skipped_check in entity_context.get("skipped_checks", []):
+        if skipped_check["id"] == check_id:
+            clean_check_result["result"] = CheckResult.SKIPPED
+            clean_check_result["suppress_comment"] = skipped_check["suppress_comment"]
+            break
+
+    return clean_check_result
diff --git a/checkov/kubernetes/runner.py b/checkov/kubernetes/runner.py
--- a/checkov/kubernetes/runner.py
+++ b/checkov/kubernetes/runner.py
@@ -27,6 +27,7 @@
     get_resource_id,
     K8_POSSIBLE_ENDINGS,
     PARENT_RESOURCE_ID_KEY_NAME,
+    create_check_result,
 )
 from checkov.runner_filter import RunnerFilter
 
@@ -258,10 +259,9 @@ def mutate_kubernetes_graph_results(
                 entity_file_abs_path = _get_entity_abs_path(root_folder, entity_file_path)
                 entity_context = self.get_entity_context(entity=entity, entity_file_path=entity_file_path)
 
-                clean_check_result: _CheckResult = {
-                    "result": check_result["result"],
-                    "evaluated_keys": check_result["evaluated_keys"],
-                }
+                clean_check_result = create_check_result(
+                    check_result=check_result, entity_context=entity_context, check_id=check.id
+                )
 
                 record = Record(
                     check_id=check.id,


Test patch:
diff --git a/tests/kubernetes/graph/resources/graph_check.yaml b/tests/kubernetes/graph/resources/graph_check.yaml
new file mode 100644
--- /dev/null
+++ b/tests/kubernetes/graph/resources/graph_check.yaml
@@ -0,0 +1,139 @@
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: internal-proxy-deployment
+  labels:
+    app: internal-proxy
+spec:
+  selector:
+    matchLabels:
+      app: internal-proxy
+  template:
+    metadata:
+      labels:
+        app: internal-proxy
+    spec:
+      containers:
+      - name: internal-api
+        image: madhuakula/k8s-goat-internal-api
+        resources:
+          limits:
+            cpu: 30m
+            memory: 40Mi
+          requests:
+            cpu: 30m
+            memory: 40Mi
+        ports:
+        - containerPort: 3000
+      - name: info-app
+        image: madhuakula/k8s-goat-info-app
+        resources:
+          limits:
+            cpu: 30m
+            memory: 40Mi
+          requests:
+            cpu: 30m
+            memory: 40Mi
+        ports:
+        - containerPort: 5000
+---
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: external-proxy-deployment
+  labels:
+    app: external-proxy
+spec:
+  selector:
+    matchLabels:
+      app: external-proxy
+  template:
+    metadata:
+      labels:
+        app: external-proxy
+    spec:
+      containers:
+      - name: internal-api
+        image: madhuakula/k8s-goat-internal-api
+        resources:
+          limits:
+            cpu: 30m
+            memory: 40Mi
+          requests:
+            cpu: 30m
+            memory: 40Mi
+        ports:
+        - containerPort: 3000
+      - name: info-app
+        image: madhuakula/k8s-goat-info-app
+        resources:
+          limits:
+            cpu: 30m
+            memory: 40Mi
+          requests:
+            cpu: 30m
+            memory: 40Mi
+        ports:
+        - containerPort: 5000
+---
+apiVersion: networking.k8s.io/v1
+kind: NetworkPolicy
+metadata:
+  name: test-network-policy
+  namespace: default
+spec:
+  podSelector:
+    matchLabels:
+      app: internal-proxy
+  policyTypes:
+    - Ingress
+    - Egress
+  ingress:
+    - from:
+        - ipBlock:
+            cidr: 172.17.0.0/16
+            except:
+              - 172.17.1.0/24
+        - podSelector:
+            matchLabels:
+              app: internal-proxy
+      ports:
+        - protocol: TCP
+          port: 6379
+  egress:
+    - to:
+        - ipBlock:
+            cidr: 10.0.0.0/24
+      ports:
+        - protocol: TCP
+          port: 5978
+---
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: skipdeployment
+  annotations:
+    "checkov.io/skip": "CKV2_K8S_6=skip it"
+  labels:
+    app: skip
+spec:
+  selector:
+    matchLabels:
+      app: skip
+  template:
+    metadata:
+      labels:
+        app: skip
+    spec:
+      containers:
+      - name: info-app
+        image: madhuakula/k8s-goat-info-app
+        resources:
+          limits:
+            cpu: 30m
+            memory: 40Mi
+          requests:
+            cpu: 30m
+            memory: 40Mi
+        ports:
+        - containerPort: 5000
diff --git a/tests/kubernetes/graph/test_running_graph_checks.py b/tests/kubernetes/graph/test_running_graph_checks.py
--- a/tests/kubernetes/graph/test_running_graph_checks.py
+++ b/tests/kubernetes/graph/test_running_graph_checks.py
@@ -1,27 +1,29 @@
-import itertools
-import os
-import unittest
+from pathlib import Path
 
 import pytest
+from pytest_mock import MockerFixture
 
 from checkov.kubernetes.runner import Runner
+from checkov.runner_filter import RunnerFilter
 
-TEST_DIRNAME = os.path.dirname(os.path.realpath(__file__))
+RESOURCES_DIR = Path(__file__).parent / "resources"
 
 
-class TestRunningGraphChecks(unittest.TestCase):
-    @pytest.mark.skip("Graph checks not written yet for K8s")
-    def test_runner(self):
-        root_dir = os.path.realpath(os.path.join(TEST_DIRNAME, "../runner/resources"))
-        report = Runner().run(root_dir)
-        assert any(
-            check.check_id == "CKV2_K8S_21" for check in itertools.chain(report.failed_checks, report.passed_checks))
-        summary = report.get_summary()
-        self.assertEqual(summary["passed"], 0)
-        self.assertEqual(summary["failed"], 5)
-        self.assertEqual(summary["skipped"], 0)
-        self.assertEqual(summary["parsing_errors"], 0)
+@pytest.mark.parametrize("graph_framework", ["NETWORKX", "IGRAPH"])
+def test_runner(mocker: MockerFixture, graph_framework):
+    # given
+    test_file_path = RESOURCES_DIR / "graph_check.yaml"
+    runner_filter = RunnerFilter(checks=["CKV2_K8S_6"])
 
+    mocker.patch.dict("os.environ", {"CHECKOV_GRAPH_FRAMEWORK": graph_framework})
 
-if __name__ == '__main__':
-    unittest.main()
+    # when
+    report = Runner().run(root_folder=None, files=[str(test_file_path)], runner_filter=runner_filter)
+
+    #  when
+    summary = report.get_summary()
+
+    assert summary["passed"] == 1
+    assert summary["failed"] == 1
+    assert summary["skipped"] == 1
+    assert summary["parsing_errors"] == 0