From 397e6e1d949d9bc617ea5f2fa2635632323b0653 Mon Sep 17 00:00:00 2001 From: Mathieu LE CLEACH Date: Mon, 23 Sep 2024 13:58:27 +0200 Subject: [PATCH] add: ability to do integrity check in mssp mode --- src/droid/convert.py | 4 +- src/droid/integrity.py | 67 ++++++++++++++++++++++++++++----- src/droid/platforms/sentinel.py | 45 +++++++++++++++++++++- 3 files changed, 102 insertions(+), 14 deletions(-) diff --git a/src/droid/convert.py b/src/droid/convert.py index 953345b..1c934a3 100644 --- a/src/droid/convert.py +++ b/src/droid/convert.py @@ -185,10 +185,10 @@ def convert_rules(parameters, droid_config, base_config, logger_param): platform = ElasticPlatform(droid_config, logger_param, "esql", raw=False) elif "eql" in platform_name: platform = ElasticPlatform(droid_config, logger_param, "eql", raw=False) - elif "microsoft_sentinel" in platform_name: - platform = SentinelPlatform(droid_config, logger_param) elif "microsoft_sentinel" in platform_name and parameters.mssp: platform = SentinelPlatform(droid_config, logger_param, export_mssp=True) + elif "microsoft_sentinel" in platform_name: + platform = SentinelPlatform(droid_config, logger_param, export_mssp=False) elif "microsoft_xdr" in platform_name and parameters.sentinel_xdr: platform = SentinelPlatform(droid_config, logger_param) elif "microsoft_xdr" in platform_name: diff --git a/src/droid/integrity.py b/src/droid/integrity.py index cc00056..70ae369 100644 --- a/src/droid/integrity.py +++ b/src/droid/integrity.py @@ -83,20 +83,63 @@ def integrity_rule_splunk(rule_converted, rule_content, platform: SplunkPlatform if error: return error -def integrity_rule_sentinel(rule_converted, rule_content, platform: SentinelPlatform, rule_file, parameters, logger, error): +def integrity_rule_sentinel_mssp(rule_converted, rule_content, platform: SentinelPlatform, rule_file, parameters, logger, error): try: - saved_search: dict = platform.get_rule(rule_content, rule_file) - # Mapping rule_content with a MS Sentinel saved search properties - mapping = { - "id": "name", - "detection": "query", - "description": "description" - } + export_list = platform.get_integrity_export_mssp() + except Exception as e: + logger.error(f"Couldn't get the export list for the designated customers - error {e}") + return error + + logger.info("Integrity check for designated customers") + + error_occured = False + + for group, info in export_list.items(): + + tenant_id = info['tenant_id'] + subscription_id = info['subscription_id'] + resource_group_name = info['resource_group_name'] + workspace_name = info['workspace_name'] + + logger.debug(f"Processing rule on {workspace_name} from group id {group}") + try: + saved_search: dict = platform.get_rule_mssp( + rule_content, rule_file, tenant_id, + subscription_id, resource_group_name, + workspace_name + ) + except Exception as e: + logger.error(f"Couldn't check the integrity for the rule {rule_file} on workspace {workspace_name} from {group} - error {e}") + return error + + error = integrity_rule_sentinel(rule_converted, rule_content, platform, rule_file, parameters, logger, error, saved_search=saved_search) + + if error: + error_occured = True + + if error_occured: + return error + +def integrity_rule_sentinel( + rule_converted, rule_content, platform: SentinelPlatform, + rule_file, parameters, logger, + error, saved_search=None + ): + + try: + if not saved_search: + saved_search: dict = platform.get_rule(rule_content, rule_file) except Exception as e: logger.error(f"Couldn't check the integrity for the rule {rule_file} - error {e}") return error + mapping = { + "id": "name", + "detection": "query", + "description": "description" + } + if saved_search: logger.info(f"Successfully retrieved the rule {rule_file}") else: @@ -223,7 +266,6 @@ def integrity_rule_elastic(rule_converted, rule_content, platform: ElasticPlatfo rule_content["detection"] = rule_converted mapping = { - #"id": "name", # Not sure why this is here? "detection": "query", "description": "description" } @@ -275,6 +317,9 @@ def integrity_rule(parameters, rule_converted, rule_content, platform, rule_file elif parameters.platform == "microsoft_xdr": error = integrity_rule_ms_xdr(rule_converted, rule_content, platform, rule_file, parameters, logger, error) return error + elif "microsoft_sentinel" in parameters.platform and parameters.mssp: + error = integrity_rule_sentinel_mssp(rule_converted, rule_content, platform, rule_file, parameters, logger, error) + return error elif "microsoft_sentinel" in parameters.platform: error = integrity_rule_sentinel(rule_converted, rule_content, platform, rule_file, parameters, logger, error) return error @@ -287,8 +332,10 @@ def integrity_rule_raw(parameters: dict, export_config: dict, logger_param: dict if parameters.platform == "splunk": platform = SplunkPlatform(export_config, logger_param) + elif parameters.platform == "microsoft_sentinel" and parameters.mssp: + platform = SentinelPlatform(export_config, logger_param, export_mssp=True) elif parameters.platform == "microsoft_sentinel": - platform = SentinelPlatform(export_config, logger_param) + platform = SentinelPlatform(export_config, logger_param, export_mssp=False) elif parameters.platform == "microsoft_xdr": platform = MicrosoftXDRPlatform(export_config, logger_param) elif parameters.platform == "esql" or parameters.platform == "eql": diff --git a/src/droid/platforms/sentinel.py b/src/droid/platforms/sentinel.py index d28aa21..fc03d29 100644 --- a/src/droid/platforms/sentinel.py +++ b/src/droid/platforms/sentinel.py @@ -199,6 +199,16 @@ def get_workspaces(self, credential, export_mode=False): return workspace_list + def get_integrity_export_mssp(self) -> list: + + if self._export_list_mssp: + self.logger.info("Integrity check for designated customers") + return self._export_list_mssp + else: + self.logger.error("No export_list_mssp found") + raise + # TODO: Integrity check to all customers + def mssp_run_sentinel_search(self, client, @@ -314,9 +324,40 @@ def run_sentinel_search(self, rule_converted, rule_file, mssp_mode): except Exception as e: self.logger.error(f"Rule {rule_file} error: {e}") + def get_rule_mssp(self, rule_content, rule_file, + tenant_id, subscription_id, resource_group_name, + workspace_name): + """Retrieve a scheduled alert rule in Sentinel in MSSP mode + """ + + self._tenant_id = tenant_id + credential = self.get_credentials() + + client = SecurityInsights(credential, subscription_id) + + try: + rule = client.alert_rules.get( + resource_group_name=resource_group_name, + workspace_name=workspace_name, + rule_id=rule_content['id'] + ) + self.logger.info(f"Successfully retrieved the rule {rule_file} for {workspace_name}") + + if rule: + return rule + else: + return None + + except ResourceNotFoundError: + self.logger.error(f"Rule not found {rule_file} in {workspace_name}") + return None + + except Exception as e: + self.logger.error(f"Could not retrieve the rule {rule_file} in {workspace_name}") + raise + def get_rule(self, rule_content, rule_file): """Retrieve a scheduled alert rule in Sentinel - Remove a scheduled alert rule in Sentinel """ credential = self.get_credentials() @@ -437,7 +478,7 @@ def create_rule(self, rule_content, rule_converted, rule_file): if self._export_mssp: if self._export_list_mssp: - self.logger.info("Exporting to restricted customers") + self.logger.info("Exporting to designated customers") for group, info in self._export_list_mssp.items(): workspace_name = info['workspace_name']