Repository: bridgecrewio/checkov

Issue:
bridgecrew/checkov Docker container hangs without exiting since version 2.1.147/latest
**Describe the issue**
We have a CI/CD pipeline using `bridgecrew/checkov:latest` to run scans against our Helm charts in Azure DevOps. Since today, we've been experiencing issues where the pipeline successfully pulls the Docker image and then just hangs indefinitely, until the 1hr timeout is reached. We have locked our pipelines in to using `bridgecrew/checkov:2.1.146` and that appears to be working, so it seems to be specific to 2.1.147/latest.

**Examples**
Unfortunately, due to the usual company policy reasons, I can't share the full details. But a simple example of a failing scan could be:
1. Download and extract the MongoDB 13.0.1 Helm chart from <https://bitnami.com/stack/mongodb/helm>.
2. Navigate your CLI of choice into the extracted Helm chart.
3. Run the following command: `docker run -v ${PWD}:/helm bridgecrew/checkov:latest --directory /helm --framework helm`

You will observe the stack trace below:


**Exception Trace**
2022-08-23 09:18:15,498 [MainThread  ] [WARNI]  Error processing helm chart common at dir: /helm/charts/common. Working dir: /tmp/tmpaxr3z0on. Error details: Error: library charts are not installable

Process ForkProcess-4:
Traceback (most recent call last):
  File "/usr/local/lib/python3.8/multiprocessing/process.py", line 315, in _bootstrap
    self.run()
  File "/usr/local/lib/python3.8/multiprocessing/process.py", line 108, in run
    self._target(*self._args, **self._kwargs)
  File "/usr/local/lib/python3.8/site-packages/checkov/common/parallelizer/parallel_runner.py", line 37, in func_wrapper
    result = original_func(item)
  File "/usr/local/lib/python3.8/site-packages/checkov/helm/runner.py", line 254, in <lambda>
    list(parallel_runner.run_function(lambda cd: Runner._convert_chart_to_k8s(cd, self.root_folder,
  File "/usr/local/lib/python3.8/site-packages/checkov/helm/runner.py", line 221, in _convert_chart_to_k8s
    o, _ = Runner._get_binary_output(chart_item, target_folder_path, helm_command, runner_filter)
TypeError: cannot unpack non-iterable NoneType object

**Desktop (please complete the following information):**
 - OS: Windows 11 (our pipelines are using Ubuntu 22.04 and we've replicated on Ubuntu 20.04)
 - Checkov 2.1.147 / latest

**Additional context**
N/A



Discussion:

Resolving patch:
diff --git a/checkov/helm/runner.py b/checkov/helm/runner.py
--- a/checkov/helm/runner.py
+++ b/checkov/helm/runner.py
@@ -21,6 +21,7 @@
 from checkov.kubernetes.graph_builder.local_graph import KubernetesLocalGraph
 from checkov.kubernetes.runner import Runner as k8_runner
 from checkov.runner_filter import RunnerFilter
+from checkov.common.parallelizer.parallel_runner import parallel_runner
 
 
 class K8sHelmRunner(k8_runner):
@@ -103,8 +104,7 @@ def check_system_deps(self) -> str | None:
             logging.info(f"Error running necessary tools to process {self.check_type} checks.")
             return self.check_type
 
-    @staticmethod
-    def _parse_output(target_dir: str, output: bytes) -> None:
+    def _parse_output(self, target_dir: str, output: bytes) -> None:
         output = str(output, 'utf-8')
         reader = io.StringIO(output)
         cur_source_file = None
@@ -150,33 +150,26 @@ def _parse_output(target_dir: str, output: bytes) -> None:
         if cur_writer:
             cur_writer.close()
 
-    @staticmethod
-    def _get_target_dir(chart_item, root_folder, target_folder_path):
+    def _convert_chart_to_k8s(self, chart_item):
         (chart_dir, chart_meta) = chart_item
-        target_dir = chart_dir.replace(root_folder, f'{target_folder_path}/')
+        target_dir = chart_dir.replace(self.root_folder, f'{self.target_folder_path}/')
         target_dir.replace("//", "/")
         chart_name = chart_meta.get('name', chart_meta.get('Name'))
+        chart_version = chart_meta.get('version', chart_meta.get('Version'))
         if not chart_name:
             logging.info(
                 f"Error parsing chart located {chart_dir}, chart has no name available",
                 exc_info=True,
             )
-            return None
+            return
         if target_dir.endswith('/'):
             target_dir = target_dir[:-1]
         if target_dir.endswith(chart_name):
             target_dir = target_dir[:-len(chart_name)]
-        return target_dir
-
-    @staticmethod 
-    def _get_binary_output(chart_item, target_dir, helm_command, runner_filter):
-        (chart_dir, chart_meta) = chart_item
-        chart_name = chart_meta.get('name', chart_meta.get('Name'))
-        chart_version = chart_meta.get('version', chart_meta.get('Version'))
         logging.info(
             f"Processing chart found at: {chart_dir}, name: {chart_name}, version: {chart_version}")
         # dependency list is nicer to parse than dependency update.
-        helm_binary_list_chart_deps = subprocess.Popen([helm_command, 'dependency', 'list', chart_dir], stdout=subprocess.PIPE, stderr=subprocess.PIPE)  # nosec
+        helm_binary_list_chart_deps = subprocess.Popen([self.helm_command, 'dependency', 'list', chart_dir], stdout=subprocess.PIPE, stderr=subprocess.PIPE)  # nosec
         o, e = helm_binary_list_chart_deps.communicate()
         logging.debug(
             f"Ran helm command to get dependency output. Chart: {chart_name}. dir: {target_dir}. Output: {str(o, 'utf-8')}. Errors: {str(e, 'utf-8')}")
@@ -188,9 +181,9 @@ def _get_binary_output(chart_item, target_dir, helm_command, runner_filter):
                 logging.warning(
                     f"Error processing helm dependancies for {chart_name} at source dir: {chart_dir}. Working dir: {target_dir}. Error details: {str(e, 'utf-8')}")
 
-        helm_command_args = [helm_command, 'template', '--dependency-update', chart_dir]
-        if runner_filter.var_files:
-            for var in runner_filter.var_files:
+        helm_command_args = [self.helm_command, 'template', '--dependency-update', chart_dir]
+        if self.runner_filter.var_files:
+            for var in self.runner_filter.var_files:
                 helm_command_args.append("--values")
                 helm_command_args.append(var)
 
@@ -201,61 +194,39 @@ def _get_binary_output(chart_item, target_dir, helm_command, runner_filter):
             if e:
                 logging.warning(
                     f"Error processing helm chart {chart_name} at dir: {chart_dir}. Working dir: {target_dir}. Error details: {str(e, 'utf-8')}")
-                return None
             logging.debug(
                 f"Ran helm command to template chart output. Chart: {chart_name}. dir: {target_dir}. Output: {str(o, 'utf-8')}. Errors: {str(e, 'utf-8')}")
-            return o, chart_item
 
         except Exception:
             logging.info(
                 f"Error processing helm chart {chart_name} at dir: {chart_dir}. Working dir: {target_dir}.",
                 exc_info=True,
             )
-            return None 
 
-    @staticmethod
-    def _convert_chart_to_k8s(chart_item, root_folder, target_folder_path, helm_command, runner_filter):
-        target_dir = Runner._get_target_dir(chart_item, root_folder, target_folder_path)
-        if not target_dir:
-            return
-        o, _ = Runner._get_binary_output(chart_item, target_folder_path, helm_command, runner_filter)
         try:
-            Runner._parse_output(target_dir, o)
+            self._parse_output(target_dir, o)
         except Exception:
-            (chart_dir, chart_meta) = chart_item
-            chart_name = chart_meta.get('name', chart_meta.get('Name'))
             logging.info(
                 f"Error parsing output {chart_name} at dir: {chart_dir}. Working dir: {target_dir}.",
                 exc_info=True,
             )
 
-    @staticmethod
-    def _get_chart_dir_and_meta(root_folder, files, runner_filter):
+    def convert_helm_to_k8s(self, root_folder: str, files: list[str], runner_filter: RunnerFilter) -> list[tuple[Any, dict[str, Any]]]:
+        self.root_folder = root_folder
+        self.runner_filter = runner_filter
         chart_directories = find_chart_directories(root_folder, files, runner_filter.excluded_paths)
         chart_dir_and_meta = list(parallel_runner.run_function(
-            lambda cd: (cd, Runner.parse_helm_chart_details(cd)), chart_directories))
+            lambda cd: (cd, self.parse_helm_chart_details(cd)), chart_directories))
         # remove parsing failures
         chart_dir_and_meta = [chart_meta for chart_meta in chart_dir_and_meta if chart_meta[1]]
-        return chart_dir_and_meta
-    
-    @staticmethod
-    def _get_processed_chart_dir_and_meta(chart_dir_and_meta, root_folder):
+        self.target_folder_path = tempfile.mkdtemp()
+
         processed_chart_dir_and_meta = []
         for chart_dir, chart_meta in chart_dir_and_meta:
             processed_chart_dir_and_meta.append((chart_dir.replace(root_folder, ""), chart_meta))
-        return processed_chart_dir_and_meta
 
-    def convert_helm_to_k8s(self, root_folder: str, files: list[str], runner_filter: RunnerFilter) -> list[tuple[Any, dict[str, Any]]]:
-        self.root_folder = root_folder
-        self.runner_filter = runner_filter
-        self.target_folder_path = tempfile.mkdtemp()
-        chart_dir_and_meta = Runner._get_chart_dir_and_meta(self.root_folder, files, self.runner_filter)
-        
-        list(parallel_runner.run_function(lambda cd: Runner._convert_chart_to_k8s(cd, self.root_folder, 
-                                                                                         self.target_folder_path, 
-                                                                                         self.helm_command, 
-                                                                                         self.runner_filter), chart_dir_and_meta))
-        return Runner._get_processed_chart_dir_and_meta(chart_dir_and_meta, self.root_folder)
+        list(parallel_runner.run_function(lambda cd: self._convert_chart_to_k8s(cd), chart_dir_and_meta))
+        return processed_chart_dir_and_meta
 
     def run(self, root_folder: str | None, external_checks_dir: list[str] | None = None, files: list[str] | None = None,
             runner_filter: RunnerFilter = RunnerFilter(), collect_skip_comments: bool = True) -> Report:
diff --git a/checkov/kustomize/runner.py b/checkov/kustomize/runner.py
--- a/checkov/kustomize/runner.py
+++ b/checkov/kustomize/runner.py
@@ -357,17 +357,14 @@ def _get_env_or_base_path_prefix(filePath, kustomizeProcessedFolderAndMeta) -> s
         return env_or_base_path_prefix
 
     @staticmethod
-    def _get_binary_output(filePath, kustomizeProcessedFolderAndMeta, templateRendererCommand):
+    def _run_kustomize_parser(filePath, sharedKustomizeFileMappings, kustomizeProcessedFolderAndMeta, templateRendererCommand, target_folder_path):
         logging.debug(f"Kustomization at {filePath} likley a {kustomizeProcessedFolderAndMeta[filePath].get('type')}")
         try:
             output = Runner._get_kubectl_output(filePath, templateRendererCommand, kustomizeProcessedFolderAndMeta)
-            return output
         except Exception:
             logging.warning(f"Error building Kustomize output at dir: {filePath}.", exc_info=True)
-            return None
+            return
 
-    @staticmethod
-    def _parse_output(output, filePath, kustomizeProcessedFolderAndMeta, target_folder_path, sharedKustomizeFileMappings):
         env_or_base_path_prefix = Runner._get_env_or_base_path_prefix(filePath, kustomizeProcessedFolderAndMeta)
         if env_or_base_path_prefix is None:
             logging.warning(f"env_or_base_path_prefix is None, filePath: {filePath}", exc_info=True)
@@ -381,13 +378,6 @@ def _parse_output(output, filePath, kustomizeProcessedFolderAndMeta, target_fold
         cur_writer = Runner._get_parsed_output(filePath, extractDir, output, sharedKustomizeFileMappings)
         if cur_writer:
             Runner._curWriterValidateStoreMapAndClose(cur_writer, filePath, sharedKustomizeFileMappings)
-        
-    @staticmethod
-    def _run_kustomize_parser(filePath, sharedKustomizeFileMappings, kustomizeProcessedFolderAndMeta, templateRendererCommand, target_folder_path):
-        output = Runner._get_binary_output(filePath, kustomizeProcessedFolderAndMeta, templateRendererCommand)
-        if not output:
-            return
-        Runner._parse_output(output, filePath, kustomizeProcessedFolderAndMeta, target_folder_path, sharedKustomizeFileMappings)
 
     def run_kustomize_to_k8s(self, root_folder, files, runner_filter):
         kustomizeDirectories = find_kustomize_directories(root_folder, files, runner_filter.excluded_paths)
diff --git a/checkov/runner_filter.py b/checkov/runner_filter.py
--- a/checkov/runner_filter.py
+++ b/checkov/runner_filter.py
@@ -3,7 +3,7 @@
 import logging
 import fnmatch
 from collections.abc import Iterable
-from typing import Any, Set, Optional, Union, List, TYPE_CHECKING, Dict
+from typing import Set, Optional, Union, List, TYPE_CHECKING, Dict
 
 from checkov.common.bridgecrew.code_categories import CodeCategoryMapping, CodeCategoryConfiguration
 from checkov.common.bridgecrew.severities import Severity, Severities
@@ -202,52 +202,3 @@ def is_policy_filtered(self, check_id: str) -> bool:
         if not self.filtered_policy_ids:
             return True
         return check_id in self.filtered_policy_ids
-    
-    def to_dict(self) -> Dict[str, Any]:
-        result: Dict[str, Any] = {}
-        for key, value in self.__dict__.items():
-            result[key] = value
-        return result
-
-    @staticmethod
-    def from_dict(obj: Dict[str, Any]) -> RunnerFilter:
-        framework = obj.get('framework')
-        checks = obj.get('checks')
-        skip_checks = obj.get('skip_checks')
-        include_all_checkov_policies = obj.get('include_all_checkov_policies')
-        if include_all_checkov_policies is None:
-            include_all_checkov_policies = True
-        download_external_modules = obj.get('download_external_modules')
-        if download_external_modules is None:
-            download_external_modules = False
-        external_modules_download_path = obj.get('external_modules_download_path')
-        if external_modules_download_path is None:
-            external_modules_download_path = DEFAULT_EXTERNAL_MODULES_DIR
-        evaluate_variables = obj.get('evaluate_variables')
-        if evaluate_variables is None:
-            evaluate_variables = True
-        runners = obj.get('runners')
-        skip_framework = obj.get('skip_framework')
-        excluded_paths = obj.get('excluded_paths')
-        all_external = obj.get('all_external')
-        if all_external is None:
-            all_external = False
-        var_files = obj.get('var_files')
-        skip_cve_package = obj.get('skip_cve_package')
-        use_enforcement_rules = obj.get('use_enforcement_rules')
-        if use_enforcement_rules is None:
-            use_enforcement_rules = False
-        filtered_policy_ids = obj.get('filtered_policy_ids')
-        show_progress_bar = obj.get('show_progress_bar')
-        if show_progress_bar is None:
-            show_progress_bar = True
-        secrets_scan_file_type = obj.get('secrets_scan_file_type')
-        run_image_referencer = obj.get('run_image_referencer')
-        if run_image_referencer is None:
-            run_image_referencer = False
-        runner_filter = RunnerFilter(framework, checks, skip_checks, include_all_checkov_policies,
-                                     download_external_modules, external_modules_download_path, evaluate_variables,
-                                     runners, skip_framework, excluded_paths, all_external, var_files,
-                                     skip_cve_package, use_enforcement_rules, filtered_policy_ids, show_progress_bar,
-                                     secrets_scan_file_type, run_image_referencer)
-        return runner_filter


Test patch: