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

CM-42904 - Add --by-cve option for cycode ignore command #274

Merged
merged 6 commits into from
Dec 17, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
76 changes: 53 additions & 23 deletions cycode/cli/commands/ignore/ignore_command.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,16 @@
import os
import re
from typing import Optional

import click

from cycode.cli import consts
from cycode.cli.config import config, configuration_manager
from cycode.cli.sentry import add_breadcrumb
from cycode.cli.utils.path_utils import get_absolute_path
from cycode.cli.utils.path_utils import get_absolute_path, is_path_exists
from cycode.cli.utils.string_utils import hash_string_to_sha256
from cycode.cyclient import logger


def _is_path_to_ignore_exists(path: str) -> bool:
return os.path.exists(path)


def _is_package_pattern_valid(package: str) -> bool:
return re.search('^[^@]+@[^@]+$', package) is not None

Expand Down Expand Up @@ -47,10 +43,16 @@ def _is_package_pattern_valid(package: str) -> bool:
required=False,
help='Ignore scanning a specific package version while running an SCA scan. Expected pattern: name@version.',
)
@click.option(
'--by-cve',
type=click.STRING,
required=False,
help='Ignore scanning a specific CVE while running an SCA scan. Expected pattern: CVE-YYYY-NNN.',
)
@click.option(
'--scan-type',
'-t',
default='secret',
default=consts.SECRET_SCAN_TYPE,
help='Specify the type of scan you wish to execute (the default is Secrets).',
type=click.Choice(config['scans']['supported_scans']),
required=False,
Expand All @@ -64,40 +66,68 @@ def _is_package_pattern_valid(package: str) -> bool:
required=False,
help='Add an ignore rule to the global CLI config.',
)
def ignore_command(
by_value: str, by_sha: str, by_path: str, by_rule: str, by_package: str, scan_type: str, is_global: bool
def ignore_command( # noqa: C901
by_value: Optional[str],
by_sha: Optional[str],
by_path: Optional[str],
by_rule: Optional[str],
by_package: Optional[str],
by_cve: Optional[str],
scan_type: str = consts.SECRET_SCAN_TYPE,
is_global: bool = False,
) -> None:
"""Ignores a specific value, path or rule ID."""
add_breadcrumb('ignore')

if not by_value and not by_sha and not by_path and not by_rule and not by_package:
raise click.ClickException('ignore by type is missing')
all_by_values = [by_value, by_sha, by_path, by_rule, by_package, by_cve]
if all(by is None for by in all_by_values):
raise click.ClickException('Ignore by type is missing')
if len([by for by in all_by_values if by is not None]) != 1:
raise click.ClickException('You must specify only one ignore by type')

if any(by is not None for by in [by_value, by_sha]) and scan_type != consts.SECRET_SCAN_TYPE:
raise click.ClickException('this exclude is supported only for secret scan type')
raise click.ClickException('This exclude is supported only for Secret scan type')
if (by_cve or by_package) and scan_type != consts.SCA_SCAN_TYPE:
raise click.ClickException('This exclude is supported only for SCA scan type')

# only one of the by values must be set
# at least one of the by values must be set
exclusion_type = exclusion_value = None

if by_value is not None:
if by_value:
exclusion_type = consts.EXCLUSIONS_BY_VALUE_SECTION_NAME
exclusion_value = hash_string_to_sha256(by_value)
elif by_sha is not None:

if by_sha:
exclusion_type = consts.EXCLUSIONS_BY_SHA_SECTION_NAME
exclusion_value = by_sha
elif by_path is not None:

if by_path:
absolute_path = get_absolute_path(by_path)
if not _is_path_to_ignore_exists(absolute_path):
raise click.ClickException('the provided path to ignore by is not exist')
if not is_path_exists(absolute_path):
raise click.ClickException('The provided path to ignore by does not exist')

exclusion_type = consts.EXCLUSIONS_BY_PATH_SECTION_NAME
exclusion_value = get_absolute_path(absolute_path)
elif by_package is not None:
if scan_type != consts.SCA_SCAN_TYPE:
raise click.ClickException('exclude by package is supported only for sca scan type')

if by_rule:
exclusion_type = consts.EXCLUSIONS_BY_RULE_SECTION_NAME
exclusion_value = by_rule

if by_package:
if not _is_package_pattern_valid(by_package):
raise click.ClickException('wrong package pattern. should be name@version.')

exclusion_type = consts.EXCLUSIONS_BY_PACKAGE_SECTION_NAME
exclusion_value = by_package
else:
exclusion_type = consts.EXCLUSIONS_BY_RULE_SECTION_NAME
exclusion_value = by_rule

if by_cve:
exclusion_type = consts.EXCLUSIONS_BY_CVE_SECTION_NAME
exclusion_value = by_cve

if not exclusion_type or not exclusion_value:
# should never happen
raise click.ClickException('Invalid ignore by type')

configuration_scope = 'global' if is_global else 'local'
logger.debug(
Expand Down
64 changes: 37 additions & 27 deletions cycode/cli/commands/scan/code_scanner.py
Original file line number Diff line number Diff line change
Expand Up @@ -764,58 +764,68 @@ def _exclude_detections_by_exclusions_configuration(detections: List[Detection],


def _should_exclude_detection(detection: Detection, exclusions: Dict) -> bool:
# FIXME(MarshalX): what the difference between by_value and by_sha?
exclusions_by_value = exclusions.get(consts.EXCLUSIONS_BY_VALUE_SECTION_NAME, [])
if _is_detection_sha_configured_in_exclusions(detection, exclusions_by_value):
logger.debug(
'Going to ignore violations because they are on the values-to-ignore list, %s',
{'value_sha': detection.detection_details.get('sha512', '')},
'Ignoring violation because its value is on the ignore list, %s',
{'value_sha': detection.detection_details.get('sha512')},
)
return True

exclusions_by_sha = exclusions.get(consts.EXCLUSIONS_BY_SHA_SECTION_NAME, [])
if _is_detection_sha_configured_in_exclusions(detection, exclusions_by_sha):
logger.debug(
'Going to ignore violations because they are on the SHA ignore list, %s',
{'sha': detection.detection_details.get('sha512', '')},
'Ignoring violation because its SHA value is on the ignore list, %s',
{'sha': detection.detection_details.get('sha512')},
)
return True

exclusions_by_rule = exclusions.get(consts.EXCLUSIONS_BY_RULE_SECTION_NAME, [])
if exclusions_by_rule:
detection_rule = detection.detection_rule_id
if detection_rule in exclusions_by_rule:
logger.debug(
'Going to ignore violations because they are on the Rule ID ignore list, %s',
{'detection_rule': detection_rule},
)
return True
detection_rule_id = detection.detection_rule_id
if detection_rule_id in exclusions_by_rule:
logger.debug(
'Ignoring violation because its Detection Rule ID is on the ignore list, %s',
{'detection_rule_id': detection_rule_id},
)
return True

exclusions_by_package = exclusions.get(consts.EXCLUSIONS_BY_PACKAGE_SECTION_NAME, [])
if exclusions_by_package:
package = _get_package_name(detection)
if package in exclusions_by_package:
logger.debug(
'Going to ignore violations because they are on the packages-to-ignore list, %s', {'package': package}
)
return True
package = _get_package_name(detection)
if package and package in exclusions_by_package:
logger.debug('Ignoring violation because its package@version is on the ignore list, %s', {'package': package})
return True

exclusions_by_cve = exclusions.get(consts.EXCLUSIONS_BY_CVE_SECTION_NAME, [])
cve = _get_cve_identifier(detection)
if cve and cve in exclusions_by_cve:
logger.debug('Ignoring violation because its CVE is on the ignore list, %s', {'cve': cve})
return True

return False


def _is_detection_sha_configured_in_exclusions(detection: Detection, exclusions: List[str]) -> bool:
detection_sha = detection.detection_details.get('sha512', '')
detection_sha = detection.detection_details.get('sha512')
return detection_sha in exclusions


def _get_package_name(detection: Detection) -> str:
package_name = detection.detection_details.get('vulnerable_component', '')
package_version = detection.detection_details.get('vulnerable_component_version', '')
def _get_package_name(detection: Detection) -> Optional[str]:
package_name = detection.detection_details.get('vulnerable_component')
package_version = detection.detection_details.get('vulnerable_component_version')

if package_name is None:
package_name = detection.detection_details.get('package_name')
package_version = detection.detection_details.get('package_version')

if package_name and package_version:
return f'{package_name}@{package_version}'

return None

if package_name == '':
package_name = detection.detection_details.get('package_name', '')
package_version = detection.detection_details.get('package_version', '')

return f'{package_name}@{package_version}'
def _get_cve_identifier(detection: Detection) -> Optional[str]:
return detection.detection_details.get('alert', {}).get('cve_identifier')


def _get_document_by_file_name(
Expand Down
1 change: 1 addition & 0 deletions cycode/cli/consts.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@
EXCLUSIONS_BY_PATH_SECTION_NAME = 'paths'
EXCLUSIONS_BY_RULE_SECTION_NAME = 'rules'
EXCLUSIONS_BY_PACKAGE_SECTION_NAME = 'packages'
EXCLUSIONS_BY_CVE_SECTION_NAME = 'cves'

# 5MB in bytes (in decimal)
FILE_MAX_SIZE_LIMIT_IN_BYTES = 5000000
Expand Down
3 changes: 2 additions & 1 deletion cycode/cli/user_settings/configuration_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,8 @@ def add_exclusion(self, scope: str, scan_type: str, exclusion_type: str, value:
config_file_manager = self.get_config_file_manager(scope)
config_file_manager.add_exclusion(scan_type, exclusion_type, value)

def _merge_exclusions(self, local_exclusions: Dict, global_exclusions: Dict) -> Dict:
@staticmethod
def _merge_exclusions(local_exclusions: Dict, global_exclusions: Dict) -> Dict:
keys = set(list(local_exclusions.keys()) + list(global_exclusions.keys()))
return {key: local_exclusions.get(key, []) + global_exclusions.get(key, []) for key in keys}

Expand Down
Loading