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

upd: Improve exports in Microsoft Sentinel in MSSP mode #18

Merged
merged 5 commits into from
Sep 23, 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
13 changes: 9 additions & 4 deletions src/droid/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,18 +175,23 @@ def droid_platform_config(args, config_path):
config["workspace_id"] = environ.get("DROID_AZURE_WORKSPACE_ID")
if environ.get("DROID_AZURE_WORKSPACE_NAME"):
config["workspace_name"] = environ.get("DROID_AZURE_WORKSPACE_NAME")
if environ.get("DROID_AZURE_SUBSCRIPTION_ID"):
config["subscription_id"] = environ.get("DROID_AZURE_SUBSCRIPTION_ID")
if environ.get("DROID_AZURE_RESOURCE_GROUP"):
config["resource_group"] = environ.get("DROID_AZURE_RESOURCE_GROUP")

except Exception:
raise Exception("Something unexpected happened...")

if args.export or args.search or args.integrity:

auth_methods = ["default", "app"]

if environ.get("DROID_MS_CLOUD_SEARCH_AUTH"):
config["search_auth"] = environ.get("DROID_MS_CLOUD_SEARCH_AUTH")
if environ.get("DROID_AZURE_SEARCH_AUTH"):
config["search_auth"] = environ.get("DROID_AZURE_SEARCH_AUTH")

if environ.get("DROID_MS_CLOUD_EXPORT_AUTH"):
config["export_auth"] = environ.get("DROID_MS_CLOUD_EXPORT_AUTH")
if environ.get("DROID_AZURE_EXPORT_AUTH"):
config["export_auth"] = environ.get("DROID_AZURE_EXPORT_AUTH")

if "search_auth" in config and config["search_auth"] not in auth_methods:
raise ValueError(f"Invalid search_auth: {config['search_auth']}")
Expand Down
4 changes: 2 additions & 2 deletions src/droid/convert.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
67 changes: 57 additions & 10 deletions src/droid/integrity.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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"
}
Expand Down Expand Up @@ -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
Expand All @@ -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":
Expand Down
56 changes: 50 additions & 6 deletions src/droid/platforms/sentinel.py
Original file line number Diff line number Diff line change
Expand Up @@ -192,13 +192,23 @@ def get_workspaces(self, credential, export_mode=False):
else:
entry_dict = {
"customer": workspace_name,
graph_key: graph_value
"workspace_id": graph_value
}

workspace_list.append(entry_dict)

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,
Expand Down Expand Up @@ -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()

Expand Down Expand Up @@ -435,17 +476,19 @@ def create_rule(self, rule_content, rule_converted, rule_file):
techniques=self.mitre_techniques(rule_content)
)

credential = self.get_credentials()

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']
self._tenant_id = info['tenant_id']
resource_group_name = info['resource_group_name']
subscription_id = info['subscription_id']

self.logger.debug(f"Exporting to {workspace_name}")
self.logger.debug(f"Exporting to {workspace_name} from group id {group}")

credential = self.get_credentials()

# Create a new SecurityInsights client for the target subscription
client = SecurityInsights(credential, subscription_id)
Expand All @@ -465,6 +508,7 @@ def create_rule(self, rule_content, rule_converted, rule_file):
client_workspaces = self.get_workspaces(credential, export_mode=True)
# TODO: Export to all customers
else:
credential = self.get_credentials()
client = SecurityInsights(credential, self._subscription_id)
try:
client.alert_rules.create_or_update(
Expand Down