diff --git a/checkov/azure_pipelines/runner.py b/checkov/azure_pipelines/runner.py index 1822f4dfe0f..b18bc839525 100644 --- a/checkov/azure_pipelines/runner.py +++ b/checkov/azure_pipelines/runner.py @@ -34,10 +34,9 @@ def is_workflow_file(self, file_path: str) -> bool: return file_path.endswith(('azure-pipelines.yml', 'azure-pipelines.yaml')) def get_resource(self, file_path: str, key: str, supported_entities: Iterable[str], - definitions: dict[str, Any] | None = None) -> str: + start_line: int = -1, end_line: int = -1) -> str: if not self.definitions or not isinstance(self.definitions, dict): return key - start_line, end_line = self.get_start_and_end_lines(key) resource_name = generate_resource_key_recursive(start_line, end_line, self.definitions[file_path]) return resource_name if resource_name else key diff --git a/checkov/circleci_pipelines/checks/ReverseShellNetcat.py b/checkov/circleci_pipelines/checks/ReverseShellNetcat.py index 77b06d813c9..eb77dcd4bf9 100644 --- a/checkov/circleci_pipelines/checks/ReverseShellNetcat.py +++ b/checkov/circleci_pipelines/checks/ReverseShellNetcat.py @@ -19,6 +19,8 @@ def __init__(self) -> None: ) def scan_conf(self, conf: dict[str, Any]) -> tuple[CheckResult, dict[str, Any]]: + if not isinstance(conf, dict): + return CheckResult.UNKNOWN, conf if "run" not in conf: return CheckResult.PASSED, conf run = conf.get("run", "") diff --git a/checkov/circleci_pipelines/checks/ShellInjection.py b/checkov/circleci_pipelines/checks/ShellInjection.py index 6f34d161899..4a4f773185e 100644 --- a/checkov/circleci_pipelines/checks/ShellInjection.py +++ b/checkov/circleci_pipelines/checks/ShellInjection.py @@ -20,6 +20,8 @@ def __init__(self) -> None: ) def scan_conf(self, conf: dict[str, Any]) -> tuple[CheckResult, dict[str, Any]]: + if not isinstance(conf, dict): + return CheckResult.UNKNOWN, conf if "run" not in conf: return CheckResult.PASSED, conf run = conf.get("run", "") diff --git a/checkov/circleci_pipelines/checks/SuspectCurlInScript.py b/checkov/circleci_pipelines/checks/SuspectCurlInScript.py index 45ea38e1bb8..0b1824ab111 100644 --- a/checkov/circleci_pipelines/checks/SuspectCurlInScript.py +++ b/checkov/circleci_pipelines/checks/SuspectCurlInScript.py @@ -18,6 +18,8 @@ def __init__(self) -> None: ) def scan_conf(self, conf: dict[str, Any]) -> tuple[CheckResult, dict[str, Any]]: + if not isinstance(conf, dict): + return CheckResult.UNKNOWN, conf if "run" not in conf: return CheckResult.PASSED, conf run = conf.get("run", "") diff --git a/checkov/circleci_pipelines/checks/image_version_not_hash.py b/checkov/circleci_pipelines/checks/image_version_not_hash.py index 632d000408f..21a84c4d9c2 100644 --- a/checkov/circleci_pipelines/checks/image_version_not_hash.py +++ b/checkov/circleci_pipelines/checks/image_version_not_hash.py @@ -14,7 +14,7 @@ def __init__(self) -> None: name=name, id=id, block_type=BlockType.ARRAY, - supported_entities=['jobs.*.docker[].{image: image, __startline__: __startline__, __endline__:__endline__}'] + supported_entities=('jobs.*.docker[].{image: image, __startline__: __startline__, __endline__:__endline__}',) ) def scan_conf(self, conf: dict[str, Any]) -> tuple[CheckResult, dict[str, Any]]: diff --git a/checkov/circleci_pipelines/checks/prevent_development_orbs.py b/checkov/circleci_pipelines/checks/prevent_development_orbs.py index 16f2db86c22..bf11e9306b3 100644 --- a/checkov/circleci_pipelines/checks/prevent_development_orbs.py +++ b/checkov/circleci_pipelines/checks/prevent_development_orbs.py @@ -25,7 +25,7 @@ def scan_conf(self, conf: dict[str, Any]) -> tuple[CheckResult, dict[str, Any]]: # We only get one return per orb: section, regardless of how many orbs, so set a flag and error later. # Potentially more JMEpath reflection-foo can resolve this so we end up with a call to scan_entity_conf per orb. return CheckResult.FAILED, conf - + return CheckResult.PASSED, conf diff --git a/checkov/circleci_pipelines/image_referencer/provider.py b/checkov/circleci_pipelines/image_referencer/provider.py index 0c74c14901c..4ea3a6fb0b4 100644 --- a/checkov/circleci_pipelines/image_referencer/provider.py +++ b/checkov/circleci_pipelines/image_referencer/provider.py @@ -1,9 +1,11 @@ from __future__ import annotations + from typing import Any import jmespath - from checkov.common.images.image_referencer import Image from checkov.common.util.consts import START_LINE, END_LINE +from checkov.yaml_doc.runner import resolve_sub_name +from checkov.yaml_doc.runner import resolve_image_name class CircleCIProvider: @@ -13,26 +15,34 @@ def __init__(self, workflow_config: dict[str, Any], file_path: str) -> None: self.file_path = file_path self.workflow_config = workflow_config + def generate_resource_key(self, start_line: int, end_line: int, tag: str) -> str: + sub_name = resolve_sub_name(self.workflow_config, start_line, end_line, tag) + image_name = resolve_image_name(self.workflow_config[tag][sub_name], start_line, end_line) + new_key = f'{tag}({sub_name}).docker.image{image_name}' if sub_name else tag + return new_key + def extract_images_from_workflow(self) -> list[Image]: images: list[Image] = [] keywords = ( - "jobs.*.docker[].{image: image, __startline__: __startline__, __endline__:__endline__}", - "executors.*.docker[].{image: image, __startline__: __startline__, __endline__:__endline__}", + ('jobs', "jobs.*.docker[].{image: image, __startline__: __startline__, __endline__:__endline__}"), + ('executors', "executors.*.docker[].{image: image, __startline__: __startline__, __endline__:__endline__}"), ) - for keyword in keywords: + for tag, keyword in keywords: results = jmespath.search(keyword, self.workflow_config) if not results: continue for result in results: image_name = result.get("image") if image_name: + resource_id = self.generate_resource_key(result[START_LINE], result[END_LINE], tag) images.append( Image( file_path=self.file_path, name=image_name, start_line=result[START_LINE], - end_line=result[END_LINE] + end_line=result[END_LINE], + related_resource_id=resource_id, ) ) return images diff --git a/checkov/circleci_pipelines/runner.py b/checkov/circleci_pipelines/runner.py index 7ce078b7c54..ab45227f694 100644 --- a/checkov/circleci_pipelines/runner.py +++ b/checkov/circleci_pipelines/runner.py @@ -1,7 +1,8 @@ from __future__ import annotations +import logging import os -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING, Any, Iterable from checkov.circleci_pipelines.image_referencer.manager import CircleCIImageReferencerManager from checkov.common.images.image_referencer import Image, ImageReferencerMixin @@ -10,6 +11,9 @@ from checkov.common.util.type_forcers import force_dict from checkov.runner_filter import RunnerFilter from checkov.yaml_doc.runner import Runner as YamlRunner +from checkov.yaml_doc.runner import resolve_sub_name +from checkov.yaml_doc.runner import resolve_image_name +from checkov.yaml_doc.runner import resolve_step_name if TYPE_CHECKING: from checkov.common.checks.base_check_registry import BaseCheckRegistry @@ -45,6 +49,32 @@ def is_workflow_file(self, file_path: str) -> bool: abspath = os.path.abspath(file_path) return WORKFLOW_DIRECTORY in abspath and abspath.endswith(("config.yml", "config.yaml")) + def get_resource(self, file_path: str, key: str, supported_entities: Iterable[str], + start_line: int = -1, end_line: int = -1) -> str: + """ + supported resources for circleCI: + jobs.*.docker[].{image: image, __startline__: __startline__, __endline__:__endline__} + jobs.*.steps[] + orbs.{orbs: @} + """ + if len(list(supported_entities)) > 1: + logging.debug("order of entities might cause extracting the wrong key for resource_id") + new_key = key + definition = self.definitions.get(file_path, {}) + if not definition or not isinstance(definition, dict): + return new_key + if 'orbs.{orbs: @}' in supported_entities: + new_key = "orbs" + elif 'jobs.*.steps[]' in supported_entities: + job_name = resolve_sub_name(definition, start_line, end_line, tag='jobs') + step_name = resolve_step_name(definition['jobs'].get(job_name), start_line, end_line) + new_key = f'jobs({job_name}).steps{step_name}' if job_name else "jobs" + elif 'jobs.*.docker[].{image: image, __startline__: __startline__, __endline__:__endline__}' in supported_entities: + job_name = resolve_sub_name(definition, start_line, end_line, tag='jobs') + image_name = resolve_image_name(definition['jobs'].get(job_name), start_line, end_line) + new_key = f'jobs({job_name}).docker.image{image_name}' if job_name else "jobs" + return new_key + def run( self, root_folder: str | None = None, diff --git a/checkov/common/runners/object_runner.py b/checkov/common/runners/object_runner.py index 225e4b0c42a..a9c84355281 100644 --- a/checkov/common/runners/object_runner.py +++ b/checkov/common/runners/object_runner.py @@ -187,9 +187,7 @@ def add_python_check_results( code_block=self.definitions_raw[file_path][start - 1:end + 1], file_path=f"/{os.path.relpath(file_path, root_folder)}", file_line_range=[start, end + 1], - resource=self.get_resource( - file_path, key, check.supported_entities, self.definitions[file_path] # type:ignore[arg-type] # key is str not BaseCheck - ), + resource=self.get_resource(file_path, key, check.supported_entities, start, end), # type:ignore[arg-type] # key is str not BaseCheck evaluations=None, check_class=check.__class__.__module__, file_abs_path=os.path.abspath(file_path), @@ -208,9 +206,7 @@ def add_python_check_results( code_block=self.definitions_raw[file_path][start - 1:end + 1], file_path=f"/{os.path.relpath(file_path, root_folder)}", file_line_range=[start, end + 1], - resource=self.get_resource( - file_path, key, check.supported_entities, # type:ignore[arg-type] # key is str not BaseCheck - ), + resource=self.get_resource(file_path, key, check.supported_entities, start, end), # type:ignore[arg-type] # key is str not BaseCheck evaluations=None, check_class=check.__class__.__module__, file_abs_path=os.path.abspath(file_path), @@ -286,7 +282,7 @@ def included_paths(self) -> Iterable[str]: return [] def get_resource(self, file_path: str, key: str, supported_entities: Iterable[str], - definitions: dict[str, Any] | None = None) -> str: + start_line: int = -1, end_line: int = -1) -> str: return f"{file_path}.{key}" @abstractmethod @@ -335,12 +331,3 @@ def _get_jobs(self, definition: dict[str, Any]) -> dict[int, str]: for step in steps: end_line_to_job_name_dict[step.get(END_LINE)] = job_name return end_line_to_job_name_dict - - @staticmethod - def get_start_and_end_lines(key: str) -> list[int]: - check_name = key.split('.')[-1] - try: - start_end_line_bracket_index = check_name.index('[') - except ValueError: - return [-1, -1] - return [int(x) for x in check_name[start_end_line_bracket_index + 1: len(check_name) - 1].split(':')] diff --git a/checkov/github_actions/image_referencer/provider.py b/checkov/github_actions/image_referencer/provider.py index 8e059464d47..d192ea175d1 100644 --- a/checkov/github_actions/image_referencer/provider.py +++ b/checkov/github_actions/image_referencer/provider.py @@ -23,7 +23,7 @@ def generate_resource_key(self, start_line: int, end_line: int) -> str: continue if job[START_LINE] <= start_line <= end_line <= job[END_LINE]: - return f'jobs.{job_name}' + return f'jobs({job_name})' return '' diff --git a/checkov/github_actions/runner.py b/checkov/github_actions/runner.py index d90d489b094..8d3f45e34ce 100644 --- a/checkov/github_actions/runner.py +++ b/checkov/github_actions/runner.py @@ -1,5 +1,6 @@ from __future__ import annotations +import logging import os from collections.abc import Iterable from typing import TYPE_CHECKING, Any @@ -16,10 +17,10 @@ import checkov.common.parsers.yaml.loader as loader from checkov.common.images.image_referencer import Image, ImageReferencerMixin from checkov.common.bridgecrew.check_type import CheckType -from checkov.common.util.consts import START_LINE, END_LINE from checkov.common.util.type_forcers import force_dict from checkov.github_actions.checks.registry import registry -from checkov.yaml_doc.runner import Runner as YamlRunner +from checkov.yaml_doc.runner import Runner as YamlRunner, resolve_step_name +from checkov.yaml_doc.runner import resolve_sub_name if TYPE_CHECKING: from checkov.common.checks.base_check_registry import BaseCheckRegistry @@ -69,18 +70,31 @@ def included_paths(self) -> Iterable[str]: return [".github"] def get_resource(self, file_path: str, key: str, supported_entities: Iterable[str], - definitions: dict[str, Any] | None = None) -> str: - if not definitions: - return key - - potential_job_name = key.split('.')[1] - if potential_job_name != '*': - new_key = f'jobs.{potential_job_name}' - else: - start_line, end_line = self.get_start_and_end_lines(key) - job_name = Runner.resolve_job_name(definitions, start_line, end_line) - step_name = Runner.resolve_step_name(definitions["jobs"][job_name], start_line, end_line) - new_key = f'jobs.{job_name}.steps.{step_name}' + start_line: int = -1, end_line: int = -1) -> str: + """ + supported resources for GHA: + jobs + jobs.*.steps[] + permissions + on + + """ + if len(list(supported_entities)) > 1: + logging.debug("order of entities might cause extracting the wrong key for resource_id") + new_key = key + definition = self.definitions.get(file_path, {}) + if not definition or not isinstance(definition, dict): + return new_key + if 'on' in supported_entities: + workflow_name = definition.get('name', "") + new_key = f"on({workflow_name})" if workflow_name else "on" + elif 'jobs' in supported_entities: + job_name = resolve_sub_name(definition, start_line, end_line, tag='jobs') + new_key = f"jobs({job_name})" if job_name else "jobs" + + if 'jobs.*.steps[]' in supported_entities and key.split('.')[1] == '*': + step_name = resolve_step_name(definition['jobs'][job_name], start_line, end_line) + new_key = f'jobs({job_name}).steps{step_name}' return new_key def run( @@ -130,21 +144,3 @@ def extract_images( images.extend(manager.extract_images_from_workflow()) return images - - @staticmethod - def resolve_job_name(definition: dict[str, Any], start_line: int, end_line: int) -> str: - for key, job in definition.get('jobs', {}).items(): - if key in [START_LINE, END_LINE]: - continue - if job[START_LINE] <= start_line <= end_line <= job[END_LINE]: - return str(key) - return "" - - @staticmethod - def resolve_step_name(job_definition: dict[str, Any], start_line: int, end_line: int) -> str: - for idx, step in enumerate([step for step in job_definition.get('steps') or [] if step]): - if step[START_LINE] <= start_line <= end_line <= step[END_LINE]: - name = step.get('name') - return f"{idx + 1}[{name}]" if name else str(idx + 1) - - return "" diff --git a/checkov/gitlab_ci/runner.py b/checkov/gitlab_ci/runner.py index 9ea8b1f0c3c..f0dd14c3c93 100644 --- a/checkov/gitlab_ci/runner.py +++ b/checkov/gitlab_ci/runner.py @@ -48,8 +48,7 @@ def included_paths(self) -> Iterable[str]: return (".gitlab-ci.yml", ".gitlab-ci.yaml") def get_resource(self, file_path: str, key: str, supported_entities: Iterable[str], - definitions: dict[str, Any] | None = None) -> str: - start_line, end_line = self.get_start_and_end_lines(key) + start_line: int = -1, end_line: int = -1) -> str: file_config = force_dict(self.definitions[file_path]) if not file_config: return key diff --git a/checkov/openapi/runner.py b/checkov/openapi/runner.py index f77a3a156a4..5b57af2c7a8 100644 --- a/checkov/openapi/runner.py +++ b/checkov/openapi/runner.py @@ -92,7 +92,7 @@ def is_valid(self, conf: dict[str, Any] | list[dict[str, Any]] | None) -> bool: return False def get_resource(self, file_path: str, key: str, supported_entities: Iterable[str], - definitions: dict[str, Any] | None = None) -> str: + start_line: int = -1, end_line: int = -1) -> str: return ",".join(supported_entities) def load_file(self, filename: str | Path) -> str: diff --git a/checkov/yaml_doc/runner.py b/checkov/yaml_doc/runner.py index cd1ac648235..b36ce9192c6 100644 --- a/checkov/yaml_doc/runner.py +++ b/checkov/yaml_doc/runner.py @@ -5,6 +5,7 @@ from checkov.common.bridgecrew.check_type import CheckType from checkov.common.parsers.yaml.parser import parse from checkov.common.runners.object_runner import Runner as ObjectRunner +from checkov.common.util.consts import START_LINE, END_LINE if TYPE_CHECKING: from checkov.common.checks.base_check_registry import BaseCheckRegistry @@ -53,3 +54,72 @@ def get_start_end_lines( start = result_config["__startline__"] end = result_config["__endline__"] return end, start + + +def resolve_sub_name(definition: dict[str, Any], start_line: int, end_line: int, tag: str) -> str: + """ + extract the value of the tag, that is within the line of range(start_line, end_line) + + >>> resolve_sub_name({"executors":{"image-executor":{"docker":[],"__startline__":8,"__endline__":11}}}, 9, 11, 'executors') + 'image-executor' + + >>> resolve_sub_name({"jobs":{"job-name":{"docker":[],"__startline__":13,"__endline__":20}}}, 15, 16, 'jobs') + 'job-name' + """ + if not definition: + return "" + for key, sub_name in definition.get(tag, {}).items(): + if key in (START_LINE, END_LINE): + continue + if sub_name[START_LINE] <= start_line <= end_line <= sub_name[END_LINE]: + return str(key) + return "" + + +def resolve_step_name(job_definition: dict[str, Any], start_line: int, end_line: int) -> str: + """ + extract the step name from the given job within the line of range(start_line, end_line) + + >>> resolve_step_name({"steps":["checkout",{}],"__startline__":42,"__endline__":49}, 48, 49) + '[1](checkout)' + + >>> resolve_step_name({"runs-on":"ubuntu-latest","steps":[{"uses":"actions/checkout@v2","__startline__":22,"__endline__":23}]}, 22, 23) + '[1]' + + >>> resolve_step_name({"runs-on":"ubuntu-latest","steps":[{"name": "ab","__startline__":22,"__endline__":23}, {"name":"step_name","__startline__":23,"__endline__":33}]}, 23, 33) + '[2](step_name)' + + """ + if not job_definition: + return "" + for idx, step in enumerate([step for step in job_definition.get('steps') or [] if step]): + if isinstance(step, str): + return f"[{idx + 1}]({step})" + elif isinstance(step, dict): + if step[START_LINE] <= start_line <= end_line <= step[END_LINE]: + name = step.get('name') + return f"[{idx + 1}]({name})" if name else f"[{idx + 1}]" + return "" + + +def resolve_image_name(image_definition: dict[str, Any], start_line: int, end_line: int) -> str: + """ + extract the image name from the given job definition within the line of range(start_line, end_line) + + >>> resolve_image_name({"docker":[{"image":"mongo:2.6.8","__startline__":15,"__endline__":16}]}, 15, 16) + '[1](mongo:2.6.8)' + + """ + if not image_definition: + return "" + for idx, step in enumerate([step for step in image_definition.get('docker') or [] if step]): + if isinstance(image_definition.get('docker'), dict): + if step == 'image': + return f"[{idx + 1}]({image_definition['docker'][step]})" + if isinstance(step, str): + return f"[{idx + 1}]({step})" + elif isinstance(step, dict): + if step[START_LINE] <= start_line <= end_line <= step[END_LINE]: + name = step.get('image') + return f"[{idx + 1}]({name})" if name else f"[{idx + 1}]" + return "" diff --git a/tests/azure_pipelines/test_resource_names.py b/tests/azure_pipelines/test_resource_names.py index 663b32aab6f..3ecd8656915 100644 --- a/tests/azure_pipelines/test_resource_names.py +++ b/tests/azure_pipelines/test_resource_names.py @@ -3,35 +3,19 @@ from checkov.azure_pipelines.runner import Runner @pytest.mark.parametrize( - "key,file_path,expected_key", + "key,file_path,expected_key, start_line, end_line", [ ('jobs.jobs.CKV_AZUREPIPELINES_1[32:39]', '/checkov/tests/azure_pipelines/resources/azure-pipelines.yml', - 'jobs[0](FailTag)'), + 'jobs[0](FailTag)', 32, 39), ('stages[].jobs[].stages[].jobs[].CKV_AZUREPIPELINES_1[14:22]', '/checkov/tests/azure_pipelines/resources/azure-pipelines.yml', - 'stages[0](Example).jobs[0](FailNoTagDisplayName)'), + 'stages[0](Example).jobs[0](FailNoTagDisplayName)', 14, 22), ('stages[].jobs[].stages[].jobs[].CKV_AZUREPIPELINES_1[22:29]', '/checkov/tests/azure_pipelines/resources/azure-pipelines.yml', - 'stages[0](Example).jobs[1](PassDigest)') + 'stages[0](Example).jobs[1](PassDigest)', 22, 29) ], ) -def test_get_resource(key, file_path, expected_key, definitions, supported_entities): +def test_get_resource(key, file_path, expected_key, definitions, supported_entities, start_line, end_line): runner = Runner() runner.definitions = definitions - new_key = runner.get_resource(file_path, key, [], definitions) + new_key = runner.get_resource(file_path, key, [], start_line, end_line) - assert new_key == expected_key - - -@pytest.mark.parametrize( - "key,expected_start_line,expected_end_line", - [ - ("jobs.jobs.CKV_AZUREPIPELINES_1[32:39]", 32, 39), - ("stages[].jobs[].stages[].jobs[].CKV_AZUREPIPELINES_1[14:22]", 14, 22), - ("stages[].jobs[].stages[].jobs[].CKV_AZUREPIPELINES_1", -1, -1) - ] -) -def test_get_start_and_end_lines(key, expected_start_line, expected_end_line): - runner = Runner() - start_line, end_line = runner.get_start_and_end_lines(key) - - assert start_line == expected_start_line - assert end_line == expected_end_line + assert new_key == expected_key \ No newline at end of file diff --git a/tests/circleci_pipelines/conftest.py b/tests/circleci_pipelines/conftest.py new file mode 100644 index 00000000000..210d530099b --- /dev/null +++ b/tests/circleci_pipelines/conftest.py @@ -0,0 +1,249 @@ +from __future__ import annotations +import pytest + +from checkov.common.images.image_referencer import Image + + +@pytest.fixture +def file_path() -> str: + return ".circleci/config.yml" + + +@pytest.fixture +def definition(file_path) -> dict: + return { + file_path: { + "orbs": { + "new-orb": "whatever/orbname@goodorb", + "some-orb": "orbs/orbname@dev:blah", + "__startline__": 3, + "__endline__": 6 + }, + "executors": { + "image-executor": { + "docker": [ + { + "image": "mongo:2.6.8", + "__startline__": 9, + "__endline__": 11 + } + ], + "__startline__": 8, + "__endline__": 11 + }, + "__startline__": 7, + "__endline__": 11 + }, + "jobs": { + "test-docker-hash-img": { + "docker": [ + { + "image": "redis@sha256:54057dd7e125ca41afe526a877e8bd35ec2cdd33b9217e022ed37bdcf7d09673", + "__startline__": 15, + "__endline__": 16 + }, + { + "auth": { + "password": "$DOCKERHUB_PASSWORD", + "username": "mydockerhub-user", + "__startline__": 18, + "__endline__": 20 + }, + "__startline__": 17, + "__endline__": 20 + } + ], + "__startline__": 13, + "__endline__": 20 + }, + "test-docker-latest-img": { + "docker": [ + { + "image": "buildpack-deps:latest", + "__startline__": 23, + "__endline__": 24 + }, + { + "auth": { + "password": "$DOCKERHUB_PASSWORD", + "username": "mydockerhub-user", + "__startline__": 26, + "__endline__": 28 + }, + "command": [ + "--smallfiles" + ], + "__startline__": 25, + "__endline__": 30 + } + ], + "__startline__": 21, + "__endline__": 30 + }, + "test-docker-versioned-img": { + "docker": [ + { + "image": "mongo:2.6.8", + "__startline__": 33, + "__endline__": 34 + }, + { + "auth": { + "password": "$DOCKERHUB_PASSWORD", + "username": "mydockerhub-user", + "__startline__": 36, + "__endline__": 38 + }, + "environment": { + "POSTGRES_USER": "user", + "__startline__": 39, + "__endline__": 40 + }, + "image": "postgres:14.2", + "__startline__": 35, + "__endline__": 41 + } + ], + "__startline__": 31, + "__endline__": 41 + }, + "test-echo": { + "docker": [ + { + "image": "cimg/python:latest", + "__startline__": 44, + "__endline__": 45 + } + ], + "steps": [ + "checkout", + { + "run": "echo \"this is an echo in a script.\"", + "__startline__": 48, + "__endline__": 49 + } + ], + "__startline__": 42, + "__endline__": 49 + }, + "test-inject": { + "docker": [ + { + "image": "cimg/python:latest", + "__startline__": 52, + "__endline__": 53 + } + ], + "steps": [ + "checkout", + { + "run": { + "command": "curl -sSJL https://www.mongodb.org/static/pgp/server-4.2.asc | sudo apt-key add -\necho ${CIRCLE_BRANCH}\n", + "name": "Multi-line run with injection via vars", + "__startline__": 56, + "__endline__": 60 + }, + "__startline__": 55, + "__endline__": 60 + } + ], + "__startline__": 50, + "__endline__": 60 + }, + "test-inject2": { + "docker": [ + { + "image": "cimg/python:latest", + "__startline__": 63, + "__endline__": 64 + } + ], + "steps": [ + "checkout", + { + "run": { + "command": "curl -sSJL https://www.mongodb.org/static/pgp/server-4.2.asc | sudo apt-key add -\necho $CIRCLE_BRANCH\n", + "name": "Multi-line run with injection via vars", + "__startline__": 68, + "__endline__": 72 + }, + "__startline__": 67, + "__endline__": 72 + } + ], + "__startline__": 61, + "__endline__": 72 + }, + "test-curl-secret": { + "docker": [ + { + "image": "cimg/python:latest", + "__startline__": 75, + "__endline__": 76 + } + ], + "steps": [ + "checkout", + { + "run": { + "command": "curl -x POST someurl $SECRET\n", + "name": "Multi-line export secret", + "__startline__": 79, + "__endline__": 82 + }, + "__startline__": 78, + "__endline__": 82 + } + ], + "__startline__": 73, + "__endline__": 82 + }, + "test-inject-ci-vars": { + "docker": [ + { + "image": "cimg/python:latest", + "__startline__": 85, + "__endline__": 86 + } + ], + "steps": [ + "checkout", + { + "run": { + "command": "echo ${CIRCLE_PR_REPONAME}\n", + "name": "Echo the PR Reponame", + "__startline__": 90, + "__endline__": 94 + }, + "__startline__": 89, + "__endline__": 94 + } + ], + "__startline__": 83, + "__endline__": 94 + }, + "__startline__": 12, + "__endline__": 94 + }, + "version": 2.1, + "workflows": { + "say-hello-workflow": { + "jobs": [ + "test-docker-hash-img", + "test-docker-latest-img", + "test-docker-versioned-img", + "test-echo", + "test-inject", + "test-inject2", + "test-inject-ci-vars" + ], + "__startline__": 97, + "__endline__": 105 + }, + "__startline__": 96, + "__endline__": 105 + }, + "__startline__": 2, + "__endline__": 105 + } + } \ No newline at end of file diff --git a/tests/circleci_pipelines/image_referencer/conftest.py b/tests/circleci_pipelines/image_referencer/conftest.py index dd5171442db..e082f11d750 100644 --- a/tests/circleci_pipelines/image_referencer/conftest.py +++ b/tests/circleci_pipelines/image_referencer/conftest.py @@ -6,197 +6,255 @@ @pytest.fixture def file_path() -> str: - return ".circleci/config.yml" + return ".circleci/config.yml" @pytest.fixture def circleci_config_with_images_definitions(file_path) -> dict: - return { - file_path: { - "orbs": { - "new-orb": "whatever/orbname@goodorb", - "some-orb": "orbs/orbname@dev:blah", - "__startline__": 6, - "__endline__": 9 - }, - "executors": { - "default-executor": { - "machine": { - "image": "windows-server-2022", - "__startline__": 12, - "__endline__": 14 - }, - "__startline__": 11, - "__endline__": 14 - }, - "image-executor": { - "docker": { - "image": "mongo:2.6.8", - "__startline__": 16, - "__endline__": 18 - }, - "__startline__": 15, - "__endline__": 18 - }, - "__startline__": 10, - "__endline__": 18 - }, - "jobs": { - "test-docker-versioned-img": { - "docker": [ - { - "image": "mongo:2.6.8", - "__startline__": 21, - "__endline__": 22 - } - ], - "steps": [ - "some-step" - ], - "__startline__": 20, - "__endline__": 25 - }, - "__startline__": 19, - "__endline__": 25 - }, - "__startline__": 5, - "__endline__": 25 - } - } + return { + file_path: { + "orbs": { + "new-orb": "whatever/orbname@goodorb", + "some-orb": "orbs/orbname@dev:blah", + "__startline__": 6, + "__endline__": 9 + }, + "executors": { + "default-executor": { + "machine": { + "image": "windows-server-2022", + "__startline__": 12, + "__endline__": 14 + }, + "__startline__": 11, + "__endline__": 14 + }, + "image-executor": { + "docker": { + "image": "mongo:2.6.8", + "__startline__": 16, + "__endline__": 18 + }, + "__startline__": 15, + "__endline__": 18 + }, + "__startline__": 10, + "__endline__": 18 + }, + "jobs": { + "test-docker-versioned-img": { + "docker": [ + { + "image": "mongo:2.6.8", + "__startline__": 21, + "__endline__": 22 + } + ], + "steps": [ + "some-step" + ], + "__startline__": 20, + "__endline__": 25 + }, + "__startline__": 19, + "__endline__": 25 + }, + "__startline__": 5, + "__endline__": 25 + } + } @pytest.fixture -def circle_ci_filepath_workflow_with_images_config(circleci_config_with_images_definitions, file_path)\ - -> tuple[str, dict]: - return file_path, circleci_config_with_images_definitions.get(file_path) +def circle_ci_filepath_workflow_with_images_config(circleci_config_with_images_definitions, file_path) \ + -> tuple[str, dict]: + return file_path, circleci_config_with_images_definitions.get(file_path) @pytest.fixture def circleci_config_no_images_definitions(file_path) -> dict: - return { - file_path: { - "orbs": { - "new-orb": "whatever/orbname@goodorb", - "some-orb": "orbs/orbname@dev:blah", - "__startline__": 6, - "__endline__": 9 - }, - "executors": { - "default-executor": { - "machine": { - "image": "windows-server-2022", - "__startline__": 12, - "__endline__": 14 - }, - "__startline__": 11, - "__endline__": 14 - }, - "__startline__": 10, - "__endline__": 14 - }, - "jobs": { - "test-macos-image": { - "macos": { - "xcode": "9.4.1", - "__startline__": 17, - "__endline__": 18 - }, - "steps": [ - "some-step" - ], - "__startline__": 16, - "__endline__": 21 - }, - "test-machine-default": { - "executor": { - "name": "win/default-executor", - "__startline__": 23, - "__endline__": 24 - }, - "steps": [ - "some-step" - ], - "__startline__": 22, - "__endline__": 27 - }, - "__startline__": 15, - "__endline__": 27 - }, - "__startline__": 5, - "__endline__": 27 - } - } + return { + file_path: { + "orbs": { + "new-orb": "whatever/orbname@goodorb", + "some-orb": "orbs/orbname@dev:blah", + "__startline__": 6, + "__endline__": 9 + }, + "executors": { + "default-executor": { + "machine": { + "image": "windows-server-2022", + "__startline__": 12, + "__endline__": 14 + }, + "__startline__": 11, + "__endline__": 14 + }, + "__startline__": 10, + "__endline__": 14 + }, + "jobs": { + "test-macos-image": { + "macos": { + "xcode": "9.4.1", + "__startline__": 17, + "__endline__": 18 + }, + "steps": [ + "some-step" + ], + "__startline__": 16, + "__endline__": 21 + }, + "test-machine-default": { + "executor": { + "name": "win/default-executor", + "__startline__": 23, + "__endline__": 24 + }, + "steps": [ + "some-step" + ], + "__startline__": 22, + "__endline__": 27 + }, + "__startline__": 15, + "__endline__": 27 + }, + "__startline__": 5, + "__endline__": 27 + } + } @pytest.fixture def circle_ci_filepath_workflow_no_images_config(circleci_config_no_images_definitions, file_path) -> tuple[str, dict]: - return file_path, circleci_config_no_images_definitions.get(file_path) + return file_path, circleci_config_no_images_definitions.get(file_path) @pytest.fixture def circle_ci_image1(file_path) -> Image: - image = Image( - end_line=18, - start_line=16, - name='mongo:2.6.8', - file_path=file_path, - ) - return image + image = Image( + end_line=18, + start_line=16, + name='mongo:2.6.8', + file_path=file_path, + related_resource_id='executors(image-executor).docker.image[1](mongo:2.6.8)', + ) + return image @pytest.fixture def circle_ci_image2(file_path) -> Image: - image = Image( - end_line=22, - start_line=21, - name='mongo:2.6.8', - file_path=file_path, - ) - return image + image = Image( + end_line=22, + start_line=21, + name='mongo:2.6.8', + file_path=file_path, + related_resource_id='jobs(test-docker-versioned-img).docker.image[1](mongo:2.6.8)', + ) + return image @pytest.fixture def image_cached_result() -> dict: - return { - "results": [ - { - "id": "sha256:9dbc24674f25eb449df11179ed3717c47348fb3aa985ae14b3936d54c2c09dde", - "name": "postgres:14.2", - "distro": "Debian GNU/Linux 11 (bullseye)", - "distroRelease": "bullseye", - "digest": "sha256:2c954f8c5d03da58f8b82645b783b56c1135df17e650b186b296fa1bb71f9cfd", - "collections": [ - "All" - ], - "packages": [ - { - "type": "os", - "name": "base-files", - "version": "11.1+deb11u3", - "licenses": [ - "GPL" - ] - } - ], - "compliances": [], - "complianceDistribution": { - "critical": 0, - "high": 2, - "medium": 0, - "low": 0, - "total": 2 - }, - "complianceScanPassed": True, - "vulnerabilities": [ - ], - "vulnerabilityDistribution": { - "critical": 9, - "high": 26, - "medium": 8, - "low": 17, - "total": 60 - }, - "vulnerabilityScanPassed": True - } - ] - } + return { + "results": [ + { + "id": "sha256:9dbc24674f25eb449df11179ed3717c47348fb3aa985ae14b3936d54c2c09dde", + "name": "postgres:14.2", + "distro": "Debian GNU/Linux 11 (bullseye)", + "distroRelease": "bullseye", + "digest": "sha256:2c954f8c5d03da58f8b82645b783b56c1135df17e650b186b296fa1bb71f9cfd", + "collections": [ + "All" + ], + "packages": [ + { + "type": "os", + "name": "base-files", + "version": "11.1+deb11u3", + "licenses": [ + "GPL" + ] + } + ], + "compliances": [], + "complianceDistribution": { + "critical": 0, + "high": 2, + "medium": 0, + "low": 0, + "total": 2 + }, + "complianceScanPassed": True, + "vulnerabilities": [ + ], + "vulnerabilityDistribution": { + "critical": 9, + "high": 26, + "medium": 8, + "low": 17, + "total": 60 + }, + "vulnerabilityScanPassed": True + } + ] + } + + +@pytest.fixture +def image_cached_results_for_report() -> tuple: + return ( + { + 'image_name': 'redis@sha256:54057dd7e125ca41afe526a877e8bd35ec2cdd33b9217e022ed37bdcf7d09673', + 'related_resource_id': 'jobs(test-docker-hash-img).docker.image[1](redis@sha256:54057dd7e125ca41afe526a877e8bd35ec2cdd33b9217e022ed37bdcf7d09673)', + 'packages': [{'type': 'os', 'name': 'base-files', 'version': '11.1+deb11u3', 'licenses': ['GPL']}] + }, + { + 'image_name': 'buildpack-deps:latest', + 'related_resource_id': 'jobs(test-docker-latest-img).docker.image[1](buildpack-deps:latest)', + 'packages': [{'type': 'os', 'name': 'base-files', 'version': '11.1+deb11u3', 'licenses': ['GPL']}] + }, + { + 'image_name': 'mongo:2.6.8', + 'related_resource_id': 'jobs(test-docker-versioned-img).docker.image[1](mongo:2.6.8)', + 'packages': [{'type': 'os', 'name': 'base-files', 'version': '11.1+deb11u3', 'licenses': ['GPL']}] + }, + { + 'image_name': 'postgres:14.2', + 'related_resource_id': 'jobs(test-docker-versioned-img).docker.image[2](postgres:14.2)', + 'packages': [{'type': 'os', 'name': 'base-files', 'version': '11.1+deb11u3', 'licenses': ['GPL']}] + }, + { + 'image_name': 'cimg/python:latest', + 'related_resource_id': 'jobs(test-echo).docker.image[1](cimg/python:latest)', + 'packages': [{'type': 'os', 'name': 'base-files', 'version': '11.1+deb11u3', 'licenses': ['GPL']}] + }, + { + 'image_name': 'cimg/python:latest', + 'related_resource_id': 'jobs(test-inject).docker.image[1](cimg/python:latest)', + 'packages': [{'type': 'os', 'name': 'base-files', 'version': '11.1+deb11u3', 'licenses': ['GPL']}] + }, + { + 'image_name': 'cimg/python:latest', + 'related_resource_id': 'jobs(test-inject2).docker.image[1](cimg/python:latest)', + 'packages': [{'type': 'os', 'name': 'base-files', 'version': '11.1+deb11u3', 'licenses': ['GPL']}] + }, + { + 'image_name': 'cimg/python:latest', + 'related_resource_id': 'jobs(test-curl-secret).docker.image[1](cimg/python:latest)', + 'packages': [{'type': 'os', 'name': 'base-files', 'version': '11.1+deb11u3', 'licenses': ['GPL']}] + }, + { + 'image_name': 'cimg/python:latest', + 'related_resource_id': 'jobs(test-inject-ci-vars).docker.image[1](cimg/python:latest)', + 'packages': [{'type': 'os', 'name': 'base-files', 'version': '11.1+deb11u3', 'licenses': ['GPL']}] + }, + { + 'image_name': 'mongo:2.6.8', + 'related_resource_id': 'executors(image-executor).docker.image[1](mongo:2.6.8)', + 'packages': [{'type': 'os', 'name': 'base-files', 'version': '11.1+deb11u3', 'licenses': ['GPL']}] + } + ) diff --git a/tests/circleci_pipelines/image_referencer/test_provider.py b/tests/circleci_pipelines/image_referencer/test_provider.py index 9343e65b017..f022c12334a 100644 --- a/tests/circleci_pipelines/image_referencer/test_provider.py +++ b/tests/circleci_pipelines/image_referencer/test_provider.py @@ -1,6 +1,7 @@ - +import pytest from checkov.circleci_pipelines.image_referencer.provider import CircleCIProvider +from checkov.circleci_pipelines.runner import Runner def test_extract_images_from_workflow(circle_ci_filepath_workflow_with_images_config, @@ -13,7 +14,6 @@ def test_extract_images_from_workflow(circle_ci_filepath_workflow_with_images_co assert set(images) == {circle_ci_image1, circle_ci_image2} - def test_extract_images_from_workflow_no_images(circle_ci_filepath_workflow_no_images_config): file_path, config = circle_ci_filepath_workflow_no_images_config @@ -21,3 +21,29 @@ def test_extract_images_from_workflow_no_images(circle_ci_filepath_workflow_no_i images = provider.extract_images_from_workflow() assert not images + +@pytest.mark.parametrize( + "start_line, end_line, tag, supported_entities, old_key_format, expected_key", + [ + (21, + 22, + 'jobs', + ('jobs.*.docker[].{image: image, __startline__: __startline__, __endline__:__endline__}',), + 'jobs.*.docker[].{image: image, __startline__: __startline__, __endline__:__endline__}.jobs.*.docker[].{image: image, __startline__: __startline__, __endline__:__endline__}.CKV_CIRCLECIPIPELINES_1[85:86]', + "jobs(test-docker-versioned-img).docker.image[1](mongo:2.6.8)"), + ], +) +def test_generate_resource_key_generates_same_key_as_get_resource(file_path, + start_line, end_line, tag, + supported_entities, + old_key_format, expected_key, + circleci_config_with_images_definitions): + definitions = circleci_config_with_images_definitions.get(file_path) + provider = CircleCIProvider(definitions, file_path) + runner = Runner() + runner.definitions[file_path] = definitions + + key1 = runner.get_resource(file_path, old_key_format, supported_entities, start_line, end_line) + key2 = provider.generate_resource_key(start_line, end_line, tag) + + assert key1 == key2 == expected_key diff --git a/tests/circleci_pipelines/image_referencer/test_runner.py b/tests/circleci_pipelines/image_referencer/test_runner.py index ea78b9d9db4..7d85c5fa42b 100644 --- a/tests/circleci_pipelines/image_referencer/test_runner.py +++ b/tests/circleci_pipelines/image_referencer/test_runner.py @@ -1,3 +1,4 @@ +import logging from pathlib import Path from checkov.circleci_pipelines.runner import Runner @@ -10,7 +11,7 @@ RESOURCES_PATH = Path(__file__).parent.parent / "resources" -def test_github_action_workflow(mocker: MockerFixture, image_cached_result, file_path): +def test_circleCI_workflow(mocker: MockerFixture, image_cached_result, file_path, image_cached_results_for_report): from checkov.common.bridgecrew.platform_integration import bc_integration test_file = RESOURCES_PATH / file_path @@ -34,7 +35,7 @@ def test_github_action_workflow(mocker: MockerFixture, image_cached_result, file sca_image_report = next(report for report in reports if report.check_type == CheckType.SCA_IMAGE) assert len(circleci_report.resources) == 0 - assert len(circleci_report.passed_checks) == 24 + assert len(circleci_report.passed_checks) == 21 assert len(circleci_report.failed_checks) == 13 assert len(circleci_report.skipped_checks) == 0 assert len(circleci_report.parsing_errors) == 0 @@ -42,6 +43,16 @@ def test_github_action_workflow(mocker: MockerFixture, image_cached_result, file assert len(sca_image_report.extra_resources) == 10 assert len(sca_image_report.image_cached_results) == 10 + got_images = ({ + 'image_name': image['dockerImageName'], + 'related_resource_id': image['relatedResourceId'], + 'packages': image['packages'] + } for image in sca_image_report.image_cached_results) + for image in got_images: + assert image in image_cached_results_for_report + assert len(sca_image_report.extra_resources) == 10 + assert len(sca_image_report.image_cached_results) == 10 + def test_runner_image_check(file_path): test_file = RESOURCES_PATH / file_path diff --git a/tests/circleci_pipelines/test_runner.py b/tests/circleci_pipelines/test_runner.py index 5bf7fc72eda..6d0c5580283 100644 --- a/tests/circleci_pipelines/test_runner.py +++ b/tests/circleci_pipelines/test_runner.py @@ -1,5 +1,6 @@ import os import unittest +import pytest from checkov.circleci_pipelines.runner import Runner from checkov.common.bridgecrew.check_type import CheckType @@ -24,7 +25,7 @@ def test_runner(self): ) self.assertEqual(len(report.failed_checks), 13) self.assertEqual(report.parsing_errors, []) - self.assertEqual(len(report.passed_checks), 24) + self.assertEqual(len(report.passed_checks), 21) self.assertEqual(report.skipped_checks, []) report.print_console() @@ -46,6 +47,37 @@ def test_runner_honors_enforcement_rules(self): self.assertEqual(len(report.skipped_checks), 0) report.print_console() +@pytest.mark.parametrize( + "key, expected_key, supported_entities, start_line, end_line", + [ + ( + 'orbs.{orbs: @}.orbs.CKV_CIRCLECIPIPELINES_4[3:6]', + "orbs", + ('orbs.{orbs: @}',), + 3, 6 + ), + ( + 'jobs.*.steps[].jobs.*.steps[].CKV_CIRCLECIPIPELINES_7[48:49]', + "jobs(test-echo).steps[1](checkout)", + ('jobs.*.steps[]',), + 48, 49 + ), + ( + 'jobs.*.docker[].{image: image, __startline__: __startline__, __endline__:__endline__}.jobs.*.docker[].{image: image, __startline__: __startline__, __endline__:__endline__}.CKV_CIRCLECIPIPELINES_2[33:34]', + 'jobs(test-docker-versioned-img).docker.image[1](mongo:2.6.8)', + ('jobs.*.docker[].{image: image, __startline__: __startline__, __endline__:__endline__}', ), + 33, 34 + ) + ] +) +def test_get_resource(file_path, key, supported_entities, expected_key, start_line, end_line, definition): + runner = Runner() + runner.definitions = definition + + new_key = runner.get_resource(file_path, key, supported_entities, start_line, end_line) + + assert new_key == expected_key + if __name__ == "__main__": unittest.main() diff --git a/tests/github_actions/image_referencer/test_github_action_provider.py b/tests/github_actions/image_referencer/test_github_action_provider.py index ada09bb9d6e..de6d707651f 100644 --- a/tests/github_actions/image_referencer/test_github_action_provider.py +++ b/tests/github_actions/image_referencer/test_github_action_provider.py @@ -17,7 +17,7 @@ def test_extract_images_from_workflow(workflow_with_images, workflow_line_number start_line=12, name='node:14.16', file_path=file_path, - related_resource_id='jobs.destroy_cert' + related_resource_id='jobs(destroy_cert)' ) ] @@ -35,8 +35,8 @@ def test_extract_images_from_workflow_no_images(workflow_without_images, workflo @pytest.mark.parametrize( "start_line,end_line,expected_key", [ - (9, 17, "jobs.container-test-job"), - (24, 30, "jobs.second_job"), + (9, 17, "jobs(container-test-job)"), + (24, 30, "jobs(second_job)"), (35, 40, "") ], ) @@ -49,17 +49,20 @@ def test_generate_resource_key(start_line, end_line, expected_key, definition): @pytest.mark.parametrize( - "start_line,end_line,old_key_format,expected_key", + "start_line, end_line, supported_entities, old_key_format, expected_key", [ - (9, 17, 'jobs.container-test-job.CKV_GHA_3[7:23]', "jobs.container-test-job"), - (24, 30, "jobs.second_job.CKV_GHA_3[24:30]", "jobs.second_job") + (9, 17, ('jobs', 'jobs.*.steps[]'), 'jobs.container-test-job.CKV_GHA_3[7:23]', "jobs(container-test-job)"), + (24, 30, ('jobs', 'jobs.*.steps[]'), "jobs.second_job.CKV_GHA_3[24:30]", "jobs(second_job)") ], ) -def test_generate_resource_key_generates_same_key_as_get_resource(start_line, end_line, old_key_format, expected_key, definition): +def test_generate_resource_key_generates_same_key_as_get_resource(start_line, end_line, supported_entities, + old_key_format, expected_key, definition): gha_provider = GithubActionProvider(definition, '', []) runner = Runner() + file_path = "mock_path" + runner.definitions[file_path] = definition - key1 = runner.get_resource("", old_key_format, [], definition) + key1 = runner.get_resource(file_path, old_key_format, supported_entities, start_line, end_line) key2 = gha_provider.generate_resource_key(start_line, end_line) assert key1 == key2 == expected_key diff --git a/tests/github_actions/image_referencer/test_manager.py b/tests/github_actions/image_referencer/test_manager.py index 4f2cede8954..5c8a8350864 100644 --- a/tests/github_actions/image_referencer/test_manager.py +++ b/tests/github_actions/image_referencer/test_manager.py @@ -15,7 +15,7 @@ def test_extract_images_from_workflow(workflow_with_images, workflow_line_number start_line=12, name='node:14.16', file_path=file_path, - related_resource_id='jobs.destroy_cert' + related_resource_id='jobs(destroy_cert)' ) ] diff --git a/tests/github_actions/test_runner_resource_names.py b/tests/github_actions/test_runner_resource_names.py index 88e8f96c513..1903ddf3273 100644 --- a/tests/github_actions/test_runner_resource_names.py +++ b/tests/github_actions/test_runner_resource_names.py @@ -1,17 +1,6 @@ import pytest from checkov.github_actions.runner import Runner -from checkov.github_actions.image_referencer.provider import GithubActionProvider as gha_provider - - -def test_get_start_and_end_lines(): - keys = ['jobs.*.steps[].jobs.*.steps[].CKV_GHA_3[18:22]', - 'jobs.*.steps[].CKV_GHA_3[18:22]', - 'jobs.job_name.CKV_GHA_3[18:22]'] - - for key in keys: - start_line, end_line = Runner.get_start_and_end_lines(key) - assert start_line == 18 - assert end_line == 22 +from checkov.yaml_doc.runner import resolve_sub_name @pytest.mark.parametrize( @@ -25,21 +14,25 @@ def test_get_start_and_end_lines(): ], ) def test_resolve_job_name(start_line, end_line, expected_job_name, definition): - job_name = Runner.resolve_job_name(definition, start_line, end_line) + job_name = resolve_sub_name(definition, start_line, end_line, tag='jobs') assert job_name == expected_job_name @pytest.mark.parametrize( - "key,expected_key", + "key,expected_key, supported_entities, start_line, end_line", [ - ('jobs.container-test-job.CKV_GHA_3[7:23]', "jobs.container-test-job"), - ('jobs.*.steps[].jobs.*.steps[].CKV_GHA_3[18:23]', "jobs.container-test-job.steps.1[Check for dockerenv file]"), + ('jobs.container-test-job.CKV_GHA_3[7:23]', "jobs(container-test-job)", + ('jobs', 'jobs.*.steps[]'), 7, 23), + ('jobs.*.steps[].jobs.*.steps[].CKV_GHA_3[18:23]', "jobs(container-test-job).steps[1](Check for dockerenv file)", + ('jobs', 'jobs.*.steps[]'), 18, 23), ], ) -def test_get_resource(key, expected_key, definition): +def test_get_resource(key, supported_entities, expected_key, start_line, end_line, definition): runner = Runner() + file_path = "mock_path" + runner.definitions[file_path] = definition - new_key = runner.get_resource("", key, [], definition) + new_key = runner.get_resource(file_path, key, supported_entities, start_line, end_line) assert new_key == expected_key diff --git a/tests/gitlab_ci/test_resource_names.py b/tests/gitlab_ci/test_resource_names.py index e86ec47df13..a23b623a76b 100644 --- a/tests/gitlab_ci/test_resource_names.py +++ b/tests/gitlab_ci/test_resource_names.py @@ -4,33 +4,17 @@ @pytest.mark.parametrize( - "key,file_path,expected_key", + "key,file_path,expected_key, start_line, end_line", [ ('*.script[].*.script[].CKV_GITLABCI_1[19:19]', '/checkov/tests/gitlab_ci/resources/images/.gitlab-ci.yml', - 'test.script'), + 'test.script', 19, 19), ('*.rules.*.rules.CKV_GITLABCI_2[7:9]', '/checkov/tests/gitlab_ci/resources/two/.gitlab-ci.yml', - 'planOnlySubset'), + 'planOnlySubset', 7, 9), ], ) -def test_get_resource(key, file_path, expected_key, definitions): +def test_get_resource(key, file_path, expected_key, definitions, start_line, end_line): runner = Runner() runner.definitions = definitions - new_key = runner.get_resource(file_path, key, [], {}) + new_key = runner.get_resource(file_path, key, [], start_line, end_line) - assert new_key == expected_key - - -@pytest.mark.parametrize( - "key,expected_start_line,expected_end_line", - [ - ("*.script[].*.script[].CKV_GITLABCI_1[19:19]", 19, 19), - ("*.rules.*.rules.CKV_GITLABCI_2[7:9]", 7, 9), - ("*.script[].CKV_GITLABCI_1", -1, -1) - ] -) -def test_get_start_and_end_lines(key, expected_start_line, expected_end_line): - runner = Runner() - start_line, end_line = runner.get_start_and_end_lines(key) - - assert start_line == expected_start_line - assert end_line == expected_end_line + assert new_key == expected_key \ No newline at end of file