From 97b633a11cfad6448e48ec65c0a942703ce84bac Mon Sep 17 00:00:00 2001 From: Ilya Siamionau Date: Mon, 16 Dec 2024 18:28:35 +0100 Subject: [PATCH 1/6] CM-42904 - Add '--by-cve' option for `cycode ignore` command --- cycode/cli/commands/ignore/ignore_command.py | 76 +++++++++++++------ cycode/cli/commands/scan/code_scanner.py | 64 +++++++++------- cycode/cli/consts.py | 1 + .../user_settings/configuration_manager.py | 3 +- 4 files changed, 93 insertions(+), 51 deletions(-) diff --git a/cycode/cli/commands/ignore/ignore_command.py b/cycode/cli/commands/ignore/ignore_command.py index ea73a8e6..b94c5612 100644 --- a/cycode/cli/commands/ignore/ignore_command.py +++ b/cycode/cli/commands/ignore/ignore_command.py @@ -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 @@ -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, @@ -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( diff --git a/cycode/cli/commands/scan/code_scanner.py b/cycode/cli/commands/scan/code_scanner.py index 59e99900..431cbd02 100644 --- a/cycode/cli/commands/scan/code_scanner.py +++ b/cycode/cli/commands/scan/code_scanner.py @@ -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 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 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 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 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 in 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( diff --git a/cycode/cli/consts.py b/cycode/cli/consts.py index cd546075..b4b09a15 100644 --- a/cycode/cli/consts.py +++ b/cycode/cli/consts.py @@ -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 diff --git a/cycode/cli/user_settings/configuration_manager.py b/cycode/cli/user_settings/configuration_manager.py index b83bed32..f8d67c42 100644 --- a/cycode/cli/user_settings/configuration_manager.py +++ b/cycode/cli/user_settings/configuration_manager.py @@ -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} From 9bd25f84292e8ad9be7937080a869a402f5c7765 Mon Sep 17 00:00:00 2001 From: Ilya Siamionau Date: Tue, 17 Dec 2024 10:46:33 +0100 Subject: [PATCH 2/6] add verb Co-authored-by: elsapet --- cycode/cli/commands/scan/code_scanner.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cycode/cli/commands/scan/code_scanner.py b/cycode/cli/commands/scan/code_scanner.py index 431cbd02..b14eaf2b 100644 --- a/cycode/cli/commands/scan/code_scanner.py +++ b/cycode/cli/commands/scan/code_scanner.py @@ -768,7 +768,7 @@ def _should_exclude_detection(detection: Detection, exclusions: Dict) -> bool: exclusions_by_value = exclusions.get(consts.EXCLUSIONS_BY_VALUE_SECTION_NAME, []) if _is_detection_sha_configured_in_exclusions(detection, exclusions_by_value): logger.debug( - 'Ignoring violation because its value on the ignore list, %s', + 'Ignoring violation because its value is on the ignore list, %s', {'value_sha': detection.detection_details.get('sha512')}, ) return True From b4817f6e443aa5e6f2adc0d694cd8d2d700f2922 Mon Sep 17 00:00:00 2001 From: Ilya Siamionau Date: Tue, 17 Dec 2024 10:46:41 +0100 Subject: [PATCH 3/6] add verb 2 Co-authored-by: elsapet --- cycode/cli/commands/scan/code_scanner.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cycode/cli/commands/scan/code_scanner.py b/cycode/cli/commands/scan/code_scanner.py index b14eaf2b..d3e53787 100644 --- a/cycode/cli/commands/scan/code_scanner.py +++ b/cycode/cli/commands/scan/code_scanner.py @@ -776,7 +776,7 @@ def _should_exclude_detection(detection: Detection, exclusions: Dict) -> bool: exclusions_by_sha = exclusions.get(consts.EXCLUSIONS_BY_SHA_SECTION_NAME, []) if _is_detection_sha_configured_in_exclusions(detection, exclusions_by_sha): logger.debug( - 'Ignoring violation because its SHA value on the ignore list, %s', + 'Ignoring violation because its SHA value is on the ignore list, %s', {'sha': detection.detection_details.get('sha512')}, ) return True From 0ac09bd40dcd64e0192f2fc70e999536693370f3 Mon Sep 17 00:00:00 2001 From: Ilya Siamionau Date: Tue, 17 Dec 2024 10:46:48 +0100 Subject: [PATCH 4/6] add verb 3 Co-authored-by: elsapet --- cycode/cli/commands/scan/code_scanner.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cycode/cli/commands/scan/code_scanner.py b/cycode/cli/commands/scan/code_scanner.py index d3e53787..103e29d8 100644 --- a/cycode/cli/commands/scan/code_scanner.py +++ b/cycode/cli/commands/scan/code_scanner.py @@ -785,7 +785,7 @@ def _should_exclude_detection(detection: Detection, exclusions: Dict) -> bool: detection_rule_id = detection.detection_rule_id if detection_rule_id in exclusions_by_rule: logger.debug( - 'Ignoring violation because its Detection Rule ID on the ignore list, %s', + 'Ignoring violation because its Detection Rule ID is on the ignore list, %s', {'detection_rule_id': detection_rule_id}, ) return True From 09b4723275ce8c6453d3c41ae6f4e8f24c744e72 Mon Sep 17 00:00:00 2001 From: Ilya Siamionau Date: Tue, 17 Dec 2024 10:46:57 +0100 Subject: [PATCH 5/6] add verb 4 Co-authored-by: elsapet --- cycode/cli/commands/scan/code_scanner.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cycode/cli/commands/scan/code_scanner.py b/cycode/cli/commands/scan/code_scanner.py index 103e29d8..ebedd843 100644 --- a/cycode/cli/commands/scan/code_scanner.py +++ b/cycode/cli/commands/scan/code_scanner.py @@ -793,7 +793,7 @@ def _should_exclude_detection(detection: Detection, exclusions: Dict) -> bool: exclusions_by_package = exclusions.get(consts.EXCLUSIONS_BY_PACKAGE_SECTION_NAME, []) package = _get_package_name(detection) if package and package in exclusions_by_package: - logger.debug('Ignoring violation because its package@version on the ignore list, %s', {'package': 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, []) From 38293b37f64fe15bd5fe0a7c069893cc691e7a34 Mon Sep 17 00:00:00 2001 From: Ilya Siamionau Date: Tue, 17 Dec 2024 10:47:08 +0100 Subject: [PATCH 6/6] add verb 5 Co-authored-by: elsapet --- cycode/cli/commands/scan/code_scanner.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cycode/cli/commands/scan/code_scanner.py b/cycode/cli/commands/scan/code_scanner.py index ebedd843..42b305be 100644 --- a/cycode/cli/commands/scan/code_scanner.py +++ b/cycode/cli/commands/scan/code_scanner.py @@ -799,7 +799,7 @@ def _should_exclude_detection(detection: Detection, exclusions: Dict) -> bool: 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 in the ignore list, %s', {'cve': cve}) + logger.debug('Ignoring violation because its CVE is on the ignore list, %s', {'cve': cve}) return True return False