From a3e2edf1e848ef64df40dbfa9f80d22b3775abc3 Mon Sep 17 00:00:00 2001 From: ompatil Date: Sun, 23 Mar 2025 20:35:26 +0530 Subject: [PATCH 1/2] Added deboard command --- mantis/db/crud_assets.py | 17 +++++++++++++++++ mantis/db/crud_extended_assets.py | 18 ++++++++++++++++++ mantis/db/crud_vulnerabilities.py | 20 +++++++++++++++++++- mantis/models/args_model.py | 1 + mantis/utils/args_parse.py | 21 ++++++++++++++++++++- mantis/utils/crud_utils.py | 25 ++++++++++++++++++++++--- mantis/workflows/deboard_workflow.py | 21 +++++++++++++++++++++ mantis/workflows/mantis_workflow.py | 3 +++ 8 files changed, 121 insertions(+), 5 deletions(-) create mode 100644 mantis/workflows/deboard_workflow.py diff --git a/mantis/db/crud_assets.py b/mantis/db/crud_assets.py index 2cda9e13..deaae5a9 100644 --- a/mantis/db/crud_assets.py +++ b/mantis/db/crud_assets.py @@ -33,3 +33,20 @@ async def update_asset_query(asset: str, org: str, mongodb_query): return False else: logging.error(f'Asset {asset} does not exists in DB, Update failed') + +async def delete_assets_query(query: dict) -> bool: + try: + if not query: + return False + + result = await assets_collection.delete_many(query) + if result.deleted_count > 0: + logging.info(f"Successfully deleted {result.deleted_count} assets") + return True + else: + logging.warning("No matching assets found to delete") + return False + except Exception as e: + logging.error(f"Error deleting assets: {e}") + return False + \ No newline at end of file diff --git a/mantis/db/crud_extended_assets.py b/mantis/db/crud_extended_assets.py index 7c8d192b..922a0977 100644 --- a/mantis/db/crud_extended_assets.py +++ b/mantis/db/crud_extended_assets.py @@ -52,3 +52,21 @@ async def update_extended_asset_query(asset: str, org: str, mongodb_query): return False else: logging.error(f'Asset {asset} does not exists in DB, Update failed') + +async def delete_extended_assets_query(query: dict) -> bool: + try: + if not query: + return False + + result = await extended_assets_collection.delete_many(query) + if result.deleted_count > 0: + logging.info(f"Successfully deleted {result.deleted_count} extended assets") + return True + else: + logging.warning("No matching extended assets found to delete") + return False + + except Exception as e: + logging.error(f"Error deleting extended assets: {e}") + return False + \ No newline at end of file diff --git a/mantis/db/crud_vulnerabilities.py b/mantis/db/crud_vulnerabilities.py index 9a774a5b..2f3d0671 100644 --- a/mantis/db/crud_vulnerabilities.py +++ b/mantis/db/crud_vulnerabilities.py @@ -46,4 +46,22 @@ async def check_field_exists(field_name: str, value: str) -> bool: except Exception as e: logging.debug(f"Error checking field {field_name} in the database: {e}") - return False \ No newline at end of file + return False + +async def delete_findings_query(query: dict) -> bool: + try: + if not query: + return False + + result = await findings_collection.delete_many(query) + if result.deleted_count > 0: + logging.info(f"Successfully deleted {result.deleted_count} findings") + return True + else: + logging.warning("No matching findings found to delete") + return False + + except Exception as e: + logging.error(f"Error deleting findings: {e}") + return False + \ No newline at end of file diff --git a/mantis/models/args_model.py b/mantis/models/args_model.py index b4a67adb..2d7039f8 100644 --- a/mantis/models/args_model.py +++ b/mantis/models/args_model.py @@ -28,4 +28,5 @@ class ArgsModel(BaseModel): after_datetime_filter: str = None before_datetime_filter: str = None in_scope: bool = False + deboard_: bool = False diff --git a/mantis/utils/args_parse.py b/mantis/utils/args_parse.py index 4dd75b2b..40b06cbb 100644 --- a/mantis/utils/args_parse.py +++ b/mantis/utils/args_parse.py @@ -59,6 +59,14 @@ def report_msg(name=None): \033[0;32mmantis report -o example_org\033[0m ''' + @staticmethod + def deboard_msg(name=None): + return ''' + \033[1;34mDEBOARD: \033[0m + + \033[0;32mmantis deboard -o example_org\033[0m + ''' + @staticmethod def args_parse() -> ArgsModel: parsed_args = {} @@ -266,6 +274,13 @@ def args_parse() -> ArgsModel: required = True, help = "name of the organisation") + deboard_parser = subparser.add_parser("deboard", help="Deboard a target", usage=ArgsParse.deboard_msg()) + + deboard_parser.add_argument('-o', '--org', + dest = 'org', + required = True, + help = "name of the organisation") + # display help, if no arguments are passed args = parser.parse_args(args=None if argv[1:] else ['--help']) logging.info(f"Arguments Passed - {args}") @@ -282,7 +297,7 @@ def args_parse() -> ArgsModel: parsed_args['input_type'] = "file" parsed_args['input'] = str(args.file_name) - if args.subcommand != "list" and args.subcommand != "report": + if args.subcommand != "list" and args.subcommand != "report" and args.subcommand != "deboard": if args.aws_profiles: parsed_args["aws_profiles"] = args.aws_profiles.split(',') @@ -359,6 +374,10 @@ def args_parse() -> ArgsModel: if args.list_sub_command_ls_subs_before_filter: parsed_args["before_datetime_filter"] = f"{args.list_sub_command_ls_subs_before_filter}T23:59:59Z" + + if args.subcommand == "deboard": + parsed_args["deboard_"] = True + parsed_args['org'] = args.org args_pydantic_obj = ArgsModel.parse_obj(parsed_args) logging.info(f'parsed args - {args_pydantic_obj}') diff --git a/mantis/utils/crud_utils.py b/mantis/utils/crud_utils.py index c8823018..99ace5a5 100644 --- a/mantis/utils/crud_utils.py +++ b/mantis/utils/crud_utils.py @@ -1,9 +1,9 @@ import hashlib import logging from mantis.db.db_models import Assets, Findings, Extended -from mantis.db.crud_assets import add_assets_query, update_asset_query -from mantis.db.crud_extended_assets import add_extended_assets_query -from mantis.db.crud_vulnerabilities import add_findings_query, findings_bulk_mixed_query +from mantis.db.crud_assets import add_assets_query, delete_assets_query, update_asset_query +from mantis.db.crud_extended_assets import add_extended_assets_query, delete_extended_assets_query +from mantis.db.crud_vulnerabilities import add_findings_query, delete_findings_query, findings_bulk_mixed_query from mantis.utils.common_utils import CommonUtils from mantis.config_parsers.config_client import ConfigProvider from mantis.constants import ASSET_TYPE_SUBDOMAIN @@ -241,3 +241,22 @@ def assign_app_context(domain): if value in domain: return key return default[0] + + @staticmethod + async def deboard_organisation(org: str) -> bool: + query = {"org": org} + + try: + deleted_assets = await delete_assets_query(query) + deleted_findings = await delete_findings_query(query) + deleted_extended_assets = await delete_extended_assets_query(query) + + if not (deleted_findings or deleted_extended_assets or deleted_assets): + logging.warning(f"Organisation {org} not found in the database") + return False + + return True + except Exception as e: + logging.error(f"Error deboarding organisation {org}: {e}") + return False + diff --git a/mantis/workflows/deboard_workflow.py b/mantis/workflows/deboard_workflow.py new file mode 100644 index 00000000..a8e2377e --- /dev/null +++ b/mantis/workflows/deboard_workflow.py @@ -0,0 +1,21 @@ +import logging +from mantis.models.args_model import ArgsModel +from mantis.utils.crud_utils import CrudUtils + +class DeboardWorkflow: + @staticmethod + async def executor(args: ArgsModel): + """ + Executes the deboarding process for a given organisation. + Removes all related organisational data including assets, findings, and extended assets. + """ + if args.org: + logging.info(f"Starting deboard process for organisation {args.org}") + + deboard = await CrudUtils.deboard_organisation(args.org) + + if (deboard): + logging.info(f"Successfully deboarded organisation {args.org}") + print(f"\n\033[1;32mAll data for {args.org} has been successfully removed from the database\033[0m\n") + else: + logging.warning("No organisation specified for deboarding") diff --git a/mantis/workflows/mantis_workflow.py b/mantis/workflows/mantis_workflow.py index 4191a363..3ddf655a 100644 --- a/mantis/workflows/mantis_workflow.py +++ b/mantis/workflows/mantis_workflow.py @@ -2,6 +2,7 @@ from mantis.modules.workflow import Workflow from mantis.workflows.list_workflow import ListWorkflow from mantis.workflows.report_workflow import ReportWorkflow +from mantis.workflows.deboard_workflow import DeboardWorkflow import asyncio class MantisWorkflow: @@ -12,6 +13,8 @@ def select_workflow(args: ArgsModel) -> None: asyncio.run(ListWorkflow.executor(args)) elif args.report_: asyncio.run(ReportWorkflow.executor()) + elif args.deboard_: + asyncio.run(DeboardWorkflow.executor(args)) else: asyncio.run(Workflow.workflow_executor(args)) From c7ea20f51a17c61b1f09f1d100354fa8f0d9aa2f Mon Sep 17 00:00:00 2001 From: ompatil Date: Mon, 24 Mar 2025 22:31:21 +0530 Subject: [PATCH 2/2] Added deboard subcommands --- mantis/db/crud_assets.py | 11 ++++--- mantis/db/crud_extended_assets.py | 14 ++++----- mantis/db/crud_vulnerabilities.py | 12 ++++---- mantis/models/args_model.py | 2 +- mantis/utils/args_parse.py | 38 +++++++++++++++++++---- mantis/utils/crud_utils.py | 20 +------------ mantis/utils/deboard_utils.py | 45 ++++++++++++++++++++++++++++ mantis/workflows/deboard_workflow.py | 17 ++++++----- 8 files changed, 104 insertions(+), 55 deletions(-) create mode 100644 mantis/utils/deboard_utils.py diff --git a/mantis/db/crud_assets.py b/mantis/db/crud_assets.py index deaae5a9..e361ed9a 100644 --- a/mantis/db/crud_assets.py +++ b/mantis/db/crud_assets.py @@ -39,13 +39,12 @@ async def delete_assets_query(query: dict) -> bool: if not query: return False + logging.debug(f"Executing delete_assets_query: {query}") result = await assets_collection.delete_many(query) - if result.deleted_count > 0: - logging.info(f"Successfully deleted {result.deleted_count} assets") - return True - else: - logging.warning("No matching assets found to delete") - return False + logging.info(f"Deleted {result.deleted_count} asset{'s' if result.deleted_count != 1 else ''} from the database") + + return result.deleted_count > 0 + except Exception as e: logging.error(f"Error deleting assets: {e}") return False diff --git a/mantis/db/crud_extended_assets.py b/mantis/db/crud_extended_assets.py index 922a0977..c065c620 100644 --- a/mantis/db/crud_extended_assets.py +++ b/mantis/db/crud_extended_assets.py @@ -57,15 +57,13 @@ async def delete_extended_assets_query(query: dict) -> bool: try: if not query: return False - + + logging.debug(f"Executing delete_extended_assets_query: {query}") result = await extended_assets_collection.delete_many(query) - if result.deleted_count > 0: - logging.info(f"Successfully deleted {result.deleted_count} extended assets") - return True - else: - logging.warning("No matching extended assets found to delete") - return False - + logging.info(f"Deleted {result.deleted_count} extended asset{'s' if result.deleted_count != 1 else ''} from the database") + + return result.deleted_count > 0 + except Exception as e: logging.error(f"Error deleting extended assets: {e}") return False diff --git a/mantis/db/crud_vulnerabilities.py b/mantis/db/crud_vulnerabilities.py index 2f3d0671..dcd9417c 100644 --- a/mantis/db/crud_vulnerabilities.py +++ b/mantis/db/crud_vulnerabilities.py @@ -53,15 +53,13 @@ async def delete_findings_query(query: dict) -> bool: if not query: return False + logging.debug(f"Executing delete_findings_query: {query}") result = await findings_collection.delete_many(query) - if result.deleted_count > 0: - logging.info(f"Successfully deleted {result.deleted_count} findings") - return True - else: - logging.warning("No matching findings found to delete") - return False + logging.info(f"Deleted {result.deleted_count} finding{'s' if result.deleted_count != 1 else ''} from the database") + return result.deleted_count > 0 + except Exception as e: logging.error(f"Error deleting findings: {e}") return False - \ No newline at end of file + \ No newline at end of file diff --git a/mantis/models/args_model.py b/mantis/models/args_model.py index 2d7039f8..58aeb596 100644 --- a/mantis/models/args_model.py +++ b/mantis/models/args_model.py @@ -29,4 +29,4 @@ class ArgsModel(BaseModel): before_datetime_filter: str = None in_scope: bool = False deboard_: bool = False - + collections: list[str] = False diff --git a/mantis/utils/args_parse.py b/mantis/utils/args_parse.py index 40b06cbb..78b47c33 100644 --- a/mantis/utils/args_parse.py +++ b/mantis/utils/args_parse.py @@ -62,9 +62,13 @@ def report_msg(name=None): @staticmethod def deboard_msg(name=None): return ''' - \033[1;34mDEBOARD: \033[0m - + \033[1;34mDEBOARD:\033[0m + \033[0;32mmantis deboard -o example_org\033[0m + \033[0;32mmantis deboard -o example_org -s example_domain\033[0m + \033[0;32mmantis deboard -o example_org -a\033[0m + \033[0;32mmantis deboard -o example_org -a -f\033[0m + \033[0;32mmantis deboard -o example_org -a -s example_domain\033[0m ''' @staticmethod @@ -276,10 +280,11 @@ def args_parse() -> ArgsModel: deboard_parser = subparser.add_parser("deboard", help="Deboard a target", usage=ArgsParse.deboard_msg()) - deboard_parser.add_argument('-o', '--org', - dest = 'org', - required = True, - help = "name of the organisation") + deboard_parser.add_argument('-o', '--org', dest='org', required=True, help="name of the organisation to deboard") + deboard_parser.add_argument('-s', '--sub', dest='subdomain', help="subdomain to deboard") + deboard_parser.add_argument('-a', '--assets', dest='assets', action='store_true', help="deboard all matching assets") + deboard_parser.add_argument('-f', '--findings', dest='findings', action='store_true', help="deboard all matching findings") + deboard_parser.add_argument('-e', '--extended-assets', dest='extended_assets', action='store_true', help="deboard all matching extended assets") # display help, if no arguments are passed args = parser.parse_args(args=None if argv[1:] else ['--help']) @@ -378,6 +383,27 @@ def args_parse() -> ArgsModel: if args.subcommand == "deboard": parsed_args["deboard_"] = True parsed_args['org'] = args.org + parsed_args["collections"] = [] + + if (args.subdomain): + parsed_args['subdomain'] = args.subdomain + + collection_mapping = { + "assets": "assets", + "findings": "findings", + "extended_assets": "extended_assets", + } + + for arg, collection in collection_mapping.items(): + if getattr(args, arg, False): + parsed_args["collections"].append(collection) + + if not parsed_args["collections"]: + # default to all collections + parsed_args["collections"] = ["assets", "findings", "extended_assets"] + + parsed_args["collections"] = list(set(parsed_args["collections"])) + args_pydantic_obj = ArgsModel.parse_obj(parsed_args) logging.info(f'parsed args - {args_pydantic_obj}') diff --git a/mantis/utils/crud_utils.py b/mantis/utils/crud_utils.py index 99ace5a5..b9d75410 100644 --- a/mantis/utils/crud_utils.py +++ b/mantis/utils/crud_utils.py @@ -241,22 +241,4 @@ def assign_app_context(domain): if value in domain: return key return default[0] - - @staticmethod - async def deboard_organisation(org: str) -> bool: - query = {"org": org} - - try: - deleted_assets = await delete_assets_query(query) - deleted_findings = await delete_findings_query(query) - deleted_extended_assets = await delete_extended_assets_query(query) - - if not (deleted_findings or deleted_extended_assets or deleted_assets): - logging.warning(f"Organisation {org} not found in the database") - return False - - return True - except Exception as e: - logging.error(f"Error deboarding organisation {org}: {e}") - return False - + \ No newline at end of file diff --git a/mantis/utils/deboard_utils.py b/mantis/utils/deboard_utils.py new file mode 100644 index 00000000..be1b6308 --- /dev/null +++ b/mantis/utils/deboard_utils.py @@ -0,0 +1,45 @@ +import logging +from mantis.db.crud_assets import delete_assets_query +from mantis.db.crud_extended_assets import delete_extended_assets_query +from mantis.db.crud_vulnerabilities import delete_findings_query + +async def deboard_organisation(args): + try: + org = args.org + subdomain = args.subdomain + collections = args.collections or ["assets", "findings", "extended_assets"] + + logging.info(f"Target collections for deboard process: {', '.join(collections)}") + + base_query = {"org": org} + queries = { + "assets": base_query.copy(), + "findings": base_query.copy(), + "extended_assets": base_query.copy(), + } + + if subdomain: + queries["assets"]["asset"] = subdomain + queries["findings"]["url"] = subdomain + queries["extended_assets"]["asset"] = subdomain + + delete_methods = { + "assets": delete_assets_query, + "findings": delete_findings_query, + "extended_assets": delete_extended_assets_query, + } + + deletion_results = {} + + for collection in collections: + if collection in delete_methods: + deletion_results[collection] = await delete_methods[collection](queries[collection]) + + if not any(deletion_results.values()): + return False + + return True + + except Exception as e: + logging.error(f"Error deboarding organisation {org}: {e}") + return False diff --git a/mantis/workflows/deboard_workflow.py b/mantis/workflows/deboard_workflow.py index a8e2377e..6f4b94ed 100644 --- a/mantis/workflows/deboard_workflow.py +++ b/mantis/workflows/deboard_workflow.py @@ -1,21 +1,22 @@ import logging from mantis.models.args_model import ArgsModel -from mantis.utils.crud_utils import CrudUtils +from mantis.utils.deboard_utils import deboard_organisation class DeboardWorkflow: @staticmethod async def executor(args: ArgsModel): """ - Executes the deboarding process for a given organisation. - Removes all related organisational data including assets, findings, and extended assets. + Executes the deboarding process for a given organisation based on provided arguments. + Deletes data from assets, findings, and extended assets collections accordingly. """ if args.org: logging.info(f"Starting deboard process for organisation {args.org}") - deboard = await CrudUtils.deboard_organisation(args.org) + deboard = await deboard_organisation(args) if (deboard): - logging.info(f"Successfully deboarded organisation {args.org}") - print(f"\n\033[1;32mAll data for {args.org} has been successfully removed from the database\033[0m\n") - else: - logging.warning("No organisation specified for deboarding") + logging.info(f"Successfully completed deboard process for organisation {args.org}") + print(f"\n\033[1;32mAll specified data for {args.org} has been successfully deleted from the database\033[0m\n") + else: + logging.info(f"Deboard process completed for organisation {args.org}, but no matching records were found") + print(f"\n\033[1;32mNo matching records found for {args.org}, Nothing was deleted\033[0m\n")