From 4c0321ab9384d0e9f4aa81afb048a293c9251243 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Sun, 26 Jan 2020 18:47:46 +0200 Subject: [PATCH 001/152] Added collector and submodule --- .gitmodules | 3 +++ .../common/data/system_info_collectors_names.py | 1 + .../system_info/collectors/scoutsuite | 1 + .../collectors/scoutsuite_collector.py | 16 ++++++++++++++++ 4 files changed, 21 insertions(+) create mode 100644 .gitmodules create mode 160000 monkey/infection_monkey/system_info/collectors/scoutsuite create mode 100644 monkey/infection_monkey/system_info/collectors/scoutsuite_collector.py diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 000000000..bcb7399ee --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "monkey/infection_monkey/system_info/collectors/scoutsuite"] + path = monkey/infection_monkey/system_info/collectors/scoutsuite + url = git@github.com:ShayNehmad/ScoutSuite.git diff --git a/monkey/common/data/system_info_collectors_names.py b/monkey/common/data/system_info_collectors_names.py index 831bbe142..ad8a3fd74 100644 --- a/monkey/common/data/system_info_collectors_names.py +++ b/monkey/common/data/system_info_collectors_names.py @@ -2,3 +2,4 @@ AWS_COLLECTOR = "AwsCollector" HOSTNAME_COLLECTOR = "HostnameCollector" ENVIRONMENT_COLLECTOR = "EnvironmentCollector" PROCESS_LIST_COLLECTOR = "ProcessListCollector" +SCOUTSUITE_COLLECTOR = "ScoutSuiteCollector" diff --git a/monkey/infection_monkey/system_info/collectors/scoutsuite b/monkey/infection_monkey/system_info/collectors/scoutsuite new file mode 160000 index 000000000..e784fc27a --- /dev/null +++ b/monkey/infection_monkey/system_info/collectors/scoutsuite @@ -0,0 +1 @@ +Subproject commit e784fc27ae8311c3c610bccd556d2bef3cd54d63 diff --git a/monkey/infection_monkey/system_info/collectors/scoutsuite_collector.py b/monkey/infection_monkey/system_info/collectors/scoutsuite_collector.py new file mode 100644 index 000000000..6645290f5 --- /dev/null +++ b/monkey/infection_monkey/system_info/collectors/scoutsuite_collector.py @@ -0,0 +1,16 @@ +import logging + +from common.data.system_info_collectors_names import SCOUTSUITE_COLLECTOR +from infection_monkey.system_info.system_info_collector import SystemInfoCollector +from infection_monkey.system_info.collectors.scoutsuite.ScoutSuite.__main__ import run + +logger = logging.getLogger(__name__) + + +class HostnameCollector(SystemInfoCollector): + def __init__(self): + super().__init__(name=SCOUTSUITE_COLLECTOR) + + def collect(self) -> dict: + + return {} From a0db78502090bd95b5a75f7c4baee7db53c105fb Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Mon, 27 Jan 2020 12:25:12 +0200 Subject: [PATCH 002/152] Update .gitmodules --- .gitmodules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitmodules b/.gitmodules index bcb7399ee..61ec3dae5 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ [submodule "monkey/infection_monkey/system_info/collectors/scoutsuite"] path = monkey/infection_monkey/system_info/collectors/scoutsuite - url = git@github.com:ShayNehmad/ScoutSuite.git + url = https://github.com/ShayNehmad/ScoutSuite.git From 87f90b36f084a308820b9665e1c3b1b2eea5a93b Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Mon, 27 Jan 2020 14:28:52 +0200 Subject: [PATCH 003/152] Excluding scoutsuite from flake8 --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 6abeb59b1..cb51083e4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -24,7 +24,7 @@ before_script: script: # Check Python code # Check syntax errors and fail the build if any are found. -- flake8 . --count --select=E901,E999,F821,F822,F823 --show-source --statistics +- flake8 . --count --select=E901,E999,F821,F822,F823 --show-source --statistics --exclude=monkey/infection_monkey/system_info/collectors/scoutsuite # Warn about linter issues. # --exit-zero forces Flake8 to use the exit status code 0 even if there are errors, which means this will NOT fail the build. From a26b9114ef0deec3eb0e8aa0ffb118dca94010bc Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Mon, 27 Jan 2020 14:28:57 +0200 Subject: [PATCH 004/152] Update scoutsuite_collector.py --- .../system_info/collectors/scoutsuite_collector.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/monkey/infection_monkey/system_info/collectors/scoutsuite_collector.py b/monkey/infection_monkey/system_info/collectors/scoutsuite_collector.py index 6645290f5..fdf7ce6fa 100644 --- a/monkey/infection_monkey/system_info/collectors/scoutsuite_collector.py +++ b/monkey/infection_monkey/system_info/collectors/scoutsuite_collector.py @@ -1,16 +1,23 @@ import logging +from common.cloud.environment_names import Environment from common.data.system_info_collectors_names import SCOUTSUITE_COLLECTOR from infection_monkey.system_info.system_info_collector import SystemInfoCollector from infection_monkey.system_info.collectors.scoutsuite.ScoutSuite.__main__ import run +from system_info.collectors.environment_collector import get_monkey_environment logger = logging.getLogger(__name__) -class HostnameCollector(SystemInfoCollector): +class ScoutSuiteCollector(SystemInfoCollector): def __init__(self): super().__init__(name=SCOUTSUITE_COLLECTOR) def collect(self) -> dict: - + env = get_monkey_environment() + if env == Environment.ON_PREMISE.value: + logger.info("Monkey is not on cloud; not running ScoutSuite") + else: + logger.info(f"Attempting to execute ScoutSuite with {env.lower()}") + run(env.lower(), debug=True, quiet=False) return {} From f49089aed306f1ee155f922110f08fc93c5b804d Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Mon, 27 Jan 2020 14:46:39 +0200 Subject: [PATCH 005/152] Added basic framework for running scoutsuite --- .travis.yml | 1 + .../collectors/scoutsuite_collector.py | 8 ++++++-- monkey/monkey_island/cc/services/config_schema.py | 15 ++++++++++++--- .../system_info_collectors/scoutsuite.py | 9 +++++++++ .../system_info_telemetry_dispatcher.py | 6 ++++-- 5 files changed, 32 insertions(+), 7 deletions(-) create mode 100644 monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/scoutsuite.py diff --git a/.travis.yml b/.travis.yml index cb51083e4..b83e12eb4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,6 +16,7 @@ install: - pip install -r monkey/monkey_island/requirements.txt # for unit tests - pip install flake8 pytest dlint # for next stages - pip install -r monkey/infection_monkey/requirements.txt # for unit tests +- pip install -r monkey/infection_monkey/system_info/collectors/scoutsuite/requirements.txt before_script: # Set the server config to `testing`. This is required for for the UTs to pass. diff --git a/monkey/infection_monkey/system_info/collectors/scoutsuite_collector.py b/monkey/infection_monkey/system_info/collectors/scoutsuite_collector.py index fdf7ce6fa..38c6d7c0f 100644 --- a/monkey/infection_monkey/system_info/collectors/scoutsuite_collector.py +++ b/monkey/infection_monkey/system_info/collectors/scoutsuite_collector.py @@ -17,7 +17,11 @@ class ScoutSuiteCollector(SystemInfoCollector): env = get_monkey_environment() if env == Environment.ON_PREMISE.value: logger.info("Monkey is not on cloud; not running ScoutSuite") + return {} else: logger.info(f"Attempting to execute ScoutSuite with {env.lower()}") - run(env.lower(), debug=True, quiet=False) - return {} + scout_suite_results = run(env.lower(), debug=True, quiet=False) + return { + "Environment": env, + "Results": scout_suite_results + } diff --git a/monkey/monkey_island/cc/services/config_schema.py b/monkey/monkey_island/cc/services/config_schema.py index 3d0220ee2..59cd97686 100644 --- a/monkey/monkey_island/cc/services/config_schema.py +++ b/monkey/monkey_island/cc/services/config_schema.py @@ -1,5 +1,5 @@ from common.data.system_info_collectors_names \ - import AWS_COLLECTOR, ENVIRONMENT_COLLECTOR, HOSTNAME_COLLECTOR, PROCESS_LIST_COLLECTOR + import AWS_COLLECTOR, ENVIRONMENT_COLLECTOR, HOSTNAME_COLLECTOR, PROCESS_LIST_COLLECTOR, SCOUTSUITE_COLLECTOR WARNING_SIGN = " \u26A0" @@ -130,7 +130,7 @@ SCHEMA = { "title": "Collect the machine's hostname", "attack_techniques": [] }, -{ + { "type": "string", "enum": [ PROCESS_LIST_COLLECTOR @@ -138,6 +138,14 @@ SCHEMA = { "title": "Collect running processes on the machine", "attack_techniques": [] }, + { + "type": "string", + "enum": [ + SCOUTSUITE_COLLECTOR + ], + "title": "If on cloud, execute ScoutSuite and collect its results", + "attack_techniques": [] + }, ], }, "post_breach_acts": { @@ -485,7 +493,8 @@ SCHEMA = { ENVIRONMENT_COLLECTOR, AWS_COLLECTOR, HOSTNAME_COLLECTOR, - PROCESS_LIST_COLLECTOR + PROCESS_LIST_COLLECTOR, + SCOUTSUITE_COLLECTOR ], "description": "Determines which system information collectors will collect information." }, diff --git a/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/scoutsuite.py b/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/scoutsuite.py new file mode 100644 index 000000000..85d053e47 --- /dev/null +++ b/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/scoutsuite.py @@ -0,0 +1,9 @@ +import logging +import json + +logger = logging.getLogger(__name__) + + +def process_scout_suite_telemetry(collector_results, monkey_guid): + # Monkey.get_single_monkey_by_guid(monkey_guid).set_hostname(collector_results["hostname"]) + logger.info(f"\n\n{json.dumps(collector_results, indent=2)}\n{monkey_guid}") diff --git a/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/system_info_telemetry_dispatcher.py b/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/system_info_telemetry_dispatcher.py index b5f2d24ea..1c651501d 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/system_info_telemetry_dispatcher.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/system_info_telemetry_dispatcher.py @@ -2,10 +2,11 @@ import logging import typing from common.data.system_info_collectors_names \ - import AWS_COLLECTOR, ENVIRONMENT_COLLECTOR, HOSTNAME_COLLECTOR, PROCESS_LIST_COLLECTOR + import AWS_COLLECTOR, ENVIRONMENT_COLLECTOR, HOSTNAME_COLLECTOR, PROCESS_LIST_COLLECTOR, SCOUTSUITE_COLLECTOR from monkey_island.cc.services.telemetry.processing.system_info_collectors.aws import process_aws_telemetry from monkey_island.cc.services.telemetry.processing.system_info_collectors.environment import process_environment_telemetry from monkey_island.cc.services.telemetry.processing.system_info_collectors.hostname import process_hostname_telemetry +from monkey_island.cc.services.telemetry.processing.system_info_collectors.scoutsuite import process_scout_suite_telemetry from monkey_island.cc.services.telemetry.zero_trust_tests.antivirus_existence import test_antivirus_existence logger = logging.getLogger(__name__) @@ -14,7 +15,8 @@ SYSTEM_INFO_COLLECTOR_TO_TELEMETRY_PROCESSORS = { AWS_COLLECTOR: [process_aws_telemetry], ENVIRONMENT_COLLECTOR: [process_environment_telemetry], HOSTNAME_COLLECTOR: [process_hostname_telemetry], - PROCESS_LIST_COLLECTOR: [test_antivirus_existence] + PROCESS_LIST_COLLECTOR: [test_antivirus_existence], + SCOUTSUITE_COLLECTOR: [process_scout_suite_telemetry] } From b8b015e84e83ed3f2863e068a6b47b23cdd7235f Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Mon, 27 Jan 2020 14:59:31 +0200 Subject: [PATCH 006/152] Update .travis.yml --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index b83e12eb4..7435a721e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -32,7 +32,7 @@ script: # --count will print the total number of errors. # --statistics Count the number of occurrences of each error/warning code and print a report. # The output is redirected to a file. -- flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics > flake8_warnings.txt +- flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics --exclude=monkey/infection_monkey/system_info/collectors/scoutsuite > flake8_warnings.txt # Display the linter issues - cat flake8_warnings.txt # Make sure that we haven't increased the amount of warnings. From b5f8fbe9f0ddd68792dd958674783582c862fe91 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Tue, 4 Feb 2020 14:39:38 +0200 Subject: [PATCH 007/152] WIP --- ...tsuite_collector.py => scout_suite_collector.py} | 13 +++++++++++-- .../processing/system_info_collectors/scoutsuite.py | 2 +- 2 files changed, 12 insertions(+), 3 deletions(-) rename monkey/infection_monkey/system_info/collectors/{scoutsuite_collector.py => scout_suite_collector.py} (74%) diff --git a/monkey/infection_monkey/system_info/collectors/scoutsuite_collector.py b/monkey/infection_monkey/system_info/collectors/scout_suite_collector.py similarity index 74% rename from monkey/infection_monkey/system_info/collectors/scoutsuite_collector.py rename to monkey/infection_monkey/system_info/collectors/scout_suite_collector.py index 38c6d7c0f..09adf94e4 100644 --- a/monkey/infection_monkey/system_info/collectors/scoutsuite_collector.py +++ b/monkey/infection_monkey/system_info/collectors/scout_suite_collector.py @@ -1,4 +1,5 @@ import logging +import tempfile from common.cloud.environment_names import Environment from common.data.system_info_collectors_names import SCOUTSUITE_COLLECTOR @@ -15,12 +16,20 @@ class ScoutSuiteCollector(SystemInfoCollector): def collect(self) -> dict: env = get_monkey_environment() + env = "AWS" if env == Environment.ON_PREMISE.value: logger.info("Monkey is not on cloud; not running ScoutSuite") return {} else: - logger.info(f"Attempting to execute ScoutSuite with {env.lower()}") - scout_suite_results = run(env.lower(), debug=True, quiet=False) + tmp_dir_path = tempfile.mkdtemp() + logger.info(f"Attempting to execute ScoutSuite with {env.lower()}, saving results in {tmp_dir_path}") + + scout_suite_results = run( + env.lower(), + debug=True, + quiet=False, + no_browser=True, + report_dir=tmp_dir_path) return { "Environment": env, "Results": scout_suite_results diff --git a/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/scoutsuite.py b/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/scoutsuite.py index 85d053e47..c0a2517f2 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/scoutsuite.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/scoutsuite.py @@ -6,4 +6,4 @@ logger = logging.getLogger(__name__) def process_scout_suite_telemetry(collector_results, monkey_guid): # Monkey.get_single_monkey_by_guid(monkey_guid).set_hostname(collector_results["hostname"]) - logger.info(f"\n\n{json.dumps(collector_results, indent=2)}\n{monkey_guid}") + logger.info(f"ScoutSuite results:\n{json.dumps(collector_results, indent=2)}") From a365d2eb3cffeac02f8144e59691c3f21ba2c6ac Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Thu, 3 Sep 2020 12:06:20 +0300 Subject: [PATCH 008/152] Exported telem categories into dict, moved scoutsuite submodule to a different dir --- .gitmodules | 2 +- .../{data => common_consts}/__init__.py | 0 .../{data => common_consts}/api_url_consts.py | 0 .../{data => common_consts}/network_consts.py | 0 .../post_breach_consts.py | 0 .../system_info_collectors_names.py | 0 .../common/common_consts/telem_categories.py | 9 +++++ .../validation_formats.py | 0 .../zero_trust_consts.py | 0 monkey/infection_monkey/control.py | 2 +- .../infection_monkey/exploit/elasticgroovy.py | 2 +- .../infection_monkey/network/elasticfinger.py | 2 +- .../post_breach/actions/add_user.py | 2 +- .../actions/change_file_privileges.py | 2 +- .../actions/clear_command_history.py | 2 +- .../actions/communicate_as_new_user.py | 2 +- .../post_breach/actions/discover_accounts.py | 2 +- .../post_breach/actions/hide_files.py | 2 +- .../actions/modify_shell_startup_files.py | 2 +- .../post_breach/actions/schedule_jobs.py | 2 +- .../post_breach/actions/use_signed_scripts.py | 2 +- .../post_breach/actions/use_trap_command.py | 2 +- .../post_breach/actions/users_custom_pba.py | 2 +- .../infection_monkey/system_info/__init__.py | 2 +- .../system_info/collectors/aws_collector.py | 2 +- .../collectors/environment_collector.py | 2 +- .../collectors/hostname_collector.py | 2 +- .../collectors/process_list_collector.py | 2 +- .../collectors/scout_suite_collector.py | 36 ------------------- .../system_info/collectors/scoutsuite | 1 - .../scoutsuite_collector/__init__.py | 0 .../scoutsuite_collector/scoutsuite | 1 + .../system_info/windows_info_collector.py | 2 +- .../telemetry/exploit_telem.py | 3 +- .../telemetry/post_breach_telem.py | 3 +- .../infection_monkey/telemetry/scan_telem.py | 3 +- .../infection_monkey/telemetry/state_telem.py | 3 +- .../telemetry/system_info_telem.py | 3 +- .../infection_monkey/telemetry/trace_telem.py | 3 +- .../telemetry/tunnel_telem.py | 3 +- monkey/monkey_island/cc/app.py | 2 +- .../cc/models/zero_trust/aggregate_finding.py | 2 +- .../cc/models/zero_trust/event.py | 2 +- .../cc/models/zero_trust/finding.py | 2 +- .../models/zero_trust/segmentation_finding.py | 2 +- .../zero_trust/test_aggregate_finding.py | 2 +- .../cc/models/zero_trust/test_event.py | 2 +- .../cc/models/zero_trust/test_finding.py | 2 +- .../zero_trust/test_segmentation_finding.py | 2 +- .../monkey_island/cc/resources/telemetry.py | 3 +- .../cc/resources/telemetry_feed.py | 15 ++++---- .../attack/technique_reports/T1053.py | 2 +- .../attack/technique_reports/T1087.py | 2 +- .../attack/technique_reports/T1136.py | 2 +- .../attack/technique_reports/T1146.py | 2 +- .../attack/technique_reports/T1154.py | 2 +- .../attack/technique_reports/T1156.py | 2 +- .../attack/technique_reports/T1158.py | 2 +- .../attack/technique_reports/T1166.py | 2 +- .../attack/technique_reports/T1168.py | 2 +- .../attack/technique_reports/T1216.py | 2 +- .../attack/technique_reports/T1504.py | 2 +- .../services/config_schema/basic_network.py | 2 +- .../system_info_collector_classes.py | 12 +++---- .../cc/services/config_schema/monkey.py | 12 +++---- .../reporting/test_zero_trust_service.py | 2 +- .../services/reporting/zero_trust_service.py | 2 +- .../telemetry/processing/post_breach.py | 2 +- .../system_info_telemetry_dispatcher.py | 10 +++--- .../zero_trust_tests/antivirus_existence.py | 2 +- .../communicate_as_new_user.py | 2 +- .../zero_trust_tests/data_endpoints.py | 4 +-- .../zero_trust_tests/machine_exploited.py | 2 +- .../zero_trust_tests/segmentation.py | 2 +- .../test_segmentation_zt_tests.py | 2 +- .../telemetry/zero_trust_tests/tunneling.py | 2 +- 76 files changed, 104 insertions(+), 122 deletions(-) rename monkey/common/{data => common_consts}/__init__.py (100%) rename monkey/common/{data => common_consts}/api_url_consts.py (100%) rename monkey/common/{data => common_consts}/network_consts.py (100%) rename monkey/common/{data => common_consts}/post_breach_consts.py (100%) rename monkey/common/{data => common_consts}/system_info_collectors_names.py (100%) create mode 100644 monkey/common/common_consts/telem_categories.py rename monkey/common/{data => common_consts}/validation_formats.py (100%) rename monkey/common/{data => common_consts}/zero_trust_consts.py (100%) delete mode 100644 monkey/infection_monkey/system_info/collectors/scout_suite_collector.py delete mode 160000 monkey/infection_monkey/system_info/collectors/scoutsuite create mode 100644 monkey/infection_monkey/system_info/collectors/scoutsuite_collector/__init__.py create mode 160000 monkey/infection_monkey/system_info/collectors/scoutsuite_collector/scoutsuite diff --git a/.gitmodules b/.gitmodules index df2805df9..6f784049d 100644 --- a/.gitmodules +++ b/.gitmodules @@ -5,5 +5,5 @@ path = docs/themes/learn url = https://github.com/guardicode/hugo-theme-learn.git [submodule "monkey/infection_monkey/system_info/collectors/scoutsuite"] - path = monkey/infection_monkey/system_info/collectors/scoutsuite + path = monkey/infection_monkey/system_info/collectors/scoutsuite_collector/scoutsuite url = https://github.com/ShayNehmad/ScoutSuite.git diff --git a/monkey/common/data/__init__.py b/monkey/common/common_consts/__init__.py similarity index 100% rename from monkey/common/data/__init__.py rename to monkey/common/common_consts/__init__.py diff --git a/monkey/common/data/api_url_consts.py b/monkey/common/common_consts/api_url_consts.py similarity index 100% rename from monkey/common/data/api_url_consts.py rename to monkey/common/common_consts/api_url_consts.py diff --git a/monkey/common/data/network_consts.py b/monkey/common/common_consts/network_consts.py similarity index 100% rename from monkey/common/data/network_consts.py rename to monkey/common/common_consts/network_consts.py diff --git a/monkey/common/data/post_breach_consts.py b/monkey/common/common_consts/post_breach_consts.py similarity index 100% rename from monkey/common/data/post_breach_consts.py rename to monkey/common/common_consts/post_breach_consts.py diff --git a/monkey/common/data/system_info_collectors_names.py b/monkey/common/common_consts/system_info_collectors_names.py similarity index 100% rename from monkey/common/data/system_info_collectors_names.py rename to monkey/common/common_consts/system_info_collectors_names.py diff --git a/monkey/common/common_consts/telem_categories.py b/monkey/common/common_consts/telem_categories.py new file mode 100644 index 000000000..c983786b9 --- /dev/null +++ b/monkey/common/common_consts/telem_categories.py @@ -0,0 +1,9 @@ +class TelemCategoryEnum: + EXPLOIT = 'exploit' + POST_BREACH = 'post_breach' + SCAN = 'scan' + SCOUTSUITE = 'scoutsuite' + STATE = 'state' + SYSTEM_INFO = 'system_info' + TRACE = 'trace' + TUNNEL = 'tunnel' diff --git a/monkey/common/data/validation_formats.py b/monkey/common/common_consts/validation_formats.py similarity index 100% rename from monkey/common/data/validation_formats.py rename to monkey/common/common_consts/validation_formats.py diff --git a/monkey/common/data/zero_trust_consts.py b/monkey/common/common_consts/zero_trust_consts.py similarity index 100% rename from monkey/common/data/zero_trust_consts.py rename to monkey/common/common_consts/zero_trust_consts.py diff --git a/monkey/infection_monkey/control.py b/monkey/infection_monkey/control.py index 35922286f..912514b8c 100644 --- a/monkey/infection_monkey/control.py +++ b/monkey/infection_monkey/control.py @@ -9,7 +9,7 @@ from requests.exceptions import ConnectionError import infection_monkey.monkeyfs as monkeyfs import infection_monkey.tunnel as tunnel -from common.data.api_url_consts import T1216_PBA_FILE_DOWNLOAD_PATH +from common.common_consts.api_url_consts import T1216_PBA_FILE_DOWNLOAD_PATH from infection_monkey.config import GUID, WormConfiguration from infection_monkey.network.info import check_internet_access, local_ips from infection_monkey.transport.http import HTTPConnectProxy diff --git a/monkey/infection_monkey/exploit/elasticgroovy.py b/monkey/infection_monkey/exploit/elasticgroovy.py index fff71024d..026ccfdbd 100644 --- a/monkey/infection_monkey/exploit/elasticgroovy.py +++ b/monkey/infection_monkey/exploit/elasticgroovy.py @@ -10,7 +10,7 @@ import re import requests -from common.data.network_consts import ES_SERVICE +from common.common_consts.network_consts import ES_SERVICE from common.utils.attack_utils import BITS_UPLOAD_STRING, ScanStatus from infection_monkey.exploit.web_rce import WebRCE from infection_monkey.model import (BITSADMIN_CMDLINE_HTTP, CHECK_COMMAND, diff --git a/monkey/infection_monkey/network/elasticfinger.py b/monkey/infection_monkey/network/elasticfinger.py index 5ba95ab93..e7a60be17 100644 --- a/monkey/infection_monkey/network/elasticfinger.py +++ b/monkey/infection_monkey/network/elasticfinger.py @@ -6,7 +6,7 @@ import requests from requests.exceptions import ConnectionError, Timeout import infection_monkey.config -from common.data.network_consts import ES_SERVICE +from common.common_consts.network_consts import ES_SERVICE from infection_monkey.network.HostFinger import HostFinger ES_PORT = 9200 diff --git a/monkey/infection_monkey/post_breach/actions/add_user.py b/monkey/infection_monkey/post_breach/actions/add_user.py index 58be89a1f..a85845840 100644 --- a/monkey/infection_monkey/post_breach/actions/add_user.py +++ b/monkey/infection_monkey/post_breach/actions/add_user.py @@ -1,4 +1,4 @@ -from common.data.post_breach_consts import POST_BREACH_BACKDOOR_USER +from common.common_consts.post_breach_consts import POST_BREACH_BACKDOOR_USER from infection_monkey.config import WormConfiguration from infection_monkey.post_breach.pba import PBA from infection_monkey.utils.users import get_commands_to_add_user diff --git a/monkey/infection_monkey/post_breach/actions/change_file_privileges.py b/monkey/infection_monkey/post_breach/actions/change_file_privileges.py index 1cf5813e3..69f8f34da 100644 --- a/monkey/infection_monkey/post_breach/actions/change_file_privileges.py +++ b/monkey/infection_monkey/post_breach/actions/change_file_privileges.py @@ -1,4 +1,4 @@ -from common.data.post_breach_consts import POST_BREACH_SETUID_SETGID +from common.common_consts.post_breach_consts import POST_BREACH_SETUID_SETGID from infection_monkey.post_breach.pba import PBA from infection_monkey.post_breach.setuid_setgid.setuid_setgid import \ get_commands_to_change_setuid_setgid diff --git a/monkey/infection_monkey/post_breach/actions/clear_command_history.py b/monkey/infection_monkey/post_breach/actions/clear_command_history.py index afd26996f..d1fd63537 100644 --- a/monkey/infection_monkey/post_breach/actions/clear_command_history.py +++ b/monkey/infection_monkey/post_breach/actions/clear_command_history.py @@ -1,6 +1,6 @@ import subprocess -from common.data.post_breach_consts import POST_BREACH_CLEAR_CMD_HISTORY +from common.common_consts.post_breach_consts import POST_BREACH_CLEAR_CMD_HISTORY from infection_monkey.post_breach.clear_command_history.clear_command_history import \ get_commands_to_clear_command_history from infection_monkey.post_breach.pba import PBA diff --git a/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py b/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py index 83065d20d..bc00c9479 100644 --- a/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py +++ b/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py @@ -3,7 +3,7 @@ import random import string import subprocess -from common.data.post_breach_consts import POST_BREACH_COMMUNICATE_AS_NEW_USER +from common.common_consts.post_breach_consts import POST_BREACH_COMMUNICATE_AS_NEW_USER from infection_monkey.post_breach.pba import PBA from infection_monkey.telemetry.post_breach_telem import PostBreachTelem from infection_monkey.utils.auto_new_user_factory import create_auto_new_user diff --git a/monkey/infection_monkey/post_breach/actions/discover_accounts.py b/monkey/infection_monkey/post_breach/actions/discover_accounts.py index 8eaab9e38..18d72e642 100644 --- a/monkey/infection_monkey/post_breach/actions/discover_accounts.py +++ b/monkey/infection_monkey/post_breach/actions/discover_accounts.py @@ -1,4 +1,4 @@ -from common.data.post_breach_consts import POST_BREACH_ACCOUNT_DISCOVERY +from common.common_consts.post_breach_consts import POST_BREACH_ACCOUNT_DISCOVERY from infection_monkey.post_breach.account_discovery.account_discovery import \ get_commands_to_discover_accounts from infection_monkey.post_breach.pba import PBA diff --git a/monkey/infection_monkey/post_breach/actions/hide_files.py b/monkey/infection_monkey/post_breach/actions/hide_files.py index 081a18598..3a8c7860d 100644 --- a/monkey/infection_monkey/post_breach/actions/hide_files.py +++ b/monkey/infection_monkey/post_breach/actions/hide_files.py @@ -1,4 +1,4 @@ -from common.data.post_breach_consts import POST_BREACH_HIDDEN_FILES +from common.common_consts.post_breach_consts import POST_BREACH_HIDDEN_FILES from infection_monkey.post_breach.pba import PBA from infection_monkey.telemetry.post_breach_telem import PostBreachTelem from infection_monkey.utils.environment import is_windows_os diff --git a/monkey/infection_monkey/post_breach/actions/modify_shell_startup_files.py b/monkey/infection_monkey/post_breach/actions/modify_shell_startup_files.py index e12e0c446..b6111904d 100644 --- a/monkey/infection_monkey/post_breach/actions/modify_shell_startup_files.py +++ b/monkey/infection_monkey/post_breach/actions/modify_shell_startup_files.py @@ -1,6 +1,6 @@ import subprocess -from common.data.post_breach_consts import \ +from common.common_consts.post_breach_consts import \ POST_BREACH_SHELL_STARTUP_FILE_MODIFICATION from infection_monkey.post_breach.pba import PBA from infection_monkey.post_breach.shell_startup_files.shell_startup_files_modification import \ diff --git a/monkey/infection_monkey/post_breach/actions/schedule_jobs.py b/monkey/infection_monkey/post_breach/actions/schedule_jobs.py index d6cdd2765..f5faaa25d 100644 --- a/monkey/infection_monkey/post_breach/actions/schedule_jobs.py +++ b/monkey/infection_monkey/post_breach/actions/schedule_jobs.py @@ -1,4 +1,4 @@ -from common.data.post_breach_consts import POST_BREACH_JOB_SCHEDULING +from common.common_consts.post_breach_consts import POST_BREACH_JOB_SCHEDULING from infection_monkey.post_breach.job_scheduling.job_scheduling import ( get_commands_to_schedule_jobs, remove_scheduled_jobs) from infection_monkey.post_breach.pba import PBA diff --git a/monkey/infection_monkey/post_breach/actions/use_signed_scripts.py b/monkey/infection_monkey/post_breach/actions/use_signed_scripts.py index 17eb86337..ed9f665f0 100644 --- a/monkey/infection_monkey/post_breach/actions/use_signed_scripts.py +++ b/monkey/infection_monkey/post_breach/actions/use_signed_scripts.py @@ -1,7 +1,7 @@ import logging import subprocess -from common.data.post_breach_consts import POST_BREACH_SIGNED_SCRIPT_PROXY_EXEC +from common.common_consts.post_breach_consts import POST_BREACH_SIGNED_SCRIPT_PROXY_EXEC from infection_monkey.post_breach.pba import PBA from infection_monkey.post_breach.signed_script_proxy.signed_script_proxy import ( cleanup_changes, get_commands_to_proxy_execution_using_signed_script) diff --git a/monkey/infection_monkey/post_breach/actions/use_trap_command.py b/monkey/infection_monkey/post_breach/actions/use_trap_command.py index 589baf1d9..9a29b7fa8 100644 --- a/monkey/infection_monkey/post_breach/actions/use_trap_command.py +++ b/monkey/infection_monkey/post_breach/actions/use_trap_command.py @@ -1,4 +1,4 @@ -from common.data.post_breach_consts import POST_BREACH_TRAP_COMMAND +from common.common_consts.post_breach_consts import POST_BREACH_TRAP_COMMAND from infection_monkey.post_breach.pba import PBA from infection_monkey.post_breach.trap_command.trap_command import \ get_trap_commands diff --git a/monkey/infection_monkey/post_breach/actions/users_custom_pba.py b/monkey/infection_monkey/post_breach/actions/users_custom_pba.py index 46f09a688..175d6b215 100644 --- a/monkey/infection_monkey/post_breach/actions/users_custom_pba.py +++ b/monkey/infection_monkey/post_breach/actions/users_custom_pba.py @@ -1,7 +1,7 @@ import logging import os -from common.data.post_breach_consts import POST_BREACH_FILE_EXECUTION +from common.common_consts.post_breach_consts import POST_BREACH_FILE_EXECUTION from common.utils.attack_utils import ScanStatus from infection_monkey.config import WormConfiguration from infection_monkey.control import ControlClient diff --git a/monkey/infection_monkey/system_info/__init__.py b/monkey/infection_monkey/system_info/__init__.py index 05bb3a4d0..452f1bcdd 100644 --- a/monkey/infection_monkey/system_info/__init__.py +++ b/monkey/infection_monkey/system_info/__init__.py @@ -4,7 +4,7 @@ from enum import IntEnum import psutil -from common.data.system_info_collectors_names import AZURE_CRED_COLLECTOR +from common.common_consts.system_info_collectors_names import AZURE_CRED_COLLECTOR from infection_monkey.network.info import get_host_subnets from infection_monkey.system_info.azure_cred_collector import AzureCollector from infection_monkey.system_info.netstat_collector import NetstatCollector diff --git a/monkey/infection_monkey/system_info/collectors/aws_collector.py b/monkey/infection_monkey/system_info/collectors/aws_collector.py index bdf470735..d31dc1ba6 100644 --- a/monkey/infection_monkey/system_info/collectors/aws_collector.py +++ b/monkey/infection_monkey/system_info/collectors/aws_collector.py @@ -1,7 +1,7 @@ import logging from common.cloud.aws.aws_instance import AwsInstance -from common.data.system_info_collectors_names import AWS_COLLECTOR +from common.common_consts.system_info_collectors_names import AWS_COLLECTOR from infection_monkey.system_info.system_info_collector import \ SystemInfoCollector diff --git a/monkey/infection_monkey/system_info/collectors/environment_collector.py b/monkey/infection_monkey/system_info/collectors/environment_collector.py index 9bcd917ee..b49fb18d8 100644 --- a/monkey/infection_monkey/system_info/collectors/environment_collector.py +++ b/monkey/infection_monkey/system_info/collectors/environment_collector.py @@ -1,6 +1,6 @@ from common.cloud.all_instances import get_all_cloud_instances from common.cloud.environment_names import Environment -from common.data.system_info_collectors_names import ENVIRONMENT_COLLECTOR +from common.common_consts.system_info_collectors_names import ENVIRONMENT_COLLECTOR from infection_monkey.system_info.system_info_collector import \ SystemInfoCollector diff --git a/monkey/infection_monkey/system_info/collectors/hostname_collector.py b/monkey/infection_monkey/system_info/collectors/hostname_collector.py index ae9560815..7f21c214d 100644 --- a/monkey/infection_monkey/system_info/collectors/hostname_collector.py +++ b/monkey/infection_monkey/system_info/collectors/hostname_collector.py @@ -1,7 +1,7 @@ import logging import socket -from common.data.system_info_collectors_names import HOSTNAME_COLLECTOR +from common.common_consts.system_info_collectors_names import HOSTNAME_COLLECTOR from infection_monkey.system_info.system_info_collector import \ SystemInfoCollector diff --git a/monkey/infection_monkey/system_info/collectors/process_list_collector.py b/monkey/infection_monkey/system_info/collectors/process_list_collector.py index b732a4090..c55f40d5b 100644 --- a/monkey/infection_monkey/system_info/collectors/process_list_collector.py +++ b/monkey/infection_monkey/system_info/collectors/process_list_collector.py @@ -2,7 +2,7 @@ import logging import psutil -from common.data.system_info_collectors_names import PROCESS_LIST_COLLECTOR +from common.common_consts.system_info_collectors_names import PROCESS_LIST_COLLECTOR from infection_monkey.system_info.system_info_collector import \ SystemInfoCollector diff --git a/monkey/infection_monkey/system_info/collectors/scout_suite_collector.py b/monkey/infection_monkey/system_info/collectors/scout_suite_collector.py deleted file mode 100644 index 09adf94e4..000000000 --- a/monkey/infection_monkey/system_info/collectors/scout_suite_collector.py +++ /dev/null @@ -1,36 +0,0 @@ -import logging -import tempfile - -from common.cloud.environment_names import Environment -from common.data.system_info_collectors_names import SCOUTSUITE_COLLECTOR -from infection_monkey.system_info.system_info_collector import SystemInfoCollector -from infection_monkey.system_info.collectors.scoutsuite.ScoutSuite.__main__ import run -from system_info.collectors.environment_collector import get_monkey_environment - -logger = logging.getLogger(__name__) - - -class ScoutSuiteCollector(SystemInfoCollector): - def __init__(self): - super().__init__(name=SCOUTSUITE_COLLECTOR) - - def collect(self) -> dict: - env = get_monkey_environment() - env = "AWS" - if env == Environment.ON_PREMISE.value: - logger.info("Monkey is not on cloud; not running ScoutSuite") - return {} - else: - tmp_dir_path = tempfile.mkdtemp() - logger.info(f"Attempting to execute ScoutSuite with {env.lower()}, saving results in {tmp_dir_path}") - - scout_suite_results = run( - env.lower(), - debug=True, - quiet=False, - no_browser=True, - report_dir=tmp_dir_path) - return { - "Environment": env, - "Results": scout_suite_results - } diff --git a/monkey/infection_monkey/system_info/collectors/scoutsuite b/monkey/infection_monkey/system_info/collectors/scoutsuite deleted file mode 160000 index e784fc27a..000000000 --- a/monkey/infection_monkey/system_info/collectors/scoutsuite +++ /dev/null @@ -1 +0,0 @@ -Subproject commit e784fc27ae8311c3c610bccd556d2bef3cd54d63 diff --git a/monkey/infection_monkey/system_info/collectors/scoutsuite_collector/__init__.py b/monkey/infection_monkey/system_info/collectors/scoutsuite_collector/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/monkey/infection_monkey/system_info/collectors/scoutsuite_collector/scoutsuite b/monkey/infection_monkey/system_info/collectors/scoutsuite_collector/scoutsuite new file mode 160000 index 000000000..6707e052b --- /dev/null +++ b/monkey/infection_monkey/system_info/collectors/scoutsuite_collector/scoutsuite @@ -0,0 +1 @@ +Subproject commit 6707e052b8573a4f9eaee7f77f6c5de404f3e8fd diff --git a/monkey/infection_monkey/system_info/windows_info_collector.py b/monkey/infection_monkey/system_info/windows_info_collector.py index d6b3cbec8..abb8a491e 100644 --- a/monkey/infection_monkey/system_info/windows_info_collector.py +++ b/monkey/infection_monkey/system_info/windows_info_collector.py @@ -2,7 +2,7 @@ import logging import os import sys -from common.data.system_info_collectors_names import MIMIKATZ_COLLECTOR +from common.common_consts.system_info_collectors_names import MIMIKATZ_COLLECTOR from infection_monkey.system_info.windows_cred_collector.mimikatz_cred_collector import \ MimikatzCredentialCollector diff --git a/monkey/infection_monkey/telemetry/exploit_telem.py b/monkey/infection_monkey/telemetry/exploit_telem.py index bb114434f..0a33d1484 100644 --- a/monkey/infection_monkey/telemetry/exploit_telem.py +++ b/monkey/infection_monkey/telemetry/exploit_telem.py @@ -1,3 +1,4 @@ +from common.common_consts.telem_categories import TelemCategoryEnum from infection_monkey.telemetry.base_telem import BaseTelem __author__ = "itay.mizeretz" @@ -15,7 +16,7 @@ class ExploitTelem(BaseTelem): self.exploiter = exploiter self.result = result - telem_category = 'exploit' + telem_category = TelemCategoryEnum.EXPLOIT def get_data(self): return { diff --git a/monkey/infection_monkey/telemetry/post_breach_telem.py b/monkey/infection_monkey/telemetry/post_breach_telem.py index e5e443123..15aa41247 100644 --- a/monkey/infection_monkey/telemetry/post_breach_telem.py +++ b/monkey/infection_monkey/telemetry/post_breach_telem.py @@ -1,5 +1,6 @@ import socket +from common.common_consts.telem_categories import TelemCategoryEnum from infection_monkey.telemetry.base_telem import BaseTelem __author__ = "itay.mizeretz" @@ -18,7 +19,7 @@ class PostBreachTelem(BaseTelem): self.result = result self.hostname, self.ip = PostBreachTelem._get_hostname_and_ip() - telem_category = 'post_breach' + telem_category = TelemCategoryEnum.POST_BREACH def get_data(self): return { diff --git a/monkey/infection_monkey/telemetry/scan_telem.py b/monkey/infection_monkey/telemetry/scan_telem.py index b1c58ab1b..a4dac1396 100644 --- a/monkey/infection_monkey/telemetry/scan_telem.py +++ b/monkey/infection_monkey/telemetry/scan_telem.py @@ -1,3 +1,4 @@ +from common.common_consts.telem_categories import TelemCategoryEnum from infection_monkey.telemetry.base_telem import BaseTelem __author__ = "itay.mizeretz" @@ -13,7 +14,7 @@ class ScanTelem(BaseTelem): super(ScanTelem, self).__init__() self.machine = machine - telem_category = 'scan' + telem_category = TelemCategoryEnum.SCAN def get_data(self): return { diff --git a/monkey/infection_monkey/telemetry/state_telem.py b/monkey/infection_monkey/telemetry/state_telem.py index 4d4224288..9ecd53c20 100644 --- a/monkey/infection_monkey/telemetry/state_telem.py +++ b/monkey/infection_monkey/telemetry/state_telem.py @@ -1,3 +1,4 @@ +from common.common_consts.telem_categories import TelemCategoryEnum from infection_monkey.telemetry.base_telem import BaseTelem __author__ = "itay.mizeretz" @@ -14,7 +15,7 @@ class StateTelem(BaseTelem): self.is_done = is_done self.version = version - telem_category = 'state' + telem_category = TelemCategoryEnum.STATE def get_data(self): return { diff --git a/monkey/infection_monkey/telemetry/system_info_telem.py b/monkey/infection_monkey/telemetry/system_info_telem.py index 69ee7beda..a7ac21456 100644 --- a/monkey/infection_monkey/telemetry/system_info_telem.py +++ b/monkey/infection_monkey/telemetry/system_info_telem.py @@ -1,3 +1,4 @@ +from common.common_consts.telem_categories import TelemCategoryEnum from infection_monkey.telemetry.base_telem import BaseTelem __author__ = "itay.mizeretz" @@ -13,7 +14,7 @@ class SystemInfoTelem(BaseTelem): super(SystemInfoTelem, self).__init__() self.system_info = system_info - telem_category = 'system_info' + telem_category = TelemCategoryEnum.SYSTEM_INFO def get_data(self): return self.system_info diff --git a/monkey/infection_monkey/telemetry/trace_telem.py b/monkey/infection_monkey/telemetry/trace_telem.py index 0782affb4..dfe3f762b 100644 --- a/monkey/infection_monkey/telemetry/trace_telem.py +++ b/monkey/infection_monkey/telemetry/trace_telem.py @@ -1,5 +1,6 @@ import logging +from common.common_consts.telem_categories import TelemCategoryEnum from infection_monkey.telemetry.base_telem import BaseTelem __author__ = "itay.mizeretz" @@ -18,7 +19,7 @@ class TraceTelem(BaseTelem): self.msg = msg LOG.debug("Trace: %s" % msg) - telem_category = 'trace' + telem_category = TelemCategoryEnum.TRACE def get_data(self): return { diff --git a/monkey/infection_monkey/telemetry/tunnel_telem.py b/monkey/infection_monkey/telemetry/tunnel_telem.py index 64533a252..b4e4a07e6 100644 --- a/monkey/infection_monkey/telemetry/tunnel_telem.py +++ b/monkey/infection_monkey/telemetry/tunnel_telem.py @@ -1,3 +1,4 @@ +from common.common_consts.telem_categories import TelemCategoryEnum from infection_monkey.control import ControlClient from infection_monkey.telemetry.base_telem import BaseTelem @@ -13,7 +14,7 @@ class TunnelTelem(BaseTelem): super(TunnelTelem, self).__init__() self.proxy = ControlClient.proxies.get('https') - telem_category = 'tunnel' + telem_category = TelemCategoryEnum.TUNNEL def get_data(self): return {'proxy': self.proxy} diff --git a/monkey/monkey_island/cc/app.py b/monkey/monkey_island/cc/app.py index e8dfd2cfc..4bbf159c8 100644 --- a/monkey/monkey_island/cc/app.py +++ b/monkey/monkey_island/cc/app.py @@ -6,7 +6,7 @@ from flask import Flask, Response, send_from_directory from werkzeug.exceptions import NotFound import monkey_island.cc.environment.environment_singleton as env_singleton -from common.data.api_url_consts import T1216_PBA_FILE_DOWNLOAD_PATH +from common.common_consts.api_url_consts import T1216_PBA_FILE_DOWNLOAD_PATH from monkey_island.cc.consts import MONKEY_ISLAND_ABS_PATH from monkey_island.cc.database import database, mongo from monkey_island.cc.resources.attack.attack_config import AttackConfiguration diff --git a/monkey/monkey_island/cc/models/zero_trust/aggregate_finding.py b/monkey/monkey_island/cc/models/zero_trust/aggregate_finding.py index c3817313f..c5684abc0 100644 --- a/monkey/monkey_island/cc/models/zero_trust/aggregate_finding.py +++ b/monkey/monkey_island/cc/models/zero_trust/aggregate_finding.py @@ -1,4 +1,4 @@ -import common.data.zero_trust_consts as zero_trust_consts +import common.common_consts.zero_trust_consts as zero_trust_consts from monkey_island.cc.models.zero_trust.finding import Finding diff --git a/monkey/monkey_island/cc/models/zero_trust/event.py b/monkey/monkey_island/cc/models/zero_trust/event.py index 7ff08305b..d1a0001af 100644 --- a/monkey/monkey_island/cc/models/zero_trust/event.py +++ b/monkey/monkey_island/cc/models/zero_trust/event.py @@ -2,7 +2,7 @@ from datetime import datetime from mongoengine import DateTimeField, EmbeddedDocument, StringField -import common.data.zero_trust_consts as zero_trust_consts +import common.common_consts.zero_trust_consts as zero_trust_consts class Event(EmbeddedDocument): diff --git a/monkey/monkey_island/cc/models/zero_trust/finding.py b/monkey/monkey_island/cc/models/zero_trust/finding.py index d6d5c6c3f..d07cb9a45 100644 --- a/monkey/monkey_island/cc/models/zero_trust/finding.py +++ b/monkey/monkey_island/cc/models/zero_trust/finding.py @@ -6,7 +6,7 @@ from typing import List from mongoengine import Document, EmbeddedDocumentListField, StringField -import common.data.zero_trust_consts as zero_trust_consts +import common.common_consts.zero_trust_consts as zero_trust_consts # Dummy import for mongoengine. # noinspection PyUnresolvedReferences from monkey_island.cc.models.zero_trust.event import Event diff --git a/monkey/monkey_island/cc/models/zero_trust/segmentation_finding.py b/monkey/monkey_island/cc/models/zero_trust/segmentation_finding.py index 60262fbfd..903cf3546 100644 --- a/monkey/monkey_island/cc/models/zero_trust/segmentation_finding.py +++ b/monkey/monkey_island/cc/models/zero_trust/segmentation_finding.py @@ -1,6 +1,6 @@ from mongoengine import StringField -import common.data.zero_trust_consts as zero_trust_consts +import common.common_consts.zero_trust_consts as zero_trust_consts from monkey_island.cc.models.zero_trust.finding import Finding diff --git a/monkey/monkey_island/cc/models/zero_trust/test_aggregate_finding.py b/monkey/monkey_island/cc/models/zero_trust/test_aggregate_finding.py index 91452dc0e..4b9765f70 100644 --- a/monkey/monkey_island/cc/models/zero_trust/test_aggregate_finding.py +++ b/monkey/monkey_island/cc/models/zero_trust/test_aggregate_finding.py @@ -3,7 +3,7 @@ import unittest import mongomock from packaging import version -import common.data.zero_trust_consts as zero_trust_consts +import common.common_consts.zero_trust_consts as zero_trust_consts from monkey_island.cc.models.zero_trust.aggregate_finding import \ AggregateFinding from monkey_island.cc.models.zero_trust.event import Event diff --git a/monkey/monkey_island/cc/models/zero_trust/test_event.py b/monkey/monkey_island/cc/models/zero_trust/test_event.py index 4a5afba50..4699dd829 100644 --- a/monkey/monkey_island/cc/models/zero_trust/test_event.py +++ b/monkey/monkey_island/cc/models/zero_trust/test_event.py @@ -1,6 +1,6 @@ from mongoengine import ValidationError -import common.data.zero_trust_consts as zero_trust_consts +import common.common_consts.zero_trust_consts as zero_trust_consts from monkey_island.cc.models.zero_trust.event import Event from monkey_island.cc.testing.IslandTestCase import IslandTestCase diff --git a/monkey/monkey_island/cc/models/zero_trust/test_finding.py b/monkey/monkey_island/cc/models/zero_trust/test_finding.py index e221dacb1..c92d4439c 100644 --- a/monkey/monkey_island/cc/models/zero_trust/test_finding.py +++ b/monkey/monkey_island/cc/models/zero_trust/test_finding.py @@ -1,6 +1,6 @@ from mongoengine import ValidationError -import common.data.zero_trust_consts as zero_trust_consts +import common.common_consts.zero_trust_consts as zero_trust_consts from monkey_island.cc.models.zero_trust.event import Event from monkey_island.cc.models.zero_trust.finding import Finding from monkey_island.cc.testing.IslandTestCase import IslandTestCase diff --git a/monkey/monkey_island/cc/models/zero_trust/test_segmentation_finding.py b/monkey/monkey_island/cc/models/zero_trust/test_segmentation_finding.py index b375d97a9..13583ad40 100644 --- a/monkey/monkey_island/cc/models/zero_trust/test_segmentation_finding.py +++ b/monkey/monkey_island/cc/models/zero_trust/test_segmentation_finding.py @@ -1,4 +1,4 @@ -import common.data.zero_trust_consts as zero_trust_consts +import common.common_consts.zero_trust_consts as zero_trust_consts from monkey_island.cc.models.zero_trust.event import Event from monkey_island.cc.models.zero_trust.segmentation_finding import \ SegmentationFinding diff --git a/monkey/monkey_island/cc/resources/telemetry.py b/monkey/monkey_island/cc/resources/telemetry.py index efdeb34b3..dd622b140 100644 --- a/monkey/monkey_island/cc/resources/telemetry.py +++ b/monkey/monkey_island/cc/resources/telemetry.py @@ -6,6 +6,7 @@ import dateutil import flask_restful from flask import request +from common.common_consts.telem_categories import TelemCategoryEnum from monkey_island.cc.database import mongo from monkey_island.cc.models.monkey import Monkey from monkey_island.cc.resources.auth.auth import jwt_required @@ -74,7 +75,7 @@ class Telemetry(flask_restful.Resource): monkey_label = telem_monkey_guid x["monkey"] = monkey_label objects.append(x) - if x['telem_category'] == 'system_info' and 'credentials' in x['data']: + if x['telem_category'] == TelemCategoryEnum.SYSTEM_INFO and 'credentials' in x['data']: for user in x['data']['credentials']: if -1 != user.find(','): new_user = user.replace(',', '.') diff --git a/monkey/monkey_island/cc/resources/telemetry_feed.py b/monkey/monkey_island/cc/resources/telemetry_feed.py index 17f263320..3da328b99 100644 --- a/monkey/monkey_island/cc/resources/telemetry_feed.py +++ b/monkey/monkey_island/cc/resources/telemetry_feed.py @@ -6,6 +6,7 @@ import flask_pymongo import flask_restful from flask import request +from common.common_consts.telem_categories import TelemCategoryEnum from monkey_island.cc.database import mongo from monkey_island.cc.resources.auth.auth import jwt_required from monkey_island.cc.services.node import NodeService @@ -109,11 +110,11 @@ class TelemetryFeed(flask_restful.Resource): TELEM_PROCESS_DICT = \ { - 'tunnel': TelemetryFeed.get_tunnel_telem_brief, - 'state': TelemetryFeed.get_state_telem_brief, - 'exploit': TelemetryFeed.get_exploit_telem_brief, - 'scan': TelemetryFeed.get_scan_telem_brief, - 'system_info': TelemetryFeed.get_systeminfo_telem_brief, - 'trace': TelemetryFeed.get_trace_telem_brief, - 'post_breach': TelemetryFeed.get_post_breach_telem_brief + TelemCategoryEnum.TUNNEL: TelemetryFeed.get_tunnel_telem_brief, + TelemCategoryEnum.STATE: TelemetryFeed.get_state_telem_brief, + TelemCategoryEnum.EXPLOIT: TelemetryFeed.get_exploit_telem_brief, + TelemCategoryEnum.SCAN: TelemetryFeed.get_scan_telem_brief, + TelemCategoryEnum.SYSTEM_INFO: TelemetryFeed.get_systeminfo_telem_brief, + TelemCategoryEnum.TRACE: TelemetryFeed.get_trace_telem_brief, + TelemCategoryEnum.POST_BREACH: TelemetryFeed.get_post_breach_telem_brief } diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1053.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1053.py index 511f819e3..9ca289e8f 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1053.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1053.py @@ -1,4 +1,4 @@ -from common.data.post_breach_consts import POST_BREACH_JOB_SCHEDULING +from common.common_consts.post_breach_consts import POST_BREACH_JOB_SCHEDULING from monkey_island.cc.services.attack.technique_reports.pba_technique import \ PostBreachTechnique diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1087.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1087.py index de0a6a470..2b8bd1374 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1087.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1087.py @@ -1,4 +1,4 @@ -from common.data.post_breach_consts import POST_BREACH_ACCOUNT_DISCOVERY +from common.common_consts.post_breach_consts import POST_BREACH_ACCOUNT_DISCOVERY from monkey_island.cc.services.attack.technique_reports.pba_technique import \ PostBreachTechnique diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1136.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1136.py index 086a1c139..8b299fbce 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1136.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1136.py @@ -1,4 +1,4 @@ -from common.data.post_breach_consts import ( +from common.common_consts.post_breach_consts import ( POST_BREACH_BACKDOOR_USER, POST_BREACH_COMMUNICATE_AS_NEW_USER) from monkey_island.cc.services.attack.technique_reports.pba_technique import \ PostBreachTechnique diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1146.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1146.py index cacbe6789..b9c7863dc 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1146.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1146.py @@ -1,4 +1,4 @@ -from common.data.post_breach_consts import POST_BREACH_CLEAR_CMD_HISTORY +from common.common_consts.post_breach_consts import POST_BREACH_CLEAR_CMD_HISTORY from monkey_island.cc.services.attack.technique_reports.pba_technique import \ PostBreachTechnique diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1154.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1154.py index c905fc9ca..22fd107a3 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1154.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1154.py @@ -1,4 +1,4 @@ -from common.data.post_breach_consts import POST_BREACH_TRAP_COMMAND +from common.common_consts.post_breach_consts import POST_BREACH_TRAP_COMMAND from monkey_island.cc.services.attack.technique_reports.pba_technique import \ PostBreachTechnique diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1156.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1156.py index 2841ed0ad..babbdeeb4 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1156.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1156.py @@ -1,4 +1,4 @@ -from common.data.post_breach_consts import \ +from common.common_consts.post_breach_consts import \ POST_BREACH_SHELL_STARTUP_FILE_MODIFICATION from monkey_island.cc.services.attack.technique_reports.pba_technique import \ PostBreachTechnique diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1158.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1158.py index 7b0f87358..2be8896f4 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1158.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1158.py @@ -1,4 +1,4 @@ -from common.data.post_breach_consts import POST_BREACH_HIDDEN_FILES +from common.common_consts.post_breach_consts import POST_BREACH_HIDDEN_FILES from monkey_island.cc.services.attack.technique_reports.pba_technique import \ PostBreachTechnique diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1166.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1166.py index e3b74e5c5..9fe5826eb 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1166.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1166.py @@ -1,4 +1,4 @@ -from common.data.post_breach_consts import POST_BREACH_SETUID_SETGID +from common.common_consts.post_breach_consts import POST_BREACH_SETUID_SETGID from monkey_island.cc.services.attack.technique_reports.pba_technique import \ PostBreachTechnique diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1168.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1168.py index 76806806c..f0db6cdd1 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1168.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1168.py @@ -1,4 +1,4 @@ -from common.data.post_breach_consts import POST_BREACH_JOB_SCHEDULING +from common.common_consts.post_breach_consts import POST_BREACH_JOB_SCHEDULING from monkey_island.cc.services.attack.technique_reports.pba_technique import \ PostBreachTechnique diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1216.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1216.py index d4efbd73e..6086194ab 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1216.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1216.py @@ -1,4 +1,4 @@ -from common.data.post_breach_consts import POST_BREACH_SIGNED_SCRIPT_PROXY_EXEC +from common.common_consts.post_breach_consts import POST_BREACH_SIGNED_SCRIPT_PROXY_EXEC from monkey_island.cc.services.attack.technique_reports.pba_technique import \ PostBreachTechnique diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1504.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1504.py index 8d8956e6b..842865456 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1504.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1504.py @@ -1,4 +1,4 @@ -from common.data.post_breach_consts import \ +from common.common_consts.post_breach_consts import \ POST_BREACH_SHELL_STARTUP_FILE_MODIFICATION from monkey_island.cc.services.attack.technique_reports.pba_technique import \ PostBreachTechnique diff --git a/monkey/monkey_island/cc/services/config_schema/basic_network.py b/monkey/monkey_island/cc/services/config_schema/basic_network.py index 33467690a..5ae044d95 100644 --- a/monkey/monkey_island/cc/services/config_schema/basic_network.py +++ b/monkey/monkey_island/cc/services/config_schema/basic_network.py @@ -1,4 +1,4 @@ -from common.data.validation_formats import IP, IP_RANGE +from common.common_consts.validation_formats import IP, IP_RANGE from monkey_island.cc.services.utils.typographic_symbols import WARNING_SIGN BASIC_NETWORK = { diff --git a/monkey/monkey_island/cc/services/config_schema/definitions/system_info_collector_classes.py b/monkey/monkey_island/cc/services/config_schema/definitions/system_info_collector_classes.py index 5f113f4a7..05bb2d48a 100644 --- a/monkey/monkey_island/cc/services/config_schema/definitions/system_info_collector_classes.py +++ b/monkey/monkey_island/cc/services/config_schema/definitions/system_info_collector_classes.py @@ -1,9 +1,9 @@ -from common.data.system_info_collectors_names import (AWS_COLLECTOR, - AZURE_CRED_COLLECTOR, - ENVIRONMENT_COLLECTOR, - HOSTNAME_COLLECTOR, - MIMIKATZ_COLLECTOR, - PROCESS_LIST_COLLECTOR) +from common.common_consts.system_info_collectors_names import (AWS_COLLECTOR, + AZURE_CRED_COLLECTOR, + ENVIRONMENT_COLLECTOR, + HOSTNAME_COLLECTOR, + MIMIKATZ_COLLECTOR, + PROCESS_LIST_COLLECTOR) SYSTEM_INFO_COLLECTOR_CLASSES = { "title": "System Information Collectors", diff --git a/monkey/monkey_island/cc/services/config_schema/monkey.py b/monkey/monkey_island/cc/services/config_schema/monkey.py index c9fbdde74..dd2348e06 100644 --- a/monkey/monkey_island/cc/services/config_schema/monkey.py +++ b/monkey/monkey_island/cc/services/config_schema/monkey.py @@ -1,9 +1,9 @@ -from common.data.system_info_collectors_names import (AWS_COLLECTOR, - AZURE_CRED_COLLECTOR, - ENVIRONMENT_COLLECTOR, - HOSTNAME_COLLECTOR, - MIMIKATZ_COLLECTOR, - PROCESS_LIST_COLLECTOR) +from common.common_consts.system_info_collectors_names import (AWS_COLLECTOR, + AZURE_CRED_COLLECTOR, + ENVIRONMENT_COLLECTOR, + HOSTNAME_COLLECTOR, + MIMIKATZ_COLLECTOR, + PROCESS_LIST_COLLECTOR) MONKEY = { "title": "Monkey", diff --git a/monkey/monkey_island/cc/services/reporting/test_zero_trust_service.py b/monkey/monkey_island/cc/services/reporting/test_zero_trust_service.py index dbadffb55..874eee293 100644 --- a/monkey/monkey_island/cc/services/reporting/test_zero_trust_service.py +++ b/monkey/monkey_island/cc/services/reporting/test_zero_trust_service.py @@ -1,4 +1,4 @@ -import common.data.zero_trust_consts as zero_trust_consts +import common.common_consts.zero_trust_consts as zero_trust_consts import monkey_island.cc.services.reporting.zero_trust_service from monkey_island.cc.models.zero_trust.finding import Finding from monkey_island.cc.services.reporting.zero_trust_service import \ diff --git a/monkey/monkey_island/cc/services/reporting/zero_trust_service.py b/monkey/monkey_island/cc/services/reporting/zero_trust_service.py index 7c31fc59a..a70f4f8b4 100644 --- a/monkey/monkey_island/cc/services/reporting/zero_trust_service.py +++ b/monkey/monkey_island/cc/services/reporting/zero_trust_service.py @@ -2,7 +2,7 @@ from typing import List from bson.objectid import ObjectId -import common.data.zero_trust_consts as zero_trust_consts +import common.common_consts.zero_trust_consts as zero_trust_consts from monkey_island.cc.models.zero_trust.finding import Finding # How many events of a single finding to return to UI. diff --git a/monkey/monkey_island/cc/services/telemetry/processing/post_breach.py b/monkey/monkey_island/cc/services/telemetry/processing/post_breach.py index 367ca87d9..28909f40b 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/post_breach.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/post_breach.py @@ -1,6 +1,6 @@ import copy -from common.data.post_breach_consts import POST_BREACH_COMMUNICATE_AS_NEW_USER +from common.common_consts.post_breach_consts import POST_BREACH_COMMUNICATE_AS_NEW_USER from monkey_island.cc.database import mongo from monkey_island.cc.models import Monkey from monkey_island.cc.services.telemetry.zero_trust_tests.communicate_as_new_user import \ diff --git a/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/system_info_telemetry_dispatcher.py b/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/system_info_telemetry_dispatcher.py index 38767a01e..5f1b6e641 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/system_info_telemetry_dispatcher.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/system_info_telemetry_dispatcher.py @@ -1,11 +1,11 @@ import logging import typing -from common.data.system_info_collectors_names import (AWS_COLLECTOR, - ENVIRONMENT_COLLECTOR, - HOSTNAME_COLLECTOR, - PROCESS_LIST_COLLECTOR, - SCOUTSUITE_COLLECTOR) +from common.common_consts.system_info_collectors_names import (AWS_COLLECTOR, + ENVIRONMENT_COLLECTOR, + HOSTNAME_COLLECTOR, + PROCESS_LIST_COLLECTOR, + SCOUTSUITE_COLLECTOR) from monkey_island.cc.services.telemetry.processing.system_info_collectors.aws import \ process_aws_telemetry from monkey_island.cc.services.telemetry.processing.system_info_collectors.environment import \ diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/antivirus_existence.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/antivirus_existence.py index 336567c7c..f16ae7295 100644 --- a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/antivirus_existence.py +++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/antivirus_existence.py @@ -1,6 +1,6 @@ import json -import common.data.zero_trust_consts as zero_trust_consts +import common.common_consts.zero_trust_consts as zero_trust_consts from monkey_island.cc.models import Monkey from monkey_island.cc.models.zero_trust.aggregate_finding import \ AggregateFinding diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/communicate_as_new_user.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/communicate_as_new_user.py index d822206af..c6d8a9570 100644 --- a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/communicate_as_new_user.py +++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/communicate_as_new_user.py @@ -1,4 +1,4 @@ -import common.data.zero_trust_consts as zero_trust_consts +import common.common_consts.zero_trust_consts as zero_trust_consts from monkey_island.cc.models.zero_trust.aggregate_finding import \ AggregateFinding from monkey_island.cc.models.zero_trust.event import Event diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/data_endpoints.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/data_endpoints.py index 447b2dee8..024a91522 100644 --- a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/data_endpoints.py +++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/data_endpoints.py @@ -1,7 +1,7 @@ import json -import common.data.zero_trust_consts as zero_trust_consts -from common.data.network_consts import ES_SERVICE +import common.common_consts.zero_trust_consts as zero_trust_consts +from common.common_consts.network_consts import ES_SERVICE from monkey_island.cc.models import Monkey from monkey_island.cc.models.zero_trust.aggregate_finding import ( AggregateFinding, add_malicious_activity_to_timeline) diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/machine_exploited.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/machine_exploited.py index 06d97d66d..51109130a 100644 --- a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/machine_exploited.py +++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/machine_exploited.py @@ -1,4 +1,4 @@ -import common.data.zero_trust_consts as zero_trust_consts +import common.common_consts.zero_trust_consts as zero_trust_consts from monkey_island.cc.models.zero_trust.aggregate_finding import ( AggregateFinding, add_malicious_activity_to_timeline) from monkey_island.cc.models.zero_trust.event import Event diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/segmentation.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/segmentation.py index a46dbc4a3..717f57896 100644 --- a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/segmentation.py +++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/segmentation.py @@ -1,6 +1,6 @@ import itertools -import common.data.zero_trust_consts as zero_trust_consts +import common.common_consts.zero_trust_consts as zero_trust_consts from common.network.network_range import NetworkRange from common.network.segmentation_utils import (get_ip_if_in_subnet, get_ip_in_src_and_not_in_dst) diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/test_segmentation_zt_tests.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/test_segmentation_zt_tests.py index b2aeaf524..937cc5baf 100644 --- a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/test_segmentation_zt_tests.py +++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/test_segmentation_zt_tests.py @@ -1,6 +1,6 @@ import uuid -import common.data.zero_trust_consts as zero_trust_consts +import common.common_consts.zero_trust_consts as zero_trust_consts from monkey_island.cc.models import Monkey from monkey_island.cc.models.zero_trust.event import Event from monkey_island.cc.models.zero_trust.finding import Finding diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/tunneling.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/tunneling.py index f4d508156..341acbf5a 100644 --- a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/tunneling.py +++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/tunneling.py @@ -1,4 +1,4 @@ -import common.data.zero_trust_consts as zero_trust_consts +import common.common_consts.zero_trust_consts as zero_trust_consts from monkey_island.cc.models import Monkey from monkey_island.cc.models.zero_trust.aggregate_finding import ( AggregateFinding, add_malicious_activity_to_timeline) From 3f725c1639d45ecde1e54c9ad239c327fac25bae Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Thu, 3 Sep 2020 12:07:04 +0300 Subject: [PATCH 009/152] Added scoutsuite_api to monkey --- .../system_info/collectors/aws_collector.py | 3 +++ .../scoutsuite_collector/scoutsuite_api.py | 20 ++++++++++++++++ .../scoutsuite_collector.py | 23 +++++++++++++++++++ .../telemetry/scoutsuite_telem.py | 20 ++++++++++++++++ 4 files changed, 66 insertions(+) create mode 100644 monkey/infection_monkey/system_info/collectors/scoutsuite_collector/scoutsuite_api.py create mode 100644 monkey/infection_monkey/system_info/collectors/scoutsuite_collector/scoutsuite_collector.py create mode 100644 monkey/infection_monkey/telemetry/scoutsuite_telem.py diff --git a/monkey/infection_monkey/system_info/collectors/aws_collector.py b/monkey/infection_monkey/system_info/collectors/aws_collector.py index d31dc1ba6..80fbd4f29 100644 --- a/monkey/infection_monkey/system_info/collectors/aws_collector.py +++ b/monkey/infection_monkey/system_info/collectors/aws_collector.py @@ -4,6 +4,7 @@ from common.cloud.aws.aws_instance import AwsInstance from common.common_consts.system_info_collectors_names import AWS_COLLECTOR from infection_monkey.system_info.system_info_collector import \ SystemInfoCollector +from infection_monkey.system_info.collectors.scoutsuite_collector.scoutsuite_collector import CLOUD_TYPES, scan_cloud_security logger = logging.getLogger(__name__) @@ -25,6 +26,8 @@ class AwsCollector(SystemInfoCollector): { 'instance_id': aws.get_instance_id() } + # TODO add IF ON ISLAND check + scan_cloud_security(cloud_type=CLOUD_TYPES.AWS) else: logger.info("Machine is NOT an AWS instance") diff --git a/monkey/infection_monkey/system_info/collectors/scoutsuite_collector/scoutsuite_api.py b/monkey/infection_monkey/system_info/collectors/scoutsuite_collector/scoutsuite_api.py new file mode 100644 index 000000000..9feec3c3d --- /dev/null +++ b/monkey/infection_monkey/system_info/collectors/scoutsuite_collector/scoutsuite_api.py @@ -0,0 +1,20 @@ +import pkgutil +import sys +from pathlib import PurePath + +_scoutsuite_api_package = pkgutil.get_loader('infection_monkey.system_info.collectors.' + 'scoutsuite_collector.scoutsuite.ScoutSuite.__main__') + + +def _add_scoutsuite_to_python_path(): + scoutsuite_path = PurePath(_scoutsuite_api_package.path).parent.parent.__str__() + sys.path.append(scoutsuite_path) + + +_add_scoutsuite_to_python_path() + +import infection_monkey.system_info.collectors.scoutsuite_collector.scoutsuite.ScoutSuite.api_run as scoutsuite_api + + +def run(*args, **kwargs): + return scoutsuite_api.run(*args, **kwargs) diff --git a/monkey/infection_monkey/system_info/collectors/scoutsuite_collector/scoutsuite_collector.py b/monkey/infection_monkey/system_info/collectors/scoutsuite_collector/scoutsuite_collector.py new file mode 100644 index 000000000..fb33cce4b --- /dev/null +++ b/monkey/infection_monkey/system_info/collectors/scoutsuite_collector/scoutsuite_collector.py @@ -0,0 +1,23 @@ +import infection_monkey.system_info.collectors.scoutsuite_collector.scoutsuite_api as scoutsuite_api +from infection_monkey.telemetry.scoutsuite_telem import ScoutSuiteTelem + + +class CLOUD_TYPES: + AWS = 'aws' + AZURE = 'azure' + GCP = 'gcp' + ALIBABA = 'aliyun' + ORACLE = 'oci' + + +def scan_cloud_security(cloud_type: CLOUD_TYPES): + results = run_scoutsuite(cloud_type) + send_results(results) + + +def run_scoutsuite(cloud_type): + return scoutsuite_api.run(provider=cloud_type) + + +def send_results(results): + ScoutSuiteTelem.send(results) diff --git a/monkey/infection_monkey/telemetry/scoutsuite_telem.py b/monkey/infection_monkey/telemetry/scoutsuite_telem.py new file mode 100644 index 000000000..d606ea3c3 --- /dev/null +++ b/monkey/infection_monkey/telemetry/scoutsuite_telem.py @@ -0,0 +1,20 @@ +from common.common_consts.telem_categories import TelemCategoryEnum +from infection_monkey.telemetry.base_telem import BaseTelem + + +class ScoutSuiteTelem(BaseTelem): + + def __init__(self, data): + """ + Default ScoutSuite telemetry constructor + :param data: Data gathered via ScoutSuite ( + """ + super().__init__() + self.data = data + + telem_category = TelemCategoryEnum.SCOUTSUITE + + def get_data(self): + return { + 'data': self.data + } From 7538f774eda75ce3e129bc9400c8723aaab0cded Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Thu, 3 Sep 2020 12:38:40 +0300 Subject: [PATCH 010/152] Migrated more hard coded telem category values to use enum --- monkey/common/common_consts/telem_categories.py | 1 + .../scoutsuite_collector.py | 2 +- .../telemetry/attack/attack_telem.py | 3 ++- .../services/telemetry/processing/processing.py | 17 +++++++++-------- 4 files changed, 13 insertions(+), 10 deletions(-) diff --git a/monkey/common/common_consts/telem_categories.py b/monkey/common/common_consts/telem_categories.py index c983786b9..70066d290 100644 --- a/monkey/common/common_consts/telem_categories.py +++ b/monkey/common/common_consts/telem_categories.py @@ -7,3 +7,4 @@ class TelemCategoryEnum: SYSTEM_INFO = 'system_info' TRACE = 'trace' TUNNEL = 'tunnel' + ATTACK = 'attack' diff --git a/monkey/infection_monkey/system_info/collectors/scoutsuite_collector/scoutsuite_collector.py b/monkey/infection_monkey/system_info/collectors/scoutsuite_collector/scoutsuite_collector.py index fb33cce4b..f7d6b7ec5 100644 --- a/monkey/infection_monkey/system_info/collectors/scoutsuite_collector/scoutsuite_collector.py +++ b/monkey/infection_monkey/system_info/collectors/scoutsuite_collector/scoutsuite_collector.py @@ -20,4 +20,4 @@ def run_scoutsuite(cloud_type): def send_results(results): - ScoutSuiteTelem.send(results) + ScoutSuiteTelem(results).send(results) diff --git a/monkey/infection_monkey/telemetry/attack/attack_telem.py b/monkey/infection_monkey/telemetry/attack/attack_telem.py index 893f4492a..ba3fae8fd 100644 --- a/monkey/infection_monkey/telemetry/attack/attack_telem.py +++ b/monkey/infection_monkey/telemetry/attack/attack_telem.py @@ -1,3 +1,4 @@ +from common.common_consts.telem_categories import TelemCategoryEnum from infection_monkey.telemetry.base_telem import BaseTelem __author__ = "VakarisZ" @@ -15,7 +16,7 @@ class AttackTelem(BaseTelem): self.technique = technique self.status = status - telem_category = 'attack' + telem_category = TelemCategoryEnum.ATTACK def get_data(self): return { diff --git a/monkey/monkey_island/cc/services/telemetry/processing/processing.py b/monkey/monkey_island/cc/services/telemetry/processing/processing.py index 566c11dcc..960a01517 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/processing.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/processing.py @@ -1,5 +1,6 @@ import logging +from common.common_consts.telem_categories import TelemCategoryEnum from monkey_island.cc.services.telemetry.processing.exploit import \ process_exploit_telemetry from monkey_island.cc.services.telemetry.processing.post_breach import \ @@ -17,15 +18,15 @@ logger = logging.getLogger(__name__) TELEMETRY_CATEGORY_TO_PROCESSING_FUNC = \ { - 'tunnel': process_tunnel_telemetry, - 'state': process_state_telemetry, - 'exploit': process_exploit_telemetry, - 'scan': process_scan_telemetry, - 'system_info': process_system_info_telemetry, - 'post_breach': process_post_breach_telemetry, + TelemCategoryEnum.TUNNEL: process_tunnel_telemetry, + TelemCategoryEnum.STATE: process_state_telemetry, + TelemCategoryEnum.EXPLOIT: process_exploit_telemetry, + TelemCategoryEnum.SCAN: process_scan_telemetry, + TelemCategoryEnum.SYSTEM_INFO: process_system_info_telemetry, + TelemCategoryEnum.POST_BREACH: process_post_breach_telemetry, # `lambda *args, **kwargs: None` is a no-op. - 'trace': lambda *args, **kwargs: None, - 'attack': lambda *args, **kwargs: None, + TelemCategoryEnum.TRACE: lambda *args, **kwargs: None, + TelemCategoryEnum.ATTACK: lambda *args, **kwargs: None, } From 3adafd31b06be79a3fd401f06632fd964995141a Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Fri, 4 Sep 2020 15:45:48 +0300 Subject: [PATCH 011/152] Small scoutsuite improvement regarding api error handling --- .../system_info/collectors/scoutsuite_collector/scoutsuite | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/infection_monkey/system_info/collectors/scoutsuite_collector/scoutsuite b/monkey/infection_monkey/system_info/collectors/scoutsuite_collector/scoutsuite index 6707e052b..e5f9760a8 160000 --- a/monkey/infection_monkey/system_info/collectors/scoutsuite_collector/scoutsuite +++ b/monkey/infection_monkey/system_info/collectors/scoutsuite_collector/scoutsuite @@ -1 +1 @@ -Subproject commit 6707e052b8573a4f9eaee7f77f6c5de404f3e8fd +Subproject commit e5f9760a822cc8856a1be3da691c511ad32ad7dd From 549e621895ae0d92d4aba40e47b033109eb6dd88 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Fri, 4 Sep 2020 15:46:50 +0300 Subject: [PATCH 012/152] Small telemetry refactoring and added ScoutSuite telem --- monkey/infection_monkey/control.py | 4 +-- .../infection_monkey/telemetry/base_telem.py | 36 +++++++++++++------ .../telemetry/scoutsuite_telem.py | 3 ++ .../cc/resources/reporting/report.py | 3 +- .../telemetry/processing/scoutsuite.py | 11 ++++++ 5 files changed, 42 insertions(+), 15 deletions(-) create mode 100644 monkey/monkey_island/cc/services/telemetry/processing/scoutsuite.py diff --git a/monkey/infection_monkey/control.py b/monkey/infection_monkey/control.py index 912514b8c..1f3c1c25e 100644 --- a/monkey/infection_monkey/control.py +++ b/monkey/infection_monkey/control.py @@ -128,12 +128,12 @@ class ControlClient(object): return {} @staticmethod - def send_telemetry(telem_category, data): + def send_telemetry(telem_category, json_data: str): if not WormConfiguration.current_server: LOG.error("Trying to send %s telemetry before current server is established, aborting." % telem_category) return try: - telemetry = {'monkey_guid': GUID, 'telem_category': telem_category, 'data': data} + telemetry = {'monkey_guid': GUID, 'telem_category': telem_category, 'data': json_data} requests.post("https://%s/api/telemetry" % (WormConfiguration.current_server,), # noqa: DUO123 data=json.dumps(telemetry), headers={'content-type': 'application/json'}, diff --git a/monkey/infection_monkey/telemetry/base_telem.py b/monkey/infection_monkey/telemetry/base_telem.py index 7617ab4e3..07559491c 100644 --- a/monkey/infection_monkey/telemetry/base_telem.py +++ b/monkey/infection_monkey/telemetry/base_telem.py @@ -5,6 +5,7 @@ import logging from infection_monkey.control import ControlClient logger = logging.getLogger(__name__) +LOGGED_DATA_LENGTH = 300 # How many characters of telemetry data will be logged __author__ = 'itay.mizeretz' @@ -22,12 +23,25 @@ class BaseTelem(object, metaclass=abc.ABCMeta): Sends telemetry to island """ data = self.get_data() + serialized_data = json.dumps(data, cls=self.json_encoder) + self.log_telem_sending(serialized_data, log_data) + ControlClient.send_telemetry(self.telem_category, serialized_data) + + @abc.abstractmethod + def get_data(self) -> dict: + """ + :return: Data of telemetry (should be dict) + """ + pass + + @property + def json_encoder(self): + return json.JSONEncoder + + def log_telem_sending(self, serialized_data: str, log_data=True): + logger.debug(f"Sending {self.telem_category} telemetry.") if log_data: - data_to_log = json.dumps(data) - else: - data_to_log = 'redacted' - logger.debug("Sending {} telemetry. Data: {}".format(self.telem_category, data_to_log)) - ControlClient.send_telemetry(self.telem_category, data) + logger.debug(f"Telemetry contents: {BaseTelem.truncate_data(serialized_data)}") @property @abc.abstractmethod @@ -37,9 +51,9 @@ class BaseTelem(object, metaclass=abc.ABCMeta): """ pass - @abc.abstractmethod - def get_data(self) -> dict: - """ - :return: Data of telemetry (should be dict) - """ - pass + @staticmethod + def truncate_data(data: str): + if len(data) <= LOGGED_DATA_LENGTH: + return data + else: + return f"{data[:LOGGED_DATA_LENGTH]}..." diff --git a/monkey/infection_monkey/telemetry/scoutsuite_telem.py b/monkey/infection_monkey/telemetry/scoutsuite_telem.py index d606ea3c3..743de93e7 100644 --- a/monkey/infection_monkey/telemetry/scoutsuite_telem.py +++ b/monkey/infection_monkey/telemetry/scoutsuite_telem.py @@ -1,4 +1,6 @@ from common.common_consts.telem_categories import TelemCategoryEnum +from infection_monkey.system_info.collectors.scoutsuite_collector.scoutsuite.ScoutSuite.output.result_encoder import \ + ScoutJsonEncoder from infection_monkey.telemetry.base_telem import BaseTelem @@ -12,6 +14,7 @@ class ScoutSuiteTelem(BaseTelem): super().__init__() self.data = data + json_encoder = ScoutJsonEncoder telem_category = TelemCategoryEnum.SCOUTSUITE def get_data(self): diff --git a/monkey/monkey_island/cc/resources/reporting/report.py b/monkey/monkey_island/cc/resources/reporting/report.py index a0ea8b0b9..f196fdfb6 100644 --- a/monkey/monkey_island/cc/resources/reporting/report.py +++ b/monkey/monkey_island/cc/resources/reporting/report.py @@ -31,8 +31,7 @@ class Report(flask_restful.Resource): "statusesToPillars": ZeroTrustService.get_statuses_to_pillars(), "pillarsToStatuses": ZeroTrustService.get_pillars_to_statuses(), "grades": ZeroTrustService.get_pillars_grades() - } - ) + }) elif report_data == REPORT_DATA_PRINCIPLES_STATUS: return jsonify(ZeroTrustService.get_principles_status()) elif report_data == REPORT_DATA_FINDINGS: diff --git a/monkey/monkey_island/cc/services/telemetry/processing/scoutsuite.py b/monkey/monkey_island/cc/services/telemetry/processing/scoutsuite.py new file mode 100644 index 000000000..ae63fe508 --- /dev/null +++ b/monkey/monkey_island/cc/services/telemetry/processing/scoutsuite.py @@ -0,0 +1,11 @@ +from monkey_island.cc.database import mongo + + +def process_scoutsuite_telemetry(telemetry_json): + update_data(telemetry_json) + + +def update_data(telemetry_json): + mongo.db.scoutsuite.update( + {'guid': telemetry_json['monkey_guid']}, + {'$push': {'results': telemetry_json['data']}}) From 9952f691981e3bdb0df867f8aecc042f1fb0aa0d Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Mon, 7 Sep 2020 13:36:18 +0300 Subject: [PATCH 013/152] Refactoring ZT findings --- .../cc/models/zero_trust/aggregate_finding.py | 22 +++++++-- .../cc/models/zero_trust/finding.py | 17 +++---- .../cc/models/zero_trust/finding_details.py | 27 +++++++++++ .../models/zero_trust/scoutsuite_finding.py | 17 +++++++ .../cc/resources/reporting/report.py | 2 +- .../monkey_island/cc/resources/telemetry.py | 1 + .../services/reporting/zero_trust_service.py | 46 ++++++++++++------- .../telemetry/processing/processing.py | 3 ++ .../telemetry/processing/scoutsuite.py | 4 ++ 9 files changed, 107 insertions(+), 32 deletions(-) create mode 100644 monkey/monkey_island/cc/models/zero_trust/finding_details.py create mode 100644 monkey/monkey_island/cc/models/zero_trust/scoutsuite_finding.py diff --git a/monkey/monkey_island/cc/models/zero_trust/aggregate_finding.py b/monkey/monkey_island/cc/models/zero_trust/aggregate_finding.py index c5684abc0..af7350830 100644 --- a/monkey/monkey_island/cc/models/zero_trust/aggregate_finding.py +++ b/monkey/monkey_island/cc/models/zero_trust/aggregate_finding.py @@ -1,5 +1,9 @@ +from typing import List + import common.common_consts.zero_trust_consts as zero_trust_consts +from monkey_island.cc.models.zero_trust.event import Event from monkey_island.cc.models.zero_trust.finding import Finding +from monkey_island.cc.models.zero_trust.finding_details import FindingDetails class AggregateFinding(Finding): @@ -12,15 +16,25 @@ class AggregateFinding(Finding): :raises: Assertion error if this is used when there's more then one finding which fits the query - this is not when this function should be used. """ - existing_findings = Finding.objects(test=test, status=status).exclude('events') + existing_findings = Finding.objects(test=test, status=status) assert (len(existing_findings) < 2), "More than one finding exists for {}:{}".format(test, status) if len(existing_findings) == 0: - Finding.save_finding(test, status, events) + AggregateFinding.create_new_finding(test, status, events) else: # Now we know for sure this is the only one - orig_finding = existing_findings[0] - orig_finding.add_events(events) + AggregateFinding.add_events(existing_findings[0], events) + + @staticmethod + def create_new_finding(test: str, status: str, events: List[Event]): + details = FindingDetails() + details.events = events + details.save() + Finding.save_finding(test, status, details) + + @staticmethod + def add_events(finding: Finding, events: List[Event]): + finding.details.fetch().add_events(events) def add_malicious_activity_to_timeline(events): diff --git a/monkey/monkey_island/cc/models/zero_trust/finding.py b/monkey/monkey_island/cc/models/zero_trust/finding.py index d07cb9a45..b6bbf900d 100644 --- a/monkey/monkey_island/cc/models/zero_trust/finding.py +++ b/monkey/monkey_island/cc/models/zero_trust/finding.py @@ -4,12 +4,13 @@ Define a Document Schema for Zero Trust findings. """ from typing import List -from mongoengine import Document, EmbeddedDocumentListField, StringField +from mongoengine import Document, EmbeddedDocumentListField, StringField, LazyReferenceField import common.common_consts.zero_trust_consts as zero_trust_consts # Dummy import for mongoengine. # noinspection PyUnresolvedReferences from monkey_island.cc.models.zero_trust.event import Event +from monkey_island.cc.models.zero_trust.finding_details import FindingDetails class Finding(Document): @@ -33,7 +34,7 @@ class Finding(Document): # SCHEMA test = StringField(required=True, choices=zero_trust_consts.TESTS) status = StringField(required=True, choices=zero_trust_consts.ORDERED_TEST_STATUSES) - events = EmbeddedDocumentListField(document_type=Event) + details = LazyReferenceField(document_type=FindingDetails, required=True) # http://docs.mongoengine.org/guide/defining-documents.html#document-inheritance meta = {'allow_inheritance': True} @@ -46,15 +47,11 @@ class Finding(Document): # Creation methods @staticmethod - def save_finding(test, status, events): - finding = Finding( - test=test, - status=status, - events=events) + def save_finding(test: str, status: str, detail_ref): + finding = Finding(test=test, + status=status, + details=detail_ref) finding.save() return finding - - def add_events(self, events: List) -> None: - self.update(push_all__events=events) diff --git a/monkey/monkey_island/cc/models/zero_trust/finding_details.py b/monkey/monkey_island/cc/models/zero_trust/finding_details.py new file mode 100644 index 000000000..260442781 --- /dev/null +++ b/monkey/monkey_island/cc/models/zero_trust/finding_details.py @@ -0,0 +1,27 @@ +from datetime import datetime +from typing import List + +from mongoengine import DateTimeField, Document, StringField, EmbeddedDocumentListField + +import common.common_consts.zero_trust_consts as zero_trust_consts +from monkey_island.cc.models.zero_trust.event import Event +from monkey_island.cc.models.zero_trust.scoutsuite_finding import ScoutsuiteFinding + + +class FindingDetails(Document): + """ + This model represents additional information about monkey finding: + Events if monkey finding + Scoutsuite findings if scoutsuite finding + """ + + # SCHEMA + events = EmbeddedDocumentListField(document_type=Event, required=False) + scoutsuite_findings = EmbeddedDocumentListField(document_type=ScoutsuiteFinding, required=False) + + # LOGIC + def add_events(self, events: List[Event]) -> None: + self.update(push_all__events=events) + + def add_scoutsuite_findings(self, scoutsuite_findings: List[ScoutsuiteFinding]) -> None: + self.update(push_all__scoutsuite_findings=scoutsuite_findings) diff --git a/monkey/monkey_island/cc/models/zero_trust/scoutsuite_finding.py b/monkey/monkey_island/cc/models/zero_trust/scoutsuite_finding.py new file mode 100644 index 000000000..f8d0f5042 --- /dev/null +++ b/monkey/monkey_island/cc/models/zero_trust/scoutsuite_finding.py @@ -0,0 +1,17 @@ +from datetime import datetime + +from mongoengine import DateTimeField, EmbeddedDocument, StringField + + +class ScoutsuiteFinding(EmbeddedDocument): + # SCHEMA + temp = StringField(required=True) + + # LOGIC + @staticmethod + def create_scoutsuite_finding(title, message, event_type, timestamp=None): + scoutsuite_finding = ScoutsuiteFinding() + + scoutsuite_finding.temp = "temp" + + return scoutsuite_finding diff --git a/monkey/monkey_island/cc/resources/reporting/report.py b/monkey/monkey_island/cc/resources/reporting/report.py index f196fdfb6..5c25d1ff6 100644 --- a/monkey/monkey_island/cc/resources/reporting/report.py +++ b/monkey/monkey_island/cc/resources/reporting/report.py @@ -35,6 +35,6 @@ class Report(flask_restful.Resource): elif report_data == REPORT_DATA_PRINCIPLES_STATUS: return jsonify(ZeroTrustService.get_principles_status()) elif report_data == REPORT_DATA_FINDINGS: - return jsonify(ZeroTrustService.get_all_findings()) + return jsonify(ZeroTrustService.get_all_monkey_findings()) flask_restful.abort(http.client.NOT_FOUND) diff --git a/monkey/monkey_island/cc/resources/telemetry.py b/monkey/monkey_island/cc/resources/telemetry.py index dd622b140..2686f08fd 100644 --- a/monkey/monkey_island/cc/resources/telemetry.py +++ b/monkey/monkey_island/cc/resources/telemetry.py @@ -46,6 +46,7 @@ class Telemetry(flask_restful.Resource): @TestTelemStore.store_test_telem def post(self): telemetry_json = json.loads(request.data) + telemetry_json['data'] = json.loads(telemetry_json['data']) telemetry_json['timestamp'] = datetime.now() telemetry_json['command_control_channel'] = {'src': request.remote_addr, 'dst': request.host} diff --git a/monkey/monkey_island/cc/services/reporting/zero_trust_service.py b/monkey/monkey_island/cc/services/reporting/zero_trust_service.py index a70f4f8b4..bd3f12e16 100644 --- a/monkey/monkey_island/cc/services/reporting/zero_trust_service.py +++ b/monkey/monkey_island/cc/services/reporting/zero_trust_service.py @@ -7,6 +7,8 @@ from monkey_island.cc.models.zero_trust.finding import Finding # How many events of a single finding to return to UI. # 50 will return 50 latest and 50 oldest events from a finding +from monkey_island.cc.models.zero_trust.finding_details import FindingDetails + EVENT_FETCH_CNT = 50 @@ -14,7 +16,7 @@ class ZeroTrustService(object): @staticmethod def get_pillars_grades(): pillars_grades = [] - all_findings = Finding.objects().exclude('events') + all_findings = Finding.objects() for pillar in zero_trust_consts.PILLARS: pillars_grades.append(ZeroTrustService.__get_pillar_grade(pillar, all_findings)) return pillars_grades @@ -41,7 +43,8 @@ class ZeroTrustService(object): if pillar in test_info[zero_trust_consts.PILLARS_KEY]: pillar_grade[finding.status] += 1 - pillar_grade[zero_trust_consts.STATUS_UNEXECUTED] = sum(1 for condition in list(test_unexecuted.values()) if condition) + pillar_grade[zero_trust_consts.STATUS_UNEXECUTED] = sum(1 for condition in + list(test_unexecuted.values()) if condition) return pillar_grade @@ -70,7 +73,7 @@ class ZeroTrustService(object): worst_status = zero_trust_consts.STATUS_UNEXECUTED all_statuses = set() for test in principle_tests: - all_statuses |= set(Finding.objects(test=test).exclude('events').distinct("status")) + all_statuses |= set(Finding.objects(test=test).distinct('status')) for status in all_statuses: if zero_trust_consts.ORDERED_TEST_STATUSES.index(status) \ @@ -83,7 +86,7 @@ class ZeroTrustService(object): def __get_tests_status(principle_tests): results = [] for test in principle_tests: - test_findings = Finding.objects(test=test).exclude('events') + test_findings = Finding.objects(test=test) results.append( { "test": zero_trust_consts.TESTS_MAP[test][zero_trust_consts.TEST_EXPLANATION_KEY], @@ -108,18 +111,30 @@ class ZeroTrustService(object): return current_worst_status @staticmethod - def get_all_findings(): + def get_all_monkey_findings(): + findings = list(Finding.objects) + for finding in findings: + details = finding.details.fetch() + finding.details = details + enriched_findings = [ZeroTrustService.__get_enriched_finding(f) for f in findings] + all_finding_details = ZeroTrustService._parse_finding_details_for_ui() + return enriched_findings + + + @staticmethod + def _parse_finding_details_for_ui() -> List[FindingDetails]: + """ + We don't need to return all events to UI, we only display N first and N last events. + This code returns a list of FindingDetails with ONLY the events which are relevant to UI. + """ pipeline = [{'$addFields': {'oldest_events': {'$slice': ['$events', EVENT_FETCH_CNT]}, - 'latest_events': {'$slice': ['$events', -1*EVENT_FETCH_CNT]}, + 'latest_events': {'$slice': ['$events', -1 * EVENT_FETCH_CNT]}, 'event_count': {'$size': '$events'}}}, {'$unset': ['events']}] - all_findings = list(Finding.objects.aggregate(*pipeline)) - for finding in all_findings: - finding['latest_events'] = ZeroTrustService._get_events_without_overlap(finding['event_count'], - finding['latest_events']) - - enriched_findings = [ZeroTrustService.__get_enriched_finding(f) for f in all_findings] - return enriched_findings + all_details = list(FindingDetails.objects.aggregate(*pipeline)) + for details in all_details: + details['latest_events'] = ZeroTrustService._get_events_without_overlap(details['event_count'], + details['latest_events']) @staticmethod def _get_events_without_overlap(event_count: int, events: List[object]) -> List[object]: @@ -140,9 +155,6 @@ class ZeroTrustService(object): 'test_key': finding['test'], 'pillars': test_info[zero_trust_consts.PILLARS_KEY], 'status': finding['status'], - 'latest_events': finding['latest_events'], - 'oldest_events': finding['oldest_events'], - 'event_count': finding['event_count'] } return enriched_finding @@ -169,7 +181,7 @@ class ZeroTrustService(object): @staticmethod def __get_status_of_single_pillar(pillar): - all_findings = Finding.objects().exclude('events') + all_findings = Finding.objects() grade = ZeroTrustService.__get_pillar_grade(pillar, all_findings) for status in zero_trust_consts.ORDERED_TEST_STATUSES: if grade[status] > 0: diff --git a/monkey/monkey_island/cc/services/telemetry/processing/processing.py b/monkey/monkey_island/cc/services/telemetry/processing/processing.py index 960a01517..0038273b1 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/processing.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/processing.py @@ -13,6 +13,8 @@ from monkey_island.cc.services.telemetry.processing.system_info import \ process_system_info_telemetry from monkey_island.cc.services.telemetry.processing.tunnel import \ process_tunnel_telemetry +from monkey_island.cc.services.telemetry.processing.scoutsuite import \ + process_scoutsuite_telemetry logger = logging.getLogger(__name__) @@ -24,6 +26,7 @@ TELEMETRY_CATEGORY_TO_PROCESSING_FUNC = \ TelemCategoryEnum.SCAN: process_scan_telemetry, TelemCategoryEnum.SYSTEM_INFO: process_system_info_telemetry, TelemCategoryEnum.POST_BREACH: process_post_breach_telemetry, + TelemCategoryEnum.SCOUTSUITE: process_scoutsuite_telemetry, # `lambda *args, **kwargs: None` is a no-op. TelemCategoryEnum.TRACE: lambda *args, **kwargs: None, TelemCategoryEnum.ATTACK: lambda *args, **kwargs: None, diff --git a/monkey/monkey_island/cc/services/telemetry/processing/scoutsuite.py b/monkey/monkey_island/cc/services/telemetry/processing/scoutsuite.py index ae63fe508..d0e25aebc 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/scoutsuite.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/scoutsuite.py @@ -1,7 +1,11 @@ +import json + from monkey_island.cc.database import mongo def process_scoutsuite_telemetry(telemetry_json): + # Encode data to json, because mongo can't save it as document (invalid document keys) + telemetry_json['data'] = json.dumps(telemetry_json['data']) update_data(telemetry_json) From 3490be1d8f499be429ad3ce2a5cdf82cbede1060 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 8 Sep 2020 12:39:55 +0300 Subject: [PATCH 014/152] Re-structured ZT files and separated class responsibilities better, also further refactor towards ZT findings being extendable with different types of details. --- .../cc/models/zero_trust/aggregate_finding.py | 45 ------------ .../cc/models/zero_trust/finding.py | 7 +- .../zero_trust/monkey_finding_details.py | 18 +++++ ...tails.py => scoutsuite_finding_details.py} | 10 +-- .../zero_trust/test_aggregate_finding.py | 11 ++- .../cc/resources/reporting/report.py | 2 +- .../cc/resources/zero_trust/finding_event.py | 5 +- .../__init__.py | 0 .../cc/services/zero_trust/events_service.py | 34 ++++++++++ .../cc/services/zero_trust/finding_service.py | 23 +++++++ .../zero_trust/monkey_finding_service.py | 68 +++++++++++++++++++ .../zero_trust/scoutsuite_finding_service.py | 3 + .../test_zero_trust_service.py | 4 +- .../zero_trust_service.py | 64 +---------------- 14 files changed, 162 insertions(+), 132 deletions(-) delete mode 100644 monkey/monkey_island/cc/models/zero_trust/aggregate_finding.py create mode 100644 monkey/monkey_island/cc/models/zero_trust/monkey_finding_details.py rename monkey/monkey_island/cc/models/zero_trust/{finding_details.py => scoutsuite_finding_details.py} (63%) rename monkey/monkey_island/cc/services/{telemetry/zero_trust_tests => zero_trust}/__init__.py (100%) create mode 100644 monkey/monkey_island/cc/services/zero_trust/events_service.py create mode 100644 monkey/monkey_island/cc/services/zero_trust/finding_service.py create mode 100644 monkey/monkey_island/cc/services/zero_trust/monkey_finding_service.py create mode 100644 monkey/monkey_island/cc/services/zero_trust/scoutsuite_finding_service.py rename monkey/monkey_island/cc/services/{reporting => zero_trust}/test_zero_trust_service.py (99%) rename monkey/monkey_island/cc/services/{reporting => zero_trust}/zero_trust_service.py (64%) diff --git a/monkey/monkey_island/cc/models/zero_trust/aggregate_finding.py b/monkey/monkey_island/cc/models/zero_trust/aggregate_finding.py deleted file mode 100644 index af7350830..000000000 --- a/monkey/monkey_island/cc/models/zero_trust/aggregate_finding.py +++ /dev/null @@ -1,45 +0,0 @@ -from typing import List - -import common.common_consts.zero_trust_consts as zero_trust_consts -from monkey_island.cc.models.zero_trust.event import Event -from monkey_island.cc.models.zero_trust.finding import Finding -from monkey_island.cc.models.zero_trust.finding_details import FindingDetails - - -class AggregateFinding(Finding): - @staticmethod - def create_or_add_to_existing(test, status, events): - """ - Create a new finding or add the events to an existing one if it's the same (same meaning same status and same - test). - - :raises: Assertion error if this is used when there's more then one finding which fits the query - this is not - when this function should be used. - """ - existing_findings = Finding.objects(test=test, status=status) - assert (len(existing_findings) < 2), "More than one finding exists for {}:{}".format(test, status) - - if len(existing_findings) == 0: - AggregateFinding.create_new_finding(test, status, events) - else: - # Now we know for sure this is the only one - AggregateFinding.add_events(existing_findings[0], events) - - @staticmethod - def create_new_finding(test: str, status: str, events: List[Event]): - details = FindingDetails() - details.events = events - details.save() - Finding.save_finding(test, status, details) - - @staticmethod - def add_events(finding: Finding, events: List[Event]): - finding.details.fetch().add_events(events) - - -def add_malicious_activity_to_timeline(events): - AggregateFinding.create_or_add_to_existing( - test=zero_trust_consts.TEST_MALICIOUS_ACTIVITY_TIMELINE, - status=zero_trust_consts.STATUS_VERIFY, - events=events - ) diff --git a/monkey/monkey_island/cc/models/zero_trust/finding.py b/monkey/monkey_island/cc/models/zero_trust/finding.py index b6bbf900d..8895a7fdb 100644 --- a/monkey/monkey_island/cc/models/zero_trust/finding.py +++ b/monkey/monkey_island/cc/models/zero_trust/finding.py @@ -4,13 +4,14 @@ Define a Document Schema for Zero Trust findings. """ from typing import List -from mongoengine import Document, EmbeddedDocumentListField, StringField, LazyReferenceField +from mongoengine import Document, StringField, GenericLazyReferenceField import common.common_consts.zero_trust_consts as zero_trust_consts # Dummy import for mongoengine. # noinspection PyUnresolvedReferences from monkey_island.cc.models.zero_trust.event import Event -from monkey_island.cc.models.zero_trust.finding_details import FindingDetails +from monkey_island.cc.models.zero_trust.monkey_finding_details import MonkeyFindingDetails +from monkey_island.cc.models.zero_trust.scoutsuite_finding_details import ScoutsuiteFindingDetails class Finding(Document): @@ -34,7 +35,7 @@ class Finding(Document): # SCHEMA test = StringField(required=True, choices=zero_trust_consts.TESTS) status = StringField(required=True, choices=zero_trust_consts.ORDERED_TEST_STATUSES) - details = LazyReferenceField(document_type=FindingDetails, required=True) + details = GenericLazyReferenceField(choices=[MonkeyFindingDetails, ScoutsuiteFindingDetails], required=True) # http://docs.mongoengine.org/guide/defining-documents.html#document-inheritance meta = {'allow_inheritance': True} diff --git a/monkey/monkey_island/cc/models/zero_trust/monkey_finding_details.py b/monkey/monkey_island/cc/models/zero_trust/monkey_finding_details.py new file mode 100644 index 000000000..029136679 --- /dev/null +++ b/monkey/monkey_island/cc/models/zero_trust/monkey_finding_details.py @@ -0,0 +1,18 @@ +from typing import List + +from mongoengine import DateTimeField, Document, StringField, EmbeddedDocumentListField + +from monkey_island.cc.models.zero_trust.event import Event + +class MonkeyFindingDetails(Document): + """ + This model represents additional information about monkey finding: + Events + """ + + # SCHEMA + events = EmbeddedDocumentListField(document_type=Event, required=False) + + # LOGIC + def add_events(self, events: List[Event]) -> None: + self.update(push_all__events=events) diff --git a/monkey/monkey_island/cc/models/zero_trust/finding_details.py b/monkey/monkey_island/cc/models/zero_trust/scoutsuite_finding_details.py similarity index 63% rename from monkey/monkey_island/cc/models/zero_trust/finding_details.py rename to monkey/monkey_island/cc/models/zero_trust/scoutsuite_finding_details.py index 260442781..ed05f6003 100644 --- a/monkey/monkey_island/cc/models/zero_trust/finding_details.py +++ b/monkey/monkey_island/cc/models/zero_trust/scoutsuite_finding_details.py @@ -1,14 +1,11 @@ -from datetime import datetime from typing import List from mongoengine import DateTimeField, Document, StringField, EmbeddedDocumentListField -import common.common_consts.zero_trust_consts as zero_trust_consts -from monkey_island.cc.models.zero_trust.event import Event from monkey_island.cc.models.zero_trust.scoutsuite_finding import ScoutsuiteFinding -class FindingDetails(Document): +class ScoutsuiteFindingDetails(Document): """ This model represents additional information about monkey finding: Events if monkey finding @@ -16,12 +13,7 @@ class FindingDetails(Document): """ # SCHEMA - events = EmbeddedDocumentListField(document_type=Event, required=False) scoutsuite_findings = EmbeddedDocumentListField(document_type=ScoutsuiteFinding, required=False) - # LOGIC - def add_events(self, events: List[Event]) -> None: - self.update(push_all__events=events) - def add_scoutsuite_findings(self, scoutsuite_findings: List[ScoutsuiteFinding]) -> None: self.update(push_all__scoutsuite_findings=scoutsuite_findings) diff --git a/monkey/monkey_island/cc/models/zero_trust/test_aggregate_finding.py b/monkey/monkey_island/cc/models/zero_trust/test_aggregate_finding.py index 4b9765f70..5d042312f 100644 --- a/monkey/monkey_island/cc/models/zero_trust/test_aggregate_finding.py +++ b/monkey/monkey_island/cc/models/zero_trust/test_aggregate_finding.py @@ -4,8 +4,7 @@ import mongomock from packaging import version import common.common_consts.zero_trust_consts as zero_trust_consts -from monkey_island.cc.models.zero_trust.aggregate_finding import \ - AggregateFinding +from monkey_island.cc.services.zero_trust.monkey_finding_service import MonkeyFindingService from monkey_island.cc.models.zero_trust.event import Event from monkey_island.cc.models.zero_trust.finding import Finding from monkey_island.cc.testing.IslandTestCase import IslandTestCase @@ -24,12 +23,12 @@ class TestAggregateFinding(IslandTestCase): events = [Event.create_event("t", "t", zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK)] self.assertEqual(len(Finding.objects(test=test, status=status)), 0) - AggregateFinding.create_or_add_to_existing(test, status, events) + MonkeyFindingService.create_or_add_to_existing(test, status, events) self.assertEqual(len(Finding.objects(test=test, status=status)), 1) self.assertEqual(len(Finding.objects(test=test, status=status)[0].events), 1) - AggregateFinding.create_or_add_to_existing(test, status, events) + MonkeyFindingService.create_or_add_to_existing(test, status, events) self.assertEqual(len(Finding.objects(test=test, status=status)), 1) self.assertEqual(len(Finding.objects(test=test, status=status)[0].events), 2) @@ -51,7 +50,7 @@ class TestAggregateFinding(IslandTestCase): self.assertEqual(len(Finding.objects(test=test, status=status)), 1) self.assertEqual(len(Finding.objects(test=test, status=status)[0].events), 1) - AggregateFinding.create_or_add_to_existing(test, status, events) + MonkeyFindingService.create_or_add_to_existing(test, status, events) self.assertEqual(len(Finding.objects(test=test, status=status)), 1) self.assertEqual(len(Finding.objects(test=test, status=status)[0].events), 2) @@ -61,4 +60,4 @@ class TestAggregateFinding(IslandTestCase): self.assertEqual(len(Finding.objects(test=test, status=status)), 2) with self.assertRaises(AssertionError): - AggregateFinding.create_or_add_to_existing(test, status, events) + MonkeyFindingService.create_or_add_to_existing(test, status, events) diff --git a/monkey/monkey_island/cc/resources/reporting/report.py b/monkey/monkey_island/cc/resources/reporting/report.py index 5c25d1ff6..f1b95607f 100644 --- a/monkey/monkey_island/cc/resources/reporting/report.py +++ b/monkey/monkey_island/cc/resources/reporting/report.py @@ -5,7 +5,7 @@ from flask import jsonify from monkey_island.cc.resources.auth.auth import jwt_required from monkey_island.cc.services.reporting.report import ReportService -from monkey_island.cc.services.reporting.zero_trust_service import \ +from monkey_island.cc.services.zero_trust.zero_trust_service import \ ZeroTrustService ZERO_TRUST_REPORT_TYPE = "zero_trust" diff --git a/monkey/monkey_island/cc/resources/zero_trust/finding_event.py b/monkey/monkey_island/cc/resources/zero_trust/finding_event.py index 8a1879c9c..0e6c09b11 100644 --- a/monkey/monkey_island/cc/resources/zero_trust/finding_event.py +++ b/monkey/monkey_island/cc/resources/zero_trust/finding_event.py @@ -3,12 +3,11 @@ import json import flask_restful from monkey_island.cc.resources.auth.auth import jwt_required -from monkey_island.cc.services.reporting.zero_trust_service import \ - ZeroTrustService +from monkey_island.cc.services.zero_trust.monkey_finding_service import MonkeyFindingService class ZeroTrustFindingEvent(flask_restful.Resource): @jwt_required def get(self, finding_id: str): - return {'events_json': json.dumps(ZeroTrustService.get_events_by_finding(finding_id), default=str)} + return {'events_json': json.dumps(MonkeyFindingService.get_events_by_finding(finding_id), default=str)} diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/__init__.py b/monkey/monkey_island/cc/services/zero_trust/__init__.py similarity index 100% rename from monkey/monkey_island/cc/services/telemetry/zero_trust_tests/__init__.py rename to monkey/monkey_island/cc/services/zero_trust/__init__.py diff --git a/monkey/monkey_island/cc/services/zero_trust/events_service.py b/monkey/monkey_island/cc/services/zero_trust/events_service.py new file mode 100644 index 000000000..5cccee7f3 --- /dev/null +++ b/monkey/monkey_island/cc/services/zero_trust/events_service.py @@ -0,0 +1,34 @@ +from typing import List + +from bson import ObjectId + +from monkey_island.cc.models.zero_trust.monkey_finding_details import MonkeyFindingDetails + +# How many events of a single finding to return to UI. +# 50 will return 50 latest and 50 oldest events from a finding +EVENT_FETCH_CNT = 50 + + +class EventsService: + + @staticmethod + def fetch_events_for_display(finding_id: ObjectId): + pipeline = [{'$match': {'_id': finding_id}}, + {'$addFields': {'oldest_events': {'$slice': ['$events', EVENT_FETCH_CNT]}, + 'latest_events': {'$slice': ['$events', -1 * EVENT_FETCH_CNT]}, + 'event_count': {'$size': '$events'}}}, + {'$unset': ['events']}] + details = MonkeyFindingDetails.objects.aggregate(*pipeline).next() + details['latest_events'] = EventsService._get_events_without_overlap(details['event_count'], + details['latest_events']) + return details + + @staticmethod + def _get_events_without_overlap(event_count: int, events: List[object]) -> List[object]: + overlap_count = event_count - EVENT_FETCH_CNT + if overlap_count >= EVENT_FETCH_CNT: + return events + elif overlap_count <= 0: + return [] + else: + return events[-1 * overlap_count:] diff --git a/monkey/monkey_island/cc/services/zero_trust/finding_service.py b/monkey/monkey_island/cc/services/zero_trust/finding_service.py new file mode 100644 index 000000000..2feb02cce --- /dev/null +++ b/monkey/monkey_island/cc/services/zero_trust/finding_service.py @@ -0,0 +1,23 @@ +from typing import List + +from common.common_consts import zero_trust_consts +from monkey_island.cc.models.zero_trust.finding import Finding + + +class FindingService: + + @staticmethod + def get_all_findings() -> List[Finding]: + return list(Finding.objects) + + @staticmethod + def get_enriched_finding(finding): + test_info = zero_trust_consts.TESTS_MAP[finding['test']] + enriched_finding = { + 'finding_id': str(finding['_id']), + 'test': test_info[zero_trust_consts.FINDING_EXPLANATION_BY_STATUS_KEY][finding['status']], + 'test_key': finding['test'], + 'pillars': test_info[zero_trust_consts.PILLARS_KEY], + 'status': finding['status'], + } + return enriched_finding diff --git a/monkey/monkey_island/cc/services/zero_trust/monkey_finding_service.py b/monkey/monkey_island/cc/services/zero_trust/monkey_finding_service.py new file mode 100644 index 000000000..a1e731b14 --- /dev/null +++ b/monkey/monkey_island/cc/services/zero_trust/monkey_finding_service.py @@ -0,0 +1,68 @@ +from typing import List + +from bson import ObjectId + +from common.common_consts import zero_trust_consts +from monkey_island.cc.models.zero_trust.event import Event +from monkey_island.cc.models.zero_trust.finding import Finding +from monkey_island.cc.models.zero_trust.monkey_finding_details import MonkeyFindingDetails +from monkey_island.cc.services.zero_trust.finding_service import FindingService + + +class MonkeyFindingService: + + @staticmethod + def create_or_add_to_existing(test, status, events): + """ + Create a new finding or add the events to an existing one if it's the same (same meaning same status and same + test). + + :raises: Assertion error if this is used when there's more then one finding which fits the query - this is not + when this function should be used. + """ + existing_findings = Finding.objects(test=test, status=status) + assert (len(existing_findings) < 2), "More than one finding exists for {}:{}".format(test, status) + + if len(existing_findings) == 0: + MonkeyFindingService.create_new_finding(test, status, events) + else: + # Now we know for sure this is the only one + MonkeyFindingService.add_events(existing_findings[0], events) + + @staticmethod + def create_new_finding(test: str, status: str, events: List[Event]): + details = MonkeyFindingDetails() + details.events = events + details.save() + Finding.save_finding(test, status, details) + + @staticmethod + def add_events(finding: Finding, events: List[Event]): + finding.details.fetch().add_events(events) + + @staticmethod + def get_all_monkey_findings(): + findings = FindingService.get_all_findings() + for i in range(len(findings)): + details = MonkeyFindingService.fetch_events_for_display(findings[i].details.id) + findings[i] = findings[i].to_mongo() + findings[i] = FindingService.get_enriched_finding(findings[i]) + findings[i]['details'] = details + return findings + + @staticmethod + def get_events_by_finding(finding_id: str) -> List[object]: + finding = Finding.objects.get(id=finding_id) + pipeline = [{'$match': {'_id': ObjectId(finding.details.id)}}, + {'$unwind': '$events'}, + {'$project': {'events': '$events'}}, + {'$replaceRoot': {'newRoot': '$events'}}] + return list(MonkeyFindingDetails.objects.aggregate(*pipeline)) + + @staticmethod + def add_malicious_activity_to_timeline(events): + MonkeyFindingService.create_or_add_to_existing( + test=zero_trust_consts.TEST_MALICIOUS_ACTIVITY_TIMELINE, + status=zero_trust_consts.STATUS_VERIFY, + events=events + ) diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite_finding_service.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite_finding_service.py new file mode 100644 index 000000000..12ab2743b --- /dev/null +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite_finding_service.py @@ -0,0 +1,3 @@ + +class ScoutsuiteFindingService: + pass diff --git a/monkey/monkey_island/cc/services/reporting/test_zero_trust_service.py b/monkey/monkey_island/cc/services/zero_trust/test_zero_trust_service.py similarity index 99% rename from monkey/monkey_island/cc/services/reporting/test_zero_trust_service.py rename to monkey/monkey_island/cc/services/zero_trust/test_zero_trust_service.py index 874eee293..8b3d33ba2 100644 --- a/monkey/monkey_island/cc/services/reporting/test_zero_trust_service.py +++ b/monkey/monkey_island/cc/services/zero_trust/test_zero_trust_service.py @@ -1,7 +1,7 @@ import common.common_consts.zero_trust_consts as zero_trust_consts -import monkey_island.cc.services.reporting.zero_trust_service +import monkey_island.cc.services.zero_trust.zero_trust_service from monkey_island.cc.models.zero_trust.finding import Finding -from monkey_island.cc.services.reporting.zero_trust_service import \ +from monkey_island.cc.services.zero_trust.zero_trust_service import \ ZeroTrustService from monkey_island.cc.testing.IslandTestCase import IslandTestCase diff --git a/monkey/monkey_island/cc/services/reporting/zero_trust_service.py b/monkey/monkey_island/cc/services/zero_trust/zero_trust_service.py similarity index 64% rename from monkey/monkey_island/cc/services/reporting/zero_trust_service.py rename to monkey/monkey_island/cc/services/zero_trust/zero_trust_service.py index bd3f12e16..613132a54 100644 --- a/monkey/monkey_island/cc/services/reporting/zero_trust_service.py +++ b/monkey/monkey_island/cc/services/zero_trust/zero_trust_service.py @@ -5,14 +5,8 @@ from bson.objectid import ObjectId import common.common_consts.zero_trust_consts as zero_trust_consts from monkey_island.cc.models.zero_trust.finding import Finding -# How many events of a single finding to return to UI. -# 50 will return 50 latest and 50 oldest events from a finding -from monkey_island.cc.models.zero_trust.finding_details import FindingDetails -EVENT_FETCH_CNT = 50 - - -class ZeroTrustService(object): +class ZeroTrustService: @staticmethod def get_pillars_grades(): pillars_grades = [] @@ -110,54 +104,6 @@ class ZeroTrustService(object): return current_worst_status - @staticmethod - def get_all_monkey_findings(): - findings = list(Finding.objects) - for finding in findings: - details = finding.details.fetch() - finding.details = details - enriched_findings = [ZeroTrustService.__get_enriched_finding(f) for f in findings] - all_finding_details = ZeroTrustService._parse_finding_details_for_ui() - return enriched_findings - - - @staticmethod - def _parse_finding_details_for_ui() -> List[FindingDetails]: - """ - We don't need to return all events to UI, we only display N first and N last events. - This code returns a list of FindingDetails with ONLY the events which are relevant to UI. - """ - pipeline = [{'$addFields': {'oldest_events': {'$slice': ['$events', EVENT_FETCH_CNT]}, - 'latest_events': {'$slice': ['$events', -1 * EVENT_FETCH_CNT]}, - 'event_count': {'$size': '$events'}}}, - {'$unset': ['events']}] - all_details = list(FindingDetails.objects.aggregate(*pipeline)) - for details in all_details: - details['latest_events'] = ZeroTrustService._get_events_without_overlap(details['event_count'], - details['latest_events']) - - @staticmethod - def _get_events_without_overlap(event_count: int, events: List[object]) -> List[object]: - overlap_count = event_count - EVENT_FETCH_CNT - if overlap_count >= EVENT_FETCH_CNT: - return events - elif overlap_count <= 0: - return [] - else: - return events[-1 * overlap_count:] - - @staticmethod - def __get_enriched_finding(finding): - test_info = zero_trust_consts.TESTS_MAP[finding['test']] - enriched_finding = { - 'finding_id': str(finding['_id']), - 'test': test_info[zero_trust_consts.FINDING_EXPLANATION_BY_STATUS_KEY][finding['status']], - 'test_key': finding['test'], - 'pillars': test_info[zero_trust_consts.PILLARS_KEY], - 'status': finding['status'], - } - return enriched_finding - @staticmethod def get_statuses_to_pillars(): results = { @@ -187,11 +133,3 @@ class ZeroTrustService(object): if grade[status] > 0: return status return zero_trust_consts.STATUS_UNEXECUTED - - @staticmethod - def get_events_by_finding(finding_id: str) -> List[object]: - pipeline = [{'$match': {'_id': ObjectId(finding_id)}}, - {'$unwind': '$events'}, - {'$project': {'events': '$events'}}, - {'$replaceRoot': {'newRoot': '$events'}}] - return list(Finding.objects.aggregate(*pipeline)) From 4e1e9907b198f18a0eb92bb3154253877d8c2918 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 8 Sep 2020 12:41:59 +0300 Subject: [PATCH 015/152] Renamed all zero trust tests to zero trust checks in back-end. This increases readability, because it differentiates unit test code from production code --- .../cc/services/telemetry/processing/exploit.py | 6 +++--- .../cc/services/telemetry/processing/post_breach.py | 6 +++--- .../cc/services/telemetry/processing/scan.py | 12 ++++++------ .../cc/services/telemetry/processing/state.py | 6 +++--- .../system_info_telemetry_dispatcher.py | 6 +++--- .../cc/services/telemetry/processing/tunnel.py | 6 +++--- .../services/telemetry/zero_trust_checks/__init__.py | 0 .../antivirus_existence.py | 9 ++++----- .../communicate_as_new_user.py | 7 +++---- .../data_endpoints.py | 11 +++++------ .../known_anti_viruses.py | 0 .../machine_exploited.py | 9 ++++----- .../segmentation.py | 4 ++-- .../test_segmentation.py} | 4 ++-- .../tunneling.py | 9 ++++----- 15 files changed, 45 insertions(+), 50 deletions(-) create mode 100644 monkey/monkey_island/cc/services/telemetry/zero_trust_checks/__init__.py rename monkey/monkey_island/cc/services/telemetry/{zero_trust_tests => zero_trust_checks}/antivirus_existence.py (85%) rename monkey/monkey_island/cc/services/telemetry/{zero_trust_tests => zero_trust_checks}/communicate_as_new_user.py (87%) rename monkey/monkey_island/cc/services/telemetry/{zero_trust_tests => zero_trust_checks}/data_endpoints.py (89%) rename monkey/monkey_island/cc/services/telemetry/{zero_trust_tests => zero_trust_checks}/known_anti_viruses.py (100%) rename monkey/monkey_island/cc/services/telemetry/{zero_trust_tests => zero_trust_checks}/machine_exploited.py (78%) rename monkey/monkey_island/cc/services/telemetry/{zero_trust_tests => zero_trust_checks}/segmentation.py (97%) rename monkey/monkey_island/cc/services/telemetry/{zero_trust_tests/test_segmentation_zt_tests.py => zero_trust_checks/test_segmentation.py} (94%) rename monkey/monkey_island/cc/services/telemetry/{zero_trust_tests => zero_trust_checks}/tunneling.py (78%) diff --git a/monkey/monkey_island/cc/services/telemetry/processing/exploit.py b/monkey/monkey_island/cc/services/telemetry/processing/exploit.py index 69c1e20f6..e88c37dfa 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/exploit.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/exploit.py @@ -9,8 +9,8 @@ from monkey_island.cc.services.edge.displayed_edge import EdgeService from monkey_island.cc.services.node import NodeService from monkey_island.cc.services.telemetry.processing.utils import \ get_edge_by_scan_or_exploit_telemetry -from monkey_island.cc.services.telemetry.zero_trust_tests.machine_exploited import \ - test_machine_exploited +from monkey_island.cc.services.telemetry.zero_trust_checks.machine_exploited import \ + check_machine_exploited def process_exploit_telemetry(telemetry_json): @@ -19,7 +19,7 @@ def process_exploit_telemetry(telemetry_json): update_network_with_exploit(edge, telemetry_json) update_node_credentials_from_successful_attempts(edge, telemetry_json) - test_machine_exploited( + check_machine_exploited( current_monkey=Monkey.get_single_monkey_by_guid(telemetry_json['monkey_guid']), exploit_successful=telemetry_json['data']['result'], exploiter=telemetry_json['data']['exploiter'], diff --git a/monkey/monkey_island/cc/services/telemetry/processing/post_breach.py b/monkey/monkey_island/cc/services/telemetry/processing/post_breach.py index 28909f40b..dd79fd7c9 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/post_breach.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/post_breach.py @@ -3,8 +3,8 @@ import copy from common.common_consts.post_breach_consts import POST_BREACH_COMMUNICATE_AS_NEW_USER from monkey_island.cc.database import mongo from monkey_island.cc.models import Monkey -from monkey_island.cc.services.telemetry.zero_trust_tests.communicate_as_new_user import \ - test_new_user_communication +from monkey_island.cc.services.telemetry.zero_trust_checks.communicate_as_new_user import \ + check_new_user_communication EXECUTION_WITHOUT_OUTPUT = "(PBA execution produced no output)" @@ -13,7 +13,7 @@ def process_communicate_as_new_user_telemetry(telemetry_json): current_monkey = Monkey.get_single_monkey_by_guid(telemetry_json['monkey_guid']) message = telemetry_json['data']['result'][0] success = telemetry_json['data']['result'][1] - test_new_user_communication(current_monkey, success, message) + check_new_user_communication(current_monkey, success, message) POST_BREACH_TELEMETRY_PROCESSING_FUNCS = { diff --git a/monkey/monkey_island/cc/services/telemetry/processing/scan.py b/monkey/monkey_island/cc/services/telemetry/processing/scan.py index 43446126c..2453223fe 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/scan.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/scan.py @@ -4,19 +4,19 @@ from monkey_island.cc.services.edge.edge import EdgeService from monkey_island.cc.services.node import NodeService from monkey_island.cc.services.telemetry.processing.utils import \ get_edge_by_scan_or_exploit_telemetry -from monkey_island.cc.services.telemetry.zero_trust_tests.data_endpoints import \ - test_open_data_endpoints -from monkey_island.cc.services.telemetry.zero_trust_tests.segmentation import \ - test_segmentation_violation +from monkey_island.cc.services.telemetry.zero_trust_checks.data_endpoints import \ + check_open_data_endpoints +from monkey_island.cc.services.telemetry.zero_trust_checks.segmentation import \ + check_segmentation_violation def process_scan_telemetry(telemetry_json): update_edges_and_nodes_based_on_scan_telemetry(telemetry_json) - test_open_data_endpoints(telemetry_json) + check_open_data_endpoints(telemetry_json) current_monkey = Monkey.get_single_monkey_by_guid(telemetry_json['monkey_guid']) target_ip = telemetry_json['data']['machine']['ip_addr'] - test_segmentation_violation(current_monkey, target_ip) + check_segmentation_violation(current_monkey, target_ip) def update_edges_and_nodes_based_on_scan_telemetry(telemetry_json): diff --git a/monkey/monkey_island/cc/services/telemetry/processing/state.py b/monkey/monkey_island/cc/services/telemetry/processing/state.py index 3ac555f3e..4f596fb88 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/state.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/state.py @@ -2,8 +2,8 @@ import logging from monkey_island.cc.models import Monkey from monkey_island.cc.services.node import NodeService -from monkey_island.cc.services.telemetry.zero_trust_tests.segmentation import \ - test_passed_findings_for_unreached_segments +from monkey_island.cc.services.telemetry.zero_trust_checks.segmentation import \ + check_passed_findings_for_unreached_segments logger = logging.getLogger(__name__) @@ -18,7 +18,7 @@ def process_state_telemetry(telemetry_json): if telemetry_json['data']['done']: current_monkey = Monkey.get_single_monkey_by_guid(telemetry_json['monkey_guid']) - test_passed_findings_for_unreached_segments(current_monkey) + check_passed_findings_for_unreached_segments(current_monkey) if telemetry_json['data']['version']: logger.info(f"monkey {telemetry_json['monkey_guid']} has version {telemetry_json['data']['version']}") diff --git a/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/system_info_telemetry_dispatcher.py b/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/system_info_telemetry_dispatcher.py index 5f1b6e641..d718df12a 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/system_info_telemetry_dispatcher.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/system_info_telemetry_dispatcher.py @@ -14,8 +14,8 @@ from monkey_island.cc.services.telemetry.processing.system_info_collectors.hostn process_hostname_telemetry from monkey_island.cc.services.telemetry.processing.system_info_collectors.scoutsuite import \ process_scout_suite_telemetry -from monkey_island.cc.services.telemetry.zero_trust_tests.antivirus_existence import \ - test_antivirus_existence +from monkey_island.cc.services.telemetry.zero_trust_checks.antivirus_existence import \ + check_antivirus_existence logger = logging.getLogger(__name__) @@ -23,7 +23,7 @@ SYSTEM_INFO_COLLECTOR_TO_TELEMETRY_PROCESSORS = { AWS_COLLECTOR: [process_aws_telemetry], ENVIRONMENT_COLLECTOR: [process_environment_telemetry], HOSTNAME_COLLECTOR: [process_hostname_telemetry], - PROCESS_LIST_COLLECTOR: [test_antivirus_existence], + PROCESS_LIST_COLLECTOR: [check_antivirus_existence], SCOUTSUITE_COLLECTOR: [process_scout_suite_telemetry] } diff --git a/monkey/monkey_island/cc/services/telemetry/processing/tunnel.py b/monkey/monkey_island/cc/services/telemetry/processing/tunnel.py index ef5ea0ff9..0800e0168 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/tunnel.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/tunnel.py @@ -1,12 +1,12 @@ from monkey_island.cc.services.node import NodeService from monkey_island.cc.services.telemetry.processing.utils import \ get_tunnel_host_ip_from_proxy_field -from monkey_island.cc.services.telemetry.zero_trust_tests.tunneling import \ - test_tunneling_violation +from monkey_island.cc.services.telemetry.zero_trust_checks.tunneling import \ + check_tunneling_violation def process_tunnel_telemetry(telemetry_json): - test_tunneling_violation(telemetry_json) + check_tunneling_violation(telemetry_json) monkey_id = NodeService.get_monkey_by_guid(telemetry_json['monkey_guid'])["_id"] if telemetry_json['data']['proxy'] is not None: tunnel_host_ip = get_tunnel_host_ip_from_proxy_field(telemetry_json) diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/__init__.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/antivirus_existence.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/antivirus_existence.py similarity index 85% rename from monkey/monkey_island/cc/services/telemetry/zero_trust_tests/antivirus_existence.py rename to monkey/monkey_island/cc/services/telemetry/zero_trust_checks/antivirus_existence.py index f16ae7295..3ed2133f0 100644 --- a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/antivirus_existence.py +++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/antivirus_existence.py @@ -2,14 +2,13 @@ import json import common.common_consts.zero_trust_consts as zero_trust_consts from monkey_island.cc.models import Monkey -from monkey_island.cc.models.zero_trust.aggregate_finding import \ - AggregateFinding from monkey_island.cc.models.zero_trust.event import Event -from monkey_island.cc.services.telemetry.zero_trust_tests.known_anti_viruses import \ +from monkey_island.cc.services.telemetry.zero_trust_checks.known_anti_viruses import \ ANTI_VIRUS_KNOWN_PROCESS_NAMES +from monkey_island.cc.services.zero_trust.monkey_finding_service import MonkeyFindingService -def test_antivirus_existence(process_list_json, monkey_guid): +def check_antivirus_existence(process_list_json, monkey_guid): current_monkey = Monkey.get_single_monkey_by_guid(monkey_guid) process_list_event = Event.create_event( @@ -32,7 +31,7 @@ def test_antivirus_existence(process_list_json, monkey_guid): test_status = zero_trust_consts.STATUS_PASSED else: test_status = zero_trust_consts.STATUS_FAILED - AggregateFinding.create_or_add_to_existing( + MonkeyFindingService.create_or_add_to_existing( test=zero_trust_consts.TEST_ENDPOINT_SECURITY_EXISTS, status=test_status, events=events ) diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/communicate_as_new_user.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/communicate_as_new_user.py similarity index 87% rename from monkey/monkey_island/cc/services/telemetry/zero_trust_tests/communicate_as_new_user.py rename to monkey/monkey_island/cc/services/telemetry/zero_trust_checks/communicate_as_new_user.py index c6d8a9570..109efbca0 100644 --- a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/communicate_as_new_user.py +++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/communicate_as_new_user.py @@ -1,6 +1,5 @@ import common.common_consts.zero_trust_consts as zero_trust_consts -from monkey_island.cc.models.zero_trust.aggregate_finding import \ - AggregateFinding +from monkey_island.cc.services.zero_trust.monkey_finding_service import MonkeyFindingService from monkey_island.cc.models.zero_trust.event import Event COMM_AS_NEW_USER_FAILED_FORMAT = "Monkey on {} couldn't communicate as new user. Details: {}" @@ -8,8 +7,8 @@ COMM_AS_NEW_USER_SUCCEEDED_FORMAT = \ "New user created by Monkey on {} successfully tried to communicate with the internet. Details: {}" -def test_new_user_communication(current_monkey, success, message): - AggregateFinding.create_or_add_to_existing( +def check_new_user_communication(current_monkey, success, message): + MonkeyFindingService.create_or_add_to_existing( test=zero_trust_consts.TEST_COMMUNICATE_AS_NEW_USER, # If the monkey succeeded to create a user, then the test failed. status=zero_trust_consts.STATUS_FAILED if success else zero_trust_consts.STATUS_PASSED, diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/data_endpoints.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/data_endpoints.py similarity index 89% rename from monkey/monkey_island/cc/services/telemetry/zero_trust_tests/data_endpoints.py rename to monkey/monkey_island/cc/services/telemetry/zero_trust_checks/data_endpoints.py index 024a91522..d4da2d8dd 100644 --- a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/data_endpoints.py +++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/data_endpoints.py @@ -3,14 +3,13 @@ import json import common.common_consts.zero_trust_consts as zero_trust_consts from common.common_consts.network_consts import ES_SERVICE from monkey_island.cc.models import Monkey -from monkey_island.cc.models.zero_trust.aggregate_finding import ( - AggregateFinding, add_malicious_activity_to_timeline) from monkey_island.cc.models.zero_trust.event import Event +from monkey_island.cc.services.zero_trust.monkey_finding_service import MonkeyFindingService HTTP_SERVERS_SERVICES_NAMES = ['tcp-80'] -def test_open_data_endpoints(telemetry_json): +def check_open_data_endpoints(telemetry_json): services = telemetry_json["data"]["machine"]["services"] current_monkey = Monkey.get_single_monkey_by_guid(telemetry_json['monkey_guid']) found_http_server_status = zero_trust_consts.STATUS_PASSED @@ -56,16 +55,16 @@ def test_open_data_endpoints(telemetry_json): event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK )) - AggregateFinding.create_or_add_to_existing( + MonkeyFindingService.create_or_add_to_existing( test=zero_trust_consts.TEST_DATA_ENDPOINT_HTTP, status=found_http_server_status, events=events ) - AggregateFinding.create_or_add_to_existing( + MonkeyFindingService.create_or_add_to_existing( test=zero_trust_consts.TEST_DATA_ENDPOINT_ELASTIC, status=found_elastic_search_server, events=events ) - add_malicious_activity_to_timeline(events) + MonkeyFindingService.add_malicious_activity_to_timeline(events) diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/known_anti_viruses.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/known_anti_viruses.py similarity index 100% rename from monkey/monkey_island/cc/services/telemetry/zero_trust_tests/known_anti_viruses.py rename to monkey/monkey_island/cc/services/telemetry/zero_trust_checks/known_anti_viruses.py diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/machine_exploited.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/machine_exploited.py similarity index 78% rename from monkey/monkey_island/cc/services/telemetry/zero_trust_tests/machine_exploited.py rename to monkey/monkey_island/cc/services/telemetry/zero_trust_checks/machine_exploited.py index 51109130a..941bc4643 100644 --- a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/machine_exploited.py +++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/machine_exploited.py @@ -1,10 +1,9 @@ import common.common_consts.zero_trust_consts as zero_trust_consts -from monkey_island.cc.models.zero_trust.aggregate_finding import ( - AggregateFinding, add_malicious_activity_to_timeline) from monkey_island.cc.models.zero_trust.event import Event +from monkey_island.cc.services.zero_trust.monkey_finding_service import MonkeyFindingService -def test_machine_exploited(current_monkey, exploit_successful, exploiter, target_ip, timestamp): +def check_machine_exploited(current_monkey, exploit_successful, exploiter, target_ip, timestamp): events = [ Event.create_event( title="Exploit attempt", @@ -30,10 +29,10 @@ def test_machine_exploited(current_monkey, exploit_successful, exploiter, target ) status = zero_trust_consts.STATUS_FAILED - AggregateFinding.create_or_add_to_existing( + MonkeyFindingService.create_or_add_to_existing( test=zero_trust_consts.TEST_MACHINE_EXPLOITED, status=status, events=events ) - add_malicious_activity_to_timeline(events) + MonkeyFindingService.add_malicious_activity_to_timeline(events) diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/segmentation.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/segmentation.py similarity index 97% rename from monkey/monkey_island/cc/services/telemetry/zero_trust_tests/segmentation.py rename to monkey/monkey_island/cc/services/telemetry/zero_trust_checks/segmentation.py index 717f57896..2e40c0698 100644 --- a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/segmentation.py +++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/segmentation.py @@ -19,7 +19,7 @@ SEGMENTATION_VIOLATION_EVENT_TEXT = \ "managed to communicate cross segment to {target_ip} (in segment {target_seg})." -def test_segmentation_violation(current_monkey, target_ip): +def check_segmentation_violation(current_monkey, target_ip): # TODO - lower code duplication between this and report.py. subnet_groups = get_config_network_segments_as_subnet_groups() for subnet_group in subnet_groups: @@ -73,7 +73,7 @@ def get_segmentation_violation_event(current_monkey, source_subnet, target_ip, t ) -def test_passed_findings_for_unreached_segments(current_monkey): +def check_passed_findings_for_unreached_segments(current_monkey): flat_all_subnets = [item for sublist in get_config_network_segments_as_subnet_groups() for item in sublist] create_or_add_findings_for_all_pairs(flat_all_subnets, current_monkey) diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/test_segmentation_zt_tests.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/test_segmentation.py similarity index 94% rename from monkey/monkey_island/cc/services/telemetry/zero_trust_tests/test_segmentation_zt_tests.py rename to monkey/monkey_island/cc/services/telemetry/zero_trust_checks/test_segmentation.py index 937cc5baf..9139d05f6 100644 --- a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/test_segmentation_zt_tests.py +++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/test_segmentation.py @@ -6,7 +6,7 @@ from monkey_island.cc.models.zero_trust.event import Event from monkey_island.cc.models.zero_trust.finding import Finding from monkey_island.cc.models.zero_trust.segmentation_finding import \ SegmentationFinding -from monkey_island.cc.services.telemetry.zero_trust_tests.segmentation import \ +from monkey_island.cc.services.telemetry.zero_trust_checks.segmentation import \ create_or_add_findings_for_all_pairs from monkey_island.cc.testing.IslandTestCase import IslandTestCase @@ -15,7 +15,7 @@ SECOND_SUBNET = "2.2.2.0/24" THIRD_SUBNET = "3.3.3.3-3.3.3.200" -class TestSegmentationTests(IslandTestCase): +class TestSegmentationChecks(IslandTestCase): def test_create_findings_for_all_done_pairs(self): self.fail_if_not_testing_env() self.clean_finding_db() diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/tunneling.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/tunneling.py similarity index 78% rename from monkey/monkey_island/cc/services/telemetry/zero_trust_tests/tunneling.py rename to monkey/monkey_island/cc/services/telemetry/zero_trust_checks/tunneling.py index 341acbf5a..dd46ec1eb 100644 --- a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/tunneling.py +++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/tunneling.py @@ -1,13 +1,12 @@ import common.common_consts.zero_trust_consts as zero_trust_consts from monkey_island.cc.models import Monkey -from monkey_island.cc.models.zero_trust.aggregate_finding import ( - AggregateFinding, add_malicious_activity_to_timeline) from monkey_island.cc.models.zero_trust.event import Event from monkey_island.cc.services.telemetry.processing.utils import \ get_tunnel_host_ip_from_proxy_field +from monkey_island.cc.services.zero_trust.monkey_finding_service import MonkeyFindingService -def test_tunneling_violation(tunnel_telemetry_json): +def check_tunneling_violation(tunnel_telemetry_json): if tunnel_telemetry_json['data']['proxy'] is not None: # Monkey is tunneling, create findings tunnel_host_ip = get_tunnel_host_ip_from_proxy_field(tunnel_telemetry_json) @@ -20,10 +19,10 @@ def test_tunneling_violation(tunnel_telemetry_json): timestamp=tunnel_telemetry_json['timestamp'] )] - AggregateFinding.create_or_add_to_existing( + MonkeyFindingService.create_or_add_to_existing( test=zero_trust_consts.TEST_TUNNELING, status=zero_trust_consts.STATUS_FAILED, events=tunneling_events ) - add_malicious_activity_to_timeline(tunneling_events) + MonkeyFindingService.add_malicious_activity_to_timeline(tunneling_events) From d9ba4dd3a42cffb520100f0ab8ad826a06b83151 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 8 Sep 2020 14:08:36 +0300 Subject: [PATCH 016/152] Small modifications: bug in ZT report resource and unused imports removed --- monkey/monkey_island/cc/resources/reporting/report.py | 3 ++- .../cc/services/zero_trust/zero_trust_service.py | 4 ---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/monkey/monkey_island/cc/resources/reporting/report.py b/monkey/monkey_island/cc/resources/reporting/report.py index f1b95607f..546f944a3 100644 --- a/monkey/monkey_island/cc/resources/reporting/report.py +++ b/monkey/monkey_island/cc/resources/reporting/report.py @@ -5,6 +5,7 @@ from flask import jsonify from monkey_island.cc.resources.auth.auth import jwt_required from monkey_island.cc.services.reporting.report import ReportService +from monkey_island.cc.services.zero_trust.monkey_finding_service import MonkeyFindingService from monkey_island.cc.services.zero_trust.zero_trust_service import \ ZeroTrustService @@ -35,6 +36,6 @@ class Report(flask_restful.Resource): elif report_data == REPORT_DATA_PRINCIPLES_STATUS: return jsonify(ZeroTrustService.get_principles_status()) elif report_data == REPORT_DATA_FINDINGS: - return jsonify(ZeroTrustService.get_all_monkey_findings()) + return jsonify(MonkeyFindingService.get_all_monkey_findings()) flask_restful.abort(http.client.NOT_FOUND) diff --git a/monkey/monkey_island/cc/services/zero_trust/zero_trust_service.py b/monkey/monkey_island/cc/services/zero_trust/zero_trust_service.py index 613132a54..75af0da0a 100644 --- a/monkey/monkey_island/cc/services/zero_trust/zero_trust_service.py +++ b/monkey/monkey_island/cc/services/zero_trust/zero_trust_service.py @@ -1,7 +1,3 @@ -from typing import List - -from bson.objectid import ObjectId - import common.common_consts.zero_trust_consts as zero_trust_consts from monkey_island.cc.models.zero_trust.finding import Finding From 96f3052dc2e85334bc21d9dac920e9e738066c28 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Fri, 11 Sep 2020 16:12:18 +0300 Subject: [PATCH 017/152] Bugfix: imports, related to "common_consts renaming" fixed. --- monkey/infection_monkey/post_breach/actions/timestomping.py | 2 +- .../monkey_island/cc/services/attack/technique_reports/T1099.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/monkey/infection_monkey/post_breach/actions/timestomping.py b/monkey/infection_monkey/post_breach/actions/timestomping.py index 50a940524..b1de27245 100644 --- a/monkey/infection_monkey/post_breach/actions/timestomping.py +++ b/monkey/infection_monkey/post_breach/actions/timestomping.py @@ -1,4 +1,4 @@ -from common.data.post_breach_consts import POST_BREACH_TIMESTOMPING +from common.common_consts.post_breach_consts import POST_BREACH_TIMESTOMPING from infection_monkey.post_breach.pba import PBA from infection_monkey.post_breach.timestomping.timestomping import \ get_timestomping_commands diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1099.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1099.py index 9cd4dc903..9e96a5b2a 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1099.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1099.py @@ -1,4 +1,4 @@ -from common.data.post_breach_consts import POST_BREACH_TIMESTOMPING +from common.common_consts.post_breach_consts import POST_BREACH_TIMESTOMPING from monkey_island.cc.services.attack.technique_reports.pba_technique import \ PostBreachTechnique From 5a6a68fde057d24f64864616872770a1d6490867 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Fri, 18 Sep 2020 09:28:31 +0300 Subject: [PATCH 018/152] Changed default flask json encoder so we could encode objects with custom fields, like field of type ObjectId --- monkey/monkey_island/cc/app.py | 3 +++ monkey/monkey_island/cc/custom_json_encoder.py | 13 +++++++++++++ 2 files changed, 16 insertions(+) create mode 100644 monkey/monkey_island/cc/custom_json_encoder.py diff --git a/monkey/monkey_island/cc/app.py b/monkey/monkey_island/cc/app.py index 4bbf159c8..8ed7189f3 100644 --- a/monkey/monkey_island/cc/app.py +++ b/monkey/monkey_island/cc/app.py @@ -8,6 +8,7 @@ from werkzeug.exceptions import NotFound import monkey_island.cc.environment.environment_singleton as env_singleton from common.common_consts.api_url_consts import T1216_PBA_FILE_DOWNLOAD_PATH from monkey_island.cc.consts import MONKEY_ISLAND_ABS_PATH +from monkey_island.cc.custom_json_encoder import CustomJSONEncoder from monkey_island.cc.database import database, mongo from monkey_island.cc.resources.attack.attack_config import AttackConfiguration from monkey_island.cc.resources.attack.attack_report import AttackReport @@ -85,6 +86,8 @@ def init_app_config(app, mongo_url): # configuration. See https://flask.palletsprojects.com/en/1.1.x/config/#JSON_SORT_KEYS. app.config['JSON_SORT_KEYS'] = False + app.json_encoder = CustomJSONEncoder + def init_app_services(app): init_jwt(app) diff --git a/monkey/monkey_island/cc/custom_json_encoder.py b/monkey/monkey_island/cc/custom_json_encoder.py new file mode 100644 index 000000000..8c945a224 --- /dev/null +++ b/monkey/monkey_island/cc/custom_json_encoder.py @@ -0,0 +1,13 @@ +from bson import ObjectId +from flask.json import JSONEncoder + + +class CustomJSONEncoder(JSONEncoder): + + def default(self, obj): + try: + if isinstance(obj, ObjectId): + return obj.__str__() + except TypeError: + pass + return JSONEncoder.default(self, obj) From 0b9b89f63968feadd025e17d8b967b733f47bca1 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Fri, 18 Sep 2020 10:01:14 +0300 Subject: [PATCH 019/152] Added rule path creators, which helps to extract scoutsuite rules from scoutsuite report data --- monkey/common/utils/exceptions.py | 4 +++ .../zero_trust/scoutsuite/consts/ec2_rules.py | 22 +++++++++++++++ .../scoutsuite/data_parsing/rule_parsing.py | 28 +++++++++++++++++++ .../abstract_rule_path_creator.py | 23 +++++++++++++++ .../ec2_rule_path_creator.py | 11 ++++++++ .../rule_path_creators_list.py | 4 +++ 6 files changed, 92 insertions(+) create mode 100644 monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/ec2_rules.py create mode 100644 monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_parsing.py create mode 100644 monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/abstract_rule_path_creator.py create mode 100644 monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/ec2_rule_path_creator.py create mode 100644 monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators_list.py diff --git a/monkey/common/utils/exceptions.py b/monkey/common/utils/exceptions.py index fa026933c..5103b297e 100644 --- a/monkey/common/utils/exceptions.py +++ b/monkey/common/utils/exceptions.py @@ -20,3 +20,7 @@ class CredentialsNotRequiredError(RegistrationNotNeededError): class AlreadyRegisteredError(RegistrationNotNeededError): """ Raise to indicate the reason why registration is not required """ + + +class RulePathCreatorNotFound(Exception): + """ Raise to indicate that ScoutSuite rule doesn't have a path creator""" diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/ec2_rules.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/ec2_rules.py new file mode 100644 index 000000000..421dbca41 --- /dev/null +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/ec2_rules.py @@ -0,0 +1,22 @@ +from enum import Enum + + +class EC2Rules(Enum): + SECURITY_GROUP_ALL_PORTS_TO_ALL = 'ec2-security-group-opens-all-ports-to-all' + SECURITY_GROUP_OPENS_TCP_PORT_TO_ALL = 'ec2-security-group-opens-TCP-port-to-all' + SECURITY_GROUP_OPENS_UDP_PORT_TO_ALL = 'ec2-security-group-opens-UDP-port-to-all' + SECURITY_GROUP_OPENS_RDP_PORT_TO_ALL = 'ec2-security-group-opens-RDP-port-to-all' + SECURITY_GROUP_OPENS_SSH_PORT_TO_ALL = 'ec2-security-group-opens-SSH-port-to-all' + SECURITY_GROUP_OPENS_MYSQL_PORT_TO_ALL = 'ec2-security-group-opens-MySQL-port-to-all' + SECURITY_GROUP_OPENS_MSSQL_PORT_TO_ALL = 'ec2-security-group-opens-MsSQL-port-to-all' + SECURITY_GROUP_OPENS_MONGODB_PORT_TO_ALL = 'ec2-security-group-opens-MongoDB-port-to-all' + SECURITY_GROUP_OPENS_ORACLE_DB_PORT_TO_ALL = 'ec2-security-group-opens-Oracle DB-port-to-all' + SECURITY_GROUP_OPENS_POSTGRESQL_PORT_TO_ALL = 'ec2-security-group-opens-PostgreSQL-port-to-all' + SECURITY_GROUP_OPENS_NFS_PORT_TO_ALL = 'ec2-security-group-opens-NFS-port-to-all' + SECURITY_GROUP_OPENS_SMTP_PORT_TO_ALL = 'ec2-security-group-opens-SMTP-port-to-all' + SECURITY_GROUP_OPENS_DNS_PORT_TO_ALL = 'ec2-security-group-opens-DNS-port-to-all' + SECURITY_GROUP_OPENS_ALL_PORTS_TO_SELF = 'ec2-security-group-opens-all-ports-to-self' + SECURITY_GROUP_OPENS_ALL_PORTS = 'ec2-security-group-opens-all-ports' + SECURITY_GROUP_OPENS_PLAINTEXT_PORT_FTP = 'ec2-security-group-opens-plaintext-port-FTP' + SECURITY_GROUP_OPENS_PLAINTEXT_PORT_TELNET = 'ec2-security-group-opens-plaintext-port-Telnet' + SECURITY_GROUP_OPENS_PORT_RANGE = 'ec2-security-group-opens-port-range' diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_parsing.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_parsing.py new file mode 100644 index 000000000..3a9f9b58b --- /dev/null +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_parsing.py @@ -0,0 +1,28 @@ +from typing import Union + +from common.utils.code_utils import get_object_value_by_path +from common.utils.exceptions import RulePathCreatorNotFound +from monkey_island.cc.services.zero_trust.scoutsuite.consts.ec2_rules import EC2Rules +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators_list import \ + RULE_PATH_CREATORS_LIST + + +class RuleParser: + + @staticmethod + def get_rule_data(scoutsuite_data, rule_name: Union[EC2Rules]): + rule_path = RuleParser.get_rule_path(rule_name) + return get_object_value_by_path(scoutsuite_data, rule_path) + + @staticmethod + def get_rule_path(rule_name: Union[EC2Rules]): + creator = RuleParser.get_rule_path_creator(rule_name) + return creator.build_rule_path(rule_name) + + @staticmethod + def get_rule_path_creator(rule_name: Union[EC2Rules]): + for rule_path_creator in RULE_PATH_CREATORS_LIST: + if rule_name in rule_path_creator.supported_rules: + return rule_path_creator + raise RulePathCreatorNotFound(f"Rule path creator not found for rule {rule_name.value}. Make sure to assign" + f"this rule to any rule path creators.") diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/abstract_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/abstract_rule_path_creator.py new file mode 100644 index 000000000..f7113fc50 --- /dev/null +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/abstract_rule_path_creator.py @@ -0,0 +1,23 @@ +from abc import ABC, abstractmethod +from typing import List, Union + +from monkey_island.cc.services.zero_trust.scoutsuite.consts.ec2_rules import EC2Rules +from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICES, FINDINGS, SERVICE_TYPES + + +class AbstractRulePathCreator(ABC): + + @property + @abstractmethod + def service_type(self) -> SERVICE_TYPES: + pass + + @property + @abstractmethod + def supported_rules(self) -> List[Union[EC2Rules]]: + pass + + @classmethod + def build_rule_path(cls, rule_name: Union[EC2Rules]) -> List[str]: + assert(rule_name in cls.supported_rules) + return [SERVICES, cls.service_type.value, FINDINGS, rule_name.value] diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/ec2_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/ec2_rule_path_creator.py new file mode 100644 index 000000000..4c13325bc --- /dev/null +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/ec2_rule_path_creator.py @@ -0,0 +1,11 @@ +from monkey_island.cc.services.zero_trust.scoutsuite.consts.ec2_rules import EC2Rules +from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import \ + SERVICE_TYPES +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import \ + AbstractRulePathCreator + + +class EC2RulePathCreator(AbstractRulePathCreator): + + service_type = SERVICE_TYPES.EC2 + supported_rules = EC2Rules diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators_list.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators_list.py new file mode 100644 index 000000000..6c4ff21df --- /dev/null +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators_list.py @@ -0,0 +1,4 @@ +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.ec2_rule_path_creator import \ + EC2RulePathCreator + +RULE_PATH_CREATORS_LIST = [EC2RulePathCreator] From 4440027699eaf6f7ad99e86069b2ec0b1f675172 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Fri, 18 Sep 2020 10:13:27 +0300 Subject: [PATCH 020/152] Backend ScoutSuite backend code, which handles ScoutSuite data reception, parsing and storing --- .../common/common_consts/zero_trust_consts.py | 19 +++++- monkey/common/utils/code_utils.py | 17 +++++ .../cc/models/zero_trust/finding.py | 30 +++++---- .../models/zero_trust/scoutsuite_data_json.py | 19 ++++++ .../models/zero_trust/scoutsuite_finding.py | 17 ----- .../zero_trust/scoutsuite_finding_details.py | 16 ++--- .../cc/models/zero_trust/scoutsuite_rule.py | 26 ++++++++ .../cc/resources/reporting/report.py | 14 +++- .../telemetry/processing/scoutsuite.py | 21 +++++- .../cc/services/zero_trust/events_service.py | 8 ++- .../cc/services/zero_trust/finding_service.py | 14 +++- .../zero_trust/monkey_finding_service.py | 13 +--- .../zero_trust/scoutsuite/consts/__init__.py | 0 .../zero_trust/scoutsuite/consts/findings.py | 19 ++++++ .../scoutsuite/consts/findings_list.py | 3 + .../scoutsuite/consts/rule_consts.py | 4 ++ .../scoutsuite/consts/service_consts.py | 9 +++ .../scoutsuite/scoutsuite_finding_service.py | 65 +++++++++++++++++++ .../scoutsuite/scoutsuite_rule_service.py | 30 +++++++++ .../zero_trust/scoutsuite_finding_service.py | 3 - 20 files changed, 286 insertions(+), 61 deletions(-) create mode 100644 monkey/monkey_island/cc/models/zero_trust/scoutsuite_data_json.py delete mode 100644 monkey/monkey_island/cc/models/zero_trust/scoutsuite_finding.py create mode 100644 monkey/monkey_island/cc/models/zero_trust/scoutsuite_rule.py create mode 100644 monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/__init__.py create mode 100644 monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/findings.py create mode 100644 monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/findings_list.py create mode 100644 monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_consts.py create mode 100644 monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/service_consts.py create mode 100644 monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_finding_service.py create mode 100644 monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_rule_service.py delete mode 100644 monkey/monkey_island/cc/services/zero_trust/scoutsuite_finding_service.py diff --git a/monkey/common/common_consts/zero_trust_consts.py b/monkey/common/common_consts/zero_trust_consts.py index 8d55bc320..edc40d0f2 100644 --- a/monkey/common/common_consts/zero_trust_consts.py +++ b/monkey/common/common_consts/zero_trust_consts.py @@ -22,6 +22,11 @@ STATUS_FAILED = "Failed" # Don't change order! The statuses are ordered by importance/severity. ORDERED_TEST_STATUSES = [STATUS_FAILED, STATUS_VERIFY, STATUS_PASSED, STATUS_UNEXECUTED] +MONKEY_FINDING = "monkey_finding" +SCOUTSUITE_FINDING = "scoutsuite_finding" +FINDING_TYPES = [MONKEY_FINDING, SCOUTSUITE_FINDING] + + TEST_DATA_ENDPOINT_ELASTIC = "unencrypted_data_endpoint_elastic" TEST_DATA_ENDPOINT_HTTP = "unencrypted_data_endpoint_http" TEST_MACHINE_EXPLOITED = "machine_exploited" @@ -31,6 +36,7 @@ TEST_MALICIOUS_ACTIVITY_TIMELINE = "malicious_activity_timeline" TEST_SEGMENTATION = "segmentation" TEST_TUNNELING = "tunneling" TEST_COMMUNICATE_AS_NEW_USER = "communicate_as_new_user" +TEST_SCOUTSUITE_PERMISSIVE_FIREWALL_RULES = "scoutsuite_permissive_firewall_rules" TESTS = ( TEST_SEGMENTATION, TEST_MALICIOUS_ACTIVITY_TIMELINE, @@ -40,7 +46,8 @@ TESTS = ( TEST_DATA_ENDPOINT_HTTP, TEST_DATA_ENDPOINT_ELASTIC, TEST_TUNNELING, - TEST_COMMUNICATE_AS_NEW_USER + TEST_COMMUNICATE_AS_NEW_USER, + TEST_SCOUTSUITE_PERMISSIVE_FIREWALL_RULES ) PRINCIPLE_DATA_TRANSIT = "data_transit" @@ -165,6 +172,16 @@ TESTS_MAP = { PILLARS_KEY: [PEOPLE, NETWORKS, VISIBILITY_ANALYTICS], POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED] }, + TEST_SCOUTSUITE_PERMISSIVE_FIREWALL_RULES: { + TEST_EXPLANATION_KEY: "ScoutSuite assessed cloud firewall rules and settings.", + FINDING_EXPLANATION_BY_STATUS_KEY: { + STATUS_FAILED: "ScoutSuite found overly permissive firewall rules.", + STATUS_PASSED: "ScoutSuite found no problems with cloud firewall rules." + }, + PRINCIPLE_KEY: PRINCIPLE_RESTRICTIVE_NETWORK_POLICIES, + PILLARS_KEY: [NETWORKS], + POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED] + }, } EVENT_TYPE_MONKEY_NETWORK = "monkey_network" diff --git a/monkey/common/utils/code_utils.py b/monkey/common/utils/code_utils.py index 214e6d108..32f972a76 100644 --- a/monkey/common/utils/code_utils.py +++ b/monkey/common/utils/code_utils.py @@ -1,5 +1,10 @@ # abstract, static method decorator # noinspection PyPep8Naming +import operator +from functools import reduce +from typing import List + + class abstractstatic(staticmethod): __slots__ = () @@ -8,3 +13,15 @@ class abstractstatic(staticmethod): function.__isabstractmethod__ = True __isabstractmethod__ = True + + +def _get_value_by_path(data, path: List[str]): + return reduce(operator.getitem, path, data) + + +def get_object_value_by_path(data_object: object, path: List[str]): + return _get_value_by_path(data_object, path) + + +def get_dict_value_by_path(data_dict: dict, path: List[str]): + return _get_value_by_path(data_dict, path) diff --git a/monkey/monkey_island/cc/models/zero_trust/finding.py b/monkey/monkey_island/cc/models/zero_trust/finding.py index 8895a7fdb..6d0827e39 100644 --- a/monkey/monkey_island/cc/models/zero_trust/finding.py +++ b/monkey/monkey_island/cc/models/zero_trust/finding.py @@ -2,16 +2,15 @@ """ Define a Document Schema for Zero Trust findings. """ -from typing import List +from typing import Union from mongoengine import Document, StringField, GenericLazyReferenceField import common.common_consts.zero_trust_consts as zero_trust_consts # Dummy import for mongoengine. # noinspection PyUnresolvedReferences -from monkey_island.cc.models.zero_trust.event import Event from monkey_island.cc.models.zero_trust.monkey_finding_details import MonkeyFindingDetails -from monkey_island.cc.models.zero_trust.scoutsuite_finding_details import ScoutsuiteFindingDetails +from monkey_island.cc.models.zero_trust.scoutsuite_finding_details import ScoutSuiteFindingDetails class Finding(Document): @@ -35,7 +34,8 @@ class Finding(Document): # SCHEMA test = StringField(required=True, choices=zero_trust_consts.TESTS) status = StringField(required=True, choices=zero_trust_consts.ORDERED_TEST_STATUSES) - details = GenericLazyReferenceField(choices=[MonkeyFindingDetails, ScoutsuiteFindingDetails], required=True) + type = StringField(required=True, choices=zero_trust_consts.FINDING_TYPES) + details = GenericLazyReferenceField(choices=[MonkeyFindingDetails, ScoutSuiteFindingDetails], required=True) # http://docs.mongoengine.org/guide/defining-documents.html#document-inheritance meta = {'allow_inheritance': True} @@ -48,11 +48,19 @@ class Finding(Document): # Creation methods @staticmethod - def save_finding(test: str, status: str, detail_ref): - finding = Finding(test=test, - status=status, - details=detail_ref) + def save_finding(test: str, + status: str, + detail_ref: Union[MonkeyFindingDetails, ScoutSuiteFindingDetails]): + temp_finding = Finding(test=test, + status=status, + details=detail_ref, + type=Finding._get_finding_type_by_details(detail_ref)) + temp_finding.save() + return temp_finding - finding.save() - - return finding + @staticmethod + def _get_finding_type_by_details(details: Union[MonkeyFindingDetails, ScoutSuiteFindingDetails]) -> str: + if type(details) == MonkeyFindingDetails: + return zero_trust_consts.MONKEY_FINDING + else: + return zero_trust_consts.SCOUTSUITE_FINDING diff --git a/monkey/monkey_island/cc/models/zero_trust/scoutsuite_data_json.py b/monkey/monkey_island/cc/models/zero_trust/scoutsuite_data_json.py new file mode 100644 index 000000000..4b493e878 --- /dev/null +++ b/monkey/monkey_island/cc/models/zero_trust/scoutsuite_data_json.py @@ -0,0 +1,19 @@ +from mongoengine import Document, DynamicField + + +class ScoutSuiteDataJson(Document): + """ + This model is a container for ScoutSuite report data dump. + """ + + # SCHEMA + scoutsuite_data = DynamicField(required=True) + + # LOGIC + @staticmethod + def add_scoutsuite_data(scoutsuite_data: str) -> None: + current_data = ScoutSuiteDataJson.objects() + if not current_data: + current_data = ScoutSuiteDataJson() + current_data.scoutsuite_data = scoutsuite_data + current_data.save() diff --git a/monkey/monkey_island/cc/models/zero_trust/scoutsuite_finding.py b/monkey/monkey_island/cc/models/zero_trust/scoutsuite_finding.py deleted file mode 100644 index f8d0f5042..000000000 --- a/monkey/monkey_island/cc/models/zero_trust/scoutsuite_finding.py +++ /dev/null @@ -1,17 +0,0 @@ -from datetime import datetime - -from mongoengine import DateTimeField, EmbeddedDocument, StringField - - -class ScoutsuiteFinding(EmbeddedDocument): - # SCHEMA - temp = StringField(required=True) - - # LOGIC - @staticmethod - def create_scoutsuite_finding(title, message, event_type, timestamp=None): - scoutsuite_finding = ScoutsuiteFinding() - - scoutsuite_finding.temp = "temp" - - return scoutsuite_finding diff --git a/monkey/monkey_island/cc/models/zero_trust/scoutsuite_finding_details.py b/monkey/monkey_island/cc/models/zero_trust/scoutsuite_finding_details.py index ed05f6003..52aa09d17 100644 --- a/monkey/monkey_island/cc/models/zero_trust/scoutsuite_finding_details.py +++ b/monkey/monkey_island/cc/models/zero_trust/scoutsuite_finding_details.py @@ -1,11 +1,9 @@ -from typing import List +from mongoengine import Document, EmbeddedDocumentListField -from mongoengine import DateTimeField, Document, StringField, EmbeddedDocumentListField - -from monkey_island.cc.models.zero_trust.scoutsuite_finding import ScoutsuiteFinding +from monkey_island.cc.models.zero_trust.scoutsuite_rule import ScoutSuiteRule -class ScoutsuiteFindingDetails(Document): +class ScoutSuiteFindingDetails(Document): """ This model represents additional information about monkey finding: Events if monkey finding @@ -13,7 +11,9 @@ class ScoutsuiteFindingDetails(Document): """ # SCHEMA - scoutsuite_findings = EmbeddedDocumentListField(document_type=ScoutsuiteFinding, required=False) + scoutsuite_rules = EmbeddedDocumentListField(document_type=ScoutSuiteRule, required=False) - def add_scoutsuite_findings(self, scoutsuite_findings: List[ScoutsuiteFinding]) -> None: - self.update(push_all__scoutsuite_findings=scoutsuite_findings) + def add_rule(self, rule: ScoutSuiteRule) -> None: + if rule not in self.scoutsuite_rules: + self.scoutsuite_rules.append(rule) + self.save() diff --git a/monkey/monkey_island/cc/models/zero_trust/scoutsuite_rule.py b/monkey/monkey_island/cc/models/zero_trust/scoutsuite_rule.py new file mode 100644 index 000000000..316a5402e --- /dev/null +++ b/monkey/monkey_island/cc/models/zero_trust/scoutsuite_rule.py @@ -0,0 +1,26 @@ +from mongoengine import StringField, EmbeddedDocument, ListField, \ + IntField + +from monkey_island.cc.services.zero_trust.scoutsuite.consts import rule_consts + + +class ScoutSuiteRule(EmbeddedDocument): + """ + This model represents additional information about monkey finding: + Events if monkey finding + Scoutsuite findings if scoutsuite finding + """ + + # SCHEMA + description = StringField(required=True) + path = StringField(required=True) + level = StringField(required=True, options=rule_consts.RULE_LEVELS) + items = ListField() + dashboard_name = StringField(required=True) + checked_items = IntField(min_value=0) + flagged_items = IntField(min_value=0) + service = StringField(required=True) + rationale = StringField(required=True) + remediation = StringField(required=False) + compliance = StringField(required=False) + references = ListField(required=False) diff --git a/monkey/monkey_island/cc/resources/reporting/report.py b/monkey/monkey_island/cc/resources/reporting/report.py index 546f944a3..5273ca810 100644 --- a/monkey/monkey_island/cc/resources/reporting/report.py +++ b/monkey/monkey_island/cc/resources/reporting/report.py @@ -1,11 +1,12 @@ import http.client import flask_restful -from flask import jsonify +from flask import jsonify, Response +from monkey_island.cc.models.zero_trust.scoutsuite_data_json import ScoutSuiteDataJson from monkey_island.cc.resources.auth.auth import jwt_required from monkey_island.cc.services.reporting.report import ReportService -from monkey_island.cc.services.zero_trust.monkey_finding_service import MonkeyFindingService +from monkey_island.cc.services.zero_trust.finding_service import FindingService from monkey_island.cc.services.zero_trust.zero_trust_service import \ ZeroTrustService @@ -16,6 +17,7 @@ REPORT_TYPES = [SECURITY_REPORT_TYPE, ZERO_TRUST_REPORT_TYPE] REPORT_DATA_PILLARS = "pillars" REPORT_DATA_FINDINGS = "findings" REPORT_DATA_PRINCIPLES_STATUS = "principles" +REPORT_DATA_SCOUTSUITE = "scoutsuite" __author__ = ["itay.mizeretz", "shay.nehmad"] @@ -36,6 +38,12 @@ class Report(flask_restful.Resource): elif report_data == REPORT_DATA_PRINCIPLES_STATUS: return jsonify(ZeroTrustService.get_principles_status()) elif report_data == REPORT_DATA_FINDINGS: - return jsonify(MonkeyFindingService.get_all_monkey_findings()) + return jsonify(FindingService.get_all_findings()) + elif report_data == REPORT_DATA_SCOUTSUITE: + try: + data = ScoutSuiteDataJson.objects.get().scoutsuite_data + except Exception: + data = {} + return Response(data, mimetype='application/json') flask_restful.abort(http.client.NOT_FOUND) diff --git a/monkey/monkey_island/cc/services/telemetry/processing/scoutsuite.py b/monkey/monkey_island/cc/services/telemetry/processing/scoutsuite.py index d0e25aebc..c29320535 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/scoutsuite.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/scoutsuite.py @@ -1,15 +1,32 @@ import json from monkey_island.cc.database import mongo +from monkey_island.cc.models.zero_trust.scoutsuite_data_json import ScoutSuiteDataJson + +from monkey_island.cc.services.zero_trust.scoutsuite.consts.findings_list import SCOUTSUITE_FINDINGS +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_parsing import RuleParser +from monkey_island.cc.services.zero_trust.scoutsuite.scoutsuite_finding_service import ScoutSuiteFindingService +from monkey_island.cc.services.zero_trust.scoutsuite.scoutsuite_rule_service import ScoutSuiteRuleService def process_scoutsuite_telemetry(telemetry_json): # Encode data to json, because mongo can't save it as document (invalid document keys) telemetry_json['data'] = json.dumps(telemetry_json['data']) + ScoutSuiteDataJson.add_scoutsuite_data(telemetry_json['data']) + scoutsuite_data = json.loads(telemetry_json['data'])['data'] + create_scoutsuite_findings(scoutsuite_data) update_data(telemetry_json) +def create_scoutsuite_findings(scoutsuite_data): + for finding in SCOUTSUITE_FINDINGS: + for rule in finding.rules: + rule_data = RuleParser.get_rule_data(scoutsuite_data, rule) + rule = ScoutSuiteRuleService.get_rule_from_rule_data(rule_data) + ScoutSuiteFindingService.process_rule(finding, rule) + + def update_data(telemetry_json): - mongo.db.scoutsuite.update( + mongo.db.scoutsuite.insert_one( {'guid': telemetry_json['monkey_guid']}, - {'$push': {'results': telemetry_json['data']}}) + {'results': telemetry_json['data']}) diff --git a/monkey/monkey_island/cc/services/zero_trust/events_service.py b/monkey/monkey_island/cc/services/zero_trust/events_service.py index 5cccee7f3..7f4f9e496 100644 --- a/monkey/monkey_island/cc/services/zero_trust/events_service.py +++ b/monkey/monkey_island/cc/services/zero_trust/events_service.py @@ -18,9 +18,11 @@ class EventsService: 'latest_events': {'$slice': ['$events', -1 * EVENT_FETCH_CNT]}, 'event_count': {'$size': '$events'}}}, {'$unset': ['events']}] - details = MonkeyFindingDetails.objects.aggregate(*pipeline).next() - details['latest_events'] = EventsService._get_events_without_overlap(details['event_count'], - details['latest_events']) + details = list(MonkeyFindingDetails.objects.aggregate(*pipeline)) + if details: + details = details[0] + details['latest_events'] = EventsService._get_events_without_overlap(details['event_count'], + details['latest_events']) return details @staticmethod diff --git a/monkey/monkey_island/cc/services/zero_trust/finding_service.py b/monkey/monkey_island/cc/services/zero_trust/finding_service.py index 2feb02cce..f6ab4b39a 100644 --- a/monkey/monkey_island/cc/services/zero_trust/finding_service.py +++ b/monkey/monkey_island/cc/services/zero_trust/finding_service.py @@ -2,13 +2,24 @@ from typing import List from common.common_consts import zero_trust_consts from monkey_island.cc.models.zero_trust.finding import Finding +from monkey_island.cc.services.zero_trust.events_service import EventsService class FindingService: @staticmethod def get_all_findings() -> List[Finding]: - return list(Finding.objects) + findings = list(Finding.objects) + details = [] + for i in range(len(findings)): + if findings[i].type == zero_trust_consts.MONKEY_FINDING: + details = EventsService.fetch_events_for_display(findings[i].details.id) + elif findings[i].type == zero_trust_consts.SCOUTSUITE_FINDING: + details = findings[i].details.fetch().to_mongo() + findings[i] = findings[i].to_mongo() + findings[i] = FindingService.get_enriched_finding(findings[i]) + findings[i]['details'] = details + return findings @staticmethod def get_enriched_finding(finding): @@ -19,5 +30,6 @@ class FindingService: 'test_key': finding['test'], 'pillars': test_info[zero_trust_consts.PILLARS_KEY], 'status': finding['status'], + 'type': finding['type'] } return enriched_finding diff --git a/monkey/monkey_island/cc/services/zero_trust/monkey_finding_service.py b/monkey/monkey_island/cc/services/zero_trust/monkey_finding_service.py index a1e731b14..1ee60b117 100644 --- a/monkey/monkey_island/cc/services/zero_trust/monkey_finding_service.py +++ b/monkey/monkey_island/cc/services/zero_trust/monkey_finding_service.py @@ -6,7 +6,6 @@ from common.common_consts import zero_trust_consts from monkey_island.cc.models.zero_trust.event import Event from monkey_island.cc.models.zero_trust.finding import Finding from monkey_island.cc.models.zero_trust.monkey_finding_details import MonkeyFindingDetails -from monkey_island.cc.services.zero_trust.finding_service import FindingService class MonkeyFindingService: @@ -38,17 +37,7 @@ class MonkeyFindingService: @staticmethod def add_events(finding: Finding, events: List[Event]): - finding.details.fetch().add_events(events) - - @staticmethod - def get_all_monkey_findings(): - findings = FindingService.get_all_findings() - for i in range(len(findings)): - details = MonkeyFindingService.fetch_events_for_display(findings[i].details.id) - findings[i] = findings[i].to_mongo() - findings[i] = FindingService.get_enriched_finding(findings[i]) - findings[i]['details'] = details - return findings + finding.details.fetch().add_events(events).save() @staticmethod def get_events_by_finding(finding_id: str) -> List[object]: diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/__init__.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/findings.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/findings.py new file mode 100644 index 000000000..792c92e80 --- /dev/null +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/findings.py @@ -0,0 +1,19 @@ +from common.common_consts import zero_trust_consts +from common.common_consts.zero_trust_consts import NETWORKS +from monkey_island.cc.services.zero_trust.scoutsuite.consts.ec2_rules import EC2Rules + + +class PERMISSIVE_FIREWALL_RULES: + rules = [EC2Rules.SECURITY_GROUP_ALL_PORTS_TO_ALL, EC2Rules.SECURITY_GROUP_OPENS_TCP_PORT_TO_ALL, + EC2Rules.SECURITY_GROUP_OPENS_UDP_PORT_TO_ALL, EC2Rules.SECURITY_GROUP_OPENS_RDP_PORT_TO_ALL, + EC2Rules.SECURITY_GROUP_OPENS_SSH_PORT_TO_ALL, EC2Rules.SECURITY_GROUP_OPENS_MYSQL_PORT_TO_ALL, + EC2Rules.SECURITY_GROUP_OPENS_MSSQL_PORT_TO_ALL, EC2Rules.SECURITY_GROUP_OPENS_MONGODB_PORT_TO_ALL, + EC2Rules.SECURITY_GROUP_OPENS_ORACLE_DB_PORT_TO_ALL, EC2Rules.SECURITY_GROUP_OPENS_POSTGRESQL_PORT_TO_ALL, + EC2Rules.SECURITY_GROUP_OPENS_NFS_PORT_TO_ALL, EC2Rules.SECURITY_GROUP_OPENS_SMTP_PORT_TO_ALL, + EC2Rules.SECURITY_GROUP_OPENS_DNS_PORT_TO_ALL, EC2Rules.SECURITY_GROUP_OPENS_ALL_PORTS_TO_SELF, + EC2Rules.SECURITY_GROUP_OPENS_ALL_PORTS, EC2Rules.SECURITY_GROUP_OPENS_PLAINTEXT_PORT_FTP, + EC2Rules.SECURITY_GROUP_OPENS_PLAINTEXT_PORT_TELNET, EC2Rules.SECURITY_GROUP_OPENS_PORT_RANGE] + + pillars = [NETWORKS] + + test = zero_trust_consts.TEST_SCOUTSUITE_PERMISSIVE_FIREWALL_RULES diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/findings_list.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/findings_list.py new file mode 100644 index 000000000..bf54ac8ce --- /dev/null +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/findings_list.py @@ -0,0 +1,3 @@ +from monkey_island.cc.services.zero_trust.scoutsuite.consts.findings import PERMISSIVE_FIREWALL_RULES + +SCOUTSUITE_FINDINGS = [PERMISSIVE_FIREWALL_RULES] diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_consts.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_consts.py new file mode 100644 index 000000000..732852174 --- /dev/null +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_consts.py @@ -0,0 +1,4 @@ +RULE_LEVEL_DANGER = 'danger' +RULE_LEVEL_WARNING = 'warning' + +RULE_LEVELS = (RULE_LEVEL_DANGER, RULE_LEVEL_WARNING) diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/service_consts.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/service_consts.py new file mode 100644 index 000000000..5c0338c26 --- /dev/null +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/service_consts.py @@ -0,0 +1,9 @@ +from enum import Enum + + +SERVICES = 'services' +FINDINGS = 'findings' + + +class SERVICE_TYPES(Enum): + EC2 = 'ec2' diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_finding_service.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_finding_service.py new file mode 100644 index 000000000..6f8e64e87 --- /dev/null +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_finding_service.py @@ -0,0 +1,65 @@ +from typing import List + +from common.common_consts import zero_trust_consts +from monkey_island.cc.models.zero_trust.finding import Finding +from monkey_island.cc.models.zero_trust.scoutsuite_finding_details import ScoutSuiteFindingDetails +from monkey_island.cc.models.zero_trust.scoutsuite_rule import ScoutSuiteRule +from monkey_island.cc.services.zero_trust.scoutsuite.scoutsuite_rule_service import ScoutSuiteRuleService + + +class ScoutSuiteFindingService: + + @staticmethod + # TODO add type hinting like finding: Union[SCOUTSUITE_FINDINGS]? + def process_rule(finding, rule: ScoutSuiteRule): + existing_findings = Finding.objects(test=finding.test, type=zero_trust_consts.SCOUTSUITE_FINDING) + assert (len(existing_findings) < 2), "More than one finding exists for {}".format(finding.test) + + if len(existing_findings) == 0: + ScoutSuiteFindingService.create_new_finding_from_rule(finding, rule) + else: + ScoutSuiteFindingService.add_rule(existing_findings[0], rule) + + @staticmethod + def create_new_finding_from_rule(finding, rule: ScoutSuiteRule): + details = ScoutSuiteFindingDetails() + details.scoutsuite_rules = [rule] + details.save() + status = ScoutSuiteFindingService.get_finding_status_from_rules(details.scoutsuite_rules) + Finding.save_finding(finding.test, status, details) + + @staticmethod + def get_finding_status_from_rules(rules: List[ScoutSuiteRule]) -> str: + if len(rules) == 0: + return zero_trust_consts.STATUS_UNEXECUTED + elif filter(lambda x: ScoutSuiteRuleService.is_rule_dangerous(x), rules): + return zero_trust_consts.STATUS_FAILED + elif filter(lambda x: ScoutSuiteRuleService.is_rule_warning(x), rules): + return zero_trust_consts.STATUS_VERIFY + else: + return zero_trust_consts.STATUS_PASSED + + @staticmethod + def add_rule(finding: Finding, rule: ScoutSuiteRule): + ScoutSuiteFindingService.change_finding_status_by_rule(finding, rule) + finding.save() + finding.details.fetch().add_rule(rule) + + @staticmethod + def change_finding_status_by_rule(finding: Finding, rule: ScoutSuiteRule): + rule_status = ScoutSuiteFindingService.get_finding_status_from_rules([rule]) + finding_status = finding.status + new_finding_status = ScoutSuiteFindingService.get_finding_status_from_rule_status(finding_status, rule_status) + if finding_status != new_finding_status: + finding.status = new_finding_status + + @staticmethod + def get_finding_status_from_rule_status(finding_status: str, rule_status: str) -> str: + if finding_status == zero_trust_consts.STATUS_FAILED or rule_status == zero_trust_consts.STATUS_FAILED: + return zero_trust_consts.STATUS_FAILED + elif finding_status == zero_trust_consts.STATUS_VERIFY or rule_status == zero_trust_consts.STATUS_VERIFY: + return zero_trust_consts.STATUS_VERIFY + elif finding_status == zero_trust_consts.STATUS_PASSED or rule_status == zero_trust_consts.STATUS_PASSED: + return zero_trust_consts.STATUS_PASSED + else: + return zero_trust_consts.STATUS_UNEXECUTED diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_rule_service.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_rule_service.py new file mode 100644 index 000000000..3b76194af --- /dev/null +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_rule_service.py @@ -0,0 +1,30 @@ +from monkey_island.cc.models.zero_trust.scoutsuite_rule import ScoutSuiteRule +from monkey_island.cc.services.zero_trust.scoutsuite.consts import rule_consts + + +class ScoutSuiteRuleService: + + @staticmethod + def get_rule_from_rule_data(rule_data: dict) -> ScoutSuiteRule: + rule = ScoutSuiteRule() + rule.description = rule_data['description'] + rule.path = rule_data['path'] + rule.level = rule_data['level'] + rule.items = rule_data['items'] + rule.dashboard_name = rule_data['dashboard_name'] + rule.checked_items = rule_data['checked_items'] + rule.flagged_items = rule_data['flagged_items'] + rule.service = rule_data['service'] + rule.rationale = rule_data['rationale'] + rule.remediation = rule_data['remediation'] + rule.compliance = rule_data['compliance'] + rule.references = rule_data['references'] + return rule + + @staticmethod + def is_rule_dangerous(rule: ScoutSuiteRule): + return rule.level == rule_consts.RULE_LEVEL_DANGER and len(rule.items) != 0 + + @staticmethod + def is_rule_warning(rule: ScoutSuiteRule): + return rule.level == rule_consts.RULE_LEVEL_WARNING and len(rule.items) != 0 diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite_finding_service.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite_finding_service.py deleted file mode 100644 index 12ab2743b..000000000 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite_finding_service.py +++ /dev/null @@ -1,3 +0,0 @@ - -class ScoutsuiteFindingService: - pass From c66cb11e79c14b89a73db49dc283feacbaa03792 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Fri, 18 Sep 2020 10:26:25 +0300 Subject: [PATCH 021/152] Added ScoutSuite UI code --- .../zero_trust/monkey_finding_details.py | 3 +- .../cc/ui/src/components/pages/ReportPage.js | 7 +- .../report-components/ZeroTrustReport.js | 9 +- .../consts/ScoutSuiteConsts/RuleLevels.js | 6 ++ .../common/consts/StatusConsts.js | 8 ++ .../zerotrust/FindingsSection.js | 12 ++- .../zerotrust/FindingsTable.js | 81 +++++++++-------- .../zerotrust/scoutsuite/ResourceDropdown.js | 75 ++++++++++++++++ .../zerotrust/scoutsuite/RuleDisplay.js | 56 ++++++++++++ .../scoutsuite/ScoutSuiteDataParser.js | 58 ++++++++++++ .../scoutsuite/ScoutSuiteRuleButton.js | 45 ++++++++++ .../scoutsuite/ScoutSuiteRuleModal.js | 58 ++++++++++++ .../ScoutSuiteSingleRuleDropdown.js | 90 +++++++++++++++++++ .../monkey_island/cc/ui/src/styles/Main.scss | 1 + .../cc/ui/src/styles/components/Collapse.scss | 9 ++ .../scoutsuite/ResourceDropdown.scss | 20 +++++ .../components/scoutsuite/RuleDisplay.scss | 21 +++++ .../components/scoutsuite/RuleModal.scss | 5 ++ 18 files changed, 519 insertions(+), 45 deletions(-) create mode 100644 monkey/monkey_island/cc/ui/src/components/report-components/common/consts/ScoutSuiteConsts/RuleLevels.js create mode 100644 monkey/monkey_island/cc/ui/src/components/report-components/common/consts/StatusConsts.js create mode 100644 monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/scoutsuite/ResourceDropdown.js create mode 100644 monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/scoutsuite/RuleDisplay.js create mode 100644 monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/scoutsuite/ScoutSuiteDataParser.js create mode 100644 monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/scoutsuite/ScoutSuiteRuleButton.js create mode 100644 monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/scoutsuite/ScoutSuiteRuleModal.js create mode 100644 monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/scoutsuite/ScoutSuiteSingleRuleDropdown.js create mode 100644 monkey/monkey_island/cc/ui/src/styles/components/scoutsuite/ResourceDropdown.scss create mode 100644 monkey/monkey_island/cc/ui/src/styles/components/scoutsuite/RuleDisplay.scss create mode 100644 monkey/monkey_island/cc/ui/src/styles/components/scoutsuite/RuleModal.scss diff --git a/monkey/monkey_island/cc/models/zero_trust/monkey_finding_details.py b/monkey/monkey_island/cc/models/zero_trust/monkey_finding_details.py index 029136679..bcd19f593 100644 --- a/monkey/monkey_island/cc/models/zero_trust/monkey_finding_details.py +++ b/monkey/monkey_island/cc/models/zero_trust/monkey_finding_details.py @@ -1,9 +1,10 @@ from typing import List -from mongoengine import DateTimeField, Document, StringField, EmbeddedDocumentListField +from mongoengine import Document, EmbeddedDocumentListField from monkey_island.cc.models.zero_trust.event import Event + class MonkeyFindingDetails(Document): """ This model represents additional information about monkey finding: diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js b/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js index cb30ba117..e0b458c8b 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js @@ -60,7 +60,7 @@ class ReportPageComponent extends AuthComponent { } getZeroTrustReportFromServer = async () => { - let ztReport = {findings: {}, principles: {}, pillars: {}}; + let ztReport = {findings: {}, principles: {}, pillars: {}, scoutsuite_data: {}}; await this.authFetch('/api/report/zero_trust/findings') .then(res => res.json()) .then(res => { @@ -76,6 +76,11 @@ class ReportPageComponent extends AuthComponent { .then(res => { ztReport.pillars = res; }); + await this.authFetch('/api/report/zero_trust/scoutsuite') + .then(res => res.json()) + .then(res => { + ztReport.scoutsuite_data = res; + }); return ztReport }; diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/ZeroTrustReport.js b/monkey/monkey_island/cc/ui/src/components/report-components/ZeroTrustReport.js index 772802c9d..b400b3418 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/ZeroTrustReport.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/ZeroTrustReport.js @@ -16,7 +16,7 @@ class ZeroTrustReportPageComponent extends AuthComponent { componentDidUpdate(prevProps) { if (this.props.report !== prevProps.report) { - this.setState(this.props.report) + this.setState(this.props.report) } } @@ -29,7 +29,9 @@ class ZeroTrustReportPageComponent extends AuthComponent { - + ; } @@ -57,7 +59,8 @@ class ZeroTrustReportPageComponent extends AuthComponent { stillLoadingDataFromServer() { return typeof this.state.findings === 'undefined' || typeof this.state.pillars === 'undefined' - || typeof this.state.principles === 'undefined'; + || typeof this.state.principles === 'undefined' + || typeof this.state.scoutsuite_data === 'undefined'; } diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/common/consts/ScoutSuiteConsts/RuleLevels.js b/monkey/monkey_island/cc/ui/src/components/report-components/common/consts/ScoutSuiteConsts/RuleLevels.js new file mode 100644 index 000000000..20b92a3eb --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/report-components/common/consts/ScoutSuiteConsts/RuleLevels.js @@ -0,0 +1,6 @@ +const RULE_LEVELS = { + LEVEL_WARNING: 'warning', + LEVEL_DANGER: 'danger' +} + +export default RULE_LEVELS diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/common/consts/StatusConsts.js b/monkey/monkey_island/cc/ui/src/components/report-components/common/consts/StatusConsts.js new file mode 100644 index 000000000..472b045ae --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/report-components/common/consts/StatusConsts.js @@ -0,0 +1,8 @@ +const STATUSES = { + STATUS_UNEXECUTED: 'Unexecuted', + STATUS_PASSED: 'Passed', + STATUS_VERIFY: 'Verify', + STATUS_FAILED: 'Failed' +} + +export default STATUSES diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/FindingsSection.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/FindingsSection.js index 5fa3a4a80..eb8231441 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/FindingsSection.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/FindingsSection.js @@ -32,9 +32,15 @@ class FindingsSection extends Component { insight as to what exactly happened during this test.

- - - + + + ); } diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/FindingsTable.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/FindingsTable.js index f83921dae..10e546767 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/FindingsTable.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/FindingsTable.js @@ -4,52 +4,59 @@ import PaginatedTable from '../common/PaginatedTable'; import * as PropTypes from 'prop-types'; import PillarLabel from './PillarLabel'; import EventsButton from './EventsButton'; +import ScoutSuiteRuleButton from './scoutsuite/ScoutSuiteRuleButton'; -const EVENTS_COLUMN_MAX_WIDTH = 170; +const EVENTS_COLUMN_MAX_WIDTH = 250; const PILLARS_COLUMN_MAX_WIDTH = 200; -const columns = [ - { - columns: [ - { - Header: 'Finding', accessor: 'test', - style: {'whiteSpace': 'unset'} // This enables word wrap - }, - - { - Header: 'Events', id: 'events', - accessor: x => { - return ; - }, - maxWidth: EVENTS_COLUMN_MAX_WIDTH - }, - - { - Header: 'Pillars', id: 'pillars', - accessor: x => { - const pillars = x.pillars; - const pillarLabels = pillars.map((pillar) => - - ); - return
{pillarLabels}
; - }, - maxWidth: PILLARS_COLUMN_MAX_WIDTH, - style: {'whiteSpace': 'unset'} - } - ] - } -]; export class FindingsTable extends Component { + columns = [ + { + columns: [ + { + Header: 'Finding', accessor: 'test', + style: {'whiteSpace': 'unset'} // This enables word wrap + }, + + { + Header: 'Details', id: 'details', + accessor: x => { + if (x.type === 'scoutsuite_finding') { + return ; + } else if (x.type === 'monkey_finding') { + return ; + } + }, + maxWidth: EVENTS_COLUMN_MAX_WIDTH + }, + + { + Header: 'Pillars', id: 'pillars', + accessor: x => { + const pillars = x.pillars; + const pillarLabels = pillars.map((pillar) => + + ); + return
{pillarLabels}
; + }, + maxWidth: PILLARS_COLUMN_MAX_WIDTH, + style: {'whiteSpace': 'unset'} + } + ] + } + ]; + render() { return

{ } tests' findings

- +
} } diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/scoutsuite/ResourceDropdown.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/scoutsuite/ResourceDropdown.js new file mode 100644 index 000000000..996f62590 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/scoutsuite/ResourceDropdown.js @@ -0,0 +1,75 @@ +import React, {useState} from 'react'; +import * as PropTypes from 'prop-types'; +import '../../../../styles/components/scoutsuite/RuleDisplay.scss' +import classNames from 'classnames'; +import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'; +import {faChevronDown} from '@fortawesome/free-solid-svg-icons/faChevronDown'; +import {faChevronUp} from '@fortawesome/free-solid-svg-icons/faChevronUp'; +import ScoutSuiteDataParser from './ScoutSuiteDataParser'; +import Collapse from '@kunukn/react-collapse'; +import {faArrowRight} from '@fortawesome/free-solid-svg-icons'; + +export default function ResourceDropdown(props) { + + const [isCollapseOpen, setIsCollapseOpen] = useState(false); + + function getResourceDropdown() { + return ( +
+ + +
+ ); + } + + function replacePathDotsWithArrows(resourcePath) { + let path_vars = resourcePath.split('.') + let display_path = [] + for(let i = 0; i < path_vars.length; i++){ + display_path.push(path_vars[i]) + if( i !== path_vars.length - 1) { + display_path.push() + } + } + return display_path; + } + + function prettyPrintJson(data) { + return JSON.stringify(data, null, 4); + } + + function getResourceDropdownContents() { + let parser = new ScoutSuiteDataParser(props.scoutsuite_data.data.services); + return ( +
+
+

Path:

+

{replacePathDotsWithArrows(props.resource_path)}

+
+
+

Value:

+
{prettyPrintJson(parser.getValueAt(props.resource_path))}
+
+
+ ); + } + + return getResourceDropdown(); +} + +ResourceDropdown.propTypes = { + resource_path: PropTypes.object, + scoutsuite_data: PropTypes.object +}; diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/scoutsuite/RuleDisplay.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/scoutsuite/RuleDisplay.js new file mode 100644 index 000000000..6078b3855 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/scoutsuite/RuleDisplay.js @@ -0,0 +1,56 @@ +import React from 'react'; +import * as PropTypes from 'prop-types'; +import '../../../../styles/components/scoutsuite/RuleDisplay.scss' +import ResourceDropdown from './ResourceDropdown'; + +export default function RuleDisplay(props) { + + return ( +
+
+

{props.rule.description}({props.rule.service})

+
+
+

{props.rule.rationale}

+
+
+

Resources checked:

+

{props.rule.checked_items}

+
+
+

Resources flagged:

+

{props.rule.flagged_items}

+
+ {props.rule.references.length !== 0 ? getReferences() : ''} + {props.rule.items.length !== 0 ? getResources() : ''} +
); + + function getReferences() { + let references = [] + props.rule.references.forEach(reference => { + references.push({reference}) + }) + return ( +
+

References:

+ {references} +
) + } + + function getResources() { + let resources = [] + props.rule.items.forEach(item => { + resources.push() + }) + return ( +
+

Resources:

+ {resources} +
) + } +} + +RuleDisplay.propTypes = { + rule: PropTypes.object, + scoutsuite_data: PropTypes.object +}; diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/scoutsuite/ScoutSuiteDataParser.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/scoutsuite/ScoutSuiteDataParser.js new file mode 100644 index 000000000..0129804f8 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/scoutsuite/ScoutSuiteDataParser.js @@ -0,0 +1,58 @@ +export default class ScoutSuiteDataParser { + constructor(runResults) { + this.runResults = runResults + } + + getValueAt(path) { + return this.getValueAtRecursive(path, this.runResults) + } + + getValueAtRecursive(path, source) { + let value = source; + let current_path = path; + let key; + // iterate over each path elements + while (current_path) { + // check if there are more elements to the path + if (current_path.indexOf('.') != -1) { + key = current_path.substr(0, current_path.indexOf('.')); + } + // last element + else { + key = current_path; + } + + try { + // path containing an ".id" + if (key == 'id') { + let v = []; + let w; + for (let k in value) { + // process recursively + w = this.getValueAtRecursive(k + current_path.substr(current_path.indexOf('.'), current_path.length), value); + v = v.concat( + Object.values(w) // get values from array, otherwise it will be an array of key/values + ); + } + return v; + } + // simple path, just return element in value + else { + value = value[key]; + } + } catch (err) { + console.log('Error: ' + err) + } + + // check if there are more elements to process + if (current_path.indexOf('.') != -1) { + current_path = current_path.substr(current_path.indexOf('.') + 1, current_path.length); + } + // otherwise we're done + else { + current_path = false; + } + } + return value; + } +} diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/scoutsuite/ScoutSuiteRuleButton.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/scoutsuite/ScoutSuiteRuleButton.js new file mode 100644 index 000000000..97c5f861a --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/scoutsuite/ScoutSuiteRuleButton.js @@ -0,0 +1,45 @@ +import React, {Component} from 'react'; +import {Badge, Button} from 'react-bootstrap'; +import * as PropTypes from 'prop-types'; + +import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'; +import {faList} from '@fortawesome/free-solid-svg-icons/faList'; +import ScoutSuiteRuleModal from './ScoutSuiteRuleModal'; + +export default class ScoutSuiteRuleButton extends Component { + constructor(props) { + super(props); + this.state = { + isModalOpen: false + } + } + + toggleModal = () => { + this.setState({isModalOpen: !this.state.isModalOpen}); + }; + + render() { + return ( + <> + +
+ +
+ ); + } + + createRuleCountBadge() { + const ruleCount = this.props.scoutsuite_rules.length > 9 ? '9+' : this.props.scoutsuite_rules.length; + return {ruleCount}; + } +} + +ScoutSuiteRuleButton.propTypes = { + scoutsuite_rules: PropTypes.array, + scoutsuite_data: PropTypes.object +}; diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/scoutsuite/ScoutSuiteRuleModal.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/scoutsuite/ScoutSuiteRuleModal.js new file mode 100644 index 000000000..92fd430da --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/scoutsuite/ScoutSuiteRuleModal.js @@ -0,0 +1,58 @@ +import React, {useState} from 'react'; +import {Modal} from 'react-bootstrap'; +import * as PropTypes from 'prop-types'; +import Pluralize from 'pluralize'; +import ScoutSuiteSingleRuleDropdown from './ScoutSuiteSingleRuleDropdown'; +import '../../../../styles/components/scoutsuite/RuleModal.scss'; + + +export default function ScoutSuiteRuleModal(props) { + const [openRuleId, setOpenRuleId] = useState(null) + + function toggleRuleDropdown(ruleId) { + if (openRuleId === ruleId) { + setOpenRuleId(null); + } else { + setOpenRuleId(ruleId); + } + } + + function renderRuleDropdowns() { + let dropdowns = []; + props.scoutsuite_rules.forEach(rule => { + let dropdown = ( toggleRuleDropdown(rule.description)} + rule={rule} + scoutsuite_data={props.scoutsuite_data}/>) + dropdowns.push(dropdown) + }); + return dropdowns; + } + + return ( +
+ props.hideCallback()} className={'scoutsuite-rule-modal'}> + +

+
ScoutSuite rules
+

+
+

+ There {Pluralize('is', props.scoutsuite_rules.length)} { +

{props.scoutsuite_rules.length}
+ } ScoutSuite {Pluralize('rule', props.scoutsuite_rules.length)} associated with finding. +

+ {renderRuleDropdowns()} +
+
+
+ ); + +} + +ScoutSuiteRuleModal.propTypes = { + isModalOpen: PropTypes.bool, + scoutsuite_rules: PropTypes.array, + scoutsuite_data: PropTypes.object, + hideCallback: PropTypes.func +}; diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/scoutsuite/ScoutSuiteSingleRuleDropdown.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/scoutsuite/ScoutSuiteSingleRuleDropdown.js new file mode 100644 index 000000000..1a931b154 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/scoutsuite/ScoutSuiteSingleRuleDropdown.js @@ -0,0 +1,90 @@ +import React from 'react'; +import Collapse from '@kunukn/react-collapse'; +import {FontAwesomeIcon} from '@fortawesome/react-fontawesome' +import {faChevronUp} from '@fortawesome/free-solid-svg-icons/faChevronUp' +import {faChevronDown} from '@fortawesome/free-solid-svg-icons/faChevronDown' + +import classNames from 'classnames'; +import * as PropTypes from 'prop-types'; +import RULE_LEVELS from '../../common/consts/ScoutSuiteConsts/RuleLevels'; +import STATUSES from '../../common/consts/StatusConsts'; +import {faCheckCircle, faCircle, faExclamationCircle} from '@fortawesome/free-solid-svg-icons'; +import RuleDisplay from './RuleDisplay'; + +export default function ScoutSuiteSingleRuleDropdown(props) { + + function getRuleCollapse() { + return ( +
+ + +
+ ); + } + + function getRuleIcon() { + let ruleStatus = getRuleStatus() + switch(ruleStatus) { + case STATUSES.STATUS_PASSED: + return faCheckCircle; + case STATUSES.STATUS_VERIFY: + return faExclamationCircle; + case STATUSES.STATUS_FAILED: + return faExclamationCircle; + case STATUSES.STATUS_UNEXECUTED: + return faCircle; + } + } + + function getDropdownClass(){ + let ruleStatus = getRuleStatus() + switch(ruleStatus) { + case STATUSES.STATUS_PASSED: + return "collapse-success"; + case STATUSES.STATUS_VERIFY: + return "collapse-warning"; + case STATUSES.STATUS_FAILED: + return "collapse-danger"; + case STATUSES.STATUS_UNEXECUTED: + return "collapse-default"; + } + } + + function getRuleStatus(){ + if(props.rule.checked_items === 0) { + return STATUSES.STATUS_UNEXECUTED + } else if (props.rule.items.length === 0) { + return STATUSES.STATUS_PASSED + } else if (props.rule.level === RULE_LEVELS.LEVEL_WARNING) { + return STATUSES.STATUS_VERIFY + } else { + return STATUSES.STATUS_FAILED + } + } + + function renderRule() { + return + } + + return getRuleCollapse(); +} + +ScoutSuiteSingleRuleDropdown.propTypes = { + isCollapseOpen: PropTypes.bool, + rule: PropTypes.object, + scoutsuite_data: PropTypes.object, + toggleCallback: PropTypes.func +}; diff --git a/monkey/monkey_island/cc/ui/src/styles/Main.scss b/monkey/monkey_island/cc/ui/src/styles/Main.scss index e26220d0d..16cb1cab8 100644 --- a/monkey/monkey_island/cc/ui/src/styles/Main.scss +++ b/monkey/monkey_island/cc/ui/src/styles/Main.scss @@ -12,6 +12,7 @@ @import 'components/PreviewPane'; @import 'components/AdvancedMultiSelect'; @import 'components/particle-component/ParticleBackground'; +@import 'components/scoutsuite/ResourceDropdown'; // Define custom elements after bootstrap import diff --git a/monkey/monkey_island/cc/ui/src/styles/components/Collapse.scss b/monkey/monkey_island/cc/ui/src/styles/components/Collapse.scss index 3e578d45c..369c93685 100644 --- a/monkey/monkey_island/cc/ui/src/styles/components/Collapse.scss +++ b/monkey/monkey_island/cc/ui/src/styles/components/Collapse.scss @@ -5,6 +5,7 @@ $disabled-color: #f2f2f2; $info-color: #ade3eb; $default-color: #8c8c8c; $warning-color: #ffe28d; +$success-color: #adf6a9; .collapse-item button { font-size: inherit; @@ -39,6 +40,10 @@ $warning-color: #ffe28d; } } +.collapse-success { + background-color: $success-color !important; +} + .collapse-danger { background-color: $danger-color !important; } @@ -99,3 +104,7 @@ $warning-color: #ffe28d; display: inline-block; min-width: 6em; } + +.rule-collapse svg{ + margin-right: 10px; +} diff --git a/monkey/monkey_island/cc/ui/src/styles/components/scoutsuite/ResourceDropdown.scss b/monkey/monkey_island/cc/ui/src/styles/components/scoutsuite/ResourceDropdown.scss new file mode 100644 index 000000000..a5be0a4ae --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/styles/components/scoutsuite/ResourceDropdown.scss @@ -0,0 +1,20 @@ +.resource-display { + margin-top: 10px; +} + +.resource-display .resource-value-json { + background-color: $gray-200; + padding: 4px; +} + +.resource-display .resource-path-contents svg { + margin-left: 5px; + margin-right: 5px; + width: 10px; +} + +.resource-display .resource-value-title, +.resource-display .resource-path-title { + font-weight: 500; + margin-bottom: 0; +} diff --git a/monkey/monkey_island/cc/ui/src/styles/components/scoutsuite/RuleDisplay.scss b/monkey/monkey_island/cc/ui/src/styles/components/scoutsuite/RuleDisplay.scss new file mode 100644 index 000000000..703e27370 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/styles/components/scoutsuite/RuleDisplay.scss @@ -0,0 +1,21 @@ +.scoutsuite-rule-display .description h3{ + font-size: 1.2em; + margin-top: 10px; +} + +.scoutsuite-rule-display p{ + display: inline-block; +} + +.scoutsuite-rule-display .checked-resources-title, +.scoutsuite-rule-display .flagged-resources-title, +.scoutsuite-rule-display .reference-list-title{ + font-weight: 500; + margin-right: 5px; + margin-bottom: 0; +} + +.scoutsuite-rule-display .reference-list a { + display: block; + margin-left: 10px; +} diff --git a/monkey/monkey_island/cc/ui/src/styles/components/scoutsuite/RuleModal.scss b/monkey/monkey_island/cc/ui/src/styles/components/scoutsuite/RuleModal.scss new file mode 100644 index 000000000..f201da303 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/styles/components/scoutsuite/RuleModal.scss @@ -0,0 +1,5 @@ +.scoutsuite-rule-modal .modal-dialog{ + max-width: 1000px; + top: 0; + padding: 30px; +} From b8f7064582c19d8af4ef7812e005f618410f27e3 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Fri, 18 Sep 2020 16:43:03 +0300 Subject: [PATCH 022/152] Fixed conflicting dependencies that require botocore >= 1.18.0 --- monkey/monkey_island/requirements.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/monkey/monkey_island/requirements.txt b/monkey/monkey_island/requirements.txt index 057ea6de2..2ab3b48a7 100644 --- a/monkey/monkey_island/requirements.txt +++ b/monkey/monkey_island/requirements.txt @@ -2,8 +2,8 @@ Flask-JWT-Extended==3.24.1 Flask-Pymongo>=2.3.0 Flask-Restful>=0.3.8 PyInstaller==3.6 -awscli>=1.18.131 -boto3>=1.14.54 +awscli==1.18.131 +boto3==1.14.54 botocore>=1.17.54,<1.18.0 cffi>=1.8,!=1.11.3 dpath>=2.0 @@ -26,4 +26,4 @@ virtualenv>=20.0.26 werkzeug>=1.0.1 wheel>=0.34.2 -pyjwt>=1.5.1 # not directly required, pinned by Snyk to avoid a vulnerability \ No newline at end of file +pyjwt>=1.5.1 # not directly required, pinned by Snyk to avoid a vulnerability From c3fde1898c0124c57841b15bcbc3799a27bf32b5 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Mon, 21 Sep 2020 11:05:20 +0300 Subject: [PATCH 023/152] Added ScoutSuite scan setup guide to run monkey page. --- .../pages/RunMonkeyPage/RunOptions.js | 11 +++- .../scoutsuite-setup/AWSSetup.js | 56 +++++++++++++++++++ .../scoutsuite-setup/CloudOptions.js | 31 ++++++++++ .../inline-selection/InlineSelection.js | 3 +- .../ui-components/inline-selection/utils.js | 16 ++++++ .../components/scoutsuite/AWSSetup.scss | 17 ++++++ 6 files changed, 132 insertions(+), 2 deletions(-) create mode 100644 monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/scoutsuite-setup/AWSSetup.js create mode 100644 monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/scoutsuite-setup/CloudOptions.js create mode 100644 monkey/monkey_island/cc/ui/src/components/ui-components/inline-selection/utils.js create mode 100644 monkey/monkey_island/cc/ui/src/styles/components/scoutsuite/AWSSetup.scss diff --git a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/RunOptions.js b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/RunOptions.js index a9fe1f8cf..fa724d6f6 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/RunOptions.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/RunOptions.js @@ -5,8 +5,10 @@ import AuthComponent from '../../AuthComponent'; import {faLaptopCode} from '@fortawesome/free-solid-svg-icons/faLaptopCode'; import InlineSelection from '../../ui-components/inline-selection/InlineSelection'; import {cloneDeep} from 'lodash'; -import {faExpandArrowsAlt} from '@fortawesome/free-solid-svg-icons'; +import {faCloud, faExpandArrowsAlt} from '@fortawesome/free-solid-svg-icons'; import RunOnIslandButton from './RunOnIslandButton'; +import AWSSetup from './scoutsuite-setup/AWSSetup'; +import CloudOptions from './scoutsuite-setup/CloudOptions'; function RunOptions(props) { @@ -61,6 +63,13 @@ function RunOptions(props) { setComponent(LocalManualRunOptions, {ips: ips, setComponent: setComponent}) }}/> + { + setComponent(CloudOptions, + {ips: ips, setComponent: setComponent}) + }}/> ); } diff --git a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/scoutsuite-setup/AWSSetup.js b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/scoutsuite-setup/AWSSetup.js new file mode 100644 index 000000000..6c184aabd --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/scoutsuite-setup/AWSSetup.js @@ -0,0 +1,56 @@ +import {Button} from 'react-bootstrap'; +import React from 'react'; +import InlineSelection from '../../../ui-components/inline-selection/InlineSelection'; +import CloudOptions from './CloudOptions'; +import {COLUMN_SIZES} from '../../../ui-components/inline-selection/utils'; +import '../../../../styles/components/scoutsuite/AWSSetup.scss'; + +export default function AWSSetup(props) { + return InlineSelection(getContents, { + ...props, + collumnSize: COLUMN_SIZES.LARGE, + onBackButtonClick: () => { + props.setComponent(CloudOptions, props) + } + }) +} + + +const getContents = (props) => { + return ( +
+

ScoutSuite configuration for AWS

+

To assess your AWS infrastructure security do the following:

+
    +
  1. + 1. Configure AWS CLI on Monkey Island Server (if you already have a configured CLI you can skip this step). +
      +
    1. + 1. Download and + install it on Monkey Island server (machine running this page). +
    2. +
    3. + 2. Run aws configure. It's important to configure credentials, which + allows ScoutSuite to get information about your cloud configuration. The most trivial way to do so is to + provide . +
    4. +
    +
  2. +
  3. + 2. If you change the configuration, make sure not to disable AWS system info collector. +
  4. +
  5. + 3. Go back and run Monkey on the Island server. +
  6. +
  7. + 4. Assess results in Zero Trust report. +
  8. +
+
+ ); +} diff --git a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/scoutsuite-setup/CloudOptions.js b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/scoutsuite-setup/CloudOptions.js new file mode 100644 index 000000000..1605a0eaa --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/scoutsuite-setup/CloudOptions.js @@ -0,0 +1,31 @@ +import React from 'react'; +import InlineSelection from '../../../ui-components/inline-selection/InlineSelection'; +import NextSelectionButton from '../../../ui-components/inline-selection/NextSelectionButton'; +import {faCloud} from '@fortawesome/free-solid-svg-icons'; +import AWSSetup from './AWSSetup'; + + +const CloudOptions = (props) => { + return InlineSelection(getContents, { + ...props, + onBackButtonClick: () => { + props.setComponent() + } + }) +} + +const getContents = (props) => { + return ( + <> + { + props.setComponent(AWSSetup, + {setComponent: props.setComponent}) + }}/> + + ) +} + +export default CloudOptions; diff --git a/monkey/monkey_island/cc/ui/src/components/ui-components/inline-selection/InlineSelection.js b/monkey/monkey_island/cc/ui/src/components/ui-components/inline-selection/InlineSelection.js index a699b7d7f..51bf341bd 100644 --- a/monkey/monkey_island/cc/ui/src/components/ui-components/inline-selection/InlineSelection.js +++ b/monkey/monkey_island/cc/ui/src/components/ui-components/inline-selection/InlineSelection.js @@ -2,13 +2,14 @@ import React from 'react'; import PropTypes from 'prop-types'; import BackButton from './BackButton'; import {Col, Row, Container} from 'react-bootstrap'; +import {getColumnSize} from './utils'; export default function InlineSelection(WrappedComponent, props) { return ( - + {renderBackButton(props)} diff --git a/monkey/monkey_island/cc/ui/src/components/ui-components/inline-selection/utils.js b/monkey/monkey_island/cc/ui/src/components/ui-components/inline-selection/utils.js new file mode 100644 index 000000000..16974f193 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/ui-components/inline-selection/utils.js @@ -0,0 +1,16 @@ +export const COLUMN_SIZES = { + LARGE: 'large', + STANDARD: 'standard', + SMALL: 'small' +} + + +export function getColumnSize (size) { + if(size === undefined || size === COLUMN_SIZES.STANDARD){ + return {lg: 9, md: 10, sm: 12} + } else if(size === COLUMN_SIZES.LARGE) { + return {lg: 12, md: 12, sm: 12} + } else if(size === COLUMN_SIZES.SMALL) { + return {lg: 7, md: 7, sm: 7} + } +} diff --git a/monkey/monkey_island/cc/ui/src/styles/components/scoutsuite/AWSSetup.scss b/monkey/monkey_island/cc/ui/src/styles/components/scoutsuite/AWSSetup.scss new file mode 100644 index 000000000..c1546ac81 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/styles/components/scoutsuite/AWSSetup.scss @@ -0,0 +1,17 @@ +.aws-scoutsuite-configuration a{ + display: inline-block; + padding: 0 0 3px 0; +} + +.aws-scoutsuite-configuration ol{ + padding-left: 15px; + margin-bottom: 30px; +} + +.aws-scoutsuite-configuration ol.nested-ol{ + margin-bottom: 0; +} + +.aws-scoutsuite-configuration li{ + margin-bottom: 5px; +} From 2c87784a48c45c33d3c79e7d81024a667a2ccd54 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Mon, 21 Sep 2020 11:12:23 +0300 Subject: [PATCH 024/152] Minor typos and improvements on AWS scoutsutie setup run option --- .../pages/RunMonkeyPage/scoutsuite-setup/AWSSetup.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/scoutsuite-setup/AWSSetup.js b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/scoutsuite-setup/AWSSetup.js index 6c184aabd..6fe85936e 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/scoutsuite-setup/AWSSetup.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/scoutsuite-setup/AWSSetup.js @@ -20,7 +20,7 @@ const getContents = (props) => { return (

ScoutSuite configuration for AWS

-

To assess your AWS infrastructure security do the following:

+

To assess your AWS infrastructure's security do the following:

  1. 1. Configure AWS CLI on Monkey Island Server (if you already have a configured CLI you can skip this step). @@ -28,10 +28,10 @@ const getContents = (props) => {
  2. 1. Download and - install it on Monkey Island server (machine running this page). + install it on the Monkey Island server (machine running this page).
  3. - 2. Run aws configure. It's important to configure credentials, which + 2. Run aws configure. It's important to configure credentials, which allows ScoutSuite to get information about your cloud configuration. The most trivial way to do so is to provide { diff --git a/monkey/monkey_island/cc/ui/src/components/pages/RegisterPage.js b/monkey/monkey_island/cc/ui/src/components/pages/RegisterPage.js index 3b8188221..093dba950 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/RegisterPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/RegisterPage.js @@ -92,7 +92,7 @@ class RegisterPageComponent extends React.Component { this.updateUsername(evt)} type='text' placeholder='Username'/> this.updatePassword(evt)} type='password' placeholder='Password'/> - diff --git a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/RunOptions.js b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/RunOptions.js index fa724d6f6..ddf7ef6cc 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/RunOptions.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/RunOptions.js @@ -7,7 +7,6 @@ import InlineSelection from '../../ui-components/inline-selection/InlineSelectio import {cloneDeep} from 'lodash'; import {faCloud, faExpandArrowsAlt} from '@fortawesome/free-solid-svg-icons'; import RunOnIslandButton from './RunOnIslandButton'; -import AWSSetup from './scoutsuite-setup/AWSSetup'; import CloudOptions from './scoutsuite-setup/CloudOptions'; function RunOptions(props) { diff --git a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/scoutsuite-setup/AWSSetup.js b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/scoutsuite-setup/AWSConfiguration/AWSCLISetup.js similarity index 71% rename from monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/scoutsuite-setup/AWSSetup.js rename to monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/scoutsuite-setup/AWSConfiguration/AWSCLISetup.js index 6fe85936e..69ab4903f 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/scoutsuite-setup/AWSSetup.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/scoutsuite-setup/AWSConfiguration/AWSCLISetup.js @@ -1,16 +1,17 @@ import {Button} from 'react-bootstrap'; import React from 'react'; -import InlineSelection from '../../../ui-components/inline-selection/InlineSelection'; -import CloudOptions from './CloudOptions'; -import {COLUMN_SIZES} from '../../../ui-components/inline-selection/utils'; -import '../../../../styles/components/scoutsuite/AWSSetup.scss'; +import InlineSelection from '../../../../ui-components/inline-selection/InlineSelection'; +import {COLUMN_SIZES} from '../../../../ui-components/inline-selection/utils'; +import '../../../../../styles/components/scoutsuite/AWSSetup.scss'; +import AWSSetupOptions from './AWSSetupOptions'; -export default function AWSSetup(props) { + +export default function AWSCLISetup(props) { return InlineSelection(getContents, { ...props, collumnSize: COLUMN_SIZES.LARGE, onBackButtonClick: () => { - props.setComponent(CloudOptions, props) + props.setComponent(AWSSetupOptions, props); } }) } @@ -33,9 +34,11 @@ const getContents = (props) => {
  4. 2. Run aws configure. It's important to configure credentials, which allows ScoutSuite to get information about your cloud configuration. The most trivial way to do so is to - provide .
  5. diff --git a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/scoutsuite-setup/AWSConfiguration/AWSKeySetup.js b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/scoutsuite-setup/AWSConfiguration/AWSKeySetup.js new file mode 100644 index 000000000..3643ba89c --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/scoutsuite-setup/AWSConfiguration/AWSKeySetup.js @@ -0,0 +1,176 @@ +import React, {useEffect, useState} from 'react'; +import InlineSelection from '../../../../ui-components/inline-selection/InlineSelection'; +import {COLUMN_SIZES} from '../../../../ui-components/inline-selection/utils'; +import AWSSetupOptions from './AWSSetupOptions'; +import {Button, Col, Form, Row} from 'react-bootstrap'; +import AuthComponent from '../../../../AuthComponent'; +import '../../../../../styles/components/scoutsuite/AWSSetup.scss'; +import {PROVIDERS} from '../ProvidersEnum'; +import classNames from 'classnames'; +import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'; +import {faChevronDown} from '@fortawesome/free-solid-svg-icons/faChevronDown'; +import {faChevronUp} from '@fortawesome/free-solid-svg-icons/faChevronUp'; +import {faQuestion} from '@fortawesome/free-solid-svg-icons'; +import Collapse from '@kunukn/react-collapse/dist/Collapse.umd'; +import keySetupForAnyUserImage from '../../../../../images/aws_keys_tutorial-any-user.png'; +import keySetupForCurrentUserImage from '../../../../../images/aws_keys_tutorial-current-user.png'; +import ImageModal from '../../../../ui-components/ImageModal'; + + +export default function AWSCLISetup(props) { + return InlineSelection(getContents, { + ...props, + collumnSize: COLUMN_SIZES.LARGE, + onBackButtonClick: () => { + props.setComponent(AWSSetupOptions, props); + } + }) +} + +const authComponent = new AuthComponent({}) + +const getContents = (props) => { + + const [accessKeyId, setAccessKeyId] = useState(''); + const [secretAccessKey, setSecretAccessKey] = useState(''); + const [sessionToken, setSessionToken] = useState(''); + const [errorMessage, setErrorMessage] = useState(''); + const [successMessage, setSuccessMessage] = useState(''); + const [docCollapseOpen, setDocCollapseOpen] = useState(false); + + function submitKeys(event) { + event.preventDefault(); + setSuccessMessage(''); + setErrorMessage(''); + authComponent.authFetch( + '/api/scoutsuite_auth/' + PROVIDERS.AWS, + { + 'method': 'POST', + 'body': JSON.stringify({ + 'accessKeyId': accessKeyId, + 'secretAccessKey': secretAccessKey, + 'sessionToken': sessionToken + }) + }) + .then(res => res.json()) + .then(res => { + if (res['error_msg'] === '') { + setSuccessMessage('AWS keys saved!'); + } else { + setErrorMessage(res['error_msg']); + } + }); + } + + useEffect(() => { + authComponent.authFetch('/api/aws_keys') + .then(res => res.json()) + .then(res => { + setAccessKeyId(res['access_key_id']); + setSecretAccessKey(res['secret_access_key']); + setSessionToken(res['session_token']); + }); + }, [props]); + + + // TODO separate into standalone component + function getKeyCreationDocsContent() { + return ( +
    +
    Tips
    +

    Consider creating a new user account just for this activity. Assign only ReadOnlyAccess and  + SecurityAudit policies.

    + +
    Keys for custom user
    +

    1. Open the IAM console at https://console.aws.amazon.com/iam/ .

    +

    2. In the navigation pane, choose Users.

    +

    3. Choose the name of the user whose access keys you want to create, and then choose the Security credentials + tab.

    +

    4. In the Access keys section, choose Create access key.

    +

    To view the new access key pair, choose Show. Your credentials will look something like this:

    +

    Access key ID: AKIAIOSFODNN7EXAMPLE

    +

    Secret access key: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY

    + + + + + + +
    Keys for current user
    +

    1. Click on your username in the upper right corner.

    +

    2. Click on "My security credentials".

    +

    3. In the Access keys section, choose Create access key.

    +

    To view the new access key pair, choose Show. Your credentials will look something like this:

    +

    Access key ID: AKIAIOSFODNN7EXAMPLE

    +

    Secret access key: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY

    + + + + + +
    ); + } + + function getKeyCreationDocs() { + return ( +
    + + +
    ); + } + + return ( +
    + {getKeyCreationDocs()} + + setAccessKeyId(evt.target.value)} + type='text' + placeholder='Access key ID' + value={accessKeyId}/> + setSecretAccessKey(evt.target.value)} + type='password' + placeholder='Secret access key' + value={secretAccessKey}/> + setSessionToken(evt.target.value)} + type='text' + placeholder='Session token (optional, only for temp. keys)' + value={sessionToken}/> + { + errorMessage ? +
    {errorMessage}
    + : + '' + } + { + successMessage ? +
    {successMessage}  + Go back and  + to start AWS scan!
    + : + '' + } + + + + + + +
    + ); +} diff --git a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/scoutsuite-setup/AWSConfiguration/AWSSetupOptions.js b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/scoutsuite-setup/AWSConfiguration/AWSSetupOptions.js new file mode 100644 index 000000000..a66a893d8 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/scoutsuite-setup/AWSConfiguration/AWSSetupOptions.js @@ -0,0 +1,40 @@ +import React from 'react'; +import InlineSelection from '../../../../ui-components/inline-selection/InlineSelection'; +import NextSelectionButton from '../../../../ui-components/inline-selection/NextSelectionButton'; +import {faKey, faTerminal} from '@fortawesome/free-solid-svg-icons'; +import AWSCLISetup from './AWSCLISetup'; +import CloudOptions from '../CloudOptions'; +import AWSKeySetup from './AWSKeySetup'; + + +const AWSSetupOptions = (props) => { + return InlineSelection(getContents, { + ...props, + onBackButtonClick: () => { + props.setComponent(CloudOptions, props); + } + }) +} + +const getContents = (props) => { + return ( + <> + { + props.setComponent(AWSKeySetup, + {setComponent: props.setComponent}) + }}/> + { + props.setComponent(AWSCLISetup, + {setComponent: props.setComponent}) + }}/> + + ) +} + +export default AWSSetupOptions; diff --git a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/scoutsuite-setup/CloudOptions.js b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/scoutsuite-setup/CloudOptions.js index 1605a0eaa..aa4dedd2e 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/scoutsuite-setup/CloudOptions.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/scoutsuite-setup/CloudOptions.js @@ -1,8 +1,10 @@ -import React from 'react'; +import React, {useEffect, useState} from 'react'; import InlineSelection from '../../../ui-components/inline-selection/InlineSelection'; import NextSelectionButton from '../../../ui-components/inline-selection/NextSelectionButton'; -import {faCloud} from '@fortawesome/free-solid-svg-icons'; -import AWSSetup from './AWSSetup'; +import {faCheck, faCloud, faSync} from '@fortawesome/free-solid-svg-icons'; +import AWSSetupOptions from './AWSConfiguration/AWSSetupOptions'; +import {PROVIDERS} from './ProvidersEnum'; +import AuthComponent from '../../../AuthComponent'; const CloudOptions = (props) => { @@ -14,14 +16,38 @@ const CloudOptions = (props) => { }) } +const authComponent = new AuthComponent({}) + const getContents = (props) => { + + const [description, setDescription] = useState("Loading..."); + const [iconType, setIconType] = useState('spinning-icon'); + const [icon, setIcon] = useState(faSync); + + useEffect(() => { + authComponent.authFetch('/api/scoutsuite_auth/' + PROVIDERS.AWS) + .then(res => res.json()) + .then(res => { + if(res.is_setup){ + setDescription(res.message + 'Click next to change the configuration.'); + setIconType('icon-success'); + setIcon(faCheck); + } else { + setDescription('Setup Amazon Web Services infrastructure scan.'); + setIconType('') + setIcon(faCloud); + } + }); + }, [props]); + return ( <> { - props.setComponent(AWSSetup, + props.setComponent(AWSSetupOptions, {setComponent: props.setComponent}) }}/> diff --git a/monkey/monkey_island/cc/ui/src/components/ui-components/ImageModal.js b/monkey/monkey_island/cc/ui/src/components/ui-components/ImageModal.js new file mode 100644 index 000000000..12632e811 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/ui-components/ImageModal.js @@ -0,0 +1,33 @@ +import React, {useState} from 'react'; +import PropTypes from 'prop-types'; +import {Button, Image, Modal} from 'react-bootstrap'; +import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'; +import {faSearchPlus} from '@fortawesome/free-solid-svg-icons'; + + +const ImageModal = (props) => { + + const [isModalOpen, setIsModalOpen] = useState(false); + + return ( +
    + + setIsModalOpen(false)}> + + + + +
    + ); +} + +export default ImageModal; + +ImageModal.propTypes = { + image: PropTypes.element +} diff --git a/monkey/monkey_island/cc/ui/src/components/ui-components/inline-selection/InlineSelection.js b/monkey/monkey_island/cc/ui/src/components/ui-components/inline-selection/InlineSelection.js index 51bf341bd..502fbf6b1 100644 --- a/monkey/monkey_island/cc/ui/src/components/ui-components/inline-selection/InlineSelection.js +++ b/monkey/monkey_island/cc/ui/src/components/ui-components/inline-selection/InlineSelection.js @@ -18,8 +18,8 @@ export default function InlineSelection(WrappedComponent, props) { ) } -function renderBackButton(props){ - if(props.onBackButtonClick !== undefined){ +function renderBackButton(props) { + if (props.onBackButtonClick !== undefined) { return (); } } diff --git a/monkey/monkey_island/cc/ui/src/components/ui-components/inline-selection/NextSelectionButton.js b/monkey/monkey_island/cc/ui/src/components/ui-components/inline-selection/NextSelectionButton.js index 7a7c47087..174dce254 100644 --- a/monkey/monkey_island/cc/ui/src/components/ui-components/inline-selection/NextSelectionButton.js +++ b/monkey/monkey_island/cc/ui/src/components/ui-components/inline-selection/NextSelectionButton.js @@ -6,7 +6,8 @@ import {faAngleRight} from '@fortawesome/free-solid-svg-icons'; export default function nextSelectionButton(props) { let description = props.description !== undefined ? (

    {props.description}

    ) : '' - let icon = props.icon !== undefined ? () : '' + let iconType = props.iconType !== undefined ? props.iconType : '' + let icon = props.icon !== undefined ? () : '' return ( @@ -24,6 +25,7 @@ export default function nextSelectionButton(props) { nextSelectionButton.propTypes = { title: PropTypes.string, + iconType: PropTypes.string, icon: FontAwesomeIcon, description: PropTypes.string, onButtonClick: PropTypes.func diff --git a/monkey/monkey_island/cc/ui/src/images/aws_keys_tutorial-any-user.png b/monkey/monkey_island/cc/ui/src/images/aws_keys_tutorial-any-user.png new file mode 100644 index 0000000000000000000000000000000000000000..c0dfb41117967e16a2cdafcb4fbd5b8144fb383d GIT binary patch literal 115125 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
    s>9o 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 literal 0 HcmV?d00001 diff --git a/monkey/monkey_island/cc/ui/src/images/aws_keys_tutorial-current-user.png b/monkey/monkey_island/cc/ui/src/images/aws_keys_tutorial-current-user.png new file mode 100644 index 0000000000000000000000000000000000000000..b1b3a997f3bd88dea5f960eb1d1ebb5f1d962197 GIT binary patch literal 127374 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 W#^MF+JgNJD&+NRVG1l-_^gjS5JeR`& literal 0 HcmV?d00001 diff --git a/monkey/monkey_island/cc/ui/src/styles/Main.scss b/monkey/monkey_island/cc/ui/src/styles/Main.scss index 602c62b3d..400af4054 100644 --- a/monkey/monkey_island/cc/ui/src/styles/Main.scss +++ b/monkey/monkey_island/cc/ui/src/styles/Main.scss @@ -3,6 +3,7 @@ @import '../../node_modules/bootstrap/scss/bootstrap'; // Imports that require variables +@import 'components/Buttons'; @import 'pages/report/ReportPage.scss'; @import 'pages/report/AttackReport.scss'; @import 'pages/ConfigurationPage'; @@ -13,11 +14,12 @@ @import 'components/AdvancedMultiSelect'; @import 'components/particle-component/ParticleBackground'; @import 'components/scoutsuite/ResourceDropdown'; +@import 'components/ImageModal'; +@import 'components/Icons'; @import 'components/inline-selection/InlineSelection'; @import 'components/inline-selection/NextSelectionButton'; @import 'components/inline-selection/BackButton'; @import 'components/inline-selection/CommandDisplay'; -@import 'components/Icons'; // Define custom elements after bootstrap import diff --git a/monkey/monkey_island/cc/ui/src/styles/components/Icons.scss b/monkey/monkey_island/cc/ui/src/styles/components/Icons.scss index ed75d8a9d..36c9082b8 100644 --- a/monkey/monkey_island/cc/ui/src/styles/components/Icons.scss +++ b/monkey/monkey_island/cc/ui/src/styles/components/Icons.scss @@ -3,6 +3,14 @@ display: inline-block; } +.icon-success { + color: $success +} + +.icon-failed { + color: $danger; +} + @keyframes spin-animation { 0% { transform: rotate(0deg); diff --git a/monkey/monkey_island/cc/ui/src/styles/components/ImageModal.scss b/monkey/monkey_island/cc/ui/src/styles/components/ImageModal.scss new file mode 100644 index 000000000..bfe630b5f --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/styles/components/ImageModal.scss @@ -0,0 +1,49 @@ +.image-modal .image-modal-thumbnail { + position: relative; + padding: 7px; +} + +.image-modal .image-modal-thumbnail:focus { + background-color: white; + border-color: white; + box-shadow: 0 2px 6px #ccc; +} + +.image-modal .image-modal-thumbnail:hover { + background-color: white; + border-color: white; + box-shadow: 0 2px 6px #ccc; +} + +.image-modal .image-modal-thumbnail-icon { + left: 50%; + top: 50%; + -webkit-transform: translate(-50%, -50%); + -moz-transform: translate(-50%, -50%); + transform: translate(-50%, -50%); + position: absolute; + min-width: 40px; + min-height: 40px; +} + +.image-modal:hover .image-modal-thumbnail-icon { + color: $monkey-yellow; +} + +.image-modal-screen { + padding: 0; +} + +.image-modal-screen .modal-dialog { + margin: 0; + top: 0; + width: 100%; + padding: 30px; + max-width: none; + max-height: none; +} + +.image-modal-screen .modal-dialog .modal-content { + width: fit-content; + margin: auto; +} diff --git a/monkey/monkey_island/cc/ui/src/styles/components/scoutsuite/AWSSetup.scss b/monkey/monkey_island/cc/ui/src/styles/components/scoutsuite/AWSSetup.scss index c1546ac81..9d8b793aa 100644 --- a/monkey/monkey_island/cc/ui/src/styles/components/scoutsuite/AWSSetup.scss +++ b/monkey/monkey_island/cc/ui/src/styles/components/scoutsuite/AWSSetup.scss @@ -1,17 +1,74 @@ -.aws-scoutsuite-configuration a{ +.aws-scoutsuite-configuration a { display: inline-block; padding: 0 0 3px 0; } -.aws-scoutsuite-configuration ol{ +.aws-scoutsuite-configuration ol { padding-left: 15px; margin-bottom: 30px; } -.aws-scoutsuite-configuration ol.nested-ol{ +.aws-scoutsuite-configuration ol.nested-ol { margin-bottom: 0; } -.aws-scoutsuite-configuration li{ +.aws-scoutsuite-configuration li { margin-bottom: 5px; } + +.monkey-submit-button { + margin-bottom: 15px; +} + +.aws-scoutsuite-key-configuration .collapse-item { + padding: 0; + margin-bottom: 15px; +} + +.aws-scoutsuite-key-configuration .collapse-item .btn-collapse .question-icon { + display: inline-block; + margin-right: 7px; + margin-bottom: 1px; +} + +.aws-scoutsuite-key-configuration .collapse-item .btn-collapse p { + display: inline-block; + margin-bottom: 0; + font-size: 1.2em; + margin-left: 5px +} + +.aws-scoutsuite-key-configuration .key-creation-tutorial { + padding-bottom: 10px; +} + +.aws-scoutsuite-key-configuration .key-creation-tutorial p { + margin-bottom: 2px; + font-weight: 400; +} + +.aws-scoutsuite-key-configuration .key-creation-tutorial h5 { + margin-top: 15px; + font-weight: 600; +} + +.aws-scoutsuite-key-configuration .key-creation-tutorial p:first-child { + margin-top: 15px; +} + +.aws-scoutsuite-key-configuration .image-modal { + margin-top: 5px; +} + +.aws-scoutsuite-key-configuration .key-creation-tutorial img { + max-width: 100%; + max-height: 100%; + border: 1px solid black; +} + +.link-in-success-message { + padding: 0 !important; + vertical-align: initial !important; +} + + diff --git a/monkey/monkey_island/cc/ui/src/styles/pages/AuthPage.scss b/monkey/monkey_island/cc/ui/src/styles/pages/AuthPage.scss index 8b1b10502..e3ecbd0e6 100644 --- a/monkey/monkey_island/cc/ui/src/styles/pages/AuthPage.scss +++ b/monkey/monkey_island/cc/ui/src/styles/pages/AuthPage.scss @@ -15,23 +15,6 @@ margin-top: 20px; } -#auth-button { - border-color: #3f3f3f; - font-size: 1.3em; - width: 100%; - background-color: #3f3f3f; - color: $monkey-yellow; -} - -#auth-button:hover { - border-color: $monkey-yellow; - font-size: 1.3em; - font-weight: bold; - width: 100%; - background-color: $monkey-yellow; - color: #000000; -} - .monkey-detective { max-height: 500px; } From 17d91766df9e3fe16c4e7c68f1e233e08ea963cc Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Thu, 1 Oct 2020 15:07:32 +0300 Subject: [PATCH 038/152] Added AWS keys to config --- monkey/common/cloud/scoutsuite_consts.py | 9 +++ monkey/infection_monkey/config.py | 4 ++ .../scoutsuite_collector.py | 32 ++++++---- monkey/monkey_island/cc/services/config.py | 36 ++++++----- .../cc/services/config_schema/internal.py | 17 +++++ .../scoutsuite/scoutsuite_auth_service.py | 63 +++++++++++++++++++ .../configuration-components/UiSchema.js | 3 + 7 files changed, 135 insertions(+), 29 deletions(-) create mode 100644 monkey/common/cloud/scoutsuite_consts.py create mode 100644 monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_auth_service.py diff --git a/monkey/common/cloud/scoutsuite_consts.py b/monkey/common/cloud/scoutsuite_consts.py new file mode 100644 index 000000000..86411e179 --- /dev/null +++ b/monkey/common/cloud/scoutsuite_consts.py @@ -0,0 +1,9 @@ +from enum import Enum + + +class PROVIDERS(Enum): + AWS = 'aws' + AZURE = 'azure' + GCP = 'gcp' + ALIBABA = 'aliyun' + ORACLE = 'oci' diff --git a/monkey/infection_monkey/config.py b/monkey/infection_monkey/config.py index 2917524c5..7597e3de3 100644 --- a/monkey/infection_monkey/config.py +++ b/monkey/infection_monkey/config.py @@ -246,6 +246,10 @@ class Configuration(object): exploit_ntlm_hash_list = [] exploit_ssh_keys = [] + access_key_id = '' + secret_access_key = '' + session_token = '' + # smb/wmi exploiter smb_download_timeout = 300 # timeout in seconds smb_service_name = "InfectionMonkey" diff --git a/monkey/infection_monkey/system_info/collectors/scoutsuite_collector/scoutsuite_collector.py b/monkey/infection_monkey/system_info/collectors/scoutsuite_collector/scoutsuite_collector.py index f7d6b7ec5..6965e53c7 100644 --- a/monkey/infection_monkey/system_info/collectors/scoutsuite_collector/scoutsuite_collector.py +++ b/monkey/infection_monkey/system_info/collectors/scoutsuite_collector/scoutsuite_collector.py @@ -1,22 +1,28 @@ +import logging + import infection_monkey.system_info.collectors.scoutsuite_collector.scoutsuite_api as scoutsuite_api +from common.cloud.scoutsuite_consts import PROVIDERS from infection_monkey.telemetry.scoutsuite_telem import ScoutSuiteTelem +from infection_monkey.config import WormConfiguration + +logger = logging.getLogger(__name__) -class CLOUD_TYPES: - AWS = 'aws' - AZURE = 'azure' - GCP = 'gcp' - ALIBABA = 'aliyun' - ORACLE = 'oci' +def scan_cloud_security(cloud_type: PROVIDERS): + try: + results = run_scoutsuite(cloud_type.value) + if 'error' in results and results['error']: + raise Exception(results['error']) + send_results(results) + except Exception as e: + logger.error(f"ScoutSuite didn't scan {cloud_type.value} security because: {e}") -def scan_cloud_security(cloud_type: CLOUD_TYPES): - results = run_scoutsuite(cloud_type) - send_results(results) - - -def run_scoutsuite(cloud_type): - return scoutsuite_api.run(provider=cloud_type) +def run_scoutsuite(cloud_type: str): + return scoutsuite_api.run(provider=cloud_type, + aws_access_key_id=WormConfiguration.access_key_id, + aws_secret_access_key=WormConfiguration.secret_access_key, + aws_session_token=WormConfiguration.session_token) def send_results(results): diff --git a/monkey/monkey_island/cc/services/config.py b/monkey/monkey_island/cc/services/config.py index e67abc1f3..e071366fb 100644 --- a/monkey/monkey_island/cc/services/config.py +++ b/monkey/monkey_island/cc/services/config.py @@ -21,12 +21,15 @@ from monkey_island.cc.services.config_schema.config_value_paths import STARTED_O logger = logging.getLogger(__name__) # This should be used for config values of array type (array of strings only) -ENCRYPTED_CONFIG_ARRAYS = \ +ENCRYPTED_CONFIG_VALUES = \ [ - ['basic', 'credentials', 'exploit_password_list'], - ['internal', 'exploits', 'exploit_lm_hash_list'], - ['internal', 'exploits', 'exploit_ntlm_hash_list'], - ['internal', 'exploits', 'exploit_ssh_keys'] + PASSWORD_LIST_PATH, + LM_HASH_LIST_PATH, + NTLM_HASH_LIST_PATH, + SSH_KEYS_PATH, + AWS_KEYS_PATH + ['access_key_id'], + AWS_KEYS_PATH + ['secret_access_key'], + AWS_KEYS_PATH + ['session_token'] ] @@ -69,8 +72,11 @@ class ConfigService: for config_key_part in config_key_as_arr: config = config[config_key_part] if should_decrypt: - if config_key_as_arr in ENCRYPTED_CONFIG_ARRAYS: - config = [encryptor.dec(x) for x in config] + if config_key_as_arr in ENCRYPTED_CONFIG_VALUES: + if isinstance(config, str): + config = encryptor.dec(config) + elif isinstance(config, list): + config = [encryptor.dec(x) for x in config] return config @staticmethod @@ -79,12 +85,6 @@ class ConfigService: mongo.db.config.update({'name': 'newconfig'}, {"$set": {mongo_key: value}}) - @staticmethod - def append_to_config_array(config_key_as_arr, value): - mongo_key = ".".join(config_key_as_arr) - mongo.db.config.update({'name': 'newconfig'}, - {"$push": {mongo_key: value}}) - @staticmethod def get_flat_config(is_initial_config=False, should_decrypt=True): config_json = ConfigService.get_config(is_initial_config, should_decrypt) @@ -92,7 +92,11 @@ class ConfigService: for i in config_json: for j in config_json[i]: for k in config_json[i][j]: - flat_config_json[k] = config_json[i][j][k] + if isinstance(config_json[i][j][k], dict): + for key, value in config_json[i][j][k].items(): + flat_config_json[key] = value + else: + flat_config_json[k] = config_json[i][j][k] return flat_config_json @@ -101,8 +105,8 @@ class ConfigService: return SCHEMA @staticmethod - def add_item_to_config_set_if_dont_exist(item_key, item_value, should_encrypt): - item_path_array = item_key.split('.') + def add_item_to_config_set_if_dont_exist(item_path_array, item_value, should_encrypt): + item_key = '.'.join(item_path_array) items_from_config = ConfigService.get_config_value(item_path_array, False, should_encrypt) if item_value in items_from_config: return diff --git a/monkey/monkey_island/cc/services/config_schema/internal.py b/monkey/monkey_island/cc/services/config_schema/internal.py index bdbae2461..b571d31ab 100644 --- a/monkey/monkey_island/cc/services/config_schema/internal.py +++ b/monkey/monkey_island/cc/services/config_schema/internal.py @@ -94,6 +94,23 @@ INTERNAL = { "type": "boolean", "default": True, "description": "Is the monkey alive" + }, + "aws_keys": { + "type": "object", + "properties": { + "access_key_id": { + "type": "string", + "default": "" + }, + "secret_access_key": { + "type": "string", + "default": "" + }, + "session_token": { + "type": "string", + "default": "" + } + } } } }, diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_auth_service.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_auth_service.py new file mode 100644 index 000000000..5cd29d423 --- /dev/null +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_auth_service.py @@ -0,0 +1,63 @@ +import pkgutil +import sys +from pathlib import PurePath +from typing import Tuple + +from common.cloud.scoutsuite_consts import PROVIDERS +from common.utils.exceptions import InvalidAWSKeys +from monkey_island.cc.encryptor import encryptor +from monkey_island.cc.services.config import ConfigService +from monkey_island.cc.services.config_schema.config_value_paths import AWS_KEYS_PATH + +_scoutsuite_api_package = pkgutil.get_loader('common.cloud.scoutsuite.ScoutSuite.__main__') + + +def _add_scoutsuite_to_python_path(): + scoutsuite_path = PurePath(_scoutsuite_api_package.path).parent.parent.__str__() + sys.path.append(scoutsuite_path) + + +_add_scoutsuite_to_python_path() + + +def is_cloud_authentication_setup(provider: PROVIDERS) -> Tuple[bool, str]: + if provider == PROVIDERS.AWS.value: + if is_aws_keys_setup(): + return True, "AWS keys already setup. Run monkey on Island to scan." + + import common.cloud.scoutsuite.ScoutSuite.providers.aws.authentication_strategy as auth_strategy + try: + profile = auth_strategy.AWSAuthenticationStrategy().authenticate() + return True, f" Profile \"{profile.session.profile_name}\" is already setup. Run monkey on Island to scan." + except Exception: + return False, "" + + +def is_aws_keys_setup(): + return (ConfigService.get_config_value(AWS_KEYS_PATH + ['access_key_id']) and + ConfigService.get_config_value(AWS_KEYS_PATH + ['secret_access_key'])) + + +def set_aws_keys(access_key_id: str, secret_access_key: str, session_token: str): + if not access_key_id or not secret_access_key: + raise InvalidAWSKeys("Missing some of the following fields: access key ID, secret access key.") + _set_aws_key('access_key_id', access_key_id) + _set_aws_key('secret_access_key', secret_access_key) + _set_aws_key('session_token', session_token) + + +def _set_aws_key(key_type: str, key_value: str): + path_to_keys = AWS_KEYS_PATH + encrypted_key = encryptor.enc(key_value) + ConfigService.set_config_value(path_to_keys + [key_type], encrypted_key) + + +def get_aws_keys(): + return {'access_key_id': _get_aws_key('access_key_id'), + 'secret_access_key': _get_aws_key('secret_access_key'), + 'session_token': _get_aws_key('session_token')} + + +def _get_aws_key(key_type: str): + path_to_keys = AWS_KEYS_PATH + return ConfigService.get_config_value(config_key_as_arr=path_to_keys + [key_type]) diff --git a/monkey/monkey_island/cc/ui/src/components/configuration-components/UiSchema.js b/monkey/monkey_island/cc/ui/src/components/configuration-components/UiSchema.js index aab3f1899..ac9104817 100644 --- a/monkey/monkey_island/cc/ui/src/components/configuration-components/UiSchema.js +++ b/monkey/monkey_island/cc/ui/src/components/configuration-components/UiSchema.js @@ -84,6 +84,9 @@ export default function UiSchema(props) { monkey: { alive: { classNames: 'config-field-hidden' + }, + aws_keys: { + classNames: 'config-field-hidden' } } } From 841f542c6bf45b4f5d04a1418ba8ea56c4068d60 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Thu, 1 Oct 2020 15:08:45 +0300 Subject: [PATCH 039/152] Refactored few more files to use config value path array, rather than hardcoded in-place value --- .../cc/services/attack/technique_reports/T1065.py | 4 +++- monkey/monkey_island/cc/services/configuration/utils.py | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1065.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1065.py index cd4d69ae8..c3fcd03e8 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1065.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1065.py @@ -4,6 +4,8 @@ from monkey_island.cc.services.config import ConfigService __author__ = "VakarisZ" +from monkey_island.cc.services.config_schema.config_value_paths import CURRENT_SERVER_PATH + class T1065(AttackTechnique): tech_id = "T1065" @@ -14,6 +16,6 @@ class T1065(AttackTechnique): @staticmethod def get_report_data(): - port = ConfigService.get_config_value(['internal', 'island_server', 'current_server']).split(':')[1] + port = ConfigService.get_config_value(CURRENT_SERVER_PATH).split(':')[1] T1065.used_msg = T1065.message % port return T1065.get_base_data_by_status(ScanStatus.USED.value) diff --git a/monkey/monkey_island/cc/services/configuration/utils.py b/monkey/monkey_island/cc/services/configuration/utils.py index 34d6a9bb5..48857e2e3 100644 --- a/monkey/monkey_island/cc/services/configuration/utils.py +++ b/monkey/monkey_island/cc/services/configuration/utils.py @@ -1,5 +1,6 @@ from monkey_island.cc.services.config import ConfigService +from monkey_island.cc.services.config_schema.config_value_paths import INACCESSIBLE_SUBNETS_PATH def get_config_network_segments_as_subnet_groups(): - return [ConfigService.get_config_value(['basic_network', 'network_analysis', 'inaccessible_subnets'])] + return [ConfigService.get_config_value(INACCESSIBLE_SUBNETS_PATH)] From d3f0dc2a750a38e754c9c3f2f485564f79ba336c Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Thu, 1 Oct 2020 15:09:10 +0300 Subject: [PATCH 040/152] Improved back button --- .../src/components/ui-components/inline-selection/BackButton.js | 2 +- .../ui/src/styles/components/inline-selection/BackButton.scss | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/ui-components/inline-selection/BackButton.js b/monkey/monkey_island/cc/ui/src/components/ui-components/inline-selection/BackButton.js index 5799a39ea..f37426df4 100644 --- a/monkey/monkey_island/cc/ui/src/components/ui-components/inline-selection/BackButton.js +++ b/monkey/monkey_island/cc/ui/src/components/ui-components/inline-selection/BackButton.js @@ -7,7 +7,7 @@ import {faCaretLeft} from '@fortawesome/free-solid-svg-icons/faCaretLeft'; export default function backButton(props) { return ( - + . @@ -48,7 +49,10 @@ const getContents = (props) => { 2. If you change the configuration, make sure not to disable AWS system info collector.
  6. - 3. Go back and run Monkey on the Island server. + 3. Go +  and run Monkey on the Island server.
  7. 4. Assess results in Zero Trust report. diff --git a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/scoutsuite-setup/AWSConfiguration/AWSKeySetup.js b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/scoutsuite-setup/AWSConfiguration/AWSKeySetup.js index 3643ba89c..af2cecf8f 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/scoutsuite-setup/AWSConfiguration/AWSKeySetup.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/scoutsuite-setup/AWSConfiguration/AWSKeySetup.js @@ -83,7 +83,7 @@ const getContents = (props) => {
    Keys for custom user

    1. Open the IAM console at https://console.aws.amazon.com/iam/ .

    + target={'_blank'}>https://console.aws.amazon.com/iam/.

    2. In the navigation pane, choose Users.

    3. Choose the name of the user whose access keys you want to create, and then choose the Security credentials tab.

    diff --git a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/scoutsuite-setup/CloudOptions.js b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/scoutsuite-setup/CloudOptions.js index aa4dedd2e..3e643b361 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/scoutsuite-setup/CloudOptions.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/scoutsuite-setup/CloudOptions.js @@ -29,7 +29,7 @@ const getContents = (props) => { .then(res => res.json()) .then(res => { if(res.is_setup){ - setDescription(res.message + 'Click next to change the configuration.'); + setDescription(res.message + ' Click next to change the configuration.'); setIconType('icon-success'); setIcon(faCheck); } else { diff --git a/monkey/monkey_island/cc/ui/src/styles/components/scoutsuite/AWSSetup.scss b/monkey/monkey_island/cc/ui/src/styles/components/scoutsuite/AWSSetup.scss index 9d8b793aa..8be9d1956 100644 --- a/monkey/monkey_island/cc/ui/src/styles/components/scoutsuite/AWSSetup.scss +++ b/monkey/monkey_island/cc/ui/src/styles/components/scoutsuite/AWSSetup.scss @@ -13,9 +13,21 @@ } .aws-scoutsuite-configuration li { + margin-bottom: 0; +} + +.aws-scoutsuite-configuration h2 { + margin-bottom: 20px; +} + +.aws-scoutsuite-configuration p { margin-bottom: 5px; } +.aws-scoutsuite-configuration .cli-link { + padding: 0 0 4px 0; +} + .monkey-submit-button { margin-bottom: 15px; } From d2a8597903db50a7f728f2827824c32a7b51147f Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Thu, 1 Oct 2020 17:56:29 +0300 Subject: [PATCH 047/152] Fixed error caused by mixing up the value of "started_on_island" with whether the current monkey is running on island. --- monkey/common/network/network_utils.py | 9 +++++++++ monkey/infection_monkey/monkey.py | 11 +++-------- .../system_info/collectors/aws_collector.py | 4 ++-- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/monkey/common/network/network_utils.py b/monkey/common/network/network_utils.py index e99d0cf2b..384017f3b 100644 --- a/monkey/common/network/network_utils.py +++ b/monkey/common/network/network_utils.py @@ -1,6 +1,9 @@ import re from urllib.parse import urlparse +from infection_monkey.config import WormConfiguration +from infection_monkey.network.tools import is_running_on_server + def get_host_from_network_location(network_location: str) -> str: """ @@ -18,3 +21,9 @@ def remove_port(url): with_port = f'{parsed.scheme}://{parsed.netloc}' without_port = re.sub(':[0-9]+(?=$|\/)', '', with_port) return without_port + + +def is_running_on_island(): + current_server_without_port = get_host_from_network_location(WormConfiguration.current_server) + running_on_island = is_running_on_server(current_server_without_port) + return running_on_island and WormConfiguration.depth == WormConfiguration.max_depth diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index 23c94a7f9..6b0e68dff 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -7,7 +7,7 @@ import time from threading import Thread import infection_monkey.tunnel as tunnel -from common.network.network_utils import get_host_from_network_location +from common.network.network_utils import is_running_on_island from common.utils.attack_utils import ScanStatus, UsageEnum from common.utils.exceptions import (ExploitingVulnerableMachineError, FailedExploitationError) @@ -19,8 +19,7 @@ from infection_monkey.model import DELAY_DELETE_CMD from infection_monkey.network.firewall import app as firewall from infection_monkey.network.HostFinger import HostFinger from infection_monkey.network.network_scanner import NetworkScanner -from infection_monkey.network.tools import (get_interface_to_target, - is_running_on_server) +from infection_monkey.network.tools import get_interface_to_target from infection_monkey.post_breach.post_breach_handler import PostBreach from infection_monkey.system_info import SystemInfoCollector from infection_monkey.system_singleton import SystemSingleton @@ -125,7 +124,7 @@ class InfectionMonkey(object): self.shutdown_by_not_alive_config() - if self.is_started_on_island(): + if is_running_on_island(): WormConfiguration.started_on_island = True ControlClient.report_start_on_island() ControlClient.should_monkey_run(self._opts.vulnerable_port) @@ -400,10 +399,6 @@ class InfectionMonkey(object): self._default_server = WormConfiguration.current_server LOG.debug("default server set to: %s" % self._default_server) - def is_started_on_island(self): - island_ip = get_host_from_network_location(self._default_server) - return is_running_on_server(island_ip) and WormConfiguration.depth == WormConfiguration.max_depth - def log_arguments(self): arg_string = " ".join([f"{key}: {value}" for key, value in vars(self._opts).items()]) LOG.info(f"Monkey started with arguments: {arg_string}") diff --git a/monkey/infection_monkey/system_info/collectors/aws_collector.py b/monkey/infection_monkey/system_info/collectors/aws_collector.py index 406c5b0ce..e8d181077 100644 --- a/monkey/infection_monkey/system_info/collectors/aws_collector.py +++ b/monkey/infection_monkey/system_info/collectors/aws_collector.py @@ -3,10 +3,10 @@ import logging from common.cloud.aws.aws_instance import AwsInstance from common.cloud.scoutsuite_consts import PROVIDERS from common.common_consts.system_info_collectors_names import AWS_COLLECTOR +from common.network.network_utils import is_running_on_island from infection_monkey.system_info.collectors.scoutsuite_collector.scoutsuite_collector import scan_cloud_security from infection_monkey.system_info.system_info_collector import \ SystemInfoCollector -from infection_monkey.config import WormConfiguration logger = logging.getLogger(__name__) @@ -20,7 +20,7 @@ class AwsCollector(SystemInfoCollector): def collect(self) -> dict: logger.info("Collecting AWS info") - if WormConfiguration.started_on_island: + if is_running_on_island(): logger.info("Attempting to scan AWS security with ScoutSuite.") scan_cloud_security(cloud_type=PROVIDERS.AWS) else: From 672c19ef0d183c1395187140bbeb202342fecb71 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Fri, 2 Oct 2020 11:00:06 +0300 Subject: [PATCH 048/152] Fixed scoutsuite bug that caused bad exception handling --- monkey/common/cloud/scoutsuite | 2 +- monkey/infection_monkey/monkey.py | 2 +- .../collectors/scoutsuite_collector/scoutsuite_collector.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/monkey/common/cloud/scoutsuite b/monkey/common/cloud/scoutsuite index 6d7178d7a..b80659387 160000 --- a/monkey/common/cloud/scoutsuite +++ b/monkey/common/cloud/scoutsuite @@ -1 +1 @@ -Subproject commit 6d7178d7a1249024bea0ec20985f73aa35ab4809 +Subproject commit b806593875e7927f1b1b20cb5bc27da0025dbb51 diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index 6b0e68dff..54fce9642 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -250,7 +250,7 @@ class InfectionMonkey(object): LOG.debug("Running with depth: %d" % WormConfiguration.depth) def collect_system_info_if_configured(self): - LOG.debug("Calling system info collection") + LOG.debug("Calling for system info collection") system_info_collector = SystemInfoCollector() system_info = system_info_collector.get_info() SystemInfoTelem(system_info).send() diff --git a/monkey/infection_monkey/system_info/collectors/scoutsuite_collector/scoutsuite_collector.py b/monkey/infection_monkey/system_info/collectors/scoutsuite_collector/scoutsuite_collector.py index 6965e53c7..06bdf91e8 100644 --- a/monkey/infection_monkey/system_info/collectors/scoutsuite_collector/scoutsuite_collector.py +++ b/monkey/infection_monkey/system_info/collectors/scoutsuite_collector/scoutsuite_collector.py @@ -11,7 +11,7 @@ logger = logging.getLogger(__name__) def scan_cloud_security(cloud_type: PROVIDERS): try: results = run_scoutsuite(cloud_type.value) - if 'error' in results and results['error']: + if isinstance(results, dict) and 'error' in results and results['error']: raise Exception(results['error']) send_results(results) except Exception as e: From 22a97096ca97f8fd697e83059b12fa3dd88193ed Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Fri, 2 Oct 2020 12:21:24 +0300 Subject: [PATCH 049/152] Altered SS rule dropdowns to display resource name whenever possible, and to display more proper value --- .../zerotrust/scoutsuite/ResourceDropdown.js | 9 +++++---- .../zerotrust/scoutsuite/RuleDisplay.js | 5 ++++- .../zerotrust/scoutsuite/ScoutSuiteDataParser.js | 16 ++++++++++++++++ 3 files changed, 25 insertions(+), 5 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/scoutsuite/ResourceDropdown.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/scoutsuite/ResourceDropdown.js index 03343d901..212d37331 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/scoutsuite/ResourceDropdown.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/scoutsuite/ResourceDropdown.js @@ -12,6 +12,8 @@ import {faArrowRight} from '@fortawesome/free-solid-svg-icons'; export default function ResourceDropdown(props) { const [isCollapseOpen, setIsCollapseOpen] = useState(false); + let parser = new ScoutSuiteDataParser(props.scoutsuite_data.data.services); + let resource_value = parser.getResourceValue(props.resource_path, props.template_path); function getResourceDropdown() { return ( @@ -20,7 +22,7 @@ export default function ResourceDropdown(props) {
-

+

Resources checked:

@@ -28,7 +28,10 @@ export default function RuleDisplay(props) { function getReferences() { let references = [] props.rule.references.forEach(reference => { - references.push({reference}) + references.push({reference}) }) return (
@@ -40,7 +43,8 @@ export default function RuleDisplay(props) { function getResources() { let resources = [] props.rule.items.forEach(item => { - let template_path = props.rule.hasOwnProperty('display_path') ? props.rule.display_path : props.rule.path; + let template_path = Object.prototype.hasOwnProperty.call(props.rule, 'display_path') + ? props.rule.display_path : props.rule.path; resources.push() diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/scoutsuite/ScoutSuiteSingleRuleDropdown.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/scoutsuite/ScoutSuiteSingleRuleDropdown.js index 6c0fb6cf5..3500ffe20 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/scoutsuite/ScoutSuiteSingleRuleDropdown.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/scoutsuite/ScoutSuiteSingleRuleDropdown.js @@ -53,13 +53,13 @@ export default function ScoutSuiteSingleRuleDropdown(props) { let ruleStatus = getRuleStatus(props.rule); switch (ruleStatus) { case STATUSES.STATUS_PASSED: - return "collapse-success"; + return 'collapse-success'; case STATUSES.STATUS_VERIFY: - return "collapse-danger"; + return 'collapse-danger'; case STATUSES.STATUS_FAILED: - return "collapse-danger"; + return 'collapse-danger'; case STATUSES.STATUS_UNEXECUTED: - return "collapse-default"; + return 'collapse-default'; } } From abe20c6a3aedbceca511fd0ba0eaafdbc9d86b6b Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 7 Oct 2020 10:32:33 +0300 Subject: [PATCH 069/152] Once again fixed isort bug in travis and fixed some imports --- .travis.yml | 5 ++-- .../analyzers/performance_analyzer.py | 3 +-- .../island_client/monkey_island_client.py | 3 +-- .../island_client/monkey_island_requests.py | 3 +-- .../log_handlers/test_logs_handler.py | 6 ++--- envs/monkey_zoo/blackbox/test_blackbox.py | 24 +++++++------------ .../performance/endpoint_performance_test.py | 12 ++++------ .../tests/performance/map_generation.py | 9 +++---- .../map_generation_from_telemetries.py | 6 ++--- .../performance/performance_test_workflow.py | 6 ++--- .../tests/performance/report_generation.py | 9 +++---- .../report_generation_from_telemetries.py | 6 ++--- .../sample_multiplier/sample_multiplier.py | 6 ++--- .../performance/telemetry_performance_test.py | 15 ++++-------- .../telemetry_performance_test_workflow.py | 9 +++---- envs/os_compatibility/test_compatibility.py | 3 +-- 16 files changed, 43 insertions(+), 82 deletions(-) diff --git a/.travis.yml b/.travis.yml index 6722830c0..f5331e911 100644 --- a/.travis.yml +++ b/.travis.yml @@ -77,11 +77,12 @@ script: - if [ $(tail -n 1 flake8_warnings.txt) -gt $PYTHON_WARNINGS_AMOUNT_UPPER_LIMIT ]; then echo "Too many python linter warnings! Failing this build. Lower the amount of linter errors in this and try again. " && exit 1; fi ## Check import order -- python -m isort . -c -p common -p infection_monkey -p monkey_island --skip ./common/cloud/scoutsuite --skip ./monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators_list.py -l 120 --wl 120 +- python -m isort . -c -p common -p infection_monkey -p monkey_island --skip ./monkey/common/cloud/scoutsuite --skip ./monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators_list.py -l 120 --wl 120 ## Run unit tests - cd monkey # This is our source dir -- python -m pytest --ignore=./common/cloud/scoutsuite # Have to use `python -m pytest` instead of `pytest` to add "{$builddir}/monkey/monkey" to sys.path. +- python -m pytest --ignore=./monkey/ + common/cloud/scoutsuite # Have to use `python -m pytest` instead of `pytest` to add "{$builddir}/monkey/monkey" to sys.path. ## Calculate Code Coverage - coverage run -m pytest diff --git a/envs/monkey_zoo/blackbox/analyzers/performance_analyzer.py b/envs/monkey_zoo/blackbox/analyzers/performance_analyzer.py index e0354530e..4a43ab6a5 100644 --- a/envs/monkey_zoo/blackbox/analyzers/performance_analyzer.py +++ b/envs/monkey_zoo/blackbox/analyzers/performance_analyzer.py @@ -3,8 +3,7 @@ from datetime import timedelta from typing import Dict from envs.monkey_zoo.blackbox.analyzers.analyzer import Analyzer -from envs.monkey_zoo.blackbox.tests.performance.performance_test_config import \ - PerformanceTestConfig +from envs.monkey_zoo.blackbox.tests.performance.performance_test_config import PerformanceTestConfig LOGGER = logging.getLogger(__name__) diff --git a/envs/monkey_zoo/blackbox/island_client/monkey_island_client.py b/envs/monkey_zoo/blackbox/island_client/monkey_island_client.py index 5932022fb..e3ecb6eb8 100644 --- a/envs/monkey_zoo/blackbox/island_client/monkey_island_client.py +++ b/envs/monkey_zoo/blackbox/island_client/monkey_island_client.py @@ -4,8 +4,7 @@ from time import sleep from bson import json_util -from envs.monkey_zoo.blackbox.island_client.monkey_island_requests import \ - MonkeyIslandRequests +from envs.monkey_zoo.blackbox.island_client.monkey_island_requests import MonkeyIslandRequests SLEEP_BETWEEN_REQUESTS_SECONDS = 0.5 MONKEY_TEST_ENDPOINT = 'api/test/monkey' diff --git a/envs/monkey_zoo/blackbox/island_client/monkey_island_requests.py b/envs/monkey_zoo/blackbox/island_client/monkey_island_requests.py index 9a98c1e06..226a0043c 100644 --- a/envs/monkey_zoo/blackbox/island_client/monkey_island_requests.py +++ b/envs/monkey_zoo/blackbox/island_client/monkey_island_requests.py @@ -5,8 +5,7 @@ from typing import Dict import requests -from envs.monkey_zoo.blackbox.island_client.supported_request_method import \ - SupportedRequestMethod +from envs.monkey_zoo.blackbox.island_client.supported_request_method import SupportedRequestMethod # SHA3-512 of '1234567890!@#$%^&*()_nothing_up_my_sleeve_1234567890!@#$%^&*()' NO_AUTH_CREDS = '55e97c9dcfd22b8079189ddaeea9bce8125887e3237b800c6176c9afa80d2062' \ diff --git a/envs/monkey_zoo/blackbox/log_handlers/test_logs_handler.py b/envs/monkey_zoo/blackbox/log_handlers/test_logs_handler.py index 3f5cfc191..bae6a9adc 100644 --- a/envs/monkey_zoo/blackbox/log_handlers/test_logs_handler.py +++ b/envs/monkey_zoo/blackbox/log_handlers/test_logs_handler.py @@ -2,10 +2,8 @@ import logging import os import shutil -from envs.monkey_zoo.blackbox.log_handlers.monkey_log_parser import \ - MonkeyLogParser -from envs.monkey_zoo.blackbox.log_handlers.monkey_logs_downloader import \ - MonkeyLogsDownloader +from envs.monkey_zoo.blackbox.log_handlers.monkey_log_parser import MonkeyLogParser +from envs.monkey_zoo.blackbox.log_handlers.monkey_logs_downloader import MonkeyLogsDownloader LOG_DIR_NAME = 'logs' LOGGER = logging.getLogger(__name__) diff --git a/envs/monkey_zoo/blackbox/test_blackbox.py b/envs/monkey_zoo/blackbox/test_blackbox.py index 45751452e..ce5e34ec0 100644 --- a/envs/monkey_zoo/blackbox/test_blackbox.py +++ b/envs/monkey_zoo/blackbox/test_blackbox.py @@ -4,25 +4,17 @@ from time import sleep import pytest -from envs.monkey_zoo.blackbox.analyzers.communication_analyzer import \ - CommunicationAnalyzer -from envs.monkey_zoo.blackbox.island_client.island_config_parser import \ - IslandConfigParser -from envs.monkey_zoo.blackbox.island_client.monkey_island_client import \ - MonkeyIslandClient -from envs.monkey_zoo.blackbox.log_handlers.test_logs_handler import \ - TestLogsHandler +from envs.monkey_zoo.blackbox.analyzers.communication_analyzer import CommunicationAnalyzer +from envs.monkey_zoo.blackbox.island_client.island_config_parser import IslandConfigParser +from envs.monkey_zoo.blackbox.island_client.monkey_island_client import MonkeyIslandClient +from envs.monkey_zoo.blackbox.log_handlers.test_logs_handler import TestLogsHandler from envs.monkey_zoo.blackbox.tests.exploitation import ExploitationTest -from envs.monkey_zoo.blackbox.tests.performance.map_generation import \ - MapGenerationTest -from envs.monkey_zoo.blackbox.tests.performance.map_generation_from_telemetries import \ - MapGenerationFromTelemetryTest -from envs.monkey_zoo.blackbox.tests.performance.report_generation import \ - ReportGenerationTest +from envs.monkey_zoo.blackbox.tests.performance.map_generation import MapGenerationTest +from envs.monkey_zoo.blackbox.tests.performance.map_generation_from_telemetries import MapGenerationFromTelemetryTest +from envs.monkey_zoo.blackbox.tests.performance.report_generation import ReportGenerationTest from envs.monkey_zoo.blackbox.tests.performance.report_generation_from_telemetries import \ ReportGenerationFromTelemetryTest -from envs.monkey_zoo.blackbox.tests.performance.telemetry_performance_test import \ - TelemetryPerformanceTest +from envs.monkey_zoo.blackbox.tests.performance.telemetry_performance_test import TelemetryPerformanceTest from envs.monkey_zoo.blackbox.utils import gcp_machine_handlers DEFAULT_TIMEOUT_SECONDS = 5*60 diff --git a/envs/monkey_zoo/blackbox/tests/performance/endpoint_performance_test.py b/envs/monkey_zoo/blackbox/tests/performance/endpoint_performance_test.py index e08ac2824..b8793452d 100644 --- a/envs/monkey_zoo/blackbox/tests/performance/endpoint_performance_test.py +++ b/envs/monkey_zoo/blackbox/tests/performance/endpoint_performance_test.py @@ -1,14 +1,10 @@ import logging -from envs.monkey_zoo.blackbox.analyzers.performance_analyzer import \ - PerformanceAnalyzer -from envs.monkey_zoo.blackbox.island_client.monkey_island_client import \ - MonkeyIslandClient -from envs.monkey_zoo.blackbox.island_client.supported_request_method import \ - SupportedRequestMethod +from envs.monkey_zoo.blackbox.analyzers.performance_analyzer import PerformanceAnalyzer +from envs.monkey_zoo.blackbox.island_client.monkey_island_client import MonkeyIslandClient +from envs.monkey_zoo.blackbox.island_client.supported_request_method import SupportedRequestMethod from envs.monkey_zoo.blackbox.tests.basic_test import BasicTest -from envs.monkey_zoo.blackbox.tests.performance.performance_test_config import \ - PerformanceTestConfig +from envs.monkey_zoo.blackbox.tests.performance.performance_test_config import PerformanceTestConfig LOGGER = logging.getLogger(__name__) diff --git a/envs/monkey_zoo/blackbox/tests/performance/map_generation.py b/envs/monkey_zoo/blackbox/tests/performance/map_generation.py index 926e5331e..eb95fdc6a 100644 --- a/envs/monkey_zoo/blackbox/tests/performance/map_generation.py +++ b/envs/monkey_zoo/blackbox/tests/performance/map_generation.py @@ -1,12 +1,9 @@ from datetime import timedelta from envs.monkey_zoo.blackbox.tests.exploitation import ExploitationTest -from envs.monkey_zoo.blackbox.tests.performance.performance_test import \ - PerformanceTest -from envs.monkey_zoo.blackbox.tests.performance.performance_test_config import \ - PerformanceTestConfig -from envs.monkey_zoo.blackbox.tests.performance.performance_test_workflow import \ - PerformanceTestWorkflow +from envs.monkey_zoo.blackbox.tests.performance.performance_test import PerformanceTest +from envs.monkey_zoo.blackbox.tests.performance.performance_test_config import PerformanceTestConfig +from envs.monkey_zoo.blackbox.tests.performance.performance_test_workflow import PerformanceTestWorkflow MAX_ALLOWED_SINGLE_PAGE_TIME = timedelta(seconds=2) MAX_ALLOWED_TOTAL_TIME = timedelta(seconds=5) diff --git a/envs/monkey_zoo/blackbox/tests/performance/map_generation_from_telemetries.py b/envs/monkey_zoo/blackbox/tests/performance/map_generation_from_telemetries.py index 1ee1b60da..1b31a8962 100644 --- a/envs/monkey_zoo/blackbox/tests/performance/map_generation_from_telemetries.py +++ b/envs/monkey_zoo/blackbox/tests/performance/map_generation_from_telemetries.py @@ -1,9 +1,7 @@ from datetime import timedelta -from envs.monkey_zoo.blackbox.tests.performance.performance_test import \ - PerformanceTest -from envs.monkey_zoo.blackbox.tests.performance.performance_test_config import \ - PerformanceTestConfig +from envs.monkey_zoo.blackbox.tests.performance.performance_test import PerformanceTest +from envs.monkey_zoo.blackbox.tests.performance.performance_test_config import PerformanceTestConfig from envs.monkey_zoo.blackbox.tests.performance.telemetry_performance_test_workflow import \ TelemetryPerformanceTestWorkflow diff --git a/envs/monkey_zoo/blackbox/tests/performance/performance_test_workflow.py b/envs/monkey_zoo/blackbox/tests/performance/performance_test_workflow.py index 5f08c976c..4e708ed9d 100644 --- a/envs/monkey_zoo/blackbox/tests/performance/performance_test_workflow.py +++ b/envs/monkey_zoo/blackbox/tests/performance/performance_test_workflow.py @@ -1,9 +1,7 @@ from envs.monkey_zoo.blackbox.tests.basic_test import BasicTest from envs.monkey_zoo.blackbox.tests.exploitation import ExploitationTest -from envs.monkey_zoo.blackbox.tests.performance.endpoint_performance_test import \ - EndpointPerformanceTest -from envs.monkey_zoo.blackbox.tests.performance.performance_test_config import \ - PerformanceTestConfig +from envs.monkey_zoo.blackbox.tests.performance.endpoint_performance_test import EndpointPerformanceTest +from envs.monkey_zoo.blackbox.tests.performance.performance_test_config import PerformanceTestConfig class PerformanceTestWorkflow(BasicTest): diff --git a/envs/monkey_zoo/blackbox/tests/performance/report_generation.py b/envs/monkey_zoo/blackbox/tests/performance/report_generation.py index eec8f067d..e204cc29f 100644 --- a/envs/monkey_zoo/blackbox/tests/performance/report_generation.py +++ b/envs/monkey_zoo/blackbox/tests/performance/report_generation.py @@ -1,12 +1,9 @@ from datetime import timedelta from envs.monkey_zoo.blackbox.tests.exploitation import ExploitationTest -from envs.monkey_zoo.blackbox.tests.performance.performance_test import \ - PerformanceTest -from envs.monkey_zoo.blackbox.tests.performance.performance_test_config import \ - PerformanceTestConfig -from envs.monkey_zoo.blackbox.tests.performance.performance_test_workflow import \ - PerformanceTestWorkflow +from envs.monkey_zoo.blackbox.tests.performance.performance_test import PerformanceTest +from envs.monkey_zoo.blackbox.tests.performance.performance_test_config import PerformanceTestConfig +from envs.monkey_zoo.blackbox.tests.performance.performance_test_workflow import PerformanceTestWorkflow MAX_ALLOWED_SINGLE_PAGE_TIME = timedelta(seconds=2) MAX_ALLOWED_TOTAL_TIME = timedelta(seconds=5) diff --git a/envs/monkey_zoo/blackbox/tests/performance/report_generation_from_telemetries.py b/envs/monkey_zoo/blackbox/tests/performance/report_generation_from_telemetries.py index 1cba745bf..abc2b35c2 100644 --- a/envs/monkey_zoo/blackbox/tests/performance/report_generation_from_telemetries.py +++ b/envs/monkey_zoo/blackbox/tests/performance/report_generation_from_telemetries.py @@ -1,9 +1,7 @@ from datetime import timedelta -from envs.monkey_zoo.blackbox.tests.performance.performance_test import \ - PerformanceTest -from envs.monkey_zoo.blackbox.tests.performance.performance_test_config import \ - PerformanceTestConfig +from envs.monkey_zoo.blackbox.tests.performance.performance_test import PerformanceTest +from envs.monkey_zoo.blackbox.tests.performance.performance_test_config import PerformanceTestConfig from envs.monkey_zoo.blackbox.tests.performance.telemetry_performance_test_workflow import \ TelemetryPerformanceTestWorkflow diff --git a/envs/monkey_zoo/blackbox/tests/performance/telem_sample_parsing/sample_multiplier/sample_multiplier.py b/envs/monkey_zoo/blackbox/tests/performance/telem_sample_parsing/sample_multiplier/sample_multiplier.py index e5b0a52cd..cb5956025 100644 --- a/envs/monkey_zoo/blackbox/tests/performance/telem_sample_parsing/sample_multiplier/sample_multiplier.py +++ b/envs/monkey_zoo/blackbox/tests/performance/telem_sample_parsing/sample_multiplier/sample_multiplier.py @@ -6,12 +6,10 @@ from typing import Dict, List from tqdm import tqdm -from envs.monkey_zoo.blackbox.tests.performance.telem_sample_parsing.sample_file_parser import \ - SampleFileParser +from envs.monkey_zoo.blackbox.tests.performance.telem_sample_parsing.sample_file_parser import SampleFileParser from envs.monkey_zoo.blackbox.tests.performance.telem_sample_parsing.sample_multiplier.fake_ip_generator import \ FakeIpGenerator -from envs.monkey_zoo.blackbox.tests.performance.telem_sample_parsing.sample_multiplier.fake_monkey import \ - FakeMonkey +from envs.monkey_zoo.blackbox.tests.performance.telem_sample_parsing.sample_multiplier.fake_monkey import FakeMonkey TELEM_DIR_PATH = './tests/performance/telemetry_sample' LOGGER = logging.getLogger(__name__) diff --git a/envs/monkey_zoo/blackbox/tests/performance/telemetry_performance_test.py b/envs/monkey_zoo/blackbox/tests/performance/telemetry_performance_test.py index 75802449e..699876cce 100644 --- a/envs/monkey_zoo/blackbox/tests/performance/telemetry_performance_test.py +++ b/envs/monkey_zoo/blackbox/tests/performance/telemetry_performance_test.py @@ -4,16 +4,11 @@ from datetime import timedelta from tqdm import tqdm -from envs.monkey_zoo.blackbox.analyzers.performance_analyzer import \ - PerformanceAnalyzer -from envs.monkey_zoo.blackbox.island_client.monkey_island_client import \ - MonkeyIslandClient -from envs.monkey_zoo.blackbox.island_client.supported_request_method import \ - SupportedRequestMethod -from envs.monkey_zoo.blackbox.tests.performance.performance_test_config import \ - PerformanceTestConfig -from envs.monkey_zoo.blackbox.tests.performance.telem_sample_parsing.sample_file_parser import \ - SampleFileParser +from envs.monkey_zoo.blackbox.analyzers.performance_analyzer import PerformanceAnalyzer +from envs.monkey_zoo.blackbox.island_client.monkey_island_client import MonkeyIslandClient +from envs.monkey_zoo.blackbox.island_client.supported_request_method import SupportedRequestMethod +from envs.monkey_zoo.blackbox.tests.performance.performance_test_config import PerformanceTestConfig +from envs.monkey_zoo.blackbox.tests.performance.telem_sample_parsing.sample_file_parser import SampleFileParser LOGGER = logging.getLogger(__name__) diff --git a/envs/monkey_zoo/blackbox/tests/performance/telemetry_performance_test_workflow.py b/envs/monkey_zoo/blackbox/tests/performance/telemetry_performance_test_workflow.py index b63d904e1..6d09752ca 100644 --- a/envs/monkey_zoo/blackbox/tests/performance/telemetry_performance_test_workflow.py +++ b/envs/monkey_zoo/blackbox/tests/performance/telemetry_performance_test_workflow.py @@ -1,10 +1,7 @@ from envs.monkey_zoo.blackbox.tests.basic_test import BasicTest -from envs.monkey_zoo.blackbox.tests.performance.endpoint_performance_test import \ - EndpointPerformanceTest -from envs.monkey_zoo.blackbox.tests.performance.performance_test_config import \ - PerformanceTestConfig -from envs.monkey_zoo.blackbox.tests.performance.telemetry_performance_test import \ - TelemetryPerformanceTest +from envs.monkey_zoo.blackbox.tests.performance.endpoint_performance_test import EndpointPerformanceTest +from envs.monkey_zoo.blackbox.tests.performance.performance_test_config import PerformanceTestConfig +from envs.monkey_zoo.blackbox.tests.performance.telemetry_performance_test import TelemetryPerformanceTest class TelemetryPerformanceTestWorkflow(BasicTest): diff --git a/envs/os_compatibility/test_compatibility.py b/envs/os_compatibility/test_compatibility.py index 17d2d3735..1cf5220bb 100644 --- a/envs/os_compatibility/test_compatibility.py +++ b/envs/os_compatibility/test_compatibility.py @@ -1,7 +1,6 @@ import pytest -from envs.monkey_zoo.blackbox.island_client.monkey_island_client import \ - MonkeyIslandClient +from envs.monkey_zoo.blackbox.island_client.monkey_island_client import MonkeyIslandClient machine_list = { "10.0.0.36": "centos_6", From 1af19dc8fa55bb9dfd30183d0599971e24bab40c Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 7 Oct 2020 10:39:44 +0300 Subject: [PATCH 070/152] Fixed more bugs in travis, related to pytest and coverage --- .travis.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index f5331e911..1c0845e29 100644 --- a/.travis.yml +++ b/.travis.yml @@ -81,11 +81,10 @@ script: ## Run unit tests - cd monkey # This is our source dir -- python -m pytest --ignore=./monkey/ - common/cloud/scoutsuite # Have to use `python -m pytest` instead of `pytest` to add "{$builddir}/monkey/monkey" to sys.path. +- python -m pytest --ignore=./common/cloud/scoutsuite # Have to use `python -m pytest` instead of `pytest` to add "{$builddir}/monkey/monkey" to sys.path. ## Calculate Code Coverage -- coverage run -m pytest +- coverage run -m pytest --ignore=./common/cloud/scoutsuite # Check JS code. The npm install must happen AFTER the flake8 because the node_modules folder will cause a lot of errors. - cd monkey_island/cc/ui From 94b960fe423a111130390f42136eb1dcee233342 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 7 Oct 2020 10:55:03 +0300 Subject: [PATCH 071/152] Last travis bugfix regarding isort --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 1c0845e29..196138558 100644 --- a/.travis.yml +++ b/.travis.yml @@ -77,7 +77,7 @@ script: - if [ $(tail -n 1 flake8_warnings.txt) -gt $PYTHON_WARNINGS_AMOUNT_UPPER_LIMIT ]; then echo "Too many python linter warnings! Failing this build. Lower the amount of linter errors in this and try again. " && exit 1; fi ## Check import order -- python -m isort . -c -p common -p infection_monkey -p monkey_island --skip ./monkey/common/cloud/scoutsuite --skip ./monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators_list.py -l 120 --wl 120 +- python -m isort . -c -p common -p infection_monkey -p monkey_island --skip ./monkey/common/cloud/scoutsuite --skip ./monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators_list.py -l 120 --wl 120 ## Run unit tests - cd monkey # This is our source dir From 01ee60b1bf9b498516c1d458ba79c77e3897a7e5 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 7 Oct 2020 11:15:00 +0300 Subject: [PATCH 072/152] Fixed broken swimm units --- .swm/OwcKMnALpn7tuBaJY1US.swm | 8 ++++---- .swm/tbxb2cGgUiJQ8Btma0fp.swm | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.swm/OwcKMnALpn7tuBaJY1US.swm b/.swm/OwcKMnALpn7tuBaJY1US.swm index 8a3b1eedc..6b6d0abc3 100644 --- a/.swm/OwcKMnALpn7tuBaJY1US.swm +++ b/.swm/OwcKMnALpn7tuBaJY1US.swm @@ -9,13 +9,13 @@ "Make sure you add the new collector to the configuration in all relevant places, including making it ON by default!" ], "files": { - "monkey/common/data/system_info_collectors_names.py": { + "monkey/common/common_consts/system_info_collectors_names.py": { "index": [ "175a054e..3b478dc9", "100644" ], - "fileA": "monkey/common/data/system_info_collectors_names.py", - "fileB": "monkey/common/data/system_info_collectors_names.py", + "fileA": "monkey/common/common_consts/system_info_collectors_names.py", + "fileB": "monkey/common/common_consts/system_info_collectors_names.py", "status": "MODIFIED", "numLineDeletions": 1, "numLineAdditions": 1, @@ -103,4 +103,4 @@ "summary": "U3lzdGVtJTIwaW5mbyUyMGNvbGxlY3RvcnMlMjBhcmUlMjB1c2VmdWwlMjB0byUyMGdldCUyMG1vcmUlMjBkYXRhJTIwZm9yJTIwdmFyaW91cyUyMHRoaW5ncyUyQyUyMHN1Y2glMjBhcyUyMFpUJTIwdGVzdHMlMjBvciUyME1JVFJFJTIwdGVjaG5pcXVlcy4lMjBUYWtlJTIwYSUyMGxvb2slMjBhdCUyMHNvbWUlMjBvdGhlciUyMHRlY2huaXF1ZXMh", "file_version": "1.0.2", "app_version": "0.1.60" -} \ No newline at end of file +} diff --git a/.swm/tbxb2cGgUiJQ8Btma0fp.swm b/.swm/tbxb2cGgUiJQ8Btma0fp.swm index 2a2c57cd4..5595eafaa 100644 --- a/.swm/tbxb2cGgUiJQ8Btma0fp.swm +++ b/.swm/tbxb2cGgUiJQ8Btma0fp.swm @@ -7,13 +7,13 @@ "Make sure to add the PBA to the configuration as well." ], "files": { - "monkey/common/data/post_breach_consts.py": { + "monkey/common/common_consts/post_breach_consts.py": { "index": [ "c3bba995..031f9ad0", "100644" ], - "fileA": "monkey/common/data/post_breach_consts.py", - "fileB": "monkey/common/data/post_breach_consts.py", + "fileA": "monkey/common/common_consts/post_breach_consts.py", + "fileB": "monkey/common/common_consts/post_breach_consts.py", "status": "MODIFIED", "numLineDeletions": 1, "numLineAdditions": 1, @@ -71,4 +71,4 @@ "summary": "VGFrZSUyMGElMjBsb29rJTIwYXQlMjB0aGUlMjBjb25maWd1cmF0aW9uJTIwb2YlMjB0aGUlMjBpc2xhbmQlMjBhZ2FpbiUyMC0lMjBzZWUlMjB0aGUlMjAlMjJjb21tYW5kJTIwdG8lMjBydW4lMjBhZnRlciUyMGJyZWFjaCUyMiUyMG9wdGlvbiUyMHdlJTIwb2ZmZXIlMjB0aGUlMjB1c2VyJTNGJTIwSXQncyUyMGltcGxlbWVudGVkJTIwZXhhY3RseSUyMGxpa2UlMjB5b3UlMjBkaWQlMjByaWdodCUyMG5vdyUyMGJ1dCUyMGVhY2glMjB1c2VyJTIwY2FuJTIwZG8lMjBpdCUyMGZvciUyMHRoZW1zZWx2ZXMuJTIwJTBBJTBBSG93ZXZlciUyQyUyMHdoYXQlMjBpZiUyMHRoZSUyMFBCQSUyMG5lZWRzJTIwdG8lMjBkbyUyMHN0dWZmJTIwd2hpY2glMjBpcyUyMG1vcmUlMjBjb21wbGV4JTIwdGhhbiUyMGp1c3QlMjBydW5uaW5nJTIwYSUyMGZldyUyMGNvbW1hbmRzJTNGJTIwSW4lMjB0aGF0JTIwY2FzZS4uLiUyMA==", "file_version": "1.0.2", "app_version": "0.1.60" -} \ No newline at end of file +} From adb617d6527a6618be71672a8ffe79a7bc43a8ca Mon Sep 17 00:00:00 2001 From: Swimm Date: Wed, 7 Oct 2020 20:48:16 +0300 Subject: [PATCH 073/152] Swimm: updated unit (OwcKMnALpn7tuBaJY1US) - fixing and upgrading to scheme 1.0.3 --- .swm/OwcKMnALpn7tuBaJY1US.swm | 244 +++++++++++++++++++++++----------- 1 file changed, 167 insertions(+), 77 deletions(-) diff --git a/.swm/OwcKMnALpn7tuBaJY1US.swm b/.swm/OwcKMnALpn7tuBaJY1US.swm index 6b6d0abc3..00ca3bc5d 100644 --- a/.swm/OwcKMnALpn7tuBaJY1US.swm +++ b/.swm/OwcKMnALpn7tuBaJY1US.swm @@ -1,6 +1,9 @@ { "id": "OwcKMnALpn7tuBaJY1US", "name": "Add a new System Info Collector", + "dod": "Add a system info collector that collects the machine hostname.", + "description": "# What are system info collectors?\n\nWell, the name pretty much explains it. They are Monkey classes which collect various information regarding the victim system, such as Environment, SSH Info, Process List, Netstat and more. \n\n## What should I add? \n\nA system info collector which collects the hostname of the system.\n\n## Test manually\n\nOnce you're done, make sure that your collector:\n* Appears in the Island configuration, and is enabled by default\n* The collector actually runs when executing a Monkey.\n* Results show up in the relevant places:\n * The infection map.\n * The security report.\n * The relevant MITRE techniques.\n\n**There are a lot of hints for this unit - don't be afraid to use them!**", + "summary": "System info collectors are useful to get more data for various things, such as ZT tests or MITRE techniques. Take a look at some other techniques!", "tests": [], "hints": [ "First thing you should do is take a look at a different collector (like EnvironemntCollector) and 100% understand how it runs, how results are relayed back to the server, and how the server processes the data.", @@ -8,99 +11,186 @@ "Take a look at SystemInfoCollector - that's the base class you'll need to implement", "Make sure you add the new collector to the configuration in all relevant places, including making it ON by default!" ], - "files": { + "swimmPatch": { "monkey/common/common_consts/system_info_collectors_names.py": { - "index": [ - "175a054e..3b478dc9", - "100644" - ], - "fileA": "monkey/common/common_consts/system_info_collectors_names.py", - "fileB": "monkey/common/common_consts/system_info_collectors_names.py", - "status": "MODIFIED", - "numLineDeletions": 1, - "numLineAdditions": 1, - "hunkContainers": [ - "JTdCJTIyaHVuayUyMiUzQSU3QiUyMmhlYWRlciUyMiUzQSUyMiU0MCU0MCUyMC0xJTJDNSUyMCUyQjElMkM1JTIwJTQwJTQwJTIyJTJDJTIyY2hhbmdlcyUyMiUzQSU1QiU3QiUyMnR5cGUlMjIlM0ElMjJjb250ZXh0JTIyJTJDJTIyZGF0YSUyMiUzQSUyMiUyMEFXU19DT0xMRUNUT1IlMjAlM0QlMjAlNUMlMjJBd3NDb2xsZWN0b3IlNUMlMjIlMjIlMkMlMjJsaW5lTnVtYmVycyUyMiUzQSU3QiUyMmElMjIlM0ExJTJDJTIyYiUyMiUzQTElN0QlN0QlMkMlN0IlMjJ0eXBlJTIyJTNBJTIyZGVsJTIyJTJDJTIybWFyayUyMiUzQSUyMi0lMjIlMkMlMjJkYXRhJTIyJTNBJTIySE9TVE5BTUVfQ09MTEVDVE9SJTIwJTNEJTIwJTVDJTIySG9zdG5hbWVDb2xsZWN0b3IlNUMlMjIlMjIlMkMlMjJsaW5lTnVtYmVycyUyMiUzQSU3QiUyMmElMjIlM0EyJTdEJTdEJTJDJTdCJTIydHlwZSUyMiUzQSUyMmFkZCUyMiUyQyUyMm1hcmslMjIlM0ElMjIlMkIlMjIlMkMlMjJkYXRhJTIyJTNBJTIyJTIzJTIwU1dJTU1FUiUzQSUyMENvbGxlY3RvciUyMG5hbWUlMjBnb2VzJTIwaGVyZS4lMjIlMkMlMjJsaW5lTnVtYmVycyUyMiUzQSU3QiUyMmIlMjIlM0EyJTdEJTdEJTJDJTdCJTIydHlwZSUyMiUzQSUyMmNvbnRleHQlMjIlMkMlMjJkYXRhJTIyJTNBJTIyJTIwRU5WSVJPTk1FTlRfQ09MTEVDVE9SJTIwJTNEJTIwJTVDJTIyRW52aXJvbm1lbnRDb2xsZWN0b3IlNUMlMjIlMjIlMkMlMjJsaW5lTnVtYmVycyUyMiUzQSU3QiUyMmElMjIlM0EzJTJDJTIyYiUyMiUzQTMlN0QlN0QlMkMlN0IlMjJ0eXBlJTIyJTNBJTIyY29udGV4dCUyMiUyQyUyMmRhdGElMjIlM0ElMjIlMjBQUk9DRVNTX0xJU1RfQ09MTEVDVE9SJTIwJTNEJTIwJTVDJTIyUHJvY2Vzc0xpc3RDb2xsZWN0b3IlNUMlMjIlMjIlMkMlMjJsaW5lTnVtYmVycyUyMiUzQSU3QiUyMmElMjIlM0E0JTJDJTIyYiUyMiUzQTQlN0QlN0QlMkMlN0IlMjJ0eXBlJTIyJTNBJTIyY29udGV4dCUyMiUyQyUyMmRhdGElMjIlM0ElMjIlMjBNSU1JS0FUWl9DT0xMRUNUT1IlMjAlM0QlMjAlNUMlMjJNaW1pa2F0ekNvbGxlY3RvciU1QyUyMiUyMiUyQyUyMmxpbmVOdW1iZXJzJTIyJTNBJTdCJTIyYSUyMiUzQTUlMkMlMjJiJTIyJTNBNSU3RCU3RCU1RCUyQyUyMmxpbmVOdW1iZXJzJTIyJTNBJTdCJTIyYSUyMiUzQSU3QiUyMnN0YXJ0TGluZSUyMiUzQTElMkMlMjJsaW5lc0NvdW50JTIyJTNBNSU3RCUyQyUyMmIlMjIlM0ElN0IlMjJzdGFydExpbmUlMjIlM0ExJTJDJTIybGluZXNDb3VudCUyMiUzQTUlN0QlN0QlN0QlN0Q=" + "diffType": "MODIFIED", + "fileDiffHeader": "diff --git a/monkey/common/common_consts/system_info_collectors_names.py b/monkey/common/common_consts/system_info_collectors_names.py\nindex c93cb253..bce6f86e 100644\n--- a/monkey/common/common_consts/system_info_collectors_names.py\n+++ b/monkey/common/common_consts/system_info_collectors_names.py", + "hunks": [ + { + "swimmHunkMetadata": {}, + "hunkDiffLines": [ + "@@ -1,5 +1,5 @@", + " AWS_COLLECTOR = \"AwsCollector\"", + "-HOSTNAME_COLLECTOR = \"HostnameCollector\"", + "+# SWIMMER: Collector name goes here.", + " ENVIRONMENT_COLLECTOR = \"EnvironmentCollector\"", + " PROCESS_LIST_COLLECTOR = \"ProcessListCollector\"", + " MIMIKATZ_COLLECTOR = \"MimikatzCollector\"" + ] + } ] }, "monkey/infection_monkey/system_info/collectors/hostname_collector.py": { - "index": [ - "ae956081..4dc6701a", - "100644" - ], - "fileA": "monkey/infection_monkey/system_info/collectors/hostname_collector.py", - "fileB": "monkey/infection_monkey/system_info/collectors/hostname_collector.py", - "status": "MODIFIED", - "numLineDeletions": 12, - "numLineAdditions": 1, - "hunkContainers": [ - "JTdCJTIyaHVuayUyMiUzQSU3QiUyMmhlYWRlciUyMiUzQSUyMiU0MCU0MCUyMC0xJTJDMTYlMjAlMkIxJTJDNSUyMCU0MCU0MCUyMiUyQyUyMmNoYW5nZXMlMjIlM0ElNUIlN0IlMjJ0eXBlJTIyJTNBJTIyY29udGV4dCUyMiUyQyUyMmRhdGElMjIlM0ElMjIlMjBpbXBvcnQlMjBsb2dnaW5nJTIyJTJDJTIybGluZU51bWJlcnMlMjIlM0ElN0IlMjJhJTIyJTNBMSUyQyUyMmIlMjIlM0ExJTdEJTdEJTJDJTdCJTIydHlwZSUyMiUzQSUyMmRlbCUyMiUyQyUyMm1hcmslMjIlM0ElMjItJTIyJTJDJTIyZGF0YSUyMiUzQSUyMmltcG9ydCUyMHNvY2tldCUyMiUyQyUyMmxpbmVOdW1iZXJzJTIyJTNBJTdCJTIyYSUyMiUzQTIlN0QlN0QlMkMlN0IlMjJ0eXBlJTIyJTNBJTIyZGVsJTIyJTJDJTIybWFyayUyMiUzQSUyMi0lMjIlMkMlMjJkYXRhJTIyJTNBJTIyJTIyJTJDJTIybGluZU51bWJlcnMlMjIlM0ElN0IlMjJhJTIyJTNBMyU3RCU3RCUyQyU3QiUyMnR5cGUlMjIlM0ElMjJkZWwlMjIlMkMlMjJtYXJrJTIyJTNBJTIyLSUyMiUyQyUyMmRhdGElMjIlM0ElMjJmcm9tJTIwY29tbW9uLmRhdGEuc3lzdGVtX2luZm9fY29sbGVjdG9yc19uYW1lcyUyMGltcG9ydCUyMEhPU1ROQU1FX0NPTExFQ1RPUiUyMiUyQyUyMmxpbmVOdW1iZXJzJTIyJTNBJTdCJTIyYSUyMiUzQTQlN0QlN0QlMkMlN0IlMjJ0eXBlJTIyJTNBJTIyZGVsJTIyJTJDJTIybWFyayUyMiUzQSUyMi0lMjIlMkMlMjJkYXRhJTIyJTNBJTIyZnJvbSUyMGluZmVjdGlvbl9tb25rZXkuc3lzdGVtX2luZm8uc3lzdGVtX2luZm9fY29sbGVjdG9yJTIwaW1wb3J0JTIwJTVDJTVDJTIyJTJDJTIybGluZU51bWJlcnMlMjIlM0ElN0IlMjJhJTIyJTNBNSU3RCU3RCUyQyU3QiUyMnR5cGUlMjIlM0ElMjJkZWwlMjIlMkMlMjJtYXJrJTIyJTNBJTIyLSUyMiUyQyUyMmRhdGElMjIlM0ElMjIlMjAlMjAlMjAlMjBTeXN0ZW1JbmZvQ29sbGVjdG9yJTIyJTJDJTIybGluZU51bWJlcnMlMjIlM0ElN0IlMjJhJTIyJTNBNiU3RCU3RCUyQyU3QiUyMnR5cGUlMjIlM0ElMjJjb250ZXh0JTIyJTJDJTIyZGF0YSUyMiUzQSUyMiUyMCUyMiUyQyUyMmxpbmVOdW1iZXJzJTIyJTNBJTdCJTIyYSUyMiUzQTclMkMlMjJiJTIyJTNBMiU3RCU3RCUyQyU3QiUyMnR5cGUlMjIlM0ElMjJjb250ZXh0JTIyJTJDJTIyZGF0YSUyMiUzQSUyMiUyMGxvZ2dlciUyMCUzRCUyMGxvZ2dpbmcuZ2V0TG9nZ2VyKF9fbmFtZV9fKSUyMiUyQyUyMmxpbmVOdW1iZXJzJTIyJTNBJTdCJTIyYSUyMiUzQTglMkMlMjJiJTIyJTNBMyU3RCU3RCUyQyU3QiUyMnR5cGUlMjIlM0ElMjJjb250ZXh0JTIyJTJDJTIyZGF0YSUyMiUzQSUyMiUyMCUyMiUyQyUyMmxpbmVOdW1iZXJzJTIyJTNBJTdCJTIyYSUyMiUzQTklMkMlMjJiJTIyJTNBNCU3RCU3RCUyQyU3QiUyMnR5cGUlMjIlM0ElMjJkZWwlMjIlMkMlMjJtYXJrJTIyJTNBJTIyLSUyMiUyQyUyMmRhdGElMjIlM0ElMjIlMjIlMkMlMjJsaW5lTnVtYmVycyUyMiUzQSU3QiUyMmElMjIlM0ExMCU3RCU3RCUyQyU3QiUyMnR5cGUlMjIlM0ElMjJkZWwlMjIlMkMlMjJtYXJrJTIyJTNBJTIyLSUyMiUyQyUyMmRhdGElMjIlM0ElMjJjbGFzcyUyMEhvc3RuYW1lQ29sbGVjdG9yKFN5c3RlbUluZm9Db2xsZWN0b3IpJTNBJTIyJTJDJTIybGluZU51bWJlcnMlMjIlM0ElN0IlMjJhJTIyJTNBMTElN0QlN0QlMkMlN0IlMjJ0eXBlJTIyJTNBJTIyZGVsJTIyJTJDJTIybWFyayUyMiUzQSUyMi0lMjIlMkMlMjJkYXRhJTIyJTNBJTIyJTIwJTIwJTIwJTIwZGVmJTIwX19pbml0X18oc2VsZiklM0ElMjIlMkMlMjJsaW5lTnVtYmVycyUyMiUzQSU3QiUyMmElMjIlM0ExMiU3RCU3RCUyQyU3QiUyMnR5cGUlMjIlM0ElMjJkZWwlMjIlMkMlMjJtYXJrJTIyJTNBJTIyLSUyMiUyQyUyMmRhdGElMjIlM0ElMjIlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjBzdXBlcigpLl9faW5pdF9fKG5hbWUlM0RIT1NUTkFNRV9DT0xMRUNUT1IpJTIyJTJDJTIybGluZU51bWJlcnMlMjIlM0ElN0IlMjJhJTIyJTNBMTMlN0QlN0QlMkMlN0IlMjJ0eXBlJTIyJTNBJTIyZGVsJTIyJTJDJTIybWFyayUyMiUzQSUyMi0lMjIlMkMlMjJkYXRhJTIyJTNBJTIyJTIyJTJDJTIybGluZU51bWJlcnMlMjIlM0ElN0IlMjJhJTIyJTNBMTQlN0QlN0QlMkMlN0IlMjJ0eXBlJTIyJTNBJTIyZGVsJTIyJTJDJTIybWFyayUyMiUzQSUyMi0lMjIlMkMlMjJkYXRhJTIyJTNBJTIyJTIwJTIwJTIwJTIwZGVmJTIwY29sbGVjdChzZWxmKSUyMC0lM0UlMjBkaWN0JTNBJTIyJTJDJTIybGluZU51bWJlcnMlMjIlM0ElN0IlMjJhJTIyJTNBMTUlN0QlN0QlMkMlN0IlMjJ0eXBlJTIyJTNBJTIyZGVsJTIyJTJDJTIybWFyayUyMiUzQSUyMi0lMjIlMkMlMjJkYXRhJTIyJTNBJTIyJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwcmV0dXJuJTIwJTdCJTVDJTIyaG9zdG5hbWUlNUMlMjIlM0ElMjBzb2NrZXQuZ2V0ZnFkbigpJTdEJTIyJTJDJTIybGluZU51bWJlcnMlMjIlM0ElN0IlMjJhJTIyJTNBMTYlN0QlN0QlMkMlN0IlMjJ0eXBlJTIyJTNBJTIyYWRkJTIyJTJDJTIybWFyayUyMiUzQSUyMiUyQiUyMiUyQyUyMmRhdGElMjIlM0ElMjIlMjMlMjBTV0lNTUVSJTNBJTIwVGhlJTIwY29sbGVjdG9yJTIwY2xhc3MlMjBnb2VzJTIwaGVyZS4lMjIlMkMlMjJsaW5lTnVtYmVycyUyMiUzQSU3QiUyMmIlMjIlM0E1JTdEJTdEJTVEJTJDJTIybGluZU51bWJlcnMlMjIlM0ElN0IlMjJhJTIyJTNBJTdCJTIyc3RhcnRMaW5lJTIyJTNBMSUyQyUyMmxpbmVzQ291bnQlMjIlM0ExNiU3RCUyQyUyMmIlMjIlM0ElN0IlMjJzdGFydExpbmUlMjIlM0ExJTJDJTIybGluZXNDb3VudCUyMiUzQTUlN0QlN0QlN0QlN0Q=" + "diffType": "MODIFIED", + "fileDiffHeader": "diff --git a/monkey/infection_monkey/system_info/collectors/hostname_collector.py b/monkey/infection_monkey/system_info/collectors/hostname_collector.py\nindex 0aeecd9f..4dc6701a 100644\n--- a/monkey/infection_monkey/system_info/collectors/hostname_collector.py\n+++ b/monkey/infection_monkey/system_info/collectors/hostname_collector.py", + "hunks": [ + { + "swimmHunkMetadata": {}, + "hunkDiffLines": [ + "@@ -1,15 +1,5 @@", + " import logging", + "-import socket", + "-", + "-from common.common_consts.system_info_collectors_names import HOSTNAME_COLLECTOR", + "-from infection_monkey.system_info.system_info_collector import SystemInfoCollector", + " ", + " logger = logging.getLogger(__name__)", + " ", + "-", + "+# SWIMMER: The collector class goes here.", + "-class HostnameCollector(SystemInfoCollector):", + "- def __init__(self):", + "- super().__init__(name=HOSTNAME_COLLECTOR)", + "-", + "- def collect(self) -> dict:", + "- return {\"hostname\": socket.getfqdn()}" + ] + } ] }, "monkey/monkey_island/cc/services/config_schema/definitions/system_info_collector_classes.py": { - "index": [ - "5f113f4a..7043e227", - "100644" - ], - "fileA": "monkey/monkey_island/cc/services/config_schema/definitions/system_info_collector_classes.py", - "fileB": "monkey/monkey_island/cc/services/config_schema/definitions/system_info_collector_classes.py", - "status": "MODIFIED", - "numLineDeletions": 10, - "numLineAdditions": 1, - "hunkContainers": [ - "JTdCJTIyaHVuayUyMiUzQSU3QiUyMmhlYWRlciUyMiUzQSUyMiU0MCU0MCUyMC0xJTJDNyUyMCUyQjElMkM2JTIwJTQwJTQwJTIyJTJDJTIyY2hhbmdlcyUyMiUzQSU1QiU3QiUyMnR5cGUlMjIlM0ElMjJjb250ZXh0JTIyJTJDJTIyZGF0YSUyMiUzQSUyMiUyMGZyb20lMjBjb21tb24uZGF0YS5zeXN0ZW1faW5mb19jb2xsZWN0b3JzX25hbWVzJTIwaW1wb3J0JTIwKEFXU19DT0xMRUNUT1IlMkMlMjIlMkMlMjJsaW5lTnVtYmVycyUyMiUzQSU3QiUyMmElMjIlM0ExJTJDJTIyYiUyMiUzQTElN0QlN0QlMkMlN0IlMjJ0eXBlJTIyJTNBJTIyY29udGV4dCUyMiUyQyUyMmRhdGElMjIlM0ElMjIlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjBBWlVSRV9DUkVEX0NPTExFQ1RPUiUyQyUyMiUyQyUyMmxpbmVOdW1iZXJzJTIyJTNBJTdCJTIyYSUyMiUzQTIlMkMlMjJiJTIyJTNBMiU3RCU3RCUyQyU3QiUyMnR5cGUlMjIlM0ElMjJjb250ZXh0JTIyJTJDJTIyZGF0YSUyMiUzQSUyMiUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMEVOVklST05NRU5UX0NPTExFQ1RPUiUyQyUyMiUyQyUyMmxpbmVOdW1iZXJzJTIyJTNBJTdCJTIyYSUyMiUzQTMlMkMlMjJiJTIyJTNBMyU3RCU3RCUyQyU3QiUyMnR5cGUlMjIlM0ElMjJkZWwlMjIlMkMlMjJtYXJrJTIyJTNBJTIyLSUyMiUyQyUyMmRhdGElMjIlM0ElMjIlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjBIT1NUTkFNRV9DT0xMRUNUT1IlMkMlMjIlMkMlMjJsaW5lTnVtYmVycyUyMiUzQSU3QiUyMmElMjIlM0E0JTdEJTdEJTJDJTdCJTIydHlwZSUyMiUzQSUyMmNvbnRleHQlMjIlMkMlMjJkYXRhJTIyJTNBJTIyJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwTUlNSUtBVFpfQ09MTEVDVE9SJTJDJTIyJTJDJTIybGluZU51bWJlcnMlMjIlM0ElN0IlMjJhJTIyJTNBNSUyQyUyMmIlMjIlM0E0JTdEJTdEJTJDJTdCJTIydHlwZSUyMiUzQSUyMmNvbnRleHQlMjIlMkMlMjJkYXRhJTIyJTNBJTIyJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwUFJPQ0VTU19MSVNUX0NPTExFQ1RPUiklMjIlMkMlMjJsaW5lTnVtYmVycyUyMiUzQSU3QiUyMmElMjIlM0E2JTJDJTIyYiUyMiUzQTUlN0QlN0QlMkMlN0IlMjJ0eXBlJTIyJTNBJTIyY29udGV4dCUyMiUyQyUyMmRhdGElMjIlM0ElMjIlMjAlMjIlMkMlMjJsaW5lTnVtYmVycyUyMiUzQSU3QiUyMmElMjIlM0E3JTJDJTIyYiUyMiUzQTYlN0QlN0QlNUQlMkMlMjJsaW5lTnVtYmVycyUyMiUzQSU3QiUyMmElMjIlM0ElN0IlMjJzdGFydExpbmUlMjIlM0ExJTJDJTIybGluZXNDb3VudCUyMiUzQTclN0QlMkMlMjJiJTIyJTNBJTdCJTIyc3RhcnRMaW5lJTIyJTNBMSUyQyUyMmxpbmVzQ291bnQlMjIlM0E2JTdEJTdEJTdEJTdE", - "JTdCJTIyaHVuayUyMiUzQSU3QiUyMmhlYWRlciUyMiUzQSUyMiU0MCU0MCUyMC0zNyUyQzE1JTIwJTJCMzYlMkM3JTIwJTQwJTQwJTIwU1lTVEVNX0lORk9fQ09MTEVDVE9SX0NMQVNTRVMlMjAlM0QlMjAlN0IlMjIlMkMlMjJjaGFuZ2VzJTIyJTNBJTVCJTdCJTIydHlwZSUyMiUzQSUyMmNvbnRleHQlMjIlMkMlMjJkYXRhJTIyJTNBJTIyJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTVDJTIyaW5mbyU1QyUyMiUzQSUyMCU1QyUyMklmJTIwb24lMjBBV1MlMkMlMjBjb2xsZWN0cyUyMG1vcmUlMjBpbmZvcm1hdGlvbiUyMGFib3V0JTIwdGhlJTIwQVdTJTIwaW5zdGFuY2UlMjBjdXJyZW50bHklMjBydW5uaW5nJTIwb24uJTVDJTIyJTJDJTIyJTJDJTIybGluZU51bWJlcnMlMjIlM0ElN0IlMjJhJTIyJTNBMzclMkMlMjJiJTIyJTNBMzYlN0QlN0QlMkMlN0IlMjJ0eXBlJTIyJTNBJTIyY29udGV4dCUyMiUyQyUyMmRhdGElMjIlM0ElMjIlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlNUMlMjJhdHRhY2tfdGVjaG5pcXVlcyU1QyUyMiUzQSUyMCU1QiU1QyUyMlQxMDgyJTVDJTIyJTVEJTIyJTJDJTIybGluZU51bWJlcnMlMjIlM0ElN0IlMjJhJTIyJTNBMzglMkMlMjJiJTIyJTNBMzclN0QlN0QlMkMlN0IlMjJ0eXBlJTIyJTNBJTIyY29udGV4dCUyMiUyQyUyMmRhdGElMjIlM0ElMjIlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlN0QlMkMlMjIlMkMlMjJsaW5lTnVtYmVycyUyMiUzQSU3QiUyMmElMjIlM0EzOSUyQyUyMmIlMjIlM0EzOCU3RCU3RCUyQyU3QiUyMnR5cGUlMjIlM0ElMjJkZWwlMjIlMkMlMjJtYXJrJTIyJTNBJTIyLSUyMiUyQyUyMmRhdGElMjIlM0ElMjIlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlN0IlMjIlMkMlMjJsaW5lTnVtYmVycyUyMiUzQSU3QiUyMmElMjIlM0E0MCU3RCU3RCUyQyU3QiUyMnR5cGUlMjIlM0ElMjJkZWwlMjIlMkMlMjJtYXJrJTIyJTNBJTIyLSUyMiUyQyUyMmRhdGElMjIlM0ElMjIlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlNUMlMjJ0eXBlJTVDJTIyJTNBJTIwJTVDJTIyc3RyaW5nJTVDJTIyJTJDJTIyJTJDJTIybGluZU51bWJlcnMlMjIlM0ElN0IlMjJhJTIyJTNBNDElN0QlN0QlMkMlN0IlMjJ0eXBlJTIyJTNBJTIyZGVsJTIyJTJDJTIybWFyayUyMiUzQSUyMi0lMjIlMkMlMjJkYXRhJTIyJTNBJTIyJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTVDJTIyZW51bSU1QyUyMiUzQSUyMCU1QiUyMiUyQyUyMmxpbmVOdW1iZXJzJTIyJTNBJTdCJTIyYSUyMiUzQTQyJTdEJTdEJTJDJTdCJTIydHlwZSUyMiUzQSUyMmRlbCUyMiUyQyUyMm1hcmslMjIlM0ElMjItJTIyJTJDJTIyZGF0YSUyMiUzQSUyMiUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMEhPU1ROQU1FX0NPTExFQ1RPUiUyMiUyQyUyMmxpbmVOdW1iZXJzJTIyJTNBJTdCJTIyYSUyMiUzQTQzJTdEJTdEJTJDJTdCJTIydHlwZSUyMiUzQSUyMmRlbCUyMiUyQyUyMm1hcmslMjIlM0ElMjItJTIyJTJDJTIyZGF0YSUyMiUzQSUyMiUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCU1RCUyQyUyMiUyQyUyMmxpbmVOdW1iZXJzJTIyJTNBJTdCJTIyYSUyMiUzQTQ0JTdEJTdEJTJDJTdCJTIydHlwZSUyMiUzQSUyMmRlbCUyMiUyQyUyMm1hcmslMjIlM0ElMjItJTIyJTJDJTIyZGF0YSUyMiUzQSUyMiUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCU1QyUyMnRpdGxlJTVDJTIyJTNBJTIwJTVDJTIySG9zdG5hbWUlMjBjb2xsZWN0b3IlNUMlMjIlMkMlMjIlMkMlMjJsaW5lTnVtYmVycyUyMiUzQSU3QiUyMmElMjIlM0E0NSU3RCU3RCUyQyU3QiUyMnR5cGUlMjIlM0ElMjJkZWwlMjIlMkMlMjJtYXJrJTIyJTNBJTIyLSUyMiUyQyUyMmRhdGElMjIlM0ElMjIlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlNUMlMjJpbmZvJTVDJTIyJTNBJTIwJTVDJTIyQ29sbGVjdHMlMjBtYWNoaW5lJ3MlMjBob3N0bmFtZS4lNUMlMjIlMkMlMjIlMkMlMjJsaW5lTnVtYmVycyUyMiUzQSU3QiUyMmElMjIlM0E0NiU3RCU3RCUyQyU3QiUyMnR5cGUlMjIlM0ElMjJkZWwlMjIlMkMlMjJtYXJrJTIyJTNBJTIyLSUyMiUyQyUyMmRhdGElMjIlM0ElMjIlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlNUMlMjJhdHRhY2tfdGVjaG5pcXVlcyU1QyUyMiUzQSUyMCU1QiU1QyUyMlQxMDgyJTVDJTIyJTJDJTIwJTVDJTIyVDEwMTYlNUMlMjIlNUQlMjIlMkMlMjJsaW5lTnVtYmVycyUyMiUzQSU3QiUyMmElMjIlM0E0NyU3RCU3RCUyQyU3QiUyMnR5cGUlMjIlM0ElMjJkZWwlMjIlMkMlMjJtYXJrJTIyJTNBJTIyLSUyMiUyQyUyMmRhdGElMjIlM0ElMjIlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlN0QlMkMlMjIlMkMlMjJsaW5lTnVtYmVycyUyMiUzQSU3QiUyMmElMjIlM0E0OCU3RCU3RCUyQyU3QiUyMnR5cGUlMjIlM0ElMjJhZGQlMjIlMkMlMjJtYXJrJTIyJTNBJTIyJTJCJTIyJTJDJTIyZGF0YSUyMiUzQSUyMiUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMyUyMFNXSU1NRVIlM0ElMjBDb2xsZWN0b3IlMjBjb25maWclMjBnb2VzJTIwaGVyZS4lMjBUaXAlM0ElMjBIb3N0bmFtZSUyMGNvbGxlY3Rpb24lMjByZWxhdGVzJTIwdG8lMjB0aGUlMjBUMTA4MiUyMGFuZCUyMFQxMDE2JTIwdGVjaG5pcXVlcy4lMjIlMkMlMjJsaW5lTnVtYmVycyUyMiUzQSU3QiUyMmIlMjIlM0EzOSU3RCU3RCUyQyU3QiUyMnR5cGUlMjIlM0ElMjJjb250ZXh0JTIyJTJDJTIyZGF0YSUyMiUzQSUyMiUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCU3QiUyMiUyQyUyMmxpbmVOdW1iZXJzJTIyJTNBJTdCJTIyYSUyMiUzQTQ5JTJDJTIyYiUyMiUzQTQwJTdEJTdEJTJDJTdCJTIydHlwZSUyMiUzQSUyMmNvbnRleHQlMjIlMkMlMjJkYXRhJTIyJTNBJTIyJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTVDJTIydHlwZSU1QyUyMiUzQSUyMCU1QyUyMnN0cmluZyU1QyUyMiUyQyUyMiUyQyUyMmxpbmVOdW1iZXJzJTIyJTNBJTdCJTIyYSUyMiUzQTUwJTJDJTIyYiUyMiUzQTQxJTdEJTdEJTJDJTdCJTIydHlwZSUyMiUzQSUyMmNvbnRleHQlMjIlMkMlMjJkYXRhJTIyJTNBJTIyJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTVDJTIyZW51bSU1QyUyMiUzQSUyMCU1QiUyMiUyQyUyMmxpbmVOdW1iZXJzJTIyJTNBJTdCJTIyYSUyMiUzQTUxJTJDJTIyYiUyMiUzQTQyJTdEJTdEJTVEJTJDJTIybGluZU51bWJlcnMlMjIlM0ElN0IlMjJhJTIyJTNBJTdCJTIyc3RhcnRMaW5lJTIyJTNBMzclMkMlMjJsaW5lc0NvdW50JTIyJTNBMTUlN0QlMkMlMjJiJTIyJTNBJTdCJTIyc3RhcnRMaW5lJTIyJTNBMzYlMkMlMjJsaW5lc0NvdW50JTIyJTNBNyU3RCU3RCU3RCU3RA==" + "diffType": "MODIFIED", + "fileDiffHeader": "diff --git a/monkey/monkey_island/cc/services/config_schema/definitions/system_info_collector_classes.py b/monkey/monkey_island/cc/services/config_schema/definitions/system_info_collector_classes.py\nindex 0ea92bea..f4517b90 100644\n--- a/monkey/monkey_island/cc/services/config_schema/definitions/system_info_collector_classes.py\n+++ b/monkey/monkey_island/cc/services/config_schema/definitions/system_info_collector_classes.py", + "hunks": [ + { + "swimmHunkMetadata": {}, + "hunkDiffLines": [ + "@@ -1,5 +1,5 @@", + " from common.common_consts.system_info_collectors_names import (AWS_COLLECTOR, AZURE_CRED_COLLECTOR,", + "- ENVIRONMENT_COLLECTOR, HOSTNAME_COLLECTOR,", + "+ ENVIRONMENT_COLLECTOR,", + " MIMIKATZ_COLLECTOR, PROCESS_LIST_COLLECTOR)", + " ", + " SYSTEM_INFO_COLLECTOR_CLASSES = {" + ] + }, + { + "swimmHunkMetadata": {}, + "hunkDiffLines": [ + "@@ -34,15 +34,7 @@", + " \"info\": \"If on AWS, collects more information about the AWS instance currently running on.\",", + " \"attack_techniques\": [\"T1082\"]", + " },", + "- {", + "+ # SWIMMER: Collector config goes here. Tip: Hostname collection relates to the T1082 and T1016 techniques.", + "- \"type\": \"string\",", + "- \"enum\": [", + "- HOSTNAME_COLLECTOR", + "- ],", + "- \"title\": \"Hostname collector\",", + "- \"info\": \"Collects machine's hostname.\",", + "- \"attack_techniques\": [\"T1082\", \"T1016\"]", + "- },", + " {", + " \"type\": \"string\",", + " \"enum\": [" + ] + } ] }, "monkey/monkey_island/cc/services/config_schema/monkey.py": { - "index": [ - "b47d6a15..1b1962a4", - "100644" - ], - "fileA": "monkey/monkey_island/cc/services/config_schema/monkey.py", - "fileB": "monkey/monkey_island/cc/services/config_schema/monkey.py", - "status": "MODIFIED", - "numLineDeletions": 2, - "numLineAdditions": 0, - "hunkContainers": [ - "JTdCJTIyaHVuayUyMiUzQSU3QiUyMmhlYWRlciUyMiUzQSUyMiU0MCU0MCUyMC0xJTJDNyUyMCUyQjElMkM2JTIwJTQwJTQwJTIyJTJDJTIyY2hhbmdlcyUyMiUzQSU1QiU3QiUyMnR5cGUlMjIlM0ElMjJjb250ZXh0JTIyJTJDJTIyZGF0YSUyMiUzQSUyMiUyMGZyb20lMjBjb21tb24uZGF0YS5zeXN0ZW1faW5mb19jb2xsZWN0b3JzX25hbWVzJTIwaW1wb3J0JTIwKEFXU19DT0xMRUNUT1IlMkMlMjIlMkMlMjJsaW5lTnVtYmVycyUyMiUzQSU3QiUyMmElMjIlM0ExJTJDJTIyYiUyMiUzQTElN0QlN0QlMkMlN0IlMjJ0eXBlJTIyJTNBJTIyY29udGV4dCUyMiUyQyUyMmRhdGElMjIlM0ElMjIlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjBBWlVSRV9DUkVEX0NPTExFQ1RPUiUyQyUyMiUyQyUyMmxpbmVOdW1iZXJzJTIyJTNBJTdCJTIyYSUyMiUzQTIlMkMlMjJiJTIyJTNBMiU3RCU3RCUyQyU3QiUyMnR5cGUlMjIlM0ElMjJjb250ZXh0JTIyJTJDJTIyZGF0YSUyMiUzQSUyMiUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMEVOVklST05NRU5UX0NPTExFQ1RPUiUyQyUyMiUyQyUyMmxpbmVOdW1iZXJzJTIyJTNBJTdCJTIyYSUyMiUzQTMlMkMlMjJiJTIyJTNBMyU3RCU3RCUyQyU3QiUyMnR5cGUlMjIlM0ElMjJkZWwlMjIlMkMlMjJtYXJrJTIyJTNBJTIyLSUyMiUyQyUyMmRhdGElMjIlM0ElMjIlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjBIT1NUTkFNRV9DT0xMRUNUT1IlMkMlMjIlMkMlMjJsaW5lTnVtYmVycyUyMiUzQSU3QiUyMmElMjIlM0E0JTdEJTdEJTJDJTdCJTIydHlwZSUyMiUzQSUyMmNvbnRleHQlMjIlMkMlMjJkYXRhJTIyJTNBJTIyJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwTUlNSUtBVFpfQ09MTEVDVE9SJTJDJTIyJTJDJTIybGluZU51bWJlcnMlMjIlM0ElN0IlMjJhJTIyJTNBNSUyQyUyMmIlMjIlM0E0JTdEJTdEJTJDJTdCJTIydHlwZSUyMiUzQSUyMmNvbnRleHQlMjIlMkMlMjJkYXRhJTIyJTNBJTIyJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwUFJPQ0VTU19MSVNUX0NPTExFQ1RPUiklMjIlMkMlMjJsaW5lTnVtYmVycyUyMiUzQSU3QiUyMmElMjIlM0E2JTJDJTIyYiUyMiUzQTUlN0QlN0QlMkMlN0IlMjJ0eXBlJTIyJTNBJTIyY29udGV4dCUyMiUyQyUyMmRhdGElMjIlM0ElMjIlMjAlMjIlMkMlMjJsaW5lTnVtYmVycyUyMiUzQSU3QiUyMmElMjIlM0E3JTJDJTIyYiUyMiUzQTYlN0QlN0QlNUQlMkMlMjJsaW5lTnVtYmVycyUyMiUzQSU3QiUyMmElMjIlM0ElN0IlMjJzdGFydExpbmUlMjIlM0ExJTJDJTIybGluZXNDb3VudCUyMiUzQTclN0QlMkMlMjJiJTIyJTNBJTdCJTIyc3RhcnRMaW5lJTIyJTNBMSUyQyUyMmxpbmVzQ291bnQlMjIlM0E2JTdEJTdEJTdEJTdE", - "JTdCJTIyaHVuayUyMiUzQSU3QiUyMmhlYWRlciUyMiUzQSUyMiU0MCU0MCUyMC04OCUyQzclMjAlMkI4NyUyQzYlMjAlNDAlNDAlMjBNT05LRVklMjAlM0QlMjAlN0IlMjIlMkMlMjJjaGFuZ2VzJTIyJTNBJTVCJTdCJTIydHlwZSUyMiUzQSUyMmNvbnRleHQlMjIlMkMlMjJkYXRhJTIyJTNBJTIyJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTVDJTIyZGVmYXVsdCU1QyUyMiUzQSUyMCU1QiUyMiUyQyUyMmxpbmVOdW1iZXJzJTIyJTNBJTdCJTIyYSUyMiUzQTg4JTJDJTIyYiUyMiUzQTg3JTdEJTdEJTJDJTdCJTIydHlwZSUyMiUzQSUyMmNvbnRleHQlMjIlMkMlMjJkYXRhJTIyJTNBJTIyJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwRU5WSVJPTk1FTlRfQ09MTEVDVE9SJTJDJTIyJTJDJTIybGluZU51bWJlcnMlMjIlM0ElN0IlMjJhJTIyJTNBODklMkMlMjJiJTIyJTNBODglN0QlN0QlMkMlN0IlMjJ0eXBlJTIyJTNBJTIyY29udGV4dCUyMiUyQyUyMmRhdGElMjIlM0ElMjIlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjBBV1NfQ09MTEVDVE9SJTJDJTIyJTJDJTIybGluZU51bWJlcnMlMjIlM0ElN0IlMjJhJTIyJTNBOTAlMkMlMjJiJTIyJTNBODklN0QlN0QlMkMlN0IlMjJ0eXBlJTIyJTNBJTIyZGVsJTIyJTJDJTIybWFyayUyMiUzQSUyMi0lMjIlMkMlMjJkYXRhJTIyJTNBJTIyJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwSE9TVE5BTUVfQ09MTEVDVE9SJTJDJTIyJTJDJTIybGluZU51bWJlcnMlMjIlM0ElN0IlMjJhJTIyJTNBOTElN0QlN0QlMkMlN0IlMjJ0eXBlJTIyJTNBJTIyY29udGV4dCUyMiUyQyUyMmRhdGElMjIlM0ElMjIlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjBQUk9DRVNTX0xJU1RfQ09MTEVDVE9SJTJDJTIyJTJDJTIybGluZU51bWJlcnMlMjIlM0ElN0IlMjJhJTIyJTNBOTIlMkMlMjJiJTIyJTNBOTAlN0QlN0QlMkMlN0IlMjJ0eXBlJTIyJTNBJTIyY29udGV4dCUyMiUyQyUyMmRhdGElMjIlM0ElMjIlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjBNSU1JS0FUWl9DT0xMRUNUT1IlMkMlMjIlMkMlMjJsaW5lTnVtYmVycyUyMiUzQSU3QiUyMmElMjIlM0E5MyUyQyUyMmIlMjIlM0E5MSU3RCU3RCUyQyU3QiUyMnR5cGUlMjIlM0ElMjJjb250ZXh0JTIyJTJDJTIyZGF0YSUyMiUzQSUyMiUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMEFaVVJFX0NSRURfQ09MTEVDVE9SJTIyJTJDJTIybGluZU51bWJlcnMlMjIlM0ElN0IlMjJhJTIyJTNBOTQlMkMlMjJiJTIyJTNBOTIlN0QlN0QlNUQlMkMlMjJsaW5lTnVtYmVycyUyMiUzQSU3QiUyMmElMjIlM0ElN0IlMjJzdGFydExpbmUlMjIlM0E4OCUyQyUyMmxpbmVzQ291bnQlMjIlM0E3JTdEJTJDJTIyYiUyMiUzQSU3QiUyMnN0YXJ0TGluZSUyMiUzQTg3JTJDJTIybGluZXNDb3VudCUyMiUzQTYlN0QlN0QlN0QlN0Q=" + "diffType": "MODIFIED", + "fileDiffHeader": "diff --git a/monkey/monkey_island/cc/services/config_schema/monkey.py b/monkey/monkey_island/cc/services/config_schema/monkey.py\nindex 01d46367..84cd9123 100644\n--- a/monkey/monkey_island/cc/services/config_schema/monkey.py\n+++ b/monkey/monkey_island/cc/services/config_schema/monkey.py", + "hunks": [ + { + "swimmHunkMetadata": {}, + "hunkDiffLines": [ + "@@ -1,5 +1,5 @@", + " from common.common_consts.system_info_collectors_names import (AWS_COLLECTOR, AZURE_CRED_COLLECTOR,", + "- ENVIRONMENT_COLLECTOR, HOSTNAME_COLLECTOR,", + "+ ENVIRONMENT_COLLECTOR,", + " MIMIKATZ_COLLECTOR, PROCESS_LIST_COLLECTOR)", + " ", + " MONKEY = {" + ] + }, + { + "swimmHunkMetadata": {}, + "hunkDiffLines": [ + "@@ -85,7 +85,6 @@", + " \"default\": [", + " ENVIRONMENT_COLLECTOR,", + " AWS_COLLECTOR,", + "- HOSTNAME_COLLECTOR,", + " PROCESS_LIST_COLLECTOR,", + " MIMIKATZ_COLLECTOR,", + " AZURE_CRED_COLLECTOR" + ] + } ] }, "monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/hostname.py": { - "index": [ - "e2de4519..04bc3556", - "100644" - ], - "fileA": "monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/hostname.py", - "fileB": "monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/hostname.py", - "status": "MODIFIED", - "numLineDeletions": 3, - "numLineAdditions": 3, - "hunkContainers": [ - "JTdCJTIyaHVuayUyMiUzQSU3QiUyMmhlYWRlciUyMiUzQSUyMiU0MCU0MCUyMC0xJTJDOSUyMCUyQjElMkM5JTIwJTQwJTQwJTIyJTJDJTIyY2hhbmdlcyUyMiUzQSU1QiU3QiUyMnR5cGUlMjIlM0ElMjJjb250ZXh0JTIyJTJDJTIyZGF0YSUyMiUzQSUyMiUyMGltcG9ydCUyMGxvZ2dpbmclMjIlMkMlMjJsaW5lTnVtYmVycyUyMiUzQSU3QiUyMmElMjIlM0ExJTJDJTIyYiUyMiUzQTElN0QlN0QlMkMlN0IlMjJ0eXBlJTIyJTNBJTIyY29udGV4dCUyMiUyQyUyMmRhdGElMjIlM0ElMjIlMjAlMjIlMkMlMjJsaW5lTnVtYmVycyUyMiUzQSU3QiUyMmElMjIlM0EyJTJDJTIyYiUyMiUzQTIlN0QlN0QlMkMlN0IlMjJ0eXBlJTIyJTNBJTIyZGVsJTIyJTJDJTIybWFyayUyMiUzQSUyMi0lMjIlMkMlMjJkYXRhJTIyJTNBJTIyZnJvbSUyMG1vbmtleV9pc2xhbmQuY2MubW9kZWxzLm1vbmtleSUyMGltcG9ydCUyME1vbmtleSUyMiUyQyUyMmxpbmVOdW1iZXJzJTIyJTNBJTdCJTIyYSUyMiUzQTMlN0QlN0QlMkMlN0IlMjJ0eXBlJTIyJTNBJTIyYWRkJTIyJTJDJTIybWFyayUyMiUzQSUyMiUyQiUyMiUyQyUyMmRhdGElMjIlM0ElMjIlMjMlMjBTV0lNTUVSJTNBJTIwVGhpcyUyMHdpbGwlMjBiZSUyMHVzZWZ1bCUyMCUzQSklMjBtb25rZXlfaXNsYW5kLmNjLm1vZGVscy5tb25rZXkuTW9ua2V5JTIwaGFzJTIwdGhlJTIwdXNlZnVsJTIyJTJDJTIybGluZU51bWJlcnMlMjIlM0ElN0IlMjJiJTIyJTNBMyU3RCU3RCUyQyU3QiUyMnR5cGUlMjIlM0ElMjJhZGQlMjIlMkMlMjJtYXJrJTIyJTNBJTIyJTJCJTIyJTJDJTIyZGF0YSUyMiUzQSUyMiUyMyUyMCU1QyUyMmdldF9zaW5nbGVfbW9ua2V5X2J5X2d1aWQlNUMlMjIlMjBhbmQlMjAlNUMlMjJzZXRfaG9zdG5hbWUlNUMlMjIlMjBtZXRob2RzLiUyMiUyQyUyMmxpbmVOdW1iZXJzJTIyJTNBJTdCJTIyYiUyMiUzQTQlN0QlN0QlMkMlN0IlMjJ0eXBlJTIyJTNBJTIyY29udGV4dCUyMiUyQyUyMmRhdGElMjIlM0ElMjIlMjAlMjIlMkMlMjJsaW5lTnVtYmVycyUyMiUzQSU3QiUyMmElMjIlM0E0JTJDJTIyYiUyMiUzQTUlN0QlN0QlMkMlN0IlMjJ0eXBlJTIyJTNBJTIyY29udGV4dCUyMiUyQyUyMmRhdGElMjIlM0ElMjIlMjBsb2dnZXIlMjAlM0QlMjBsb2dnaW5nLmdldExvZ2dlcihfX25hbWVfXyklMjIlMkMlMjJsaW5lTnVtYmVycyUyMiUzQSU3QiUyMmElMjIlM0E1JTJDJTIyYiUyMiUzQTYlN0QlN0QlMkMlN0IlMjJ0eXBlJTIyJTNBJTIyY29udGV4dCUyMiUyQyUyMmRhdGElMjIlM0ElMjIlMjAlMjIlMkMlMjJsaW5lTnVtYmVycyUyMiUzQSU3QiUyMmElMjIlM0E2JTJDJTIyYiUyMiUzQTclN0QlN0QlMkMlN0IlMjJ0eXBlJTIyJTNBJTIyY29udGV4dCUyMiUyQyUyMmRhdGElMjIlM0ElMjIlMjAlMjIlMkMlMjJsaW5lTnVtYmVycyUyMiUzQSU3QiUyMmElMjIlM0E3JTJDJTIyYiUyMiUzQTglN0QlN0QlMkMlN0IlMjJ0eXBlJTIyJTNBJTIyZGVsJTIyJTJDJTIybWFyayUyMiUzQSUyMi0lMjIlMkMlMjJkYXRhJTIyJTNBJTIyZGVmJTIwcHJvY2Vzc19ob3N0bmFtZV90ZWxlbWV0cnkoY29sbGVjdG9yX3Jlc3VsdHMlMkMlMjBtb25rZXlfZ3VpZCklM0ElMjIlMkMlMjJsaW5lTnVtYmVycyUyMiUzQSU3QiUyMmElMjIlM0E4JTdEJTdEJTJDJTdCJTIydHlwZSUyMiUzQSUyMmRlbCUyMiUyQyUyMm1hcmslMjIlM0ElMjItJTIyJTJDJTIyZGF0YSUyMiUzQSUyMiUyMCUyMCUyMCUyME1vbmtleS5nZXRfc2luZ2xlX21vbmtleV9ieV9ndWlkKG1vbmtleV9ndWlkKS5zZXRfaG9zdG5hbWUoY29sbGVjdG9yX3Jlc3VsdHMlNUIlNUMlMjJob3N0bmFtZSU1QyUyMiU1RCklMjIlMkMlMjJsaW5lTnVtYmVycyUyMiUzQSU3QiUyMmElMjIlM0E5JTdEJTdEJTJDJTdCJTIydHlwZSUyMiUzQSUyMmFkZCUyMiUyQyUyMm1hcmslMjIlM0ElMjIlMkIlMjIlMkMlMjJkYXRhJTIyJTNBJTIyJTIzJTIwU1dJTU1FUiUzQSUyMFByb2Nlc3NpbmclMjBmdW5jdGlvbiUyMGdvZXMlMjBoZXJlLiUyMiUyQyUyMmxpbmVOdW1iZXJzJTIyJTNBJTdCJTIyYiUyMiUzQTklN0QlN0QlNUQlMkMlMjJsaW5lTnVtYmVycyUyMiUzQSU3QiUyMmElMjIlM0ElN0IlMjJzdGFydExpbmUlMjIlM0ExJTJDJTIybGluZXNDb3VudCUyMiUzQTklN0QlMkMlMjJiJTIyJTNBJTdCJTIyc3RhcnRMaW5lJTIyJTNBMSUyQyUyMmxpbmVzQ291bnQlMjIlM0E5JTdEJTdEJTdEJTdE" + "diffType": "MODIFIED", + "fileDiffHeader": "diff --git a/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/hostname.py b/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/hostname.py\nindex e2de4519..8764e29d 100644\n--- a/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/hostname.py\n+++ b/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/hostname.py", + "hunks": [ + { + "swimmHunkMetadata": {}, + "hunkDiffLines": [ + "@@ -1,9 +1,9 @@", + " import logging", + " ", + "-from monkey_island.cc.models.monkey import Monkey", + "+# SWIMMER: This will be useful :) monkey_island.cc.models.monkey.Monkey has the useful", + "+# \"get_single_monkey_by_guid\" and \"set_hostname\" methods", + " ", + " logger = logging.getLogger(__name__)", + " ", + " ", + "-def process_hostname_telemetry(collector_results, monkey_guid):", + "+# SWIMMER: Processing function goes here.", + "- Monkey.get_single_monkey_by_guid(monkey_guid).set_hostname(collector_results[\"hostname\"])" + ] + } ] }, "monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/system_info_telemetry_dispatcher.py": { - "index": [ - "639a392c..7aa6d3a6", - "100644" - ], - "fileA": "monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/system_info_telemetry_dispatcher.py", - "fileB": "monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/system_info_telemetry_dispatcher.py", - "status": "MODIFIED", - "numLineDeletions": 4, - "numLineAdditions": 0, - "hunkContainers": [ - "JTdCJTIyaHVuayUyMiUzQSU3QiUyMmhlYWRlciUyMiUzQSUyMiU0MCU0MCUyMC0zJTJDMTQlMjAlMkIzJTJDMTElMjAlNDAlNDAlMjBpbXBvcnQlMjB0eXBpbmclMjIlMkMlMjJjaGFuZ2VzJTIyJTNBJTVCJTdCJTIydHlwZSUyMiUzQSUyMmNvbnRleHQlMjIlMkMlMjJkYXRhJTIyJTNBJTIyJTIwJTIyJTJDJTIybGluZU51bWJlcnMlMjIlM0ElN0IlMjJhJTIyJTNBMyUyQyUyMmIlMjIlM0EzJTdEJTdEJTJDJTdCJTIydHlwZSUyMiUzQSUyMmNvbnRleHQlMjIlMkMlMjJkYXRhJTIyJTNBJTIyJTIwZnJvbSUyMGNvbW1vbi5kYXRhLnN5c3RlbV9pbmZvX2NvbGxlY3RvcnNfbmFtZXMlMjBpbXBvcnQlMjAoQVdTX0NPTExFQ1RPUiUyQyUyMiUyQyUyMmxpbmVOdW1iZXJzJTIyJTNBJTdCJTIyYSUyMiUzQTQlMkMlMjJiJTIyJTNBNCU3RCU3RCUyQyU3QiUyMnR5cGUlMjIlM0ElMjJjb250ZXh0JTIyJTJDJTIyZGF0YSUyMiUzQSUyMiUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMEVOVklST05NRU5UX0NPTExFQ1RPUiUyQyUyMiUyQyUyMmxpbmVOdW1iZXJzJTIyJTNBJTdCJTIyYSUyMiUzQTUlMkMlMjJiJTIyJTNBNSU3RCU3RCUyQyU3QiUyMnR5cGUlMjIlM0ElMjJkZWwlMjIlMkMlMjJtYXJrJTIyJTNBJTIyLSUyMiUyQyUyMmRhdGElMjIlM0ElMjIlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjBIT1NUTkFNRV9DT0xMRUNUT1IlMkMlMjIlMkMlMjJsaW5lTnVtYmVycyUyMiUzQSU3QiUyMmElMjIlM0E2JTdEJTdEJTJDJTdCJTIydHlwZSUyMiUzQSUyMmNvbnRleHQlMjIlMkMlMjJkYXRhJTIyJTNBJTIyJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwUFJPQ0VTU19MSVNUX0NPTExFQ1RPUiklMjIlMkMlMjJsaW5lTnVtYmVycyUyMiUzQSU3QiUyMmElMjIlM0E3JTJDJTIyYiUyMiUzQTYlN0QlN0QlMkMlN0IlMjJ0eXBlJTIyJTNBJTIyY29udGV4dCUyMiUyQyUyMmRhdGElMjIlM0ElMjIlMjBmcm9tJTIwbW9ua2V5X2lzbGFuZC5jYy5zZXJ2aWNlcy50ZWxlbWV0cnkucHJvY2Vzc2luZy5zeXN0ZW1faW5mb19jb2xsZWN0b3JzLmF3cyUyMGltcG9ydCUyMCU1QyU1QyUyMiUyQyUyMmxpbmVOdW1iZXJzJTIyJTNBJTdCJTIyYSUyMiUzQTglMkMlMjJiJTIyJTNBNyU3RCU3RCUyQyU3QiUyMnR5cGUlMjIlM0ElMjJjb250ZXh0JTIyJTJDJTIyZGF0YSUyMiUzQSUyMiUyMCUyMCUyMCUyMCUyMHByb2Nlc3NfYXdzX3RlbGVtZXRyeSUyMiUyQyUyMmxpbmVOdW1iZXJzJTIyJTNBJTdCJTIyYSUyMiUzQTklMkMlMjJiJTIyJTNBOCU3RCU3RCUyQyU3QiUyMnR5cGUlMjIlM0ElMjJjb250ZXh0JTIyJTJDJTIyZGF0YSUyMiUzQSUyMiUyMGZyb20lMjBtb25rZXlfaXNsYW5kLmNjLnNlcnZpY2VzLnRlbGVtZXRyeS5wcm9jZXNzaW5nLnN5c3RlbV9pbmZvX2NvbGxlY3RvcnMuZW52aXJvbm1lbnQlMjBpbXBvcnQlMjAlNUMlNUMlMjIlMkMlMjJsaW5lTnVtYmVycyUyMiUzQSU3QiUyMmElMjIlM0ExMCUyQyUyMmIlMjIlM0E5JTdEJTdEJTJDJTdCJTIydHlwZSUyMiUzQSUyMmNvbnRleHQlMjIlMkMlMjJkYXRhJTIyJTNBJTIyJTIwJTIwJTIwJTIwJTIwcHJvY2Vzc19lbnZpcm9ubWVudF90ZWxlbWV0cnklMjIlMkMlMjJsaW5lTnVtYmVycyUyMiUzQSU3QiUyMmElMjIlM0ExMSUyQyUyMmIlMjIlM0ExMCU3RCU3RCUyQyU3QiUyMnR5cGUlMjIlM0ElMjJkZWwlMjIlMkMlMjJtYXJrJTIyJTNBJTIyLSUyMiUyQyUyMmRhdGElMjIlM0ElMjJmcm9tJTIwbW9ua2V5X2lzbGFuZC5jYy5zZXJ2aWNlcy50ZWxlbWV0cnkucHJvY2Vzc2luZy5zeXN0ZW1faW5mb19jb2xsZWN0b3JzLmhvc3RuYW1lJTIwaW1wb3J0JTIwJTVDJTVDJTIyJTJDJTIybGluZU51bWJlcnMlMjIlM0ElN0IlMjJhJTIyJTNBMTIlN0QlN0QlMkMlN0IlMjJ0eXBlJTIyJTNBJTIyZGVsJTIyJTJDJTIybWFyayUyMiUzQSUyMi0lMjIlMkMlMjJkYXRhJTIyJTNBJTIyJTIwJTIwJTIwJTIwcHJvY2Vzc19ob3N0bmFtZV90ZWxlbWV0cnklMjIlMkMlMjJsaW5lTnVtYmVycyUyMiUzQSU3QiUyMmElMjIlM0ExMyU3RCU3RCUyQyU3QiUyMnR5cGUlMjIlM0ElMjJjb250ZXh0JTIyJTJDJTIyZGF0YSUyMiUzQSUyMiUyMGZyb20lMjBtb25rZXlfaXNsYW5kLmNjLnNlcnZpY2VzLnRlbGVtZXRyeS56ZXJvX3RydXN0X3Rlc3RzLmFudGl2aXJ1c19leGlzdGVuY2UlMjBpbXBvcnQlMjAlNUMlNUMlMjIlMkMlMjJsaW5lTnVtYmVycyUyMiUzQSU3QiUyMmElMjIlM0ExNCUyQyUyMmIlMjIlM0ExMSU3RCU3RCUyQyU3QiUyMnR5cGUlMjIlM0ElMjJjb250ZXh0JTIyJTJDJTIyZGF0YSUyMiUzQSUyMiUyMCUyMCUyMCUyMCUyMHRlc3RfYW50aXZpcnVzX2V4aXN0ZW5jZSUyMiUyQyUyMmxpbmVOdW1iZXJzJTIyJTNBJTdCJTIyYSUyMiUzQTE1JTJDJTIyYiUyMiUzQTEyJTdEJTdEJTJDJTdCJTIydHlwZSUyMiUzQSUyMmNvbnRleHQlMjIlMkMlMjJkYXRhJTIyJTNBJTIyJTIwJTIyJTJDJTIybGluZU51bWJlcnMlMjIlM0ElN0IlMjJhJTIyJTNBMTYlMkMlMjJiJTIyJTNBMTMlN0QlN0QlNUQlMkMlMjJsaW5lTnVtYmVycyUyMiUzQSU3QiUyMmElMjIlM0ElN0IlMjJzdGFydExpbmUlMjIlM0EzJTJDJTIybGluZXNDb3VudCUyMiUzQTE0JTdEJTJDJTIyYiUyMiUzQSU3QiUyMnN0YXJ0TGluZSUyMiUzQTMlMkMlMjJsaW5lc0NvdW50JTIyJTNBMTElN0QlN0QlN0QlN0Q=", - "JTdCJTIyaHVuayUyMiUzQSU3QiUyMmhlYWRlciUyMiUzQSUyMiU0MCU0MCUyMC0xOSUyQzclMjAlMkIxNiUyQzYlMjAlNDAlNDAlMjBsb2dnZXIlMjAlM0QlMjBsb2dnaW5nLmdldExvZ2dlcihfX25hbWVfXyklMjIlMkMlMjJjaGFuZ2VzJTIyJTNBJTVCJTdCJTIydHlwZSUyMiUzQSUyMmNvbnRleHQlMjIlMkMlMjJkYXRhJTIyJTNBJTIyJTIwU1lTVEVNX0lORk9fQ09MTEVDVE9SX1RPX1RFTEVNRVRSWV9QUk9DRVNTT1JTJTIwJTNEJTIwJTdCJTIyJTJDJTIybGluZU51bWJlcnMlMjIlM0ElN0IlMjJhJTIyJTNBMTklMkMlMjJiJTIyJTNBMTYlN0QlN0QlMkMlN0IlMjJ0eXBlJTIyJTNBJTIyY29udGV4dCUyMiUyQyUyMmRhdGElMjIlM0ElMjIlMjAlMjAlMjAlMjAlMjBBV1NfQ09MTEVDVE9SJTNBJTIwJTVCcHJvY2Vzc19hd3NfdGVsZW1ldHJ5JTVEJTJDJTIyJTJDJTIybGluZU51bWJlcnMlMjIlM0ElN0IlMjJhJTIyJTNBMjAlMkMlMjJiJTIyJTNBMTclN0QlN0QlMkMlN0IlMjJ0eXBlJTIyJTNBJTIyY29udGV4dCUyMiUyQyUyMmRhdGElMjIlM0ElMjIlMjAlMjAlMjAlMjAlMjBFTlZJUk9OTUVOVF9DT0xMRUNUT1IlM0ElMjAlNUJwcm9jZXNzX2Vudmlyb25tZW50X3RlbGVtZXRyeSU1RCUyQyUyMiUyQyUyMmxpbmVOdW1iZXJzJTIyJTNBJTdCJTIyYSUyMiUzQTIxJTJDJTIyYiUyMiUzQTE4JTdEJTdEJTJDJTdCJTIydHlwZSUyMiUzQSUyMmRlbCUyMiUyQyUyMm1hcmslMjIlM0ElMjItJTIyJTJDJTIyZGF0YSUyMiUzQSUyMiUyMCUyMCUyMCUyMEhPU1ROQU1FX0NPTExFQ1RPUiUzQSUyMCU1QnByb2Nlc3NfaG9zdG5hbWVfdGVsZW1ldHJ5JTVEJTJDJTIyJTJDJTIybGluZU51bWJlcnMlMjIlM0ElN0IlMjJhJTIyJTNBMjIlN0QlN0QlMkMlN0IlMjJ0eXBlJTIyJTNBJTIyY29udGV4dCUyMiUyQyUyMmRhdGElMjIlM0ElMjIlMjAlMjAlMjAlMjAlMjBQUk9DRVNTX0xJU1RfQ09MTEVDVE9SJTNBJTIwJTVCdGVzdF9hbnRpdmlydXNfZXhpc3RlbmNlJTVEJTIyJTJDJTIybGluZU51bWJlcnMlMjIlM0ElN0IlMjJhJTIyJTNBMjMlMkMlMjJiJTIyJTNBMTklN0QlN0QlMkMlN0IlMjJ0eXBlJTIyJTNBJTIyY29udGV4dCUyMiUyQyUyMmRhdGElMjIlM0ElMjIlMjAlN0QlMjIlMkMlMjJsaW5lTnVtYmVycyUyMiUzQSU3QiUyMmElMjIlM0EyNCUyQyUyMmIlMjIlM0EyMCU3RCU3RCUyQyU3QiUyMnR5cGUlMjIlM0ElMjJjb250ZXh0JTIyJTJDJTIyZGF0YSUyMiUzQSUyMiUyMCUyMiUyQyUyMmxpbmVOdW1iZXJzJTIyJTNBJTdCJTIyYSUyMiUzQTI1JTJDJTIyYiUyMiUzQTIxJTdEJTdEJTVEJTJDJTIybGluZU51bWJlcnMlMjIlM0ElN0IlMjJhJTIyJTNBJTdCJTIyc3RhcnRMaW5lJTIyJTNBMTklMkMlMjJsaW5lc0NvdW50JTIyJTNBNyU3RCUyQyUyMmIlMjIlM0ElN0IlMjJzdGFydExpbmUlMjIlM0ExNiUyQyUyMmxpbmVzQ291bnQlMjIlM0E2JTdEJTdEJTdEJTdE" + "diffType": "MODIFIED", + "fileDiffHeader": "diff --git a/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/system_info_telemetry_dispatcher.py b/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/system_info_telemetry_dispatcher.py\nindex 1d71c89c..edb1aefa 100644\n--- a/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/system_info_telemetry_dispatcher.py\n+++ b/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/system_info_telemetry_dispatcher.py", + "hunks": [ + { + "swimmHunkMetadata": {}, + "hunkDiffLines": [ + "@@ -2,11 +2,10 @@", + " import typing", + " ", + " from common.common_consts.system_info_collectors_names import (", + "- AWS_COLLECTOR, ENVIRONMENT_COLLECTOR, HOSTNAME_COLLECTOR, PROCESS_LIST_COLLECTOR, SCOUTSUITE_COLLECTOR)", + "+ AWS_COLLECTOR, ENVIRONMENT_COLLECTOR, PROCESS_LIST_COLLECTOR, SCOUTSUITE_COLLECTOR)", + " from monkey_island.cc.services.telemetry.processing.system_info_collectors.aws import process_aws_telemetry", + " from monkey_island.cc.services.telemetry.processing.system_info_collectors.environment import \\", + " process_environment_telemetry", + "-from monkey_island.cc.services.telemetry.processing.system_info_collectors.hostname import process_hostname_telemetry", + " from monkey_island.cc.services.telemetry.processing.system_info_collectors.scoutsuite import \\", + " process_scout_suite_telemetry", + " from monkey_island.cc.services.telemetry.zero_trust_checks.antivirus_existence import check_antivirus_existence" + ] + }, + { + "swimmHunkMetadata": {}, + "hunkDiffLines": [ + "@@ -16,7 +15,6 @@", + " SYSTEM_INFO_COLLECTOR_TO_TELEMETRY_PROCESSORS = {", + " AWS_COLLECTOR: [process_aws_telemetry],", + " ENVIRONMENT_COLLECTOR: [process_environment_telemetry],", + "- HOSTNAME_COLLECTOR: [process_hostname_telemetry],", + " PROCESS_LIST_COLLECTOR: [check_antivirus_existence],", + " SCOUTSUITE_COLLECTOR: [process_scout_suite_telemetry]", + " }" + ] + } ] } }, - "diff": "ZGlmZiUyMC0tZ2l0JTIwYSUyRm1vbmtleSUyRmNvbW1vbiUyRmRhdGElMkZzeXN0ZW1faW5mb19jb2xsZWN0b3JzX25hbWVzLnB5JTIwYiUyRm1vbmtleSUyRmNvbW1vbiUyRmRhdGElMkZzeXN0ZW1faW5mb19jb2xsZWN0b3JzX25hbWVzLnB5JTBBaW5kZXglMjAxNzVhMDU0ZS4uM2I0NzhkYzklMjAxMDA2NDQlMEEtLS0lMjBhJTJGbW9ua2V5JTJGY29tbW9uJTJGZGF0YSUyRnN5c3RlbV9pbmZvX2NvbGxlY3RvcnNfbmFtZXMucHklMEElMkIlMkIlMkIlMjBiJTJGbW9ua2V5JTJGY29tbW9uJTJGZGF0YSUyRnN5c3RlbV9pbmZvX2NvbGxlY3RvcnNfbmFtZXMucHklMEElNDAlNDAlMjAtMSUyQzUlMjAlMkIxJTJDNSUyMCU0MCU0MCUwQSUyMEFXU19DT0xMRUNUT1IlMjAlM0QlMjAlMjJBd3NDb2xsZWN0b3IlMjIlMEEtSE9TVE5BTUVfQ09MTEVDVE9SJTIwJTNEJTIwJTIySG9zdG5hbWVDb2xsZWN0b3IlMjIlMEElMkIlMjMlMjBTV0lNTUVSJTNBJTIwQ29sbGVjdG9yJTIwbmFtZSUyMGdvZXMlMjBoZXJlLiUwQSUyMEVOVklST05NRU5UX0NPTExFQ1RPUiUyMCUzRCUyMCUyMkVudmlyb25tZW50Q29sbGVjdG9yJTIyJTBBJTIwUFJPQ0VTU19MSVNUX0NPTExFQ1RPUiUyMCUzRCUyMCUyMlByb2Nlc3NMaXN0Q29sbGVjdG9yJTIyJTBBJTIwTUlNSUtBVFpfQ09MTEVDVE9SJTIwJTNEJTIwJTIyTWltaWthdHpDb2xsZWN0b3IlMjIlMEFkaWZmJTIwLS1naXQlMjBhJTJGbW9ua2V5JTJGaW5mZWN0aW9uX21vbmtleSUyRnN5c3RlbV9pbmZvJTJGY29sbGVjdG9ycyUyRmhvc3RuYW1lX2NvbGxlY3Rvci5weSUyMGIlMkZtb25rZXklMkZpbmZlY3Rpb25fbW9ua2V5JTJGc3lzdGVtX2luZm8lMkZjb2xsZWN0b3JzJTJGaG9zdG5hbWVfY29sbGVjdG9yLnB5JTBBaW5kZXglMjBhZTk1NjA4MS4uNGRjNjcwMWElMjAxMDA2NDQlMEEtLS0lMjBhJTJGbW9ua2V5JTJGaW5mZWN0aW9uX21vbmtleSUyRnN5c3RlbV9pbmZvJTJGY29sbGVjdG9ycyUyRmhvc3RuYW1lX2NvbGxlY3Rvci5weSUwQSUyQiUyQiUyQiUyMGIlMkZtb25rZXklMkZpbmZlY3Rpb25fbW9ua2V5JTJGc3lzdGVtX2luZm8lMkZjb2xsZWN0b3JzJTJGaG9zdG5hbWVfY29sbGVjdG9yLnB5JTBBJTQwJTQwJTIwLTElMkMxNiUyMCUyQjElMkM1JTIwJTQwJTQwJTBBJTIwaW1wb3J0JTIwbG9nZ2luZyUwQS1pbXBvcnQlMjBzb2NrZXQlMEEtJTBBLWZyb20lMjBjb21tb24uZGF0YS5zeXN0ZW1faW5mb19jb2xsZWN0b3JzX25hbWVzJTIwaW1wb3J0JTIwSE9TVE5BTUVfQ09MTEVDVE9SJTBBLWZyb20lMjBpbmZlY3Rpb25fbW9ua2V5LnN5c3RlbV9pbmZvLnN5c3RlbV9pbmZvX2NvbGxlY3RvciUyMGltcG9ydCUyMCU1QyUwQS0lMjAlMjAlMjAlMjBTeXN0ZW1JbmZvQ29sbGVjdG9yJTBBJTIwJTBBJTIwbG9nZ2VyJTIwJTNEJTIwbG9nZ2luZy5nZXRMb2dnZXIoX19uYW1lX18pJTBBJTIwJTBBLSUwQS1jbGFzcyUyMEhvc3RuYW1lQ29sbGVjdG9yKFN5c3RlbUluZm9Db2xsZWN0b3IpJTNBJTBBLSUyMCUyMCUyMCUyMGRlZiUyMF9faW5pdF9fKHNlbGYpJTNBJTBBLSUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMHN1cGVyKCkuX19pbml0X18obmFtZSUzREhPU1ROQU1FX0NPTExFQ1RPUiklMEEtJTBBLSUyMCUyMCUyMCUyMGRlZiUyMGNvbGxlY3Qoc2VsZiklMjAtJTNFJTIwZGljdCUzQSUwQS0lMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjByZXR1cm4lMjAlN0IlMjJob3N0bmFtZSUyMiUzQSUyMHNvY2tldC5nZXRmcWRuKCklN0QlMEElMkIlMjMlMjBTV0lNTUVSJTNBJTIwVGhlJTIwY29sbGVjdG9yJTIwY2xhc3MlMjBnb2VzJTIwaGVyZS4lMEFkaWZmJTIwLS1naXQlMjBhJTJGbW9ua2V5JTJGbW9ua2V5X2lzbGFuZCUyRmNjJTJGc2VydmljZXMlMkZjb25maWdfc2NoZW1hJTJGZGVmaW5pdGlvbnMlMkZzeXN0ZW1faW5mb19jb2xsZWN0b3JfY2xhc3Nlcy5weSUyMGIlMkZtb25rZXklMkZtb25rZXlfaXNsYW5kJTJGY2MlMkZzZXJ2aWNlcyUyRmNvbmZpZ19zY2hlbWElMkZkZWZpbml0aW9ucyUyRnN5c3RlbV9pbmZvX2NvbGxlY3Rvcl9jbGFzc2VzLnB5JTBBaW5kZXglMjA1ZjExM2Y0YS4uNzA0M2UyMjclMjAxMDA2NDQlMEEtLS0lMjBhJTJGbW9ua2V5JTJGbW9ua2V5X2lzbGFuZCUyRmNjJTJGc2VydmljZXMlMkZjb25maWdfc2NoZW1hJTJGZGVmaW5pdGlvbnMlMkZzeXN0ZW1faW5mb19jb2xsZWN0b3JfY2xhc3Nlcy5weSUwQSUyQiUyQiUyQiUyMGIlMkZtb25rZXklMkZtb25rZXlfaXNsYW5kJTJGY2MlMkZzZXJ2aWNlcyUyRmNvbmZpZ19zY2hlbWElMkZkZWZpbml0aW9ucyUyRnN5c3RlbV9pbmZvX2NvbGxlY3Rvcl9jbGFzc2VzLnB5JTBBJTQwJTQwJTIwLTElMkM3JTIwJTJCMSUyQzYlMjAlNDAlNDAlMEElMjBmcm9tJTIwY29tbW9uLmRhdGEuc3lzdGVtX2luZm9fY29sbGVjdG9yc19uYW1lcyUyMGltcG9ydCUyMChBV1NfQ09MTEVDVE9SJTJDJTBBJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwQVpVUkVfQ1JFRF9DT0xMRUNUT1IlMkMlMEElMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjBFTlZJUk9OTUVOVF9DT0xMRUNUT1IlMkMlMEEtJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwSE9TVE5BTUVfQ09MTEVDVE9SJTJDJTBBJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwTUlNSUtBVFpfQ09MTEVDVE9SJTJDJTBBJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwUFJPQ0VTU19MSVNUX0NPTExFQ1RPUiklMEElMjAlMEElNDAlNDAlMjAtMzclMkMxNSUyMCUyQjM2JTJDNyUyMCU0MCU0MCUyMFNZU1RFTV9JTkZPX0NPTExFQ1RPUl9DTEFTU0VTJTIwJTNEJTIwJTdCJTBBJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIyaW5mbyUyMiUzQSUyMCUyMklmJTIwb24lMjBBV1MlMkMlMjBjb2xsZWN0cyUyMG1vcmUlMjBpbmZvcm1hdGlvbiUyMGFib3V0JTIwdGhlJTIwQVdTJTIwaW5zdGFuY2UlMjBjdXJyZW50bHklMjBydW5uaW5nJTIwb24uJTIyJTJDJTBBJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIyYXR0YWNrX3RlY2huaXF1ZXMlMjIlM0ElMjAlNUIlMjJUMTA4MiUyMiU1RCUwQSUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCU3RCUyQyUwQS0lMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlN0IlMEEtJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIydHlwZSUyMiUzQSUyMCUyMnN0cmluZyUyMiUyQyUwQS0lMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjJlbnVtJTIyJTNBJTIwJTVCJTBBLSUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMEhPU1ROQU1FX0NPTExFQ1RPUiUwQS0lMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlNUQlMkMlMEEtJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIydGl0bGUlMjIlM0ElMjAlMjJIb3N0bmFtZSUyMGNvbGxlY3RvciUyMiUyQyUwQS0lMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjJpbmZvJTIyJTNBJTIwJTIyQ29sbGVjdHMlMjBtYWNoaW5lJ3MlMjBob3N0bmFtZS4lMjIlMkMlMEEtJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIyYXR0YWNrX3RlY2huaXF1ZXMlMjIlM0ElMjAlNUIlMjJUMTA4MiUyMiUyQyUyMCUyMlQxMDE2JTIyJTVEJTBBLSUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCU3RCUyQyUwQSUyQiUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMyUyMFNXSU1NRVIlM0ElMjBDb2xsZWN0b3IlMjBjb25maWclMjBnb2VzJTIwaGVyZS4lMjBUaXAlM0ElMjBIb3N0bmFtZSUyMGNvbGxlY3Rpb24lMjByZWxhdGVzJTIwdG8lMjB0aGUlMjBUMTA4MiUyMGFuZCUyMFQxMDE2JTIwdGVjaG5pcXVlcy4lMEElMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlN0IlMEElMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjJ0eXBlJTIyJTNBJTIwJTIyc3RyaW5nJTIyJTJDJTBBJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIyZW51bSUyMiUzQSUyMCU1QiUwQWRpZmYlMjAtLWdpdCUyMGElMkZtb25rZXklMkZtb25rZXlfaXNsYW5kJTJGY2MlMkZzZXJ2aWNlcyUyRmNvbmZpZ19zY2hlbWElMkZtb25rZXkucHklMjBiJTJGbW9ua2V5JTJGbW9ua2V5X2lzbGFuZCUyRmNjJTJGc2VydmljZXMlMkZjb25maWdfc2NoZW1hJTJGbW9ua2V5LnB5JTBBaW5kZXglMjBiNDdkNmExNS4uMWIxOTYyYTQlMjAxMDA2NDQlMEEtLS0lMjBhJTJGbW9ua2V5JTJGbW9ua2V5X2lzbGFuZCUyRmNjJTJGc2VydmljZXMlMkZjb25maWdfc2NoZW1hJTJGbW9ua2V5LnB5JTBBJTJCJTJCJTJCJTIwYiUyRm1vbmtleSUyRm1vbmtleV9pc2xhbmQlMkZjYyUyRnNlcnZpY2VzJTJGY29uZmlnX3NjaGVtYSUyRm1vbmtleS5weSUwQSU0MCU0MCUyMC0xJTJDNyUyMCUyQjElMkM2JTIwJTQwJTQwJTBBJTIwZnJvbSUyMGNvbW1vbi5kYXRhLnN5c3RlbV9pbmZvX2NvbGxlY3RvcnNfbmFtZXMlMjBpbXBvcnQlMjAoQVdTX0NPTExFQ1RPUiUyQyUwQSUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMEFaVVJFX0NSRURfQ09MTEVDVE9SJTJDJTBBJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwRU5WSVJPTk1FTlRfQ09MTEVDVE9SJTJDJTBBLSUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMEhPU1ROQU1FX0NPTExFQ1RPUiUyQyUwQSUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyME1JTUlLQVRaX0NPTExFQ1RPUiUyQyUwQSUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMFBST0NFU1NfTElTVF9DT0xMRUNUT1IpJTBBJTIwJTBBJTQwJTQwJTIwLTg4JTJDNyUyMCUyQjg3JTJDNiUyMCU0MCU0MCUyME1PTktFWSUyMCUzRCUyMCU3QiUwQSUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMmRlZmF1bHQlMjIlM0ElMjAlNUIlMEElMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjBFTlZJUk9OTUVOVF9DT0xMRUNUT1IlMkMlMEElMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjBBV1NfQ09MTEVDVE9SJTJDJTBBLSUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMEhPU1ROQU1FX0NPTExFQ1RPUiUyQyUwQSUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMFBST0NFU1NfTElTVF9DT0xMRUNUT1IlMkMlMEElMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjBNSU1JS0FUWl9DT0xMRUNUT1IlMkMlMEElMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjBBWlVSRV9DUkVEX0NPTExFQ1RPUiUwQWRpZmYlMjAtLWdpdCUyMGElMkZtb25rZXklMkZtb25rZXlfaXNsYW5kJTJGY2MlMkZzZXJ2aWNlcyUyRnRlbGVtZXRyeSUyRnByb2Nlc3NpbmclMkZzeXN0ZW1faW5mb19jb2xsZWN0b3JzJTJGaG9zdG5hbWUucHklMjBiJTJGbW9ua2V5JTJGbW9ua2V5X2lzbGFuZCUyRmNjJTJGc2VydmljZXMlMkZ0ZWxlbWV0cnklMkZwcm9jZXNzaW5nJTJGc3lzdGVtX2luZm9fY29sbGVjdG9ycyUyRmhvc3RuYW1lLnB5JTBBaW5kZXglMjBlMmRlNDUxOS4uMDRiYzM1NTYlMjAxMDA2NDQlMEEtLS0lMjBhJTJGbW9ua2V5JTJGbW9ua2V5X2lzbGFuZCUyRmNjJTJGc2VydmljZXMlMkZ0ZWxlbWV0cnklMkZwcm9jZXNzaW5nJTJGc3lzdGVtX2luZm9fY29sbGVjdG9ycyUyRmhvc3RuYW1lLnB5JTBBJTJCJTJCJTJCJTIwYiUyRm1vbmtleSUyRm1vbmtleV9pc2xhbmQlMkZjYyUyRnNlcnZpY2VzJTJGdGVsZW1ldHJ5JTJGcHJvY2Vzc2luZyUyRnN5c3RlbV9pbmZvX2NvbGxlY3RvcnMlMkZob3N0bmFtZS5weSUwQSU0MCU0MCUyMC0xJTJDOSUyMCUyQjElMkM5JTIwJTQwJTQwJTBBJTIwaW1wb3J0JTIwbG9nZ2luZyUwQSUyMCUwQS1mcm9tJTIwbW9ua2V5X2lzbGFuZC5jYy5tb2RlbHMubW9ua2V5JTIwaW1wb3J0JTIwTW9ua2V5JTBBJTJCJTIzJTIwU1dJTU1FUiUzQSUyMFRoaXMlMjB3aWxsJTIwYmUlMjB1c2VmdWwlMjAlM0EpJTIwbW9ua2V5X2lzbGFuZC5jYy5tb2RlbHMubW9ua2V5Lk1vbmtleSUyMGhhcyUyMHRoZSUyMHVzZWZ1bCUwQSUyQiUyMyUyMCUyMmdldF9zaW5nbGVfbW9ua2V5X2J5X2d1aWQlMjIlMjBhbmQlMjAlMjJzZXRfaG9zdG5hbWUlMjIlMjBtZXRob2RzLiUwQSUyMCUwQSUyMGxvZ2dlciUyMCUzRCUyMGxvZ2dpbmcuZ2V0TG9nZ2VyKF9fbmFtZV9fKSUwQSUyMCUwQSUyMCUwQS1kZWYlMjBwcm9jZXNzX2hvc3RuYW1lX3RlbGVtZXRyeShjb2xsZWN0b3JfcmVzdWx0cyUyQyUyMG1vbmtleV9ndWlkKSUzQSUwQS0lMjAlMjAlMjAlMjBNb25rZXkuZ2V0X3NpbmdsZV9tb25rZXlfYnlfZ3VpZChtb25rZXlfZ3VpZCkuc2V0X2hvc3RuYW1lKGNvbGxlY3Rvcl9yZXN1bHRzJTVCJTIyaG9zdG5hbWUlMjIlNUQpJTBBJTJCJTIzJTIwU1dJTU1FUiUzQSUyMFByb2Nlc3NpbmclMjBmdW5jdGlvbiUyMGdvZXMlMjBoZXJlLiUwQWRpZmYlMjAtLWdpdCUyMGElMkZtb25rZXklMkZtb25rZXlfaXNsYW5kJTJGY2MlMkZzZXJ2aWNlcyUyRnRlbGVtZXRyeSUyRnByb2Nlc3NpbmclMkZzeXN0ZW1faW5mb19jb2xsZWN0b3JzJTJGc3lzdGVtX2luZm9fdGVsZW1ldHJ5X2Rpc3BhdGNoZXIucHklMjBiJTJGbW9ua2V5JTJGbW9ua2V5X2lzbGFuZCUyRmNjJTJGc2VydmljZXMlMkZ0ZWxlbWV0cnklMkZwcm9jZXNzaW5nJTJGc3lzdGVtX2luZm9fY29sbGVjdG9ycyUyRnN5c3RlbV9pbmZvX3RlbGVtZXRyeV9kaXNwYXRjaGVyLnB5JTBBaW5kZXglMjA2MzlhMzkyYy4uN2FhNmQzYTYlMjAxMDA2NDQlMEEtLS0lMjBhJTJGbW9ua2V5JTJGbW9ua2V5X2lzbGFuZCUyRmNjJTJGc2VydmljZXMlMkZ0ZWxlbWV0cnklMkZwcm9jZXNzaW5nJTJGc3lzdGVtX2luZm9fY29sbGVjdG9ycyUyRnN5c3RlbV9pbmZvX3RlbGVtZXRyeV9kaXNwYXRjaGVyLnB5JTBBJTJCJTJCJTJCJTIwYiUyRm1vbmtleSUyRm1vbmtleV9pc2xhbmQlMkZjYyUyRnNlcnZpY2VzJTJGdGVsZW1ldHJ5JTJGcHJvY2Vzc2luZyUyRnN5c3RlbV9pbmZvX2NvbGxlY3RvcnMlMkZzeXN0ZW1faW5mb190ZWxlbWV0cnlfZGlzcGF0Y2hlci5weSUwQSU0MCU0MCUyMC0zJTJDMTQlMjAlMkIzJTJDMTElMjAlNDAlNDAlMjBpbXBvcnQlMjB0eXBpbmclMEElMjAlMEElMjBmcm9tJTIwY29tbW9uLmRhdGEuc3lzdGVtX2luZm9fY29sbGVjdG9yc19uYW1lcyUyMGltcG9ydCUyMChBV1NfQ09MTEVDVE9SJTJDJTBBJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwRU5WSVJPTk1FTlRfQ09MTEVDVE9SJTJDJTBBLSUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMEhPU1ROQU1FX0NPTExFQ1RPUiUyQyUwQSUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMFBST0NFU1NfTElTVF9DT0xMRUNUT1IpJTBBJTIwZnJvbSUyMG1vbmtleV9pc2xhbmQuY2Muc2VydmljZXMudGVsZW1ldHJ5LnByb2Nlc3Npbmcuc3lzdGVtX2luZm9fY29sbGVjdG9ycy5hd3MlMjBpbXBvcnQlMjAlNUMlMEElMjAlMjAlMjAlMjAlMjBwcm9jZXNzX2F3c190ZWxlbWV0cnklMEElMjBmcm9tJTIwbW9ua2V5X2lzbGFuZC5jYy5zZXJ2aWNlcy50ZWxlbWV0cnkucHJvY2Vzc2luZy5zeXN0ZW1faW5mb19jb2xsZWN0b3JzLmVudmlyb25tZW50JTIwaW1wb3J0JTIwJTVDJTBBJTIwJTIwJTIwJTIwJTIwcHJvY2Vzc19lbnZpcm9ubWVudF90ZWxlbWV0cnklMEEtZnJvbSUyMG1vbmtleV9pc2xhbmQuY2Muc2VydmljZXMudGVsZW1ldHJ5LnByb2Nlc3Npbmcuc3lzdGVtX2luZm9fY29sbGVjdG9ycy5ob3N0bmFtZSUyMGltcG9ydCUyMCU1QyUwQS0lMjAlMjAlMjAlMjBwcm9jZXNzX2hvc3RuYW1lX3RlbGVtZXRyeSUwQSUyMGZyb20lMjBtb25rZXlfaXNsYW5kLmNjLnNlcnZpY2VzLnRlbGVtZXRyeS56ZXJvX3RydXN0X3Rlc3RzLmFudGl2aXJ1c19leGlzdGVuY2UlMjBpbXBvcnQlMjAlNUMlMEElMjAlMjAlMjAlMjAlMjB0ZXN0X2FudGl2aXJ1c19leGlzdGVuY2UlMEElMjAlMEElNDAlNDAlMjAtMTklMkM3JTIwJTJCMTYlMkM2JTIwJTQwJTQwJTIwbG9nZ2VyJTIwJTNEJTIwbG9nZ2luZy5nZXRMb2dnZXIoX19uYW1lX18pJTBBJTIwU1lTVEVNX0lORk9fQ09MTEVDVE9SX1RPX1RFTEVNRVRSWV9QUk9DRVNTT1JTJTIwJTNEJTIwJTdCJTBBJTIwJTIwJTIwJTIwJTIwQVdTX0NPTExFQ1RPUiUzQSUyMCU1QnByb2Nlc3NfYXdzX3RlbGVtZXRyeSU1RCUyQyUwQSUyMCUyMCUyMCUyMCUyMEVOVklST05NRU5UX0NPTExFQ1RPUiUzQSUyMCU1QnByb2Nlc3NfZW52aXJvbm1lbnRfdGVsZW1ldHJ5JTVEJTJDJTBBLSUyMCUyMCUyMCUyMEhPU1ROQU1FX0NPTExFQ1RPUiUzQSUyMCU1QnByb2Nlc3NfaG9zdG5hbWVfdGVsZW1ldHJ5JTVEJTJDJTBBJTIwJTIwJTIwJTIwJTIwUFJPQ0VTU19MSVNUX0NPTExFQ1RPUiUzQSUyMCU1QnRlc3RfYW50aXZpcnVzX2V4aXN0ZW5jZSU1RCUwQSUyMCU3RCUwQSUyMCUwQQ==", - "description": "JTIzJTIwV2hhdCUyMGFyZSUyMHN5c3RlbSUyMGluZm8lMjBjb2xsZWN0b3JzJTNGJTBBJTBBV2VsbCUyQyUyMHRoZSUyMG5hbWUlMjBwcmV0dHklMjBtdWNoJTIwZXhwbGFpbnMlMjBpdC4lMjBUaGV5JTIwYXJlJTIwTW9ua2V5JTIwY2xhc3NlcyUyMHdoaWNoJTIwY29sbGVjdCUyMHZhcmlvdXMlMjBpbmZvcm1hdGlvbiUyMHJlZ2FyZGluZyUyMHRoZSUyMHZpY3RpbSUyMHN5c3RlbSUyQyUyMHN1Y2glMjBhcyUyMEVudmlyb25tZW50JTJDJTIwU1NIJTIwSW5mbyUyQyUyMFByb2Nlc3MlMjBMaXN0JTJDJTIwTmV0c3RhdCUyMGFuZCUyMG1vcmUuJTIwJTBBJTBBJTIzJTIzJTIwV2hhdCUyMHNob3VsZCUyMEklMjBhZGQlM0YlMjAlMEElMEFBJTIwc3lzdGVtJTIwaW5mbyUyMGNvbGxlY3RvciUyMHdoaWNoJTIwY29sbGVjdHMlMjB0aGUlMjBob3N0bmFtZSUyMG9mJTIwdGhlJTIwc3lzdGVtLiUwQSUwQSUyMyUyMyUyMFRlc3QlMjBtYW51YWxseSUwQSUwQU9uY2UlMjB5b3UncmUlMjBkb25lJTJDJTIwbWFrZSUyMHN1cmUlMjB0aGF0JTIweW91ciUyMGNvbGxlY3RvciUzQSUwQSolMjBBcHBlYXJzJTIwaW4lMjB0aGUlMjBJc2xhbmQlMjBjb25maWd1cmF0aW9uJTJDJTIwYW5kJTIwaXMlMjBlbmFibGVkJTIwYnklMjBkZWZhdWx0JTBBKiUyMFRoZSUyMGNvbGxlY3RvciUyMGFjdHVhbGx5JTIwcnVucyUyMHdoZW4lMjBleGVjdXRpbmclMjBhJTIwTW9ua2V5LiUwQSolMjBSZXN1bHRzJTIwc2hvdyUyMHVwJTIwaW4lMjB0aGUlMjByZWxldmFudCUyMHBsYWNlcyUzQSUwQSUyMCUyMColMjBUaGUlMjBpbmZlY3Rpb24lMjBtYXAuJTBBJTIwJTIwKiUyMFRoZSUyMHNlY3VyaXR5JTIwcmVwb3J0LiUwQSUyMCUyMColMjBUaGUlMjByZWxldmFudCUyME1JVFJFJTIwdGVjaG5pcXVlcy4lMEElMEEqKlRoZXJlJTIwYXJlJTIwYSUyMGxvdCUyMG9mJTIwaGludHMlMjBmb3IlMjB0aGlzJTIwdW5pdCUyMC0lMjBkb24ndCUyMGJlJTIwYWZyYWlkJTIwdG8lMjB1c2UlMjB0aGVtISoq", - "dod": "QWRkJTIwYSUyMHN5c3RlbSUyMGluZm8lMjBjb2xsZWN0b3IlMjB0aGF0JTIwY29sbGVjdHMlMjB0aGUlMjBtYWNoaW5lJTIwaG9zdG5hbWUu", - "summary": "U3lzdGVtJTIwaW5mbyUyMGNvbGxlY3RvcnMlMjBhcmUlMjB1c2VmdWwlMjB0byUyMGdldCUyMG1vcmUlMjBkYXRhJTIwZm9yJTIwdmFyaW91cyUyMHRoaW5ncyUyQyUyMHN1Y2glMjBhcyUyMFpUJTIwdGVzdHMlMjBvciUyME1JVFJFJTIwdGVjaG5pcXVlcy4lMjBUYWtlJTIwYSUyMGxvb2slMjBhdCUyMHNvbWUlMjBvdGhlciUyMHRlY2huaXF1ZXMh", - "file_version": "1.0.2", - "app_version": "0.1.60" -} + "app_version": "0.2.1", + "file_version": "1.0.3" +} \ No newline at end of file From c697f895a6e19cce8462352323be453b85161922 Mon Sep 17 00:00:00 2001 From: Swimm Date: Wed, 7 Oct 2020 20:57:57 +0300 Subject: [PATCH 074/152] Swimm: updated unit (tbxb2cGgUiJQ8Btma0fp) - fixing and upgrading to scheme 1.0.3 --- .swm/tbxb2cGgUiJQ8Btma0fp.swm | 149 ++++++++++++++++++++++------------ 1 file changed, 96 insertions(+), 53 deletions(-) diff --git a/.swm/tbxb2cGgUiJQ8Btma0fp.swm b/.swm/tbxb2cGgUiJQ8Btma0fp.swm index 5595eafaa..b19ec94a3 100644 --- a/.swm/tbxb2cGgUiJQ8Btma0fp.swm +++ b/.swm/tbxb2cGgUiJQ8Btma0fp.swm @@ -1,74 +1,117 @@ { "id": "tbxb2cGgUiJQ8Btma0fp", "name": "Add a simple Post Breach action", + "dod": "You should add a new PBA to the Monkey which creates a new user on the machine.", + "description": "Read [our documentation about adding a new PBA](https://www.guardicore.com/infectionmonkey/docs/development/adding-post-breach-actions/).\n\nAfter that we want you to add the BackdoorUser PBA. The commands that add users for Win and Linux can be retrieved from `get_commands_to_add_user` - make sure you see how to use this function correctly. \n\nNote that the PBA should impact the T1136 MITRE technique as well! \n\n# Manual test to confirm\n\n1. Run the Monkey Island\n2. Make sure your new PBA is enabled by default in the config - for this test, disable network scanning, exploiting, and all other PBAs\n3. Run Monkey\n4. See the PBA in the security report\n5, See the PBA in the MITRE report in the relevant technique\n", + "summary": "Take a look at the configuration of the island again - see the \"command to run after breach\" option we offer the user? It's implemented exactly like you did right now but each user can do it for themselves. \n\nHowever, what if the PBA needs to do stuff which is more complex than just running a few commands? In that case... ", "tests": [], "hints": [ "See `ScheduleJobs` PBA for an example of a PBA which only uses shell commands", "Make sure to add the PBA to the configuration as well." ], - "files": { + "swimmPatch": { "monkey/common/common_consts/post_breach_consts.py": { - "index": [ - "c3bba995..031f9ad0", - "100644" - ], - "fileA": "monkey/common/common_consts/post_breach_consts.py", - "fileB": "monkey/common/common_consts/post_breach_consts.py", - "status": "MODIFIED", - "numLineDeletions": 1, - "numLineAdditions": 1, - "hunkContainers": [ - "JTdCJTIyaHVuayUyMiUzQSU3QiUyMmhlYWRlciUyMiUzQSUyMiU0MCU0MCUyMC0xJTJDNSUyMCUyQjElMkM1JTIwJTQwJTQwJTIyJTJDJTIyY2hhbmdlcyUyMiUzQSU1QiU3QiUyMnR5cGUlMjIlM0ElMjJjb250ZXh0JTIyJTJDJTIyZGF0YSUyMiUzQSUyMiUyMFBPU1RfQlJFQUNIX0NPTU1VTklDQVRFX0FTX05FV19VU0VSJTIwJTNEJTIwJTVDJTIyQ29tbXVuaWNhdGUlMjBhcyUyMG5ldyUyMHVzZXIlNUMlMjIlMjIlMkMlMjJsaW5lTnVtYmVycyUyMiUzQSU3QiUyMmElMjIlM0ExJTJDJTIyYiUyMiUzQTElN0QlN0QlMkMlN0IlMjJ0eXBlJTIyJTNBJTIyZGVsJTIyJTJDJTIybWFyayUyMiUzQSUyMi0lMjIlMkMlMjJkYXRhJTIyJTNBJTIyUE9TVF9CUkVBQ0hfQkFDS0RPT1JfVVNFUiUyMCUzRCUyMCU1QyUyMkJhY2tkb29yJTIwdXNlciU1QyUyMiUyMiUyQyUyMmxpbmVOdW1iZXJzJTIyJTNBJTdCJTIyYSUyMiUzQTIlN0QlN0QlMkMlN0IlMjJ0eXBlJTIyJTNBJTIyYWRkJTIyJTJDJTIybWFyayUyMiUzQSUyMiUyQiUyMiUyQyUyMmRhdGElMjIlM0ElMjIlMjMlMjBTd2ltbWVyJTNBJTIwUFVUJTIwVEhFJTIwTkVXJTIwQ09OU1QlMjBIRVJFISUyMiUyQyUyMmxpbmVOdW1iZXJzJTIyJTNBJTdCJTIyYiUyMiUzQTIlN0QlN0QlMkMlN0IlMjJ0eXBlJTIyJTNBJTIyY29udGV4dCUyMiUyQyUyMmRhdGElMjIlM0ElMjIlMjBQT1NUX0JSRUFDSF9GSUxFX0VYRUNVVElPTiUyMCUzRCUyMCU1QyUyMkZpbGUlMjBleGVjdXRpb24lNUMlMjIlMjIlMkMlMjJsaW5lTnVtYmVycyUyMiUzQSU3QiUyMmElMjIlM0EzJTJDJTIyYiUyMiUzQTMlN0QlN0QlMkMlN0IlMjJ0eXBlJTIyJTNBJTIyY29udGV4dCUyMiUyQyUyMmRhdGElMjIlM0ElMjIlMjBQT1NUX0JSRUFDSF9TSEVMTF9TVEFSVFVQX0ZJTEVfTU9ESUZJQ0FUSU9OJTIwJTNEJTIwJTVDJTIyTW9kaWZ5JTIwc2hlbGwlMjBzdGFydHVwJTIwZmlsZSU1QyUyMiUyMiUyQyUyMmxpbmVOdW1iZXJzJTIyJTNBJTdCJTIyYSUyMiUzQTQlMkMlMjJiJTIyJTNBNCU3RCU3RCUyQyU3QiUyMnR5cGUlMjIlM0ElMjJjb250ZXh0JTIyJTJDJTIyZGF0YSUyMiUzQSUyMiUyMFBPU1RfQlJFQUNIX0hJRERFTl9GSUxFUyUyMCUzRCUyMCU1QyUyMkhpZGUlMjBmaWxlcyUyMGFuZCUyMGRpcmVjdG9yaWVzJTVDJTIyJTIyJTJDJTIybGluZU51bWJlcnMlMjIlM0ElN0IlMjJhJTIyJTNBNSUyQyUyMmIlMjIlM0E1JTdEJTdEJTVEJTJDJTIybGluZU51bWJlcnMlMjIlM0ElN0IlMjJhJTIyJTNBJTdCJTIyc3RhcnRMaW5lJTIyJTNBMSUyQyUyMmxpbmVzQ291bnQlMjIlM0E1JTdEJTJDJTIyYiUyMiUzQSU3QiUyMnN0YXJ0TGluZSUyMiUzQTElMkMlMjJsaW5lc0NvdW50JTIyJTNBNSU3RCU3RCU3RCU3RA==" + "diffType": "MODIFIED", + "fileDiffHeader": "diff --git a/monkey/common/common_consts/post_breach_consts.py b/monkey/common/common_consts/post_breach_consts.py\nindex 25e6679c..5198f006 100644\n--- a/monkey/common/common_consts/post_breach_consts.py\n+++ b/monkey/common/common_consts/post_breach_consts.py", + "hunks": [ + { + "swimmHunkMetadata": {}, + "hunkDiffLines": [ + "@@ -1,5 +1,4 @@", + " POST_BREACH_COMMUNICATE_AS_NEW_USER = \"Communicate as new user\"", + "-POST_BREACH_BACKDOOR_USER = \"Backdoor user\"", + " POST_BREACH_FILE_EXECUTION = \"File execution\"", + " POST_BREACH_SHELL_STARTUP_FILE_MODIFICATION = \"Modify shell startup file\"", + " POST_BREACH_HIDDEN_FILES = \"Hide files and directories\"" + ] + } ] }, "monkey/infection_monkey/post_breach/actions/add_user.py": { - "index": [ - "58be89a1..d8476a97", - "100644" - ], - "fileA": "monkey/infection_monkey/post_breach/actions/add_user.py", - "fileB": "monkey/infection_monkey/post_breach/actions/add_user.py", - "status": "MODIFIED", - "numLineDeletions": 9, - "numLineAdditions": 1, - "hunkContainers": [ - "JTdCJTIyaHVuayUyMiUzQSU3QiUyMmhlYWRlciUyMiUzQSUyMiU0MCU0MCUyMC0xJTJDMTUlMjAlMkIxJTJDNyUyMCU0MCU0MCUyMiUyQyUyMmNoYW5nZXMlMjIlM0ElNUIlN0IlMjJ0eXBlJTIyJTNBJTIyZGVsJTIyJTJDJTIybWFyayUyMiUzQSUyMi0lMjIlMkMlMjJkYXRhJTIyJTNBJTIyZnJvbSUyMGNvbW1vbi5kYXRhLnBvc3RfYnJlYWNoX2NvbnN0cyUyMGltcG9ydCUyMFBPU1RfQlJFQUNIX0JBQ0tET09SX1VTRVIlMjIlMkMlMjJsaW5lTnVtYmVycyUyMiUzQSU3QiUyMmElMjIlM0ExJTdEJTdEJTJDJTdCJTIydHlwZSUyMiUzQSUyMmRlbCUyMiUyQyUyMm1hcmslMjIlM0ElMjItJTIyJTJDJTIyZGF0YSUyMiUzQSUyMmZyb20lMjBpbmZlY3Rpb25fbW9ua2V5LmNvbmZpZyUyMGltcG9ydCUyMFdvcm1Db25maWd1cmF0aW9uJTIyJTJDJTIybGluZU51bWJlcnMlMjIlM0ElN0IlMjJhJTIyJTNBMiU3RCU3RCUyQyU3QiUyMnR5cGUlMjIlM0ElMjJjb250ZXh0JTIyJTJDJTIyZGF0YSUyMiUzQSUyMiUyMGZyb20lMjBpbmZlY3Rpb25fbW9ua2V5LnBvc3RfYnJlYWNoLnBiYSUyMGltcG9ydCUyMFBCQSUyMiUyQyUyMmxpbmVOdW1iZXJzJTIyJTNBJTdCJTIyYSUyMiUzQTMlMkMlMjJiJTIyJTNBMSU3RCU3RCUyQyU3QiUyMnR5cGUlMjIlM0ElMjJjb250ZXh0JTIyJTJDJTIyZGF0YSUyMiUzQSUyMiUyMGZyb20lMjBpbmZlY3Rpb25fbW9ua2V5LnV0aWxzLnVzZXJzJTIwaW1wb3J0JTIwZ2V0X2NvbW1hbmRzX3RvX2FkZF91c2VyJTIyJTJDJTIybGluZU51bWJlcnMlMjIlM0ElN0IlMjJhJTIyJTNBNCUyQyUyMmIlMjIlM0EyJTdEJTdEJTJDJTdCJTIydHlwZSUyMiUzQSUyMmNvbnRleHQlMjIlMkMlMjJkYXRhJTIyJTNBJTIyJTIwJTIyJTJDJTIybGluZU51bWJlcnMlMjIlM0ElN0IlMjJhJTIyJTNBNSUyQyUyMmIlMjIlM0EzJTdEJTdEJTJDJTdCJTIydHlwZSUyMiUzQSUyMmNvbnRleHQlMjIlMkMlMjJkYXRhJTIyJTNBJTIyJTIwJTIyJTJDJTIybGluZU51bWJlcnMlMjIlM0ElN0IlMjJhJTIyJTNBNiUyQyUyMmIlMjIlM0E0JTdEJTdEJTJDJTdCJTIydHlwZSUyMiUzQSUyMmNvbnRleHQlMjIlMkMlMjJkYXRhJTIyJTNBJTIyJTIwY2xhc3MlMjBCYWNrZG9vclVzZXIoUEJBKSUzQSUyMiUyQyUyMmxpbmVOdW1iZXJzJTIyJTNBJTdCJTIyYSUyMiUzQTclMkMlMjJiJTIyJTNBNSU3RCU3RCUyQyU3QiUyMnR5cGUlMjIlM0ElMjJjb250ZXh0JTIyJTJDJTIyZGF0YSUyMiUzQSUyMiUyMCUyMCUyMCUyMCUyMGRlZiUyMF9faW5pdF9fKHNlbGYpJTNBJTIyJTJDJTIybGluZU51bWJlcnMlMjIlM0ElN0IlMjJhJTIyJTNBOCUyQyUyMmIlMjIlM0E2JTdEJTdEJTJDJTdCJTIydHlwZSUyMiUzQSUyMmRlbCUyMiUyQyUyMm1hcmslMjIlM0ElMjItJTIyJTJDJTIyZGF0YSUyMiUzQSUyMiUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMGxpbnV4X2NtZHMlMkMlMjB3aW5kb3dzX2NtZHMlMjAlM0QlMjBnZXRfY29tbWFuZHNfdG9fYWRkX3VzZXIoJTIyJTJDJTIybGluZU51bWJlcnMlMjIlM0ElN0IlMjJhJTIyJTNBOSU3RCU3RCUyQyU3QiUyMnR5cGUlMjIlM0ElMjJkZWwlMjIlMkMlMjJtYXJrJTIyJTNBJTIyLSUyMiUyQyUyMmRhdGElMjIlM0ElMjIlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjBXb3JtQ29uZmlndXJhdGlvbi51c2VyX3RvX2FkZCUyQyUyMiUyQyUyMmxpbmVOdW1iZXJzJTIyJTNBJTdCJTIyYSUyMiUzQTEwJTdEJTdEJTJDJTdCJTIydHlwZSUyMiUzQSUyMmRlbCUyMiUyQyUyMm1hcmslMjIlM0ElMjItJTIyJTJDJTIyZGF0YSUyMiUzQSUyMiUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMFdvcm1Db25maWd1cmF0aW9uLnJlbW90ZV91c2VyX3Bhc3MpJTIyJTJDJTIybGluZU51bWJlcnMlMjIlM0ElN0IlMjJhJTIyJTNBMTElN0QlN0QlMkMlN0IlMjJ0eXBlJTIyJTNBJTIyZGVsJTIyJTJDJTIybWFyayUyMiUzQSUyMi0lMjIlMkMlMjJkYXRhJTIyJTNBJTIyJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwc3VwZXIoQmFja2Rvb3JVc2VyJTJDJTIwc2VsZikuX19pbml0X18oJTIyJTJDJTIybGluZU51bWJlcnMlMjIlM0ElN0IlMjJhJTIyJTNBMTIlN0QlN0QlMkMlN0IlMjJ0eXBlJTIyJTNBJTIyZGVsJTIyJTJDJTIybWFyayUyMiUzQSUyMi0lMjIlMkMlMjJkYXRhJTIyJTNBJTIyJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwUE9TVF9CUkVBQ0hfQkFDS0RPT1JfVVNFUiUyQyUyMiUyQyUyMmxpbmVOdW1iZXJzJTIyJTNBJTdCJTIyYSUyMiUzQTEzJTdEJTdEJTJDJTdCJTIydHlwZSUyMiUzQSUyMmRlbCUyMiUyQyUyMm1hcmslMjIlM0ElMjItJTIyJTJDJTIyZGF0YSUyMiUzQSUyMiUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMGxpbnV4X2NtZCUzRCclMjAnLmpvaW4obGludXhfY21kcyklMkMlMjIlMkMlMjJsaW5lTnVtYmVycyUyMiUzQSU3QiUyMmElMjIlM0ExNCU3RCU3RCUyQyU3QiUyMnR5cGUlMjIlM0ElMjJkZWwlMjIlMkMlMjJtYXJrJTIyJTNBJTIyLSUyMiUyQyUyMmRhdGElMjIlM0ElMjIlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjB3aW5kb3dzX2NtZCUzRHdpbmRvd3NfY21kcyklMjIlMkMlMjJsaW5lTnVtYmVycyUyMiUzQSU3QiUyMmElMjIlM0ExNSU3RCU3RCUyQyU3QiUyMnR5cGUlMjIlM0ElMjJhZGQlMjIlMkMlMjJtYXJrJTIyJTNBJTIyJTJCJTIyJTJDJTIyZGF0YSUyMiUzQSUyMiUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMHBhc3MlMjAlMjAlMjMlMjBTd2ltbWVyJTNBJTIwSW1wbCUyMGhlcmUhJTIyJTJDJTIybGluZU51bWJlcnMlMjIlM0ElN0IlMjJiJTIyJTNBNyU3RCU3RCU1RCUyQyUyMmxpbmVOdW1iZXJzJTIyJTNBJTdCJTIyYSUyMiUzQSU3QiUyMnN0YXJ0TGluZSUyMiUzQTElMkMlMjJsaW5lc0NvdW50JTIyJTNBMTUlN0QlMkMlMjJiJTIyJTNBJTdCJTIyc3RhcnRMaW5lJTIyJTNBMSUyQyUyMmxpbmVzQ291bnQlMjIlM0E3JTdEJTdEJTdEJTdE" + "diffType": "MODIFIED", + "fileDiffHeader": "diff --git a/monkey/infection_monkey/post_breach/actions/add_user.py b/monkey/infection_monkey/post_breach/actions/add_user.py\nindex a8584584..d8476a97 100644\n--- a/monkey/infection_monkey/post_breach/actions/add_user.py\n+++ b/monkey/infection_monkey/post_breach/actions/add_user.py", + "hunks": [ + { + "swimmHunkMetadata": {}, + "hunkDiffLines": [ + "@@ -1,15 +1,7 @@", + "-from common.common_consts.post_breach_consts import POST_BREACH_BACKDOOR_USER", + "-from infection_monkey.config import WormConfiguration", + " from infection_monkey.post_breach.pba import PBA", + " from infection_monkey.utils.users import get_commands_to_add_user", + " ", + " ", + " class BackdoorUser(PBA):", + " def __init__(self):", + "- linux_cmds, windows_cmds = get_commands_to_add_user(", + "+ pass # Swimmer: Impl here!", + "- WormConfiguration.user_to_add,", + "- WormConfiguration.remote_user_pass)", + "- super(BackdoorUser, self).__init__(", + "- POST_BREACH_BACKDOOR_USER,", + "- linux_cmd=' '.join(linux_cmds),", + "- windows_cmd=windows_cmds)" + ] + } ] }, "monkey/monkey_island/cc/services/attack/technique_reports/T1136.py": { - "index": [ - "086a1c13..da99e86c", - "100644" - ], - "fileA": "monkey/monkey_island/cc/services/attack/technique_reports/T1136.py", - "fileB": "monkey/monkey_island/cc/services/attack/technique_reports/T1136.py", - "status": "MODIFIED", - "numLineDeletions": 2, - "numLineAdditions": 2, - "hunkContainers": [ - "JTdCJTIyaHVuayUyMiUzQSU3QiUyMmhlYWRlciUyMiUzQSUyMiU0MCU0MCUyMC0xJTJDNSUyMCUyQjElMkM1JTIwJTQwJTQwJTIyJTJDJTIyY2hhbmdlcyUyMiUzQSU1QiU3QiUyMnR5cGUlMjIlM0ElMjJjb250ZXh0JTIyJTJDJTIyZGF0YSUyMiUzQSUyMiUyMGZyb20lMjBjb21tb24uZGF0YS5wb3N0X2JyZWFjaF9jb25zdHMlMjBpbXBvcnQlMjAoJTIyJTJDJTIybGluZU51bWJlcnMlMjIlM0ElN0IlMjJhJTIyJTNBMSUyQyUyMmIlMjIlM0ExJTdEJTdEJTJDJTdCJTIydHlwZSUyMiUzQSUyMmRlbCUyMiUyQyUyMm1hcmslMjIlM0ElMjItJTIyJTJDJTIyZGF0YSUyMiUzQSUyMiUyMCUyMCUyMCUyMFBPU1RfQlJFQUNIX0JBQ0tET09SX1VTRVIlMkMlMjBQT1NUX0JSRUFDSF9DT01NVU5JQ0FURV9BU19ORVdfVVNFUiklMjIlMkMlMjJsaW5lTnVtYmVycyUyMiUzQSU3QiUyMmElMjIlM0EyJTdEJTdEJTJDJTdCJTIydHlwZSUyMiUzQSUyMmFkZCUyMiUyQyUyMm1hcmslMjIlM0ElMjIlMkIlMjIlMkMlMjJkYXRhJTIyJTNBJTIyJTIwJTIwJTIwJTIwUE9TVF9CUkVBQ0hfQ09NTVVOSUNBVEVfQVNfTkVXX1VTRVIpJTIyJTJDJTIybGluZU51bWJlcnMlMjIlM0ElN0IlMjJiJTIyJTNBMiU3RCU3RCUyQyU3QiUyMnR5cGUlMjIlM0ElMjJjb250ZXh0JTIyJTJDJTIyZGF0YSUyMiUzQSUyMiUyMGZyb20lMjBtb25rZXlfaXNsYW5kLmNjLnNlcnZpY2VzLmF0dGFjay50ZWNobmlxdWVfcmVwb3J0cy5wYmFfdGVjaG5pcXVlJTIwaW1wb3J0JTIwJTVDJTVDJTIyJTJDJTIybGluZU51bWJlcnMlMjIlM0ElN0IlMjJhJTIyJTNBMyUyQyUyMmIlMjIlM0EzJTdEJTdEJTJDJTdCJTIydHlwZSUyMiUzQSUyMmNvbnRleHQlMjIlMkMlMjJkYXRhJTIyJTNBJTIyJTIwJTIwJTIwJTIwJTIwUG9zdEJyZWFjaFRlY2huaXF1ZSUyMiUyQyUyMmxpbmVOdW1iZXJzJTIyJTNBJTdCJTIyYSUyMiUzQTQlMkMlMjJiJTIyJTNBNCU3RCU3RCUyQyU3QiUyMnR5cGUlMjIlM0ElMjJjb250ZXh0JTIyJTJDJTIyZGF0YSUyMiUzQSUyMiUyMCUyMiUyQyUyMmxpbmVOdW1iZXJzJTIyJTNBJTdCJTIyYSUyMiUzQTUlMkMlMjJiJTIyJTNBNSU3RCU3RCU1RCUyQyUyMmxpbmVOdW1iZXJzJTIyJTNBJTdCJTIyYSUyMiUzQSU3QiUyMnN0YXJ0TGluZSUyMiUzQTElMkMlMjJsaW5lc0NvdW50JTIyJTNBNSU3RCUyQyUyMmIlMjIlM0ElN0IlMjJzdGFydExpbmUlMjIlM0ExJTJDJTIybGluZXNDb3VudCUyMiUzQTUlN0QlN0QlN0QlN0Q=", - "JTdCJTIyaHVuayUyMiUzQSU3QiUyMmhlYWRlciUyMiUzQSUyMiU0MCU0MCUyMC0xMSUyQzQlMjAlMkIxMSUyQzQlMjAlNDAlNDAlMjBjbGFzcyUyMFQxMTM2KFBvc3RCcmVhY2hUZWNobmlxdWUpJTNBJTIyJTJDJTIyY2hhbmdlcyUyMiUzQSU1QiU3QiUyMnR5cGUlMjIlM0ElMjJjb250ZXh0JTIyJTJDJTIyZGF0YSUyMiUzQSUyMiUyMCUyMCUyMCUyMCUyMHVuc2Nhbm5lZF9tc2clMjAlM0QlMjAlNUMlMjJNb25rZXklMjBkaWRuJ3QlMjB0cnklMjBjcmVhdGluZyUyMGElMjBuZXclMjB1c2VyJTIwb24lMjB0aGUlMjBuZXR3b3JrJ3MlMjBzeXN0ZW1zLiU1QyUyMiUyMiUyQyUyMmxpbmVOdW1iZXJzJTIyJTNBJTdCJTIyYSUyMiUzQTExJTJDJTIyYiUyMiUzQTExJTdEJTdEJTJDJTdCJTIydHlwZSUyMiUzQSUyMmNvbnRleHQlMjIlMkMlMjJkYXRhJTIyJTNBJTIyJTIwJTIwJTIwJTIwJTIwc2Nhbm5lZF9tc2clMjAlM0QlMjAlNUMlMjJNb25rZXklMjB0cmllZCUyMGNyZWF0aW5nJTIwYSUyMG5ldyUyMHVzZXIlMjBvbiUyMHRoZSUyMG5ldHdvcmsncyUyMHN5c3RlbXMlMkMlMjBidXQlMjBmYWlsZWQuJTVDJTIyJTIyJTJDJTIybGluZU51bWJlcnMlMjIlM0ElN0IlMjJhJTIyJTNBMTIlMkMlMjJiJTIyJTNBMTIlN0QlN0QlMkMlN0IlMjJ0eXBlJTIyJTNBJTIyY29udGV4dCUyMiUyQyUyMmRhdGElMjIlM0ElMjIlMjAlMjAlMjAlMjAlMjB1c2VkX21zZyUyMCUzRCUyMCU1QyUyMk1vbmtleSUyMGNyZWF0ZWQlMjBhJTIwbmV3JTIwdXNlciUyMG9uJTIwdGhlJTIwbmV0d29yaydzJTIwc3lzdGVtcy4lNUMlMjIlMjIlMkMlMjJsaW5lTnVtYmVycyUyMiUzQSU3QiUyMmElMjIlM0ExMyUyQyUyMmIlMjIlM0ExMyU3RCU3RCUyQyU3QiUyMnR5cGUlMjIlM0ElMjJkZWwlMjIlMkMlMjJtYXJrJTIyJTNBJTIyLSUyMiUyQyUyMmRhdGElMjIlM0ElMjIlMjAlMjAlMjAlMjBwYmFfbmFtZXMlMjAlM0QlMjAlNUJQT1NUX0JSRUFDSF9CQUNLRE9PUl9VU0VSJTJDJTIwUE9TVF9CUkVBQ0hfQ09NTVVOSUNBVEVfQVNfTkVXX1VTRVIlNUQlMjIlMkMlMjJsaW5lTnVtYmVycyUyMiUzQSU3QiUyMmElMjIlM0ExNCU3RCU3RCUyQyU3QiUyMnR5cGUlMjIlM0ElMjJhZGQlMjIlMkMlMjJtYXJrJTIyJTNBJTIyJTJCJTIyJTJDJTIyZGF0YSUyMiUzQSUyMiUyMCUyMCUyMCUyMHBiYV9uYW1lcyUyMCUzRCUyMCU1QlBPU1RfQlJFQUNIX0NPTU1VTklDQVRFX0FTX05FV19VU0VSJTVEJTIyJTJDJTIybGluZU51bWJlcnMlMjIlM0ElN0IlMjJiJTIyJTNBMTQlN0QlN0QlNUQlMkMlMjJsaW5lTnVtYmVycyUyMiUzQSU3QiUyMmElMjIlM0ElN0IlMjJzdGFydExpbmUlMjIlM0ExMSUyQyUyMmxpbmVzQ291bnQlMjIlM0E0JTdEJTJDJTIyYiUyMiUzQSU3QiUyMnN0YXJ0TGluZSUyMiUzQTExJTJDJTIybGluZXNDb3VudCUyMiUzQTQlN0QlN0QlN0QlN0Q=" + "diffType": "MODIFIED", + "fileDiffHeader": "diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1136.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1136.py\nindex d9d86e08..0abe6a02 100644\n--- a/monkey/monkey_island/cc/services/attack/technique_reports/T1136.py\n+++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1136.py", + "hunks": [ + { + "swimmHunkMetadata": {}, + "hunkDiffLines": [ + "@@ -1,4 +1,4 @@", + "-from common.common_consts.post_breach_consts import POST_BREACH_BACKDOOR_USER, POST_BREACH_COMMUNICATE_AS_NEW_USER", + "+from common.common_consts.post_breach_consts import POST_BREACH_COMMUNICATE_AS_NEW_USER", + " from monkey_island.cc.services.attack.technique_reports.pba_technique import PostBreachTechnique", + " ", + " __author__ = \"shreyamalviya\"" + ] + }, + { + "swimmHunkMetadata": {}, + "hunkDiffLines": [ + "@@ -9,4 +9,4 @@", + " unscanned_msg = \"Monkey didn't try creating a new user on the network's systems.\"", + " scanned_msg = \"Monkey tried creating a new user on the network's systems, but failed.\"", + " used_msg = \"Monkey created a new user on the network's systems.\"", + "- pba_names = [POST_BREACH_BACKDOOR_USER, POST_BREACH_COMMUNICATE_AS_NEW_USER]", + "+ pba_names = [POST_BREACH_COMMUNICATE_AS_NEW_USER]" + ] + } ] }, "monkey/monkey_island/cc/services/config_schema/definitions/post_breach_actions.py": { - "index": [ - "f3e2a9bf..2c4aa664", - "100644" - ], - "fileA": "monkey/monkey_island/cc/services/config_schema/definitions/post_breach_actions.py", - "fileB": "monkey/monkey_island/cc/services/config_schema/definitions/post_breach_actions.py", - "status": "MODIFIED", - "numLineDeletions": 9, - "numLineAdditions": 1, - "hunkContainers": [ - "JTdCJTIyaHVuayUyMiUzQSU3QiUyMmhlYWRlciUyMiUzQSUyMiU0MCU0MCUyMC00JTJDMTUlMjAlMkI0JTJDNyUyMCU0MCU0MCUyMFBPU1RfQlJFQUNIX0FDVElPTlMlMjAlM0QlMjAlN0IlMjIlMkMlMjJjaGFuZ2VzJTIyJTNBJTVCJTdCJTIydHlwZSUyMiUzQSUyMmNvbnRleHQlMjIlMkMlMjJkYXRhJTIyJTNBJTIyJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTVDJTIybWlnaHQlMjBkbyUyMGFmdGVyJTIwYnJlYWNoaW5nJTIwYSUyMG5ldyUyMG1hY2hpbmUuJTIwVXNlZCUyMGluJTIwQVRUJTI2Q0slMjBhbmQlMjBaZXJvJTIwdHJ1c3QlMjByZXBvcnRzLiU1QyUyMiUyQyUyMiUyQyUyMmxpbmVOdW1iZXJzJTIyJTNBJTdCJTIyYSUyMiUzQTQlMkMlMjJiJTIyJTNBNCU3RCU3RCUyQyU3QiUyMnR5cGUlMjIlM0ElMjJjb250ZXh0JTIyJTJDJTIyZGF0YSUyMiUzQSUyMiUyMCUyMCUyMCUyMCUyMCU1QyUyMnR5cGUlNUMlMjIlM0ElMjAlNUMlMjJzdHJpbmclNUMlMjIlMkMlMjIlMkMlMjJsaW5lTnVtYmVycyUyMiUzQSU3QiUyMmElMjIlM0E1JTJDJTIyYiUyMiUzQTUlN0QlN0QlMkMlN0IlMjJ0eXBlJTIyJTNBJTIyY29udGV4dCUyMiUyQyUyMmRhdGElMjIlM0ElMjIlMjAlMjAlMjAlMjAlMjAlNUMlMjJhbnlPZiU1QyUyMiUzQSUyMCU1QiUyMiUyQyUyMmxpbmVOdW1iZXJzJTIyJTNBJTdCJTIyYSUyMiUzQTYlMkMlMjJiJTIyJTNBNiU3RCU3RCUyQyU3QiUyMnR5cGUlMjIlM0ElMjJkZWwlMjIlMkMlMjJtYXJrJTIyJTNBJTIyLSUyMiUyQyUyMmRhdGElMjIlM0ElMjIlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlN0IlMjIlMkMlMjJsaW5lTnVtYmVycyUyMiUzQSU3QiUyMmElMjIlM0E3JTdEJTdEJTJDJTdCJTIydHlwZSUyMiUzQSUyMmRlbCUyMiUyQyUyMm1hcmslMjIlM0ElMjItJTIyJTJDJTIyZGF0YSUyMiUzQSUyMiUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCU1QyUyMnR5cGUlNUMlMjIlM0ElMjAlNUMlMjJzdHJpbmclNUMlMjIlMkMlMjIlMkMlMjJsaW5lTnVtYmVycyUyMiUzQSU3QiUyMmElMjIlM0E4JTdEJTdEJTJDJTdCJTIydHlwZSUyMiUzQSUyMmRlbCUyMiUyQyUyMm1hcmslMjIlM0ElMjItJTIyJTJDJTIyZGF0YSUyMiUzQSUyMiUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCU1QyUyMmVudW0lNUMlMjIlM0ElMjAlNUIlMjIlMkMlMjJsaW5lTnVtYmVycyUyMiUzQSU3QiUyMmElMjIlM0E5JTdEJTdEJTJDJTdCJTIydHlwZSUyMiUzQSUyMmRlbCUyMiUyQyUyMm1hcmslMjIlM0ElMjItJTIyJTJDJTIyZGF0YSUyMiUzQSUyMiUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCU1QyUyMkJhY2tkb29yVXNlciU1QyUyMiUyMiUyQyUyMmxpbmVOdW1iZXJzJTIyJTNBJTdCJTIyYSUyMiUzQTEwJTdEJTdEJTJDJTdCJTIydHlwZSUyMiUzQSUyMmRlbCUyMiUyQyUyMm1hcmslMjIlM0ElMjItJTIyJTJDJTIyZGF0YSUyMiUzQSUyMiUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCU1RCUyQyUyMiUyQyUyMmxpbmVOdW1iZXJzJTIyJTNBJTdCJTIyYSUyMiUzQTExJTdEJTdEJTJDJTdCJTIydHlwZSUyMiUzQSUyMmRlbCUyMiUyQyUyMm1hcmslMjIlM0ElMjItJTIyJTJDJTIyZGF0YSUyMiUzQSUyMiUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCU1QyUyMnRpdGxlJTVDJTIyJTNBJTIwJTVDJTIyQmFjayUyMGRvb3IlMjB1c2VyJTVDJTIyJTJDJTIyJTJDJTIybGluZU51bWJlcnMlMjIlM0ElN0IlMjJhJTIyJTNBMTIlN0QlN0QlMkMlN0IlMjJ0eXBlJTIyJTNBJTIyZGVsJTIyJTJDJTIybWFyayUyMiUzQSUyMi0lMjIlMkMlMjJkYXRhJTIyJTNBJTIyJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTVDJTIyaW5mbyU1QyUyMiUzQSUyMCU1QyUyMkF0dGVtcHRzJTIwdG8lMjBjcmVhdGUlMjBhJTIwbmV3JTIwdXNlciUyMG9uJTIwdGhlJTIwc3lzdGVtJTIwYW5kJTIwZGVsZXRlJTIwaXQlMjBhZnRlcndhcmRzLiU1QyUyMiUyQyUyMiUyQyUyMmxpbmVOdW1iZXJzJTIyJTNBJTdCJTIyYSUyMiUzQTEzJTdEJTdEJTJDJTdCJTIydHlwZSUyMiUzQSUyMmRlbCUyMiUyQyUyMm1hcmslMjIlM0ElMjItJTIyJTJDJTIyZGF0YSUyMiUzQSUyMiUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCU1QyUyMmF0dGFja190ZWNobmlxdWVzJTVDJTIyJTNBJTIwJTVCJTVDJTIyVDExMzYlNUMlMjIlNUQlMjIlMkMlMjJsaW5lTnVtYmVycyUyMiUzQSU3QiUyMmElMjIlM0ExNCU3RCU3RCUyQyU3QiUyMnR5cGUlMjIlM0ElMjJkZWwlMjIlMkMlMjJtYXJrJTIyJTNBJTIyLSUyMiUyQyUyMmRhdGElMjIlM0ElMjIlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlN0QlMkMlMjIlMkMlMjJsaW5lTnVtYmVycyUyMiUzQSU3QiUyMmElMjIlM0ExNSU3RCU3RCUyQyU3QiUyMnR5cGUlMjIlM0ElMjJhZGQlMjIlMkMlMjJtYXJrJTIyJTNBJTIyJTJCJTIyJTJDJTIyZGF0YSUyMiUzQSUyMiUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMyUyMFN3aW1tZXIlM0ElMjBBZGQlMjBuZXclMjBQQkElMjBoZXJlJTIwdG8lMjBjb25maWchJTIyJTJDJTIybGluZU51bWJlcnMlMjIlM0ElN0IlMjJiJTIyJTNBNyU3RCU3RCUyQyU3QiUyMnR5cGUlMjIlM0ElMjJjb250ZXh0JTIyJTJDJTIyZGF0YSUyMiUzQSUyMiUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCU3QiUyMiUyQyUyMmxpbmVOdW1iZXJzJTIyJTNBJTdCJTIyYSUyMiUzQTE2JTJDJTIyYiUyMiUzQTglN0QlN0QlMkMlN0IlMjJ0eXBlJTIyJTNBJTIyY29udGV4dCUyMiUyQyUyMmRhdGElMjIlM0ElMjIlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlNUMlMjJ0eXBlJTVDJTIyJTNBJTIwJTVDJTIyc3RyaW5nJTVDJTIyJTJDJTIyJTJDJTIybGluZU51bWJlcnMlMjIlM0ElN0IlMjJhJTIyJTNBMTclMkMlMjJiJTIyJTNBOSU3RCU3RCUyQyU3QiUyMnR5cGUlMjIlM0ElMjJjb250ZXh0JTIyJTJDJTIyZGF0YSUyMiUzQSUyMiUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCU1QyUyMmVudW0lNUMlMjIlM0ElMjAlNUIlMjIlMkMlMjJsaW5lTnVtYmVycyUyMiUzQSU3QiUyMmElMjIlM0ExOCUyQyUyMmIlMjIlM0ExMCU3RCU3RCU1RCUyQyUyMmxpbmVOdW1iZXJzJTIyJTNBJTdCJTIyYSUyMiUzQSU3QiUyMnN0YXJ0TGluZSUyMiUzQTQlMkMlMjJsaW5lc0NvdW50JTIyJTNBMTUlN0QlMkMlMjJiJTIyJTNBJTdCJTIyc3RhcnRMaW5lJTIyJTNBNCUyQyUyMmxpbmVzQ291bnQlMjIlM0E3JTdEJTdEJTdEJTdE" + "diffType": "MODIFIED", + "fileDiffHeader": "diff --git a/monkey/monkey_island/cc/services/config_schema/definitions/post_breach_actions.py b/monkey/monkey_island/cc/services/config_schema/definitions/post_breach_actions.py\nindex f1fe0f6f..39ebd33a 100644\n--- a/monkey/monkey_island/cc/services/config_schema/definitions/post_breach_actions.py\n+++ b/monkey/monkey_island/cc/services/config_schema/definitions/post_breach_actions.py", + "hunks": [ + { + "swimmHunkMetadata": {}, + "hunkDiffLines": [ + "@@ -4,15 +4,7 @@", + " \"might do after breaching a new machine. Used in ATT&CK and Zero trust reports.\",", + " \"type\": \"string\",", + " \"anyOf\": [", + "- {", + "+ # Swimmer: Add new PBA here to config!", + "- \"type\": \"string\",", + "- \"enum\": [", + "- \"BackdoorUser\"", + "- ],", + "- \"title\": \"Back door user\",", + "- \"info\": \"Attempts to create a new user on the system and delete it afterwards.\",", + "- \"attack_techniques\": [\"T1136\"]", + "- },", + " {", + " \"type\": \"string\",", + " \"enum\": [" + ] + } ] } }, - "diff": "ZGlmZiUyMC0tZ2l0JTIwYSUyRm1vbmtleSUyRmNvbW1vbiUyRmRhdGElMkZwb3N0X2JyZWFjaF9jb25zdHMucHklMjBiJTJGbW9ua2V5JTJGY29tbW9uJTJGZGF0YSUyRnBvc3RfYnJlYWNoX2NvbnN0cy5weSUwQWluZGV4JTIwYzNiYmE5OTUuLjAzMWY5YWQwJTIwMTAwNjQ0JTBBLS0tJTIwYSUyRm1vbmtleSUyRmNvbW1vbiUyRmRhdGElMkZwb3N0X2JyZWFjaF9jb25zdHMucHklMEElMkIlMkIlMkIlMjBiJTJGbW9ua2V5JTJGY29tbW9uJTJGZGF0YSUyRnBvc3RfYnJlYWNoX2NvbnN0cy5weSUwQSU0MCU0MCUyMC0xJTJDNSUyMCUyQjElMkM1JTIwJTQwJTQwJTBBJTIwUE9TVF9CUkVBQ0hfQ09NTVVOSUNBVEVfQVNfTkVXX1VTRVIlMjAlM0QlMjAlMjJDb21tdW5pY2F0ZSUyMGFzJTIwbmV3JTIwdXNlciUyMiUwQS1QT1NUX0JSRUFDSF9CQUNLRE9PUl9VU0VSJTIwJTNEJTIwJTIyQmFja2Rvb3IlMjB1c2VyJTIyJTBBJTJCJTIzJTIwU3dpbW1lciUzQSUyMFBVVCUyMFRIRSUyME5FVyUyMENPTlNUJTIwSEVSRSElMEElMjBQT1NUX0JSRUFDSF9GSUxFX0VYRUNVVElPTiUyMCUzRCUyMCUyMkZpbGUlMjBleGVjdXRpb24lMjIlMEElMjBQT1NUX0JSRUFDSF9TSEVMTF9TVEFSVFVQX0ZJTEVfTU9ESUZJQ0FUSU9OJTIwJTNEJTIwJTIyTW9kaWZ5JTIwc2hlbGwlMjBzdGFydHVwJTIwZmlsZSUyMiUwQSUyMFBPU1RfQlJFQUNIX0hJRERFTl9GSUxFUyUyMCUzRCUyMCUyMkhpZGUlMjBmaWxlcyUyMGFuZCUyMGRpcmVjdG9yaWVzJTIyJTBBZGlmZiUyMC0tZ2l0JTIwYSUyRm1vbmtleSUyRmluZmVjdGlvbl9tb25rZXklMkZwb3N0X2JyZWFjaCUyRmFjdGlvbnMlMkZhZGRfdXNlci5weSUyMGIlMkZtb25rZXklMkZpbmZlY3Rpb25fbW9ua2V5JTJGcG9zdF9icmVhY2glMkZhY3Rpb25zJTJGYWRkX3VzZXIucHklMEFpbmRleCUyMDU4YmU4OWExLi5kODQ3NmE5NyUyMDEwMDY0NCUwQS0tLSUyMGElMkZtb25rZXklMkZpbmZlY3Rpb25fbW9ua2V5JTJGcG9zdF9icmVhY2glMkZhY3Rpb25zJTJGYWRkX3VzZXIucHklMEElMkIlMkIlMkIlMjBiJTJGbW9ua2V5JTJGaW5mZWN0aW9uX21vbmtleSUyRnBvc3RfYnJlYWNoJTJGYWN0aW9ucyUyRmFkZF91c2VyLnB5JTBBJTQwJTQwJTIwLTElMkMxNSUyMCUyQjElMkM3JTIwJTQwJTQwJTBBLWZyb20lMjBjb21tb24uZGF0YS5wb3N0X2JyZWFjaF9jb25zdHMlMjBpbXBvcnQlMjBQT1NUX0JSRUFDSF9CQUNLRE9PUl9VU0VSJTBBLWZyb20lMjBpbmZlY3Rpb25fbW9ua2V5LmNvbmZpZyUyMGltcG9ydCUyMFdvcm1Db25maWd1cmF0aW9uJTBBJTIwZnJvbSUyMGluZmVjdGlvbl9tb25rZXkucG9zdF9icmVhY2gucGJhJTIwaW1wb3J0JTIwUEJBJTBBJTIwZnJvbSUyMGluZmVjdGlvbl9tb25rZXkudXRpbHMudXNlcnMlMjBpbXBvcnQlMjBnZXRfY29tbWFuZHNfdG9fYWRkX3VzZXIlMEElMjAlMEElMjAlMEElMjBjbGFzcyUyMEJhY2tkb29yVXNlcihQQkEpJTNBJTBBJTIwJTIwJTIwJTIwJTIwZGVmJTIwX19pbml0X18oc2VsZiklM0ElMEEtJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwbGludXhfY21kcyUyQyUyMHdpbmRvd3NfY21kcyUyMCUzRCUyMGdldF9jb21tYW5kc190b19hZGRfdXNlciglMEEtJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwV29ybUNvbmZpZ3VyYXRpb24udXNlcl90b19hZGQlMkMlMEEtJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwV29ybUNvbmZpZ3VyYXRpb24ucmVtb3RlX3VzZXJfcGFzcyklMEEtJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwc3VwZXIoQmFja2Rvb3JVc2VyJTJDJTIwc2VsZikuX19pbml0X18oJTBBLSUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMFBPU1RfQlJFQUNIX0JBQ0tET09SX1VTRVIlMkMlMEEtJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwbGludXhfY21kJTNEJyUyMCcuam9pbihsaW51eF9jbWRzKSUyQyUwQS0lMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjB3aW5kb3dzX2NtZCUzRHdpbmRvd3NfY21kcyklMEElMkIlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjBwYXNzJTIwJTIwJTIzJTIwU3dpbW1lciUzQSUyMEltcGwlMjBoZXJlISUwQWRpZmYlMjAtLWdpdCUyMGElMkZtb25rZXklMkZtb25rZXlfaXNsYW5kJTJGY2MlMkZzZXJ2aWNlcyUyRmF0dGFjayUyRnRlY2huaXF1ZV9yZXBvcnRzJTJGVDExMzYucHklMjBiJTJGbW9ua2V5JTJGbW9ua2V5X2lzbGFuZCUyRmNjJTJGc2VydmljZXMlMkZhdHRhY2slMkZ0ZWNobmlxdWVfcmVwb3J0cyUyRlQxMTM2LnB5JTBBaW5kZXglMjAwODZhMWMxMy4uZGE5OWU4NmMlMjAxMDA2NDQlMEEtLS0lMjBhJTJGbW9ua2V5JTJGbW9ua2V5X2lzbGFuZCUyRmNjJTJGc2VydmljZXMlMkZhdHRhY2slMkZ0ZWNobmlxdWVfcmVwb3J0cyUyRlQxMTM2LnB5JTBBJTJCJTJCJTJCJTIwYiUyRm1vbmtleSUyRm1vbmtleV9pc2xhbmQlMkZjYyUyRnNlcnZpY2VzJTJGYXR0YWNrJTJGdGVjaG5pcXVlX3JlcG9ydHMlMkZUMTEzNi5weSUwQSU0MCU0MCUyMC0xJTJDNSUyMCUyQjElMkM1JTIwJTQwJTQwJTBBJTIwZnJvbSUyMGNvbW1vbi5kYXRhLnBvc3RfYnJlYWNoX2NvbnN0cyUyMGltcG9ydCUyMCglMEEtJTIwJTIwJTIwJTIwUE9TVF9CUkVBQ0hfQkFDS0RPT1JfVVNFUiUyQyUyMFBPU1RfQlJFQUNIX0NPTU1VTklDQVRFX0FTX05FV19VU0VSKSUwQSUyQiUyMCUyMCUyMCUyMFBPU1RfQlJFQUNIX0NPTU1VTklDQVRFX0FTX05FV19VU0VSKSUwQSUyMGZyb20lMjBtb25rZXlfaXNsYW5kLmNjLnNlcnZpY2VzLmF0dGFjay50ZWNobmlxdWVfcmVwb3J0cy5wYmFfdGVjaG5pcXVlJTIwaW1wb3J0JTIwJTVDJTBBJTIwJTIwJTIwJTIwJTIwUG9zdEJyZWFjaFRlY2huaXF1ZSUwQSUyMCUwQSU0MCU0MCUyMC0xMSUyQzQlMjAlMkIxMSUyQzQlMjAlNDAlNDAlMjBjbGFzcyUyMFQxMTM2KFBvc3RCcmVhY2hUZWNobmlxdWUpJTNBJTBBJTIwJTIwJTIwJTIwJTIwdW5zY2FubmVkX21zZyUyMCUzRCUyMCUyMk1vbmtleSUyMGRpZG4ndCUyMHRyeSUyMGNyZWF0aW5nJTIwYSUyMG5ldyUyMHVzZXIlMjBvbiUyMHRoZSUyMG5ldHdvcmsncyUyMHN5c3RlbXMuJTIyJTBBJTIwJTIwJTIwJTIwJTIwc2Nhbm5lZF9tc2clMjAlM0QlMjAlMjJNb25rZXklMjB0cmllZCUyMGNyZWF0aW5nJTIwYSUyMG5ldyUyMHVzZXIlMjBvbiUyMHRoZSUyMG5ldHdvcmsncyUyMHN5c3RlbXMlMkMlMjBidXQlMjBmYWlsZWQuJTIyJTBBJTIwJTIwJTIwJTIwJTIwdXNlZF9tc2clMjAlM0QlMjAlMjJNb25rZXklMjBjcmVhdGVkJTIwYSUyMG5ldyUyMHVzZXIlMjBvbiUyMHRoZSUyMG5ldHdvcmsncyUyMHN5c3RlbXMuJTIyJTBBLSUyMCUyMCUyMCUyMHBiYV9uYW1lcyUyMCUzRCUyMCU1QlBPU1RfQlJFQUNIX0JBQ0tET09SX1VTRVIlMkMlMjBQT1NUX0JSRUFDSF9DT01NVU5JQ0FURV9BU19ORVdfVVNFUiU1RCUwQSUyQiUyMCUyMCUyMCUyMHBiYV9uYW1lcyUyMCUzRCUyMCU1QlBPU1RfQlJFQUNIX0NPTU1VTklDQVRFX0FTX05FV19VU0VSJTVEJTBBZGlmZiUyMC0tZ2l0JTIwYSUyRm1vbmtleSUyRm1vbmtleV9pc2xhbmQlMkZjYyUyRnNlcnZpY2VzJTJGY29uZmlnX3NjaGVtYSUyRmRlZmluaXRpb25zJTJGcG9zdF9icmVhY2hfYWN0aW9ucy5weSUyMGIlMkZtb25rZXklMkZtb25rZXlfaXNsYW5kJTJGY2MlMkZzZXJ2aWNlcyUyRmNvbmZpZ19zY2hlbWElMkZkZWZpbml0aW9ucyUyRnBvc3RfYnJlYWNoX2FjdGlvbnMucHklMEFpbmRleCUyMGYzZTJhOWJmLi4yYzRhYTY2NCUyMDEwMDY0NCUwQS0tLSUyMGElMkZtb25rZXklMkZtb25rZXlfaXNsYW5kJTJGY2MlMkZzZXJ2aWNlcyUyRmNvbmZpZ19zY2hlbWElMkZkZWZpbml0aW9ucyUyRnBvc3RfYnJlYWNoX2FjdGlvbnMucHklMEElMkIlMkIlMkIlMjBiJTJGbW9ua2V5JTJGbW9ua2V5X2lzbGFuZCUyRmNjJTJGc2VydmljZXMlMkZjb25maWdfc2NoZW1hJTJGZGVmaW5pdGlvbnMlMkZwb3N0X2JyZWFjaF9hY3Rpb25zLnB5JTBBJTQwJTQwJTIwLTQlMkMxNSUyMCUyQjQlMkM3JTIwJTQwJTQwJTIwUE9TVF9CUkVBQ0hfQUNUSU9OUyUyMCUzRCUyMCU3QiUwQSUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMm1pZ2h0JTIwZG8lMjBhZnRlciUyMGJyZWFjaGluZyUyMGElMjBuZXclMjBtYWNoaW5lLiUyMFVzZWQlMjBpbiUyMEFUVCUyNkNLJTIwYW5kJTIwWmVybyUyMHRydXN0JTIwcmVwb3J0cy4lMjIlMkMlMEElMjAlMjAlMjAlMjAlMjAlMjJ0eXBlJTIyJTNBJTIwJTIyc3RyaW5nJTIyJTJDJTBBJTIwJTIwJTIwJTIwJTIwJTIyYW55T2YlMjIlM0ElMjAlNUIlMEEtJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTdCJTBBLSUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMnR5cGUlMjIlM0ElMjAlMjJzdHJpbmclMjIlMkMlMEEtJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIyZW51bSUyMiUzQSUyMCU1QiUwQS0lMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjJCYWNrZG9vclVzZXIlMjIlMEEtJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTVEJTJDJTBBLSUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMnRpdGxlJTIyJTNBJTIwJTIyQmFjayUyMGRvb3IlMjB1c2VyJTIyJTJDJTBBLSUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMmluZm8lMjIlM0ElMjAlMjJBdHRlbXB0cyUyMHRvJTIwY3JlYXRlJTIwYSUyMG5ldyUyMHVzZXIlMjBvbiUyMHRoZSUyMHN5c3RlbSUyMGFuZCUyMGRlbGV0ZSUyMGl0JTIwYWZ0ZXJ3YXJkcy4lMjIlMkMlMEEtJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIyYXR0YWNrX3RlY2huaXF1ZXMlMjIlM0ElMjAlNUIlMjJUMTEzNiUyMiU1RCUwQS0lMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlN0QlMkMlMEElMkIlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjMlMjBTd2ltbWVyJTNBJTIwQWRkJTIwbmV3JTIwUEJBJTIwaGVyZSUyMHRvJTIwY29uZmlnISUwQSUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCU3QiUwQSUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMnR5cGUlMjIlM0ElMjAlMjJzdHJpbmclMjIlMkMlMEElMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjJlbnVtJTIyJTNBJTIwJTVCJTBB", - "description": "UmVhZCUyMCU1Qm91ciUyMGRvY3VtZW50YXRpb24lMjBhYm91dCUyMGFkZGluZyUyMGElMjBuZXclMjBQQkElNUQoaHR0cHMlM0ElMkYlMkZ3d3cuZ3VhcmRpY29yZS5jb20lMkZpbmZlY3Rpb25tb25rZXklMkZkb2NzJTJGZGV2ZWxvcG1lbnQlMkZhZGRpbmctcG9zdC1icmVhY2gtYWN0aW9ucyUyRikuJTBBJTBBQWZ0ZXIlMjB0aGF0JTIwd2UlMjB3YW50JTIweW91JTIwdG8lMjBhZGQlMjB0aGUlMjBCYWNrZG9vclVzZXIlMjBQQkEuJTIwVGhlJTIwY29tbWFuZHMlMjB0aGF0JTIwYWRkJTIwdXNlcnMlMjBmb3IlMjBXaW4lMjBhbmQlMjBMaW51eCUyMGNhbiUyMGJlJTIwcmV0cmlldmVkJTIwZnJvbSUyMCU2MGdldF9jb21tYW5kc190b19hZGRfdXNlciU2MCUyMC0lMjBtYWtlJTIwc3VyZSUyMHlvdSUyMHNlZSUyMGhvdyUyMHRvJTIwdXNlJTIwdGhpcyUyMGZ1bmN0aW9uJTIwY29ycmVjdGx5LiUyMCUwQSUwQU5vdGUlMjB0aGF0JTIwdGhlJTIwUEJBJTIwc2hvdWxkJTIwaW1wYWN0JTIwdGhlJTIwVDExMzYlMjBNSVRSRSUyMHRlY2huaXF1ZSUyMGFzJTIwd2VsbCElMjAlMEElMEElMjMlMjBNYW51YWwlMjB0ZXN0JTIwdG8lMjBjb25maXJtJTBBJTBBMS4lMjBSdW4lMjB0aGUlMjBNb25rZXklMjBJc2xhbmQlMEEyLiUyME1ha2UlMjBzdXJlJTIweW91ciUyMG5ldyUyMFBCQSUyMGlzJTIwZW5hYmxlZCUyMGJ5JTIwZGVmYXVsdCUyMGluJTIwdGhlJTIwY29uZmlnJTIwLSUyMGZvciUyMHRoaXMlMjB0ZXN0JTJDJTIwZGlzYWJsZSUyMG5ldHdvcmslMjBzY2FubmluZyUyQyUyMGV4cGxvaXRpbmclMkMlMjBhbmQlMjBhbGwlMjBvdGhlciUyMFBCQXMlMEEzLiUyMFJ1biUyME1vbmtleSUwQTQuJTIwU2VlJTIwdGhlJTIwUEJBJTIwaW4lMjB0aGUlMjBzZWN1cml0eSUyMHJlcG9ydCUwQTUlMkMlMjBTZWUlMjB0aGUlMjBQQkElMjBpbiUyMHRoZSUyME1JVFJFJTIwcmVwb3J0JTIwaW4lMjB0aGUlMjByZWxldmFudCUyMHRlY2huaXF1ZSUwQQ==", - "dod": "WW91JTIwc2hvdWxkJTIwYWRkJTIwYSUyMG5ldyUyMFBCQSUyMHRvJTIwdGhlJTIwTW9ua2V5JTIwd2hpY2glMjBjcmVhdGVzJTIwYSUyMG5ldyUyMHVzZXIlMjBvbiUyMHRoZSUyMG1hY2hpbmUu", - "summary": "VGFrZSUyMGElMjBsb29rJTIwYXQlMjB0aGUlMjBjb25maWd1cmF0aW9uJTIwb2YlMjB0aGUlMjBpc2xhbmQlMjBhZ2FpbiUyMC0lMjBzZWUlMjB0aGUlMjAlMjJjb21tYW5kJTIwdG8lMjBydW4lMjBhZnRlciUyMGJyZWFjaCUyMiUyMG9wdGlvbiUyMHdlJTIwb2ZmZXIlMjB0aGUlMjB1c2VyJTNGJTIwSXQncyUyMGltcGxlbWVudGVkJTIwZXhhY3RseSUyMGxpa2UlMjB5b3UlMjBkaWQlMjByaWdodCUyMG5vdyUyMGJ1dCUyMGVhY2glMjB1c2VyJTIwY2FuJTIwZG8lMjBpdCUyMGZvciUyMHRoZW1zZWx2ZXMuJTIwJTBBJTBBSG93ZXZlciUyQyUyMHdoYXQlMjBpZiUyMHRoZSUyMFBCQSUyMG5lZWRzJTIwdG8lMjBkbyUyMHN0dWZmJTIwd2hpY2glMjBpcyUyMG1vcmUlMjBjb21wbGV4JTIwdGhhbiUyMGp1c3QlMjBydW5uaW5nJTIwYSUyMGZldyUyMGNvbW1hbmRzJTNGJTIwSW4lMjB0aGF0JTIwY2FzZS4uLiUyMA==", - "file_version": "1.0.2", - "app_version": "0.1.60" -} + "app_version": "0.2.1", + "file_version": "1.0.3" +} \ No newline at end of file From 3dc7208b86294a60cd574f17614fb1cbb93105d4 Mon Sep 17 00:00:00 2001 From: omerr Date: Wed, 7 Oct 2020 23:34:33 +0300 Subject: [PATCH 075/152] Swimm: updated unit (AzD8XysWg1BBXCjCDkfq) - upgrading to scheme 1.0.3 --- .swm/AzD8XysWg1BBXCjCDkfq.swm | 105 +++++++++++++++++++++------------- 1 file changed, 64 insertions(+), 41 deletions(-) diff --git a/.swm/AzD8XysWg1BBXCjCDkfq.swm b/.swm/AzD8XysWg1BBXCjCDkfq.swm index 9594d72d9..6eeb46b0e 100644 --- a/.swm/AzD8XysWg1BBXCjCDkfq.swm +++ b/.swm/AzD8XysWg1BBXCjCDkfq.swm @@ -1,58 +1,81 @@ { "id": "AzD8XysWg1BBXCjCDkfq", "name": "Add a new configuration setting to the Agent ⚙", - "dod": "TWFrZSUyMHRoZSUyMG1heCUyMHZpY3RpbSUyMG51bWJlciUyMHRoYXQlMjBNb25rZXklMjB3aWxsJTIwZmluZCUyMGJlZm9yZSUyMHN0b3BwaW5nJTIwY29uZmlndXJhYmxlJTIwYnklMjB0aGUlMjB1c2VyJTIwaW5zdGVhZCUyMG9mJTIwY29uc3RhbnQu", - "description": "JTIzJTIwTWFrZSUyMHNvbWV0aGluZyUyMGNvbmZpZ3VyYWJsZSUwQSUwQUluJTIwdGhpcyUyMHVuaXQlMkMlMjB5b3UlMjB3aWxsJTIwbGVhcm4lMjBob3clMjB0byUyMGFkZCUyMGElMjBjb25maWd1cmF0aW9uJTIwb3B0aW9uJTIwdG8lMjBNb25rZXklMjBhbmQlMjBob3clMjB0byUyMHVzZSUyMGl0JTIwaW4lMjB0aGUlMjBNb25rZXklMjBBZ2VudCUyMGNvZGUuJTIwJTBBJTBBISU1QmNvbXB1dGVyJTIwZmlyZSU1RChodHRwcyUzQSUyRiUyRm1lZGlhLmdpcGh5LmNvbSUyRm1lZGlhJTJGN0o0UDdjVXVyMkRsRXJpanAzJTJGZ2lwaHkuZ2lmJTIwJTIyY29tcHV0ZXIlMjBmaXJlJTIyKSUwQSUwQSUyMyUyMyUyMFdoeSUyMGlzJTIwdGhpcyUyMGltcG9ydGFudCUzRiUwQSUwQUVuYWJsaW5nJTIwdXNlcnMlMjB0byUyMGNvbmZpZ3VyZSUyMHRoZSUyME1vbmtleSdzJTIwYmVoYXZpb3VyJTIwZ2l2ZXMlMjB0aGVtJTIwYSUyMGxvdCUyMG1vcmUlMjBmcmVlZG9tJTIwaW4lMjBob3clMjB0aGV5JTIwd2FudCUyMHRvJTIwdXNlJTIwdGhlJTIwTW9ua2V5JTIwYW5kJTIwZW5hYmxlcyUyMG1vcmUlMjB1c2UlMjBjYXNlcy4lMEElMEElMjMlMjMlMjBXaGF0JTIwaXMlMjAlMjJNYXglMjB2aWN0aW1zJTIwdG8lMjBmaW5kJTIyJTNGJTBBJTBBVGhlJTIwTW9ua2V5JTIwaGFzJTIwYSUyMGZ1bmN0aW9uJTIwd2hpY2glMjBmaW5kcyUyMCUyMnZpY3RpbSUyMiUyMG1hY2hpbmVzJTIwb24lMjB0aGUlMjBuZXR3b3JrJTIwZm9yJTIwdGhlJTIwTW9ua2V5JTIwdG8lMjB0cnklMjBhbmQlMjBleHBsb2l0LiUyMEl0J3MlMjBjYWxsZWQlMjAlNjBnZXRfdmljdGltX21hY2hpbmVzJTYwLiUyMFRoaXMlMjBmdW5jdGlvbiUyMGFjY2VwdHMlMjBhbiUyMGFyZ3VtZW50JTIwd2hpY2glMjBsaW1pdHMlMjBob3clMjBtYW55JTIwbWFjaGluZXMlMjB0aGUlMjBNb25rZXklMjBzaG91bGQlMjBmaW5kLiUwQSUwQVdlJTIwd2FudCUyMHRvJTIwbWFrZSUyMHRoYXQlMjB2YWx1ZSUyMGVkaXRhYmxlJTIwYnklMjB0aGUlMjB1c2VyJTIwaW5zdGVhZCUyMG9mJTIwY29uc3RhbnQlMjBpbiUyMHRoZSUyMGNvZGUuJTBBJTBBJTIzJTIzJTIwTWFudWFsJTIwdGVzdGluZyUwQSUwQTEuJTIwQWZ0ZXIlMjB5b3UndmUlMjBwZXJmb3JtZWQlMjB0aGUlMjByZXF1aXJlZCUyMGNoYW5nZXMlMkMlMjByZWxvYWQlMjB0aGUlMjBTZXJ2ZXIlMjBhbmQlMjBjaGVjayUyMHlvdXIlMjB2YWx1ZSUyMGV4aXN0cyUyMGluJTIwdGhlJTIwSW50ZXJuYWwlMjB0YWIlMjBvZiUyMHRoZSUyMGNvbmZpZyUyMChzZWUlMjBpbWFnZSkuJTBBJTBBISU1QiU1RChodHRwcyUzQSUyRiUyRmkuaW1ndXIuY29tJTJGZTBYQXh1Vi5wbmcpJTBBJTBBMi4lMjBTZXQlMjB0aGUlMjBuZXclMjB2YWx1ZSUyMHRvJTIwMSUyQyUyMGFuZCUyMHJ1biUyME1vbmtleSUyMGxvY2FsbHklMjAoZnJvbSUyMHNvdXJjZSkuJTIwU2VlJTIwdGhhdCUyMHRoZSUyME1vbmtleSUyMG9ubHklMjBzY2FucyUyMG9uZSUyMG1hY2hpbmUu", - "summary": "KiUyMFdoZW4lMjBjaGFuZ2luZyUyMGNvbmZpZyUyMHNjaGVtYSUyMGJ5JTIwYWRkaW5nJTIwb3IlMjBkZWxldGluZyUyMGtleXMlMkMlMjB5b3UlMjBuZWVkJTIwdG8lMjB1cGRhdGUlMjB0aGUlMjBCbGFja2JveCUyMFRlc3QlMjBjb25maWd1cmF0aW9ucyUyMGFzJTIwd2VsbCUyMCU1QmhlcmUlNUQoaHR0cHMlM0ElMkYlMkZnaXRodWIuY29tJTJGZ3VhcmRpY29yZSUyRm1vbmtleSUyRnRyZWUlMkZkZXZlbG9wJTJGZW52cyUyRm1vbmtleV96b28lMkZibGFja2JveCUyRmlzbGFuZF9jb25maWdzKS4=", - "diff": "ZGlmZiUyMC0tZ2l0JTIwYSUyRm1vbmtleSUyRmluZmVjdGlvbl9tb25rZXklMkZjb25maWcucHklMjBiJTJGbW9ua2V5JTJGaW5mZWN0aW9uX21vbmtleSUyRmNvbmZpZy5weSUwQWluZGV4JTIwMWZiY2I4NzYuLjY3ZWQxOWRlJTIwMTAwNjQ0JTBBLS0tJTIwYSUyRm1vbmtleSUyRmluZmVjdGlvbl9tb25rZXklMkZjb25maWcucHklMEElMkIlMkIlMkIlMjBiJTJGbW9ua2V5JTJGaW5mZWN0aW9uX21vbmtleSUyRmNvbmZpZy5weSUwQSU0MCU0MCUyMC0xMzElMkM5JTIwJTJCMTMxJTJDNiUyMCU0MCU0MCUyMGNsYXNzJTIwQ29uZmlndXJhdGlvbihvYmplY3QpJTNBJTBBJTIwJTIwJTIwJTIwJTIwZXhwbG9pdGVyX2NsYXNzZXMlMjAlM0QlMjAlNUIlNUQlMEElMjAlMjAlMjAlMjAlMjBzeXN0ZW1faW5mb19jb2xsZWN0b3JfY2xhc3NlcyUyMCUzRCUyMCU1QiU1RCUwQSUyMCUwQS0lMjAlMjAlMjAlMjAlMjMlMjBob3clMjBtYW55JTIwdmljdGltcyUyMHRvJTIwbG9vayUyMGZvciUyMGluJTIwYSUyMHNpbmdsZSUyMHNjYW4lMjBpdGVyYXRpb24lMEEtJTIwJTIwJTIwJTIwdmljdGltc19tYXhfZmluZCUyMCUzRCUyMDEwMCUwQS0lMEElMjAlMjAlMjAlMjAlMjAlMjMlMjBob3clMjBtYW55JTIwdmljdGltcyUyMHRvJTIwZXhwbG9pdCUyMGJlZm9yZSUyMHN0b3BwaW5nJTBBJTIwJTIwJTIwJTIwJTIwdmljdGltc19tYXhfZXhwbG9pdCUyMCUzRCUyMDEwMCUwQSUyMCUwQWRpZmYlMjAtLWdpdCUyMGElMkZtb25rZXklMkZpbmZlY3Rpb25fbW9ua2V5JTJGbW9ua2V5LnB5JTIwYiUyRm1vbmtleSUyRmluZmVjdGlvbl9tb25rZXklMkZtb25rZXkucHklMEFpbmRleCUyMDQ0NGJkZTQ1Li5mZjIzZjY3MSUyMDEwMDY0NCUwQS0tLSUyMGElMkZtb25rZXklMkZpbmZlY3Rpb25fbW9ua2V5JTJGbW9ua2V5LnB5JTBBJTJCJTJCJTJCJTIwYiUyRm1vbmtleSUyRmluZmVjdGlvbl9tb25rZXklMkZtb25rZXkucHklMEElNDAlNDAlMjAtMTU4JTJDNyUyMCUyQjE1OCUyQzclMjAlNDAlNDAlMjBjbGFzcyUyMEluZmVjdGlvbk1vbmtleShvYmplY3QpJTNBJTBBJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwaWYlMjBub3QlMjBzZWxmLl9rZWVwX3J1bm5pbmclMjBvciUyMG5vdCUyMFdvcm1Db25maWd1cmF0aW9uLmFsaXZlJTNBJTBBJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwYnJlYWslMEElMjAlMEEtJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwbWFjaGluZXMlMjAlM0QlMjBzZWxmLl9uZXR3b3JrLmdldF92aWN0aW1fbWFjaGluZXMobWF4X2ZpbmQlM0RXb3JtQ29uZmlndXJhdGlvbi52aWN0aW1zX21heF9maW5kJTJDJTBBJTJCJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwbWFjaGluZXMlMjAlM0QlMjBzZWxmLl9uZXR3b3JrLmdldF92aWN0aW1fbWFjaGluZXMobWF4X2ZpbmQlM0QxMDAlMkMlMEElMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjBzdG9wX2NhbGxiYWNrJTNEQ29udHJvbENsaWVudC5jaGVja19mb3Jfc3RvcCklMEElMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjBpc19lbXB0eSUyMCUzRCUyMFRydWUlMEElMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjBmb3IlMjBtYWNoaW5lJTIwaW4lMjBtYWNoaW5lcyUzQSUwQWRpZmYlMjAtLWdpdCUyMGElMkZtb25rZXklMkZtb25rZXlfaXNsYW5kJTJGY2MlMkZzZXJ2aWNlcyUyRmNvbmZpZ19zY2hlbWElMkZpbnRlcm5hbC5weSUyMGIlMkZtb25rZXklMkZtb25rZXlfaXNsYW5kJTJGY2MlMkZzZXJ2aWNlcyUyRmNvbmZpZ19zY2hlbWElMkZpbnRlcm5hbC5weSUwQWluZGV4JTIwYmRiYWUyNDYuLmQ2MDQyZDM1JTIwMTAwNjQ0JTBBLS0tJTIwYSUyRm1vbmtleSUyRm1vbmtleV9pc2xhbmQlMkZjYyUyRnNlcnZpY2VzJTJGY29uZmlnX3NjaGVtYSUyRmludGVybmFsLnB5JTBBJTJCJTJCJTJCJTIwYiUyRm1vbmtleSUyRm1vbmtleV9pc2xhbmQlMkZjYyUyRnNlcnZpY2VzJTJGY29uZmlnX3NjaGVtYSUyRmludGVybmFsLnB5JTBBJTQwJTQwJTIwLTQwJTJDMTIlMjAlMkI0MCUyQzYlMjAlNDAlNDAlMjBJTlRFUk5BTCUyMCUzRCUyMCU3QiUwQSUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMnRpdGxlJTIyJTNBJTIwJTIyTW9ua2V5JTIyJTJDJTBBJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIydHlwZSUyMiUzQSUyMCUyMm9iamVjdCUyMiUyQyUwQSUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMnByb3BlcnRpZXMlMjIlM0ElMjAlN0IlMEEtJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIydmljdGltc19tYXhfZmluZCUyMiUzQSUyMCU3QiUwQS0lMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjJ0aXRsZSUyMiUzQSUyMCUyMk1heCUyMHZpY3RpbXMlMjB0byUyMGZpbmQlMjIlMkMlMEEtJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIydHlwZSUyMiUzQSUyMCUyMmludGVnZXIlMjIlMkMlMEEtJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIyZGVmYXVsdCUyMiUzQSUyMDEwMCUyQyUwQS0lMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjJkZXNjcmlwdGlvbiUyMiUzQSUyMCUyMkRldGVybWluZXMlMjB0aGUlMjBtYXhpbXVtJTIwbnVtYmVyJTIwb2YlMjBtYWNoaW5lcyUyMHRoZSUyMG1vbmtleSUyMGlzJTIwYWxsb3dlZCUyMHRvJTIwc2NhbiUyMiUwQS0lMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlN0QlMkMlMEElMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjJ2aWN0aW1zX21heF9leHBsb2l0JTIyJTNBJTIwJTdCJTBBJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIydGl0bGUlMjIlM0ElMjAlMjJNYXglMjB2aWN0aW1zJTIwdG8lMjBleHBsb2l0JTIyJTJDJTBBJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIydHlwZSUyMiUzQSUyMCUyMmludGVnZXIlMjIlMkMlMEE=", + "dod": "Make the max victim number that Monkey will find before stopping configurable by the user instead of constant.", + "description": "# Make something configurable\n\nIn this unit, you will learn how to add a configuration option to Monkey and how to use it in the Monkey Agent code. \n\n![computer fire](https://media.giphy.com/media/7J4P7cUur2DlErijp3/giphy.gif \"computer fire\")\n\n## Why is this important?\n\nEnabling users to configure the Monkey's behaviour gives them a lot more freedom in how they want to use the Monkey and enables more use cases.\n\n## What is \"Max victims to find\"?\n\nThe Monkey has a function which finds \"victim\" machines on the network for the Monkey to try and exploit. It's called `get_victim_machines`. This function accepts an argument which limits how many machines the Monkey should find.\n\nWe want to make that value editable by the user instead of constant in the code.\n\n## Manual testing\n\n1. After you've performed the required changes, reload the Server and check your value exists in the Internal tab of the config (see image).\n\n![](https://i.imgur.com/e0XAxuV.png)\n\n2. Set the new value to 1, and run Monkey locally (from source). See that the Monkey only scans one machine.", + "summary": "* When changing config schema by adding or deleting keys, you need to update the Blackbox Test configurations as well [here](https://github.com/guardicore/monkey/tree/develop/envs/monkey_zoo/blackbox/island_configs).", + "diff": "diff --git a/monkey/infection_monkey/config.py b/monkey/infection_monkey/config.py\nindex 1fbcb876..67ed19de 100644\n--- a/monkey/infection_monkey/config.py\n+++ b/monkey/infection_monkey/config.py\n@@ -120,9 +120,6 @@\n exploiter_classes = []\n system_info_collector_classes = []\n \n- # how many victims to look for in a single scan iteration\n- victims_max_find = 100\n-\n # how many victims to exploit before stopping\n victims_max_exploit = 100\n \ndiff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py\nindex 444bde45..ff23f671 100644\n--- a/monkey/infection_monkey/monkey.py\n+++ b/monkey/infection_monkey/monkey.py\n@@ -154,7 +154,7 @@\n if not self._keep_running or not WormConfiguration.alive:\n break\n \n- machines = self._network.get_victim_machines(max_find=WormConfiguration.victims_max_find,\n+ machines = self._network.get_victim_machines(max_find=100,\n stop_callback=ControlClient.check_for_stop)\n is_empty = True\n for machine in machines:\ndiff --git a/monkey/monkey_island/cc/services/config_schema/internal.py b/monkey/monkey_island/cc/services/config_schema/internal.py\nindex bdbae246..d6042d35 100644\n--- a/monkey/monkey_island/cc/services/config_schema/internal.py\n+++ b/monkey/monkey_island/cc/services/config_schema/internal.py\n@@ -40,12 +40,6 @@\n \"title\": \"Monkey\",\n \"type\": \"object\",\n \"properties\": {\n- \"victims_max_find\": {\n- \"title\": \"Max victims to find\",\n- \"type\": \"integer\",\n- \"default\": 100,\n- \"description\": \"Determines the maximum number of machines the monkey is allowed to scan\"\n- },\n \"victims_max_exploit\": {\n \"title\": \"Max victims to exploit\",\n \"type\": \"integer\",\n", "tests": [], "hints": [ "Look for `victims_max_exploit` - it's rather similar." ], - "files": { + "app_version": "0.2.1", + "file_version": "1.0.3", + "swimmPatch": { "monkey/infection_monkey/config.py": { - "index": [ - "1fbcb876..67ed19de", - "100644" - ], - "fileA": "monkey/infection_monkey/config.py", - "fileB": "monkey/infection_monkey/config.py", - "status": "MODIFIED", - "numLineDeletions": 3, - "numLineAdditions": 0, - "hunkContainers": [ - "JTdCJTIyaHVuayUyMiUzQSU3QiUyMmhlYWRlciUyMiUzQSUyMiU0MCU0MCUyMC0xMzElMkM5JTIwJTJCMTMxJTJDNiUyMCU0MCU0MCUyMGNsYXNzJTIwQ29uZmlndXJhdGlvbihvYmplY3QpJTNBJTIyJTJDJTIyY2hhbmdlcyUyMiUzQSU1QiU3QiUyMnR5cGUlMjIlM0ElMjJjb250ZXh0JTIyJTJDJTIyZGF0YSUyMiUzQSUyMiUyMCUyMCUyMCUyMCUyMGV4cGxvaXRlcl9jbGFzc2VzJTIwJTNEJTIwJTVCJTVEJTIyJTJDJTIybGluZU51bWJlcnMlMjIlM0ElN0IlMjJhJTIyJTNBMTMxJTJDJTIyYiUyMiUzQTEzMSU3RCU3RCUyQyU3QiUyMnR5cGUlMjIlM0ElMjJjb250ZXh0JTIyJTJDJTIyZGF0YSUyMiUzQSUyMiUyMCUyMCUyMCUyMCUyMHN5c3RlbV9pbmZvX2NvbGxlY3Rvcl9jbGFzc2VzJTIwJTNEJTIwJTVCJTVEJTIyJTJDJTIybGluZU51bWJlcnMlMjIlM0ElN0IlMjJhJTIyJTNBMTMyJTJDJTIyYiUyMiUzQTEzMiU3RCU3RCUyQyU3QiUyMnR5cGUlMjIlM0ElMjJjb250ZXh0JTIyJTJDJTIyZGF0YSUyMiUzQSUyMiUyMCUyMiUyQyUyMmxpbmVOdW1iZXJzJTIyJTNBJTdCJTIyYSUyMiUzQTEzMyUyQyUyMmIlMjIlM0ExMzMlN0QlN0QlMkMlN0IlMjJ0eXBlJTIyJTNBJTIyZGVsJTIyJTJDJTIybWFyayUyMiUzQSUyMi0lMjIlMkMlMjJkYXRhJTIyJTNBJTIyJTIwJTIwJTIwJTIwJTIzJTIwaG93JTIwbWFueSUyMHZpY3RpbXMlMjB0byUyMGxvb2slMjBmb3IlMjBpbiUyMGElMjBzaW5nbGUlMjBzY2FuJTIwaXRlcmF0aW9uJTIyJTJDJTIybGluZU51bWJlcnMlMjIlM0ElN0IlMjJhJTIyJTNBMTM0JTdEJTdEJTJDJTdCJTIydHlwZSUyMiUzQSUyMmRlbCUyMiUyQyUyMm1hcmslMjIlM0ElMjItJTIyJTJDJTIyZGF0YSUyMiUzQSUyMiUyMCUyMCUyMCUyMHZpY3RpbXNfbWF4X2ZpbmQlMjAlM0QlMjAxMDAlMjIlMkMlMjJsaW5lTnVtYmVycyUyMiUzQSU3QiUyMmElMjIlM0ExMzUlN0QlN0QlMkMlN0IlMjJ0eXBlJTIyJTNBJTIyZGVsJTIyJTJDJTIybWFyayUyMiUzQSUyMi0lMjIlMkMlMjJkYXRhJTIyJTNBJTIyJTIyJTJDJTIybGluZU51bWJlcnMlMjIlM0ElN0IlMjJhJTIyJTNBMTM2JTdEJTdEJTJDJTdCJTIydHlwZSUyMiUzQSUyMmNvbnRleHQlMjIlMkMlMjJkYXRhJTIyJTNBJTIyJTIwJTIwJTIwJTIwJTIwJTIzJTIwaG93JTIwbWFueSUyMHZpY3RpbXMlMjB0byUyMGV4cGxvaXQlMjBiZWZvcmUlMjBzdG9wcGluZyUyMiUyQyUyMmxpbmVOdW1iZXJzJTIyJTNBJTdCJTIyYSUyMiUzQTEzNyUyQyUyMmIlMjIlM0ExMzQlN0QlN0QlMkMlN0IlMjJ0eXBlJTIyJTNBJTIyY29udGV4dCUyMiUyQyUyMmRhdGElMjIlM0ElMjIlMjAlMjAlMjAlMjAlMjB2aWN0aW1zX21heF9leHBsb2l0JTIwJTNEJTIwMTAwJTIyJTJDJTIybGluZU51bWJlcnMlMjIlM0ElN0IlMjJhJTIyJTNBMTM4JTJDJTIyYiUyMiUzQTEzNSU3RCU3RCUyQyU3QiUyMnR5cGUlMjIlM0ElMjJjb250ZXh0JTIyJTJDJTIyZGF0YSUyMiUzQSUyMiUyMCUyMiUyQyUyMmxpbmVOdW1iZXJzJTIyJTNBJTdCJTIyYSUyMiUzQTEzOSUyQyUyMmIlMjIlM0ExMzYlN0QlN0QlNUQlMkMlMjJsaW5lTnVtYmVycyUyMiUzQSU3QiUyMmElMjIlM0ElN0IlMjJzdGFydExpbmUlMjIlM0ExMzElMkMlMjJsaW5lc0NvdW50JTIyJTNBOSU3RCUyQyUyMmIlMjIlM0ElN0IlMjJzdGFydExpbmUlMjIlM0ExMzElMkMlMjJsaW5lc0NvdW50JTIyJTNBNiU3RCU3RCU3RCU3RA==" + "diffType": "MODIFIED", + "fileDiffHeader": "diff --git a/monkey/infection_monkey/config.py b/monkey/infection_monkey/config.py\nindex 1fbcb876..67ed19de 100644\n--- a/monkey/infection_monkey/config.py\n+++ b/monkey/infection_monkey/config.py", + "hunks": [ + { + "swimmHunkMetadata": {}, + "hunkDiffLines": [ + "@@ -120,9 +120,6 @@", + " exploiter_classes = []", + " system_info_collector_classes = []", + " ", + "- # how many victims to look for in a single scan iteration", + "- victims_max_find = 100", + "-", + " # how many victims to exploit before stopping", + " victims_max_exploit = 100", + " " + ] + } ] }, "monkey/infection_monkey/monkey.py": { - "index": [ - "444bde45..ff23f671", - "100644" - ], - "fileA": "monkey/infection_monkey/monkey.py", - "fileB": "monkey/infection_monkey/monkey.py", - "status": "MODIFIED", - "numLineDeletions": 1, - "numLineAdditions": 1, - "hunkContainers": [ - "JTdCJTIyaHVuayUyMiUzQSU3QiUyMmhlYWRlciUyMiUzQSUyMiU0MCU0MCUyMC0xNTglMkM3JTIwJTJCMTU4JTJDNyUyMCU0MCU0MCUyMGNsYXNzJTIwSW5mZWN0aW9uTW9ua2V5KG9iamVjdCklM0ElMjIlMkMlMjJjaGFuZ2VzJTIyJTNBJTVCJTdCJTIydHlwZSUyMiUzQSUyMmNvbnRleHQlMjIlMkMlMjJkYXRhJTIyJTNBJTIyJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwaWYlMjBub3QlMjBzZWxmLl9rZWVwX3J1bm5pbmclMjBvciUyMG5vdCUyMFdvcm1Db25maWd1cmF0aW9uLmFsaXZlJTNBJTIyJTJDJTIybGluZU51bWJlcnMlMjIlM0ElN0IlMjJhJTIyJTNBMTU4JTJDJTIyYiUyMiUzQTE1OCU3RCU3RCUyQyU3QiUyMnR5cGUlMjIlM0ElMjJjb250ZXh0JTIyJTJDJTIyZGF0YSUyMiUzQSUyMiUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMGJyZWFrJTIyJTJDJTIybGluZU51bWJlcnMlMjIlM0ElN0IlMjJhJTIyJTNBMTU5JTJDJTIyYiUyMiUzQTE1OSU3RCU3RCUyQyU3QiUyMnR5cGUlMjIlM0ElMjJjb250ZXh0JTIyJTJDJTIyZGF0YSUyMiUzQSUyMiUyMCUyMiUyQyUyMmxpbmVOdW1iZXJzJTIyJTNBJTdCJTIyYSUyMiUzQTE2MCUyQyUyMmIlMjIlM0ExNjAlN0QlN0QlMkMlN0IlMjJ0eXBlJTIyJTNBJTIyZGVsJTIyJTJDJTIybWFyayUyMiUzQSUyMi0lMjIlMkMlMjJkYXRhJTIyJTNBJTIyJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwbWFjaGluZXMlMjAlM0QlMjBzZWxmLl9uZXR3b3JrLmdldF92aWN0aW1fbWFjaGluZXMobWF4X2ZpbmQlM0RXb3JtQ29uZmlndXJhdGlvbi52aWN0aW1zX21heF9maW5kJTJDJTIyJTJDJTIybGluZU51bWJlcnMlMjIlM0ElN0IlMjJhJTIyJTNBMTYxJTdEJTdEJTJDJTdCJTIydHlwZSUyMiUzQSUyMmFkZCUyMiUyQyUyMm1hcmslMjIlM0ElMjIlMkIlMjIlMkMlMjJkYXRhJTIyJTNBJTIyJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwbWFjaGluZXMlMjAlM0QlMjBzZWxmLl9uZXR3b3JrLmdldF92aWN0aW1fbWFjaGluZXMobWF4X2ZpbmQlM0QxMDAlMkMlMjIlMkMlMjJsaW5lTnVtYmVycyUyMiUzQSU3QiUyMmIlMjIlM0ExNjElN0QlN0QlMkMlN0IlMjJ0eXBlJTIyJTNBJTIyY29udGV4dCUyMiUyQyUyMmRhdGElMjIlM0ElMjIlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjBzdG9wX2NhbGxiYWNrJTNEQ29udHJvbENsaWVudC5jaGVja19mb3Jfc3RvcCklMjIlMkMlMjJsaW5lTnVtYmVycyUyMiUzQSU3QiUyMmElMjIlM0ExNjIlMkMlMjJiJTIyJTNBMTYyJTdEJTdEJTJDJTdCJTIydHlwZSUyMiUzQSUyMmNvbnRleHQlMjIlMkMlMjJkYXRhJTIyJTNBJTIyJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwaXNfZW1wdHklMjAlM0QlMjBUcnVlJTIyJTJDJTIybGluZU51bWJlcnMlMjIlM0ElN0IlMjJhJTIyJTNBMTYzJTJDJTIyYiUyMiUzQTE2MyU3RCU3RCUyQyU3QiUyMnR5cGUlMjIlM0ElMjJjb250ZXh0JTIyJTJDJTIyZGF0YSUyMiUzQSUyMiUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMGZvciUyMG1hY2hpbmUlMjBpbiUyMG1hY2hpbmVzJTNBJTIyJTJDJTIybGluZU51bWJlcnMlMjIlM0ElN0IlMjJhJTIyJTNBMTY0JTJDJTIyYiUyMiUzQTE2NCU3RCU3RCU1RCUyQyUyMmxpbmVOdW1iZXJzJTIyJTNBJTdCJTIyYSUyMiUzQSU3QiUyMnN0YXJ0TGluZSUyMiUzQTE1OCUyQyUyMmxpbmVzQ291bnQlMjIlM0E3JTdEJTJDJTIyYiUyMiUzQSU3QiUyMnN0YXJ0TGluZSUyMiUzQTE1OCUyQyUyMmxpbmVzQ291bnQlMjIlM0E3JTdEJTdEJTdEJTdE" + "diffType": "MODIFIED", + "fileDiffHeader": "diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py\nindex 444bde45..ff23f671 100644\n--- a/monkey/infection_monkey/monkey.py\n+++ b/monkey/infection_monkey/monkey.py", + "hunks": [ + { + "swimmHunkMetadata": {}, + "hunkDiffLines": [ + "@@ -154,7 +154,7 @@", + " if not self._keep_running or not WormConfiguration.alive:", + " break", + " ", + "- machines = self._network.get_victim_machines(max_find=WormConfiguration.victims_max_find,", + "+ machines = self._network.get_victim_machines(max_find=100,", + " stop_callback=ControlClient.check_for_stop)", + " is_empty = True", + " for machine in machines:" + ] + } ] }, "monkey/monkey_island/cc/services/config_schema/internal.py": { - "index": [ - "bdbae246..d6042d35", - "100644" - ], - "fileA": "monkey/monkey_island/cc/services/config_schema/internal.py", - "fileB": "monkey/monkey_island/cc/services/config_schema/internal.py", - "status": "MODIFIED", - "numLineDeletions": 6, - "numLineAdditions": 0, - "hunkContainers": [ - "JTdCJTIyaHVuayUyMiUzQSU3QiUyMmhlYWRlciUyMiUzQSUyMiU0MCU0MCUyMC00MCUyQzEyJTIwJTJCNDAlMkM2JTIwJTQwJTQwJTIwSU5URVJOQUwlMjAlM0QlMjAlN0IlMjIlMkMlMjJjaGFuZ2VzJTIyJTNBJTVCJTdCJTIydHlwZSUyMiUzQSUyMmNvbnRleHQlMjIlMkMlMjJkYXRhJTIyJTNBJTIyJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTVDJTIydGl0bGUlNUMlMjIlM0ElMjAlNUMlMjJNb25rZXklNUMlMjIlMkMlMjIlMkMlMjJsaW5lTnVtYmVycyUyMiUzQSU3QiUyMmElMjIlM0E0MCUyQyUyMmIlMjIlM0E0MCU3RCU3RCUyQyU3QiUyMnR5cGUlMjIlM0ElMjJjb250ZXh0JTIyJTJDJTIyZGF0YSUyMiUzQSUyMiUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCU1QyUyMnR5cGUlNUMlMjIlM0ElMjAlNUMlMjJvYmplY3QlNUMlMjIlMkMlMjIlMkMlMjJsaW5lTnVtYmVycyUyMiUzQSU3QiUyMmElMjIlM0E0MSUyQyUyMmIlMjIlM0E0MSU3RCU3RCUyQyU3QiUyMnR5cGUlMjIlM0ElMjJjb250ZXh0JTIyJTJDJTIyZGF0YSUyMiUzQSUyMiUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCU1QyUyMnByb3BlcnRpZXMlNUMlMjIlM0ElMjAlN0IlMjIlMkMlMjJsaW5lTnVtYmVycyUyMiUzQSU3QiUyMmElMjIlM0E0MiUyQyUyMmIlMjIlM0E0MiU3RCU3RCUyQyU3QiUyMnR5cGUlMjIlM0ElMjJkZWwlMjIlMkMlMjJtYXJrJTIyJTNBJTIyLSUyMiUyQyUyMmRhdGElMjIlM0ElMjIlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlNUMlMjJ2aWN0aW1zX21heF9maW5kJTVDJTIyJTNBJTIwJTdCJTIyJTJDJTIybGluZU51bWJlcnMlMjIlM0ElN0IlMjJhJTIyJTNBNDMlN0QlN0QlMkMlN0IlMjJ0eXBlJTIyJTNBJTIyZGVsJTIyJTJDJTIybWFyayUyMiUzQSUyMi0lMjIlMkMlMjJkYXRhJTIyJTNBJTIyJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTVDJTIydGl0bGUlNUMlMjIlM0ElMjAlNUMlMjJNYXglMjB2aWN0aW1zJTIwdG8lMjBmaW5kJTVDJTIyJTJDJTIyJTJDJTIybGluZU51bWJlcnMlMjIlM0ElN0IlMjJhJTIyJTNBNDQlN0QlN0QlMkMlN0IlMjJ0eXBlJTIyJTNBJTIyZGVsJTIyJTJDJTIybWFyayUyMiUzQSUyMi0lMjIlMkMlMjJkYXRhJTIyJTNBJTIyJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTVDJTIydHlwZSU1QyUyMiUzQSUyMCU1QyUyMmludGVnZXIlNUMlMjIlMkMlMjIlMkMlMjJsaW5lTnVtYmVycyUyMiUzQSU3QiUyMmElMjIlM0E0NSU3RCU3RCUyQyU3QiUyMnR5cGUlMjIlM0ElMjJkZWwlMjIlMkMlMjJtYXJrJTIyJTNBJTIyLSUyMiUyQyUyMmRhdGElMjIlM0ElMjIlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlNUMlMjJkZWZhdWx0JTVDJTIyJTNBJTIwMTAwJTJDJTIyJTJDJTIybGluZU51bWJlcnMlMjIlM0ElN0IlMjJhJTIyJTNBNDYlN0QlN0QlMkMlN0IlMjJ0eXBlJTIyJTNBJTIyZGVsJTIyJTJDJTIybWFyayUyMiUzQSUyMi0lMjIlMkMlMjJkYXRhJTIyJTNBJTIyJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTVDJTIyZGVzY3JpcHRpb24lNUMlMjIlM0ElMjAlNUMlMjJEZXRlcm1pbmVzJTIwdGhlJTIwbWF4aW11bSUyMG51bWJlciUyMG9mJTIwbWFjaGluZXMlMjB0aGUlMjBtb25rZXklMjBpcyUyMGFsbG93ZWQlMjB0byUyMHNjYW4lNUMlMjIlMjIlMkMlMjJsaW5lTnVtYmVycyUyMiUzQSU3QiUyMmElMjIlM0E0NyU3RCU3RCUyQyU3QiUyMnR5cGUlMjIlM0ElMjJkZWwlMjIlMkMlMjJtYXJrJTIyJTNBJTIyLSUyMiUyQyUyMmRhdGElMjIlM0ElMjIlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlN0QlMkMlMjIlMkMlMjJsaW5lTnVtYmVycyUyMiUzQSU3QiUyMmElMjIlM0E0OCU3RCU3RCUyQyU3QiUyMnR5cGUlMjIlM0ElMjJjb250ZXh0JTIyJTJDJTIyZGF0YSUyMiUzQSUyMiUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCU1QyUyMnZpY3RpbXNfbWF4X2V4cGxvaXQlNUMlMjIlM0ElMjAlN0IlMjIlMkMlMjJsaW5lTnVtYmVycyUyMiUzQSU3QiUyMmElMjIlM0E0OSUyQyUyMmIlMjIlM0E0MyU3RCU3RCUyQyU3QiUyMnR5cGUlMjIlM0ElMjJjb250ZXh0JTIyJTJDJTIyZGF0YSUyMiUzQSUyMiUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCU1QyUyMnRpdGxlJTVDJTIyJTNBJTIwJTVDJTIyTWF4JTIwdmljdGltcyUyMHRvJTIwZXhwbG9pdCU1QyUyMiUyQyUyMiUyQyUyMmxpbmVOdW1iZXJzJTIyJTNBJTdCJTIyYSUyMiUzQTUwJTJDJTIyYiUyMiUzQTQ0JTdEJTdEJTJDJTdCJTIydHlwZSUyMiUzQSUyMmNvbnRleHQlMjIlMkMlMjJkYXRhJTIyJTNBJTIyJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTVDJTIydHlwZSU1QyUyMiUzQSUyMCU1QyUyMmludGVnZXIlNUMlMjIlMkMlMjIlMkMlMjJsaW5lTnVtYmVycyUyMiUzQSU3QiUyMmElMjIlM0E1MSUyQyUyMmIlMjIlM0E0NSU3RCU3RCU1RCUyQyUyMmxpbmVOdW1iZXJzJTIyJTNBJTdCJTIyYSUyMiUzQSU3QiUyMnN0YXJ0TGluZSUyMiUzQTQwJTJDJTIybGluZXNDb3VudCUyMiUzQTEyJTdEJTJDJTIyYiUyMiUzQSU3QiUyMnN0YXJ0TGluZSUyMiUzQTQwJTJDJTIybGluZXNDb3VudCUyMiUzQTYlN0QlN0QlN0QlN0Q=" + "diffType": "MODIFIED", + "fileDiffHeader": "diff --git a/monkey/monkey_island/cc/services/config_schema/internal.py b/monkey/monkey_island/cc/services/config_schema/internal.py\nindex bdbae246..d6042d35 100644\n--- a/monkey/monkey_island/cc/services/config_schema/internal.py\n+++ b/monkey/monkey_island/cc/services/config_schema/internal.py", + "hunks": [ + { + "swimmHunkMetadata": {}, + "hunkDiffLines": [ + "@@ -40,12 +40,6 @@", + " \"title\": \"Monkey\",", + " \"type\": \"object\",", + " \"properties\": {", + "- \"victims_max_find\": {", + "- \"title\": \"Max victims to find\",", + "- \"type\": \"integer\",", + "- \"default\": 100,", + "- \"description\": \"Determines the maximum number of machines the monkey is allowed to scan\"", + "- },", + " \"victims_max_exploit\": {", + " \"title\": \"Max victims to exploit\",", + " \"type\": \"integer\"," + ] + } ] } - }, - "app_version": "0.1.80", - "file_version": "1.0.2" + } } \ No newline at end of file From 22f77d4a0a38ca17459335094c77b6e0f1bff5d6 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Thu, 8 Oct 2020 10:12:45 +0300 Subject: [PATCH 076/152] Updated swimm version for travis --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 196138558..b2c955385 100644 --- a/.travis.yml +++ b/.travis.yml @@ -99,7 +99,7 @@ script: # verify swimm - cd $TRAVIS_BUILD_DIR -- wget https://firebasestorage.googleapis.com/v0/b/swimmio.appspot.com/o/Release%2Fv018%2Fswimm-0.1.8-linux-executable\?alt\=media\&token\=e59c0a18-577f-4b77-bb3b-91b22c3d8b2a -O swimm +- wget https://firebasestorage.googleapis.com/v0/b/swimmio.appspot.com/o/Release%2Fv021%2FSwimm_0.2.1_amd64.deb\?alt\=media\&token\=d42813a1-839f-46ff-9fc3-65ada41551ca -O swimm - chmod +x ./swimm - ./swimm --version - ./swimm verify From 7478eab359d4c6d3cb5b7b5c4f681a08c87dc44f Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Thu, 8 Oct 2020 11:11:57 +0300 Subject: [PATCH 077/152] Updated swimm version for travis --- .travis.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index b2c955385..bf183544b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -101,8 +101,11 @@ script: - cd $TRAVIS_BUILD_DIR - wget https://firebasestorage.googleapis.com/v0/b/swimmio.appspot.com/o/Release%2Fv021%2FSwimm_0.2.1_amd64.deb\?alt\=media\&token\=d42813a1-839f-46ff-9fc3-65ada41551ca -O swimm - chmod +x ./swimm -- ./swimm --version -- ./swimm verify +- sudo dpkg -i ./swimm +- sudo apt-get -f install -y +- sudo dpkg -i ./swimm +- swimm --version +- swimm verify after_success: # Upload code coverage results to codecov.io, see https://github.com/codecov/codecov-bash for more information From eac960c73d545c16393028ef76dfdc6cd2be6324 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Fri, 9 Oct 2020 10:19:32 +0300 Subject: [PATCH 078/152] Fixed version update bug that happens on systems with no internet connection --- monkey/common/utils/exceptions.py | 4 ++++ monkey/monkey_island/cc/services/version_update.py | 7 ++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/monkey/common/utils/exceptions.py b/monkey/common/utils/exceptions.py index 4b27cd04d..27d128e88 100644 --- a/monkey/common/utils/exceptions.py +++ b/monkey/common/utils/exceptions.py @@ -28,3 +28,7 @@ class RulePathCreatorNotFound(Exception): class InvalidAWSKeys(Exception): """ Raise to indicate that AWS API keys are invalid""" + + +class NoInternetError(Exception): + """ Raise to indicate problems caused when no internet connection is present""" diff --git a/monkey/monkey_island/cc/services/version_update.py b/monkey/monkey_island/cc/services/version_update.py index ad1f81513..33fc8ef74 100644 --- a/monkey/monkey_island/cc/services/version_update.py +++ b/monkey/monkey_island/cc/services/version_update.py @@ -3,6 +3,7 @@ import logging import requests import monkey_island.cc.environment.environment_singleton as env_singleton +from common.utils.exceptions import NoInternetError from common.version import get_version __author__ = "itay.mizeretz" @@ -42,7 +43,11 @@ class VersionUpdateService: """ url = VersionUpdateService.VERSION_SERVER_CHECK_NEW_URL % (env_singleton.env.get_deployment(), get_version()) - reply = requests.get(url, timeout=15) + try: + reply = requests.get(url, timeout=7) + except requests.exceptions.RequestException: + logger.info("Can't get latest monkey version, probably no connection to the internet.") + raise NoInternetError res = reply.json().get('newer_version', None) From eb5648dc0e912018421916cd555e2c8d97a359ff Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 13 Oct 2020 17:44:25 +0300 Subject: [PATCH 079/152] Fixed segmentation findings to use the same infrastructure as other findings. Small segmentation finding bugfix --- .../zero_trust/monkey_finding_details.py | 12 ++++- .../models/zero_trust/segmentation_finding.py | 50 ------------------- .../cc/models/zero_trust/subnet_pair.py | 19 +++++++ .../zero_trust_checks/segmentation.py | 16 +++--- .../cc/services/zero_trust/events_service.py | 36 ------------- .../cc/services/zero_trust/finding_service.py | 4 +- .../zero_trust/monkey_finding_service.py | 31 +++++++++--- 7 files changed, 64 insertions(+), 104 deletions(-) delete mode 100644 monkey/monkey_island/cc/models/zero_trust/segmentation_finding.py create mode 100644 monkey/monkey_island/cc/models/zero_trust/subnet_pair.py delete mode 100644 monkey/monkey_island/cc/services/zero_trust/events_service.py diff --git a/monkey/monkey_island/cc/models/zero_trust/monkey_finding_details.py b/monkey/monkey_island/cc/models/zero_trust/monkey_finding_details.py index 5516d001b..84ca0f7fc 100644 --- a/monkey/monkey_island/cc/models/zero_trust/monkey_finding_details.py +++ b/monkey/monkey_island/cc/models/zero_trust/monkey_finding_details.py @@ -1,8 +1,10 @@ +from __future__ import annotations from typing import List from mongoengine import Document, EmbeddedDocumentListField from monkey_island.cc.models.zero_trust.event import Event +from monkey_island.cc.models.zero_trust.subnet_pair import SubnetPair class MonkeyFindingDetails(Document): @@ -13,8 +15,16 @@ class MonkeyFindingDetails(Document): # SCHEMA events = EmbeddedDocumentListField(document_type=Event, required=False) + checked_subnet_pairs = EmbeddedDocumentListField(document_type=SubnetPair, required=False) # LOGIC - def add_events(self, events: List[Event]) -> None: + def add_events(self, events: List[Event]) -> MonkeyFindingDetails: self.update(push_all__events=events) return self + + def add_checked_subnet_pair(self, subnet_pair: SubnetPair) -> MonkeyFindingDetails: + self.update(push__checked_subnet_pairs=subnet_pair) + return self + + def is_with_subnet_pair(self, subnet_pair: SubnetPair): + return subnet_pair in self.checked_subnet_pairs diff --git a/monkey/monkey_island/cc/models/zero_trust/segmentation_finding.py b/monkey/monkey_island/cc/models/zero_trust/segmentation_finding.py deleted file mode 100644 index 903cf3546..000000000 --- a/monkey/monkey_island/cc/models/zero_trust/segmentation_finding.py +++ /dev/null @@ -1,50 +0,0 @@ -from mongoengine import StringField - -import common.common_consts.zero_trust_consts as zero_trust_consts -from monkey_island.cc.models.zero_trust.finding import Finding - - -def need_to_overwrite_status(saved_status, new_status): - return (saved_status == zero_trust_consts.STATUS_PASSED) and (new_status == zero_trust_consts.STATUS_FAILED) - - -class SegmentationFinding(Finding): - first_subnet = StringField() - second_subnet = StringField() - - @staticmethod - def create_or_add_to_existing_finding(subnets, status, segmentation_event): - """ - Creates a segmentation finding. If a segmentation finding with the relevant subnets already exists, adds the - event to the existing finding, and the "worst" status is chosen (i.e. if the existing one is "Failed" it will - remain so). - - :param subnets: the 2 subnets of this finding. - :param status: STATUS_PASSED or STATUS_FAILED - :param segmentation_event: The specific event - """ - assert len(subnets) == 2 - - # Sort them so A -> B and B -> A segmentation findings will be the same one. - subnets.sort() - - existing_findings = SegmentationFinding.objects(first_subnet=subnets[0], second_subnet=subnets[1]) - - if len(existing_findings) == 0: - # No finding exists - create. - new_finding = SegmentationFinding( - first_subnet=subnets[0], - second_subnet=subnets[1], - test=zero_trust_consts.TEST_SEGMENTATION, - status=status, - events=[segmentation_event] - ) - new_finding.save() - else: - # A finding exists (should be one). Add the event to it. - assert len(existing_findings) == 1 - existing_finding = existing_findings[0] - existing_finding.events.append(segmentation_event) - if need_to_overwrite_status(existing_finding.status, status): - existing_finding.status = status - existing_finding.save() diff --git a/monkey/monkey_island/cc/models/zero_trust/subnet_pair.py b/monkey/monkey_island/cc/models/zero_trust/subnet_pair.py new file mode 100644 index 000000000..8d3de96e4 --- /dev/null +++ b/monkey/monkey_island/cc/models/zero_trust/subnet_pair.py @@ -0,0 +1,19 @@ +from typing import List + +from mongoengine import EmbeddedDocument, StringField + + +class SubnetPair(EmbeddedDocument): + """ + This model represents a pair of subnets. It is meant to hold details about cross-segmentation check between two + subnets. + """ + # SCHEMA + first_subnet = StringField() + second_subnet = StringField() + + # LOGIC + @staticmethod + def create_subnet_pair(subnets: List[str]): + subnets.sort() + return SubnetPair(first_subnet=subnets[0], second_subnet=subnets[1]) diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/segmentation.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/segmentation.py index 51af818d1..a5eb865a7 100644 --- a/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/segmentation.py +++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/segmentation.py @@ -5,8 +5,8 @@ from common.network.network_range import NetworkRange from common.network.segmentation_utils import get_ip_if_in_subnet, get_ip_in_src_and_not_in_dst from monkey_island.cc.models import Monkey from monkey_island.cc.models.zero_trust.event import Event -from monkey_island.cc.models.zero_trust.segmentation_finding import SegmentationFinding from monkey_island.cc.services.configuration.utils import get_config_network_segments_as_subnet_groups +from monkey_island.cc.services.zero_trust.monkey_finding_service import MonkeyFindingService SEGMENTATION_DONE_EVENT_TEXT = "Monkey on {hostname} is done attempting cross-segment communications " \ "from `{src_seg}` segments to `{dst_seg}` segments." @@ -26,10 +26,11 @@ def check_segmentation_violation(current_monkey, target_ip): target_subnet = subnet_pair[1] if is_segmentation_violation(current_monkey, target_ip, source_subnet, target_subnet): event = get_segmentation_violation_event(current_monkey, source_subnet, target_ip, target_subnet) - SegmentationFinding.create_or_add_to_existing_finding( - subnets=[source_subnet, target_subnet], + MonkeyFindingService.create_or_add_to_existing( + test=zero_trust_consts.TEST_SEGMENTATION, + subnet_pairs=[[source_subnet, target_subnet]], status=zero_trust_consts.STATUS_FAILED, - segmentation_event=event + events=[event] ) @@ -90,10 +91,11 @@ def create_or_add_findings_for_all_pairs(all_subnets, current_monkey): all_subnets_pairs_for_this_monkey = itertools.product(this_monkey_subnets, other_subnets) for subnet_pair in all_subnets_pairs_for_this_monkey: - SegmentationFinding.create_or_add_to_existing_finding( - subnets=list(subnet_pair), + MonkeyFindingService.create_or_add_to_existing( + subnet_pairs=[list(subnet_pair)], status=zero_trust_consts.STATUS_PASSED, - segmentation_event=get_segmentation_done_event(current_monkey, subnet_pair) + events=[get_segmentation_done_event(current_monkey, subnet_pair)], + test=zero_trust_consts.TEST_SEGMENTATION ) diff --git a/monkey/monkey_island/cc/services/zero_trust/events_service.py b/monkey/monkey_island/cc/services/zero_trust/events_service.py deleted file mode 100644 index 7f4f9e496..000000000 --- a/monkey/monkey_island/cc/services/zero_trust/events_service.py +++ /dev/null @@ -1,36 +0,0 @@ -from typing import List - -from bson import ObjectId - -from monkey_island.cc.models.zero_trust.monkey_finding_details import MonkeyFindingDetails - -# How many events of a single finding to return to UI. -# 50 will return 50 latest and 50 oldest events from a finding -EVENT_FETCH_CNT = 50 - - -class EventsService: - - @staticmethod - def fetch_events_for_display(finding_id: ObjectId): - pipeline = [{'$match': {'_id': finding_id}}, - {'$addFields': {'oldest_events': {'$slice': ['$events', EVENT_FETCH_CNT]}, - 'latest_events': {'$slice': ['$events', -1 * EVENT_FETCH_CNT]}, - 'event_count': {'$size': '$events'}}}, - {'$unset': ['events']}] - details = list(MonkeyFindingDetails.objects.aggregate(*pipeline)) - if details: - details = details[0] - details['latest_events'] = EventsService._get_events_without_overlap(details['event_count'], - details['latest_events']) - return details - - @staticmethod - def _get_events_without_overlap(event_count: int, events: List[object]) -> List[object]: - overlap_count = event_count - EVENT_FETCH_CNT - if overlap_count >= EVENT_FETCH_CNT: - return events - elif overlap_count <= 0: - return [] - else: - return events[-1 * overlap_count:] diff --git a/monkey/monkey_island/cc/services/zero_trust/finding_service.py b/monkey/monkey_island/cc/services/zero_trust/finding_service.py index f6ab4b39a..4d165da7e 100644 --- a/monkey/monkey_island/cc/services/zero_trust/finding_service.py +++ b/monkey/monkey_island/cc/services/zero_trust/finding_service.py @@ -2,7 +2,7 @@ from typing import List from common.common_consts import zero_trust_consts from monkey_island.cc.models.zero_trust.finding import Finding -from monkey_island.cc.services.zero_trust.events_service import EventsService +from monkey_island.cc.services.zero_trust.monkey_details_service import MonkeyDetailsService class FindingService: @@ -13,7 +13,7 @@ class FindingService: details = [] for i in range(len(findings)): if findings[i].type == zero_trust_consts.MONKEY_FINDING: - details = EventsService.fetch_events_for_display(findings[i].details.id) + details = MonkeyDetailsService.fetch_details_for_display(findings[i].details.id) elif findings[i].type == zero_trust_consts.SCOUTSUITE_FINDING: details = findings[i].details.fetch().to_mongo() findings[i] = findings[i].to_mongo() diff --git a/monkey/monkey_island/cc/services/zero_trust/monkey_finding_service.py b/monkey/monkey_island/cc/services/zero_trust/monkey_finding_service.py index 1ee60b117..fbc477953 100644 --- a/monkey/monkey_island/cc/services/zero_trust/monkey_finding_service.py +++ b/monkey/monkey_island/cc/services/zero_trust/monkey_finding_service.py @@ -6,12 +6,13 @@ from common.common_consts import zero_trust_consts from monkey_island.cc.models.zero_trust.event import Event from monkey_island.cc.models.zero_trust.finding import Finding from monkey_island.cc.models.zero_trust.monkey_finding_details import MonkeyFindingDetails +from monkey_island.cc.models.zero_trust.subnet_pair import SubnetPair class MonkeyFindingService: @staticmethod - def create_or_add_to_existing(test, status, events): + def create_or_add_to_existing(test, status, events, subnet_pairs=None): """ Create a new finding or add the events to an existing one if it's the same (same meaning same status and same test). @@ -23,15 +24,18 @@ class MonkeyFindingService: assert (len(existing_findings) < 2), "More than one finding exists for {}:{}".format(test, status) if len(existing_findings) == 0: - MonkeyFindingService.create_new_finding(test, status, events) + MonkeyFindingService.create_new_finding(test, status, events, subnet_pairs) else: # Now we know for sure this is the only one MonkeyFindingService.add_events(existing_findings[0], events) + if subnet_pairs: + MonkeyFindingService.add_subnet_pairs(existing_findings[0], subnet_pairs) @staticmethod - def create_new_finding(test: str, status: str, events: List[Event]): + def create_new_finding(test: str, status: str, events: List[Event], subnet_pairs: List[SubnetPair]): details = MonkeyFindingDetails() details.events = events + details.subnet_pairs = subnet_pairs details.save() Finding.save_finding(test, status, details) @@ -39,6 +43,20 @@ class MonkeyFindingService: def add_events(finding: Finding, events: List[Event]): finding.details.fetch().add_events(events).save() + @staticmethod + def add_subnet_pairs(finding: Finding, subnet_pairs: List[List[str]]): + finding_details = finding.details.fetch() + for subnet_pair in subnet_pairs: + subnet_pair_document = SubnetPair.create_subnet_pair(subnet_pair) + if not MonkeyFindingService.is_subnet_pair_in_finding(finding, subnet_pair_document): + finding_details.add_checked_subnet_pair(subnet_pair_document) + finding_details.save() + + @staticmethod + def is_subnet_pair_in_finding(finding: Finding, subnet_pair: SubnetPair): + details = finding.details.fetch() + return details.is_with_subnet_pair(subnet_pair) + @staticmethod def get_events_by_finding(finding_id: str) -> List[object]: finding = Finding.objects.get(id=finding_id) @@ -50,8 +68,5 @@ class MonkeyFindingService: @staticmethod def add_malicious_activity_to_timeline(events): - MonkeyFindingService.create_or_add_to_existing( - test=zero_trust_consts.TEST_MALICIOUS_ACTIVITY_TIMELINE, - status=zero_trust_consts.STATUS_VERIFY, - events=events - ) + MonkeyFindingService.create_or_add_to_existing(test=zero_trust_consts.TEST_MALICIOUS_ACTIVITY_TIMELINE, + status=zero_trust_consts.STATUS_VERIFY, events=events) From 24ac497eec3a29a0699725bdf55260e23624c7e0 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 13 Oct 2020 17:45:00 +0300 Subject: [PATCH 080/152] Minor style refactoring --- .../zero_trust_checks/antivirus_existence.py | 5 ++--- .../zero_trust_checks/communicate_as_new_user.py | 15 ++++++--------- .../telemetry/zero_trust_checks/data_endpoints.py | 14 ++++---------- .../zero_trust_checks/machine_exploited.py | 7 ++----- .../telemetry/zero_trust_checks/tunneling.py | 7 ++----- 5 files changed, 16 insertions(+), 32 deletions(-) diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/antivirus_existence.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/antivirus_existence.py index 2aad6b896..e15969ec8 100644 --- a/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/antivirus_existence.py +++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/antivirus_existence.py @@ -30,9 +30,8 @@ def check_antivirus_existence(process_list_json, monkey_guid): test_status = zero_trust_consts.STATUS_PASSED else: test_status = zero_trust_consts.STATUS_FAILED - MonkeyFindingService.create_or_add_to_existing( - test=zero_trust_consts.TEST_ENDPOINT_SECURITY_EXISTS, status=test_status, events=events - ) + MonkeyFindingService.create_or_add_to_existing(test=zero_trust_consts.TEST_ENDPOINT_SECURITY_EXISTS, + status=test_status, events=events) def filter_av_processes(process_list): diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/communicate_as_new_user.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/communicate_as_new_user.py index 830c1e56f..00c197e0a 100644 --- a/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/communicate_as_new_user.py +++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/communicate_as_new_user.py @@ -8,15 +8,12 @@ COMM_AS_NEW_USER_SUCCEEDED_FORMAT = \ def check_new_user_communication(current_monkey, success, message): - MonkeyFindingService.create_or_add_to_existing( - test=zero_trust_consts.TEST_COMMUNICATE_AS_NEW_USER, - # If the monkey succeeded to create a user, then the test failed. - status=zero_trust_consts.STATUS_FAILED if success else zero_trust_consts.STATUS_PASSED, - events=[ - get_attempt_event(current_monkey), - get_result_event(current_monkey, message, success) - ] - ) + MonkeyFindingService.create_or_add_to_existing(test=zero_trust_consts.TEST_COMMUNICATE_AS_NEW_USER, + status=zero_trust_consts.STATUS_FAILED if success else zero_trust_consts.STATUS_PASSED, + events=[ + get_attempt_event(current_monkey), + get_result_event(current_monkey, message, success) + ]) def get_attempt_event(current_monkey): diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/data_endpoints.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/data_endpoints.py index d4da2d8dd..e74c5c464 100644 --- a/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/data_endpoints.py +++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/data_endpoints.py @@ -55,16 +55,10 @@ def check_open_data_endpoints(telemetry_json): event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK )) - MonkeyFindingService.create_or_add_to_existing( - test=zero_trust_consts.TEST_DATA_ENDPOINT_HTTP, - status=found_http_server_status, - events=events - ) + MonkeyFindingService.create_or_add_to_existing(test=zero_trust_consts.TEST_DATA_ENDPOINT_HTTP, + status=found_http_server_status, events=events) - MonkeyFindingService.create_or_add_to_existing( - test=zero_trust_consts.TEST_DATA_ENDPOINT_ELASTIC, - status=found_elastic_search_server, - events=events - ) + MonkeyFindingService.create_or_add_to_existing(test=zero_trust_consts.TEST_DATA_ENDPOINT_ELASTIC, + status=found_elastic_search_server, events=events) MonkeyFindingService.add_malicious_activity_to_timeline(events) diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/machine_exploited.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/machine_exploited.py index 941bc4643..e47c4a831 100644 --- a/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/machine_exploited.py +++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/machine_exploited.py @@ -29,10 +29,7 @@ def check_machine_exploited(current_monkey, exploit_successful, exploiter, targe ) status = zero_trust_consts.STATUS_FAILED - MonkeyFindingService.create_or_add_to_existing( - test=zero_trust_consts.TEST_MACHINE_EXPLOITED, - status=status, - events=events - ) + MonkeyFindingService.create_or_add_to_existing(test=zero_trust_consts.TEST_MACHINE_EXPLOITED, status=status, + events=events) MonkeyFindingService.add_malicious_activity_to_timeline(events) diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/tunneling.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/tunneling.py index 9d8140d58..dc5092345 100644 --- a/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/tunneling.py +++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/tunneling.py @@ -18,10 +18,7 @@ def check_tunneling_violation(tunnel_telemetry_json): timestamp=tunnel_telemetry_json['timestamp'] )] - MonkeyFindingService.create_or_add_to_existing( - test=zero_trust_consts.TEST_TUNNELING, - status=zero_trust_consts.STATUS_FAILED, - events=tunneling_events - ) + MonkeyFindingService.create_or_add_to_existing(test=zero_trust_consts.TEST_TUNNELING, + status=zero_trust_consts.STATUS_FAILED, events=tunneling_events) MonkeyFindingService.add_malicious_activity_to_timeline(tunneling_events) From 89bdbf946f580157c209800aa76b4c526d68dcd8 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 13 Oct 2020 17:45:20 +0300 Subject: [PATCH 081/152] Minor exception handling improvement --- monkey/infection_monkey/exploit/HostExploiter.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/monkey/infection_monkey/exploit/HostExploiter.py b/monkey/infection_monkey/exploit/HostExploiter.py index 274d07329..f8fda6d18 100644 --- a/monkey/infection_monkey/exploit/HostExploiter.py +++ b/monkey/infection_monkey/exploit/HostExploiter.py @@ -3,6 +3,7 @@ from abc import abstractmethod from datetime import datetime import infection_monkey.exploit +from common.utils.exceptions import FailedExploitationError from common.utils.exploit_enum import ExploitType from infection_monkey.config import WormConfiguration from infection_monkey.utils.plugins.plugin import Plugin @@ -73,6 +74,8 @@ class HostExploiter(Plugin): result = None try: result = self._exploit_host() + except FailedExploitationError as e: + logger.debug(e) except Exception as _: logger.error(f'Exception in exploit_host', exc_info=True) finally: From eb5f8091950abc7930df335d30cce7b32b1a2ada Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 14 Oct 2020 10:30:13 +0300 Subject: [PATCH 082/152] Removed the storage of subnets that violate segmentation, because this info isn't being used anywhere --- .../zero_trust/monkey_finding_details.py | 9 ----- .../cc/models/zero_trust/subnet_pair.py | 19 ---------- .../zero_trust_checks/segmentation.py | 2 -- .../zero_trust/monkey_details_service.py | 36 +++++++++++++++++++ .../zero_trust/monkey_finding_service.py | 24 ++----------- 5 files changed, 39 insertions(+), 51 deletions(-) delete mode 100644 monkey/monkey_island/cc/models/zero_trust/subnet_pair.py create mode 100644 monkey/monkey_island/cc/services/zero_trust/monkey_details_service.py diff --git a/monkey/monkey_island/cc/models/zero_trust/monkey_finding_details.py b/monkey/monkey_island/cc/models/zero_trust/monkey_finding_details.py index 84ca0f7fc..d05cbc171 100644 --- a/monkey/monkey_island/cc/models/zero_trust/monkey_finding_details.py +++ b/monkey/monkey_island/cc/models/zero_trust/monkey_finding_details.py @@ -4,7 +4,6 @@ from typing import List from mongoengine import Document, EmbeddedDocumentListField from monkey_island.cc.models.zero_trust.event import Event -from monkey_island.cc.models.zero_trust.subnet_pair import SubnetPair class MonkeyFindingDetails(Document): @@ -15,16 +14,8 @@ class MonkeyFindingDetails(Document): # SCHEMA events = EmbeddedDocumentListField(document_type=Event, required=False) - checked_subnet_pairs = EmbeddedDocumentListField(document_type=SubnetPair, required=False) # LOGIC def add_events(self, events: List[Event]) -> MonkeyFindingDetails: self.update(push_all__events=events) return self - - def add_checked_subnet_pair(self, subnet_pair: SubnetPair) -> MonkeyFindingDetails: - self.update(push__checked_subnet_pairs=subnet_pair) - return self - - def is_with_subnet_pair(self, subnet_pair: SubnetPair): - return subnet_pair in self.checked_subnet_pairs diff --git a/monkey/monkey_island/cc/models/zero_trust/subnet_pair.py b/monkey/monkey_island/cc/models/zero_trust/subnet_pair.py deleted file mode 100644 index 8d3de96e4..000000000 --- a/monkey/monkey_island/cc/models/zero_trust/subnet_pair.py +++ /dev/null @@ -1,19 +0,0 @@ -from typing import List - -from mongoengine import EmbeddedDocument, StringField - - -class SubnetPair(EmbeddedDocument): - """ - This model represents a pair of subnets. It is meant to hold details about cross-segmentation check between two - subnets. - """ - # SCHEMA - first_subnet = StringField() - second_subnet = StringField() - - # LOGIC - @staticmethod - def create_subnet_pair(subnets: List[str]): - subnets.sort() - return SubnetPair(first_subnet=subnets[0], second_subnet=subnets[1]) diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/segmentation.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/segmentation.py index a5eb865a7..1c43e2863 100644 --- a/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/segmentation.py +++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/segmentation.py @@ -28,7 +28,6 @@ def check_segmentation_violation(current_monkey, target_ip): event = get_segmentation_violation_event(current_monkey, source_subnet, target_ip, target_subnet) MonkeyFindingService.create_or_add_to_existing( test=zero_trust_consts.TEST_SEGMENTATION, - subnet_pairs=[[source_subnet, target_subnet]], status=zero_trust_consts.STATUS_FAILED, events=[event] ) @@ -92,7 +91,6 @@ def create_or_add_findings_for_all_pairs(all_subnets, current_monkey): for subnet_pair in all_subnets_pairs_for_this_monkey: MonkeyFindingService.create_or_add_to_existing( - subnet_pairs=[list(subnet_pair)], status=zero_trust_consts.STATUS_PASSED, events=[get_segmentation_done_event(current_monkey, subnet_pair)], test=zero_trust_consts.TEST_SEGMENTATION diff --git a/monkey/monkey_island/cc/services/zero_trust/monkey_details_service.py b/monkey/monkey_island/cc/services/zero_trust/monkey_details_service.py new file mode 100644 index 000000000..623285ddd --- /dev/null +++ b/monkey/monkey_island/cc/services/zero_trust/monkey_details_service.py @@ -0,0 +1,36 @@ +from typing import List + +from bson import ObjectId + +from monkey_island.cc.models.zero_trust.monkey_finding_details import MonkeyFindingDetails + +# How many events of a single finding to return to UI. +# 50 will return 50 latest and 50 oldest events from a finding +EVENT_FETCH_CNT = 50 + + +class MonkeyDetailsService: + + @staticmethod + def fetch_details_for_display(finding_id: ObjectId): + pipeline = [{'$match': {'_id': finding_id}}, + {'$addFields': {'oldest_events': {'$slice': ['$events', EVENT_FETCH_CNT]}, + 'latest_events': {'$slice': ['$events', -1 * EVENT_FETCH_CNT]}, + 'event_count': {'$size': '$events'}}}, + {'$unset': ['events', 'checked_subnet_pairs']}] + details = list(MonkeyFindingDetails.objects.aggregate(*pipeline)) + if details: + details = details[0] + details['latest_events'] = MonkeyDetailsService._get_events_without_overlap(details['event_count'], + details['latest_events']) + return details + + @staticmethod + def _get_events_without_overlap(event_count: int, events: List[object]) -> List[object]: + overlap_count = event_count - EVENT_FETCH_CNT + if overlap_count >= EVENT_FETCH_CNT: + return events + elif overlap_count <= 0: + return [] + else: + return events[-1 * overlap_count:] diff --git a/monkey/monkey_island/cc/services/zero_trust/monkey_finding_service.py b/monkey/monkey_island/cc/services/zero_trust/monkey_finding_service.py index fbc477953..46c3137bf 100644 --- a/monkey/monkey_island/cc/services/zero_trust/monkey_finding_service.py +++ b/monkey/monkey_island/cc/services/zero_trust/monkey_finding_service.py @@ -6,13 +6,12 @@ from common.common_consts import zero_trust_consts from monkey_island.cc.models.zero_trust.event import Event from monkey_island.cc.models.zero_trust.finding import Finding from monkey_island.cc.models.zero_trust.monkey_finding_details import MonkeyFindingDetails -from monkey_island.cc.models.zero_trust.subnet_pair import SubnetPair class MonkeyFindingService: @staticmethod - def create_or_add_to_existing(test, status, events, subnet_pairs=None): + def create_or_add_to_existing(test, status, events): """ Create a new finding or add the events to an existing one if it's the same (same meaning same status and same test). @@ -24,18 +23,15 @@ class MonkeyFindingService: assert (len(existing_findings) < 2), "More than one finding exists for {}:{}".format(test, status) if len(existing_findings) == 0: - MonkeyFindingService.create_new_finding(test, status, events, subnet_pairs) + MonkeyFindingService.create_new_finding(test, status, events) else: # Now we know for sure this is the only one MonkeyFindingService.add_events(existing_findings[0], events) - if subnet_pairs: - MonkeyFindingService.add_subnet_pairs(existing_findings[0], subnet_pairs) @staticmethod - def create_new_finding(test: str, status: str, events: List[Event], subnet_pairs: List[SubnetPair]): + def create_new_finding(test: str, status: str, events: List[Event]): details = MonkeyFindingDetails() details.events = events - details.subnet_pairs = subnet_pairs details.save() Finding.save_finding(test, status, details) @@ -43,20 +39,6 @@ class MonkeyFindingService: def add_events(finding: Finding, events: List[Event]): finding.details.fetch().add_events(events).save() - @staticmethod - def add_subnet_pairs(finding: Finding, subnet_pairs: List[List[str]]): - finding_details = finding.details.fetch() - for subnet_pair in subnet_pairs: - subnet_pair_document = SubnetPair.create_subnet_pair(subnet_pair) - if not MonkeyFindingService.is_subnet_pair_in_finding(finding, subnet_pair_document): - finding_details.add_checked_subnet_pair(subnet_pair_document) - finding_details.save() - - @staticmethod - def is_subnet_pair_in_finding(finding: Finding, subnet_pair: SubnetPair): - details = finding.details.fetch() - return details.is_with_subnet_pair(subnet_pair) - @staticmethod def get_events_by_finding(finding_id: str) -> List[object]: finding = Finding.objects.get(id=finding_id) From f894256e56069c54667e7c01a592d61df24238ca Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 14 Oct 2020 11:30:44 +0300 Subject: [PATCH 083/152] Minor phrasing improvements in scoutsuite setup tutorials --- .../scoutsuite-setup/AWSConfiguration/AWSCLISetup.js | 6 +++--- .../scoutsuite-setup/AWSConfiguration/AWSKeySetup.js | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/scoutsuite-setup/AWSConfiguration/AWSCLISetup.js b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/scoutsuite-setup/AWSConfiguration/AWSCLISetup.js index ea5665421..178c60d8b 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/scoutsuite-setup/AWSConfiguration/AWSCLISetup.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/scoutsuite-setup/AWSConfiguration/AWSCLISetup.js @@ -27,13 +27,13 @@ const getContents = (props) => { 1. Configure AWS CLI on Monkey Island Server (if you already have a configured CLI you can skip this step).
  1. - 1. Download and install it on the Monkey Island server (machine running this page).
  2. - 2. Run aws configure. It's important to configure credentials, which - allows ScoutSuite to get information about your cloud configuration. The most trivial way to do so is to + b. Run aws configure. It's important to configure credentials as it + allows ScoutSuite to get information about your cloud configuration. The simplest way to do so is to provide 
-
-

Resources flagged:

-

{props.rule.flagged_items}

-
{props.rule.references.length !== 0 ? getReferences() : ''} {props.rule.items.length !== 0 ? getResources() : ''}
); @@ -54,7 +50,7 @@ export default function RuleDisplay(props) { } return (
-

Resources:

+

Flagged resources ({props.rule.flagged_items}):

{resources}
) } From 7e0748980792def5ab97bec3fe6ab72f327cfa4a Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 12 Jan 2021 12:40:45 +0200 Subject: [PATCH 099/152] Moved rule parsing methods into a separate component, added more details about rules in rule overview: added how many failed/passed/uncheck rules there are for a finding. --- .../scoutsuite/ScoutSuiteRuleModal.js | 74 +++++++++++-------- .../ScoutSuiteSingleRuleDropdown.js | 14 +--- .../scoutsuite/rule-parsing/ParsingUtils.js | 47 ++++++++++++ 3 files changed, 90 insertions(+), 45 deletions(-) create mode 100644 monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/scoutsuite/rule-parsing/ParsingUtils.js diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/scoutsuite/ScoutSuiteRuleModal.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/scoutsuite/ScoutSuiteRuleModal.js index 926d52b9a..b7f212278 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/scoutsuite/ScoutSuiteRuleModal.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/scoutsuite/ScoutSuiteRuleModal.js @@ -2,9 +2,10 @@ import React, {useState} from 'react'; import {Modal} from 'react-bootstrap'; import * as PropTypes from 'prop-types'; import Pluralize from 'pluralize'; -import ScoutSuiteSingleRuleDropdown, {getRuleStatus} from './ScoutSuiteSingleRuleDropdown'; +import ScoutSuiteSingleRuleDropdown from './ScoutSuiteSingleRuleDropdown'; import '../../../../styles/components/scoutsuite/RuleModal.scss'; import STATUSES from '../../common/consts/StatusConsts'; +import {getRuleCountByStatus, sortRules} from './rule-parsing/ParsingUtils'; export default function ScoutSuiteRuleModal(props) { @@ -18,46 +19,54 @@ export default function ScoutSuiteRuleModal(props) { } } - function compareRules(firstRule, secondRule) { - let firstStatus = getRuleStatus(firstRule); - let secondStatus = getRuleStatus(secondRule); - return compareRuleStatuses(firstStatus, secondStatus); - } - - - function compareRuleStatuses(ruleStatusOne, ruleStatusTwo) { - if (ruleStatusOne === ruleStatusTwo) { - return 0; - } else if (ruleStatusOne === STATUSES.STATUS_FAILED) { - return -1; - } else if (ruleStatusTwo === STATUSES.STATUS_FAILED) { - return 1; - } else if (ruleStatusOne === STATUSES.STATUS_VERIFY) { - return -1; - } else if (ruleStatusTwo === STATUSES.STATUS_VERIFY) { - return 1; - } else if (ruleStatusOne === STATUSES.STATUS_PASSED) { - return -1; - } else if (ruleStatusTwo === STATUSES.STATUS_PASSED) { - return 1; - } - } - function renderRuleDropdowns() { let dropdowns = []; - let rules = props.scoutsuite_rules; - rules.sort(compareRules); + let rules = sortRules(props.scoutsuite_rules); rules.forEach(rule => { let dropdown = ( toggleRuleDropdown(rule.description)} rule={rule} scoutsuite_data={props.scoutsuite_data} - key={rule.description+rule.path}/>) + key={rule.description + rule.path}/>) dropdowns.push(dropdown) }); return dropdowns; } + function getGeneralRuleOverview() { + return <> + There {Pluralize('is', props.scoutsuite_rules.length)} +  {props.scoutsuite_rules.length} +  ScoutSuite {Pluralize('rule', props.scoutsuite_rules.length)} associated with this finding. + + } + + function getFailedRuleOverview() { + let failedRuleCnt = getRuleCountByStatus(props.scoutsuite_rules, STATUSES.STATUS_FAILED) + + + getRuleCountByStatus(props.scoutsuite_rules, STATUSES.STATUS_VERIFY); + return <> +  {failedRuleCnt} +  failed security {Pluralize('rule', failedRuleCnt)}. + + } + + function getPassedRuleOverview() { + let passedRuleCnt = getRuleCountByStatus(props.scoutsuite_rules, STATUSES.STATUS_PASSED); + return <> +  {passedRuleCnt} +  passed security {Pluralize('rule', passedRuleCnt)}. + + } + + function getUnexecutedRuleOverview() { + let unexecutedRuleCnt = getRuleCountByStatus(props.scoutsuite_rules, STATUSES.STATUS_UNEXECUTED); + return <> +  {unexecutedRuleCnt} +  {Pluralize('rule', unexecutedRuleCnt)} {Pluralize('was', unexecutedRuleCnt)} not + checked (no relevant resources for the rule). + + } + return (
props.hideCallback()} className={'scoutsuite-rule-modal'}> @@ -67,9 +76,10 @@ export default function ScoutSuiteRuleModal(props) {

- There {Pluralize('is', props.scoutsuite_rules.length)} { - {props.scoutsuite_rules.length} - } ScoutSuite {Pluralize('rule', props.scoutsuite_rules.length)} associated with this finding. + {getGeneralRuleOverview()} + {getFailedRuleOverview()} + {getPassedRuleOverview()} + {getUnexecutedRuleOverview()}

{renderRuleDropdowns()} diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/scoutsuite/ScoutSuiteSingleRuleDropdown.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/scoutsuite/ScoutSuiteSingleRuleDropdown.js index 72853a1e0..c396066b4 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/scoutsuite/ScoutSuiteSingleRuleDropdown.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/scoutsuite/ScoutSuiteSingleRuleDropdown.js @@ -6,10 +6,10 @@ import {faChevronDown} from '@fortawesome/free-solid-svg-icons/faChevronDown' import classNames from 'classnames'; import * as PropTypes from 'prop-types'; -import RULE_LEVELS from '../../common/consts/ScoutSuiteConsts/RuleLevels'; import STATUSES from '../../common/consts/StatusConsts'; import {faCheckCircle, faCircle, faExclamationCircle} from '@fortawesome/free-solid-svg-icons'; import RuleDisplay from './RuleDisplay'; +import {getRuleStatus} from './rule-parsing/ParsingUtils'; export default function ScoutSuiteSingleRuleDropdown(props) { @@ -70,18 +70,6 @@ export default function ScoutSuiteSingleRuleDropdown(props) { return getRuleCollapse(); } -export function getRuleStatus(rule) { - if (rule.checked_items === 0) { - return STATUSES.STATUS_UNEXECUTED - } else if (rule.items.length === 0) { - return STATUSES.STATUS_PASSED - } else if (rule.level === RULE_LEVELS.LEVEL_WARNING) { - return STATUSES.STATUS_VERIFY - } else { - return STATUSES.STATUS_FAILED - } -} - ScoutSuiteSingleRuleDropdown.propTypes = { isCollapseOpen: PropTypes.bool, diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/scoutsuite/rule-parsing/ParsingUtils.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/scoutsuite/rule-parsing/ParsingUtils.js new file mode 100644 index 000000000..5070797ab --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/scoutsuite/rule-parsing/ParsingUtils.js @@ -0,0 +1,47 @@ +import STATUSES from '../../../common/consts/StatusConsts'; +import RULE_LEVELS from '../../../common/consts/ScoutSuiteConsts/RuleLevels'; + +export function getRuleStatus(rule) { + if (rule.checked_items === 0) { + return STATUSES.STATUS_UNEXECUTED + } else if (rule.items.length === 0) { + return STATUSES.STATUS_PASSED + } else if (rule.level === RULE_LEVELS.LEVEL_WARNING) { + return STATUSES.STATUS_VERIFY + } else { + return STATUSES.STATUS_FAILED + } +} + +export function getRuleCountByStatus(rules, status) { + return rules.filter(rule => getRuleStatus(rule) === status).length; +} + +export function sortRules(rules) { + rules.sort(compareRules); + return rules; +} + +function compareRules(firstRule, secondRule) { + let firstStatus = getRuleStatus(firstRule); + let secondStatus = getRuleStatus(secondRule); + return compareRuleStatuses(firstStatus, secondStatus); +} + +function compareRuleStatuses(ruleStatusOne, ruleStatusTwo) { + if (ruleStatusOne === ruleStatusTwo) { + return 0; + } else if (ruleStatusOne === STATUSES.STATUS_FAILED) { + return -1; + } else if (ruleStatusTwo === STATUSES.STATUS_FAILED) { + return 1; + } else if (ruleStatusOne === STATUSES.STATUS_VERIFY) { + return -1; + } else if (ruleStatusTwo === STATUSES.STATUS_VERIFY) { + return 1; + } else if (ruleStatusOne === STATUSES.STATUS_PASSED) { + return -1; + } else if (ruleStatusTwo === STATUSES.STATUS_PASSED) { + return 1; + } +} From 7b60d4d2e60ad4e4e8c04c51a5d34c1353a49f86 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 13 Jan 2021 11:03:13 +0200 Subject: [PATCH 100/152] Refactored ScoutSuiteDataParser.js to improve the readability of scoutsuite data extraction process temp --- .../zerotrust/scoutsuite/RuleDisplay.js | 30 ++-- .../scoutsuite/ScoutSuiteDataParser.js | 132 +++++++++++------- 2 files changed, 99 insertions(+), 63 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/scoutsuite/RuleDisplay.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/scoutsuite/RuleDisplay.js index 7b4e8f3bd..ac267e193 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/scoutsuite/RuleDisplay.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/scoutsuite/RuleDisplay.js @@ -17,8 +17,8 @@ export default function RuleDisplay(props) {

Resources checked:

{props.rule.checked_items}

- {props.rule.references.length !== 0 ? getReferences() : ''} - {props.rule.items.length !== 0 ? getResources() : ''} + {getReferences()} + {getResources()} ); function getReferences() { @@ -30,11 +30,13 @@ export default function RuleDisplay(props) { rel="noopener noreferrer" key={reference}>{reference}) }) - return ( -
-

References:

- {references} -
) + if (references.length) { + return ( +
+

References:

+ {references} +
) + } } function getResources() { @@ -46,13 +48,15 @@ export default function RuleDisplay(props) { resources.push() + key={template_path + i}/>) + } + if (resources.length) { + return ( +
+

Flagged resources ({props.rule.flagged_items}):

+ {resources} +
) } - return ( -
-

Flagged resources ({props.rule.flagged_items}):

- {resources} -
) } } diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/scoutsuite/ScoutSuiteDataParser.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/scoutsuite/ScoutSuiteDataParser.js index 452a9e321..729499dec 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/scoutsuite/ScoutSuiteDataParser.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/scoutsuite/ScoutSuiteDataParser.js @@ -3,72 +3,104 @@ export default class ScoutSuiteDataParser { this.runResults = runResults } - /* - itemPath contains path to a specific value e.g. s3.buckets.da1e7081077ce92.secure_transport_enabled" - templatePath contains a template path for resource we would want to display e.g. s3.buckets.id + /** + * + * @param itemPath contains path to a specific value e.g. s3.buckets.da1e7081077ce92.secure_transport_enabled + * @param templatePath contains a template path for resource we would want to display e.g. s3.buckets.id + * @returns {*[]|*} */ getResourceValue(itemPath, templatePath) { let resourcePath = this.fillTemplatePath(itemPath, templatePath); - return this.getValueAt(resourcePath); + return this.getObjectValueByPath(resourcePath, this.runResults); } fillTemplatePath(itemPath, templatePath) { let itemPathArray = itemPath.split('.'); let templatePathArray = templatePath.split('.'); - let resourcePathArray = templatePathArray.map((val, i) => {return val === 'id' ? itemPathArray[i] : val}) + let resourcePathArray = templatePathArray.map((val, i) => { + return val === 'id' ? itemPathArray[i] : val + }) return resourcePathArray.join('.'); } - getValueAt(path) { - return this.getValueAtRecursive(path, this.runResults) + /** + * Retrieves value from ScoutSuite data object based on path, provided in the rule + * @param path E.g. a.id.c.id.e + * @param source E.g. {a: {b: {c: {d: {e: [{result1: 'result1'}, {result2: 'result2'}]}}}}} + * @returns {*[]|*} E.g. ['result1', 'result2'] + */ + getObjectValueByPath(path, source) { + let key; + + while (path) { + key = this.getNextKeyInPath(path); + source = this.getValueForKey(key, path, source); + path = this.trimFirstKey(path); + } + + return source; } - getValueAtRecursive(path, source) { - let value = source; - let current_path = path; - let key; - // iterate over each path elements - while (current_path) { - // check if there are more elements to the path - if (current_path.indexOf('.') != -1) { - key = current_path.substr(0, current_path.indexOf('.')); - } - // last element - else { - key = current_path; - } + getNextKeyInPath(path) { + if (path.indexOf('.') !== -1) { + return path.substr(0, path.indexOf('.')); + } else { + return path; + } + } - try { - // path containing an ".id" - if (key == 'id') { - let v = []; - let w; - for (let k in value) { - // process recursively - w = this.getValueAtRecursive(k + current_path.substr(current_path.indexOf('.'), current_path.length), value); - v = v.concat( - Object.values(w) // get values from array, otherwise it will be an array of key/values - ); - } - return v; - } - // simple path, just return element in value - else { - value = value[key]; - } - } catch (err) { - console.log('Error: ' + err) - } + /** + * Returns value from object, based on path and current key + * @param key E.g. "a" + * @param path E.g. "a.b.c" + * @param source E.g. {a: {b: {c: 'result'}}} + * @returns {[]|*} E.g. {b: {c: 'result'}} + */ + getValueForKey(key, path, source) { + if (key === 'id') { + return this.getValueByReplacingUnknownKey(path, source); + } else { + return source[key]; + } + } - // check if there are more elements to process - if (current_path.indexOf('.') != -1) { - current_path = current_path.substr(current_path.indexOf('.') + 1, current_path.length); - } - // otherwise we're done - else { - current_path = false; - } + /** + * Gets value from object if first key in path doesn't match source object + * @param path unknown.b.c + * @param source {a: {b: {c: [{result:'result'}]}}} + * @returns {[]} 'result' + */ + getValueByReplacingUnknownKey(path, source) { + let value = []; + for (let key in source) { + value = this.getObjectValueByPath(this.replaceFirstKey(path, key), source); + value = value.concat(Object.values(value)); } return value; } + + /** + * Replaces first key in path + * @param path E.g. "one.two.three" + * @param replacement E.g. "four" + * @returns string E.g. "four.two.three" + */ + replaceFirstKey(path, replacement) { + return replacement + path.substr(path.indexOf('.'), path.length); + } + + /** + * Trims the first key from dot separated path. + * @param path E.g. "one.two.three" + * @returns {string|boolean} E.g. "two.three" + */ + trimFirstKey(path) { + if (path.indexOf('.') !== -1) { + return path.substr(path.indexOf('.') + 1, path.length); + } else { + return false; + } + } + + } From b90f6587c1311c18195d8c9a4a7e469fd962e347 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 13 Jan 2021 16:52:23 +0200 Subject: [PATCH 101/152] Reverted resource value display to show "False", because for IAM rules it makes sense and expresses if it the rule is enabled or not. --- .../zerotrust/scoutsuite/ResourceDropdown.js | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/scoutsuite/ResourceDropdown.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/scoutsuite/ResourceDropdown.js index 001aa4ef7..81aee324e 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/scoutsuite/ResourceDropdown.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/scoutsuite/ResourceDropdown.js @@ -43,7 +43,7 @@ export default function ResourceDropdown(props) { for (let i = 0; i < path_vars.length; i++) { display_path.push(path_vars[i]) if (i !== path_vars.length - 1) { - display_path.push() + display_path.push() } } return display_path; @@ -54,16 +54,12 @@ export default function ResourceDropdown(props) { } function getResourceValueDisplay() { - if (resource_value) { - return ( -
-

Value:

-
{prettyPrintJson(resource_value)}
-
- ); - } else { - return ''; - } + return ( +
+

Value:

+
{prettyPrintJson(resource_value)}
+
+ ); } function getResourceDropdownContents() { From 87dafeb440bf2924f616675c1e5e47ce61e3c12f Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 13 Jan 2021 17:57:54 +0200 Subject: [PATCH 102/152] Refactored scoutsuite rule count badge readability. --- .../zerotrust/scoutsuite/ScoutSuiteRuleButton.js | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/scoutsuite/ScoutSuiteRuleButton.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/scoutsuite/ScoutSuiteRuleButton.js index 97c5f861a..c22c54a96 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/scoutsuite/ScoutSuiteRuleButton.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/scoutsuite/ScoutSuiteRuleButton.js @@ -27,18 +27,27 @@ export default class ScoutSuiteRuleButton extends Component { hideCallback={this.toggleModal} />
); } createRuleCountBadge() { - const ruleCount = this.props.scoutsuite_rules.length > 9 ? '9+' : this.props.scoutsuite_rules.length; - return {ruleCount}; + } } +function RuleCountBadge(props) { + const maxRuleCountToShow = 9; + const textForMoreThanMaxRuleCount = maxRuleCountToShow + '+'; + + const ruleCountText = props.count > maxRuleCountToShow ? + textForMoreThanMaxRuleCount : props.count; + return {ruleCountText}; +} + ScoutSuiteRuleButton.propTypes = { scoutsuite_rules: PropTypes.array, scoutsuite_data: PropTypes.object From 2dfcbb49d44e73997da6a49d7437822ec435903f Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Thu, 14 Jan 2021 11:59:57 +0200 Subject: [PATCH 103/152] Minor refactoring and typo fix --- .../zero_trust/scoutsuite/consts/service_consts.py | 2 +- .../rule_path_creators/cloudtrail_rule_path_creator.py | 2 +- .../zerotrust/scoutsuite/ScoutSuiteRuleModal.js | 7 ++----- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/service_consts.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/service_consts.py index e3cd005b9..ac5209b45 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/service_consts.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/service_consts.py @@ -8,7 +8,7 @@ class SERVICE_TYPES(Enum): ACM = 'acm' AWSLAMBDA = 'awslambda' CLOUDFORMATION = 'cloudformation' - CLOUSDTRAIL = 'cloudtrail' + CLOUDTRAIL = 'cloudtrail' CLOUDWATCH = 'cloudwatch' CONFIG = 'config' DIRECTCONNECT = 'directconnect' diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/cloudtrail_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/cloudtrail_rule_path_creator.py index 1a8336629..2f626dfd5 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/cloudtrail_rule_path_creator.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/cloudtrail_rule_path_creator.py @@ -6,5 +6,5 @@ from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_buil class CloudTrailRulePathCreator(AbstractRulePathCreator): - service_type = SERVICE_TYPES.CLOUSDTRAIL + service_type = SERVICE_TYPES.CLOUDTRAIL supported_rules = CloudTrailRules diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/scoutsuite/ScoutSuiteRuleModal.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/scoutsuite/ScoutSuiteRuleModal.js index b7f212278..fd7fa3851 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/scoutsuite/ScoutSuiteRuleModal.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/scoutsuite/ScoutSuiteRuleModal.js @@ -12,11 +12,8 @@ export default function ScoutSuiteRuleModal(props) { const [openRuleId, setOpenRuleId] = useState(null) function toggleRuleDropdown(ruleId) { - if (openRuleId === ruleId) { - setOpenRuleId(null); - } else { - setOpenRuleId(ruleId); - } + let ruleIdToSet = (openRuleId === ruleId) ? null : ruleId; + setOpenRuleId(ruleIdToSet); } function renderRuleDropdowns() { From cd9d2904c5293db76805a8ab9d6760788794a723 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Thu, 14 Jan 2021 12:02:38 +0200 Subject: [PATCH 104/152] Added comment explaining why finding details are in a separate documents to discourage uninformed refactoring in the future --- monkey/monkey_island/cc/models/zero_trust/finding.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/monkey/monkey_island/cc/models/zero_trust/finding.py b/monkey/monkey_island/cc/models/zero_trust/finding.py index 4b3093545..b9aefb42a 100644 --- a/monkey/monkey_island/cc/models/zero_trust/finding.py +++ b/monkey/monkey_island/cc/models/zero_trust/finding.py @@ -37,6 +37,9 @@ class Finding(Document): test = StringField(required=True, choices=zero_trust_consts.TESTS) status = StringField(required=True, choices=zero_trust_consts.ORDERED_TEST_STATUSES) finding_type = StringField(required=True, choices=zero_trust_consts.FINDING_TYPES) + + # Details are in a separate document in order to discourage pulling them when not needed + # due to performance. details = GenericLazyReferenceField(choices=[MonkeyFindingDetails, ScoutSuiteFindingDetails], required=True) # http://docs.mongoengine.org/guide/defining-documents.html#document-inheritance meta = {'allow_inheritance': True} From a818025f63935955597a8d494158712a0bc347fe Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Thu, 14 Jan 2021 12:05:13 +0200 Subject: [PATCH 105/152] Typo fix in service_consts.py --- .../cc/services/zero_trust/scoutsuite/consts/service_consts.py | 2 +- .../rule_path_creators/elbv2_rule_path_creator.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/service_consts.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/service_consts.py index ac5209b45..a31c83d3e 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/service_consts.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/service_consts.py @@ -16,7 +16,7 @@ class SERVICE_TYPES(Enum): EFS = 'efs' ELASTICACHE = 'elasticache' ELB = 'elb' - ELBv2 = 'elbv2' + ELB_V2 = 'elbv2' EMR = 'emr' IAM = 'iam' KMS = 'kms' diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/elbv2_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/elbv2_rule_path_creator.py index b268a5a58..2472bf076 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/elbv2_rule_path_creator.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/elbv2_rule_path_creator.py @@ -6,5 +6,5 @@ from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_buil class ELBv2RulePathCreator(AbstractRulePathCreator): - service_type = SERVICE_TYPES.ELBv2 + service_type = SERVICE_TYPES.ELB_V2 supported_rules = ELBv2Rules From 761ed2ec43e94eba1bbd76d16cf923855f5f7329 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Thu, 14 Jan 2021 12:17:34 +0200 Subject: [PATCH 106/152] Refactored code of rule ordering --- .../scoutsuite/rule-parsing/ParsingUtils.js | 21 +++++++------------ 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/scoutsuite/rule-parsing/ParsingUtils.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/scoutsuite/rule-parsing/ParsingUtils.js index 5070797ab..da1417d1b 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/scoutsuite/rule-parsing/ParsingUtils.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/scoutsuite/rule-parsing/ParsingUtils.js @@ -29,19 +29,12 @@ function compareRules(firstRule, secondRule) { } function compareRuleStatuses(ruleStatusOne, ruleStatusTwo) { - if (ruleStatusOne === ruleStatusTwo) { - return 0; - } else if (ruleStatusOne === STATUSES.STATUS_FAILED) { - return -1; - } else if (ruleStatusTwo === STATUSES.STATUS_FAILED) { - return 1; - } else if (ruleStatusOne === STATUSES.STATUS_VERIFY) { - return -1; - } else if (ruleStatusTwo === STATUSES.STATUS_VERIFY) { - return 1; - } else if (ruleStatusOne === STATUSES.STATUS_PASSED) { - return -1; - } else if (ruleStatusTwo === STATUSES.STATUS_PASSED) { - return 1; + const severity_order = { + [STATUSES.STATUS_FAILED]: 1, + [STATUSES.STATUS_VERIFY]: 2, + [STATUSES.STATUS_PASSED]: 3, + [STATUSES.STATUS_UNEXECUTED]: 4 } + + return severity_order[ruleStatusOne] - severity_order[ruleStatusTwo] } From 22194c566acec0f1d545d46055ec358596921dad Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Thu, 14 Jan 2021 13:01:25 +0200 Subject: [PATCH 107/152] Refactored aws access keys in config, added them to encrypted parameter list and added ScoutSuite specific exception --- monkey/common/utils/exceptions.py | 4 ++++ monkey/infection_monkey/config.py | 9 +++++---- .../scoutsuite_collector/scoutsuite_collector.py | 11 ++++++----- monkey/monkey_island/cc/services/config.py | 6 +++--- .../cc/services/config_schema/internal.py | 6 +++--- .../scoutsuite/scoutsuite_auth_service.py | 16 ++++++++-------- 6 files changed, 29 insertions(+), 23 deletions(-) diff --git a/monkey/common/utils/exceptions.py b/monkey/common/utils/exceptions.py index 27d128e88..b1811ab88 100644 --- a/monkey/common/utils/exceptions.py +++ b/monkey/common/utils/exceptions.py @@ -32,3 +32,7 @@ class InvalidAWSKeys(Exception): class NoInternetError(Exception): """ Raise to indicate problems caused when no internet connection is present""" + + +class ScoutSuiteScanError(Exception): + """ Raise to indicate problems ScoutSuite encountered during scanning""" diff --git a/monkey/infection_monkey/config.py b/monkey/infection_monkey/config.py index add35e39e..018f3aacc 100644 --- a/monkey/infection_monkey/config.py +++ b/monkey/infection_monkey/config.py @@ -11,7 +11,8 @@ GUID = str(uuid.getnode()) EXTERNAL_CONFIG_FILE = os.path.join(os.path.abspath(os.path.dirname(sys.argv[0])), 'monkey.bin') -SENSITIVE_FIELDS = ["exploit_password_list", "exploit_user_list", "exploit_ssh_keys"] +SENSITIVE_FIELDS = ["exploit_password_list", "exploit_user_list", "exploit_ssh_keys", "aws_secret_access_key", + "aws_session_token"] LOCAL_CONFIG_VARS = ["name", "id", "current_server", "max_depth"] HIDDEN_FIELD_REPLACEMENT_CONTENT = "hidden" @@ -245,9 +246,9 @@ class Configuration(object): exploit_ntlm_hash_list = [] exploit_ssh_keys = [] - access_key_id = '' - secret_access_key = '' - session_token = '' + aws_access_key_id = '' + aws_secret_access_key = '' + aws_session_token = '' # smb/wmi exploiter smb_download_timeout = 300 # timeout in seconds diff --git a/monkey/infection_monkey/system_info/collectors/scoutsuite_collector/scoutsuite_collector.py b/monkey/infection_monkey/system_info/collectors/scoutsuite_collector/scoutsuite_collector.py index acf63104a..25b0ea833 100644 --- a/monkey/infection_monkey/system_info/collectors/scoutsuite_collector/scoutsuite_collector.py +++ b/monkey/infection_monkey/system_info/collectors/scoutsuite_collector/scoutsuite_collector.py @@ -2,6 +2,7 @@ import logging import infection_monkey.system_info.collectors.scoutsuite_collector.scoutsuite_api as scoutsuite_api from common.cloud.scoutsuite_consts import CloudProviders +from common.utils.exceptions import ScoutSuiteScanError from infection_monkey.config import WormConfiguration from infection_monkey.telemetry.scoutsuite_telem import ScoutSuiteTelem @@ -12,17 +13,17 @@ def scan_cloud_security(cloud_type: CloudProviders): try: results = run_scoutsuite(cloud_type.value) if isinstance(results, dict) and 'error' in results and results['error']: - raise Exception(results['error']) + raise ScoutSuiteScanError(results['error']) send_results(results) - except Exception as e: + except (Exception, ScoutSuiteScanError) as e: logger.error(f"ScoutSuite didn't scan {cloud_type.value} security because: {e}") def run_scoutsuite(cloud_type: str): return scoutsuite_api.run(provider=cloud_type, - aws_access_key_id=WormConfiguration.access_key_id, - aws_secret_access_key=WormConfiguration.secret_access_key, - aws_session_token=WormConfiguration.session_token) + aws_access_key_id=WormConfiguration.aws_access_key_id, + aws_secret_access_key=WormConfiguration.aws_secret_access_key, + aws_session_token=WormConfiguration.aws_session_token) def send_results(results): diff --git a/monkey/monkey_island/cc/services/config.py b/monkey/monkey_island/cc/services/config.py index 9b2f0ba9b..b4370a63b 100644 --- a/monkey/monkey_island/cc/services/config.py +++ b/monkey/monkey_island/cc/services/config.py @@ -28,9 +28,9 @@ ENCRYPTED_CONFIG_VALUES = \ LM_HASH_LIST_PATH, NTLM_HASH_LIST_PATH, SSH_KEYS_PATH, - AWS_KEYS_PATH + ['access_key_id'], - AWS_KEYS_PATH + ['secret_access_key'], - AWS_KEYS_PATH + ['session_token'] + AWS_KEYS_PATH + ['aws_access_key_id'], + AWS_KEYS_PATH + ['aws_secret_access_key'], + AWS_KEYS_PATH + ['aws_session_token'] ] diff --git a/monkey/monkey_island/cc/services/config_schema/internal.py b/monkey/monkey_island/cc/services/config_schema/internal.py index b571d31ab..156dae7ad 100644 --- a/monkey/monkey_island/cc/services/config_schema/internal.py +++ b/monkey/monkey_island/cc/services/config_schema/internal.py @@ -98,15 +98,15 @@ INTERNAL = { "aws_keys": { "type": "object", "properties": { - "access_key_id": { + "aws_access_key_id": { "type": "string", "default": "" }, - "secret_access_key": { + "aws_secret_access_key": { "type": "string", "default": "" }, - "session_token": { + "aws_session_token": { "type": "string", "default": "" } diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_auth_service.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_auth_service.py index 308a6e967..e9a965a69 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_auth_service.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_auth_service.py @@ -35,16 +35,16 @@ def is_cloud_authentication_setup(provider: CloudProviders) -> Tuple[bool, str]: def is_aws_keys_setup(): - return (ConfigService.get_config_value(AWS_KEYS_PATH + ['access_key_id']) and - ConfigService.get_config_value(AWS_KEYS_PATH + ['secret_access_key'])) + return (ConfigService.get_config_value(AWS_KEYS_PATH + ['aws_access_key_id']) and + ConfigService.get_config_value(AWS_KEYS_PATH + ['aws_secret_access_key'])) def set_aws_keys(access_key_id: str, secret_access_key: str, session_token: str): if not access_key_id or not secret_access_key: raise InvalidAWSKeys("Missing some of the following fields: access key ID, secret access key.") - _set_aws_key('access_key_id', access_key_id) - _set_aws_key('secret_access_key', secret_access_key) - _set_aws_key('session_token', session_token) + _set_aws_key('aws_access_key_id', access_key_id) + _set_aws_key('aws_secret_access_key', secret_access_key) + _set_aws_key('aws_session_token', session_token) def _set_aws_key(key_type: str, key_value: str): @@ -54,9 +54,9 @@ def _set_aws_key(key_type: str, key_value: str): def get_aws_keys(): - return {'access_key_id': _get_aws_key('access_key_id'), - 'secret_access_key': _get_aws_key('secret_access_key'), - 'session_token': _get_aws_key('session_token')} + return {'access_key_id': _get_aws_key('aws_access_key_id'), + 'secret_access_key': _get_aws_key('aws_secret_access_key'), + 'session_token': _get_aws_key('aws_session_token')} def _get_aws_key(key_type: str): From de69d167ba33594fb95cef6d79b4fd10d029d6b8 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Thu, 14 Jan 2021 16:05:52 +0200 Subject: [PATCH 108/152] Minor scoutsuite code refactorings --- .../telemetry/scoutsuite_telem.py | 2 +- .../services/zero_trust/scoutsuite/__init__.py | 13 +++++++++++++ .../scoutsuite/scoutsuite_auth_service.py | 17 +++-------------- .../AWSConfiguration/AWSKeySetup.js | 9 ++++++--- .../src/components/ui-components/ImageModal.js | 2 +- 5 files changed, 24 insertions(+), 19 deletions(-) create mode 100644 monkey/monkey_island/cc/services/zero_trust/scoutsuite/__init__.py diff --git a/monkey/infection_monkey/telemetry/scoutsuite_telem.py b/monkey/infection_monkey/telemetry/scoutsuite_telem.py index 7c3f94653..816042d7c 100644 --- a/monkey/infection_monkey/telemetry/scoutsuite_telem.py +++ b/monkey/infection_monkey/telemetry/scoutsuite_telem.py @@ -8,7 +8,7 @@ class ScoutSuiteTelem(BaseTelem): def __init__(self, data): """ Default ScoutSuite telemetry constructor - :param data: Data gathered via ScoutSuite ( + :param data: Data gathered via ScoutSuite """ super().__init__() self.data = data diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/__init__.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/__init__.py new file mode 100644 index 000000000..e8a36338b --- /dev/null +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/__init__.py @@ -0,0 +1,13 @@ +import pkgutil +import sys +from pathlib import PurePath + +_scoutsuite_api_package = pkgutil.get_loader('common.cloud.scoutsuite.ScoutSuite.__main__') + + +def _add_scoutsuite_to_python_path(): + scoutsuite_path = PurePath(_scoutsuite_api_package.path).parent.parent.__str__() + sys.path.append(scoutsuite_path) + + +_add_scoutsuite_to_python_path() diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_auth_service.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_auth_service.py index e9a965a69..eb0d5dfbd 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_auth_service.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_auth_service.py @@ -1,24 +1,13 @@ -import pkgutil -import sys -from pathlib import PurePath from typing import Tuple +from ScoutSuite.providers.base.authentication_strategy import AuthenticationException + from common.cloud.scoutsuite_consts import CloudProviders from common.utils.exceptions import InvalidAWSKeys from monkey_island.cc.server_utils.encryptor import encryptor from monkey_island.cc.services.config import ConfigService from monkey_island.cc.services.config_schema.config_value_paths import AWS_KEYS_PATH -_scoutsuite_api_package = pkgutil.get_loader('common.cloud.scoutsuite.ScoutSuite.__main__') - - -def _add_scoutsuite_to_python_path(): - scoutsuite_path = PurePath(_scoutsuite_api_package.path).parent.parent.__str__() - sys.path.append(scoutsuite_path) - - -_add_scoutsuite_to_python_path() - def is_cloud_authentication_setup(provider: CloudProviders) -> Tuple[bool, str]: if provider == CloudProviders.AWS.value: @@ -30,7 +19,7 @@ def is_cloud_authentication_setup(provider: CloudProviders) -> Tuple[bool, str]: profile = auth_strategy.AWSAuthenticationStrategy().authenticate() return True, f" Profile \"{profile.session.profile_name}\" is already setup. " \ f"Run Monkey on Island to start the scan." - except Exception: + except AuthenticationException: return False, "" diff --git a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/scoutsuite-setup/AWSConfiguration/AWSKeySetup.js b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/scoutsuite-setup/AWSConfiguration/AWSKeySetup.js index 05ae86f89..04a1f490b 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/scoutsuite-setup/AWSConfiguration/AWSKeySetup.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/scoutsuite-setup/AWSConfiguration/AWSKeySetup.js @@ -56,6 +56,8 @@ const getContents = (props) => { .then(res => { if (res['error_msg'] === '') { setSuccessMessage('AWS keys saved!'); + } else if (res['message'] === 'Internal Server Error') { + setErrorMessage('Something went wrong, double check keys and contact support if problem persists.'); } else { setErrorMessage(res['error_msg']); } @@ -79,11 +81,12 @@ const getContents = (props) => {
Tips

Consider creating a new user account just for this activity. Assign only ReadOnlyAccess and  - SecurityAudit policies.

+ SecurityAudit policies.

Keys for custom user

1. Open the IAM console at https://console.aws.amazon.com/iam/.

+ target={'_blank'} + rel="noopener noreferrer">https://console.aws.amazon.com/iam/.

2. In the navigation pane, choose Users.

3. Choose the name of the user whose access keys you want to create, and then choose the Security credentials tab.

@@ -157,7 +160,7 @@ const getContents = (props) => { { successMessage ?
{successMessage}  - Go back and  + Go back and  to start AWS scan!
: diff --git a/monkey/monkey_island/cc/ui/src/components/ui-components/ImageModal.js b/monkey/monkey_island/cc/ui/src/components/ui-components/ImageModal.js index 12632e811..bd012944b 100644 --- a/monkey/monkey_island/cc/ui/src/components/ui-components/ImageModal.js +++ b/monkey/monkey_island/cc/ui/src/components/ui-components/ImageModal.js @@ -29,5 +29,5 @@ const ImageModal = (props) => { export default ImageModal; ImageModal.propTypes = { - image: PropTypes.element + image: PropTypes.string } From e79290e76170168b7153de247dfd9a0dabb5b77a Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Fri, 15 Jan 2021 11:20:57 +0200 Subject: [PATCH 109/152] Refactored scoutsuite rule button from "ScoutSuite rules" to just "Rules" to look more consistent with "Events" button --- .../src/components/report-components/zerotrust/FindingsTable.js | 2 +- .../zerotrust/scoutsuite/ScoutSuiteRuleButton.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/FindingsTable.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/FindingsTable.js index d7a86c9f9..efd5a6ac4 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/FindingsTable.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/FindingsTable.js @@ -6,7 +6,7 @@ import PillarLabel from './PillarLabel'; import EventsButton from './EventsButton'; import ScoutSuiteRuleButton from './scoutsuite/ScoutSuiteRuleButton'; -const EVENTS_COLUMN_MAX_WIDTH = 250; +const EVENTS_COLUMN_MAX_WIDTH = 180; const PILLARS_COLUMN_MAX_WIDTH = 260; diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/scoutsuite/ScoutSuiteRuleButton.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/scoutsuite/ScoutSuiteRuleButton.js index c22c54a96..316f2f90b 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/scoutsuite/ScoutSuiteRuleButton.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/scoutsuite/ScoutSuiteRuleButton.js @@ -27,7 +27,7 @@ export default class ScoutSuiteRuleButton extends Component { hideCallback={this.toggleModal} />
From 85f4c4f2506c64d6e3602b442dedc299eac97988 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Fri, 15 Jan 2021 11:22:40 +0200 Subject: [PATCH 110/152] Small ScoutSuite feature code style refactorings --- monkey/common/utils/exceptions.py | 4 ++++ .../cc/services/zero_trust/finding_service.py | 8 +++++--- .../cc/services/zero_trust/monkey_details_service.py | 2 +- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/monkey/common/utils/exceptions.py b/monkey/common/utils/exceptions.py index b1811ab88..333db5d60 100644 --- a/monkey/common/utils/exceptions.py +++ b/monkey/common/utils/exceptions.py @@ -36,3 +36,7 @@ class NoInternetError(Exception): class ScoutSuiteScanError(Exception): """ Raise to indicate problems ScoutSuite encountered during scanning""" + + +class UnknownFindingError(Exception): + """ Raise when provided finding is of unknown type""" diff --git a/monkey/monkey_island/cc/services/zero_trust/finding_service.py b/monkey/monkey_island/cc/services/zero_trust/finding_service.py index 618be9550..02459eb0e 100644 --- a/monkey/monkey_island/cc/services/zero_trust/finding_service.py +++ b/monkey/monkey_island/cc/services/zero_trust/finding_service.py @@ -1,6 +1,7 @@ from typing import List from common.common_consts import zero_trust_consts +from common.utils.exceptions import UnknownFindingError from monkey_island.cc.models.zero_trust.finding import Finding from monkey_island.cc.services.zero_trust.monkey_details_service import MonkeyDetailsService @@ -10,19 +11,20 @@ class FindingService: @staticmethod def get_all_findings() -> List[Finding]: findings = list(Finding.objects) - details = [] for i in range(len(findings)): if findings[i].finding_type == zero_trust_consts.MONKEY_FINDING: details = MonkeyDetailsService.fetch_details_for_display(findings[i].details.id) elif findings[i].finding_type == zero_trust_consts.SCOUTSUITE_FINDING: details = findings[i].details.fetch().to_mongo() + else: + raise UnknownFindingError(f"Unknown finding type {findings[i].finding_type}") findings[i] = findings[i].to_mongo() - findings[i] = FindingService.get_enriched_finding(findings[i]) + findings[i] = FindingService._get_enriched_finding(findings[i]) findings[i]['details'] = details return findings @staticmethod - def get_enriched_finding(finding): + def _get_enriched_finding(finding): test_info = zero_trust_consts.TESTS_MAP[finding['test']] enriched_finding = { 'finding_id': str(finding['_id']), diff --git a/monkey/monkey_island/cc/services/zero_trust/monkey_details_service.py b/monkey/monkey_island/cc/services/zero_trust/monkey_details_service.py index 623285ddd..5332ed90b 100644 --- a/monkey/monkey_island/cc/services/zero_trust/monkey_details_service.py +++ b/monkey/monkey_island/cc/services/zero_trust/monkey_details_service.py @@ -12,7 +12,7 @@ EVENT_FETCH_CNT = 50 class MonkeyDetailsService: @staticmethod - def fetch_details_for_display(finding_id: ObjectId): + def fetch_details_for_display(finding_id: ObjectId) -> dict: pipeline = [{'$match': {'_id': finding_id}}, {'$addFields': {'oldest_events': {'$slice': ['$events', EVENT_FETCH_CNT]}, 'latest_events': {'$slice': ['$events', -1 * EVENT_FETCH_CNT]}, From 02a45c7449c0ee92b807c49804f9965a2447b333 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Fri, 15 Jan 2021 15:08:49 +0200 Subject: [PATCH 111/152] Moved and renamed some services to improve directory structure of zero trust services --- .../cc/models/zero_trust/scoutsuite_rule.py | 2 +- .../zero_trust/test_aggregate_finding.py | 10 +++--- .../cc/resources/zero_trust/finding_event.py | 4 +-- .../zero_trust/scoutsuite_auth/aws_keys.py | 2 +- .../scoutsuite_auth/scoutsuite_auth.py | 4 +-- .../telemetry/processing/scoutsuite.py | 10 +++--- .../zero_trust_checks/antivirus_existence.py | 6 ++-- .../communicate_as_new_user.py | 9 ++--- .../zero_trust_checks/data_endpoints.py | 12 +++---- .../zero_trust_checks/machine_exploited.py | 8 ++--- .../zero_trust_checks/segmentation.py | 6 ++-- .../zero_trust_checks/test_segmentation.py | 4 +-- .../telemetry/zero_trust_checks/tunneling.py | 8 ++--- .../zero_trust/monkey_findings/__init__.py | 0 .../monkey_zt_details_service.py} | 6 ++-- .../monkey_zt_finding_service.py} | 10 +++--- .../scoutsuite/consts/findings_list.py | 8 ----- .../cloudformation_rule_path_creator.py | 10 ------ .../cloudtrail_rule_path_creator.py | 10 ------ .../cloudwatch_rule_path_creator.py | 10 ------ .../config_rule_path_creator.py | 10 ------ .../ec2_rule_path_creator.py | 10 ------ .../elb_rule_path_creator.py | 10 ------ .../elbv2_rule_path_creator.py | 10 ------ .../iam_rule_path_creator.py | 10 ------ .../rds_rule_path_creator.py | 10 ------ .../redshift_rule_path_creator.py | 10 ------ .../s3_rule_path_creator.py | 10 ------ .../ses_rule_path_creator.py | 10 ------ .../sns_rule_path_creator.py | 10 ------ .../sqs_rule_path_creator.py | 10 ------ .../vpc_rule_path_creator.py | 10 ------ .../rule_path_creators_list.py | 35 ------------------- .../__init__.py | 0 .../consts/findings.py | 30 ++++++++-------- .../consts/findings_list.py | 8 +++++ .../consts/rule_consts.py | 0 .../consts/rule_names/cloudformation_rules.py | 0 .../consts/rule_names/cloudtrail_rules.py | 0 .../consts/rule_names/cloudwatch_rules.py | 0 .../consts/rule_names/config_rules.py | 0 .../consts/rule_names/ec2_rules.py | 0 .../consts/rule_names/elb_rules.py | 0 .../consts/rule_names/elbv2_rules.py | 0 .../consts/rule_names/iam_rules.py | 0 .../consts/rule_names/rds_rules.py | 0 .../consts/rule_names/redshift_rules.py | 0 .../consts/rule_names/s3_rules.py | 0 .../consts/rule_names/ses_rules.py | 0 .../consts/rule_names/sns_rules.py | 0 .../consts/rule_names/sqs_rules.py | 0 .../consts/rule_names/vpc_rules.py | 0 .../consts/service_consts.py | 0 .../data_parsing/rule_parser.py | 2 +- .../abstract_rule_path_creator.py | 2 +- .../cloudformation_rule_path_creator.py | 9 +++++ .../cloudtrail_rule_path_creator.py | 9 +++++ .../cloudwatch_rule_path_creator.py | 9 +++++ .../config_rule_path_creator.py | 9 +++++ .../ec2_rule_path_creator.py | 9 +++++ .../elb_rule_path_creator.py | 9 +++++ .../elbv2_rule_path_creator.py | 9 +++++ .../iam_rule_path_creator.py | 9 +++++ .../rds_rule_path_creator.py | 9 +++++ .../redshift_rule_path_creator.py | 9 +++++ .../s3_rule_path_creator.py | 9 +++++ .../ses_rule_path_creator.py | 9 +++++ .../sns_rule_path_creator.py | 9 +++++ .../sqs_rule_path_creator.py | 9 +++++ .../vpc_rule_path_creator.py | 9 +++++ .../rule_path_creators_list.py | 35 +++++++++++++++++++ .../scoutsuite_auth_service.py | 0 .../scoutsuite_rule_service.py | 2 +- .../scoutsuite_zt_finding_service.py} | 18 +++++----- ...rvice.py => zero_trust_finding_service.py} | 10 +++--- 75 files changed, 261 insertions(+), 275 deletions(-) create mode 100644 monkey/monkey_island/cc/services/zero_trust/monkey_findings/__init__.py rename monkey/monkey_island/cc/services/zero_trust/{monkey_details_service.py => monkey_findings/monkey_zt_details_service.py} (87%) rename monkey/monkey_island/cc/services/zero_trust/{monkey_finding_service.py => monkey_findings/monkey_zt_finding_service.py} (82%) delete mode 100644 monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/findings_list.py delete mode 100644 monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/cloudformation_rule_path_creator.py delete mode 100644 monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/cloudtrail_rule_path_creator.py delete mode 100644 monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/cloudwatch_rule_path_creator.py delete mode 100644 monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/config_rule_path_creator.py delete mode 100644 monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/ec2_rule_path_creator.py delete mode 100644 monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/elb_rule_path_creator.py delete mode 100644 monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/elbv2_rule_path_creator.py delete mode 100644 monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/iam_rule_path_creator.py delete mode 100644 monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/rds_rule_path_creator.py delete mode 100644 monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/redshift_rule_path_creator.py delete mode 100644 monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/s3_rule_path_creator.py delete mode 100644 monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/ses_rule_path_creator.py delete mode 100644 monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/sns_rule_path_creator.py delete mode 100644 monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/sqs_rule_path_creator.py delete mode 100644 monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/vpc_rule_path_creator.py delete mode 100644 monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators_list.py rename monkey/monkey_island/cc/services/zero_trust/{scoutsuite => scoutsuite_findings}/__init__.py (100%) rename monkey/monkey_island/cc/services/zero_trust/{scoutsuite => scoutsuite_findings}/consts/findings.py (83%) create mode 100644 monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/consts/findings_list.py rename monkey/monkey_island/cc/services/zero_trust/{scoutsuite => scoutsuite_findings}/consts/rule_consts.py (100%) rename monkey/monkey_island/cc/services/zero_trust/{scoutsuite => scoutsuite_findings}/consts/rule_names/cloudformation_rules.py (100%) rename monkey/monkey_island/cc/services/zero_trust/{scoutsuite => scoutsuite_findings}/consts/rule_names/cloudtrail_rules.py (100%) rename monkey/monkey_island/cc/services/zero_trust/{scoutsuite => scoutsuite_findings}/consts/rule_names/cloudwatch_rules.py (100%) rename monkey/monkey_island/cc/services/zero_trust/{scoutsuite => scoutsuite_findings}/consts/rule_names/config_rules.py (100%) rename monkey/monkey_island/cc/services/zero_trust/{scoutsuite => scoutsuite_findings}/consts/rule_names/ec2_rules.py (100%) rename monkey/monkey_island/cc/services/zero_trust/{scoutsuite => scoutsuite_findings}/consts/rule_names/elb_rules.py (100%) rename monkey/monkey_island/cc/services/zero_trust/{scoutsuite => scoutsuite_findings}/consts/rule_names/elbv2_rules.py (100%) rename monkey/monkey_island/cc/services/zero_trust/{scoutsuite => scoutsuite_findings}/consts/rule_names/iam_rules.py (100%) rename monkey/monkey_island/cc/services/zero_trust/{scoutsuite => scoutsuite_findings}/consts/rule_names/rds_rules.py (100%) rename monkey/monkey_island/cc/services/zero_trust/{scoutsuite => scoutsuite_findings}/consts/rule_names/redshift_rules.py (100%) rename monkey/monkey_island/cc/services/zero_trust/{scoutsuite => scoutsuite_findings}/consts/rule_names/s3_rules.py (100%) rename monkey/monkey_island/cc/services/zero_trust/{scoutsuite => scoutsuite_findings}/consts/rule_names/ses_rules.py (100%) rename monkey/monkey_island/cc/services/zero_trust/{scoutsuite => scoutsuite_findings}/consts/rule_names/sns_rules.py (100%) rename monkey/monkey_island/cc/services/zero_trust/{scoutsuite => scoutsuite_findings}/consts/rule_names/sqs_rules.py (100%) rename monkey/monkey_island/cc/services/zero_trust/{scoutsuite => scoutsuite_findings}/consts/rule_names/vpc_rules.py (100%) rename monkey/monkey_island/cc/services/zero_trust/{scoutsuite => scoutsuite_findings}/consts/service_consts.py (100%) rename monkey/monkey_island/cc/services/zero_trust/{scoutsuite => scoutsuite_findings}/data_parsing/rule_parser.py (88%) rename monkey/monkey_island/cc/services/zero_trust/{scoutsuite => scoutsuite_findings}/data_parsing/rule_path_building/abstract_rule_path_creator.py (80%) create mode 100644 monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/data_parsing/rule_path_building/rule_path_creators/cloudformation_rule_path_creator.py create mode 100644 monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/data_parsing/rule_path_building/rule_path_creators/cloudtrail_rule_path_creator.py create mode 100644 monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/data_parsing/rule_path_building/rule_path_creators/cloudwatch_rule_path_creator.py create mode 100644 monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/data_parsing/rule_path_building/rule_path_creators/config_rule_path_creator.py create mode 100644 monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/data_parsing/rule_path_building/rule_path_creators/ec2_rule_path_creator.py create mode 100644 monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/data_parsing/rule_path_building/rule_path_creators/elb_rule_path_creator.py create mode 100644 monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/data_parsing/rule_path_building/rule_path_creators/elbv2_rule_path_creator.py create mode 100644 monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/data_parsing/rule_path_building/rule_path_creators/iam_rule_path_creator.py create mode 100644 monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/data_parsing/rule_path_building/rule_path_creators/rds_rule_path_creator.py create mode 100644 monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/data_parsing/rule_path_building/rule_path_creators/redshift_rule_path_creator.py create mode 100644 monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/data_parsing/rule_path_building/rule_path_creators/s3_rule_path_creator.py create mode 100644 monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/data_parsing/rule_path_building/rule_path_creators/ses_rule_path_creator.py create mode 100644 monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/data_parsing/rule_path_building/rule_path_creators/sns_rule_path_creator.py create mode 100644 monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/data_parsing/rule_path_building/rule_path_creators/sqs_rule_path_creator.py create mode 100644 monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/data_parsing/rule_path_building/rule_path_creators/vpc_rule_path_creator.py create mode 100644 monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/data_parsing/rule_path_building/rule_path_creators_list.py rename monkey/monkey_island/cc/services/zero_trust/{scoutsuite => scoutsuite_findings}/scoutsuite_auth_service.py (100%) rename monkey/monkey_island/cc/services/zero_trust/{scoutsuite => scoutsuite_findings}/scoutsuite_rule_service.py (92%) rename monkey/monkey_island/cc/services/zero_trust/{scoutsuite/scoutsuite_finding_service.py => scoutsuite_findings/scoutsuite_zt_finding_service.py} (75%) rename monkey/monkey_island/cc/services/zero_trust/{finding_service.py => zero_trust_finding_service.py} (77%) diff --git a/monkey/monkey_island/cc/models/zero_trust/scoutsuite_rule.py b/monkey/monkey_island/cc/models/zero_trust/scoutsuite_rule.py index dee49983a..4fa37faf6 100644 --- a/monkey/monkey_island/cc/models/zero_trust/scoutsuite_rule.py +++ b/monkey/monkey_island/cc/models/zero_trust/scoutsuite_rule.py @@ -1,6 +1,6 @@ from mongoengine import DynamicField, EmbeddedDocument, IntField, ListField, StringField -from monkey_island.cc.services.zero_trust.scoutsuite.consts import rule_consts +from monkey_island.cc.services.zero_trust.scoutsuite_findings.consts import rule_consts class ScoutSuiteRule(EmbeddedDocument): diff --git a/monkey/monkey_island/cc/models/zero_trust/test_aggregate_finding.py b/monkey/monkey_island/cc/models/zero_trust/test_aggregate_finding.py index 134abf559..fe8757b9a 100644 --- a/monkey/monkey_island/cc/models/zero_trust/test_aggregate_finding.py +++ b/monkey/monkey_island/cc/models/zero_trust/test_aggregate_finding.py @@ -6,7 +6,7 @@ from packaging import version import common.common_consts.zero_trust_consts as zero_trust_consts from monkey_island.cc.models.zero_trust.event import Event from monkey_island.cc.models.zero_trust.finding import Finding -from monkey_island.cc.services.zero_trust.monkey_finding_service import MonkeyFindingService +from monkey_island.cc.services.zero_trust.monkey_findings.monkey_zt_finding_service import MonkeyZTFindingService from monkey_island.cc.testing.IslandTestCase import IslandTestCase @@ -23,12 +23,12 @@ class TestAggregateFinding(IslandTestCase): events = [Event.create_event("t", "t", zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK)] self.assertEqual(len(Finding.objects(test=test, status=status)), 0) - MonkeyFindingService.create_or_add_to_existing(test, status, events) + MonkeyZTFindingService.create_or_add_to_existing(test, status, events) self.assertEqual(len(Finding.objects(test=test, status=status)), 1) self.assertEqual(len(Finding.objects(test=test, status=status)[0].events), 1) - MonkeyFindingService.create_or_add_to_existing(test, status, events) + MonkeyZTFindingService.create_or_add_to_existing(test, status, events) self.assertEqual(len(Finding.objects(test=test, status=status)), 1) self.assertEqual(len(Finding.objects(test=test, status=status)[0].events), 2) @@ -50,7 +50,7 @@ class TestAggregateFinding(IslandTestCase): self.assertEqual(len(Finding.objects(test=test, status=status)), 1) self.assertEqual(len(Finding.objects(test=test, status=status)[0].events), 1) - MonkeyFindingService.create_or_add_to_existing(test, status, events) + MonkeyZTFindingService.create_or_add_to_existing(test, status, events) self.assertEqual(len(Finding.objects(test=test, status=status)), 1) self.assertEqual(len(Finding.objects(test=test, status=status)[0].events), 2) @@ -60,4 +60,4 @@ class TestAggregateFinding(IslandTestCase): self.assertEqual(len(Finding.objects(test=test, status=status)), 2) with self.assertRaises(AssertionError): - MonkeyFindingService.create_or_add_to_existing(test, status, events) + MonkeyZTFindingService.create_or_add_to_existing(test, status, events) diff --git a/monkey/monkey_island/cc/resources/zero_trust/finding_event.py b/monkey/monkey_island/cc/resources/zero_trust/finding_event.py index 0e6c09b11..ddef04b77 100644 --- a/monkey/monkey_island/cc/resources/zero_trust/finding_event.py +++ b/monkey/monkey_island/cc/resources/zero_trust/finding_event.py @@ -3,11 +3,11 @@ import json import flask_restful from monkey_island.cc.resources.auth.auth import jwt_required -from monkey_island.cc.services.zero_trust.monkey_finding_service import MonkeyFindingService +from monkey_island.cc.services.zero_trust.monkey_findings.monkey_zt_finding_service import MonkeyZTFindingService class ZeroTrustFindingEvent(flask_restful.Resource): @jwt_required def get(self, finding_id: str): - return {'events_json': json.dumps(MonkeyFindingService.get_events_by_finding(finding_id), default=str)} + return {'events_json': json.dumps(MonkeyZTFindingService.get_events_by_finding(finding_id), default=str)} diff --git a/monkey/monkey_island/cc/resources/zero_trust/scoutsuite_auth/aws_keys.py b/monkey/monkey_island/cc/resources/zero_trust/scoutsuite_auth/aws_keys.py index 53e757f11..0642333bb 100644 --- a/monkey/monkey_island/cc/resources/zero_trust/scoutsuite_auth/aws_keys.py +++ b/monkey/monkey_island/cc/resources/zero_trust/scoutsuite_auth/aws_keys.py @@ -1,7 +1,7 @@ import flask_restful from monkey_island.cc.resources.auth.auth import jwt_required -from monkey_island.cc.services.zero_trust.scoutsuite.scoutsuite_auth_service import get_aws_keys +from monkey_island.cc.services.zero_trust.scoutsuite_findings.scoutsuite_auth_service import get_aws_keys class AWSKeys(flask_restful.Resource): diff --git a/monkey/monkey_island/cc/resources/zero_trust/scoutsuite_auth/scoutsuite_auth.py b/monkey/monkey_island/cc/resources/zero_trust/scoutsuite_auth/scoutsuite_auth.py index dbed4dd51..ea2086dc5 100644 --- a/monkey/monkey_island/cc/resources/zero_trust/scoutsuite_auth/scoutsuite_auth.py +++ b/monkey/monkey_island/cc/resources/zero_trust/scoutsuite_auth/scoutsuite_auth.py @@ -6,8 +6,8 @@ from flask import request from common.cloud.scoutsuite_consts import CloudProviders from common.utils.exceptions import InvalidAWSKeys from monkey_island.cc.resources.auth.auth import jwt_required -from monkey_island.cc.services.zero_trust.scoutsuite.scoutsuite_auth_service import (is_cloud_authentication_setup, - set_aws_keys) +from monkey_island.cc.services.zero_trust.scoutsuite_findings.scoutsuite_auth_service import (is_cloud_authentication_setup, + set_aws_keys) class ScoutSuiteAuth(flask_restful.Resource): diff --git a/monkey/monkey_island/cc/services/telemetry/processing/scoutsuite.py b/monkey/monkey_island/cc/services/telemetry/processing/scoutsuite.py index 8bbfb2a23..94904b4c7 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/scoutsuite.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/scoutsuite.py @@ -2,10 +2,10 @@ import json from monkey_island.cc.database import mongo from monkey_island.cc.models.zero_trust.scoutsuite_data_json import ScoutSuiteDataJson -from monkey_island.cc.services.zero_trust.scoutsuite.consts.findings_list import SCOUTSUITE_FINDINGS -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_parser import RuleParser -from monkey_island.cc.services.zero_trust.scoutsuite.scoutsuite_finding_service import ScoutSuiteFindingService -from monkey_island.cc.services.zero_trust.scoutsuite.scoutsuite_rule_service import ScoutSuiteRuleService +from ...zero_trust.scoutsuite_findings.consts.findings_list import SCOUTSUITE_FINDINGS +from ...zero_trust.scoutsuite_findings.data_parsing.rule_parser import RuleParser +from ...zero_trust.scoutsuite_findings.scoutsuite_zt_finding_service import ScoutSuiteZTFindingService +from ...zero_trust.scoutsuite_findings.scoutsuite_rule_service import ScoutSuiteRuleService def process_scoutsuite_telemetry(telemetry_json): @@ -22,7 +22,7 @@ def create_scoutsuite_findings(scoutsuite_data): for rule in finding.rules: rule_data = RuleParser.get_rule_data(scoutsuite_data, rule) rule = ScoutSuiteRuleService.get_rule_from_rule_data(rule_data) - ScoutSuiteFindingService.process_rule(finding, rule) + ScoutSuiteZTFindingService.process_rule(finding, rule) def update_data(telemetry_json): diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/antivirus_existence.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/antivirus_existence.py index e15969ec8..a6b90cc45 100644 --- a/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/antivirus_existence.py +++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/antivirus_existence.py @@ -4,7 +4,7 @@ import common.common_consts.zero_trust_consts as zero_trust_consts from monkey_island.cc.models import Monkey from monkey_island.cc.models.zero_trust.event import Event from monkey_island.cc.services.telemetry.zero_trust_checks.known_anti_viruses import ANTI_VIRUS_KNOWN_PROCESS_NAMES -from monkey_island.cc.services.zero_trust.monkey_finding_service import MonkeyFindingService +from monkey_island.cc.services.zero_trust.monkey_findings.monkey_zt_finding_service import MonkeyZTFindingService def check_antivirus_existence(process_list_json, monkey_guid): @@ -30,8 +30,8 @@ def check_antivirus_existence(process_list_json, monkey_guid): test_status = zero_trust_consts.STATUS_PASSED else: test_status = zero_trust_consts.STATUS_FAILED - MonkeyFindingService.create_or_add_to_existing(test=zero_trust_consts.TEST_ENDPOINT_SECURITY_EXISTS, - status=test_status, events=events) + MonkeyZTFindingService.create_or_add_to_existing(test=zero_trust_consts.TEST_ENDPOINT_SECURITY_EXISTS, + status=test_status, events=events) def filter_av_processes(process_list): diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/communicate_as_new_user.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/communicate_as_new_user.py index 00c197e0a..94412b3ba 100644 --- a/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/communicate_as_new_user.py +++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/communicate_as_new_user.py @@ -1,6 +1,6 @@ import common.common_consts.zero_trust_consts as zero_trust_consts from monkey_island.cc.models.zero_trust.event import Event -from monkey_island.cc.services.zero_trust.monkey_finding_service import MonkeyFindingService +from monkey_island.cc.services.zero_trust.monkey_findings.monkey_zt_finding_service import MonkeyZTFindingService COMM_AS_NEW_USER_FAILED_FORMAT = "Monkey on {} couldn't communicate as new user. Details: {}" COMM_AS_NEW_USER_SUCCEEDED_FORMAT = \ @@ -8,9 +8,10 @@ COMM_AS_NEW_USER_SUCCEEDED_FORMAT = \ def check_new_user_communication(current_monkey, success, message): - MonkeyFindingService.create_or_add_to_existing(test=zero_trust_consts.TEST_COMMUNICATE_AS_NEW_USER, - status=zero_trust_consts.STATUS_FAILED if success else zero_trust_consts.STATUS_PASSED, - events=[ + status = zero_trust_consts.STATUS_FAILED if success else zero_trust_consts.STATUS_PASSED + MonkeyZTFindingService.create_or_add_to_existing(test=zero_trust_consts.TEST_COMMUNICATE_AS_NEW_USER, + status=status, + events=[ get_attempt_event(current_monkey), get_result_event(current_monkey, message, success) ]) diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/data_endpoints.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/data_endpoints.py index e74c5c464..a5d42ef2c 100644 --- a/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/data_endpoints.py +++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/data_endpoints.py @@ -4,7 +4,7 @@ import common.common_consts.zero_trust_consts as zero_trust_consts from common.common_consts.network_consts import ES_SERVICE from monkey_island.cc.models import Monkey from monkey_island.cc.models.zero_trust.event import Event -from monkey_island.cc.services.zero_trust.monkey_finding_service import MonkeyFindingService +from monkey_island.cc.services.zero_trust.monkey_findings.monkey_zt_finding_service import MonkeyZTFindingService HTTP_SERVERS_SERVICES_NAMES = ['tcp-80'] @@ -55,10 +55,10 @@ def check_open_data_endpoints(telemetry_json): event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK )) - MonkeyFindingService.create_or_add_to_existing(test=zero_trust_consts.TEST_DATA_ENDPOINT_HTTP, - status=found_http_server_status, events=events) + MonkeyZTFindingService.create_or_add_to_existing(test=zero_trust_consts.TEST_DATA_ENDPOINT_HTTP, + status=found_http_server_status, events=events) - MonkeyFindingService.create_or_add_to_existing(test=zero_trust_consts.TEST_DATA_ENDPOINT_ELASTIC, - status=found_elastic_search_server, events=events) + MonkeyZTFindingService.create_or_add_to_existing(test=zero_trust_consts.TEST_DATA_ENDPOINT_ELASTIC, + status=found_elastic_search_server, events=events) - MonkeyFindingService.add_malicious_activity_to_timeline(events) + MonkeyZTFindingService.add_malicious_activity_to_timeline(events) diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/machine_exploited.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/machine_exploited.py index e47c4a831..d6813259c 100644 --- a/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/machine_exploited.py +++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/machine_exploited.py @@ -1,6 +1,6 @@ import common.common_consts.zero_trust_consts as zero_trust_consts from monkey_island.cc.models.zero_trust.event import Event -from monkey_island.cc.services.zero_trust.monkey_finding_service import MonkeyFindingService +from monkey_island.cc.services.zero_trust.monkey_findings.monkey_zt_finding_service import MonkeyZTFindingService def check_machine_exploited(current_monkey, exploit_successful, exploiter, target_ip, timestamp): @@ -29,7 +29,7 @@ def check_machine_exploited(current_monkey, exploit_successful, exploiter, targe ) status = zero_trust_consts.STATUS_FAILED - MonkeyFindingService.create_or_add_to_existing(test=zero_trust_consts.TEST_MACHINE_EXPLOITED, status=status, - events=events) + MonkeyZTFindingService.create_or_add_to_existing(test=zero_trust_consts.TEST_MACHINE_EXPLOITED, status=status, + events=events) - MonkeyFindingService.add_malicious_activity_to_timeline(events) + MonkeyZTFindingService.add_malicious_activity_to_timeline(events) diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/segmentation.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/segmentation.py index 1c43e2863..d5a56b36d 100644 --- a/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/segmentation.py +++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/segmentation.py @@ -6,7 +6,7 @@ from common.network.segmentation_utils import get_ip_if_in_subnet, get_ip_in_src from monkey_island.cc.models import Monkey from monkey_island.cc.models.zero_trust.event import Event from monkey_island.cc.services.configuration.utils import get_config_network_segments_as_subnet_groups -from monkey_island.cc.services.zero_trust.monkey_finding_service import MonkeyFindingService +from monkey_island.cc.services.zero_trust.monkey_findings.monkey_zt_finding_service import MonkeyZTFindingService SEGMENTATION_DONE_EVENT_TEXT = "Monkey on {hostname} is done attempting cross-segment communications " \ "from `{src_seg}` segments to `{dst_seg}` segments." @@ -26,7 +26,7 @@ def check_segmentation_violation(current_monkey, target_ip): target_subnet = subnet_pair[1] if is_segmentation_violation(current_monkey, target_ip, source_subnet, target_subnet): event = get_segmentation_violation_event(current_monkey, source_subnet, target_ip, target_subnet) - MonkeyFindingService.create_or_add_to_existing( + MonkeyZTFindingService.create_or_add_to_existing( test=zero_trust_consts.TEST_SEGMENTATION, status=zero_trust_consts.STATUS_FAILED, events=[event] @@ -90,7 +90,7 @@ def create_or_add_findings_for_all_pairs(all_subnets, current_monkey): all_subnets_pairs_for_this_monkey = itertools.product(this_monkey_subnets, other_subnets) for subnet_pair in all_subnets_pairs_for_this_monkey: - MonkeyFindingService.create_or_add_to_existing( + MonkeyZTFindingService.create_or_add_to_existing( status=zero_trust_consts.STATUS_PASSED, events=[get_segmentation_done_event(current_monkey, subnet_pair)], test=zero_trust_consts.TEST_SEGMENTATION diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/test_segmentation.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/test_segmentation.py index 6b1c76aea..e4aa49dc2 100644 --- a/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/test_segmentation.py +++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/test_segmentation.py @@ -5,7 +5,7 @@ from monkey_island.cc.models import Monkey from monkey_island.cc.models.zero_trust.event import Event from monkey_island.cc.models.zero_trust.finding import Finding from monkey_island.cc.services.telemetry.zero_trust_checks.segmentation import create_or_add_findings_for_all_pairs -from monkey_island.cc.services.zero_trust.monkey_finding_service import MonkeyFindingService +from monkey_island.cc.services.zero_trust.monkey_findings.monkey_zt_finding_service import MonkeyZTFindingService from monkey_island.cc.testing.IslandTestCase import IslandTestCase FIRST_SUBNET = "1.1.1.1" @@ -37,7 +37,7 @@ class TestSegmentationChecks(IslandTestCase): 2) # This is a monkey from 2nd subnet communicated with 1st subnet. - MonkeyFindingService.create_or_add_to_existing( + MonkeyZTFindingService.create_or_add_to_existing( status=zero_trust_consts.STATUS_FAILED, test=zero_trust_consts.TEST_SEGMENTATION, events=[Event.create_event(title="sdf", diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/tunneling.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/tunneling.py index dc5092345..4b755be98 100644 --- a/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/tunneling.py +++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/tunneling.py @@ -2,7 +2,7 @@ import common.common_consts.zero_trust_consts as zero_trust_consts from monkey_island.cc.models import Monkey from monkey_island.cc.models.zero_trust.event import Event from monkey_island.cc.services.telemetry.processing.utils import get_tunnel_host_ip_from_proxy_field -from monkey_island.cc.services.zero_trust.monkey_finding_service import MonkeyFindingService +from monkey_island.cc.services.zero_trust.monkey_findings.monkey_zt_finding_service import MonkeyZTFindingService def check_tunneling_violation(tunnel_telemetry_json): @@ -18,7 +18,7 @@ def check_tunneling_violation(tunnel_telemetry_json): timestamp=tunnel_telemetry_json['timestamp'] )] - MonkeyFindingService.create_or_add_to_existing(test=zero_trust_consts.TEST_TUNNELING, - status=zero_trust_consts.STATUS_FAILED, events=tunneling_events) + MonkeyZTFindingService.create_or_add_to_existing(test=zero_trust_consts.TEST_TUNNELING, + status=zero_trust_consts.STATUS_FAILED, events=tunneling_events) - MonkeyFindingService.add_malicious_activity_to_timeline(tunneling_events) + MonkeyZTFindingService.add_malicious_activity_to_timeline(tunneling_events) diff --git a/monkey/monkey_island/cc/services/zero_trust/monkey_findings/__init__.py b/monkey/monkey_island/cc/services/zero_trust/monkey_findings/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/monkey/monkey_island/cc/services/zero_trust/monkey_details_service.py b/monkey/monkey_island/cc/services/zero_trust/monkey_findings/monkey_zt_details_service.py similarity index 87% rename from monkey/monkey_island/cc/services/zero_trust/monkey_details_service.py rename to monkey/monkey_island/cc/services/zero_trust/monkey_findings/monkey_zt_details_service.py index 5332ed90b..e73ce0cec 100644 --- a/monkey/monkey_island/cc/services/zero_trust/monkey_details_service.py +++ b/monkey/monkey_island/cc/services/zero_trust/monkey_findings/monkey_zt_details_service.py @@ -9,7 +9,7 @@ from monkey_island.cc.models.zero_trust.monkey_finding_details import MonkeyFind EVENT_FETCH_CNT = 50 -class MonkeyDetailsService: +class MonkeyZTDetailsService: @staticmethod def fetch_details_for_display(finding_id: ObjectId) -> dict: @@ -21,8 +21,8 @@ class MonkeyDetailsService: details = list(MonkeyFindingDetails.objects.aggregate(*pipeline)) if details: details = details[0] - details['latest_events'] = MonkeyDetailsService._get_events_without_overlap(details['event_count'], - details['latest_events']) + details['latest_events'] = MonkeyZTDetailsService._get_events_without_overlap(details['event_count'], + details['latest_events']) return details @staticmethod diff --git a/monkey/monkey_island/cc/services/zero_trust/monkey_finding_service.py b/monkey/monkey_island/cc/services/zero_trust/monkey_findings/monkey_zt_finding_service.py similarity index 82% rename from monkey/monkey_island/cc/services/zero_trust/monkey_finding_service.py rename to monkey/monkey_island/cc/services/zero_trust/monkey_findings/monkey_zt_finding_service.py index 46c3137bf..c3c45e69e 100644 --- a/monkey/monkey_island/cc/services/zero_trust/monkey_finding_service.py +++ b/monkey/monkey_island/cc/services/zero_trust/monkey_findings/monkey_zt_finding_service.py @@ -8,7 +8,7 @@ from monkey_island.cc.models.zero_trust.finding import Finding from monkey_island.cc.models.zero_trust.monkey_finding_details import MonkeyFindingDetails -class MonkeyFindingService: +class MonkeyZTFindingService: @staticmethod def create_or_add_to_existing(test, status, events): @@ -23,10 +23,10 @@ class MonkeyFindingService: assert (len(existing_findings) < 2), "More than one finding exists for {}:{}".format(test, status) if len(existing_findings) == 0: - MonkeyFindingService.create_new_finding(test, status, events) + MonkeyZTFindingService.create_new_finding(test, status, events) else: # Now we know for sure this is the only one - MonkeyFindingService.add_events(existing_findings[0], events) + MonkeyZTFindingService.add_events(existing_findings[0], events) @staticmethod def create_new_finding(test: str, status: str, events: List[Event]): @@ -50,5 +50,5 @@ class MonkeyFindingService: @staticmethod def add_malicious_activity_to_timeline(events): - MonkeyFindingService.create_or_add_to_existing(test=zero_trust_consts.TEST_MALICIOUS_ACTIVITY_TIMELINE, - status=zero_trust_consts.STATUS_VERIFY, events=events) + MonkeyZTFindingService.create_or_add_to_existing(test=zero_trust_consts.TEST_MALICIOUS_ACTIVITY_TIMELINE, + status=zero_trust_consts.STATUS_VERIFY, events=events) diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/findings_list.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/findings_list.py deleted file mode 100644 index 72a4cb47a..000000000 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/findings_list.py +++ /dev/null @@ -1,8 +0,0 @@ -from monkey_island.cc.services.zero_trust.scoutsuite.consts.findings import (DataLossPrevention, Logging, - PermissiveFirewallRules, - RestrictivePolicies, - SecureAuthentication, ServiceSecurity, - UnencryptedData) - -SCOUTSUITE_FINDINGS = [PermissiveFirewallRules, UnencryptedData, DataLossPrevention, SecureAuthentication, - RestrictivePolicies, Logging, ServiceSecurity] diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/cloudformation_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/cloudformation_rule_path_creator.py deleted file mode 100644 index 10adb474c..000000000 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/cloudformation_rule_path_creator.py +++ /dev/null @@ -1,10 +0,0 @@ -from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.cloudformation_rules import CloudformationRules -from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import \ - AbstractRulePathCreator - - -class CloudformationRulePathCreator(AbstractRulePathCreator): - - service_type = SERVICE_TYPES.CLOUDFORMATION - supported_rules = CloudformationRules diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/cloudtrail_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/cloudtrail_rule_path_creator.py deleted file mode 100644 index 2f626dfd5..000000000 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/cloudtrail_rule_path_creator.py +++ /dev/null @@ -1,10 +0,0 @@ -from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.cloudtrail_rules import CloudTrailRules -from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import \ - AbstractRulePathCreator - - -class CloudTrailRulePathCreator(AbstractRulePathCreator): - - service_type = SERVICE_TYPES.CLOUDTRAIL - supported_rules = CloudTrailRules diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/cloudwatch_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/cloudwatch_rule_path_creator.py deleted file mode 100644 index f6d4d673d..000000000 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/cloudwatch_rule_path_creator.py +++ /dev/null @@ -1,10 +0,0 @@ -from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.cloudwatch_rules import CloudWatchRules -from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import \ - AbstractRulePathCreator - - -class CloudWatchRulePathCreator(AbstractRulePathCreator): - - service_type = SERVICE_TYPES.CLOUDWATCH - supported_rules = CloudWatchRules diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/config_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/config_rule_path_creator.py deleted file mode 100644 index 59a2e49eb..000000000 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/config_rule_path_creator.py +++ /dev/null @@ -1,10 +0,0 @@ -from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.config_rules import ConfigRules -from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import \ - AbstractRulePathCreator - - -class ConfigRulePathCreator(AbstractRulePathCreator): - - service_type = SERVICE_TYPES.CONFIG - supported_rules = ConfigRules diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/ec2_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/ec2_rule_path_creator.py deleted file mode 100644 index 4a37b0a7e..000000000 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/ec2_rule_path_creator.py +++ /dev/null @@ -1,10 +0,0 @@ -from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.ec2_rules import EC2Rules -from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import \ - AbstractRulePathCreator - - -class EC2RulePathCreator(AbstractRulePathCreator): - - service_type = SERVICE_TYPES.EC2 - supported_rules = EC2Rules diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/elb_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/elb_rule_path_creator.py deleted file mode 100644 index a38ae2881..000000000 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/elb_rule_path_creator.py +++ /dev/null @@ -1,10 +0,0 @@ -from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.elb_rules import ELBRules -from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import \ - AbstractRulePathCreator - - -class ELBRulePathCreator(AbstractRulePathCreator): - - service_type = SERVICE_TYPES.ELB - supported_rules = ELBRules diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/elbv2_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/elbv2_rule_path_creator.py deleted file mode 100644 index 2472bf076..000000000 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/elbv2_rule_path_creator.py +++ /dev/null @@ -1,10 +0,0 @@ -from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.elbv2_rules import ELBv2Rules -from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import \ - AbstractRulePathCreator - - -class ELBv2RulePathCreator(AbstractRulePathCreator): - - service_type = SERVICE_TYPES.ELB_V2 - supported_rules = ELBv2Rules diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/iam_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/iam_rule_path_creator.py deleted file mode 100644 index a601cb9cd..000000000 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/iam_rule_path_creator.py +++ /dev/null @@ -1,10 +0,0 @@ -from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.iam_rules import IAMRules -from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import \ - AbstractRulePathCreator - - -class IAMRulePathCreator(AbstractRulePathCreator): - - service_type = SERVICE_TYPES.IAM - supported_rules = IAMRules diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/rds_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/rds_rule_path_creator.py deleted file mode 100644 index 0b8bf54af..000000000 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/rds_rule_path_creator.py +++ /dev/null @@ -1,10 +0,0 @@ -from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rds_rules import RDSRules -from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import \ - AbstractRulePathCreator - - -class RDSRulePathCreator(AbstractRulePathCreator): - - service_type = SERVICE_TYPES.RDS - supported_rules = RDSRules diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/redshift_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/redshift_rule_path_creator.py deleted file mode 100644 index 4de7016a4..000000000 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/redshift_rule_path_creator.py +++ /dev/null @@ -1,10 +0,0 @@ -from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.redshift_rules import RedshiftRules -from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import \ - AbstractRulePathCreator - - -class RedshiftRulePathCreator(AbstractRulePathCreator): - - service_type = SERVICE_TYPES.REDSHIFT - supported_rules = RedshiftRules diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/s3_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/s3_rule_path_creator.py deleted file mode 100644 index 4c0a0dccc..000000000 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/s3_rule_path_creator.py +++ /dev/null @@ -1,10 +0,0 @@ -from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.s3_rules import S3Rules -from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import \ - AbstractRulePathCreator - - -class S3RulePathCreator(AbstractRulePathCreator): - - service_type = SERVICE_TYPES.S3 - supported_rules = S3Rules diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/ses_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/ses_rule_path_creator.py deleted file mode 100644 index c7cac2bce..000000000 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/ses_rule_path_creator.py +++ /dev/null @@ -1,10 +0,0 @@ -from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.ses_rules import SESRules -from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import \ - AbstractRulePathCreator - - -class SESRulePathCreator(AbstractRulePathCreator): - - service_type = SERVICE_TYPES.SES - supported_rules = SESRules diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/sns_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/sns_rule_path_creator.py deleted file mode 100644 index 60a2f5b1c..000000000 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/sns_rule_path_creator.py +++ /dev/null @@ -1,10 +0,0 @@ -from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.sns_rules import SNSRules -from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import \ - AbstractRulePathCreator - - -class SNSRulePathCreator(AbstractRulePathCreator): - - service_type = SERVICE_TYPES.SNS - supported_rules = SNSRules diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/sqs_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/sqs_rule_path_creator.py deleted file mode 100644 index 619cf2ddb..000000000 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/sqs_rule_path_creator.py +++ /dev/null @@ -1,10 +0,0 @@ -from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.sqs_rules import SQSRules -from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import \ - AbstractRulePathCreator - - -class SQSRulePathCreator(AbstractRulePathCreator): - - service_type = SERVICE_TYPES.SQS - supported_rules = SQSRules diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/vpc_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/vpc_rule_path_creator.py deleted file mode 100644 index 280d0933e..000000000 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/vpc_rule_path_creator.py +++ /dev/null @@ -1,10 +0,0 @@ -from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.vpc_rules import VPCRules -from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import \ - AbstractRulePathCreator - - -class VPCRulePathCreator(AbstractRulePathCreator): - - service_type = SERVICE_TYPES.VPC - supported_rules = VPCRules diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators_list.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators_list.py deleted file mode 100644 index 4dce7ed2b..000000000 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators_list.py +++ /dev/null @@ -1,35 +0,0 @@ -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.\ - cloudformation_rule_path_creator import CloudformationRulePathCreator -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.\ - cloudtrail_rule_path_creator import CloudTrailRulePathCreator -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.\ - cloudwatch_rule_path_creator import CloudWatchRulePathCreator -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.\ - config_rule_path_creator import ConfigRulePathCreator -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.\ - ec2_rule_path_creator import EC2RulePathCreator -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.\ - elb_rule_path_creator import ELBRulePathCreator -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.\ - elbv2_rule_path_creator import ELBv2RulePathCreator -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.\ - iam_rule_path_creator import IAMRulePathCreator -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.\ - rds_rule_path_creator import RDSRulePathCreator -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.\ - redshift_rule_path_creator import RedshiftRulePathCreator -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.\ - s3_rule_path_creator import S3RulePathCreator -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.\ - ses_rule_path_creator import SESRulePathCreator -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.\ - sns_rule_path_creator import SNSRulePathCreator -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators. \ - sqs_rule_path_creator import SQSRulePathCreator -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators. \ - vpc_rule_path_creator import VPCRulePathCreator - -RULE_PATH_CREATORS_LIST = [EC2RulePathCreator, ELBv2RulePathCreator, RDSRulePathCreator, RedshiftRulePathCreator, - S3RulePathCreator, IAMRulePathCreator, CloudTrailRulePathCreator, ELBRulePathCreator, - VPCRulePathCreator, CloudWatchRulePathCreator, SQSRulePathCreator, SNSRulePathCreator, - SESRulePathCreator, ConfigRulePathCreator, CloudformationRulePathCreator] diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/__init__.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/__init__.py similarity index 100% rename from monkey/monkey_island/cc/services/zero_trust/scoutsuite/__init__.py rename to monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/__init__.py diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/findings.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/consts/findings.py similarity index 83% rename from monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/findings.py rename to monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/consts/findings.py index 7fa96544b..762f6bf80 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/findings.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/consts/findings.py @@ -1,21 +1,21 @@ from abc import ABC, abstractmethod from common.common_consts import zero_trust_consts -from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.cloudformation_rules import CloudformationRules -from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.cloudtrail_rules import CloudTrailRules -from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.cloudwatch_rules import CloudWatchRules -from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.config_rules import ConfigRules -from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.ec2_rules import EC2Rules -from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.elb_rules import ELBRules -from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.elbv2_rules import ELBv2Rules -from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.iam_rules import IAMRules -from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rds_rules import RDSRules -from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.redshift_rules import RedshiftRules -from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.s3_rules import S3Rules -from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.ses_rules import SESRules -from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.sns_rules import SNSRules -from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.sqs_rules import SQSRules -from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.vpc_rules import VPCRules +from .rule_names.cloudformation_rules import CloudformationRules +from .rule_names.cloudtrail_rules import CloudTrailRules +from .rule_names.cloudwatch_rules import CloudWatchRules +from .rule_names.config_rules import ConfigRules +from .rule_names.ec2_rules import EC2Rules +from .rule_names.elb_rules import ELBRules +from .rule_names.elbv2_rules import ELBv2Rules +from .rule_names.iam_rules import IAMRules +from .rule_names.rds_rules import RDSRules +from .rule_names.redshift_rules import RedshiftRules +from .rule_names.s3_rules import S3Rules +from .rule_names.ses_rules import SESRules +from .rule_names.sns_rules import SNSRules +from .rule_names.sqs_rules import SQSRules +from .rule_names.vpc_rules import VPCRules class ScoutSuiteFinding(ABC): diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/consts/findings_list.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/consts/findings_list.py new file mode 100644 index 000000000..fdef7d62b --- /dev/null +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/consts/findings_list.py @@ -0,0 +1,8 @@ +from .findings import (DataLossPrevention, Logging, + PermissiveFirewallRules, + RestrictivePolicies, + SecureAuthentication, ServiceSecurity, + UnencryptedData) + +SCOUTSUITE_FINDINGS = [PermissiveFirewallRules, UnencryptedData, DataLossPrevention, SecureAuthentication, + RestrictivePolicies, Logging, ServiceSecurity] diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_consts.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/consts/rule_consts.py similarity index 100% rename from monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_consts.py rename to monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/consts/rule_consts.py diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/cloudformation_rules.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/consts/rule_names/cloudformation_rules.py similarity index 100% rename from monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/cloudformation_rules.py rename to monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/consts/rule_names/cloudformation_rules.py diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/cloudtrail_rules.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/consts/rule_names/cloudtrail_rules.py similarity index 100% rename from monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/cloudtrail_rules.py rename to monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/consts/rule_names/cloudtrail_rules.py diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/cloudwatch_rules.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/consts/rule_names/cloudwatch_rules.py similarity index 100% rename from monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/cloudwatch_rules.py rename to monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/consts/rule_names/cloudwatch_rules.py diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/config_rules.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/consts/rule_names/config_rules.py similarity index 100% rename from monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/config_rules.py rename to monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/consts/rule_names/config_rules.py diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/ec2_rules.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/consts/rule_names/ec2_rules.py similarity index 100% rename from monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/ec2_rules.py rename to monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/consts/rule_names/ec2_rules.py diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/elb_rules.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/consts/rule_names/elb_rules.py similarity index 100% rename from monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/elb_rules.py rename to monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/consts/rule_names/elb_rules.py diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/elbv2_rules.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/consts/rule_names/elbv2_rules.py similarity index 100% rename from monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/elbv2_rules.py rename to monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/consts/rule_names/elbv2_rules.py diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/iam_rules.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/consts/rule_names/iam_rules.py similarity index 100% rename from monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/iam_rules.py rename to monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/consts/rule_names/iam_rules.py diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/rds_rules.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/consts/rule_names/rds_rules.py similarity index 100% rename from monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/rds_rules.py rename to monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/consts/rule_names/rds_rules.py diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/redshift_rules.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/consts/rule_names/redshift_rules.py similarity index 100% rename from monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/redshift_rules.py rename to monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/consts/rule_names/redshift_rules.py diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/s3_rules.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/consts/rule_names/s3_rules.py similarity index 100% rename from monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/s3_rules.py rename to monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/consts/rule_names/s3_rules.py diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/ses_rules.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/consts/rule_names/ses_rules.py similarity index 100% rename from monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/ses_rules.py rename to monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/consts/rule_names/ses_rules.py diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/sns_rules.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/consts/rule_names/sns_rules.py similarity index 100% rename from monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/sns_rules.py rename to monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/consts/rule_names/sns_rules.py diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/sqs_rules.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/consts/rule_names/sqs_rules.py similarity index 100% rename from monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/sqs_rules.py rename to monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/consts/rule_names/sqs_rules.py diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/vpc_rules.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/consts/rule_names/vpc_rules.py similarity index 100% rename from monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/vpc_rules.py rename to monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/consts/rule_names/vpc_rules.py diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/service_consts.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/consts/service_consts.py similarity index 100% rename from monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/service_consts.py rename to monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/consts/service_consts.py diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_parser.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/data_parsing/rule_parser.py similarity index 88% rename from monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_parser.py rename to monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/data_parsing/rule_parser.py index c5855ddd5..84dba4003 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_parser.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/data_parsing/rule_parser.py @@ -1,6 +1,6 @@ from common.utils.code_utils import get_dict_value_by_path from common.utils.exceptions import RulePathCreatorNotFound -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators_list import \ +from monkey_island.cc.services.zero_trust.scoutsuite_findings.data_parsing.rule_path_building.rule_path_creators_list import \ RULE_PATH_CREATORS_LIST diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/abstract_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/data_parsing/rule_path_building/abstract_rule_path_creator.py similarity index 80% rename from monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/abstract_rule_path_creator.py rename to monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/data_parsing/rule_path_building/abstract_rule_path_creator.py index c24a5cf0b..78c505d92 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/abstract_rule_path_creator.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/data_parsing/rule_path_building/abstract_rule_path_creator.py @@ -1,7 +1,7 @@ from abc import ABC, abstractmethod from typing import List -from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import FINDINGS, SERVICE_TYPES, SERVICES +from ...consts.service_consts import FINDINGS, SERVICE_TYPES, SERVICES class AbstractRulePathCreator(ABC): diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/data_parsing/rule_path_building/rule_path_creators/cloudformation_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/data_parsing/rule_path_building/rule_path_creators/cloudformation_rule_path_creator.py new file mode 100644 index 000000000..28a550527 --- /dev/null +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/data_parsing/rule_path_building/rule_path_creators/cloudformation_rule_path_creator.py @@ -0,0 +1,9 @@ +from ..abstract_rule_path_creator import AbstractRulePathCreator +from ....consts.rule_names.cloudformation_rules import CloudformationRules +from ....consts.service_consts import SERVICE_TYPES + + +class CloudformationRulePathCreator(AbstractRulePathCreator): + + service_type = SERVICE_TYPES.CLOUDFORMATION + supported_rules = CloudformationRules diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/data_parsing/rule_path_building/rule_path_creators/cloudtrail_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/data_parsing/rule_path_building/rule_path_creators/cloudtrail_rule_path_creator.py new file mode 100644 index 000000000..e0734fb42 --- /dev/null +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/data_parsing/rule_path_building/rule_path_creators/cloudtrail_rule_path_creator.py @@ -0,0 +1,9 @@ +from ..abstract_rule_path_creator import AbstractRulePathCreator +from ....consts.rule_names.cloudtrail_rules import CloudTrailRules +from ....consts.service_consts import SERVICE_TYPES + + +class CloudTrailRulePathCreator(AbstractRulePathCreator): + + service_type = SERVICE_TYPES.CLOUDTRAIL + supported_rules = CloudTrailRules diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/data_parsing/rule_path_building/rule_path_creators/cloudwatch_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/data_parsing/rule_path_building/rule_path_creators/cloudwatch_rule_path_creator.py new file mode 100644 index 000000000..acbb66611 --- /dev/null +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/data_parsing/rule_path_building/rule_path_creators/cloudwatch_rule_path_creator.py @@ -0,0 +1,9 @@ +from ..abstract_rule_path_creator import AbstractRulePathCreator +from ....consts.rule_names.cloudwatch_rules import CloudWatchRules +from ....consts.service_consts import SERVICE_TYPES + + +class CloudWatchRulePathCreator(AbstractRulePathCreator): + + service_type = SERVICE_TYPES.CLOUDWATCH + supported_rules = CloudWatchRules diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/data_parsing/rule_path_building/rule_path_creators/config_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/data_parsing/rule_path_building/rule_path_creators/config_rule_path_creator.py new file mode 100644 index 000000000..aded2d3c6 --- /dev/null +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/data_parsing/rule_path_building/rule_path_creators/config_rule_path_creator.py @@ -0,0 +1,9 @@ +from ..abstract_rule_path_creator import AbstractRulePathCreator +from ....consts.rule_names.config_rules import ConfigRules +from ....consts.service_consts import SERVICE_TYPES + + +class ConfigRulePathCreator(AbstractRulePathCreator): + + service_type = SERVICE_TYPES.CONFIG + supported_rules = ConfigRules diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/data_parsing/rule_path_building/rule_path_creators/ec2_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/data_parsing/rule_path_building/rule_path_creators/ec2_rule_path_creator.py new file mode 100644 index 000000000..3692df963 --- /dev/null +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/data_parsing/rule_path_building/rule_path_creators/ec2_rule_path_creator.py @@ -0,0 +1,9 @@ +from ..abstract_rule_path_creator import AbstractRulePathCreator +from ....consts.rule_names.ec2_rules import EC2Rules +from ....consts.service_consts import SERVICE_TYPES + + +class EC2RulePathCreator(AbstractRulePathCreator): + + service_type = SERVICE_TYPES.EC2 + supported_rules = EC2Rules diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/data_parsing/rule_path_building/rule_path_creators/elb_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/data_parsing/rule_path_building/rule_path_creators/elb_rule_path_creator.py new file mode 100644 index 000000000..055c61744 --- /dev/null +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/data_parsing/rule_path_building/rule_path_creators/elb_rule_path_creator.py @@ -0,0 +1,9 @@ +from ..abstract_rule_path_creator import AbstractRulePathCreator +from ....consts.rule_names.elb_rules import ELBRules +from ....consts.service_consts import SERVICE_TYPES + + +class ELBRulePathCreator(AbstractRulePathCreator): + + service_type = SERVICE_TYPES.ELB + supported_rules = ELBRules diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/data_parsing/rule_path_building/rule_path_creators/elbv2_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/data_parsing/rule_path_building/rule_path_creators/elbv2_rule_path_creator.py new file mode 100644 index 000000000..d45303f88 --- /dev/null +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/data_parsing/rule_path_building/rule_path_creators/elbv2_rule_path_creator.py @@ -0,0 +1,9 @@ +from ..abstract_rule_path_creator import AbstractRulePathCreator +from ....consts.rule_names.elbv2_rules import ELBv2Rules +from ....consts.service_consts import SERVICE_TYPES + + +class ELBv2RulePathCreator(AbstractRulePathCreator): + + service_type = SERVICE_TYPES.ELB_V2 + supported_rules = ELBv2Rules diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/data_parsing/rule_path_building/rule_path_creators/iam_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/data_parsing/rule_path_building/rule_path_creators/iam_rule_path_creator.py new file mode 100644 index 000000000..b131cd43b --- /dev/null +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/data_parsing/rule_path_building/rule_path_creators/iam_rule_path_creator.py @@ -0,0 +1,9 @@ +from ..abstract_rule_path_creator import AbstractRulePathCreator +from ....consts.rule_names.iam_rules import IAMRules +from ....consts.service_consts import SERVICE_TYPES + + +class IAMRulePathCreator(AbstractRulePathCreator): + + service_type = SERVICE_TYPES.IAM + supported_rules = IAMRules diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/data_parsing/rule_path_building/rule_path_creators/rds_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/data_parsing/rule_path_building/rule_path_creators/rds_rule_path_creator.py new file mode 100644 index 000000000..ac08a51b5 --- /dev/null +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/data_parsing/rule_path_building/rule_path_creators/rds_rule_path_creator.py @@ -0,0 +1,9 @@ +from ..abstract_rule_path_creator import AbstractRulePathCreator +from ....consts.rule_names.rds_rules import RDSRules +from ....consts.service_consts import SERVICE_TYPES + + +class RDSRulePathCreator(AbstractRulePathCreator): + + service_type = SERVICE_TYPES.RDS + supported_rules = RDSRules diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/data_parsing/rule_path_building/rule_path_creators/redshift_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/data_parsing/rule_path_building/rule_path_creators/redshift_rule_path_creator.py new file mode 100644 index 000000000..e567dec35 --- /dev/null +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/data_parsing/rule_path_building/rule_path_creators/redshift_rule_path_creator.py @@ -0,0 +1,9 @@ +from ..abstract_rule_path_creator import AbstractRulePathCreator +from ....consts.rule_names.redshift_rules import RedshiftRules +from ....consts.service_consts import SERVICE_TYPES + + +class RedshiftRulePathCreator(AbstractRulePathCreator): + + service_type = SERVICE_TYPES.REDSHIFT + supported_rules = RedshiftRules diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/data_parsing/rule_path_building/rule_path_creators/s3_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/data_parsing/rule_path_building/rule_path_creators/s3_rule_path_creator.py new file mode 100644 index 000000000..67be3914b --- /dev/null +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/data_parsing/rule_path_building/rule_path_creators/s3_rule_path_creator.py @@ -0,0 +1,9 @@ +from ..abstract_rule_path_creator import AbstractRulePathCreator +from ....consts.rule_names.s3_rules import S3Rules +from ....consts.service_consts import SERVICE_TYPES + + +class S3RulePathCreator(AbstractRulePathCreator): + + service_type = SERVICE_TYPES.S3 + supported_rules = S3Rules diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/data_parsing/rule_path_building/rule_path_creators/ses_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/data_parsing/rule_path_building/rule_path_creators/ses_rule_path_creator.py new file mode 100644 index 000000000..664dc9bc5 --- /dev/null +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/data_parsing/rule_path_building/rule_path_creators/ses_rule_path_creator.py @@ -0,0 +1,9 @@ +from ..abstract_rule_path_creator import AbstractRulePathCreator +from ....consts.rule_names.ses_rules import SESRules +from ....consts.service_consts import SERVICE_TYPES + + +class SESRulePathCreator(AbstractRulePathCreator): + + service_type = SERVICE_TYPES.SES + supported_rules = SESRules diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/data_parsing/rule_path_building/rule_path_creators/sns_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/data_parsing/rule_path_building/rule_path_creators/sns_rule_path_creator.py new file mode 100644 index 000000000..19189d258 --- /dev/null +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/data_parsing/rule_path_building/rule_path_creators/sns_rule_path_creator.py @@ -0,0 +1,9 @@ +from ..abstract_rule_path_creator import AbstractRulePathCreator +from ....consts.rule_names.sns_rules import SNSRules +from ....consts.service_consts import SERVICE_TYPES + + +class SNSRulePathCreator(AbstractRulePathCreator): + + service_type = SERVICE_TYPES.SNS + supported_rules = SNSRules diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/data_parsing/rule_path_building/rule_path_creators/sqs_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/data_parsing/rule_path_building/rule_path_creators/sqs_rule_path_creator.py new file mode 100644 index 000000000..214d19127 --- /dev/null +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/data_parsing/rule_path_building/rule_path_creators/sqs_rule_path_creator.py @@ -0,0 +1,9 @@ +from ..abstract_rule_path_creator import AbstractRulePathCreator +from ....consts.rule_names.sqs_rules import SQSRules +from ....consts.service_consts import SERVICE_TYPES + + +class SQSRulePathCreator(AbstractRulePathCreator): + + service_type = SERVICE_TYPES.SQS + supported_rules = SQSRules diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/data_parsing/rule_path_building/rule_path_creators/vpc_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/data_parsing/rule_path_building/rule_path_creators/vpc_rule_path_creator.py new file mode 100644 index 000000000..53abe3932 --- /dev/null +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/data_parsing/rule_path_building/rule_path_creators/vpc_rule_path_creator.py @@ -0,0 +1,9 @@ +from ..abstract_rule_path_creator import AbstractRulePathCreator +from ....consts.rule_names.vpc_rules import VPCRules +from ....consts.service_consts import SERVICE_TYPES + + +class VPCRulePathCreator(AbstractRulePathCreator): + + service_type = SERVICE_TYPES.VPC + supported_rules = VPCRules diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/data_parsing/rule_path_building/rule_path_creators_list.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/data_parsing/rule_path_building/rule_path_creators_list.py new file mode 100644 index 000000000..b69aa985b --- /dev/null +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/data_parsing/rule_path_building/rule_path_creators_list.py @@ -0,0 +1,35 @@ +from monkey_island.cc.services.zero_trust.scoutsuite_findings.data_parsing.rule_path_building.rule_path_creators.\ + cloudformation_rule_path_creator import CloudformationRulePathCreator +from monkey_island.cc.services.zero_trust.scoutsuite_findings.data_parsing.rule_path_building.rule_path_creators.\ + cloudtrail_rule_path_creator import CloudTrailRulePathCreator +from monkey_island.cc.services.zero_trust.scoutsuite_findings.data_parsing.rule_path_building.rule_path_creators.\ + cloudwatch_rule_path_creator import CloudWatchRulePathCreator +from monkey_island.cc.services.zero_trust.scoutsuite_findings.data_parsing.rule_path_building.rule_path_creators.\ + config_rule_path_creator import ConfigRulePathCreator +from monkey_island.cc.services.zero_trust.scoutsuite_findings.data_parsing.rule_path_building.rule_path_creators.\ + ec2_rule_path_creator import EC2RulePathCreator +from monkey_island.cc.services.zero_trust.scoutsuite_findings.data_parsing.rule_path_building.rule_path_creators.\ + elb_rule_path_creator import ELBRulePathCreator +from monkey_island.cc.services.zero_trust.scoutsuite_findings.data_parsing.rule_path_building.rule_path_creators.\ + elbv2_rule_path_creator import ELBv2RulePathCreator +from monkey_island.cc.services.zero_trust.scoutsuite_findings.data_parsing.rule_path_building.rule_path_creators.\ + iam_rule_path_creator import IAMRulePathCreator +from monkey_island.cc.services.zero_trust.scoutsuite_findings.data_parsing.rule_path_building.rule_path_creators.\ + rds_rule_path_creator import RDSRulePathCreator +from monkey_island.cc.services.zero_trust.scoutsuite_findings.data_parsing.rule_path_building.rule_path_creators.\ + redshift_rule_path_creator import RedshiftRulePathCreator +from monkey_island.cc.services.zero_trust.scoutsuite_findings.data_parsing.rule_path_building.rule_path_creators.\ + s3_rule_path_creator import S3RulePathCreator +from monkey_island.cc.services.zero_trust.scoutsuite_findings.data_parsing.rule_path_building.rule_path_creators.\ + ses_rule_path_creator import SESRulePathCreator +from monkey_island.cc.services.zero_trust.scoutsuite_findings.data_parsing.rule_path_building.rule_path_creators.\ + sns_rule_path_creator import SNSRulePathCreator +from monkey_island.cc.services.zero_trust.scoutsuite_findings.data_parsing.rule_path_building.rule_path_creators. \ + sqs_rule_path_creator import SQSRulePathCreator +from monkey_island.cc.services.zero_trust.scoutsuite_findings.data_parsing.rule_path_building.rule_path_creators. \ + vpc_rule_path_creator import VPCRulePathCreator + +RULE_PATH_CREATORS_LIST = [EC2RulePathCreator, ELBv2RulePathCreator, RDSRulePathCreator, RedshiftRulePathCreator, + S3RulePathCreator, IAMRulePathCreator, CloudTrailRulePathCreator, ELBRulePathCreator, + VPCRulePathCreator, CloudWatchRulePathCreator, SQSRulePathCreator, SNSRulePathCreator, + SESRulePathCreator, ConfigRulePathCreator, CloudformationRulePathCreator] diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_auth_service.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/scoutsuite_auth_service.py similarity index 100% rename from monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_auth_service.py rename to monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/scoutsuite_auth_service.py diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_rule_service.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/scoutsuite_rule_service.py similarity index 92% rename from monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_rule_service.py rename to monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/scoutsuite_rule_service.py index 3b76194af..77d9c52f2 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_rule_service.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/scoutsuite_rule_service.py @@ -1,5 +1,5 @@ from monkey_island.cc.models.zero_trust.scoutsuite_rule import ScoutSuiteRule -from monkey_island.cc.services.zero_trust.scoutsuite.consts import rule_consts +from monkey_island.cc.services.zero_trust.scoutsuite_findings.consts import rule_consts class ScoutSuiteRuleService: diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_finding_service.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/scoutsuite_zt_finding_service.py similarity index 75% rename from monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_finding_service.py rename to monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/scoutsuite_zt_finding_service.py index eff9e64b0..a75dc838c 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_finding_service.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/scoutsuite_zt_finding_service.py @@ -4,11 +4,11 @@ from common.common_consts import zero_trust_consts from monkey_island.cc.models.zero_trust.finding import Finding from monkey_island.cc.models.zero_trust.scoutsuite_finding_details import ScoutSuiteFindingDetails from monkey_island.cc.models.zero_trust.scoutsuite_rule import ScoutSuiteRule -from monkey_island.cc.services.zero_trust.scoutsuite.consts.findings import ScoutSuiteFinding -from monkey_island.cc.services.zero_trust.scoutsuite.scoutsuite_rule_service import ScoutSuiteRuleService +from monkey_island.cc.services.zero_trust.scoutsuite_findings.consts.findings import ScoutSuiteFinding +from monkey_island.cc.services.zero_trust.scoutsuite_findings.scoutsuite_rule_service import ScoutSuiteRuleService -class ScoutSuiteFindingService: +class ScoutSuiteZTFindingService: @staticmethod def process_rule(finding: ScoutSuiteFinding, rule: ScoutSuiteRule): @@ -16,16 +16,16 @@ class ScoutSuiteFindingService: assert (len(existing_findings) < 2), "More than one finding exists for {}".format(finding.test) if len(existing_findings) == 0: - ScoutSuiteFindingService.create_new_finding_from_rule(finding, rule) + ScoutSuiteZTFindingService.create_new_finding_from_rule(finding, rule) else: - ScoutSuiteFindingService.add_rule(existing_findings[0], rule) + ScoutSuiteZTFindingService.add_rule(existing_findings[0], rule) @staticmethod def create_new_finding_from_rule(finding: ScoutSuiteFinding, rule: ScoutSuiteRule): details = ScoutSuiteFindingDetails() details.scoutsuite_rules = [rule] details.save() - status = ScoutSuiteFindingService.get_finding_status_from_rules(details.scoutsuite_rules) + status = ScoutSuiteZTFindingService.get_finding_status_from_rules(details.scoutsuite_rules) Finding.save_finding(finding.test, status, details) @staticmethod @@ -41,15 +41,15 @@ class ScoutSuiteFindingService: @staticmethod def add_rule(finding: Finding, rule: ScoutSuiteRule): - ScoutSuiteFindingService.change_finding_status_by_rule(finding, rule) + ScoutSuiteZTFindingService.change_finding_status_by_rule(finding, rule) finding.save() finding.details.fetch().add_rule(rule) @staticmethod def change_finding_status_by_rule(finding: Finding, rule: ScoutSuiteRule): - rule_status = ScoutSuiteFindingService.get_finding_status_from_rules([rule]) + rule_status = ScoutSuiteZTFindingService.get_finding_status_from_rules([rule]) finding_status = finding.status - new_finding_status = ScoutSuiteFindingService.get_finding_status_from_rule_status(finding_status, rule_status) + new_finding_status = ScoutSuiteZTFindingService.get_finding_status_from_rule_status(finding_status, rule_status) if finding_status != new_finding_status: finding.status = new_finding_status diff --git a/monkey/monkey_island/cc/services/zero_trust/finding_service.py b/monkey/monkey_island/cc/services/zero_trust/zero_trust_finding_service.py similarity index 77% rename from monkey/monkey_island/cc/services/zero_trust/finding_service.py rename to monkey/monkey_island/cc/services/zero_trust/zero_trust_finding_service.py index 02459eb0e..92f5aebe0 100644 --- a/monkey/monkey_island/cc/services/zero_trust/finding_service.py +++ b/monkey/monkey_island/cc/services/zero_trust/zero_trust_finding_service.py @@ -3,28 +3,28 @@ from typing import List from common.common_consts import zero_trust_consts from common.utils.exceptions import UnknownFindingError from monkey_island.cc.models.zero_trust.finding import Finding -from monkey_island.cc.services.zero_trust.monkey_details_service import MonkeyDetailsService +from monkey_island.cc.services.zero_trust.monkey_findings.monkey_zt_details_service import MonkeyZTDetailsService -class FindingService: +class ZeroTrustFindingService: @staticmethod def get_all_findings() -> List[Finding]: findings = list(Finding.objects) for i in range(len(findings)): if findings[i].finding_type == zero_trust_consts.MONKEY_FINDING: - details = MonkeyDetailsService.fetch_details_for_display(findings[i].details.id) + details = MonkeyZTDetailsService.fetch_details_for_display(findings[i].details.id) elif findings[i].finding_type == zero_trust_consts.SCOUTSUITE_FINDING: details = findings[i].details.fetch().to_mongo() else: raise UnknownFindingError(f"Unknown finding type {findings[i].finding_type}") findings[i] = findings[i].to_mongo() - findings[i] = FindingService._get_enriched_finding(findings[i]) + findings[i] = ZeroTrustFindingService._get_enriched_finding(findings[i]) findings[i]['details'] = details return findings @staticmethod - def _get_enriched_finding(finding): + def _get_enriched_finding(finding: Finding) -> dict: test_info = zero_trust_consts.TESTS_MAP[finding['test']] enriched_finding = { 'finding_id': str(finding['_id']), From 3a9aa3191f8ee6a7a77fae4907a7c8a3ac886b33 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Fri, 15 Jan 2021 15:10:07 +0200 Subject: [PATCH 112/152] Separated zero trust and security report resources --- monkey/monkey_island/cc/app.py | 14 +++--- .../cc/resources/reporting/__init__.py | 0 .../cc/resources/reporting/report.py | 48 ------------------- .../cc/resources/security_report.py | 11 +++++ .../resources/zero_trust/zero_trust_report.py | 38 +++++++++++++++ .../cc/ui/src/components/pages/ReportPage.js | 10 ++-- 6 files changed, 60 insertions(+), 61 deletions(-) delete mode 100644 monkey/monkey_island/cc/resources/reporting/__init__.py delete mode 100644 monkey/monkey_island/cc/resources/reporting/report.py create mode 100644 monkey/monkey_island/cc/resources/security_report.py create mode 100644 monkey/monkey_island/cc/resources/zero_trust/zero_trust_report.py diff --git a/monkey/monkey_island/cc/app.py b/monkey/monkey_island/cc/app.py index 848d596d9..c53c04caa 100644 --- a/monkey/monkey_island/cc/app.py +++ b/monkey/monkey_island/cc/app.py @@ -7,6 +7,7 @@ from werkzeug.exceptions import NotFound import monkey_island.cc.environment.environment_singleton as env_singleton from common.common_consts.api_url_consts import T1216_PBA_FILE_DOWNLOAD_PATH +from monkey_island.cc.resources.zero_trust.zero_trust_report import ZeroTrustReport from monkey_island.cc.server_utils.consts import MONKEY_ISLAND_ABS_PATH from monkey_island.cc.server_utils.custom_json_encoder import CustomJSONEncoder from monkey_island.cc.database import database, mongo @@ -33,7 +34,7 @@ from monkey_island.cc.resources.node_states import NodeStates from monkey_island.cc.resources.pba_file_download import PBAFileDownload from monkey_island.cc.resources.pba_file_upload import FileUpload from monkey_island.cc.resources.remote_run import RemoteRun -from monkey_island.cc.resources.reporting.report import Report +from monkey_island.cc.resources.security_report import SecurityReport from monkey_island.cc.resources.root import Root from monkey_island.cc.resources.T1216_pba_file_download import T1216PBAFileDownload from monkey_island.cc.resources.telemetry import Telemetry @@ -123,13 +124,11 @@ def init_api_resources(api): api.add_resource(Node, '/api/netmap/node', '/api/netmap/node/') api.add_resource(NodeStates, '/api/netmap/nodeStates') - # report_type: zero_trust or security - api.add_resource( - Report, - '/api/report/', - '/api/report//') - api.add_resource(ZeroTrustFindingEvent, '/api/zero-trust/finding-event/') + api.add_resource(SecurityReport, '/api/report/security') + api.add_resource(ZeroTrustReport, '/api/report/zero-trust/') + api.add_resource(AttackReport, '/api/report/attack') + api.add_resource(ZeroTrustFindingEvent, '/api/zero-trust/finding-event/') api.add_resource(TelemetryFeed, '/api/telemetry-feed', '/api/telemetry-feed/') api.add_resource(Log, '/api/log', '/api/log/') api.add_resource(IslandLog, '/api/log/island/download', '/api/log/island/download/') @@ -140,7 +139,6 @@ def init_api_resources(api): '/api/fileUpload/?restore=') api.add_resource(RemoteRun, '/api/remote-monkey', '/api/remote-monkey/') api.add_resource(AttackConfiguration, '/api/attack') - api.add_resource(AttackReport, '/api/attack/report') api.add_resource(VersionUpdate, '/api/version-update', '/api/version-update/') api.add_resource(RemotePortCheck, '/api/monkey_control/check_remote_port/') api.add_resource(StartedOnIsland, '/api/monkey_control/started_on_island') diff --git a/monkey/monkey_island/cc/resources/reporting/__init__.py b/monkey/monkey_island/cc/resources/reporting/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/monkey/monkey_island/cc/resources/reporting/report.py b/monkey/monkey_island/cc/resources/reporting/report.py deleted file mode 100644 index 1e262a997..000000000 --- a/monkey/monkey_island/cc/resources/reporting/report.py +++ /dev/null @@ -1,48 +0,0 @@ -import http.client - -import flask_restful -from flask import Response, jsonify - -from monkey_island.cc.models.zero_trust.scoutsuite_data_json import ScoutSuiteDataJson -from monkey_island.cc.resources.auth.auth import jwt_required -from monkey_island.cc.services.reporting.report import ReportService -from monkey_island.cc.services.zero_trust.finding_service import FindingService -from monkey_island.cc.services.zero_trust.zero_trust_service import ZeroTrustService - -ZERO_TRUST_REPORT_TYPE = "zero_trust" -SECURITY_REPORT_TYPE = "security" -REPORT_TYPES = [SECURITY_REPORT_TYPE, ZERO_TRUST_REPORT_TYPE] - -REPORT_DATA_PILLARS = "pillars" -REPORT_DATA_FINDINGS = "findings" -REPORT_DATA_PRINCIPLES_STATUS = "principles" -REPORT_DATA_SCOUTSUITE = "scoutsuite" - -__author__ = ["itay.mizeretz", "shay.nehmad"] - - -class Report(flask_restful.Resource): - - @jwt_required - def get(self, report_type=SECURITY_REPORT_TYPE, report_data=None): - if report_type == SECURITY_REPORT_TYPE: - return ReportService.get_report() - elif report_type == ZERO_TRUST_REPORT_TYPE: - if report_data == REPORT_DATA_PILLARS: - return jsonify({ - "statusesToPillars": ZeroTrustService.get_statuses_to_pillars(), - "pillarsToStatuses": ZeroTrustService.get_pillars_to_statuses(), - "grades": ZeroTrustService.get_pillars_grades() - }) - elif report_data == REPORT_DATA_PRINCIPLES_STATUS: - return jsonify(ZeroTrustService.get_principles_status()) - elif report_data == REPORT_DATA_FINDINGS: - return jsonify(FindingService.get_all_findings()) - elif report_data == REPORT_DATA_SCOUTSUITE: - try: - data = ScoutSuiteDataJson.objects.get().scoutsuite_data - except Exception: - data = "{}" - return Response(data, mimetype='application/json') - - flask_restful.abort(http.client.NOT_FOUND) diff --git a/monkey/monkey_island/cc/resources/security_report.py b/monkey/monkey_island/cc/resources/security_report.py new file mode 100644 index 000000000..db434d616 --- /dev/null +++ b/monkey/monkey_island/cc/resources/security_report.py @@ -0,0 +1,11 @@ +import flask_restful + +from monkey_island.cc.resources.auth.auth import jwt_required +from monkey_island.cc.services.reporting.report import ReportService + + +class SecurityReport(flask_restful.Resource): + + @jwt_required + def get(self): + return ReportService.get_report() diff --git a/monkey/monkey_island/cc/resources/zero_trust/zero_trust_report.py b/monkey/monkey_island/cc/resources/zero_trust/zero_trust_report.py new file mode 100644 index 000000000..0261ccb7f --- /dev/null +++ b/monkey/monkey_island/cc/resources/zero_trust/zero_trust_report.py @@ -0,0 +1,38 @@ +import http.client + +import flask_restful +from flask import Response, jsonify + +from monkey_island.cc.models.zero_trust.scoutsuite_data_json import ScoutSuiteDataJson +from monkey_island.cc.resources.auth.auth import jwt_required +from monkey_island.cc.services.zero_trust.zero_trust_finding_service import ZeroTrustFindingService +from monkey_island.cc.services.zero_trust.zero_trust_service import ZeroTrustService + +REPORT_DATA_PILLARS = "pillars" +REPORT_DATA_FINDINGS = "findings" +REPORT_DATA_PRINCIPLES_STATUS = "principles" +REPORT_DATA_SCOUTSUITE = "scoutsuite" + + +class ZeroTrustReport(flask_restful.Resource): + + @jwt_required + def get(self, report_data=None): + if report_data == REPORT_DATA_PILLARS: + return jsonify({ + "statusesToPillars": ZeroTrustService.get_statuses_to_pillars(), + "pillarsToStatuses": ZeroTrustService.get_pillars_to_statuses(), + "grades": ZeroTrustService.get_pillars_grades() + }) + elif report_data == REPORT_DATA_PRINCIPLES_STATUS: + return jsonify(ZeroTrustService.get_principles_status()) + elif report_data == REPORT_DATA_FINDINGS: + return jsonify(ZeroTrustFindingService.get_all_findings()) + elif report_data == REPORT_DATA_SCOUTSUITE: + try: + data = ScoutSuiteDataJson.objects.get().scoutsuite_data + except Exception: + data = "{}" + return Response(data, mimetype='application/json') + + flask_restful.abort(http.client.NOT_FOUND) diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js b/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js index e0b458c8b..4ce777f1e 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js @@ -46,7 +46,7 @@ class ReportPageComponent extends AuthComponent { securityReport: res }); }); - this.authFetch('/api/attack/report') + this.authFetch('/api/report/attack') .then(res => res.json()) .then(res => { this.setState({ @@ -61,22 +61,22 @@ class ReportPageComponent extends AuthComponent { getZeroTrustReportFromServer = async () => { let ztReport = {findings: {}, principles: {}, pillars: {}, scoutsuite_data: {}}; - await this.authFetch('/api/report/zero_trust/findings') + await this.authFetch('/api/report/zero-trust/findings') .then(res => res.json()) .then(res => { ztReport.findings = res; }); - await this.authFetch('/api/report/zero_trust/principles') + await this.authFetch('/api/report/zero-trust/principles') .then(res => res.json()) .then(res => { ztReport.principles = res; }); - await this.authFetch('/api/report/zero_trust/pillars') + await this.authFetch('/api/report/zero-trust/pillars') .then(res => res.json()) .then(res => { ztReport.pillars = res; }); - await this.authFetch('/api/report/zero_trust/scoutsuite') + await this.authFetch('/api/report/zero-trust/scoutsuite') .then(res => res.json()) .then(res => { ztReport.scoutsuite_data = res; From 01feea905b3680be13da27320e903a5dd5151f71 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Fri, 15 Jan 2021 15:34:59 +0200 Subject: [PATCH 113/152] Refactored "scoutsuite_findings" directory back to "scoutsuite" directory, because it doesn't only parse findings --- .../cc/models/zero_trust/scoutsuite_rule.py | 2 +- .../zero_trust/scoutsuite_auth/aws_keys.py | 2 +- .../scoutsuite_auth/scoutsuite_auth.py | 4 +-- .../resources/zero_trust/zero_trust_report.py | 4 +-- .../telemetry/processing/scoutsuite.py | 8 ++--- .../zero_trust/report_data/__init__.py | 0 .../finding_service.py} | 4 +-- .../test_zero_trust_service.py | 0 .../__init__.py | 0 .../consts/findings.py | 0 .../consts/findings_list.py | 0 .../consts/rule_consts.py | 0 .../consts/rule_names/cloudformation_rules.py | 0 .../consts/rule_names/cloudtrail_rules.py | 0 .../consts/rule_names/cloudwatch_rules.py | 0 .../consts/rule_names/config_rules.py | 0 .../consts/rule_names/ec2_rules.py | 0 .../consts/rule_names/elb_rules.py | 0 .../consts/rule_names/elbv2_rules.py | 0 .../consts/rule_names/iam_rules.py | 0 .../consts/rule_names/rds_rules.py | 0 .../consts/rule_names/redshift_rules.py | 0 .../consts/rule_names/s3_rules.py | 0 .../consts/rule_names/ses_rules.py | 0 .../consts/rule_names/sns_rules.py | 0 .../consts/rule_names/sqs_rules.py | 0 .../consts/rule_names/vpc_rules.py | 0 .../consts/service_consts.py | 0 .../data_parsing/rule_parser.py | 2 +- .../abstract_rule_path_creator.py | 0 .../cloudformation_rule_path_creator.py | 0 .../cloudtrail_rule_path_creator.py | 0 .../cloudwatch_rule_path_creator.py | 0 .../config_rule_path_creator.py | 0 .../ec2_rule_path_creator.py | 0 .../elb_rule_path_creator.py | 0 .../elbv2_rule_path_creator.py | 0 .../iam_rule_path_creator.py | 0 .../rds_rule_path_creator.py | 0 .../redshift_rule_path_creator.py | 0 .../s3_rule_path_creator.py | 0 .../ses_rule_path_creator.py | 0 .../sns_rule_path_creator.py | 0 .../sqs_rule_path_creator.py | 0 .../vpc_rule_path_creator.py | 0 .../rule_path_creators_list.py | 35 +++++++++++++++++++ .../scoutsuite_auth_service.py | 0 .../scoutsuite_rule_service.py | 2 +- .../scoutsuite_zt_finding_service.py | 4 +-- .../rule_path_creators_list.py | 35 ------------------- 50 files changed, 51 insertions(+), 51 deletions(-) create mode 100644 monkey/monkey_island/cc/services/zero_trust/report_data/__init__.py rename monkey/monkey_island/cc/services/zero_trust/{zero_trust_finding_service.py => report_data/finding_service.py} (93%) rename monkey/monkey_island/cc/services/zero_trust/{ => report_data}/test_zero_trust_service.py (100%) rename monkey/monkey_island/cc/services/zero_trust/{scoutsuite_findings => scoutsuite}/__init__.py (100%) rename monkey/monkey_island/cc/services/zero_trust/{scoutsuite_findings => scoutsuite}/consts/findings.py (100%) rename monkey/monkey_island/cc/services/zero_trust/{scoutsuite_findings => scoutsuite}/consts/findings_list.py (100%) rename monkey/monkey_island/cc/services/zero_trust/{scoutsuite_findings => scoutsuite}/consts/rule_consts.py (100%) rename monkey/monkey_island/cc/services/zero_trust/{scoutsuite_findings => scoutsuite}/consts/rule_names/cloudformation_rules.py (100%) rename monkey/monkey_island/cc/services/zero_trust/{scoutsuite_findings => scoutsuite}/consts/rule_names/cloudtrail_rules.py (100%) rename monkey/monkey_island/cc/services/zero_trust/{scoutsuite_findings => scoutsuite}/consts/rule_names/cloudwatch_rules.py (100%) rename monkey/monkey_island/cc/services/zero_trust/{scoutsuite_findings => scoutsuite}/consts/rule_names/config_rules.py (100%) rename monkey/monkey_island/cc/services/zero_trust/{scoutsuite_findings => scoutsuite}/consts/rule_names/ec2_rules.py (100%) rename monkey/monkey_island/cc/services/zero_trust/{scoutsuite_findings => scoutsuite}/consts/rule_names/elb_rules.py (100%) rename monkey/monkey_island/cc/services/zero_trust/{scoutsuite_findings => scoutsuite}/consts/rule_names/elbv2_rules.py (100%) rename monkey/monkey_island/cc/services/zero_trust/{scoutsuite_findings => scoutsuite}/consts/rule_names/iam_rules.py (100%) rename monkey/monkey_island/cc/services/zero_trust/{scoutsuite_findings => scoutsuite}/consts/rule_names/rds_rules.py (100%) rename monkey/monkey_island/cc/services/zero_trust/{scoutsuite_findings => scoutsuite}/consts/rule_names/redshift_rules.py (100%) rename monkey/monkey_island/cc/services/zero_trust/{scoutsuite_findings => scoutsuite}/consts/rule_names/s3_rules.py (100%) rename monkey/monkey_island/cc/services/zero_trust/{scoutsuite_findings => scoutsuite}/consts/rule_names/ses_rules.py (100%) rename monkey/monkey_island/cc/services/zero_trust/{scoutsuite_findings => scoutsuite}/consts/rule_names/sns_rules.py (100%) rename monkey/monkey_island/cc/services/zero_trust/{scoutsuite_findings => scoutsuite}/consts/rule_names/sqs_rules.py (100%) rename monkey/monkey_island/cc/services/zero_trust/{scoutsuite_findings => scoutsuite}/consts/rule_names/vpc_rules.py (100%) rename monkey/monkey_island/cc/services/zero_trust/{scoutsuite_findings => scoutsuite}/consts/service_consts.py (100%) rename monkey/monkey_island/cc/services/zero_trust/{scoutsuite_findings => scoutsuite}/data_parsing/rule_parser.py (88%) rename monkey/monkey_island/cc/services/zero_trust/{scoutsuite_findings => scoutsuite}/data_parsing/rule_path_building/abstract_rule_path_creator.py (100%) rename monkey/monkey_island/cc/services/zero_trust/{scoutsuite_findings => scoutsuite}/data_parsing/rule_path_building/rule_path_creators/cloudformation_rule_path_creator.py (100%) rename monkey/monkey_island/cc/services/zero_trust/{scoutsuite_findings => scoutsuite}/data_parsing/rule_path_building/rule_path_creators/cloudtrail_rule_path_creator.py (100%) rename monkey/monkey_island/cc/services/zero_trust/{scoutsuite_findings => scoutsuite}/data_parsing/rule_path_building/rule_path_creators/cloudwatch_rule_path_creator.py (100%) rename monkey/monkey_island/cc/services/zero_trust/{scoutsuite_findings => scoutsuite}/data_parsing/rule_path_building/rule_path_creators/config_rule_path_creator.py (100%) rename monkey/monkey_island/cc/services/zero_trust/{scoutsuite_findings => scoutsuite}/data_parsing/rule_path_building/rule_path_creators/ec2_rule_path_creator.py (100%) rename monkey/monkey_island/cc/services/zero_trust/{scoutsuite_findings => scoutsuite}/data_parsing/rule_path_building/rule_path_creators/elb_rule_path_creator.py (100%) rename monkey/monkey_island/cc/services/zero_trust/{scoutsuite_findings => scoutsuite}/data_parsing/rule_path_building/rule_path_creators/elbv2_rule_path_creator.py (100%) rename monkey/monkey_island/cc/services/zero_trust/{scoutsuite_findings => scoutsuite}/data_parsing/rule_path_building/rule_path_creators/iam_rule_path_creator.py (100%) rename monkey/monkey_island/cc/services/zero_trust/{scoutsuite_findings => scoutsuite}/data_parsing/rule_path_building/rule_path_creators/rds_rule_path_creator.py (100%) rename monkey/monkey_island/cc/services/zero_trust/{scoutsuite_findings => scoutsuite}/data_parsing/rule_path_building/rule_path_creators/redshift_rule_path_creator.py (100%) rename monkey/monkey_island/cc/services/zero_trust/{scoutsuite_findings => scoutsuite}/data_parsing/rule_path_building/rule_path_creators/s3_rule_path_creator.py (100%) rename monkey/monkey_island/cc/services/zero_trust/{scoutsuite_findings => scoutsuite}/data_parsing/rule_path_building/rule_path_creators/ses_rule_path_creator.py (100%) rename monkey/monkey_island/cc/services/zero_trust/{scoutsuite_findings => scoutsuite}/data_parsing/rule_path_building/rule_path_creators/sns_rule_path_creator.py (100%) rename monkey/monkey_island/cc/services/zero_trust/{scoutsuite_findings => scoutsuite}/data_parsing/rule_path_building/rule_path_creators/sqs_rule_path_creator.py (100%) rename monkey/monkey_island/cc/services/zero_trust/{scoutsuite_findings => scoutsuite}/data_parsing/rule_path_building/rule_path_creators/vpc_rule_path_creator.py (100%) create mode 100644 monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators_list.py rename monkey/monkey_island/cc/services/zero_trust/{scoutsuite_findings => scoutsuite}/scoutsuite_auth_service.py (100%) rename monkey/monkey_island/cc/services/zero_trust/{scoutsuite_findings => scoutsuite}/scoutsuite_rule_service.py (92%) rename monkey/monkey_island/cc/services/zero_trust/{scoutsuite_findings => scoutsuite}/scoutsuite_zt_finding_service.py (93%) delete mode 100644 monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/data_parsing/rule_path_building/rule_path_creators_list.py diff --git a/monkey/monkey_island/cc/models/zero_trust/scoutsuite_rule.py b/monkey/monkey_island/cc/models/zero_trust/scoutsuite_rule.py index 4fa37faf6..dee49983a 100644 --- a/monkey/monkey_island/cc/models/zero_trust/scoutsuite_rule.py +++ b/monkey/monkey_island/cc/models/zero_trust/scoutsuite_rule.py @@ -1,6 +1,6 @@ from mongoengine import DynamicField, EmbeddedDocument, IntField, ListField, StringField -from monkey_island.cc.services.zero_trust.scoutsuite_findings.consts import rule_consts +from monkey_island.cc.services.zero_trust.scoutsuite.consts import rule_consts class ScoutSuiteRule(EmbeddedDocument): diff --git a/monkey/monkey_island/cc/resources/zero_trust/scoutsuite_auth/aws_keys.py b/monkey/monkey_island/cc/resources/zero_trust/scoutsuite_auth/aws_keys.py index 0642333bb..53e757f11 100644 --- a/monkey/monkey_island/cc/resources/zero_trust/scoutsuite_auth/aws_keys.py +++ b/monkey/monkey_island/cc/resources/zero_trust/scoutsuite_auth/aws_keys.py @@ -1,7 +1,7 @@ import flask_restful from monkey_island.cc.resources.auth.auth import jwt_required -from monkey_island.cc.services.zero_trust.scoutsuite_findings.scoutsuite_auth_service import get_aws_keys +from monkey_island.cc.services.zero_trust.scoutsuite.scoutsuite_auth_service import get_aws_keys class AWSKeys(flask_restful.Resource): diff --git a/monkey/monkey_island/cc/resources/zero_trust/scoutsuite_auth/scoutsuite_auth.py b/monkey/monkey_island/cc/resources/zero_trust/scoutsuite_auth/scoutsuite_auth.py index ea2086dc5..dbed4dd51 100644 --- a/monkey/monkey_island/cc/resources/zero_trust/scoutsuite_auth/scoutsuite_auth.py +++ b/monkey/monkey_island/cc/resources/zero_trust/scoutsuite_auth/scoutsuite_auth.py @@ -6,8 +6,8 @@ from flask import request from common.cloud.scoutsuite_consts import CloudProviders from common.utils.exceptions import InvalidAWSKeys from monkey_island.cc.resources.auth.auth import jwt_required -from monkey_island.cc.services.zero_trust.scoutsuite_findings.scoutsuite_auth_service import (is_cloud_authentication_setup, - set_aws_keys) +from monkey_island.cc.services.zero_trust.scoutsuite.scoutsuite_auth_service import (is_cloud_authentication_setup, + set_aws_keys) class ScoutSuiteAuth(flask_restful.Resource): diff --git a/monkey/monkey_island/cc/resources/zero_trust/zero_trust_report.py b/monkey/monkey_island/cc/resources/zero_trust/zero_trust_report.py index 0261ccb7f..984b415cd 100644 --- a/monkey/monkey_island/cc/resources/zero_trust/zero_trust_report.py +++ b/monkey/monkey_island/cc/resources/zero_trust/zero_trust_report.py @@ -5,7 +5,7 @@ from flask import Response, jsonify from monkey_island.cc.models.zero_trust.scoutsuite_data_json import ScoutSuiteDataJson from monkey_island.cc.resources.auth.auth import jwt_required -from monkey_island.cc.services.zero_trust.zero_trust_finding_service import ZeroTrustFindingService +from monkey_island.cc.services.zero_trust.report_data.finding_service import FindingService from monkey_island.cc.services.zero_trust.zero_trust_service import ZeroTrustService REPORT_DATA_PILLARS = "pillars" @@ -27,7 +27,7 @@ class ZeroTrustReport(flask_restful.Resource): elif report_data == REPORT_DATA_PRINCIPLES_STATUS: return jsonify(ZeroTrustService.get_principles_status()) elif report_data == REPORT_DATA_FINDINGS: - return jsonify(ZeroTrustFindingService.get_all_findings()) + return jsonify(FindingService.get_all_findings()) elif report_data == REPORT_DATA_SCOUTSUITE: try: data = ScoutSuiteDataJson.objects.get().scoutsuite_data diff --git a/monkey/monkey_island/cc/services/telemetry/processing/scoutsuite.py b/monkey/monkey_island/cc/services/telemetry/processing/scoutsuite.py index 94904b4c7..93a597f90 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/scoutsuite.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/scoutsuite.py @@ -2,10 +2,10 @@ import json from monkey_island.cc.database import mongo from monkey_island.cc.models.zero_trust.scoutsuite_data_json import ScoutSuiteDataJson -from ...zero_trust.scoutsuite_findings.consts.findings_list import SCOUTSUITE_FINDINGS -from ...zero_trust.scoutsuite_findings.data_parsing.rule_parser import RuleParser -from ...zero_trust.scoutsuite_findings.scoutsuite_zt_finding_service import ScoutSuiteZTFindingService -from ...zero_trust.scoutsuite_findings.scoutsuite_rule_service import ScoutSuiteRuleService +from ...zero_trust.scoutsuite.consts.findings_list import SCOUTSUITE_FINDINGS +from ...zero_trust.scoutsuite.data_parsing.rule_parser import RuleParser +from ...zero_trust.scoutsuite.scoutsuite_zt_finding_service import ScoutSuiteZTFindingService +from ...zero_trust.scoutsuite.scoutsuite_rule_service import ScoutSuiteRuleService def process_scoutsuite_telemetry(telemetry_json): diff --git a/monkey/monkey_island/cc/services/zero_trust/report_data/__init__.py b/monkey/monkey_island/cc/services/zero_trust/report_data/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/monkey/monkey_island/cc/services/zero_trust/zero_trust_finding_service.py b/monkey/monkey_island/cc/services/zero_trust/report_data/finding_service.py similarity index 93% rename from monkey/monkey_island/cc/services/zero_trust/zero_trust_finding_service.py rename to monkey/monkey_island/cc/services/zero_trust/report_data/finding_service.py index 92f5aebe0..2c5322d92 100644 --- a/monkey/monkey_island/cc/services/zero_trust/zero_trust_finding_service.py +++ b/monkey/monkey_island/cc/services/zero_trust/report_data/finding_service.py @@ -6,7 +6,7 @@ from monkey_island.cc.models.zero_trust.finding import Finding from monkey_island.cc.services.zero_trust.monkey_findings.monkey_zt_details_service import MonkeyZTDetailsService -class ZeroTrustFindingService: +class FindingService: @staticmethod def get_all_findings() -> List[Finding]: @@ -19,7 +19,7 @@ class ZeroTrustFindingService: else: raise UnknownFindingError(f"Unknown finding type {findings[i].finding_type}") findings[i] = findings[i].to_mongo() - findings[i] = ZeroTrustFindingService._get_enriched_finding(findings[i]) + findings[i] = FindingService._get_enriched_finding(findings[i]) findings[i]['details'] = details return findings diff --git a/monkey/monkey_island/cc/services/zero_trust/test_zero_trust_service.py b/monkey/monkey_island/cc/services/zero_trust/report_data/test_zero_trust_service.py similarity index 100% rename from monkey/monkey_island/cc/services/zero_trust/test_zero_trust_service.py rename to monkey/monkey_island/cc/services/zero_trust/report_data/test_zero_trust_service.py diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/__init__.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/__init__.py similarity index 100% rename from monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/__init__.py rename to monkey/monkey_island/cc/services/zero_trust/scoutsuite/__init__.py diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/consts/findings.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/findings.py similarity index 100% rename from monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/consts/findings.py rename to monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/findings.py diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/consts/findings_list.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/findings_list.py similarity index 100% rename from monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/consts/findings_list.py rename to monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/findings_list.py diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/consts/rule_consts.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_consts.py similarity index 100% rename from monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/consts/rule_consts.py rename to monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_consts.py diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/consts/rule_names/cloudformation_rules.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/cloudformation_rules.py similarity index 100% rename from monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/consts/rule_names/cloudformation_rules.py rename to monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/cloudformation_rules.py diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/consts/rule_names/cloudtrail_rules.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/cloudtrail_rules.py similarity index 100% rename from monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/consts/rule_names/cloudtrail_rules.py rename to monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/cloudtrail_rules.py diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/consts/rule_names/cloudwatch_rules.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/cloudwatch_rules.py similarity index 100% rename from monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/consts/rule_names/cloudwatch_rules.py rename to monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/cloudwatch_rules.py diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/consts/rule_names/config_rules.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/config_rules.py similarity index 100% rename from monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/consts/rule_names/config_rules.py rename to monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/config_rules.py diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/consts/rule_names/ec2_rules.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/ec2_rules.py similarity index 100% rename from monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/consts/rule_names/ec2_rules.py rename to monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/ec2_rules.py diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/consts/rule_names/elb_rules.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/elb_rules.py similarity index 100% rename from monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/consts/rule_names/elb_rules.py rename to monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/elb_rules.py diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/consts/rule_names/elbv2_rules.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/elbv2_rules.py similarity index 100% rename from monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/consts/rule_names/elbv2_rules.py rename to monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/elbv2_rules.py diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/consts/rule_names/iam_rules.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/iam_rules.py similarity index 100% rename from monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/consts/rule_names/iam_rules.py rename to monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/iam_rules.py diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/consts/rule_names/rds_rules.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/rds_rules.py similarity index 100% rename from monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/consts/rule_names/rds_rules.py rename to monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/rds_rules.py diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/consts/rule_names/redshift_rules.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/redshift_rules.py similarity index 100% rename from monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/consts/rule_names/redshift_rules.py rename to monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/redshift_rules.py diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/consts/rule_names/s3_rules.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/s3_rules.py similarity index 100% rename from monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/consts/rule_names/s3_rules.py rename to monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/s3_rules.py diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/consts/rule_names/ses_rules.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/ses_rules.py similarity index 100% rename from monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/consts/rule_names/ses_rules.py rename to monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/ses_rules.py diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/consts/rule_names/sns_rules.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/sns_rules.py similarity index 100% rename from monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/consts/rule_names/sns_rules.py rename to monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/sns_rules.py diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/consts/rule_names/sqs_rules.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/sqs_rules.py similarity index 100% rename from monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/consts/rule_names/sqs_rules.py rename to monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/sqs_rules.py diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/consts/rule_names/vpc_rules.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/vpc_rules.py similarity index 100% rename from monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/consts/rule_names/vpc_rules.py rename to monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/vpc_rules.py diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/consts/service_consts.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/service_consts.py similarity index 100% rename from monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/consts/service_consts.py rename to monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/service_consts.py diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/data_parsing/rule_parser.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_parser.py similarity index 88% rename from monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/data_parsing/rule_parser.py rename to monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_parser.py index 84dba4003..c5855ddd5 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/data_parsing/rule_parser.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_parser.py @@ -1,6 +1,6 @@ from common.utils.code_utils import get_dict_value_by_path from common.utils.exceptions import RulePathCreatorNotFound -from monkey_island.cc.services.zero_trust.scoutsuite_findings.data_parsing.rule_path_building.rule_path_creators_list import \ +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators_list import \ RULE_PATH_CREATORS_LIST diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/data_parsing/rule_path_building/abstract_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/abstract_rule_path_creator.py similarity index 100% rename from monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/data_parsing/rule_path_building/abstract_rule_path_creator.py rename to monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/abstract_rule_path_creator.py diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/data_parsing/rule_path_building/rule_path_creators/cloudformation_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/cloudformation_rule_path_creator.py similarity index 100% rename from monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/data_parsing/rule_path_building/rule_path_creators/cloudformation_rule_path_creator.py rename to monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/cloudformation_rule_path_creator.py diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/data_parsing/rule_path_building/rule_path_creators/cloudtrail_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/cloudtrail_rule_path_creator.py similarity index 100% rename from monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/data_parsing/rule_path_building/rule_path_creators/cloudtrail_rule_path_creator.py rename to monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/cloudtrail_rule_path_creator.py diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/data_parsing/rule_path_building/rule_path_creators/cloudwatch_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/cloudwatch_rule_path_creator.py similarity index 100% rename from monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/data_parsing/rule_path_building/rule_path_creators/cloudwatch_rule_path_creator.py rename to monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/cloudwatch_rule_path_creator.py diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/data_parsing/rule_path_building/rule_path_creators/config_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/config_rule_path_creator.py similarity index 100% rename from monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/data_parsing/rule_path_building/rule_path_creators/config_rule_path_creator.py rename to monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/config_rule_path_creator.py diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/data_parsing/rule_path_building/rule_path_creators/ec2_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/ec2_rule_path_creator.py similarity index 100% rename from monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/data_parsing/rule_path_building/rule_path_creators/ec2_rule_path_creator.py rename to monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/ec2_rule_path_creator.py diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/data_parsing/rule_path_building/rule_path_creators/elb_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/elb_rule_path_creator.py similarity index 100% rename from monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/data_parsing/rule_path_building/rule_path_creators/elb_rule_path_creator.py rename to monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/elb_rule_path_creator.py diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/data_parsing/rule_path_building/rule_path_creators/elbv2_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/elbv2_rule_path_creator.py similarity index 100% rename from monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/data_parsing/rule_path_building/rule_path_creators/elbv2_rule_path_creator.py rename to monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/elbv2_rule_path_creator.py diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/data_parsing/rule_path_building/rule_path_creators/iam_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/iam_rule_path_creator.py similarity index 100% rename from monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/data_parsing/rule_path_building/rule_path_creators/iam_rule_path_creator.py rename to monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/iam_rule_path_creator.py diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/data_parsing/rule_path_building/rule_path_creators/rds_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/rds_rule_path_creator.py similarity index 100% rename from monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/data_parsing/rule_path_building/rule_path_creators/rds_rule_path_creator.py rename to monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/rds_rule_path_creator.py diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/data_parsing/rule_path_building/rule_path_creators/redshift_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/redshift_rule_path_creator.py similarity index 100% rename from monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/data_parsing/rule_path_building/rule_path_creators/redshift_rule_path_creator.py rename to monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/redshift_rule_path_creator.py diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/data_parsing/rule_path_building/rule_path_creators/s3_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/s3_rule_path_creator.py similarity index 100% rename from monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/data_parsing/rule_path_building/rule_path_creators/s3_rule_path_creator.py rename to monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/s3_rule_path_creator.py diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/data_parsing/rule_path_building/rule_path_creators/ses_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/ses_rule_path_creator.py similarity index 100% rename from monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/data_parsing/rule_path_building/rule_path_creators/ses_rule_path_creator.py rename to monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/ses_rule_path_creator.py diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/data_parsing/rule_path_building/rule_path_creators/sns_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/sns_rule_path_creator.py similarity index 100% rename from monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/data_parsing/rule_path_building/rule_path_creators/sns_rule_path_creator.py rename to monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/sns_rule_path_creator.py diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/data_parsing/rule_path_building/rule_path_creators/sqs_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/sqs_rule_path_creator.py similarity index 100% rename from monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/data_parsing/rule_path_building/rule_path_creators/sqs_rule_path_creator.py rename to monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/sqs_rule_path_creator.py diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/data_parsing/rule_path_building/rule_path_creators/vpc_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/vpc_rule_path_creator.py similarity index 100% rename from monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/data_parsing/rule_path_building/rule_path_creators/vpc_rule_path_creator.py rename to monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/vpc_rule_path_creator.py diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators_list.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators_list.py new file mode 100644 index 000000000..4dce7ed2b --- /dev/null +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators_list.py @@ -0,0 +1,35 @@ +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.\ + cloudformation_rule_path_creator import CloudformationRulePathCreator +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.\ + cloudtrail_rule_path_creator import CloudTrailRulePathCreator +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.\ + cloudwatch_rule_path_creator import CloudWatchRulePathCreator +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.\ + config_rule_path_creator import ConfigRulePathCreator +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.\ + ec2_rule_path_creator import EC2RulePathCreator +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.\ + elb_rule_path_creator import ELBRulePathCreator +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.\ + elbv2_rule_path_creator import ELBv2RulePathCreator +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.\ + iam_rule_path_creator import IAMRulePathCreator +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.\ + rds_rule_path_creator import RDSRulePathCreator +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.\ + redshift_rule_path_creator import RedshiftRulePathCreator +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.\ + s3_rule_path_creator import S3RulePathCreator +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.\ + ses_rule_path_creator import SESRulePathCreator +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.\ + sns_rule_path_creator import SNSRulePathCreator +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators. \ + sqs_rule_path_creator import SQSRulePathCreator +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators. \ + vpc_rule_path_creator import VPCRulePathCreator + +RULE_PATH_CREATORS_LIST = [EC2RulePathCreator, ELBv2RulePathCreator, RDSRulePathCreator, RedshiftRulePathCreator, + S3RulePathCreator, IAMRulePathCreator, CloudTrailRulePathCreator, ELBRulePathCreator, + VPCRulePathCreator, CloudWatchRulePathCreator, SQSRulePathCreator, SNSRulePathCreator, + SESRulePathCreator, ConfigRulePathCreator, CloudformationRulePathCreator] diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/scoutsuite_auth_service.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_auth_service.py similarity index 100% rename from monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/scoutsuite_auth_service.py rename to monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_auth_service.py diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/scoutsuite_rule_service.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_rule_service.py similarity index 92% rename from monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/scoutsuite_rule_service.py rename to monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_rule_service.py index 77d9c52f2..3b76194af 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/scoutsuite_rule_service.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_rule_service.py @@ -1,5 +1,5 @@ from monkey_island.cc.models.zero_trust.scoutsuite_rule import ScoutSuiteRule -from monkey_island.cc.services.zero_trust.scoutsuite_findings.consts import rule_consts +from monkey_island.cc.services.zero_trust.scoutsuite.consts import rule_consts class ScoutSuiteRuleService: diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/scoutsuite_zt_finding_service.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_zt_finding_service.py similarity index 93% rename from monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/scoutsuite_zt_finding_service.py rename to monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_zt_finding_service.py index a75dc838c..dd467741a 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/scoutsuite_zt_finding_service.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_zt_finding_service.py @@ -4,8 +4,8 @@ from common.common_consts import zero_trust_consts from monkey_island.cc.models.zero_trust.finding import Finding from monkey_island.cc.models.zero_trust.scoutsuite_finding_details import ScoutSuiteFindingDetails from monkey_island.cc.models.zero_trust.scoutsuite_rule import ScoutSuiteRule -from monkey_island.cc.services.zero_trust.scoutsuite_findings.consts.findings import ScoutSuiteFinding -from monkey_island.cc.services.zero_trust.scoutsuite_findings.scoutsuite_rule_service import ScoutSuiteRuleService +from monkey_island.cc.services.zero_trust.scoutsuite.consts.findings import ScoutSuiteFinding +from monkey_island.cc.services.zero_trust.scoutsuite.scoutsuite_rule_service import ScoutSuiteRuleService class ScoutSuiteZTFindingService: diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/data_parsing/rule_path_building/rule_path_creators_list.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/data_parsing/rule_path_building/rule_path_creators_list.py deleted file mode 100644 index b69aa985b..000000000 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite_findings/data_parsing/rule_path_building/rule_path_creators_list.py +++ /dev/null @@ -1,35 +0,0 @@ -from monkey_island.cc.services.zero_trust.scoutsuite_findings.data_parsing.rule_path_building.rule_path_creators.\ - cloudformation_rule_path_creator import CloudformationRulePathCreator -from monkey_island.cc.services.zero_trust.scoutsuite_findings.data_parsing.rule_path_building.rule_path_creators.\ - cloudtrail_rule_path_creator import CloudTrailRulePathCreator -from monkey_island.cc.services.zero_trust.scoutsuite_findings.data_parsing.rule_path_building.rule_path_creators.\ - cloudwatch_rule_path_creator import CloudWatchRulePathCreator -from monkey_island.cc.services.zero_trust.scoutsuite_findings.data_parsing.rule_path_building.rule_path_creators.\ - config_rule_path_creator import ConfigRulePathCreator -from monkey_island.cc.services.zero_trust.scoutsuite_findings.data_parsing.rule_path_building.rule_path_creators.\ - ec2_rule_path_creator import EC2RulePathCreator -from monkey_island.cc.services.zero_trust.scoutsuite_findings.data_parsing.rule_path_building.rule_path_creators.\ - elb_rule_path_creator import ELBRulePathCreator -from monkey_island.cc.services.zero_trust.scoutsuite_findings.data_parsing.rule_path_building.rule_path_creators.\ - elbv2_rule_path_creator import ELBv2RulePathCreator -from monkey_island.cc.services.zero_trust.scoutsuite_findings.data_parsing.rule_path_building.rule_path_creators.\ - iam_rule_path_creator import IAMRulePathCreator -from monkey_island.cc.services.zero_trust.scoutsuite_findings.data_parsing.rule_path_building.rule_path_creators.\ - rds_rule_path_creator import RDSRulePathCreator -from monkey_island.cc.services.zero_trust.scoutsuite_findings.data_parsing.rule_path_building.rule_path_creators.\ - redshift_rule_path_creator import RedshiftRulePathCreator -from monkey_island.cc.services.zero_trust.scoutsuite_findings.data_parsing.rule_path_building.rule_path_creators.\ - s3_rule_path_creator import S3RulePathCreator -from monkey_island.cc.services.zero_trust.scoutsuite_findings.data_parsing.rule_path_building.rule_path_creators.\ - ses_rule_path_creator import SESRulePathCreator -from monkey_island.cc.services.zero_trust.scoutsuite_findings.data_parsing.rule_path_building.rule_path_creators.\ - sns_rule_path_creator import SNSRulePathCreator -from monkey_island.cc.services.zero_trust.scoutsuite_findings.data_parsing.rule_path_building.rule_path_creators. \ - sqs_rule_path_creator import SQSRulePathCreator -from monkey_island.cc.services.zero_trust.scoutsuite_findings.data_parsing.rule_path_building.rule_path_creators. \ - vpc_rule_path_creator import VPCRulePathCreator - -RULE_PATH_CREATORS_LIST = [EC2RulePathCreator, ELBv2RulePathCreator, RDSRulePathCreator, RedshiftRulePathCreator, - S3RulePathCreator, IAMRulePathCreator, CloudTrailRulePathCreator, ELBRulePathCreator, - VPCRulePathCreator, CloudWatchRulePathCreator, SQSRulePathCreator, SNSRulePathCreator, - SESRulePathCreator, ConfigRulePathCreator, CloudformationRulePathCreator] From e69c94ae508778727a7760f0b2496fe3185a23e3 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Mon, 18 Jan 2021 12:01:33 +0200 Subject: [PATCH 114/152] Split and moved zero trust service into pillar_service.py and principle_service.py --- .../resources/zero_trust/zero_trust_report.py | 13 ++-- .../__init__.py | 0 .../finding_service.py | 0 .../zero_trust_report/pillar_service.py | 69 ++++++++++++++++++ .../principle_service.py} | 71 ++----------------- .../test_zero_trust_service.py | 0 6 files changed, 80 insertions(+), 73 deletions(-) rename monkey/monkey_island/cc/services/zero_trust/{report_data => zero_trust_report}/__init__.py (100%) rename monkey/monkey_island/cc/services/zero_trust/{report_data => zero_trust_report}/finding_service.py (100%) create mode 100644 monkey/monkey_island/cc/services/zero_trust/zero_trust_report/pillar_service.py rename monkey/monkey_island/cc/services/zero_trust/{zero_trust_service.py => zero_trust_report/principle_service.py} (50%) rename monkey/monkey_island/cc/services/zero_trust/{report_data => zero_trust_report}/test_zero_trust_service.py (100%) diff --git a/monkey/monkey_island/cc/resources/zero_trust/zero_trust_report.py b/monkey/monkey_island/cc/resources/zero_trust/zero_trust_report.py index 984b415cd..0f9feab76 100644 --- a/monkey/monkey_island/cc/resources/zero_trust/zero_trust_report.py +++ b/monkey/monkey_island/cc/resources/zero_trust/zero_trust_report.py @@ -5,8 +5,9 @@ from flask import Response, jsonify from monkey_island.cc.models.zero_trust.scoutsuite_data_json import ScoutSuiteDataJson from monkey_island.cc.resources.auth.auth import jwt_required -from monkey_island.cc.services.zero_trust.report_data.finding_service import FindingService -from monkey_island.cc.services.zero_trust.zero_trust_service import ZeroTrustService +from monkey_island.cc.services.zero_trust.zero_trust_report.finding_service import FindingService +from monkey_island.cc.services.zero_trust.zero_trust_report.pillar_service import PillarService +from monkey_island.cc.services.zero_trust.zero_trust_report.principle_service import PrincipleService REPORT_DATA_PILLARS = "pillars" REPORT_DATA_FINDINGS = "findings" @@ -20,12 +21,12 @@ class ZeroTrustReport(flask_restful.Resource): def get(self, report_data=None): if report_data == REPORT_DATA_PILLARS: return jsonify({ - "statusesToPillars": ZeroTrustService.get_statuses_to_pillars(), - "pillarsToStatuses": ZeroTrustService.get_pillars_to_statuses(), - "grades": ZeroTrustService.get_pillars_grades() + "statusesToPillars": PillarService.get_statuses_to_pillars(), + "pillarsToStatuses": PillarService.get_pillars_to_statuses(), + "grades": PillarService.get_pillars_grades() }) elif report_data == REPORT_DATA_PRINCIPLES_STATUS: - return jsonify(ZeroTrustService.get_principles_status()) + return jsonify(PrincipleService.get_principles_status()) elif report_data == REPORT_DATA_FINDINGS: return jsonify(FindingService.get_all_findings()) elif report_data == REPORT_DATA_SCOUTSUITE: diff --git a/monkey/monkey_island/cc/services/zero_trust/report_data/__init__.py b/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/__init__.py similarity index 100% rename from monkey/monkey_island/cc/services/zero_trust/report_data/__init__.py rename to monkey/monkey_island/cc/services/zero_trust/zero_trust_report/__init__.py diff --git a/monkey/monkey_island/cc/services/zero_trust/report_data/finding_service.py b/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/finding_service.py similarity index 100% rename from monkey/monkey_island/cc/services/zero_trust/report_data/finding_service.py rename to monkey/monkey_island/cc/services/zero_trust/zero_trust_report/finding_service.py diff --git a/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/pillar_service.py b/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/pillar_service.py new file mode 100644 index 000000000..67fccff8f --- /dev/null +++ b/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/pillar_service.py @@ -0,0 +1,69 @@ +import common.common_consts.zero_trust_consts as zero_trust_consts +from monkey_island.cc.models.zero_trust.finding import Finding + + +class PillarService: + + @staticmethod + def get_pillars_grades(): + pillars_grades = [] + all_findings = Finding.objects() + for pillar in zero_trust_consts.PILLARS: + pillars_grades.append(PillarService.__get_pillar_grade(pillar, all_findings)) + return pillars_grades + + @staticmethod + def __get_pillar_grade(pillar, all_findings): + pillar_grade = { + "pillar": pillar, + zero_trust_consts.STATUS_FAILED: 0, + zero_trust_consts.STATUS_VERIFY: 0, + zero_trust_consts.STATUS_PASSED: 0, + zero_trust_consts.STATUS_UNEXECUTED: 0 + } + + tests_of_this_pillar = zero_trust_consts.PILLARS_TO_TESTS[pillar] + + test_unexecuted = {} + for test in tests_of_this_pillar: + test_unexecuted[test] = True + + for finding in all_findings: + test_unexecuted[finding.test] = False + test_info = zero_trust_consts.TESTS_MAP[finding.test] + if pillar in test_info[zero_trust_consts.PILLARS_KEY]: + pillar_grade[finding.status] += 1 + + pillar_grade[zero_trust_consts.STATUS_UNEXECUTED] = list(test_unexecuted.values()).count(True) + + return pillar_grade + + @staticmethod + def get_statuses_to_pillars(): + results = { + zero_trust_consts.STATUS_FAILED: [], + zero_trust_consts.STATUS_VERIFY: [], + zero_trust_consts.STATUS_PASSED: [], + zero_trust_consts.STATUS_UNEXECUTED: [] + } + for pillar in zero_trust_consts.PILLARS: + results[PillarService.__get_status_of_single_pillar(pillar)].append(pillar) + + return results + + @staticmethod + def get_pillars_to_statuses(): + results = {} + for pillar in zero_trust_consts.PILLARS: + results[pillar] = PillarService.__get_status_of_single_pillar(pillar) + + return results + + @staticmethod + def __get_status_of_single_pillar(pillar): + all_findings = Finding.objects() + grade = PillarService.__get_pillar_grade(pillar, all_findings) + for status in zero_trust_consts.ORDERED_TEST_STATUSES: + if grade[status] > 0: + return status + return zero_trust_consts.STATUS_UNEXECUTED diff --git a/monkey/monkey_island/cc/services/zero_trust/zero_trust_service.py b/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/principle_service.py similarity index 50% rename from monkey/monkey_island/cc/services/zero_trust/zero_trust_service.py rename to monkey/monkey_island/cc/services/zero_trust/zero_trust_report/principle_service.py index 09b09689b..006cb053e 100644 --- a/monkey/monkey_island/cc/services/zero_trust/zero_trust_service.py +++ b/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/principle_service.py @@ -2,40 +2,7 @@ import common.common_consts.zero_trust_consts as zero_trust_consts from monkey_island.cc.models.zero_trust.finding import Finding -class ZeroTrustService: - @staticmethod - def get_pillars_grades(): - pillars_grades = [] - all_findings = Finding.objects() - for pillar in zero_trust_consts.PILLARS: - pillars_grades.append(ZeroTrustService.__get_pillar_grade(pillar, all_findings)) - return pillars_grades - - @staticmethod - def __get_pillar_grade(pillar, all_findings): - pillar_grade = { - "pillar": pillar, - zero_trust_consts.STATUS_FAILED: 0, - zero_trust_consts.STATUS_VERIFY: 0, - zero_trust_consts.STATUS_PASSED: 0, - zero_trust_consts.STATUS_UNEXECUTED: 0 - } - - tests_of_this_pillar = zero_trust_consts.PILLARS_TO_TESTS[pillar] - - test_unexecuted = {} - for test in tests_of_this_pillar: - test_unexecuted[test] = True - - for finding in all_findings: - test_unexecuted[finding.test] = False - test_info = zero_trust_consts.TESTS_MAP[finding.test] - if pillar in test_info[zero_trust_consts.PILLARS_KEY]: - pillar_grade[finding.status] += 1 - - pillar_grade[zero_trust_consts.STATUS_UNEXECUTED] = list(test_unexecuted.values()).count(True) - - return pillar_grade +class PrincipleService: @staticmethod def get_principles_status(): @@ -50,8 +17,8 @@ class ZeroTrustService: all_principles_statuses[pillar].append( { "principle": zero_trust_consts.PRINCIPLES[principle], - "tests": ZeroTrustService.__get_tests_status(principle_tests), - "status": ZeroTrustService.__get_principle_status(principle_tests) + "tests": PrincipleService.__get_tests_status(principle_tests), + "status": PrincipleService.__get_principle_status(principle_tests) } ) @@ -79,7 +46,7 @@ class ZeroTrustService: results.append( { "test": zero_trust_consts.TESTS_MAP[test][zero_trust_consts.TEST_EXPLANATION_KEY], - "status": ZeroTrustService.__get_lcd_worst_status_for_test(test_findings) + "status": PrincipleService.__get_lcd_worst_status_for_test(test_findings) } ) return results @@ -98,33 +65,3 @@ class ZeroTrustService: current_worst_status = finding.status return current_worst_status - - @staticmethod - def get_statuses_to_pillars(): - results = { - zero_trust_consts.STATUS_FAILED: [], - zero_trust_consts.STATUS_VERIFY: [], - zero_trust_consts.STATUS_PASSED: [], - zero_trust_consts.STATUS_UNEXECUTED: [] - } - for pillar in zero_trust_consts.PILLARS: - results[ZeroTrustService.__get_status_of_single_pillar(pillar)].append(pillar) - - return results - - @staticmethod - def get_pillars_to_statuses(): - results = {} - for pillar in zero_trust_consts.PILLARS: - results[pillar] = ZeroTrustService.__get_status_of_single_pillar(pillar) - - return results - - @staticmethod - def __get_status_of_single_pillar(pillar): - all_findings = Finding.objects() - grade = ZeroTrustService.__get_pillar_grade(pillar, all_findings) - for status in zero_trust_consts.ORDERED_TEST_STATUSES: - if grade[status] > 0: - return status - return zero_trust_consts.STATUS_UNEXECUTED diff --git a/monkey/monkey_island/cc/services/zero_trust/report_data/test_zero_trust_service.py b/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/test_zero_trust_service.py similarity index 100% rename from monkey/monkey_island/cc/services/zero_trust/report_data/test_zero_trust_service.py rename to monkey/monkey_island/cc/services/zero_trust/zero_trust_report/test_zero_trust_service.py From d4dc42adb55f32b74ee2ec27ddbee9d8a94945b4 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 19 Jan 2021 15:51:18 +0200 Subject: [PATCH 115/152] Removed the need to change server_config.json just to run tests. --- .travis.yml | 6 ----- .../cc/environment/environment_singleton.py | 4 +--- .../cc/environment/set_server_config.py | 2 +- monkey/monkey_island/cc/models/__init__.py | 11 +++------ monkey/monkey_island/cc/models/test_monkey.py | 21 ---------------- .../cc/models/zero_trust/test_event.py | 3 --- .../cc/models/zero_trust/test_finding.py | 7 ------ .../cc/services/edge/test_displayed_edge.py | 1 - .../cc/services/edge/test_edge.py | 3 --- .../cc/services/reporting/test_pth_report.py | 6 ----- .../test_environment.py | 3 --- .../test_system_info_telemetry_dispatcher.py | 3 --- .../zero_trust_checks/test_segmentation.py | 3 --- .../cc/testing/IslandTestCase.py | 24 ++++++++++++++++--- 14 files changed, 26 insertions(+), 71 deletions(-) diff --git a/.travis.yml b/.travis.yml index 5d638de04..8c32482f7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -53,12 +53,6 @@ install: # print hugo version (useful for debugging documentation build errors) - hugo version -before_script: -# Set the server config to `testing`. This is required for for the UTs to pass. -- pushd /home/travis/build/guardicore/monkey/monkey -- python monkey_island/cc/environment/set_server_config.py testing -- popd - script: # Check Python code ## Check syntax errors and fail the build if any are found. diff --git a/monkey/monkey_island/cc/environment/environment_singleton.py b/monkey/monkey_island/cc/environment/environment_singleton.py index 121c7001d..6b98d0b7c 100644 --- a/monkey/monkey_island/cc/environment/environment_singleton.py +++ b/monkey/monkey_island/cc/environment/environment_singleton.py @@ -10,13 +10,11 @@ logger = logging.getLogger(__name__) AWS = 'aws' STANDARD = 'standard' PASSWORD = 'password' -TESTING = 'testing' ENV_DICT = { STANDARD: standard.StandardEnvironment, AWS: aws.AwsEnvironment, - PASSWORD: password.PasswordEnvironment, - TESTING: testing.TestingEnvironment + PASSWORD: password.PasswordEnvironment } env = None diff --git a/monkey/monkey_island/cc/environment/set_server_config.py b/monkey/monkey_island/cc/environment/set_server_config.py index d31dd6916..9d7c036b2 100644 --- a/monkey/monkey_island/cc/environment/set_server_config.py +++ b/monkey/monkey_island/cc/environment/set_server_config.py @@ -42,7 +42,7 @@ def main(): def parse_args(): parser = argparse.ArgumentParser() - parser.add_argument("server_config", choices=["standard", "testing", "password"]) + parser.add_argument("server_config", choices=["standard", "password"]) args = parser.parse_args() return args diff --git a/monkey/monkey_island/cc/models/__init__.py b/monkey/monkey_island/cc/models/__init__.py index 9d0114b78..87626c448 100644 --- a/monkey/monkey_island/cc/models/__init__.py +++ b/monkey/monkey_island/cc/models/__init__.py @@ -10,11 +10,6 @@ from .monkey import Monkey # noqa: F401 from .monkey_ttl import MonkeyTtl # noqa: F401 from .pba_results import PbaResults # noqa: F401 -# This section sets up the DB connection according to the environment. -# If testing, use mongomock which only emulates mongo. for more information, see -# http://docs.mongoengine.org/guide/mongomock.html . -# Otherwise, use an actual mongod instance with connection parameters supplied by env. -if env_singleton.env.testing: # See monkey_island.cc.environment.testing - connect('mongoenginetest', host='mongomock://localhost') -else: - connect(db=env_singleton.env.mongo_db_name, host=env_singleton.env.mongo_db_host, port=env_singleton.env.mongo_db_port) +connect(db=env_singleton.env.mongo_db_name, + host=env_singleton.env.mongo_db_host, + port=env_singleton.env.mongo_db_port) diff --git a/monkey/monkey_island/cc/models/test_monkey.py b/monkey/monkey_island/cc/models/test_monkey.py index b2bba9aa0..5acf8220f 100644 --- a/monkey/monkey_island/cc/models/test_monkey.py +++ b/monkey/monkey_island/cc/models/test_monkey.py @@ -22,9 +22,6 @@ class TestMonkey(IslandTestCase): """ def test_is_dead(self): - self.fail_if_not_testing_env() - self.clean_monkey_db() - # Arrange alive_monkey_ttl = MonkeyTtl.create_ttl_expire_in(30) alive_monkey_ttl.save() @@ -52,9 +49,6 @@ class TestMonkey(IslandTestCase): self.assertFalse(alive_monkey.is_dead()) def test_ttl_renewal(self): - self.fail_if_not_testing_env() - self.clean_monkey_db() - # Arrange monkey = Monkey(guid=str(uuid.uuid4())) monkey.save() @@ -65,9 +59,6 @@ class TestMonkey(IslandTestCase): self.assertIsNotNone(monkey.ttl_ref) def test_get_single_monkey_by_id(self): - self.fail_if_not_testing_env() - self.clean_monkey_db() - # Arrange a_monkey = Monkey(guid=str(uuid.uuid4())) a_monkey.save() @@ -81,9 +72,6 @@ class TestMonkey(IslandTestCase): _ = Monkey.get_single_monkey_by_id("abcdefabcdefabcdefabcdef") def test_get_os(self): - self.fail_if_not_testing_env() - self.clean_monkey_db() - linux_monkey = Monkey(guid=str(uuid.uuid4()), description="Linux shay-Virtual-Machine 4.15.0-50-generic #54-Ubuntu") windows_monkey = Monkey(guid=str(uuid.uuid4()), @@ -99,9 +87,6 @@ class TestMonkey(IslandTestCase): self.assertEqual(1, len([m for m in Monkey.objects() if m.get_os() == "unknown"])) def test_get_tunneled_monkeys(self): - self.fail_if_not_testing_env() - self.clean_monkey_db() - linux_monkey = Monkey(guid=str(uuid.uuid4()), description="Linux shay-Virtual-Machine") windows_monkey = Monkey(guid=str(uuid.uuid4()), @@ -121,9 +106,6 @@ class TestMonkey(IslandTestCase): self.assertTrue(test, "Tunneling test") def test_get_label_by_id(self): - self.fail_if_not_testing_env() - self.clean_monkey_db() - hostname_example = "a_hostname" ip_example = "1.1.1.1" linux_monkey = Monkey(guid=str(uuid.uuid4()), @@ -169,9 +151,6 @@ class TestMonkey(IslandTestCase): self.assertEqual(cache_info_after_query_3.misses, 2) def test_is_monkey(self): - self.fail_if_not_testing_env() - self.clean_monkey_db() - a_monkey = Monkey(guid=str(uuid.uuid4())) a_monkey.save() diff --git a/monkey/monkey_island/cc/models/zero_trust/test_event.py b/monkey/monkey_island/cc/models/zero_trust/test_event.py index 4699dd829..a84268e40 100644 --- a/monkey/monkey_island/cc/models/zero_trust/test_event.py +++ b/monkey/monkey_island/cc/models/zero_trust/test_event.py @@ -7,9 +7,6 @@ from monkey_island.cc.testing.IslandTestCase import IslandTestCase class TestEvent(IslandTestCase): def test_create_event(self): - self.fail_if_not_testing_env() - self.clean_finding_db() - with self.assertRaises(ValidationError): _ = Event.create_event( title=None, # title required diff --git a/monkey/monkey_island/cc/models/zero_trust/test_finding.py b/monkey/monkey_island/cc/models/zero_trust/test_finding.py index bc51d83d8..dc3a274f2 100644 --- a/monkey/monkey_island/cc/models/zero_trust/test_finding.py +++ b/monkey/monkey_island/cc/models/zero_trust/test_finding.py @@ -16,11 +16,7 @@ class TestFinding(IslandTestCase): server.json file is found and loaded. """ - @pytest.mark.skip(reason="Broken during ScoutSuite refactoring, need to be fixed") def test_save_finding_validation(self): - self.fail_if_not_testing_env() - self.clean_finding_db() - with self.assertRaises(ValidationError): _ = Finding.save_finding(test="bla bla", status=zero_trust_consts.STATUS_FAILED, events=[]) @@ -29,9 +25,6 @@ class TestFinding(IslandTestCase): @pytest.mark.skip(reason="Broken during ScoutSuite refactoring, need to be fixed") def test_save_finding_sanity(self): - self.fail_if_not_testing_env() - self.clean_finding_db() - self.assertEqual(len(Finding.objects(test=zero_trust_consts.TEST_SEGMENTATION)), 0) event_example = Event.create_event( diff --git a/monkey/monkey_island/cc/services/edge/test_displayed_edge.py b/monkey/monkey_island/cc/services/edge/test_displayed_edge.py index d2a4e1f58..cd8519327 100644 --- a/monkey/monkey_island/cc/services/edge/test_displayed_edge.py +++ b/monkey/monkey_island/cc/services/edge/test_displayed_edge.py @@ -47,7 +47,6 @@ EXPLOIT_DATA_MOCK = [{ class TestDisplayedEdgeService(IslandTestCase): def test_get_displayed_edges_by_to(self): - self.clean_edge_db() dst_id = ObjectId() diff --git a/monkey/monkey_island/cc/services/edge/test_edge.py b/monkey/monkey_island/cc/services/edge/test_edge.py index 8ebf45343..bc2c40e80 100644 --- a/monkey/monkey_island/cc/services/edge/test_edge.py +++ b/monkey/monkey_island/cc/services/edge/test_edge.py @@ -19,9 +19,6 @@ class TestEdgeService(IslandTestCase): """ def test_get_or_create_edge(self): - self.fail_if_not_testing_env() - self.clean_edge_db() - src_id = ObjectId() dst_id = ObjectId() diff --git a/monkey/monkey_island/cc/services/reporting/test_pth_report.py b/monkey/monkey_island/cc/services/reporting/test_pth_report.py index b5a628fb1..fc1e6dd27 100644 --- a/monkey/monkey_island/cc/services/reporting/test_pth_report.py +++ b/monkey/monkey_island/cc/services/reporting/test_pth_report.py @@ -7,9 +7,6 @@ from monkey_island.cc.testing.IslandTestCase import IslandTestCase class TestPTHReportServiceGenerateMapNodes(IslandTestCase): def test_generate_map_nodes(self): - self.fail_if_not_testing_env() - self.clean_monkey_db() - self.assertEqual(PTHReportService.generate_map_nodes(), []) windows_monkey_with_services = Monkey( @@ -43,9 +40,6 @@ class TestPTHReportServiceGenerateMapNodes(IslandTestCase): self.assertEqual(2, len(map_nodes)) def test_generate_map_nodes_parsing(self): - self.fail_if_not_testing_env() - self.clean_monkey_db() - monkey_id = str(uuid.uuid4()) hostname = "A_Windows_PC_1" windows_monkey_with_services = Monkey( diff --git a/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/test_environment.py b/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/test_environment.py index f85b2b88c..8752a9e9d 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/test_environment.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/test_environment.py @@ -8,9 +8,6 @@ from monkey_island.cc.testing.IslandTestCase import IslandTestCase class TestEnvironmentTelemetryProcessing(IslandTestCase): def test_process_environment_telemetry(self): - self.fail_if_not_testing_env() - self.clean_monkey_db() - # Arrange monkey_guid = str(uuid.uuid4()) a_monkey = Monkey(guid=monkey_guid) diff --git a/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/test_system_info_telemetry_dispatcher.py b/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/test_system_info_telemetry_dispatcher.py index 2af2d5970..4c31d466e 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/test_system_info_telemetry_dispatcher.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/test_system_info_telemetry_dispatcher.py @@ -36,9 +36,6 @@ class SystemInfoTelemetryDispatcherTest(IslandTestCase): dispatcher.dispatch_collector_results_to_relevant_processors(good_telem_empty_collectors) def test_dispatch_to_relevant_collector(self): - self.fail_if_not_testing_env() - self.clean_monkey_db() - a_monkey = Monkey(guid=str(uuid.uuid4())) a_monkey.save() diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/test_segmentation.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/test_segmentation.py index e4aa49dc2..6db918b52 100644 --- a/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/test_segmentation.py +++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/test_segmentation.py @@ -16,9 +16,6 @@ THIRD_SUBNET = "3.3.3.3-3.3.3.200" class TestSegmentationChecks(IslandTestCase): def test_create_findings_for_all_done_pairs(self): - self.fail_if_not_testing_env() - self.clean_finding_db() - all_subnets = [FIRST_SUBNET, SECOND_SUBNET, THIRD_SUBNET] monkey = Monkey( diff --git a/monkey/monkey_island/cc/testing/IslandTestCase.py b/monkey/monkey_island/cc/testing/IslandTestCase.py index b260f62c9..d6231c01e 100644 --- a/monkey/monkey_island/cc/testing/IslandTestCase.py +++ b/monkey/monkey_island/cc/testing/IslandTestCase.py @@ -1,5 +1,7 @@ import unittest +import mongoengine + import monkey_island.cc.environment.environment_singleton as env_singleton from monkey_island.cc.models import Monkey from monkey_island.cc.models.edge import Edge @@ -7,17 +9,33 @@ from monkey_island.cc.models.zero_trust.finding import Finding class IslandTestCase(unittest.TestCase): + + def __init__(self, methodName): + # Make sure test is working with mongomock + if mongoengine.connection.get_connection().server_info()['sysInfo'] != 'Mock': + mongoengine.disconnect() + mongoengine.connect('mongoenginetest', host='mongomock://localhost') + else: + IslandTestCase.clean_db() + super().__init__(methodName) + def fail_if_not_testing_env(self): self.assertFalse(not env_singleton.env.testing, "Change server_config.json to testing environment.") @staticmethod - def clean_monkey_db(): + def clean_db(): + IslandTestCase._clean_edge_db() + IslandTestCase._clean_monkey_db() + IslandTestCase._clean_finding_db() + + @staticmethod + def _clean_monkey_db(): Monkey.objects().delete() @staticmethod - def clean_edge_db(): + def _clean_edge_db(): Edge.objects().delete() @staticmethod - def clean_finding_db(): + def _clean_finding_db(): Finding.objects().delete() From 1b35b8fb4ad5d2e42d8800034007ab614910fa6f Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 20 Jan 2021 10:53:40 +0200 Subject: [PATCH 116/152] Improved finding_service.py by specifying datatype it returns --- .../zero_trust_report/finding_service.py | 55 +++++++++++++------ 1 file changed, 37 insertions(+), 18 deletions(-) diff --git a/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/finding_service.py b/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/finding_service.py index 2c5322d92..3f972bb4c 100644 --- a/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/finding_service.py +++ b/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/finding_service.py @@ -1,4 +1,7 @@ -from typing import List +from dataclasses import dataclass +from typing import List, Union + +from bson import SON from common.common_consts import zero_trust_consts from common.utils.exceptions import UnknownFindingError @@ -6,32 +9,48 @@ from monkey_island.cc.models.zero_trust.finding import Finding from monkey_island.cc.services.zero_trust.monkey_findings.monkey_zt_details_service import MonkeyZTDetailsService +@dataclass +class EnrichedFinding: + finding_id: str + test: str + test_key: str + pillars: List[str] + status: str + finding_type: str + details: Union[dict, None] + + class FindingService: @staticmethod - def get_all_findings() -> List[Finding]: + def get_all_findings() -> List[EnrichedFinding]: findings = list(Finding.objects) for i in range(len(findings)): - if findings[i].finding_type == zero_trust_consts.MONKEY_FINDING: - details = MonkeyZTDetailsService.fetch_details_for_display(findings[i].details.id) - elif findings[i].finding_type == zero_trust_consts.SCOUTSUITE_FINDING: - details = findings[i].details.fetch().to_mongo() - else: - raise UnknownFindingError(f"Unknown finding type {findings[i].finding_type}") + details = FindingService._get_finding_details(findings[i]) findings[i] = findings[i].to_mongo() findings[i] = FindingService._get_enriched_finding(findings[i]) - findings[i]['details'] = details + findings[i].details = details return findings @staticmethod - def _get_enriched_finding(finding: Finding) -> dict: + def _get_enriched_finding(finding: Finding) -> EnrichedFinding: test_info = zero_trust_consts.TESTS_MAP[finding['test']] - enriched_finding = { - 'finding_id': str(finding['_id']), - 'test': test_info[zero_trust_consts.FINDING_EXPLANATION_BY_STATUS_KEY][finding['status']], - 'test_key': finding['test'], - 'pillars': test_info[zero_trust_consts.PILLARS_KEY], - 'status': finding['status'], - 'finding_type': finding['finding_type'] - } + enriched_finding = EnrichedFinding( + finding_id=str(finding['_id']), + test=test_info[zero_trust_consts.FINDING_EXPLANATION_BY_STATUS_KEY][finding['status']], + test_key=finding['test'], + pillars=test_info[zero_trust_consts.PILLARS_KEY], + status=finding['status'], + finding_type=finding['finding_type'], + details=None + ) return enriched_finding + + @staticmethod + def _get_finding_details(finding: Finding) -> Union[dict, SON]: + if finding.finding_type == zero_trust_consts.MONKEY_FINDING: + return MonkeyZTDetailsService.fetch_details_for_display(finding.details.id) + elif finding.finding_type == zero_trust_consts.SCOUTSUITE_FINDING: + return finding.details.fetch().to_mongo() + else: + raise UnknownFindingError(f"Unknown finding type {finding.finding_type}") From d31e9064c8e014bd491f643acedb8ed87c4ce4de Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 20 Jan 2021 10:55:15 +0200 Subject: [PATCH 117/152] Added UT's to monkey_zt_finding_service.py and scoutsuite_zt_finding_service.py --- .../zero_trust/test_aggregate_finding.py | 63 ----------- .../cc/models/zero_trust/test_finding.py | 28 +++-- .../test_monkey_zt_finding_service.py | 52 +++++++++ .../test_scoutsuite_zt_finding_service.py | 105 ++++++++++++++++++ 4 files changed, 174 insertions(+), 74 deletions(-) delete mode 100644 monkey/monkey_island/cc/models/zero_trust/test_aggregate_finding.py create mode 100644 monkey/monkey_island/cc/services/zero_trust/monkey_findings/test_monkey_zt_finding_service.py create mode 100644 monkey/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_zt_finding_service.py diff --git a/monkey/monkey_island/cc/models/zero_trust/test_aggregate_finding.py b/monkey/monkey_island/cc/models/zero_trust/test_aggregate_finding.py deleted file mode 100644 index fe8757b9a..000000000 --- a/monkey/monkey_island/cc/models/zero_trust/test_aggregate_finding.py +++ /dev/null @@ -1,63 +0,0 @@ -import unittest - -import mongomock -from packaging import version - -import common.common_consts.zero_trust_consts as zero_trust_consts -from monkey_island.cc.models.zero_trust.event import Event -from monkey_island.cc.models.zero_trust.finding import Finding -from monkey_island.cc.services.zero_trust.monkey_findings.monkey_zt_finding_service import MonkeyZTFindingService -from monkey_island.cc.testing.IslandTestCase import IslandTestCase - - -class TestAggregateFinding(IslandTestCase): - - @unittest.skipIf(version.parse(mongomock.__version__) <= version.parse("3.19.0"), - "mongomock version doesn't support this test") - def test_create_or_add_to_existing(self): - self.fail_if_not_testing_env() - self.clean_finding_db() - - test = zero_trust_consts.TEST_MALICIOUS_ACTIVITY_TIMELINE - status = zero_trust_consts.STATUS_VERIFY - events = [Event.create_event("t", "t", zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK)] - self.assertEqual(len(Finding.objects(test=test, status=status)), 0) - - MonkeyZTFindingService.create_or_add_to_existing(test, status, events) - - self.assertEqual(len(Finding.objects(test=test, status=status)), 1) - self.assertEqual(len(Finding.objects(test=test, status=status)[0].events), 1) - - MonkeyZTFindingService.create_or_add_to_existing(test, status, events) - - self.assertEqual(len(Finding.objects(test=test, status=status)), 1) - self.assertEqual(len(Finding.objects(test=test, status=status)[0].events), 2) - - @unittest.skipIf(version.parse(mongomock.__version__) <= version.parse("3.19.0"), - "mongomock version doesn't support this test") - def test_create_or_add_to_existing_2_tests_already_exist(self): - self.fail_if_not_testing_env() - self.clean_finding_db() - - test = zero_trust_consts.TEST_MALICIOUS_ACTIVITY_TIMELINE - status = zero_trust_consts.STATUS_VERIFY - event = Event.create_event("t", "t", zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK) - events = [event] - self.assertEqual(len(Finding.objects(test=test, status=status)), 0) - - Finding.save_finding(test, status, events) - - self.assertEqual(len(Finding.objects(test=test, status=status)), 1) - self.assertEqual(len(Finding.objects(test=test, status=status)[0].events), 1) - - MonkeyZTFindingService.create_or_add_to_existing(test, status, events) - - self.assertEqual(len(Finding.objects(test=test, status=status)), 1) - self.assertEqual(len(Finding.objects(test=test, status=status)[0].events), 2) - - Finding.save_finding(test, status, events) - - self.assertEqual(len(Finding.objects(test=test, status=status)), 2) - - with self.assertRaises(AssertionError): - MonkeyZTFindingService.create_or_add_to_existing(test, status, events) diff --git a/monkey/monkey_island/cc/models/zero_trust/test_finding.py b/monkey/monkey_island/cc/models/zero_trust/test_finding.py index dc3a274f2..a2a6be6ea 100644 --- a/monkey/monkey_island/cc/models/zero_trust/test_finding.py +++ b/monkey/monkey_island/cc/models/zero_trust/test_finding.py @@ -4,33 +4,39 @@ from mongoengine import ValidationError import common.common_consts.zero_trust_consts as zero_trust_consts from monkey_island.cc.models.zero_trust.event import Event from monkey_island.cc.models.zero_trust.finding import Finding +from monkey_island.cc.models.zero_trust.monkey_finding_details import MonkeyFindingDetails +from monkey_island.cc.models.zero_trust.scoutsuite_finding_details import ScoutSuiteFindingDetails from monkey_island.cc.testing.IslandTestCase import IslandTestCase -class TestFinding(IslandTestCase): - """ - Make sure to set server environment to `testing` in server.json! Otherwise this will mess up your mongo instance and - won't work. +MONKEY_FINDING_DETAIL_MOCK = MonkeyFindingDetails() +MONKEY_FINDING_DETAIL_MOCK.events = ['mock1', 'mock2'] +SCOUTSUITE_FINDING_DETAIL_MOCK = ScoutSuiteFindingDetails() +SCOUTSUITE_FINDING_DETAIL_MOCK.scoutsuite_rules = [] - Also, the working directory needs to be the working directory from which you usually run the island so the - server.json file is found and loaded. - """ + +class TestFinding(IslandTestCase): def test_save_finding_validation(self): with self.assertRaises(ValidationError): - _ = Finding.save_finding(test="bla bla", status=zero_trust_consts.STATUS_FAILED, events=[]) + _ = Finding.save_finding(test="bla bla", + status=zero_trust_consts.STATUS_FAILED, + detail_ref=MONKEY_FINDING_DETAIL_MOCK) with self.assertRaises(ValidationError): - _ = Finding.save_finding(test=zero_trust_consts.TEST_SEGMENTATION, status="bla bla", events=[]) + _ = Finding.save_finding(test=zero_trust_consts.TEST_SEGMENTATION, + status="bla bla", + detail_ref=SCOUTSUITE_FINDING_DETAIL_MOCK) - @pytest.mark.skip(reason="Broken during ScoutSuite refactoring, need to be fixed") def test_save_finding_sanity(self): self.assertEqual(len(Finding.objects(test=zero_trust_consts.TEST_SEGMENTATION)), 0) event_example = Event.create_event( title="Event Title", message="event message", event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK) + monkey_details_example = MonkeyFindingDetails() + monkey_details_example.events.append(event_example) Finding.save_finding(test=zero_trust_consts.TEST_SEGMENTATION, - status=zero_trust_consts.STATUS_FAILED, events=[event_example]) + status=zero_trust_consts.STATUS_FAILED, detail_ref=monkey_details_example) self.assertEqual(len(Finding.objects(test=zero_trust_consts.TEST_SEGMENTATION)), 1) self.assertEqual(len(Finding.objects(status=zero_trust_consts.STATUS_FAILED)), 1) diff --git a/monkey/monkey_island/cc/services/zero_trust/monkey_findings/test_monkey_zt_finding_service.py b/monkey/monkey_island/cc/services/zero_trust/monkey_findings/test_monkey_zt_finding_service.py new file mode 100644 index 000000000..bcf600854 --- /dev/null +++ b/monkey/monkey_island/cc/services/zero_trust/monkey_findings/test_monkey_zt_finding_service.py @@ -0,0 +1,52 @@ +from datetime import datetime +from typing import List + +from bson import ObjectId + +from common.common_consts import zero_trust_consts +from monkey_island.cc.models.zero_trust.event import Event +from monkey_island.cc.models.zero_trust.finding import Finding +from monkey_island.cc.models.zero_trust.monkey_finding_details import MonkeyFindingDetails +from monkey_island.cc.services.zero_trust.monkey_findings.monkey_zt_finding_service import MonkeyZTFindingService +from monkey_island.cc.testing.IslandTestCase import IslandTestCase + +EVENTS = [ + Event.create_event( + title='Process list', + message='Monkey on gc-pc-244 scanned the process list', + event_type='monkey_local', + timestamp=datetime.strptime('2021-01-19 12:07:17.802138', '%Y-%m-%d %H:%M:%S.%f') + ), + Event.create_event( + title='Communicate as new user', + message='Monkey on gc-pc-244 couldn\'t communicate as new user. ' + 'Details: System error 5 has occurred. Access is denied.', + event_type='monkey_network', + timestamp=datetime.strptime('2021-01-19 12:22:42.246020', '%Y-%m-%d %H:%M:%S.%f') + ) +] + +TESTS = [ + zero_trust_consts.TEST_ENDPOINT_SECURITY_EXISTS, + zero_trust_consts.TEST_COMMUNICATE_AS_NEW_USER +] + +STATUS = [ + zero_trust_consts.STATUS_PASSED, + zero_trust_consts.STATUS_FAILED, + zero_trust_consts.STATUS_VERIFY +] + + +class TestMonkeyZTFindingService(IslandTestCase): + + def test_create_or_add_to_existing(self): + + # Create new finding + MonkeyZTFindingService.create_or_add_to_existing(test=TESTS[0], status=STATUS[0], events=EVENTS[0]) + + # Add events to an existing finding + MonkeyZTFindingService.create_or_add_to_existing(test=TESTS[0], status=STATUS[0], events=EVENTS[1]) + + # Create new finding + MonkeyZTFindingService.create_or_add_to_existing(test=TESTS[1], status=STATUS[1], events=EVENTS[1]) diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_zt_finding_service.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_zt_finding_service.py new file mode 100644 index 000000000..a6082bd05 --- /dev/null +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_zt_finding_service.py @@ -0,0 +1,105 @@ +from common.common_consts import zero_trust_consts +from monkey_island.cc.models.zero_trust.finding import Finding +from monkey_island.cc.models.zero_trust.scoutsuite_rule import ScoutSuiteRule +from monkey_island.cc.services.zero_trust.scoutsuite.consts.findings import PermissiveFirewallRules, \ + UnencryptedData +from monkey_island.cc.services.zero_trust.scoutsuite.scoutsuite_zt_finding_service import ScoutSuiteZTFindingService +from monkey_island.cc.testing.IslandTestCase import IslandTestCase + +RULES = [ + ScoutSuiteRule( + checked_items=179, + compliance=None, + dashboard_name='Rules', + description='Security Group Opens All Ports to All', + flagged_items=2, + items=[ + 'ec2.regions.eu-central-1.vpcs.vpc-0ee259b1a13c50229.security_groups.sg-035779fe5c293fc72' + '.rules.ingress.protocols.ALL.ports.1-65535.cidrs.2.CIDR', + 'ec2.regions.eu-central-1.vpcs.vpc-00015526b6695f9aa.security_groups.sg-019eb67135ec81e65' + '.rules.ingress.protocols.ALL.ports.1-65535.cidrs.0.CIDR' + ], + level='danger', + path='ec2.regions.id.vpcs.id.security_groups.id.rules.id.protocols.id.ports.id.cidrs.id.CIDR', + rationale='It was detected that all ports in the security group are open, and any source IP address' + ' could send traffic to these ports, which creates a wider attack surface for resources ' + 'assigned to it. Open ports should be reduced to the minimum needed to correctly', + references=[], + remediation=None, + service='EC2' + ), + ScoutSuiteRule( + checked_items=179, + compliance=[{'name': 'CIS Amazon Web Services Foundations', 'version': '1.0.0', 'reference': '4.1'}, + {'name': 'CIS Amazon Web Services Foundations', 'version': '1.0.0', 'reference': '4.2'}, + {'name': 'CIS Amazon Web Services Foundations', 'version': '1.1.0', 'reference': '4.1'}, + {'name': 'CIS Amazon Web Services Foundations', 'version': '1.1.0', 'reference': '4.2'}, + {'name': 'CIS Amazon Web Services Foundations', 'version': '1.2.0', 'reference': '4.1'}, + {'name': 'CIS Amazon Web Services Foundations', 'version': '1.2.0', 'reference': '4.2'}], + dashboard_name='Rules', + description='Security Group Opens RDP Port to All', + flagged_items=7, + items=[ + 'ec2.regions.eu-central-1.vpcs.vpc-076500a2138ee09da.security_groups.sg-00bdef5951797199c' + '.rules.ingress.protocols.TCP.ports.3389.cidrs.0.CIDR', + 'ec2.regions.eu-central-1.vpcs.vpc-d33026b8.security_groups.sg-007931ba8a364e330' + '.rules.ingress.protocols.TCP.ports.3389.cidrs.0.CIDR', + 'ec2.regions.eu-central-1.vpcs.vpc-d33026b8.security_groups.sg-05014daf996b042dd' + '.rules.ingress.protocols.TCP.ports.3389.cidrs.0.CIDR', + 'ec2.regions.eu-central-1.vpcs.vpc-d33026b8.security_groups.sg-0c745fe56c66335b2' + '.rules.ingress.protocols.TCP.ports.3389.cidrs.0.CIDR', + 'ec2.regions.eu-central-1.vpcs.vpc-d33026b8.security_groups.sg-0f99b85cfad63d1b1' + '.rules.ingress.protocols.TCP.ports.3389.cidrs.0.CIDR', + 'ec2.regions.us-east-1.vpcs.vpc-9e56cae4.security_groups.sg-0dc253aa79062835a' + '.rules.ingress.protocols.TCP.ports.3389.cidrs.0.CIDR', + 'ec2.regions.us-east-1.vpcs.vpc-002d543353cd4e97d.security_groups.sg-01902f153d4f938da' + '.rules.ingress.protocols.TCP.ports.3389.cidrs.0.CIDR'], + level='danger', + path='ec2.regions.id.vpcs.id.security_groups.id.rules.id.protocols.id.ports.id.cidrs.id.CIDR', + rationale='The security group was found to be exposing a well-known port to all source addresses.' + ' Well-known ports are commonly probed by automated scanning tools, and could be an indicator ' + 'of sensitive services exposed to Internet. If such services need to be expos', + references=[], + remediation='Remove the inbound rules that expose open ports', + service='EC2' + ) +] + +FINDINGS = [ + PermissiveFirewallRules, + UnencryptedData +] + + +class TestScoutSuiteZTFindingService(IslandTestCase): + + def test_process_rule(self): + # Creates new PermissiveFirewallRules finding with a rule + ScoutSuiteZTFindingService.process_rule(FINDINGS[0], RULES[0]) + findings = list(Finding.objects()) + self.assertEqual(len(findings), 1) + self.assertEqual(findings[0].finding_type, zero_trust_consts.SCOUTSUITE_FINDING) + # Assert that details were created properly + details = findings[0].details.fetch() + self.assertEqual(len(details.scoutsuite_rules), 1) + self.assertEqual(details.scoutsuite_rules[0], RULES[0]) + + # Rule processing should add rule to an already existing finding + ScoutSuiteZTFindingService.process_rule(FINDINGS[0], RULES[1]) + findings = list(Finding.objects()) + self.assertEqual(len(findings), 1) + self.assertEqual(findings[0].finding_type, zero_trust_consts.SCOUTSUITE_FINDING) + # Assert that details were created properly + details = findings[0].details.fetch() + self.assertEqual(len(details.scoutsuite_rules), 2) + self.assertEqual(details.scoutsuite_rules[1], RULES[1]) + + # New finding created + ScoutSuiteZTFindingService.process_rule(FINDINGS[1], RULES[1]) + findings = list(Finding.objects()) + self.assertEqual(len(findings), 2) + self.assertEqual(findings[1].finding_type, zero_trust_consts.SCOUTSUITE_FINDING) + # Assert that details were created properly + details = findings[1].details.fetch() + self.assertEqual(len(details.scoutsuite_rules), 1) + self.assertEqual(details.scoutsuite_rules[0], RULES[1]) From 2df889ee311b76ddd3ddee52b71d3e13ab974590 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 20 Jan 2021 15:31:42 +0200 Subject: [PATCH 118/152] Refactored unittests to pytest on island code. Cleaned up test infrasctructure: moved common test files to /test_common --- .../common/network/test_segmentation_utils.py | 18 +++-- monkey/monkey_island/cc/conftest.py | 1 + .../cc/environment/test__init__.py | 2 +- .../cc/environment/test_environment_config.py | 2 +- monkey/monkey_island/cc/models/test_monkey.py | 71 +++++++++---------- .../cc/models/zero_trust/test_event.py | 8 +-- .../cc/models/zero_trust/test_finding.py | 13 ++-- .../cc/services/edge/test_displayed_edge.py | 26 ++++--- .../cc/services/edge/test_edge.py | 44 +++++------- .../cc/services/reporting/test_pth_report.py | 17 +++-- .../test_environment.py | 5 +- .../test_system_info_telemetry_dispatcher.py | 23 +++--- .../zero_trust_checks/test_segmentation.py | 30 ++++---- .../test_monkey_zt_finding_service.py | 8 +-- .../test_scoutsuite_zt_finding_service.py | 30 ++++---- .../cc/{testing => test_common}/__init__.py | 0 .../environment/server_config_mocks.py | 0 .../cc/test_common/mongomock_fixtures.py | 32 +++++++++ .../profiling}/README.md | 0 .../profiling}/profiler_decorator.py | 0 .../cc/testing/IslandTestCase.py | 41 ----------- 21 files changed, 173 insertions(+), 198 deletions(-) create mode 100644 monkey/monkey_island/cc/conftest.py rename monkey/monkey_island/cc/{testing => test_common}/__init__.py (100%) rename monkey/monkey_island/cc/{testing => test_common}/environment/server_config_mocks.py (100%) create mode 100644 monkey/monkey_island/cc/test_common/mongomock_fixtures.py rename monkey/monkey_island/cc/{testing => test_common/profiling}/README.md (100%) rename monkey/monkey_island/cc/{testing => test_common/profiling}/profiler_decorator.py (100%) delete mode 100644 monkey/monkey_island/cc/testing/IslandTestCase.py diff --git a/monkey/common/network/test_segmentation_utils.py b/monkey/common/network/test_segmentation_utils.py index 9dea1af19..1bb3d0484 100644 --- a/monkey/common/network/test_segmentation_utils.py +++ b/monkey/common/network/test_segmentation_utils.py @@ -1,30 +1,28 @@ from common.network.network_range import CidrRange from common.network.segmentation_utils import get_ip_in_src_and_not_in_dst -from monkey_island.cc.testing.IslandTestCase import IslandTestCase -class TestSegmentationUtils(IslandTestCase): +class TestSegmentationUtils: def test_get_ip_in_src_and_not_in_dst(self): - self.fail_if_not_testing_env() source = CidrRange("1.1.1.0/24") target = CidrRange("2.2.2.0/24") # IP not in both - self.assertIsNone(get_ip_in_src_and_not_in_dst( + assert get_ip_in_src_and_not_in_dst( ["3.3.3.3", "4.4.4.4"], source, target - )) + ) is None # IP not in source, in target - self.assertIsNone(get_ip_in_src_and_not_in_dst( + assert (get_ip_in_src_and_not_in_dst( ["2.2.2.2"], source, target - )) + )) is None # IP in source, not in target - self.assertIsNotNone(get_ip_in_src_and_not_in_dst( + assert (get_ip_in_src_and_not_in_dst( ["8.8.8.8", "1.1.1.1"], source, target )) # IP in both subnets - self.assertIsNone(get_ip_in_src_and_not_in_dst( + assert (get_ip_in_src_and_not_in_dst( ["8.8.8.8", "1.1.1.1"], source, source - )) + )) is None diff --git a/monkey/monkey_island/cc/conftest.py b/monkey/monkey_island/cc/conftest.py new file mode 100644 index 000000000..aee73ddb6 --- /dev/null +++ b/monkey/monkey_island/cc/conftest.py @@ -0,0 +1 @@ +from .test_common.mongomock_fixtures import * diff --git a/monkey/monkey_island/cc/environment/test__init__.py b/monkey/monkey_island/cc/environment/test__init__.py index 72d1abe75..c55e1b65b 100644 --- a/monkey/monkey_island/cc/environment/test__init__.py +++ b/monkey/monkey_island/cc/environment/test__init__.py @@ -4,7 +4,7 @@ from typing import Dict from unittest import TestCase from unittest.mock import MagicMock, patch -import monkey_island.cc.testing.environment.server_config_mocks as config_mocks +import monkey_island.cc.test_common.environment.server_config_mocks as config_mocks from common.utils.exceptions import (AlreadyRegisteredError, CredentialsNotRequiredError, InvalidRegistrationCredentialsError, RegistrationNotNeededError) from monkey_island.cc.environment import Environment, EnvironmentConfig, UserCreds diff --git a/monkey/monkey_island/cc/environment/test_environment_config.py b/monkey/monkey_island/cc/environment/test_environment_config.py index d30c86804..ed9b0ef96 100644 --- a/monkey/monkey_island/cc/environment/test_environment_config.py +++ b/monkey/monkey_island/cc/environment/test_environment_config.py @@ -5,7 +5,7 @@ from typing import Dict from unittest import TestCase from unittest.mock import MagicMock, patch -import monkey_island.cc.testing.environment.server_config_mocks as config_mocks +import monkey_island.cc.test_common.environment.server_config_mocks as config_mocks from monkey_island.cc.server_utils.consts import MONKEY_ISLAND_ABS_PATH from monkey_island.cc.environment.environment_config import EnvironmentConfig from monkey_island.cc.environment.user_creds import UserCreds diff --git a/monkey/monkey_island/cc/models/test_monkey.py b/monkey/monkey_island/cc/models/test_monkey.py index 5acf8220f..fad2ea94e 100644 --- a/monkey/monkey_island/cc/models/test_monkey.py +++ b/monkey/monkey_island/cc/models/test_monkey.py @@ -5,22 +5,15 @@ from time import sleep import pytest from monkey_island.cc.models.monkey import Monkey, MonkeyNotFoundError -from monkey_island.cc.testing.IslandTestCase import IslandTestCase from .monkey_ttl import MonkeyTtl logger = logging.getLogger(__name__) -class TestMonkey(IslandTestCase): - """ - Make sure to set server environment to `testing` in server_config.json! - Otherwise this will mess up your mongo instance and won't work. - - Also, the working directory needs to be the working directory from which you usually run the island so the - server_config.json file is found and loaded. - """ +class TestMonkey: + @pytest.mark.usefixtures('uses_database') def test_is_dead(self): # Arrange alive_monkey_ttl = MonkeyTtl.create_ttl_expire_in(30) @@ -44,20 +37,22 @@ class TestMonkey(IslandTestCase): dead_monkey.save() # act + assert - self.assertTrue(dead_monkey.is_dead()) - self.assertTrue(mia_monkey.is_dead()) - self.assertFalse(alive_monkey.is_dead()) + assert dead_monkey.is_dead() + assert mia_monkey.is_dead() + assert not alive_monkey.is_dead() + @pytest.mark.usefixtures('uses_database') def test_ttl_renewal(self): # Arrange monkey = Monkey(guid=str(uuid.uuid4())) monkey.save() - self.assertIsNone(monkey.ttl_ref) + assert monkey.ttl_ref is None # act + assert monkey.renew_ttl() - self.assertIsNotNone(monkey.ttl_ref) + assert monkey.ttl_ref + @pytest.mark.usefixtures('uses_database') def test_get_single_monkey_by_id(self): # Arrange a_monkey = Monkey(guid=str(uuid.uuid4())) @@ -65,12 +60,13 @@ class TestMonkey(IslandTestCase): # Act + assert # Find the existing one - self.assertIsNotNone(Monkey.get_single_monkey_by_id(a_monkey.id)) + assert Monkey.get_single_monkey_by_id(a_monkey.id) is not None # Raise on non-existent monkey with pytest.raises(MonkeyNotFoundError) as _: _ = Monkey.get_single_monkey_by_id("abcdefabcdefabcdefabcdef") + @pytest.mark.usefixtures('uses_database') def test_get_os(self): linux_monkey = Monkey(guid=str(uuid.uuid4()), description="Linux shay-Virtual-Machine 4.15.0-50-generic #54-Ubuntu") @@ -82,10 +78,11 @@ class TestMonkey(IslandTestCase): windows_monkey.save() unknown_monkey.save() - self.assertEqual(1, len([m for m in Monkey.objects() if m.get_os() == "windows"])) - self.assertEqual(1, len([m for m in Monkey.objects() if m.get_os() == "linux"])) - self.assertEqual(1, len([m for m in Monkey.objects() if m.get_os() == "unknown"])) + assert 1 == len([m for m in Monkey.objects() if m.get_os() == "windows"]) + assert 1 == len([m for m in Monkey.objects() if m.get_os() == "linux"]) + assert 1 == len([m for m in Monkey.objects() if m.get_os() == "unknown"]) + @pytest.mark.usefixtures('uses_database') def test_get_tunneled_monkeys(self): linux_monkey = Monkey(guid=str(uuid.uuid4()), description="Linux shay-Virtual-Machine") @@ -103,8 +100,9 @@ class TestMonkey(IslandTestCase): and unknown_monkey in tunneled_monkeys and linux_monkey not in tunneled_monkeys and len(tunneled_monkeys) == 2) - self.assertTrue(test, "Tunneling test") + assert test == "Tunneling test" + @pytest.mark.usefixtures('uses_database') def test_get_label_by_id(self): hostname_example = "a_hostname" ip_example = "1.1.1.1" @@ -117,26 +115,26 @@ class TestMonkey(IslandTestCase): logger.debug(id(Monkey.get_label_by_id)) cache_info_before_query = Monkey.get_label_by_id.storage.backend.cache_info() - self.assertEqual(cache_info_before_query.hits, 0) - self.assertEqual(cache_info_before_query.misses, 0) + assert cache_info_before_query.hits == 0 + assert cache_info_before_query.misses == 0 # not cached label = Monkey.get_label_by_id(linux_monkey.id) cache_info_after_query_1 = Monkey.get_label_by_id.storage.backend.cache_info() - self.assertEqual(cache_info_after_query_1.hits, 0) - self.assertEqual(cache_info_after_query_1.misses, 1) + assert cache_info_after_query_1.hits == 0 + assert cache_info_after_query_1.misses == 1 logger.debug("1) ID: {} label: {}".format(linux_monkey.id, label)) - self.assertIsNotNone(label) - self.assertIn(hostname_example, label) - self.assertIn(ip_example, label) + assert label is not None + assert hostname_example in label + assert ip_example in label # should be cached label = Monkey.get_label_by_id(linux_monkey.id) logger.debug("2) ID: {} label: {}".format(linux_monkey.id, label)) cache_info_after_query_2 = Monkey.get_label_by_id.storage.backend.cache_info() - self.assertEqual(cache_info_after_query_2.hits, 1) - self.assertEqual(cache_info_after_query_2.misses, 1) + assert cache_info_after_query_2.hits == 1 + assert cache_info_after_query_2.misses == 1 # set hostname deletes the id from the cache. linux_monkey.set_hostname("Another hostname") @@ -147,24 +145,25 @@ class TestMonkey(IslandTestCase): cache_info_after_query_3 = Monkey.get_label_by_id.storage.backend.cache_info() logger.debug("Cache info: {}".format(str(cache_info_after_query_3))) # still 1 hit only - self.assertEqual(cache_info_after_query_3.hits, 1) - self.assertEqual(cache_info_after_query_3.misses, 2) + assert cache_info_after_query_3.hits == 1 + assert cache_info_after_query_3.misses == 2 + @pytest.mark.usefixtures('uses_database') def test_is_monkey(self): a_monkey = Monkey(guid=str(uuid.uuid4())) a_monkey.save() cache_info_before_query = Monkey.is_monkey.storage.backend.cache_info() - self.assertEqual(cache_info_before_query.hits, 0) + assert cache_info_before_query.hits == 0 # not cached - self.assertTrue(Monkey.is_monkey(a_monkey.id)) + assert Monkey.is_monkey(a_monkey.id) fake_id = "123456789012" - self.assertFalse(Monkey.is_monkey(fake_id)) + assert not Monkey.is_monkey(fake_id) # should be cached - self.assertTrue(Monkey.is_monkey(a_monkey.id)) - self.assertFalse(Monkey.is_monkey(fake_id)) + assert Monkey.is_monkey(a_monkey.id) + assert not Monkey.is_monkey(fake_id) cache_info_after_query = Monkey.is_monkey.storage.backend.cache_info() - self.assertEqual(cache_info_after_query.hits, 2) + assert cache_info_after_query.hits == 2 diff --git a/monkey/monkey_island/cc/models/zero_trust/test_event.py b/monkey/monkey_island/cc/models/zero_trust/test_event.py index a84268e40..f4044c037 100644 --- a/monkey/monkey_island/cc/models/zero_trust/test_event.py +++ b/monkey/monkey_island/cc/models/zero_trust/test_event.py @@ -1,20 +1,20 @@ +import pytest from mongoengine import ValidationError import common.common_consts.zero_trust_consts as zero_trust_consts from monkey_island.cc.models.zero_trust.event import Event -from monkey_island.cc.testing.IslandTestCase import IslandTestCase -class TestEvent(IslandTestCase): +class TestEvent: def test_create_event(self): - with self.assertRaises(ValidationError): + with pytest.raises(ValidationError): _ = Event.create_event( title=None, # title required message="bla bla", event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK ) - with self.assertRaises(ValidationError): + with pytest.raises(ValidationError): _ = Event.create_event( title="skjs", message="bla bla", diff --git a/monkey/monkey_island/cc/models/zero_trust/test_finding.py b/monkey/monkey_island/cc/models/zero_trust/test_finding.py index a2a6be6ea..fd78e2671 100644 --- a/monkey/monkey_island/cc/models/zero_trust/test_finding.py +++ b/monkey/monkey_island/cc/models/zero_trust/test_finding.py @@ -6,7 +6,6 @@ from monkey_island.cc.models.zero_trust.event import Event from monkey_island.cc.models.zero_trust.finding import Finding from monkey_island.cc.models.zero_trust.monkey_finding_details import MonkeyFindingDetails from monkey_island.cc.models.zero_trust.scoutsuite_finding_details import ScoutSuiteFindingDetails -from monkey_island.cc.testing.IslandTestCase import IslandTestCase MONKEY_FINDING_DETAIL_MOCK = MonkeyFindingDetails() @@ -15,21 +14,21 @@ SCOUTSUITE_FINDING_DETAIL_MOCK = ScoutSuiteFindingDetails() SCOUTSUITE_FINDING_DETAIL_MOCK.scoutsuite_rules = [] -class TestFinding(IslandTestCase): +class TestFinding: def test_save_finding_validation(self): - with self.assertRaises(ValidationError): + with pytest.raises(ValidationError): _ = Finding.save_finding(test="bla bla", status=zero_trust_consts.STATUS_FAILED, detail_ref=MONKEY_FINDING_DETAIL_MOCK) - with self.assertRaises(ValidationError): + with pytest.raises(ValidationError): _ = Finding.save_finding(test=zero_trust_consts.TEST_SEGMENTATION, status="bla bla", detail_ref=SCOUTSUITE_FINDING_DETAIL_MOCK) def test_save_finding_sanity(self): - self.assertEqual(len(Finding.objects(test=zero_trust_consts.TEST_SEGMENTATION)), 0) + assert len(Finding.objects(test=zero_trust_consts.TEST_SEGMENTATION)) == 0 event_example = Event.create_event( title="Event Title", message="event message", event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK) @@ -38,5 +37,5 @@ class TestFinding(IslandTestCase): Finding.save_finding(test=zero_trust_consts.TEST_SEGMENTATION, status=zero_trust_consts.STATUS_FAILED, detail_ref=monkey_details_example) - self.assertEqual(len(Finding.objects(test=zero_trust_consts.TEST_SEGMENTATION)), 1) - self.assertEqual(len(Finding.objects(status=zero_trust_consts.STATUS_FAILED)), 1) + assert len(Finding.objects(test=zero_trust_consts.TEST_SEGMENTATION)) == 1 + assert len(Finding.objects(status=zero_trust_consts.STATUS_FAILED)) == 1 diff --git a/monkey/monkey_island/cc/services/edge/test_displayed_edge.py b/monkey/monkey_island/cc/services/edge/test_displayed_edge.py index cd8519327..5aa97d923 100644 --- a/monkey/monkey_island/cc/services/edge/test_displayed_edge.py +++ b/monkey/monkey_island/cc/services/edge/test_displayed_edge.py @@ -2,7 +2,6 @@ from bson import ObjectId from monkey_island.cc.services.edge.displayed_edge import DisplayedEdgeService from monkey_island.cc.services.edge.edge import RIGHT_ARROW, EdgeService -from monkey_island.cc.testing.IslandTestCase import IslandTestCase SCAN_DATA_MOCK = [{ "timestamp": "2020-05-27T14:59:28.944Z", @@ -45,7 +44,7 @@ EXPLOIT_DATA_MOCK = [{ }] -class TestDisplayedEdgeService(IslandTestCase): +class TestDisplayedEdgeService: def test_get_displayed_edges_by_to(self): dst_id = ObjectId() @@ -57,7 +56,7 @@ class TestDisplayedEdgeService(IslandTestCase): EdgeService.get_or_create_edge(src_id2, dst_id, "Ubuntu-4ubuntu3.2", "Ubuntu-4ubuntu2.8") displayed_edges = DisplayedEdgeService.get_displayed_edges_by_dst(str(dst_id)) - self.assertEqual(len(displayed_edges), 2) + assert len(displayed_edges) == 2 def test_edge_to_displayed_edge(self): src_node_id = ObjectId() @@ -74,22 +73,21 @@ class TestDisplayedEdgeService(IslandTestCase): displayed_edge = DisplayedEdgeService.edge_to_displayed_edge(edge) - self.assertEqual(displayed_edge['to'], dst_node_id) - self.assertEqual(displayed_edge['from'], src_node_id) - self.assertEqual(displayed_edge['ip_address'], "10.2.2.2") - self.assertListEqual(displayed_edge['services'], ["tcp-8088: unknown", "tcp-22: ssh"]) - self.assertEqual(displayed_edge['os'], {"type": "linux", - "version": "Ubuntu-4ubuntu2.8"}) - self.assertEqual(displayed_edge['exploits'], EXPLOIT_DATA_MOCK) - self.assertEqual(displayed_edge['_label'], "Ubuntu-4ubuntu3.2 " + RIGHT_ARROW + " Ubuntu-4ubuntu2.8") - self.assertEqual(displayed_edge['group'], "exploited") + assert displayed_edge['to'] == dst_node_id + assert displayed_edge['from'] == src_node_id + assert displayed_edge['ip_address'] == "10.2.2.2" + assert displayed_edge['services'] == ["tcp-8088: unknown", "tcp-22: ssh"] + assert displayed_edge['os'] == {"type": "linux", "version": "Ubuntu-4ubuntu2.8"} + assert displayed_edge['exploits'] == EXPLOIT_DATA_MOCK + assert displayed_edge['_label'] == "Ubuntu-4ubuntu3.2 " + RIGHT_ARROW + " Ubuntu-4ubuntu2.8" + assert displayed_edge['group'] == "exploited" return displayed_edge def test_services_to_displayed_services(self): services1 = DisplayedEdgeService.services_to_displayed_services(SCAN_DATA_MOCK[-1]["data"]["services"], True) - self.assertEqual(services1, ["tcp-8088", "tcp-22"]) + assert services1 == ["tcp-8088", "tcp-22"] services2 = DisplayedEdgeService.services_to_displayed_services(SCAN_DATA_MOCK[-1]["data"]["services"], False) - self.assertEqual(services2, ["tcp-8088: unknown", "tcp-22: ssh"]) + assert services2 == ["tcp-8088: unknown", "tcp-22: ssh"] diff --git a/monkey/monkey_island/cc/services/edge/test_edge.py b/monkey/monkey_island/cc/services/edge/test_edge.py index bc2c40e80..26ab82311 100644 --- a/monkey/monkey_island/cc/services/edge/test_edge.py +++ b/monkey/monkey_island/cc/services/edge/test_edge.py @@ -1,57 +1,51 @@ import logging +import pytest from mongomock import ObjectId from monkey_island.cc.models.edge import Edge from monkey_island.cc.services.edge.edge import EdgeService -from monkey_island.cc.testing.IslandTestCase import IslandTestCase logger = logging.getLogger(__name__) -class TestEdgeService(IslandTestCase): - """ - Make sure to set server environment to `testing` in server_config.json! - Otherwise this will mess up your mongo instance and won't work. - - Also, the working directory needs to be the working directory from which you usually run the island so the - server_config.json file is found and loaded. - """ +class TestEdgeService: + @pytest.mark.usefixtures('uses_database') def test_get_or_create_edge(self): src_id = ObjectId() dst_id = ObjectId() test_edge1 = EdgeService.get_or_create_edge(src_id, dst_id, "Mock label 1", "Mock label 2") - self.assertEqual(test_edge1.src_node_id, src_id) - self.assertEqual(test_edge1.dst_node_id, dst_id) - self.assertFalse(test_edge1.exploited) - self.assertFalse(test_edge1.tunnel) - self.assertListEqual(test_edge1.scans, []) - self.assertListEqual(test_edge1.exploits, []) - self.assertEqual(test_edge1.src_label, "Mock label 1") - self.assertEqual(test_edge1.dst_label, "Mock label 2") - self.assertIsNone(test_edge1.group) - self.assertIsNone(test_edge1.domain_name) - self.assertIsNone(test_edge1.ip_address) + assert test_edge1.src_node_id == src_id + assert test_edge1.dst_node_id == dst_id + assert not test_edge1.exploited + assert not test_edge1.tunnel + assert test_edge1.scans == [] + assert test_edge1.exploits == [] + assert test_edge1.src_label == "Mock label 1" + assert test_edge1.dst_label == "Mock label 2" + assert test_edge1.group is None + assert test_edge1.domain_name is None + assert test_edge1.ip_address is None EdgeService.get_or_create_edge(src_id, dst_id, "Mock label 1", "Mock label 2") - self.assertEqual(len(Edge.objects()), 1) + assert len(Edge.objects()) == 1 def test_get_edge_group(self): edge = Edge(src_node_id=ObjectId(), dst_node_id=ObjectId(), exploited=True) - self.assertEqual("exploited", EdgeService.get_group(edge)) + assert "exploited" == EdgeService.get_group(edge) edge.exploited = False edge.tunnel = True - self.assertEqual("tunnel", EdgeService.get_group(edge)) + assert "tunnel" == EdgeService.get_group(edge) edge.tunnel = False edge.exploits.append(["mock_exploit_data"]) - self.assertEqual("scan", EdgeService.get_group(edge)) + assert "scan" == EdgeService.get_group(edge) edge.exploits = [] edge.scans = [] - self.assertEqual("empty", EdgeService.get_group(edge)) + assert "empty" == EdgeService.get_group(edge) diff --git a/monkey/monkey_island/cc/services/reporting/test_pth_report.py b/monkey/monkey_island/cc/services/reporting/test_pth_report.py index fc1e6dd27..e5fc9aa9a 100644 --- a/monkey/monkey_island/cc/services/reporting/test_pth_report.py +++ b/monkey/monkey_island/cc/services/reporting/test_pth_report.py @@ -2,12 +2,11 @@ import uuid from monkey_island.cc.models import Monkey from monkey_island.cc.services.reporting.pth_report import PTHReportService -from monkey_island.cc.testing.IslandTestCase import IslandTestCase -class TestPTHReportServiceGenerateMapNodes(IslandTestCase): +class TestPTHReportServiceGenerateMapNodes(): def test_generate_map_nodes(self): - self.assertEqual(PTHReportService.generate_map_nodes(), []) + assert PTHReportService.generate_map_nodes() == [] windows_monkey_with_services = Monkey( guid=str(uuid.uuid4()), @@ -37,7 +36,7 @@ class TestPTHReportServiceGenerateMapNodes(IslandTestCase): map_nodes = PTHReportService.generate_map_nodes() - self.assertEqual(2, len(map_nodes)) + assert 2 == len(map_nodes) def test_generate_map_nodes_parsing(self): monkey_id = str(uuid.uuid4()) @@ -53,8 +52,8 @@ class TestPTHReportServiceGenerateMapNodes(IslandTestCase): map_nodes = PTHReportService.generate_map_nodes() - self.assertEqual(map_nodes[0]["id"], monkey_id) - self.assertEqual(map_nodes[0]["label"], "A_Windows_PC_1 : 1.1.1.1") - self.assertEqual(map_nodes[0]["group"], "critical") - self.assertEqual(len(map_nodes[0]["services"]), 2) - self.assertEqual(map_nodes[0]["hostname"], hostname) + assert map_nodes[0]["id"] == monkey_id + assert map_nodes[0]["label"] == "A_Windows_PC_1 : 1.1.1.1" + assert map_nodes[0]["group"] == "critical" + assert len(map_nodes[0]["services"]) == 2 + assert map_nodes[0]["hostname"] == hostname diff --git a/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/test_environment.py b/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/test_environment.py index 8752a9e9d..6369ea9e1 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/test_environment.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/test_environment.py @@ -3,10 +3,9 @@ import uuid from monkey_island.cc.models import Monkey from monkey_island.cc.services.telemetry.processing.system_info_collectors.system_info_telemetry_dispatcher import \ SystemInfoTelemetryDispatcher -from monkey_island.cc.testing.IslandTestCase import IslandTestCase -class TestEnvironmentTelemetryProcessing(IslandTestCase): +class TestEnvironmentTelemetryProcessing: def test_process_environment_telemetry(self): # Arrange monkey_guid = str(uuid.uuid4()) @@ -25,4 +24,4 @@ class TestEnvironmentTelemetryProcessing(IslandTestCase): } dispatcher.dispatch_collector_results_to_relevant_processors(telem_json) - self.assertEqual(Monkey.get_single_monkey_by_guid(monkey_guid).environment, on_premise) + assert Monkey.get_single_monkey_by_guid(monkey_guid).environment == on_premise diff --git a/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/test_system_info_telemetry_dispatcher.py b/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/test_system_info_telemetry_dispatcher.py index 4c31d466e..eed93058a 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/test_system_info_telemetry_dispatcher.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/test_system_info_telemetry_dispatcher.py @@ -1,32 +1,33 @@ import uuid +import pytest + from monkey_island.cc.models import Monkey from monkey_island.cc.services.telemetry.processing.system_info_collectors.system_info_telemetry_dispatcher import ( SystemInfoTelemetryDispatcher, process_aws_telemetry) -from monkey_island.cc.testing.IslandTestCase import IslandTestCase TEST_SYS_INFO_TO_PROCESSING = { "AwsCollector": [process_aws_telemetry], } -class SystemInfoTelemetryDispatcherTest(IslandTestCase): +class TestSystemInfoTelemetryDispatcher: def test_dispatch_to_relevant_collector_bad_inputs(self): - self.fail_if_not_testing_env() dispatcher = SystemInfoTelemetryDispatcher(TEST_SYS_INFO_TO_PROCESSING) # Bad format telem JSONs - throws bad_empty_telem_json = {} - self.assertRaises(KeyError, dispatcher.dispatch_collector_results_to_relevant_processors, bad_empty_telem_json) + with pytest.raises(KeyError): + dispatcher.dispatch_collector_results_to_relevant_processors(bad_empty_telem_json) + bad_no_data_telem_json = {"monkey_guid": "bla"} - self.assertRaises(KeyError, - dispatcher.dispatch_collector_results_to_relevant_processors, - bad_no_data_telem_json) + with pytest.raises(KeyError): + dispatcher.dispatch_collector_results_to_relevant_processors(bad_no_data_telem_json) + bad_no_monkey_telem_json = {"data": {"collectors": {"AwsCollector": "Bla"}}} - self.assertRaises(KeyError, - dispatcher.dispatch_collector_results_to_relevant_processors, - bad_no_monkey_telem_json) + with pytest.raises(KeyError): + dispatcher.dispatch_collector_results_to_relevant_processors(bad_no_monkey_telem_json) # Telem JSON with no collectors - nothing gets dispatched good_telem_no_collectors = {"monkey_guid": "bla", "data": {"bla": "bla"}} @@ -53,4 +54,4 @@ class SystemInfoTelemetryDispatcherTest(IslandTestCase): } dispatcher.dispatch_collector_results_to_relevant_processors(telem_json) - self.assertEquals(Monkey.get_single_monkey_by_guid(a_monkey.guid).aws_instance_id, instance_id) + assert Monkey.get_single_monkey_by_guid(a_monkey.guid).aws_instance_id == instance_id diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/test_segmentation.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/test_segmentation.py index 6db918b52..b29f9e3c6 100644 --- a/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/test_segmentation.py +++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/test_segmentation.py @@ -6,14 +6,13 @@ from monkey_island.cc.models.zero_trust.event import Event from monkey_island.cc.models.zero_trust.finding import Finding from monkey_island.cc.services.telemetry.zero_trust_checks.segmentation import create_or_add_findings_for_all_pairs from monkey_island.cc.services.zero_trust.monkey_findings.monkey_zt_finding_service import MonkeyZTFindingService -from monkey_island.cc.testing.IslandTestCase import IslandTestCase FIRST_SUBNET = "1.1.1.1" SECOND_SUBNET = "2.2.2.0/24" THIRD_SUBNET = "3.3.3.3-3.3.3.200" -class TestSegmentationChecks(IslandTestCase): +class TestSegmentationChecks: def test_create_findings_for_all_done_pairs(self): all_subnets = [FIRST_SUBNET, SECOND_SUBNET, THIRD_SUBNET] @@ -23,15 +22,15 @@ class TestSegmentationChecks(IslandTestCase): ip_addresses=[FIRST_SUBNET]) # no findings - self.assertEqual(len(Finding.objects(test=zero_trust_consts.TEST_SEGMENTATION)), 0) + assert len(Finding.objects(test=zero_trust_consts.TEST_SEGMENTATION)) == 0 # This is like the monkey is done and sent done telem create_or_add_findings_for_all_pairs(all_subnets, monkey) # There are 2 subnets in which the monkey is NOT - self.assertEqual( - len(Finding.objects(test=zero_trust_consts.TEST_SEGMENTATION, status=zero_trust_consts.STATUS_PASSED)), - 2) + zt_seg_findings = Finding.objects(test=zero_trust_consts.TEST_SEGMENTATION, + status=zero_trust_consts.STATUS_PASSED) + assert len(zt_seg_findings) == 2 # This is a monkey from 2nd subnet communicated with 1st subnet. MonkeyZTFindingService.create_or_add_to_existing( @@ -43,12 +42,13 @@ class TestSegmentationChecks(IslandTestCase): ) - self.assertEqual( - len(Finding.objects(test=zero_trust_consts.TEST_SEGMENTATION, status=zero_trust_consts.STATUS_PASSED)), - 1) - self.assertEqual( - len(Finding.objects(test=zero_trust_consts.TEST_SEGMENTATION, status=zero_trust_consts.STATUS_FAILED)), - 1) - self.assertEqual( - len(Finding.objects(test=zero_trust_consts.TEST_SEGMENTATION)), - 2) + zt_seg_findings = Finding.objects(test=zero_trust_consts.TEST_SEGMENTATION, + status=zero_trust_consts.STATUS_PASSED) + assert len(zt_seg_findings) == 1 + + zt_seg_findings = Finding.objects(test=zero_trust_consts.TEST_SEGMENTATION, + status=zero_trust_consts.STATUS_FAILED) + assert len(zt_seg_findings) == 1 + + zt_seg_findings = Finding.objects(test=zero_trust_consts.TEST_SEGMENTATION) + assert len(zt_seg_findings) == 2 diff --git a/monkey/monkey_island/cc/services/zero_trust/monkey_findings/test_monkey_zt_finding_service.py b/monkey/monkey_island/cc/services/zero_trust/monkey_findings/test_monkey_zt_finding_service.py index bcf600854..cadb88aed 100644 --- a/monkey/monkey_island/cc/services/zero_trust/monkey_findings/test_monkey_zt_finding_service.py +++ b/monkey/monkey_island/cc/services/zero_trust/monkey_findings/test_monkey_zt_finding_service.py @@ -1,14 +1,8 @@ from datetime import datetime -from typing import List - -from bson import ObjectId from common.common_consts import zero_trust_consts from monkey_island.cc.models.zero_trust.event import Event -from monkey_island.cc.models.zero_trust.finding import Finding -from monkey_island.cc.models.zero_trust.monkey_finding_details import MonkeyFindingDetails from monkey_island.cc.services.zero_trust.monkey_findings.monkey_zt_finding_service import MonkeyZTFindingService -from monkey_island.cc.testing.IslandTestCase import IslandTestCase EVENTS = [ Event.create_event( @@ -38,7 +32,7 @@ STATUS = [ ] -class TestMonkeyZTFindingService(IslandTestCase): +class TestMonkeyZTFindingService: def test_create_or_add_to_existing(self): diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_zt_finding_service.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_zt_finding_service.py index a6082bd05..e3a8de1bc 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_zt_finding_service.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_zt_finding_service.py @@ -1,10 +1,11 @@ +import pytest + from common.common_consts import zero_trust_consts from monkey_island.cc.models.zero_trust.finding import Finding from monkey_island.cc.models.zero_trust.scoutsuite_rule import ScoutSuiteRule from monkey_island.cc.services.zero_trust.scoutsuite.consts.findings import PermissiveFirewallRules, \ UnencryptedData from monkey_island.cc.services.zero_trust.scoutsuite.scoutsuite_zt_finding_service import ScoutSuiteZTFindingService -from monkey_island.cc.testing.IslandTestCase import IslandTestCase RULES = [ ScoutSuiteRule( @@ -71,35 +72,36 @@ FINDINGS = [ ] -class TestScoutSuiteZTFindingService(IslandTestCase): +class TestScoutSuiteZTFindingService: + @pytest.mark.usefixtures('uses_database') def test_process_rule(self): # Creates new PermissiveFirewallRules finding with a rule ScoutSuiteZTFindingService.process_rule(FINDINGS[0], RULES[0]) findings = list(Finding.objects()) - self.assertEqual(len(findings), 1) - self.assertEqual(findings[0].finding_type, zero_trust_consts.SCOUTSUITE_FINDING) + assert len(findings) == 1 + assert findings[0].finding_type == zero_trust_consts.SCOUTSUITE_FINDING # Assert that details were created properly details = findings[0].details.fetch() - self.assertEqual(len(details.scoutsuite_rules), 1) - self.assertEqual(details.scoutsuite_rules[0], RULES[0]) + assert len(details.scoutsuite_rules) == 1 + assert details.scoutsuite_rules[0] == RULES[0] # Rule processing should add rule to an already existing finding ScoutSuiteZTFindingService.process_rule(FINDINGS[0], RULES[1]) findings = list(Finding.objects()) - self.assertEqual(len(findings), 1) - self.assertEqual(findings[0].finding_type, zero_trust_consts.SCOUTSUITE_FINDING) + assert len(findings) == 1 + assert findings[0].finding_type == zero_trust_consts.SCOUTSUITE_FINDING # Assert that details were created properly details = findings[0].details.fetch() - self.assertEqual(len(details.scoutsuite_rules), 2) - self.assertEqual(details.scoutsuite_rules[1], RULES[1]) + assert len(details.scoutsuite_rules) == 2 + assert details.scoutsuite_rules[1] == RULES[1] # New finding created ScoutSuiteZTFindingService.process_rule(FINDINGS[1], RULES[1]) findings = list(Finding.objects()) - self.assertEqual(len(findings), 2) - self.assertEqual(findings[1].finding_type, zero_trust_consts.SCOUTSUITE_FINDING) + assert len(findings) == 2 + assert findings[1].finding_type == zero_trust_consts.SCOUTSUITE_FINDING # Assert that details were created properly details = findings[1].details.fetch() - self.assertEqual(len(details.scoutsuite_rules), 1) - self.assertEqual(details.scoutsuite_rules[0], RULES[1]) + assert len(details.scoutsuite_rules) == 1 + assert details.scoutsuite_rules[0] == RULES[1] diff --git a/monkey/monkey_island/cc/testing/__init__.py b/monkey/monkey_island/cc/test_common/__init__.py similarity index 100% rename from monkey/monkey_island/cc/testing/__init__.py rename to monkey/monkey_island/cc/test_common/__init__.py diff --git a/monkey/monkey_island/cc/testing/environment/server_config_mocks.py b/monkey/monkey_island/cc/test_common/environment/server_config_mocks.py similarity index 100% rename from monkey/monkey_island/cc/testing/environment/server_config_mocks.py rename to monkey/monkey_island/cc/test_common/environment/server_config_mocks.py diff --git a/monkey/monkey_island/cc/test_common/mongomock_fixtures.py b/monkey/monkey_island/cc/test_common/mongomock_fixtures.py new file mode 100644 index 000000000..8a49d0254 --- /dev/null +++ b/monkey/monkey_island/cc/test_common/mongomock_fixtures.py @@ -0,0 +1,32 @@ +import mongoengine +import pytest + +from monkey_island.cc.models import Monkey +from monkey_island.cc.models.edge import Edge +from monkey_island.cc.models.zero_trust.finding import Finding + + +@pytest.fixture(scope='session', autouse=True) +def change_to_mongo_mock(): + # Make sure tests are working with mongomock + mongoengine.disconnect() + mongoengine.connect('mongoenginetest', host='mongomock://localhost') + + +@pytest.fixture(scope='function') +def uses_database(): + _clean_edge_db() + _clean_monkey_db() + _clean_finding_db() + + +def _clean_monkey_db(): + Monkey.objects().delete() + + +def _clean_edge_db(): + Edge.objects().delete() + + +def _clean_finding_db(): + Finding.objects().delete() diff --git a/monkey/monkey_island/cc/testing/README.md b/monkey/monkey_island/cc/test_common/profiling/README.md similarity index 100% rename from monkey/monkey_island/cc/testing/README.md rename to monkey/monkey_island/cc/test_common/profiling/README.md diff --git a/monkey/monkey_island/cc/testing/profiler_decorator.py b/monkey/monkey_island/cc/test_common/profiling/profiler_decorator.py similarity index 100% rename from monkey/monkey_island/cc/testing/profiler_decorator.py rename to monkey/monkey_island/cc/test_common/profiling/profiler_decorator.py diff --git a/monkey/monkey_island/cc/testing/IslandTestCase.py b/monkey/monkey_island/cc/testing/IslandTestCase.py deleted file mode 100644 index d6231c01e..000000000 --- a/monkey/monkey_island/cc/testing/IslandTestCase.py +++ /dev/null @@ -1,41 +0,0 @@ -import unittest - -import mongoengine - -import monkey_island.cc.environment.environment_singleton as env_singleton -from monkey_island.cc.models import Monkey -from monkey_island.cc.models.edge import Edge -from monkey_island.cc.models.zero_trust.finding import Finding - - -class IslandTestCase(unittest.TestCase): - - def __init__(self, methodName): - # Make sure test is working with mongomock - if mongoengine.connection.get_connection().server_info()['sysInfo'] != 'Mock': - mongoengine.disconnect() - mongoengine.connect('mongoenginetest', host='mongomock://localhost') - else: - IslandTestCase.clean_db() - super().__init__(methodName) - - def fail_if_not_testing_env(self): - self.assertFalse(not env_singleton.env.testing, "Change server_config.json to testing environment.") - - @staticmethod - def clean_db(): - IslandTestCase._clean_edge_db() - IslandTestCase._clean_monkey_db() - IslandTestCase._clean_finding_db() - - @staticmethod - def _clean_monkey_db(): - Monkey.objects().delete() - - @staticmethod - def _clean_edge_db(): - Edge.objects().delete() - - @staticmethod - def _clean_finding_db(): - Finding.objects().delete() From 06d3c70c3e528048f04e177d9df968030caea284 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Fri, 22 Jan 2021 17:24:00 +0200 Subject: [PATCH 119/152] PTH map got removed because it wasn't working. No point in testing code which we know doesn't work of feature we don't use --- .../cc/services/reporting/test_pth_report.py | 59 ------------------- 1 file changed, 59 deletions(-) delete mode 100644 monkey/monkey_island/cc/services/reporting/test_pth_report.py diff --git a/monkey/monkey_island/cc/services/reporting/test_pth_report.py b/monkey/monkey_island/cc/services/reporting/test_pth_report.py deleted file mode 100644 index e5fc9aa9a..000000000 --- a/monkey/monkey_island/cc/services/reporting/test_pth_report.py +++ /dev/null @@ -1,59 +0,0 @@ -import uuid - -from monkey_island.cc.models import Monkey -from monkey_island.cc.services.reporting.pth_report import PTHReportService - - -class TestPTHReportServiceGenerateMapNodes(): - def test_generate_map_nodes(self): - assert PTHReportService.generate_map_nodes() == [] - - windows_monkey_with_services = Monkey( - guid=str(uuid.uuid4()), - hostname="A_Windows_PC_1", - critical_services=["aCriticalService", "Domain Controller"], - ip_addresses=["1.1.1.1", "2.2.2.2"], - description="windows 10" - ) - windows_monkey_with_services.save() - - windows_monkey_with_no_services = Monkey( - guid=str(uuid.uuid4()), - hostname="A_Windows_PC_2", - critical_services=[], - ip_addresses=["3.3.3.3"], - description="windows 10" - ) - windows_monkey_with_no_services.save() - - linux_monkey = Monkey( - guid=str(uuid.uuid4()), - hostname="A_Linux_PC", - ip_addresses=["4.4.4.4"], - description="linux ubuntu" - ) - linux_monkey.save() - - map_nodes = PTHReportService.generate_map_nodes() - - assert 2 == len(map_nodes) - - def test_generate_map_nodes_parsing(self): - monkey_id = str(uuid.uuid4()) - hostname = "A_Windows_PC_1" - windows_monkey_with_services = Monkey( - guid=monkey_id, - hostname=hostname, - critical_services=["aCriticalService", "Domain Controller"], - ip_addresses=["1.1.1.1"], - description="windows 10" - ) - windows_monkey_with_services.save() - - map_nodes = PTHReportService.generate_map_nodes() - - assert map_nodes[0]["id"] == monkey_id - assert map_nodes[0]["label"] == "A_Windows_PC_1 : 1.1.1.1" - assert map_nodes[0]["group"] == "critical" - assert len(map_nodes[0]["services"]) == 2 - assert map_nodes[0]["hostname"] == hostname From 20cc720c21085aae19c8b0e9ade28aa9eadcc968 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 27 Jan 2021 08:46:10 +0200 Subject: [PATCH 120/152] Configured fixtures to be picked up by any test in island --- monkey/monkey_island/cc/conftest.py | 2 +- monkey/monkey_island/cc/test_common/fixtures/__init__.py | 2 ++ .../cc/test_common/{ => fixtures}/mongomock_fixtures.py | 0 3 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 monkey/monkey_island/cc/test_common/fixtures/__init__.py rename monkey/monkey_island/cc/test_common/{ => fixtures}/mongomock_fixtures.py (100%) diff --git a/monkey/monkey_island/cc/conftest.py b/monkey/monkey_island/cc/conftest.py index aee73ddb6..ad683d49e 100644 --- a/monkey/monkey_island/cc/conftest.py +++ b/monkey/monkey_island/cc/conftest.py @@ -1 +1 @@ -from .test_common.mongomock_fixtures import * +from monkey_island.cc.test_common.fixtures import * diff --git a/monkey/monkey_island/cc/test_common/fixtures/__init__.py b/monkey/monkey_island/cc/test_common/fixtures/__init__.py new file mode 100644 index 000000000..1188586b1 --- /dev/null +++ b/monkey/monkey_island/cc/test_common/fixtures/__init__.py @@ -0,0 +1,2 @@ +from .fixture_enum import FixtureEnum +from .mongomock_fixtures import * diff --git a/monkey/monkey_island/cc/test_common/mongomock_fixtures.py b/monkey/monkey_island/cc/test_common/fixtures/mongomock_fixtures.py similarity index 100% rename from monkey/monkey_island/cc/test_common/mongomock_fixtures.py rename to monkey/monkey_island/cc/test_common/fixtures/mongomock_fixtures.py From 7f690bb88011d18336c0f80fdefa6ab69b563fe9 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 27 Jan 2021 08:53:09 +0200 Subject: [PATCH 121/152] Refactored the use of fixtures and fixed up various tests accordingly --- monkey/monkey_island/cc/models/test_monkey.py | 17 +- .../cc/models/zero_trust/test_finding.py | 5 +- .../cc/services/edge/test_edge.py | 3 +- .../zero_trust_checks/test_segmentation.py | 6 +- .../test_monkey_zt_finding_service.py | 34 +- .../test_scoutsuite_zt_finding_service.py | 77 +--- .../test_zero_trust_service.py | 341 ------------------ .../cc/test_common/fixtures/fixture_enum.py | 4 + 8 files changed, 58 insertions(+), 429 deletions(-) delete mode 100644 monkey/monkey_island/cc/services/zero_trust/zero_trust_report/test_zero_trust_service.py create mode 100644 monkey/monkey_island/cc/test_common/fixtures/fixture_enum.py diff --git a/monkey/monkey_island/cc/models/test_monkey.py b/monkey/monkey_island/cc/models/test_monkey.py index fad2ea94e..7860de20e 100644 --- a/monkey/monkey_island/cc/models/test_monkey.py +++ b/monkey/monkey_island/cc/models/test_monkey.py @@ -7,13 +7,14 @@ import pytest from monkey_island.cc.models.monkey import Monkey, MonkeyNotFoundError from .monkey_ttl import MonkeyTtl +from ..test_common.fixtures import FixtureEnum logger = logging.getLogger(__name__) class TestMonkey: - @pytest.mark.usefixtures('uses_database') + @pytest.mark.usefixtures(FixtureEnum.USES_DATABASE) def test_is_dead(self): # Arrange alive_monkey_ttl = MonkeyTtl.create_ttl_expire_in(30) @@ -41,7 +42,7 @@ class TestMonkey: assert mia_monkey.is_dead() assert not alive_monkey.is_dead() - @pytest.mark.usefixtures('uses_database') + @pytest.mark.usefixtures(FixtureEnum.USES_DATABASE) def test_ttl_renewal(self): # Arrange monkey = Monkey(guid=str(uuid.uuid4())) @@ -52,7 +53,7 @@ class TestMonkey: monkey.renew_ttl() assert monkey.ttl_ref - @pytest.mark.usefixtures('uses_database') + @pytest.mark.usefixtures(FixtureEnum.USES_DATABASE) def test_get_single_monkey_by_id(self): # Arrange a_monkey = Monkey(guid=str(uuid.uuid4())) @@ -66,7 +67,7 @@ class TestMonkey: with pytest.raises(MonkeyNotFoundError) as _: _ = Monkey.get_single_monkey_by_id("abcdefabcdefabcdefabcdef") - @pytest.mark.usefixtures('uses_database') + @pytest.mark.usefixtures(FixtureEnum.USES_DATABASE) def test_get_os(self): linux_monkey = Monkey(guid=str(uuid.uuid4()), description="Linux shay-Virtual-Machine 4.15.0-50-generic #54-Ubuntu") @@ -82,7 +83,7 @@ class TestMonkey: assert 1 == len([m for m in Monkey.objects() if m.get_os() == "linux"]) assert 1 == len([m for m in Monkey.objects() if m.get_os() == "unknown"]) - @pytest.mark.usefixtures('uses_database') + @pytest.mark.usefixtures(FixtureEnum.USES_DATABASE) def test_get_tunneled_monkeys(self): linux_monkey = Monkey(guid=str(uuid.uuid4()), description="Linux shay-Virtual-Machine") @@ -100,9 +101,9 @@ class TestMonkey: and unknown_monkey in tunneled_monkeys and linux_monkey not in tunneled_monkeys and len(tunneled_monkeys) == 2) - assert test == "Tunneling test" + assert test - @pytest.mark.usefixtures('uses_database') + @pytest.mark.usefixtures(FixtureEnum.USES_DATABASE) def test_get_label_by_id(self): hostname_example = "a_hostname" ip_example = "1.1.1.1" @@ -148,7 +149,7 @@ class TestMonkey: assert cache_info_after_query_3.hits == 1 assert cache_info_after_query_3.misses == 2 - @pytest.mark.usefixtures('uses_database') + @pytest.mark.usefixtures(FixtureEnum.USES_DATABASE) def test_is_monkey(self): a_monkey = Monkey(guid=str(uuid.uuid4())) a_monkey.save() diff --git a/monkey/monkey_island/cc/models/zero_trust/test_finding.py b/monkey/monkey_island/cc/models/zero_trust/test_finding.py index fd78e2671..4df4b7bab 100644 --- a/monkey/monkey_island/cc/models/zero_trust/test_finding.py +++ b/monkey/monkey_island/cc/models/zero_trust/test_finding.py @@ -6,7 +6,7 @@ from monkey_island.cc.models.zero_trust.event import Event from monkey_island.cc.models.zero_trust.finding import Finding from monkey_island.cc.models.zero_trust.monkey_finding_details import MonkeyFindingDetails from monkey_island.cc.models.zero_trust.scoutsuite_finding_details import ScoutSuiteFindingDetails - +from monkey_island.cc.test_common.fixtures import FixtureEnum MONKEY_FINDING_DETAIL_MOCK = MonkeyFindingDetails() MONKEY_FINDING_DETAIL_MOCK.events = ['mock1', 'mock2'] @@ -16,6 +16,7 @@ SCOUTSUITE_FINDING_DETAIL_MOCK.scoutsuite_rules = [] class TestFinding: + @pytest.mark.usefixtures(FixtureEnum.USES_DATABASE) def test_save_finding_validation(self): with pytest.raises(ValidationError): _ = Finding.save_finding(test="bla bla", @@ -27,6 +28,7 @@ class TestFinding: status="bla bla", detail_ref=SCOUTSUITE_FINDING_DETAIL_MOCK) + @pytest.mark.usefixtures(FixtureEnum.USES_DATABASE) def test_save_finding_sanity(self): assert len(Finding.objects(test=zero_trust_consts.TEST_SEGMENTATION)) == 0 @@ -34,6 +36,7 @@ class TestFinding: title="Event Title", message="event message", event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK) monkey_details_example = MonkeyFindingDetails() monkey_details_example.events.append(event_example) + monkey_details_example.save() Finding.save_finding(test=zero_trust_consts.TEST_SEGMENTATION, status=zero_trust_consts.STATUS_FAILED, detail_ref=monkey_details_example) diff --git a/monkey/monkey_island/cc/services/edge/test_edge.py b/monkey/monkey_island/cc/services/edge/test_edge.py index 26ab82311..f327bc2d1 100644 --- a/monkey/monkey_island/cc/services/edge/test_edge.py +++ b/monkey/monkey_island/cc/services/edge/test_edge.py @@ -5,13 +5,14 @@ from mongomock import ObjectId from monkey_island.cc.models.edge import Edge from monkey_island.cc.services.edge.edge import EdgeService +from monkey_island.cc.test_common.fixtures import FixtureEnum logger = logging.getLogger(__name__) class TestEdgeService: - @pytest.mark.usefixtures('uses_database') + @pytest.mark.usefixtures(FixtureEnum.USES_DATABASE) def test_get_or_create_edge(self): src_id = ObjectId() dst_id = ObjectId() diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/test_segmentation.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/test_segmentation.py index b29f9e3c6..ca58549d1 100644 --- a/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/test_segmentation.py +++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/test_segmentation.py @@ -30,7 +30,10 @@ class TestSegmentationChecks: # There are 2 subnets in which the monkey is NOT zt_seg_findings = Finding.objects(test=zero_trust_consts.TEST_SEGMENTATION, status=zero_trust_consts.STATUS_PASSED) - assert len(zt_seg_findings) == 2 + + # Assert that there's only one finding with multiple events (one for each subnet) + assert len(zt_seg_findings) == 1 + assert len(Finding.objects().get().details.fetch().events) == 2 # This is a monkey from 2nd subnet communicated with 1st subnet. MonkeyZTFindingService.create_or_add_to_existing( @@ -39,7 +42,6 @@ class TestSegmentationChecks: events=[Event.create_event(title="sdf", message="asd", event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK)] - ) zt_seg_findings = Finding.objects(test=zero_trust_consts.TEST_SEGMENTATION, diff --git a/monkey/monkey_island/cc/services/zero_trust/monkey_findings/test_monkey_zt_finding_service.py b/monkey/monkey_island/cc/services/zero_trust/monkey_findings/test_monkey_zt_finding_service.py index cadb88aed..c3db9ea39 100644 --- a/monkey/monkey_island/cc/services/zero_trust/monkey_findings/test_monkey_zt_finding_service.py +++ b/monkey/monkey_island/cc/services/zero_trust/monkey_findings/test_monkey_zt_finding_service.py @@ -1,8 +1,12 @@ from datetime import datetime +import pytest + from common.common_consts import zero_trust_consts from monkey_island.cc.models.zero_trust.event import Event +from monkey_island.cc.models.zero_trust.finding import Finding from monkey_island.cc.services.zero_trust.monkey_findings.monkey_zt_finding_service import MonkeyZTFindingService +from monkey_island.cc.test_common.fixtures import FixtureEnum EVENTS = [ Event.create_event( @@ -34,13 +38,33 @@ STATUS = [ class TestMonkeyZTFindingService: - def test_create_or_add_to_existing(self): - + @pytest.mark.usefixtures(FixtureEnum.USES_DATABASE) + def test_create_or_add_to_existing_creation(self): # Create new finding - MonkeyZTFindingService.create_or_add_to_existing(test=TESTS[0], status=STATUS[0], events=EVENTS[0]) + MonkeyZTFindingService.create_or_add_to_existing(test=TESTS[0], status=STATUS[0], events=[EVENTS[0]]) + # Assert that it was properly created + findings = list(Finding.objects()) + assert len(findings) == 1 + assert findings[0].test == TESTS[0] + assert findings[0].status == STATUS[0] + finding_details = findings[0].details.fetch() + assert len(finding_details.events) == 1 + assert finding_details.events[0].message == EVENTS[0].message + + @pytest.mark.usefixtures(FixtureEnum.USES_DATABASE) + def test_create_or_add_to_existing_addition(self): + # Create new finding + MonkeyZTFindingService.create_or_add_to_existing(test=TESTS[0], status=STATUS[0], events=[EVENTS[0]]) + # Assert that there's only one finding + assert len(Finding.objects()) == 1 # Add events to an existing finding - MonkeyZTFindingService.create_or_add_to_existing(test=TESTS[0], status=STATUS[0], events=EVENTS[1]) + MonkeyZTFindingService.create_or_add_to_existing(test=TESTS[0], status=STATUS[0], events=[EVENTS[1]]) + # Assert there's still only one finding, only events got appended + assert len(Finding.objects()) == 1 + assert len(Finding.objects()[0].details.fetch().events) == 2 # Create new finding - MonkeyZTFindingService.create_or_add_to_existing(test=TESTS[1], status=STATUS[1], events=EVENTS[1]) + MonkeyZTFindingService.create_or_add_to_existing(test=TESTS[1], status=STATUS[1], events=[EVENTS[1]]) + # Assert there was a new finding created, because test and status is different + assert len(Finding.objects()) == 2 diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_zt_finding_service.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_zt_finding_service.py index e3a8de1bc..0350bd2f3 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_zt_finding_service.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_zt_finding_service.py @@ -2,82 +2,17 @@ import pytest from common.common_consts import zero_trust_consts from monkey_island.cc.models.zero_trust.finding import Finding -from monkey_island.cc.models.zero_trust.scoutsuite_rule import ScoutSuiteRule -from monkey_island.cc.services.zero_trust.scoutsuite.consts.findings import PermissiveFirewallRules, \ - UnencryptedData from monkey_island.cc.services.zero_trust.scoutsuite.scoutsuite_zt_finding_service import ScoutSuiteZTFindingService - -RULES = [ - ScoutSuiteRule( - checked_items=179, - compliance=None, - dashboard_name='Rules', - description='Security Group Opens All Ports to All', - flagged_items=2, - items=[ - 'ec2.regions.eu-central-1.vpcs.vpc-0ee259b1a13c50229.security_groups.sg-035779fe5c293fc72' - '.rules.ingress.protocols.ALL.ports.1-65535.cidrs.2.CIDR', - 'ec2.regions.eu-central-1.vpcs.vpc-00015526b6695f9aa.security_groups.sg-019eb67135ec81e65' - '.rules.ingress.protocols.ALL.ports.1-65535.cidrs.0.CIDR' - ], - level='danger', - path='ec2.regions.id.vpcs.id.security_groups.id.rules.id.protocols.id.ports.id.cidrs.id.CIDR', - rationale='It was detected that all ports in the security group are open, and any source IP address' - ' could send traffic to these ports, which creates a wider attack surface for resources ' - 'assigned to it. Open ports should be reduced to the minimum needed to correctly', - references=[], - remediation=None, - service='EC2' - ), - ScoutSuiteRule( - checked_items=179, - compliance=[{'name': 'CIS Amazon Web Services Foundations', 'version': '1.0.0', 'reference': '4.1'}, - {'name': 'CIS Amazon Web Services Foundations', 'version': '1.0.0', 'reference': '4.2'}, - {'name': 'CIS Amazon Web Services Foundations', 'version': '1.1.0', 'reference': '4.1'}, - {'name': 'CIS Amazon Web Services Foundations', 'version': '1.1.0', 'reference': '4.2'}, - {'name': 'CIS Amazon Web Services Foundations', 'version': '1.2.0', 'reference': '4.1'}, - {'name': 'CIS Amazon Web Services Foundations', 'version': '1.2.0', 'reference': '4.2'}], - dashboard_name='Rules', - description='Security Group Opens RDP Port to All', - flagged_items=7, - items=[ - 'ec2.regions.eu-central-1.vpcs.vpc-076500a2138ee09da.security_groups.sg-00bdef5951797199c' - '.rules.ingress.protocols.TCP.ports.3389.cidrs.0.CIDR', - 'ec2.regions.eu-central-1.vpcs.vpc-d33026b8.security_groups.sg-007931ba8a364e330' - '.rules.ingress.protocols.TCP.ports.3389.cidrs.0.CIDR', - 'ec2.regions.eu-central-1.vpcs.vpc-d33026b8.security_groups.sg-05014daf996b042dd' - '.rules.ingress.protocols.TCP.ports.3389.cidrs.0.CIDR', - 'ec2.regions.eu-central-1.vpcs.vpc-d33026b8.security_groups.sg-0c745fe56c66335b2' - '.rules.ingress.protocols.TCP.ports.3389.cidrs.0.CIDR', - 'ec2.regions.eu-central-1.vpcs.vpc-d33026b8.security_groups.sg-0f99b85cfad63d1b1' - '.rules.ingress.protocols.TCP.ports.3389.cidrs.0.CIDR', - 'ec2.regions.us-east-1.vpcs.vpc-9e56cae4.security_groups.sg-0dc253aa79062835a' - '.rules.ingress.protocols.TCP.ports.3389.cidrs.0.CIDR', - 'ec2.regions.us-east-1.vpcs.vpc-002d543353cd4e97d.security_groups.sg-01902f153d4f938da' - '.rules.ingress.protocols.TCP.ports.3389.cidrs.0.CIDR'], - level='danger', - path='ec2.regions.id.vpcs.id.security_groups.id.rules.id.protocols.id.ports.id.cidrs.id.CIDR', - rationale='The security group was found to be exposing a well-known port to all source addresses.' - ' Well-known ports are commonly probed by automated scanning tools, and could be an indicator ' - 'of sensitive services exposed to Internet. If such services need to be expos', - references=[], - remediation='Remove the inbound rules that expose open ports', - service='EC2' - ) -] - -FINDINGS = [ - PermissiveFirewallRules, - UnencryptedData -] +from monkey_island.cc.services.zero_trust.test_common.scoutsuite_finding_data import RULES, SCOUTSUITE_FINDINGS +from monkey_island.cc.test_common.fixtures import FixtureEnum class TestScoutSuiteZTFindingService: - @pytest.mark.usefixtures('uses_database') + @pytest.mark.usefixtures(FixtureEnum.USES_DATABASE) def test_process_rule(self): # Creates new PermissiveFirewallRules finding with a rule - ScoutSuiteZTFindingService.process_rule(FINDINGS[0], RULES[0]) + ScoutSuiteZTFindingService.process_rule(SCOUTSUITE_FINDINGS[0], RULES[0]) findings = list(Finding.objects()) assert len(findings) == 1 assert findings[0].finding_type == zero_trust_consts.SCOUTSUITE_FINDING @@ -87,7 +22,7 @@ class TestScoutSuiteZTFindingService: assert details.scoutsuite_rules[0] == RULES[0] # Rule processing should add rule to an already existing finding - ScoutSuiteZTFindingService.process_rule(FINDINGS[0], RULES[1]) + ScoutSuiteZTFindingService.process_rule(SCOUTSUITE_FINDINGS[0], RULES[1]) findings = list(Finding.objects()) assert len(findings) == 1 assert findings[0].finding_type == zero_trust_consts.SCOUTSUITE_FINDING @@ -97,7 +32,7 @@ class TestScoutSuiteZTFindingService: assert details.scoutsuite_rules[1] == RULES[1] # New finding created - ScoutSuiteZTFindingService.process_rule(FINDINGS[1], RULES[1]) + ScoutSuiteZTFindingService.process_rule(SCOUTSUITE_FINDINGS[1], RULES[1]) findings = list(Finding.objects()) assert len(findings) == 2 assert findings[1].finding_type == zero_trust_consts.SCOUTSUITE_FINDING diff --git a/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/test_zero_trust_service.py b/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/test_zero_trust_service.py deleted file mode 100644 index 8c8adb133..000000000 --- a/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/test_zero_trust_service.py +++ /dev/null @@ -1,341 +0,0 @@ -import pytest - -import common.common_consts.zero_trust_consts as zero_trust_consts -import monkey_island.cc.services.zero_trust.zero_trust_service -from monkey_island.cc.models.zero_trust.finding import Finding -from monkey_island.cc.services.zero_trust.zero_trust_service import ZeroTrustService -from monkey_island.cc.testing.IslandTestCase import IslandTestCase - -EXPECTED_DICT = { - zero_trust_consts.AUTOMATION_ORCHESTRATION: [], - zero_trust_consts.DATA: [ - { - "principle": zero_trust_consts.PRINCIPLES[zero_trust_consts.PRINCIPLE_DATA_CONFIDENTIALITY], - "status": zero_trust_consts.STATUS_FAILED, - "tests": [ - { - "status": zero_trust_consts.STATUS_FAILED, - "test": zero_trust_consts.TESTS_MAP - [zero_trust_consts.TEST_DATA_ENDPOINT_HTTP][zero_trust_consts.TEST_EXPLANATION_KEY] - }, - { - "status": zero_trust_consts.STATUS_UNEXECUTED, - "test": zero_trust_consts.TESTS_MAP - [zero_trust_consts.TEST_DATA_ENDPOINT_ELASTIC][zero_trust_consts.TEST_EXPLANATION_KEY] - }, - ] - } - ], - zero_trust_consts.DEVICES: [ - { - "principle": zero_trust_consts.PRINCIPLES[zero_trust_consts.PRINCIPLE_ENDPOINT_SECURITY], - "status": zero_trust_consts.STATUS_FAILED, - "tests": [ - { - "status": zero_trust_consts.STATUS_UNEXECUTED, - "test": zero_trust_consts.TESTS_MAP - [zero_trust_consts.TEST_MACHINE_EXPLOITED][zero_trust_consts.TEST_EXPLANATION_KEY] - }, - { - "status": zero_trust_consts.STATUS_FAILED, - "test": zero_trust_consts.TESTS_MAP - [zero_trust_consts.TEST_ENDPOINT_SECURITY_EXISTS][zero_trust_consts.TEST_EXPLANATION_KEY] - }, - ] - } - ], - zero_trust_consts.NETWORKS: [ - { - "principle": zero_trust_consts.PRINCIPLES[zero_trust_consts.PRINCIPLE_SEGMENTATION], - "status": zero_trust_consts.STATUS_UNEXECUTED, - "tests": [ - { - "status": zero_trust_consts.STATUS_UNEXECUTED, - "test": zero_trust_consts.TESTS_MAP[zero_trust_consts.TEST_SEGMENTATION][ - zero_trust_consts.TEST_EXPLANATION_KEY] - } - ] - }, - { - "principle": zero_trust_consts.PRINCIPLES[zero_trust_consts.PRINCIPLE_USER_BEHAVIOUR], - "status": zero_trust_consts.STATUS_VERIFY, - "tests": [ - { - "status": zero_trust_consts.STATUS_VERIFY, - "test": zero_trust_consts.TESTS_MAP[zero_trust_consts.TEST_SCHEDULED_EXECUTION][ - zero_trust_consts.TEST_EXPLANATION_KEY] - } - ] - }, - { - "principle": zero_trust_consts.PRINCIPLES[zero_trust_consts.PRINCIPLE_USERS_MAC_POLICIES], - "status": zero_trust_consts.STATUS_UNEXECUTED, - "tests": [ - { - "status": zero_trust_consts.STATUS_UNEXECUTED, - "test": zero_trust_consts.TESTS_MAP[zero_trust_consts.TEST_COMMUNICATE_AS_NEW_USER][ - zero_trust_consts.TEST_EXPLANATION_KEY] - } - ] - }, - { - "principle": zero_trust_consts.PRINCIPLES[zero_trust_consts.PRINCIPLE_ANALYZE_NETWORK_TRAFFIC], - "status": zero_trust_consts.STATUS_UNEXECUTED, - "tests": [ - { - "status": zero_trust_consts.STATUS_UNEXECUTED, - "test": zero_trust_consts.TESTS_MAP[zero_trust_consts.TEST_MALICIOUS_ACTIVITY_TIMELINE][ - zero_trust_consts.TEST_EXPLANATION_KEY] - } - ] - }, - { - "principle": zero_trust_consts.PRINCIPLES[zero_trust_consts.PRINCIPLE_RESTRICTIVE_NETWORK_POLICIES], - "status": zero_trust_consts.STATUS_UNEXECUTED, - "tests": [ - { - "status": zero_trust_consts.STATUS_UNEXECUTED, - "test": zero_trust_consts.TESTS_MAP[zero_trust_consts.TEST_TUNNELING][ - zero_trust_consts.TEST_EXPLANATION_KEY] - } - ] - }, - ], - zero_trust_consts.PEOPLE: [ - { - "principle": zero_trust_consts.PRINCIPLES[zero_trust_consts.PRINCIPLE_USER_BEHAVIOUR], - "status": zero_trust_consts.STATUS_VERIFY, - "tests": [ - { - "status": zero_trust_consts.STATUS_VERIFY, - "test": zero_trust_consts.TESTS_MAP[zero_trust_consts.TEST_SCHEDULED_EXECUTION][ - zero_trust_consts.TEST_EXPLANATION_KEY] - } - ] - }, - { - "principle": zero_trust_consts.PRINCIPLES[zero_trust_consts.PRINCIPLE_USERS_MAC_POLICIES], - "status": zero_trust_consts.STATUS_UNEXECUTED, - "tests": [ - { - "status": zero_trust_consts.STATUS_UNEXECUTED, - "test": zero_trust_consts.TESTS_MAP[zero_trust_consts.TEST_COMMUNICATE_AS_NEW_USER][ - zero_trust_consts.TEST_EXPLANATION_KEY] - } - ] - } - ], - zero_trust_consts.VISIBILITY_ANALYTICS: [ - { - "principle": zero_trust_consts.PRINCIPLES[zero_trust_consts.PRINCIPLE_USERS_MAC_POLICIES], - "status": zero_trust_consts.STATUS_UNEXECUTED, - "tests": [ - { - "status": zero_trust_consts.STATUS_UNEXECUTED, - "test": zero_trust_consts.TESTS_MAP[zero_trust_consts.TEST_COMMUNICATE_AS_NEW_USER][ - zero_trust_consts.TEST_EXPLANATION_KEY] - } - ] - }, - { - "principle": zero_trust_consts.PRINCIPLES[zero_trust_consts.PRINCIPLE_ANALYZE_NETWORK_TRAFFIC], - "status": zero_trust_consts.STATUS_UNEXECUTED, - "tests": [ - { - "status": zero_trust_consts.STATUS_UNEXECUTED, - "test": zero_trust_consts.TESTS_MAP[zero_trust_consts.TEST_MALICIOUS_ACTIVITY_TIMELINE][ - zero_trust_consts.TEST_EXPLANATION_KEY] - } - ] - }, - { - "principle": zero_trust_consts.PRINCIPLES[zero_trust_consts.PRINCIPLE_RESTRICTIVE_NETWORK_POLICIES], - "status": zero_trust_consts.STATUS_UNEXECUTED, - "tests": [ - { - "status": zero_trust_consts.STATUS_UNEXECUTED, - "test": zero_trust_consts.TESTS_MAP[zero_trust_consts.TEST_TUNNELING][ - zero_trust_consts.TEST_EXPLANATION_KEY] - } - ] - }, - ], - zero_trust_consts.WORKLOADS: [] -} - - -def save_example_findings(): - # arrange - Finding.save_finding(zero_trust_consts.TEST_ENDPOINT_SECURITY_EXISTS, zero_trust_consts.STATUS_PASSED, - []) # devices passed = 1 - Finding.save_finding(zero_trust_consts.TEST_ENDPOINT_SECURITY_EXISTS, zero_trust_consts.STATUS_PASSED, - []) # devices passed = 2 - Finding.save_finding(zero_trust_consts.TEST_ENDPOINT_SECURITY_EXISTS, zero_trust_consts.STATUS_FAILED, - []) # devices failed = 1 - # devices unexecuted = 1 - # people verify = 1 - # networks verify = 1 - Finding.save_finding(zero_trust_consts.TEST_SCHEDULED_EXECUTION, zero_trust_consts.STATUS_VERIFY, []) - # people verify = 2 - # networks verify = 2 - Finding.save_finding(zero_trust_consts.TEST_SCHEDULED_EXECUTION, zero_trust_consts.STATUS_VERIFY, []) - # data failed 1 - Finding.save_finding(zero_trust_consts.TEST_DATA_ENDPOINT_HTTP, zero_trust_consts.STATUS_FAILED, []) - # data failed 2 - Finding.save_finding(zero_trust_consts.TEST_DATA_ENDPOINT_HTTP, zero_trust_consts.STATUS_FAILED, []) - # data failed 3 - Finding.save_finding(zero_trust_consts.TEST_DATA_ENDPOINT_HTTP, zero_trust_consts.STATUS_FAILED, []) - # data failed 4 - Finding.save_finding(zero_trust_consts.TEST_DATA_ENDPOINT_HTTP, zero_trust_consts.STATUS_FAILED, []) - # data failed 5 - Finding.save_finding(zero_trust_consts.TEST_DATA_ENDPOINT_HTTP, zero_trust_consts.STATUS_FAILED, []) - # data verify 1 - Finding.save_finding(zero_trust_consts.TEST_DATA_ENDPOINT_HTTP, zero_trust_consts.STATUS_VERIFY, []) - # data verify 2 - Finding.save_finding(zero_trust_consts.TEST_DATA_ENDPOINT_HTTP, zero_trust_consts.STATUS_VERIFY, []) - # data passed 1 - Finding.save_finding(zero_trust_consts.TEST_DATA_ENDPOINT_HTTP, zero_trust_consts.STATUS_PASSED, []) - - -class TestZeroTrustService(IslandTestCase): - - @pytest.mark.skip(reason="Broken during ScoutSuite refactoring, need to be fixed") - def test_get_pillars_grades(self): - self.fail_if_not_testing_env() - self.clean_finding_db() - - save_example_findings() - - expected = [ - { - zero_trust_consts.STATUS_FAILED: 5, - zero_trust_consts.STATUS_VERIFY: 2, - zero_trust_consts.STATUS_PASSED: 1, - zero_trust_consts.STATUS_UNEXECUTED: 1, - "pillar": "Data" - }, - { - zero_trust_consts.STATUS_FAILED: 0, - zero_trust_consts.STATUS_VERIFY: 2, - zero_trust_consts.STATUS_PASSED: 0, - zero_trust_consts.STATUS_UNEXECUTED: 1, - "pillar": "People" - }, - { - zero_trust_consts.STATUS_FAILED: 0, - zero_trust_consts.STATUS_VERIFY: 2, - zero_trust_consts.STATUS_PASSED: 0, - zero_trust_consts.STATUS_UNEXECUTED: 4, - "pillar": "Networks" - }, - { - zero_trust_consts.STATUS_FAILED: 1, - zero_trust_consts.STATUS_VERIFY: 0, - zero_trust_consts.STATUS_PASSED: 2, - zero_trust_consts.STATUS_UNEXECUTED: 1, - "pillar": "Devices" - }, - { - zero_trust_consts.STATUS_FAILED: 0, - zero_trust_consts.STATUS_VERIFY: 0, - zero_trust_consts.STATUS_PASSED: 0, - zero_trust_consts.STATUS_UNEXECUTED: 0, - "pillar": "Workloads" - }, - { - zero_trust_consts.STATUS_FAILED: 0, - zero_trust_consts.STATUS_VERIFY: 0, - zero_trust_consts.STATUS_PASSED: 0, - zero_trust_consts.STATUS_UNEXECUTED: 3, - "pillar": "Visibility & Analytics" - }, - { - zero_trust_consts.STATUS_FAILED: 0, - zero_trust_consts.STATUS_VERIFY: 0, - zero_trust_consts.STATUS_PASSED: 0, - zero_trust_consts.STATUS_UNEXECUTED: 0, - "pillar": "Automation & Orchestration" - } - ] - - result = ZeroTrustService.get_pillars_grades() - - self.assertEqual(result, expected) - - @pytest.mark.skip(reason="Broken during ScoutSuite refactoring, need to be fixed") - def test_get_principles_status(self): - self.fail_if_not_testing_env() - self.clean_finding_db() - - self.maxDiff = None - - save_example_findings() - - expected = dict(EXPECTED_DICT) # new mutable - - result = ZeroTrustService.get_principles_status() - # Compare expected and result, no order: - for pillar_name, pillar_principles_status_result in result.items(): - for index, pillar_principle_status_expected in enumerate(expected.get(pillar_name)): - correct_one = None - for pillar_principle_status_result in pillar_principles_status_result: - if pillar_principle_status_result["principle"] == pillar_principle_status_expected["principle"]: - correct_one = pillar_principle_status_result - break - - # Compare tests no order - self.assertTrue(compare_lists_no_order(correct_one["tests"], pillar_principle_status_expected["tests"])) - # Compare the rest - del pillar_principle_status_expected["tests"] - del correct_one["tests"] - self.assertEqual(sorted(correct_one), sorted(pillar_principle_status_expected)) - - @pytest.mark.skip(reason="Broken during ScoutSuite refactoring, need to be fixed") - def test_get_pillars_to_statuses(self): - self.fail_if_not_testing_env() - self.clean_finding_db() - - self.maxDiff = None - - expected = { - zero_trust_consts.AUTOMATION_ORCHESTRATION: zero_trust_consts.STATUS_UNEXECUTED, - zero_trust_consts.DEVICES: zero_trust_consts.STATUS_UNEXECUTED, - zero_trust_consts.NETWORKS: zero_trust_consts.STATUS_UNEXECUTED, - zero_trust_consts.PEOPLE: zero_trust_consts.STATUS_UNEXECUTED, - zero_trust_consts.VISIBILITY_ANALYTICS: zero_trust_consts.STATUS_UNEXECUTED, - zero_trust_consts.WORKLOADS: zero_trust_consts.STATUS_UNEXECUTED, - zero_trust_consts.DATA: zero_trust_consts.STATUS_UNEXECUTED - } - - self.assertEqual(ZeroTrustService.get_pillars_to_statuses(), expected) - - save_example_findings() - - expected = { - zero_trust_consts.AUTOMATION_ORCHESTRATION: zero_trust_consts.STATUS_UNEXECUTED, - zero_trust_consts.DEVICES: zero_trust_consts.STATUS_FAILED, - zero_trust_consts.NETWORKS: zero_trust_consts.STATUS_VERIFY, - zero_trust_consts.PEOPLE: zero_trust_consts.STATUS_VERIFY, - zero_trust_consts.VISIBILITY_ANALYTICS: zero_trust_consts.STATUS_UNEXECUTED, - zero_trust_consts.WORKLOADS: zero_trust_consts.STATUS_UNEXECUTED, - zero_trust_consts.DATA: zero_trust_consts.STATUS_FAILED - } - - self.assertEqual(ZeroTrustService.get_pillars_to_statuses(), expected) - - @pytest.mark.skip(reason="Broken during ScoutSuite refactoring, need to be fixed") - def test_get_events_without_overlap(self): - monkey_island.cc.services.reporting.zero_trust_service.EVENT_FETCH_CNT = 5 - self.assertListEqual([], ZeroTrustService._get_events_without_overlap(5, [1, 2, 3])) - self.assertListEqual([3], ZeroTrustService._get_events_without_overlap(6, [1, 2, 3])) - self.assertListEqual([1, 2, 3, 4, 5], ZeroTrustService._get_events_without_overlap(10, [1, 2, 3, 4, 5])) - - -def compare_lists_no_order(s, t): - t = list(t) # make a mutable copy - try: - for elem in s: - t.remove(elem) - except ValueError: - return False - return not t diff --git a/monkey/monkey_island/cc/test_common/fixtures/fixture_enum.py b/monkey/monkey_island/cc/test_common/fixtures/fixture_enum.py new file mode 100644 index 000000000..00ab2905f --- /dev/null +++ b/monkey/monkey_island/cc/test_common/fixtures/fixture_enum.py @@ -0,0 +1,4 @@ + + +class FixtureEnum: + USES_DATABASE = 'uses_database' From 393eed42daa6d755bf1d29f17e58b14c1a1b87ca Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 27 Jan 2021 08:54:09 +0200 Subject: [PATCH 122/152] Added zero trust report backend tests and common test data used in these tests --- .../zero_trust/monkey_finding_details.py | 4 - .../zero_trust/scoutsuite_finding_details.py | 5 - .../zero_trust/test_common/finding_data.py | 23 ++++ .../test_common/monkey_finding_data.py | 33 +++++ .../test_common/scoutsuite_finding_data.py | 76 ++++++++++++ .../test_common/example_finding_data.py | 57 +++++++++ .../zero_trust_report/test_finding_service.py | 51 ++++++++ .../zero_trust_report/test_pillar_service.py | 113 ++++++++++++++++++ .../test_principle_service.py | 94 +++++++++++++++ 9 files changed, 447 insertions(+), 9 deletions(-) create mode 100644 monkey/monkey_island/cc/services/zero_trust/test_common/finding_data.py create mode 100644 monkey/monkey_island/cc/services/zero_trust/test_common/monkey_finding_data.py create mode 100644 monkey/monkey_island/cc/services/zero_trust/test_common/scoutsuite_finding_data.py create mode 100644 monkey/monkey_island/cc/services/zero_trust/zero_trust_report/test_common/example_finding_data.py create mode 100644 monkey/monkey_island/cc/services/zero_trust/zero_trust_report/test_finding_service.py create mode 100644 monkey/monkey_island/cc/services/zero_trust/zero_trust_report/test_pillar_service.py create mode 100644 monkey/monkey_island/cc/services/zero_trust/zero_trust_report/test_principle_service.py diff --git a/monkey/monkey_island/cc/models/zero_trust/monkey_finding_details.py b/monkey/monkey_island/cc/models/zero_trust/monkey_finding_details.py index 1a75c7b4d..bec29fb21 100644 --- a/monkey/monkey_island/cc/models/zero_trust/monkey_finding_details.py +++ b/monkey/monkey_island/cc/models/zero_trust/monkey_finding_details.py @@ -8,10 +8,6 @@ from monkey_island.cc.models.zero_trust.event import Event class MonkeyFindingDetails(Document): - """ - This model represents additional information about monkey finding: - Events - """ # SCHEMA events = EmbeddedDocumentListField(document_type=Event, required=False) diff --git a/monkey/monkey_island/cc/models/zero_trust/scoutsuite_finding_details.py b/monkey/monkey_island/cc/models/zero_trust/scoutsuite_finding_details.py index 52aa09d17..cbc8c5f29 100644 --- a/monkey/monkey_island/cc/models/zero_trust/scoutsuite_finding_details.py +++ b/monkey/monkey_island/cc/models/zero_trust/scoutsuite_finding_details.py @@ -4,11 +4,6 @@ from monkey_island.cc.models.zero_trust.scoutsuite_rule import ScoutSuiteRule class ScoutSuiteFindingDetails(Document): - """ - This model represents additional information about monkey finding: - Events if monkey finding - Scoutsuite findings if scoutsuite finding - """ # SCHEMA scoutsuite_rules = EmbeddedDocumentListField(document_type=ScoutSuiteRule, required=False) diff --git a/monkey/monkey_island/cc/services/zero_trust/test_common/finding_data.py b/monkey/monkey_island/cc/services/zero_trust/test_common/finding_data.py new file mode 100644 index 000000000..0b8de9221 --- /dev/null +++ b/monkey/monkey_island/cc/services/zero_trust/test_common/finding_data.py @@ -0,0 +1,23 @@ +from common.common_consts.zero_trust_consts import TEST_SCOUTSUITE_SERVICE_SECURITY, STATUS_FAILED, SCOUTSUITE_FINDING, \ + TEST_ENDPOINT_SECURITY_EXISTS, STATUS_PASSED, MONKEY_FINDING +from monkey_island.cc.models.zero_trust.finding import Finding +from monkey_island.cc.services.zero_trust.test_common.monkey_finding_data import get_monkey_details_dto +from monkey_island.cc.services.zero_trust.test_common.scoutsuite_finding_data import get_scoutsuite_details_dto + + +def get_scoutsuite_finding_dto() -> Finding: + scoutsuite_details = get_scoutsuite_details_dto() + scoutsuite_details.save() + return Finding(test=TEST_SCOUTSUITE_SERVICE_SECURITY, + status=STATUS_FAILED, + finding_type=SCOUTSUITE_FINDING, + details=scoutsuite_details) + + +def get_monkey_finding_dto() -> Finding: + monkey_details = get_monkey_details_dto() + monkey_details.save() + return Finding(test=TEST_ENDPOINT_SECURITY_EXISTS, + status=STATUS_PASSED, + finding_type=MONKEY_FINDING, + details=monkey_details) diff --git a/monkey/monkey_island/cc/services/zero_trust/test_common/monkey_finding_data.py b/monkey/monkey_island/cc/services/zero_trust/test_common/monkey_finding_data.py new file mode 100644 index 000000000..d727ea9e8 --- /dev/null +++ b/monkey/monkey_island/cc/services/zero_trust/test_common/monkey_finding_data.py @@ -0,0 +1,33 @@ +from monkey_island.cc.models.zero_trust.event import Event +from monkey_island.cc.models.zero_trust.monkey_finding_details import MonkeyFindingDetails + +EVENTS = [ + { + "timestamp": "2021-01-20T15:40:28.357Z", + "title": "Process list", + "message": "Monkey on gc-pc-244 scanned the process list", + "event_type": "monkey_local" + }, + { + "timestamp": "2021-01-20T16:08:29.519Z", + "title": "Process list", + "message": "", + "event_type": "monkey_local" + }, +] + +EVENTS_DTO = [ + Event(timestamp=event['timestamp'], + title=event['title'], + message=event['message'], + event_type=event['event_type']) for event in EVENTS +] + +DETAILS_DTO = [] + + +def get_monkey_details_dto() -> MonkeyFindingDetails: + monkey_details = MonkeyFindingDetails() + monkey_details.events.append(EVENTS_DTO[0]) + monkey_details.events.append(EVENTS_DTO[1]) + return monkey_details diff --git a/monkey/monkey_island/cc/services/zero_trust/test_common/scoutsuite_finding_data.py b/monkey/monkey_island/cc/services/zero_trust/test_common/scoutsuite_finding_data.py new file mode 100644 index 000000000..cddc6b72c --- /dev/null +++ b/monkey/monkey_island/cc/services/zero_trust/test_common/scoutsuite_finding_data.py @@ -0,0 +1,76 @@ +from monkey_island.cc.models.zero_trust.scoutsuite_finding_details import ScoutSuiteFindingDetails +from monkey_island.cc.models.zero_trust.scoutsuite_rule import ScoutSuiteRule +from monkey_island.cc.services.zero_trust.scoutsuite.consts.findings import PermissiveFirewallRules, UnencryptedData + +SCOUTSUITE_FINDINGS = [ + PermissiveFirewallRules, + UnencryptedData +] + +RULES = [ + ScoutSuiteRule( + checked_items=179, + compliance=None, + dashboard_name='Rules', + description='Security Group Opens All Ports to All', + flagged_items=2, + items=[ + 'ec2.regions.eu-central-1.vpcs.vpc-0ee259b1a13c50229.security_groups.sg-035779fe5c293fc72' + '.rules.ingress.protocols.ALL.ports.1-65535.cidrs.2.CIDR', + 'ec2.regions.eu-central-1.vpcs.vpc-00015526b6695f9aa.security_groups.sg-019eb67135ec81e65' + '.rules.ingress.protocols.ALL.ports.1-65535.cidrs.0.CIDR' + ], + level='danger', + path='ec2.regions.id.vpcs.id.security_groups.id.rules.id.protocols.id.ports.id.cidrs.id.CIDR', + rationale='It was detected that all ports in the security group are open, and any source IP address' + ' could send traffic to these ports, which creates a wider attack surface for resources ' + 'assigned to it. Open ports should be reduced to the minimum needed to correctly', + references=[], + remediation=None, + service='EC2' + ), + ScoutSuiteRule( + checked_items=179, + compliance=[{'name': 'CIS Amazon Web Services Foundations', 'version': '1.0.0', 'reference': '4.1'}, + {'name': 'CIS Amazon Web Services Foundations', 'version': '1.0.0', 'reference': '4.2'}, + {'name': 'CIS Amazon Web Services Foundations', 'version': '1.1.0', 'reference': '4.1'}, + {'name': 'CIS Amazon Web Services Foundations', 'version': '1.1.0', 'reference': '4.2'}, + {'name': 'CIS Amazon Web Services Foundations', 'version': '1.2.0', 'reference': '4.1'}, + {'name': 'CIS Amazon Web Services Foundations', 'version': '1.2.0', 'reference': '4.2'}], + dashboard_name='Rules', + description='Security Group Opens RDP Port to All', + flagged_items=7, + items=[ + 'ec2.regions.eu-central-1.vpcs.vpc-076500a2138ee09da.security_groups.sg-00bdef5951797199c' + '.rules.ingress.protocols.TCP.ports.3389.cidrs.0.CIDR', + 'ec2.regions.eu-central-1.vpcs.vpc-d33026b8.security_groups.sg-007931ba8a364e330' + '.rules.ingress.protocols.TCP.ports.3389.cidrs.0.CIDR', + 'ec2.regions.eu-central-1.vpcs.vpc-d33026b8.security_groups.sg-05014daf996b042dd' + '.rules.ingress.protocols.TCP.ports.3389.cidrs.0.CIDR', + 'ec2.regions.eu-central-1.vpcs.vpc-d33026b8.security_groups.sg-0c745fe56c66335b2' + '.rules.ingress.protocols.TCP.ports.3389.cidrs.0.CIDR', + 'ec2.regions.eu-central-1.vpcs.vpc-d33026b8.security_groups.sg-0f99b85cfad63d1b1' + '.rules.ingress.protocols.TCP.ports.3389.cidrs.0.CIDR', + 'ec2.regions.us-east-1.vpcs.vpc-9e56cae4.security_groups.sg-0dc253aa79062835a' + '.rules.ingress.protocols.TCP.ports.3389.cidrs.0.CIDR', + 'ec2.regions.us-east-1.vpcs.vpc-002d543353cd4e97d.security_groups.sg-01902f153d4f938da' + '.rules.ingress.protocols.TCP.ports.3389.cidrs.0.CIDR'], + level='danger', + path='ec2.regions.id.vpcs.id.security_groups.id.rules.id.protocols.id.ports.id.cidrs.id.CIDR', + rationale='The security group was found to be exposing a well-known port to all source addresses.' + ' Well-known ports are commonly probed by automated scanning tools, and could be an indicator ' + 'of sensitive services exposed to Internet. If such services need to be expos', + references=[], + remediation='Remove the inbound rules that expose open ports', + service='EC2' + ) +] + + +def get_scoutsuite_details_dto() -> ScoutSuiteFindingDetails: + scoutsuite_details = ScoutSuiteFindingDetails() + scoutsuite_details.scoutsuite_rules.append(RULES[0]) + scoutsuite_details.scoutsuite_rules.append(RULES[1]) + return scoutsuite_details + + diff --git a/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/test_common/example_finding_data.py b/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/test_common/example_finding_data.py new file mode 100644 index 000000000..917678ed8 --- /dev/null +++ b/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/test_common/example_finding_data.py @@ -0,0 +1,57 @@ +from common.common_consts import zero_trust_consts +from monkey_island.cc.services.zero_trust.test_common.finding_data import get_monkey_finding_dto, \ + get_scoutsuite_finding_dto + + +def save_example_findings(): + # devices passed = 1 + _save_finding_with_status('scoutsuite', zero_trust_consts.TEST_ENDPOINT_SECURITY_EXISTS, + zero_trust_consts.STATUS_PASSED) + # devices passed = 2 + _save_finding_with_status('scoutsuite', zero_trust_consts.TEST_ENDPOINT_SECURITY_EXISTS, + zero_trust_consts.STATUS_PASSED) + # devices failed = 1 + _save_finding_with_status('monkey', zero_trust_consts.TEST_ENDPOINT_SECURITY_EXISTS, + zero_trust_consts.STATUS_FAILED) + # people verify = 1 + # networks verify = 1 + _save_finding_with_status('scoutsuite', zero_trust_consts.TEST_SCHEDULED_EXECUTION, + zero_trust_consts.STATUS_VERIFY) + # people verify = 2 + # networks verify = 2 + _save_finding_with_status('monkey', zero_trust_consts.TEST_SCHEDULED_EXECUTION, + zero_trust_consts.STATUS_VERIFY) + # data failed 1 + _save_finding_with_status('monkey', zero_trust_consts.TEST_DATA_ENDPOINT_HTTP, + zero_trust_consts.STATUS_FAILED) + # data failed 2 + _save_finding_with_status('scoutsuite', zero_trust_consts.TEST_SCOUTSUITE_UNENCRYPTED_DATA, + zero_trust_consts.STATUS_FAILED) + # data failed 3 + _save_finding_with_status('monkey', zero_trust_consts.TEST_DATA_ENDPOINT_HTTP, + zero_trust_consts.STATUS_FAILED) + # data failed 4 + _save_finding_with_status('monkey', zero_trust_consts.TEST_DATA_ENDPOINT_HTTP, + zero_trust_consts.STATUS_FAILED) + # data failed 5 + _save_finding_with_status('scoutsuite', zero_trust_consts.TEST_SCOUTSUITE_UNENCRYPTED_DATA, + zero_trust_consts.STATUS_FAILED) + # data verify 1 + _save_finding_with_status('monkey', zero_trust_consts.TEST_DATA_ENDPOINT_HTTP, + zero_trust_consts.STATUS_VERIFY) + # data verify 2 + _save_finding_with_status('monkey', zero_trust_consts.TEST_DATA_ENDPOINT_HTTP, + zero_trust_consts.STATUS_VERIFY) + # data passed 1 + _save_finding_with_status('scoutsuite', zero_trust_consts.TEST_SCOUTSUITE_UNENCRYPTED_DATA, + zero_trust_consts.STATUS_PASSED) + + +def _save_finding_with_status(finding_type: str, test: str, status: str): + if finding_type == 'scoutsuite': + finding = get_scoutsuite_finding_dto() + else: + finding = get_monkey_finding_dto() + finding.test = test + finding.status = status + finding.save() diff --git a/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/test_finding_service.py b/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/test_finding_service.py new file mode 100644 index 000000000..36ef060fd --- /dev/null +++ b/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/test_finding_service.py @@ -0,0 +1,51 @@ +from unittest.mock import MagicMock + +import pytest + +from common.common_consts.zero_trust_consts import TESTS_MAP, TEST_SCOUTSUITE_SERVICE_SECURITY, STATUS_FAILED, \ + SCOUTSUITE_FINDING, DEVICES, NETWORKS, MONKEY_FINDING, STATUS_PASSED, TEST_ENDPOINT_SECURITY_EXISTS +from monkey_island.cc.services.zero_trust.monkey_findings.monkey_zt_details_service import MonkeyZTDetailsService +from monkey_island.cc.services.zero_trust.test_common.finding_data import get_scoutsuite_finding_dto, get_monkey_finding_dto +from monkey_island.cc.services.zero_trust.zero_trust_report.finding_service import FindingService, EnrichedFinding +from monkey_island.cc.test_common.fixtures.fixture_enum import FixtureEnum + + +@pytest.mark.usefixtures(FixtureEnum.USES_DATABASE) +def test_get_all_findings(): + get_scoutsuite_finding_dto().save() + get_monkey_finding_dto().save() + + # This method fails due to mongomock not being able to simulate $unset, so don't test details + MonkeyZTDetailsService.fetch_details_for_display = MagicMock(return_value=None) + + findings = FindingService.get_all_findings() + + description = TESTS_MAP[TEST_SCOUTSUITE_SERVICE_SECURITY]['finding_explanation'][STATUS_FAILED] + expected_finding0 = EnrichedFinding(finding_id=findings[0].finding_id, + finding_type=SCOUTSUITE_FINDING, + pillars=[DEVICES, NETWORKS], + status=STATUS_FAILED, + test=description, + test_key=TEST_SCOUTSUITE_SERVICE_SECURITY, + details=None) + + description = TESTS_MAP[TEST_ENDPOINT_SECURITY_EXISTS]['finding_explanation'][STATUS_PASSED] + expected_finding1 = EnrichedFinding(finding_id=findings[1].finding_id, + finding_type=MONKEY_FINDING, + pillars=[DEVICES], + status=STATUS_PASSED, + test=description, + test_key=TEST_ENDPOINT_SECURITY_EXISTS, + details=None) + + # Don't test details + details = [] + for finding in findings: + details.append(finding.details) + finding.details = None + + assert findings[0] == expected_finding0 + assert findings[1] == expected_finding1 + + + diff --git a/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/test_pillar_service.py b/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/test_pillar_service.py new file mode 100644 index 000000000..48f3c0d85 --- /dev/null +++ b/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/test_pillar_service.py @@ -0,0 +1,113 @@ +from typing import List + +import pytest + +from common.common_consts import zero_trust_consts +from common.common_consts.zero_trust_consts import DATA, PEOPLE, NETWORKS, WORKLOADS, VISIBILITY_ANALYTICS, \ + AUTOMATION_ORCHESTRATION, DEVICES +from monkey_island.cc.services.zero_trust.zero_trust_report.pillar_service import PillarService +from monkey_island.cc.services.zero_trust.zero_trust_report.test_common.example_finding_data import \ + save_example_findings +from monkey_island.cc.test_common.fixtures import FixtureEnum + + +@pytest.mark.usefixtures(FixtureEnum.USES_DATABASE) +def test_get_pillars_grades(): + save_example_findings() + expected_grades = _get_expected_pillar_grades() + computed_grades = PillarService.get_pillars_grades() + assert expected_grades == computed_grades + + +def _get_expected_pillar_grades() -> List[dict]: + return [ + { + zero_trust_consts.STATUS_FAILED: 5, + zero_trust_consts.STATUS_VERIFY: 2, + zero_trust_consts.STATUS_PASSED: 1, + # 2 different tests of DATA pillar were executed in _save_findings() + zero_trust_consts.STATUS_UNEXECUTED: _get_cnt_of_tests_in_pillar(DATA) - 2, + "pillar": "Data" + }, + { + zero_trust_consts.STATUS_FAILED: 0, + zero_trust_consts.STATUS_VERIFY: 2, + zero_trust_consts.STATUS_PASSED: 0, + # 1 test of PEOPLE pillar were executed in _save_findings() + zero_trust_consts.STATUS_UNEXECUTED: _get_cnt_of_tests_in_pillar(PEOPLE) - 1, + "pillar": "People" + }, + { + zero_trust_consts.STATUS_FAILED: 0, + zero_trust_consts.STATUS_VERIFY: 2, + zero_trust_consts.STATUS_PASSED: 0, + # 1 different tests of NETWORKS pillar were executed in _save_findings() + zero_trust_consts.STATUS_UNEXECUTED: _get_cnt_of_tests_in_pillar(NETWORKS) - 1, + "pillar": "Networks" + }, + { + zero_trust_consts.STATUS_FAILED: 1, + zero_trust_consts.STATUS_VERIFY: 0, + zero_trust_consts.STATUS_PASSED: 2, + # 1 different tests of DEVICES pillar were executed in _save_findings() + zero_trust_consts.STATUS_UNEXECUTED: _get_cnt_of_tests_in_pillar(DEVICES) - 1, + "pillar": "Devices" + }, + { + zero_trust_consts.STATUS_FAILED: 0, + zero_trust_consts.STATUS_VERIFY: 0, + zero_trust_consts.STATUS_PASSED: 0, + # 0 different tests of WORKLOADS pillar were executed in _save_findings() + zero_trust_consts.STATUS_UNEXECUTED: _get_cnt_of_tests_in_pillar(WORKLOADS), + "pillar": "Workloads" + }, + { + zero_trust_consts.STATUS_FAILED: 0, + zero_trust_consts.STATUS_VERIFY: 0, + zero_trust_consts.STATUS_PASSED: 0, + # 0 different tests of VISIBILITY_ANALYTICS pillar were executed in _save_findings() + zero_trust_consts.STATUS_UNEXECUTED: _get_cnt_of_tests_in_pillar(VISIBILITY_ANALYTICS), + "pillar": "Visibility & Analytics" + }, + { + zero_trust_consts.STATUS_FAILED: 0, + zero_trust_consts.STATUS_VERIFY: 0, + zero_trust_consts.STATUS_PASSED: 0, + # 0 different tests of AUTOMATION_ORCHESTRATION pillar were executed in _save_findings() + zero_trust_consts.STATUS_UNEXECUTED: _get_cnt_of_tests_in_pillar(AUTOMATION_ORCHESTRATION), + "pillar": "Automation & Orchestration" + } + ] + + +def _get_cnt_of_tests_in_pillar(pillar: str): + tests_in_pillar = [value for (key, value) in zero_trust_consts.TESTS_MAP.items() if pillar in value['pillars']] + return len(tests_in_pillar) + + +@pytest.mark.usefixtures(FixtureEnum.USES_DATABASE) +def test_get_pillars_to_statuses(): + # Test empty database + expected = { + zero_trust_consts.AUTOMATION_ORCHESTRATION: zero_trust_consts.STATUS_UNEXECUTED, + zero_trust_consts.DEVICES: zero_trust_consts.STATUS_UNEXECUTED, + zero_trust_consts.NETWORKS: zero_trust_consts.STATUS_UNEXECUTED, + zero_trust_consts.PEOPLE: zero_trust_consts.STATUS_UNEXECUTED, + zero_trust_consts.VISIBILITY_ANALYTICS: zero_trust_consts.STATUS_UNEXECUTED, + zero_trust_consts.WORKLOADS: zero_trust_consts.STATUS_UNEXECUTED, + zero_trust_consts.DATA: zero_trust_consts.STATUS_UNEXECUTED + } + assert PillarService.get_pillars_to_statuses() == expected + + # Test with example finding set + save_example_findings() + expected = { + zero_trust_consts.AUTOMATION_ORCHESTRATION: zero_trust_consts.STATUS_UNEXECUTED, + zero_trust_consts.DEVICES: zero_trust_consts.STATUS_FAILED, + zero_trust_consts.NETWORKS: zero_trust_consts.STATUS_VERIFY, + zero_trust_consts.PEOPLE: zero_trust_consts.STATUS_VERIFY, + zero_trust_consts.VISIBILITY_ANALYTICS: zero_trust_consts.STATUS_UNEXECUTED, + zero_trust_consts.WORKLOADS: zero_trust_consts.STATUS_UNEXECUTED, + zero_trust_consts.DATA: zero_trust_consts.STATUS_FAILED + } + assert PillarService.get_pillars_to_statuses() == expected diff --git a/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/test_principle_service.py b/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/test_principle_service.py new file mode 100644 index 000000000..fd2502f59 --- /dev/null +++ b/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/test_principle_service.py @@ -0,0 +1,94 @@ +import pytest + +from common.common_consts import zero_trust_consts +from monkey_island.cc.services.zero_trust.test_common.finding_data import get_monkey_finding_dto, \ + get_scoutsuite_finding_dto +from monkey_island.cc.services.zero_trust.zero_trust_report.principle_service import PrincipleService +from monkey_island.cc.test_common.fixtures import FixtureEnum + + +EXPECTED_DICT = { + 'test_pillar1': [ + { + "principle": 'Test principle description2', + "status": zero_trust_consts.STATUS_FAILED, + "tests": [ + { + "status": zero_trust_consts.STATUS_PASSED, + "test": "You ran a test2" + }, + { + "status": zero_trust_consts.STATUS_FAILED, + "test": "You ran a test3" + } + ] + } + ], + 'test_pillar2': [ + { + "principle": "Test principle description", + "status": zero_trust_consts.STATUS_PASSED, + "tests": [ + { + "status": zero_trust_consts.STATUS_PASSED, + "test": "You ran a test1" + } + ] + }, + { + "principle": "Test principle description2", + "status": zero_trust_consts.STATUS_FAILED, + "tests": [ + { + "status": zero_trust_consts.STATUS_PASSED, + "test": "You ran a test2" + }, + { + "status": zero_trust_consts.STATUS_FAILED, + "test": "You ran a test3" + }, + ] + } + ] +} + + +@pytest.mark.usefixtures(FixtureEnum.USES_DATABASE) +def test_get_principles_status(): + TEST_PILLAR1 = 'test_pillar1' + TEST_PILLAR2 = 'test_pillar2' + zero_trust_consts.PILLARS = (TEST_PILLAR1, TEST_PILLAR2) + + principles_to_tests = {'network_policies': ['segmentation'], + 'endpoint_security': ['tunneling', 'scoutsuite_service_security']} + zero_trust_consts.PRINCIPLES_TO_TESTS = principles_to_tests + + principles_to_pillars = {'network_policies': {'test_pillar2'}, + 'endpoint_security': {'test_pillar1', 'test_pillar2'}} + zero_trust_consts.PRINCIPLES_TO_PILLARS = principles_to_pillars + + principles = {'network_policies': 'Test principle description', 'endpoint_security': 'Test principle description2'} + zero_trust_consts.PRINCIPLES = principles + + tests_map = {'segmentation': {'explanation': 'You ran a test1'}, + 'tunneling': {'explanation': 'You ran a test2'}, + 'scoutsuite_service_security': {'explanation': 'You ran a test3'}} + zero_trust_consts.TESTS_MAP = tests_map + + monkey_finding = get_monkey_finding_dto() + monkey_finding.test = 'segmentation' + monkey_finding.save() + + monkey_finding = get_monkey_finding_dto() + monkey_finding.test = 'tunneling' + monkey_finding.save() + + scoutsuite_finding = get_scoutsuite_finding_dto() + scoutsuite_finding.test = 'scoutsuite_service_security' + scoutsuite_finding.save() + + expected = dict(EXPECTED_DICT) # new mutable + + result = PrincipleService.get_principles_status() + + assert result == expected From 6fac75edb6d94fa62a67dfdae4f6b3da48d1c24c Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 27 Jan 2021 11:33:05 +0200 Subject: [PATCH 123/152] Implemented more unit tests for scoutsuite --- .../scoutsuite_zt_finding_service.py | 4 +- .../test_scoutsuite_auth_service.py | 32 +++++++++++ .../test_scoutsuite_rule_service.py | 54 +++++++++++++++++++ 3 files changed, 88 insertions(+), 2 deletions(-) create mode 100644 monkey/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_auth_service.py create mode 100644 monkey/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_rule_service.py diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_zt_finding_service.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_zt_finding_service.py index dd467741a..24740840f 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_zt_finding_service.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_zt_finding_service.py @@ -16,12 +16,12 @@ class ScoutSuiteZTFindingService: assert (len(existing_findings) < 2), "More than one finding exists for {}".format(finding.test) if len(existing_findings) == 0: - ScoutSuiteZTFindingService.create_new_finding_from_rule(finding, rule) + ScoutSuiteZTFindingService._create_new_finding_from_rule(finding, rule) else: ScoutSuiteZTFindingService.add_rule(existing_findings[0], rule) @staticmethod - def create_new_finding_from_rule(finding: ScoutSuiteFinding, rule: ScoutSuiteRule): + def _create_new_finding_from_rule(finding: ScoutSuiteFinding, rule: ScoutSuiteRule): details = ScoutSuiteFindingDetails() details.scoutsuite_rules = [rule] details.save() diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_auth_service.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_auth_service.py new file mode 100644 index 000000000..322af5af6 --- /dev/null +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_auth_service.py @@ -0,0 +1,32 @@ +from unittest.mock import MagicMock + +import pytest +import dpath.util + +from monkey_island.cc.database import mongo +from monkey_island.cc.server_utils import encryptor +from monkey_island.cc.services.config import ConfigService +from monkey_island.cc.services.config_schema.config_value_paths import AWS_KEYS_PATH +from monkey_island.cc.services.zero_trust.scoutsuite.scoutsuite_auth_service import is_aws_keys_setup +from monkey_island.cc.test_common.fixtures import FixtureEnum + + +class MockObject: + pass + +@pytest.mark.usefixtures(FixtureEnum.USES_DATABASE) +def test_is_aws_keys_setup(): + # Mock default configuration + ConfigService.init_default_config() + mongo.db = MockObject() + mongo.db.config = MockObject() + ConfigService.encrypt_config(ConfigService.default_config) + mongo.db.config.find_one = MagicMock(return_value=ConfigService.default_config) + assert not is_aws_keys_setup() + + # Make sure noone changed config path and broke this function + bogus_key_value = encryptor.encryptor.enc('bogus_aws_key') + dpath.util.set(ConfigService.default_config, AWS_KEYS_PATH+['aws_secret_access_key'], bogus_key_value) + dpath.util.set(ConfigService.default_config, AWS_KEYS_PATH+['aws_access_key_id'], bogus_key_value) + + assert is_aws_keys_setup() diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_rule_service.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_rule_service.py new file mode 100644 index 000000000..e08c8a290 --- /dev/null +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_rule_service.py @@ -0,0 +1,54 @@ +from copy import deepcopy + +from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_consts import RULE_LEVEL_WARNING, RULE_LEVEL_DANGER +from monkey_island.cc.services.zero_trust.scoutsuite.scoutsuite_rule_service import ScoutSuiteRuleService +from monkey_island.cc.services.zero_trust.test_common.scoutsuite_finding_data import RULES + +example_scoutsuite_data = { + 'checked_items': 179, + 'compliance': None, + 'dashboard_name': 'Rules', + 'description': 'Security Group Opens All Ports to All', + 'flagged_items': 2, + 'items': [ + 'ec2.regions.eu-central-1.vpcs.vpc-0ee259b1a13c50229.security_groups.sg-035779fe5c293fc72' + '.rules.ingress.protocols.ALL.ports.1-65535.cidrs.2.CIDR', + 'ec2.regions.eu-central-1.vpcs.vpc-00015526b6695f9aa.security_groups.sg-019eb67135ec81e65' + '.rules.ingress.protocols.ALL.ports.1-65535.cidrs.0.CIDR' + ], + 'level': 'danger', + 'path': 'ec2.regions.id.vpcs.id.security_groups.id.rules.id.protocols.id.ports.id.cidrs.id.CIDR', + 'rationale': 'It was detected that all ports in the security group are open, and any source IP address' + ' could send traffic to these ports, which creates a wider attack surface for resources ' + 'assigned to it. Open ports should be reduced to the minimum needed to correctly', + 'references': [], + 'remediation': None, + 'service': 'EC2' +} + + +def test_get_rule_from_rule_data(): + assert ScoutSuiteRuleService.get_rule_from_rule_data(example_scoutsuite_data) == RULES[0] + + +def test_is_rule_dangerous(): + test_rule = deepcopy(RULES[0]) + assert ScoutSuiteRuleService.is_rule_dangerous(test_rule) + + test_rule.level = RULE_LEVEL_WARNING + assert not ScoutSuiteRuleService.is_rule_dangerous(test_rule) + + test_rule.level = RULE_LEVEL_DANGER + test_rule.items = [] + assert not ScoutSuiteRuleService.is_rule_dangerous(test_rule) + + +def test_is_rule_warning(): + test_rule = deepcopy(RULES[0]) + assert not ScoutSuiteRuleService.is_rule_warning(test_rule) + + test_rule.level = RULE_LEVEL_WARNING + assert ScoutSuiteRuleService.is_rule_warning(test_rule) + + test_rule.items = [] + assert not ScoutSuiteRuleService.is_rule_warning(test_rule) From d0404cbeae083b7fab07b211d273be4c788d5da4 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 27 Jan 2021 16:58:05 +0200 Subject: [PATCH 124/152] Removed scoutsuite from requirements, because it's imported as a subpackage --- monkey/common/cloud/scoutsuite | 2 +- monkey/infection_monkey/requirements.txt | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/monkey/common/cloud/scoutsuite b/monkey/common/cloud/scoutsuite index b27a719de..e5dd01dab 160000 --- a/monkey/common/cloud/scoutsuite +++ b/monkey/common/cloud/scoutsuite @@ -1 +1 @@ -Subproject commit b27a719de18b47eacbeccd81627fc28114d09297 +Subproject commit e5dd01dab08ed8d887736cfcee068c1b8895d4a1 diff --git a/monkey/infection_monkey/requirements.txt b/monkey/infection_monkey/requirements.txt index 8df4c7403..c9633b555 100644 --- a/monkey/infection_monkey/requirements.txt +++ b/monkey/infection_monkey/requirements.txt @@ -14,4 +14,3 @@ pymssql<3.0 pypykatz==0.3.12 requests>=2.24 wmi==1.5.1 ; sys_platform == 'win32' -ScoutSuite From 7aef86744eb4fe3449dbb773f2a24ec77293c17d Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Thu, 28 Jan 2021 12:13:54 +0200 Subject: [PATCH 125/152] Improved readability of zero trust report resource by creating separate service for raw scoutsuite data and moving pillar report data structure into separate method on pillar service --- .../cc/resources/zero_trust/zero_trust_report.py | 15 ++++----------- .../zero_trust_report/pillar_service.py | 12 +++++++++--- .../scoutsuite_raw_data_service.py | 13 +++++++++++++ .../zero_trust_report/test_pillar_service.py | 6 +++--- 4 files changed, 29 insertions(+), 17 deletions(-) create mode 100644 monkey/monkey_island/cc/services/zero_trust/zero_trust_report/scoutsuite_raw_data_service.py diff --git a/monkey/monkey_island/cc/resources/zero_trust/zero_trust_report.py b/monkey/monkey_island/cc/resources/zero_trust/zero_trust_report.py index 0f9feab76..a85499b2f 100644 --- a/monkey/monkey_island/cc/resources/zero_trust/zero_trust_report.py +++ b/monkey/monkey_island/cc/resources/zero_trust/zero_trust_report.py @@ -3,11 +3,11 @@ import http.client import flask_restful from flask import Response, jsonify -from monkey_island.cc.models.zero_trust.scoutsuite_data_json import ScoutSuiteDataJson from monkey_island.cc.resources.auth.auth import jwt_required from monkey_island.cc.services.zero_trust.zero_trust_report.finding_service import FindingService from monkey_island.cc.services.zero_trust.zero_trust_report.pillar_service import PillarService from monkey_island.cc.services.zero_trust.zero_trust_report.principle_service import PrincipleService +from monkey_island.cc.services.zero_trust.zero_trust_report.scoutsuite_raw_data_service import ScoutSuiteRawDataService REPORT_DATA_PILLARS = "pillars" REPORT_DATA_FINDINGS = "findings" @@ -20,20 +20,13 @@ class ZeroTrustReport(flask_restful.Resource): @jwt_required def get(self, report_data=None): if report_data == REPORT_DATA_PILLARS: - return jsonify({ - "statusesToPillars": PillarService.get_statuses_to_pillars(), - "pillarsToStatuses": PillarService.get_pillars_to_statuses(), - "grades": PillarService.get_pillars_grades() - }) + return jsonify(PillarService.get_pillar_report_data()) elif report_data == REPORT_DATA_PRINCIPLES_STATUS: return jsonify(PrincipleService.get_principles_status()) elif report_data == REPORT_DATA_FINDINGS: return jsonify(FindingService.get_all_findings()) elif report_data == REPORT_DATA_SCOUTSUITE: - try: - data = ScoutSuiteDataJson.objects.get().scoutsuite_data - except Exception: - data = "{}" - return Response(data, mimetype='application/json') + return Response(ScoutSuiteRawDataService.get_scoutsuite_data_json(), + mimetype='application/json') flask_restful.abort(http.client.NOT_FOUND) diff --git a/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/pillar_service.py b/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/pillar_service.py index 67fccff8f..9ec584571 100644 --- a/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/pillar_service.py +++ b/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/pillar_service.py @@ -5,7 +5,13 @@ from monkey_island.cc.models.zero_trust.finding import Finding class PillarService: @staticmethod - def get_pillars_grades(): + def get_pillar_report_data(): + return {"statusesToPillars": PillarService._get_statuses_to_pillars(), + "pillarsToStatuses": PillarService._get_pillars_to_statuses(), + "grades": PillarService._get_pillars_grades()} + + @staticmethod + def _get_pillars_grades(): pillars_grades = [] all_findings = Finding.objects() for pillar in zero_trust_consts.PILLARS: @@ -39,7 +45,7 @@ class PillarService: return pillar_grade @staticmethod - def get_statuses_to_pillars(): + def _get_statuses_to_pillars(): results = { zero_trust_consts.STATUS_FAILED: [], zero_trust_consts.STATUS_VERIFY: [], @@ -52,7 +58,7 @@ class PillarService: return results @staticmethod - def get_pillars_to_statuses(): + def _get_pillars_to_statuses(): results = {} for pillar in zero_trust_consts.PILLARS: results[pillar] = PillarService.__get_status_of_single_pillar(pillar) diff --git a/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/scoutsuite_raw_data_service.py b/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/scoutsuite_raw_data_service.py new file mode 100644 index 000000000..006f3250f --- /dev/null +++ b/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/scoutsuite_raw_data_service.py @@ -0,0 +1,13 @@ +from monkey_island.cc.models.zero_trust.scoutsuite_data_json import ScoutSuiteDataJson + + +class ScoutSuiteRawDataService: + + # Return unparsed json of ScoutSuite results, + # so that UI can pick out values it needs for report + @staticmethod + def get_scoutsuite_data_json() -> str: + try: + return ScoutSuiteDataJson.objects.get().scoutsuite_data + except Exception: + return "{}" diff --git a/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/test_pillar_service.py b/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/test_pillar_service.py index 48f3c0d85..bf2bbe1a5 100644 --- a/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/test_pillar_service.py +++ b/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/test_pillar_service.py @@ -15,7 +15,7 @@ from monkey_island.cc.test_common.fixtures import FixtureEnum def test_get_pillars_grades(): save_example_findings() expected_grades = _get_expected_pillar_grades() - computed_grades = PillarService.get_pillars_grades() + computed_grades = PillarService._get_pillars_grades() assert expected_grades == computed_grades @@ -97,7 +97,7 @@ def test_get_pillars_to_statuses(): zero_trust_consts.WORKLOADS: zero_trust_consts.STATUS_UNEXECUTED, zero_trust_consts.DATA: zero_trust_consts.STATUS_UNEXECUTED } - assert PillarService.get_pillars_to_statuses() == expected + assert PillarService._get_pillars_to_statuses() == expected # Test with example finding set save_example_findings() @@ -110,4 +110,4 @@ def test_get_pillars_to_statuses(): zero_trust_consts.WORKLOADS: zero_trust_consts.STATUS_UNEXECUTED, zero_trust_consts.DATA: zero_trust_consts.STATUS_FAILED } - assert PillarService.get_pillars_to_statuses() == expected + assert PillarService._get_pillars_to_statuses() == expected From 255bfe9444b0977955252c7f83047c3fcda8ef4d Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Thu, 28 Jan 2021 12:15:01 +0200 Subject: [PATCH 126/152] Minor readability improvements: typehints and comments where needed --- .../collectors/scoutsuite_collector/scoutsuite_api.py | 2 ++ .../scoutsuite_collector/scoutsuite_collector.py | 3 ++- monkey/infection_monkey/telemetry/scoutsuite_telem.py | 3 ++- .../cc/services/zero_trust/scoutsuite/consts/findings.py | 5 +++-- .../zerotrust/scoutsuite/ScoutSuiteDataParser.js | 5 +++-- .../zerotrust/scoutsuite/ScoutSuiteRuleButton.js | 8 ++++---- 6 files changed, 16 insertions(+), 10 deletions(-) diff --git a/monkey/infection_monkey/system_info/collectors/scoutsuite_collector/scoutsuite_api.py b/monkey/infection_monkey/system_info/collectors/scoutsuite_collector/scoutsuite_api.py index 3235f7d34..575058946 100644 --- a/monkey/infection_monkey/system_info/collectors/scoutsuite_collector/scoutsuite_api.py +++ b/monkey/infection_monkey/system_info/collectors/scoutsuite_collector/scoutsuite_api.py @@ -10,6 +10,8 @@ def _add_scoutsuite_to_python_path(): sys.path.append(scoutsuite_path) +# Add ScoutSuite to python path because this way +# we don't need to change any imports in ScoutSuite code _add_scoutsuite_to_python_path() import common.cloud.scoutsuite.ScoutSuite.api_run as scoutsuite_api diff --git a/monkey/infection_monkey/system_info/collectors/scoutsuite_collector/scoutsuite_collector.py b/monkey/infection_monkey/system_info/collectors/scoutsuite_collector/scoutsuite_collector.py index 25b0ea833..7726e980e 100644 --- a/monkey/infection_monkey/system_info/collectors/scoutsuite_collector/scoutsuite_collector.py +++ b/monkey/infection_monkey/system_info/collectors/scoutsuite_collector/scoutsuite_collector.py @@ -1,6 +1,7 @@ import logging import infection_monkey.system_info.collectors.scoutsuite_collector.scoutsuite_api as scoutsuite_api +from common.cloud.scoutsuite.ScoutSuite.providers.aws.provider import AWSProvider from common.cloud.scoutsuite_consts import CloudProviders from common.utils.exceptions import ScoutSuiteScanError from infection_monkey.config import WormConfiguration @@ -26,5 +27,5 @@ def run_scoutsuite(cloud_type: str): aws_session_token=WormConfiguration.aws_session_token) -def send_results(results): +def send_results(results: AWSProvider): ScoutSuiteTelem(results).send() diff --git a/monkey/infection_monkey/telemetry/scoutsuite_telem.py b/monkey/infection_monkey/telemetry/scoutsuite_telem.py index 816042d7c..4e49c0695 100644 --- a/monkey/infection_monkey/telemetry/scoutsuite_telem.py +++ b/monkey/infection_monkey/telemetry/scoutsuite_telem.py @@ -1,11 +1,12 @@ from common.cloud.scoutsuite.ScoutSuite.output.result_encoder import ScoutJsonEncoder +from common.cloud.scoutsuite.ScoutSuite.providers.aws.provider import AWSProvider from common.common_consts.telem_categories import TelemCategoryEnum from infection_monkey.telemetry.base_telem import BaseTelem class ScoutSuiteTelem(BaseTelem): - def __init__(self, data): + def __init__(self, data: AWSProvider): """ Default ScoutSuite telemetry constructor :param data: Data gathered via ScoutSuite diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/findings.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/findings.py index 762f6bf80..3368cbbdf 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/findings.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/findings.py @@ -1,4 +1,5 @@ from abc import ABC, abstractmethod +from typing import List from common.common_consts import zero_trust_consts from .rule_names.cloudformation_rules import CloudformationRules @@ -21,12 +22,12 @@ from .rule_names.vpc_rules import VPCRules class ScoutSuiteFinding(ABC): @property @abstractmethod - def rules(self): + def rules(self) -> List[str]: pass @property @abstractmethod - def test(self): + def test(self) -> str: pass diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/scoutsuite/ScoutSuiteDataParser.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/scoutsuite/ScoutSuiteDataParser.js index 729499dec..9657c0bba 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/scoutsuite/ScoutSuiteDataParser.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/scoutsuite/ScoutSuiteDataParser.js @@ -4,10 +4,11 @@ export default class ScoutSuiteDataParser { } /** - * + * Gets value of cloud resource based on path of specific checked field and more abstract template path, + * which describes the scope of resource values. * @param itemPath contains path to a specific value e.g. s3.buckets.da1e7081077ce92.secure_transport_enabled * @param templatePath contains a template path for resource we would want to display e.g. s3.buckets.id - * @returns {*[]|*} + * @returns {*[]|*} resource value e.g. {'bucket_id': 123, 'bucket_max_size': '123GB'} */ getResourceValue(itemPath, templatePath) { let resourcePath = this.fillTemplatePath(itemPath, templatePath); diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/scoutsuite/ScoutSuiteRuleButton.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/scoutsuite/ScoutSuiteRuleButton.js index 316f2f90b..4b46c7c3b 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/scoutsuite/ScoutSuiteRuleButton.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/scoutsuite/ScoutSuiteRuleButton.js @@ -40,11 +40,11 @@ export default class ScoutSuiteRuleButton extends Component { } function RuleCountBadge(props) { - const maxRuleCountToShow = 9; - const textForMoreThanMaxRuleCount = maxRuleCountToShow + '+'; + const MAX_RULE_COUNT_TO_SHOW = 9; + const TEXT_FOR_LARGE_RULE_COUNT = MAX_RULE_COUNT_TO_SHOW + '+'; - const ruleCountText = props.count > maxRuleCountToShow ? - textForMoreThanMaxRuleCount : props.count; + const ruleCountText = props.count > MAX_RULE_COUNT_TO_SHOW ? + TEXT_FOR_LARGE_RULE_COUNT : props.count; return {ruleCountText}; } From 7761d16cf8602b91a78547283e2935fe9ccc96e4 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Thu, 28 Jan 2021 12:16:08 +0200 Subject: [PATCH 127/152] Bolded the fact that user has to run monkey from Island to start ScoutSuite security scan --- .../zero_trust/scoutsuite/scoutsuite_auth_service.py | 5 ++--- .../RunMonkeyPage/scoutsuite-setup/CloudOptions.js | 10 +++++++++- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_auth_service.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_auth_service.py index eb0d5dfbd..dc3f8d5ee 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_auth_service.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_auth_service.py @@ -12,13 +12,12 @@ from monkey_island.cc.services.config_schema.config_value_paths import AWS_KEYS_ def is_cloud_authentication_setup(provider: CloudProviders) -> Tuple[bool, str]: if provider == CloudProviders.AWS.value: if is_aws_keys_setup(): - return True, "AWS keys already setup. Run Monkey on Island to start the scan." + return True, "AWS keys already setup." import common.cloud.scoutsuite.ScoutSuite.providers.aws.authentication_strategy as auth_strategy try: profile = auth_strategy.AWSAuthenticationStrategy().authenticate() - return True, f" Profile \"{profile.session.profile_name}\" is already setup. " \ - f"Run Monkey on Island to start the scan." + return True, f" Profile \"{profile.session.profile_name}\" is already setup. " except AuthenticationException: return False, "" diff --git a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/scoutsuite-setup/CloudOptions.js b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/scoutsuite-setup/CloudOptions.js index 1e1d83c4a..bd9c83f04 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/scoutsuite-setup/CloudOptions.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/scoutsuite-setup/CloudOptions.js @@ -29,7 +29,7 @@ const getContents = (props) => { .then(res => res.json()) .then(res => { if(res.is_setup){ - setDescription(res.message + ' Click next to change the configuration.'); + setDescription(getDescription(res.message)); setIconType('icon-success'); setIcon(faCheck); } else { @@ -40,6 +40,14 @@ const getContents = (props) => { }); }, [props]); + function getDescription(message){ + return ( + <> + {message} Run from the Island to start the scan. Click next to change the configuration. + + ) + } + return ( <> Date: Thu, 28 Jan 2021 15:11:58 +0200 Subject: [PATCH 128/152] Renamed file to match class --- .../{test_segmentation.py => test_segmentation_checks.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename monkey/monkey_island/cc/services/telemetry/zero_trust_checks/{test_segmentation.py => test_segmentation_checks.py} (100%) diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/test_segmentation.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/test_segmentation_checks.py similarity index 100% rename from monkey/monkey_island/cc/services/telemetry/zero_trust_checks/test_segmentation.py rename to monkey/monkey_island/cc/services/telemetry/zero_trust_checks/test_segmentation_checks.py From ad0b428699b9894ffbec1d8c67cbbc61941f6b9d Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Thu, 28 Jan 2021 15:12:15 +0200 Subject: [PATCH 129/152] Refactored long imports to relative imports --- .../system_info_telemetry_dispatcher.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/system_info_telemetry_dispatcher.py b/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/system_info_telemetry_dispatcher.py index b004037b6..454657b27 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/system_info_telemetry_dispatcher.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/system_info_telemetry_dispatcher.py @@ -3,12 +3,10 @@ import typing from common.common_consts.system_info_collectors_names import (AWS_COLLECTOR, ENVIRONMENT_COLLECTOR, HOSTNAME_COLLECTOR, PROCESS_LIST_COLLECTOR, SCOUTSUITE_COLLECTOR) -from monkey_island.cc.services.telemetry.processing.system_info_collectors.aws import process_aws_telemetry -from monkey_island.cc.services.telemetry.processing.system_info_collectors.environment import \ - process_environment_telemetry -from monkey_island.cc.services.telemetry.processing.system_info_collectors.hostname import process_hostname_telemetry -from monkey_island.cc.services.telemetry.processing.system_info_collectors.scoutsuite import \ - process_scout_suite_telemetry +from .aws import process_aws_telemetry +from .environment import process_environment_telemetry +from .hostname import process_hostname_telemetry +from .scoutsuite import process_scout_suite_telemetry from monkey_island.cc.services.telemetry.zero_trust_checks.antivirus_existence import check_antivirus_existence logger = logging.getLogger(__name__) From d333e8c1c0cb14f4442a091fa5ef86a5091d1fcb Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Thu, 28 Jan 2021 15:33:33 +0200 Subject: [PATCH 130/152] Refactored fetch_details_for_display to return empty dict instead of empty array(because of type hint) --- .../zero_trust/monkey_findings/monkey_zt_details_service.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/monkey/monkey_island/cc/services/zero_trust/monkey_findings/monkey_zt_details_service.py b/monkey/monkey_island/cc/services/zero_trust/monkey_findings/monkey_zt_details_service.py index e73ce0cec..893f8221f 100644 --- a/monkey/monkey_island/cc/services/zero_trust/monkey_findings/monkey_zt_details_service.py +++ b/monkey/monkey_island/cc/services/zero_trust/monkey_findings/monkey_zt_details_service.py @@ -23,7 +23,9 @@ class MonkeyZTDetailsService: details = details[0] details['latest_events'] = MonkeyZTDetailsService._get_events_without_overlap(details['event_count'], details['latest_events']) - return details + return details + else: + return {} @staticmethod def _get_events_without_overlap(event_count: int, events: List[object]) -> List[object]: From 2549e1f3454cdf3f5f074ba65cf340ed07e4fa90 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Thu, 28 Jan 2021 16:32:24 +0200 Subject: [PATCH 131/152] Deleted custom travis flags that remove scoutsuite code checking, because they are already added in config files --- .travis.yml | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index adecc82d1..29b455ef7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -61,8 +61,7 @@ script: ## Warn about linter issues. ### --exit-zero forces Flake8 to use the exit status code 0 even if there are errors, which means this will NOT fail the build. ### The output is redirected to a file. -# TODO move scoutsuite stuff to config -- flake8 ./monkey --exit-zero --exclude=monkey/common/cloud/scoutsuite --config=./ci_scripts/flake8_linter_check.ini > ./ci_scripts/flake8_warnings.txt +- flake8 ./monkey --exit-zero --config=./ci_scripts/flake8_linter_check.ini > ./ci_scripts/flake8_warnings.txt ## Display the linter issues - cat ./ci_scripts/flake8_warnings.txt ## Make sure that we haven't increased the amount of warnings. @@ -70,13 +69,11 @@ script: - if [ $(tail -n 1 ./ci_scripts/flake8_warnings.txt) -gt $PYTHON_WARNINGS_AMOUNT_UPPER_LIMIT ]; then echo "Too many python linter warnings! Failing this build. Lower the amount of linter errors in this and try again. " && exit 1; fi ## Check import order -# TODO move scoutsuite stuff to config -- python -m isort ./monkey --settings-file ./ci_scripts/isort.cfg --skip ./monkey/common/cloud/scoutsuite --skip ./monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators_list.py +- python -m isort ./monkey --settings-file ./ci_scripts/isort.cfg ## Run unit tests and generate coverage data - cd monkey # This is our source dir -# TODO move scoutsuite stuff to config file -- python -m pytest --ignore=./common/cloud/scoutsuite --cov=. # Have to use `python -m pytest` instead of `pytest` to add "{$builddir}/monkey/monkey" to sys.path. +- python -m pytest --cov=. # Have to use `python -m pytest` instead of `pytest` to add "{$builddir}/monkey/monkey" to sys.path. # Check JS code. The npm install must happen AFTER the flake8 because the node_modules folder will cause a lot of errors. - cd monkey_island/cc/ui From a836ab7e1d7a6e1523ef8e45e76f2a8dae20964c Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Fri, 29 Jan 2021 12:35:40 +0200 Subject: [PATCH 132/152] Renamed some files and other minor improvements --- .../cc/models/zero_trust/scoutsuite_data_json.py | 6 +++--- monkey/monkey_island/cc/services/post_breach_files.py | 2 +- .../cc/services/telemetry/processing/scoutsuite.py | 6 +++--- .../zero_trust/scoutsuite/consts/findings_list.py | 8 -------- .../consts/{findings.py => scoutsuite_findings.py} | 0 .../scoutsuite/consts/scoutsuite_findings_list.py | 8 ++++++++ .../scoutsuite/scoutsuite_zt_finding_service.py | 2 +- .../zero_trust/test_common/scoutsuite_finding_data.py | 2 +- .../zero_trust_report/scoutsuite_raw_data_service.py | 4 ++-- 9 files changed, 19 insertions(+), 19 deletions(-) delete mode 100644 monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/findings_list.py rename monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/{findings.py => scoutsuite_findings.py} (100%) create mode 100644 monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/scoutsuite_findings_list.py diff --git a/monkey/monkey_island/cc/models/zero_trust/scoutsuite_data_json.py b/monkey/monkey_island/cc/models/zero_trust/scoutsuite_data_json.py index 9dbf7855a..166c247bf 100644 --- a/monkey/monkey_island/cc/models/zero_trust/scoutsuite_data_json.py +++ b/monkey/monkey_island/cc/models/zero_trust/scoutsuite_data_json.py @@ -1,7 +1,7 @@ from mongoengine import Document, DynamicField -class ScoutSuiteDataJson(Document): +class ScoutSuiteRawDataJson(Document): """ This model is a container for ScoutSuite report data dump. """ @@ -13,8 +13,8 @@ class ScoutSuiteDataJson(Document): @staticmethod def add_scoutsuite_data(scoutsuite_data: str) -> None: try: - current_data = ScoutSuiteDataJson.objects()[0] + current_data = ScoutSuiteRawDataJson.objects()[0] except IndexError: - current_data = ScoutSuiteDataJson() + current_data = ScoutSuiteRawDataJson() current_data.scoutsuite_data = scoutsuite_data current_data.save() diff --git a/monkey/monkey_island/cc/services/post_breach_files.py b/monkey/monkey_island/cc/services/post_breach_files.py index 2bb310e14..44f1b91b2 100644 --- a/monkey/monkey_island/cc/services/post_breach_files.py +++ b/monkey/monkey_island/cc/services/post_breach_files.py @@ -6,7 +6,7 @@ import monkey_island.cc.services.config __author__ = "VakarisZ" -from monkey_island.cc.consts import MONKEY_ISLAND_ABS_PATH +from monkey_island.cc.server_utils.consts import MONKEY_ISLAND_ABS_PATH logger = logging.getLogger(__name__) diff --git a/monkey/monkey_island/cc/services/telemetry/processing/scoutsuite.py b/monkey/monkey_island/cc/services/telemetry/processing/scoutsuite.py index 93a597f90..d3822da27 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/scoutsuite.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/scoutsuite.py @@ -1,8 +1,8 @@ import json from monkey_island.cc.database import mongo -from monkey_island.cc.models.zero_trust.scoutsuite_data_json import ScoutSuiteDataJson -from ...zero_trust.scoutsuite.consts.findings_list import SCOUTSUITE_FINDINGS +from monkey_island.cc.models.zero_trust.scoutsuite_data_json import ScoutSuiteRawDataJson +from ...zero_trust.scoutsuite.consts.scoutsuite_findings_list import SCOUTSUITE_FINDINGS from ...zero_trust.scoutsuite.data_parsing.rule_parser import RuleParser from ...zero_trust.scoutsuite.scoutsuite_zt_finding_service import ScoutSuiteZTFindingService from ...zero_trust.scoutsuite.scoutsuite_rule_service import ScoutSuiteRuleService @@ -11,7 +11,7 @@ from ...zero_trust.scoutsuite.scoutsuite_rule_service import ScoutSuiteRuleServi def process_scoutsuite_telemetry(telemetry_json): # Encode data to json, because mongo can't save it as document (invalid document keys) telemetry_json['data'] = json.dumps(telemetry_json['data']) - ScoutSuiteDataJson.add_scoutsuite_data(telemetry_json['data']) + ScoutSuiteRawDataJson.add_scoutsuite_data(telemetry_json['data']) scoutsuite_data = json.loads(telemetry_json['data'])['data'] create_scoutsuite_findings(scoutsuite_data) update_data(telemetry_json) diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/findings_list.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/findings_list.py deleted file mode 100644 index fdef7d62b..000000000 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/findings_list.py +++ /dev/null @@ -1,8 +0,0 @@ -from .findings import (DataLossPrevention, Logging, - PermissiveFirewallRules, - RestrictivePolicies, - SecureAuthentication, ServiceSecurity, - UnencryptedData) - -SCOUTSUITE_FINDINGS = [PermissiveFirewallRules, UnencryptedData, DataLossPrevention, SecureAuthentication, - RestrictivePolicies, Logging, ServiceSecurity] diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/findings.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/scoutsuite_findings.py similarity index 100% rename from monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/findings.py rename to monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/scoutsuite_findings.py diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/scoutsuite_findings_list.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/scoutsuite_findings_list.py new file mode 100644 index 000000000..b123e720e --- /dev/null +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/scoutsuite_findings_list.py @@ -0,0 +1,8 @@ +from .scoutsuite_findings import (DataLossPrevention, Logging, + PermissiveFirewallRules, + RestrictivePolicies, + SecureAuthentication, ServiceSecurity, + UnencryptedData) + +SCOUTSUITE_FINDINGS = [PermissiveFirewallRules, UnencryptedData, DataLossPrevention, SecureAuthentication, + RestrictivePolicies, Logging, ServiceSecurity] diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_zt_finding_service.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_zt_finding_service.py index 24740840f..27b1b82b3 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_zt_finding_service.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_zt_finding_service.py @@ -4,7 +4,7 @@ from common.common_consts import zero_trust_consts from monkey_island.cc.models.zero_trust.finding import Finding from monkey_island.cc.models.zero_trust.scoutsuite_finding_details import ScoutSuiteFindingDetails from monkey_island.cc.models.zero_trust.scoutsuite_rule import ScoutSuiteRule -from monkey_island.cc.services.zero_trust.scoutsuite.consts.findings import ScoutSuiteFinding +from monkey_island.cc.services.zero_trust.scoutsuite.consts.scoutsuite_findings import ScoutSuiteFinding from monkey_island.cc.services.zero_trust.scoutsuite.scoutsuite_rule_service import ScoutSuiteRuleService diff --git a/monkey/monkey_island/cc/services/zero_trust/test_common/scoutsuite_finding_data.py b/monkey/monkey_island/cc/services/zero_trust/test_common/scoutsuite_finding_data.py index cddc6b72c..b82a53260 100644 --- a/monkey/monkey_island/cc/services/zero_trust/test_common/scoutsuite_finding_data.py +++ b/monkey/monkey_island/cc/services/zero_trust/test_common/scoutsuite_finding_data.py @@ -1,6 +1,6 @@ from monkey_island.cc.models.zero_trust.scoutsuite_finding_details import ScoutSuiteFindingDetails from monkey_island.cc.models.zero_trust.scoutsuite_rule import ScoutSuiteRule -from monkey_island.cc.services.zero_trust.scoutsuite.consts.findings import PermissiveFirewallRules, UnencryptedData +from ..scoutsuite.consts.scoutsuite_findings import PermissiveFirewallRules, UnencryptedData SCOUTSUITE_FINDINGS = [ PermissiveFirewallRules, diff --git a/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/scoutsuite_raw_data_service.py b/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/scoutsuite_raw_data_service.py index 006f3250f..3a3c06452 100644 --- a/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/scoutsuite_raw_data_service.py +++ b/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/scoutsuite_raw_data_service.py @@ -1,4 +1,4 @@ -from monkey_island.cc.models.zero_trust.scoutsuite_data_json import ScoutSuiteDataJson +from monkey_island.cc.models.zero_trust.scoutsuite_data_json import ScoutSuiteRawDataJson class ScoutSuiteRawDataService: @@ -8,6 +8,6 @@ class ScoutSuiteRawDataService: @staticmethod def get_scoutsuite_data_json() -> str: try: - return ScoutSuiteDataJson.objects.get().scoutsuite_data + return ScoutSuiteRawDataJson.objects.get().scoutsuite_data except Exception: return "{}" From ba9e8c22b4c3dff60ae3f4cab44b017fae60fc54 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Fri, 29 Jan 2021 12:42:24 +0200 Subject: [PATCH 133/152] Improved mokey event fetching and added unit tests --- .../monkey_zt_details_service.py | 29 ++++++++++------- .../test_monkey_zt_details_service.py | 31 +++++++++++++++++++ 2 files changed, 48 insertions(+), 12 deletions(-) create mode 100644 monkey/monkey_island/cc/services/zero_trust/monkey_findings/test_monkey_zt_details_service.py diff --git a/monkey/monkey_island/cc/services/zero_trust/monkey_findings/monkey_zt_details_service.py b/monkey/monkey_island/cc/services/zero_trust/monkey_findings/monkey_zt_details_service.py index 893f8221f..63f809fee 100644 --- a/monkey/monkey_island/cc/services/zero_trust/monkey_findings/monkey_zt_details_service.py +++ b/monkey/monkey_island/cc/services/zero_trust/monkey_findings/monkey_zt_details_service.py @@ -4,9 +4,10 @@ from bson import ObjectId from monkey_island.cc.models.zero_trust.monkey_finding_details import MonkeyFindingDetails + # How many events of a single finding to return to UI. -# 50 will return 50 latest and 50 oldest events from a finding -EVENT_FETCH_CNT = 50 +# 100 will return 50 latest and 50 oldest events from a finding +MAX_EVENT_FETCH_CNT = 100 class MonkeyZTDetailsService: @@ -14,25 +15,29 @@ class MonkeyZTDetailsService: @staticmethod def fetch_details_for_display(finding_id: ObjectId) -> dict: pipeline = [{'$match': {'_id': finding_id}}, - {'$addFields': {'oldest_events': {'$slice': ['$events', EVENT_FETCH_CNT]}, - 'latest_events': {'$slice': ['$events', -1 * EVENT_FETCH_CNT]}, + {'$addFields': {'oldest_events': {'$slice': ['$events', int(MAX_EVENT_FETCH_CNT / 2)]}, + 'latest_events': {'$slice': ['$events', int(-1 * MAX_EVENT_FETCH_CNT / 2)]}, 'event_count': {'$size': '$events'}}}, - {'$unset': ['events', 'checked_subnet_pairs']}] + {'$unset': ['events']}] details = list(MonkeyFindingDetails.objects.aggregate(*pipeline)) if details: details = details[0] - details['latest_events'] = MonkeyZTDetailsService._get_events_without_overlap(details['event_count'], - details['latest_events']) + details['latest_events'] = MonkeyZTDetailsService._remove_redundant_events(details['event_count'], + details['latest_events']) return details else: return {} @staticmethod - def _get_events_without_overlap(event_count: int, events: List[object]) -> List[object]: - overlap_count = event_count - EVENT_FETCH_CNT - if overlap_count >= EVENT_FETCH_CNT: - return events + def _remove_redundant_events(fetched_event_count: int, latest_events: List[object]) -> List[object]: + overlap_count = fetched_event_count - int(MAX_EVENT_FETCH_CNT/2) + # None of 'latest_events' are in 'oldest_events' + if overlap_count >= MAX_EVENT_FETCH_CNT: + return latest_events + # All 'latest_events' are already in 'oldest_events' elif overlap_count <= 0: return [] + # Some of 'latest_events' are already in 'oldest_events'. + # Return only those that are not else: - return events[-1 * overlap_count:] + return latest_events[-1 * overlap_count:] diff --git a/monkey/monkey_island/cc/services/zero_trust/monkey_findings/test_monkey_zt_details_service.py b/monkey/monkey_island/cc/services/zero_trust/monkey_findings/test_monkey_zt_details_service.py new file mode 100644 index 000000000..a53ef70c8 --- /dev/null +++ b/monkey/monkey_island/cc/services/zero_trust/monkey_findings/test_monkey_zt_details_service.py @@ -0,0 +1,31 @@ +from monkey_island.cc.services.zero_trust.monkey_findings import monkey_zt_details_service +from monkey_island.cc.services.zero_trust.monkey_findings.monkey_zt_details_service import MonkeyZTDetailsService + + +def test__remove_redundant_events(monkeypatch): + monkeypatch.setattr(monkey_zt_details_service, 'MAX_EVENT_FETCH_CNT', 6) + + # No events are redundant, 8 events in the database, but we display only 6 (3 latest and 3 oldest) + latest_events = ['6', '7', '8'] + _do_redundant_event_removal_test(latest_events, 8, ['6', '7', '8']) + + # All latest events are redundant (only 3 events in db and we fetched them twice) + latest_events = ['1', '2', '3'] + _do_redundant_event_removal_test(latest_events, 3, []) + + # Some latest events are redundant (5 events in db and we fetched 3 oldest and 3 latest) + latest_events = ['3', '4', '5'] + _do_redundant_event_removal_test(latest_events, 5, ['4', '5']) + + # None of the events are redundant (6 events in db and we fetched 3 oldest and 3 latest) + latest_events = ['4', '5', '6'] + _do_redundant_event_removal_test(latest_events, 6, ['4', '5', '6']) + + # No events fetched, should return empty array also + latest_events = [] + _do_redundant_event_removal_test(latest_events, 0, []) + + +def _do_redundant_event_removal_test(input_list, fetched_event_cnt, expected_output): + output_events = MonkeyZTDetailsService._remove_redundant_events(fetched_event_cnt, input_list) + assert output_events == expected_output From c45ff1dc1fec91ec93a90fbd4617510f273e5df1 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Fri, 29 Jan 2021 13:01:22 +0200 Subject: [PATCH 134/152] Used dpath module instead of custom code to traverse object. --- monkey/common/utils/code_utils.py | 9 --------- monkey/common/utils/test_code_utils.py | 9 --------- .../zero_trust/scoutsuite/data_parsing/rule_parser.py | 5 +++-- 3 files changed, 3 insertions(+), 20 deletions(-) delete mode 100644 monkey/common/utils/test_code_utils.py diff --git a/monkey/common/utils/code_utils.py b/monkey/common/utils/code_utils.py index d7fad9062..214e6d108 100644 --- a/monkey/common/utils/code_utils.py +++ b/monkey/common/utils/code_utils.py @@ -1,10 +1,5 @@ # abstract, static method decorator # noinspection PyPep8Naming -import operator -from functools import reduce -from typing import List, Union, Any - - class abstractstatic(staticmethod): __slots__ = () @@ -13,7 +8,3 @@ class abstractstatic(staticmethod): function.__isabstractmethod__ = True __isabstractmethod__ = True - - -def get_dict_value_by_path(data: dict, path: List[str]) -> Any: - return reduce(operator.getitem, path, data) diff --git a/monkey/common/utils/test_code_utils.py b/monkey/common/utils/test_code_utils.py deleted file mode 100644 index b383b0a33..000000000 --- a/monkey/common/utils/test_code_utils.py +++ /dev/null @@ -1,9 +0,0 @@ -import unittest - -from common.utils.code_utils import get_dict_value_by_path - - -class TestCodeUtils(unittest.TestCase): - def test_get_dict_value_by_path(self): - dict_for_test = {'a': {'b': {'c': 'result'}}} - self.assertEqual(get_dict_value_by_path(dict_for_test, ['a', 'b', 'c']), 'result') diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_parser.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_parser.py index c5855ddd5..8da69a9bb 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_parser.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_parser.py @@ -1,4 +1,5 @@ -from common.utils.code_utils import get_dict_value_by_path +import dpath.util + from common.utils.exceptions import RulePathCreatorNotFound from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators_list import \ RULE_PATH_CREATORS_LIST @@ -9,7 +10,7 @@ class RuleParser: @staticmethod def get_rule_data(scoutsuite_data, rule_name): rule_path = RuleParser.get_rule_path(rule_name) - return get_dict_value_by_path(data=scoutsuite_data, path=rule_path) + return dpath.util.get(scoutsuite_data, rule_path) @staticmethod def get_rule_path(rule_name): From 284cc3afdbbfe3ca1a3f1e31dd45014114629148 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Fri, 29 Jan 2021 18:17:32 +0200 Subject: [PATCH 135/152] Removed scoutsuite telemetry processing as there's nothing to process and other minor code improvements --- .../cc/models/zero_trust/scoutsuite_rule.py | 6 +++--- .../processing/system_info_collectors/scoutsuite.py | 9 --------- .../system_info_telemetry_dispatcher.py | 4 +--- .../scoutsuite/test_scoutsuite_auth_service.py | 1 + 4 files changed, 5 insertions(+), 15 deletions(-) delete mode 100644 monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/scoutsuite.py diff --git a/monkey/monkey_island/cc/models/zero_trust/scoutsuite_rule.py b/monkey/monkey_island/cc/models/zero_trust/scoutsuite_rule.py index dee49983a..fcf09df9c 100644 --- a/monkey/monkey_island/cc/models/zero_trust/scoutsuite_rule.py +++ b/monkey/monkey_island/cc/models/zero_trust/scoutsuite_rule.py @@ -5,9 +5,9 @@ from monkey_island.cc.services.zero_trust.scoutsuite.consts import rule_consts class ScoutSuiteRule(EmbeddedDocument): """ - This model represents additional information about monkey finding: - Events if monkey finding - Scoutsuite findings if scoutsuite finding + This model represents ScoutSuite security rule check results: + how many resources break the security rule + security rule description and remediation and etc. """ # SCHEMA diff --git a/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/scoutsuite.py b/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/scoutsuite.py deleted file mode 100644 index 2c3ab4f52..000000000 --- a/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/scoutsuite.py +++ /dev/null @@ -1,9 +0,0 @@ -import json -import logging - -logger = logging.getLogger(__name__) - - -def process_scout_suite_telemetry(collector_results, monkey_guid): - # Monkey.get_single_monkey_by_guid(monkey_guid).set_hostname(collector_results["hostname"]) - logger.info(f"ScoutSuite results:\n{json.dumps(collector_results, indent=2)}") diff --git a/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/system_info_telemetry_dispatcher.py b/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/system_info_telemetry_dispatcher.py index 454657b27..c84704a0b 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/system_info_telemetry_dispatcher.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/system_info_telemetry_dispatcher.py @@ -6,7 +6,6 @@ from common.common_consts.system_info_collectors_names import (AWS_COLLECTOR, EN from .aws import process_aws_telemetry from .environment import process_environment_telemetry from .hostname import process_hostname_telemetry -from .scoutsuite import process_scout_suite_telemetry from monkey_island.cc.services.telemetry.zero_trust_checks.antivirus_existence import check_antivirus_existence logger = logging.getLogger(__name__) @@ -15,8 +14,7 @@ SYSTEM_INFO_COLLECTOR_TO_TELEMETRY_PROCESSORS = { AWS_COLLECTOR: [process_aws_telemetry], ENVIRONMENT_COLLECTOR: [process_environment_telemetry], HOSTNAME_COLLECTOR: [process_hostname_telemetry], - PROCESS_LIST_COLLECTOR: [check_antivirus_existence], - SCOUTSUITE_COLLECTOR: [process_scout_suite_telemetry] + PROCESS_LIST_COLLECTOR: [check_antivirus_existence] } diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_auth_service.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_auth_service.py index 322af5af6..24e700ce6 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_auth_service.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_auth_service.py @@ -14,6 +14,7 @@ from monkey_island.cc.test_common.fixtures import FixtureEnum class MockObject: pass + @pytest.mark.usefixtures(FixtureEnum.USES_DATABASE) def test_is_aws_keys_setup(): # Mock default configuration From bcfa8fff788f559cb5c56e0f70b5fd71c92dada3 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Fri, 5 Feb 2021 11:03:27 +0200 Subject: [PATCH 136/152] Extracted count badge into a separate component which is reused between scoutsuite rules button and monkey events button --- .../zerotrust/EventsButton.js | 10 +++------- .../scoutsuite/ScoutSuiteRuleButton.js | 12 ++---------- .../src/components/ui-components/CountBadge.js | 18 ++++++++++++++++++ 3 files changed, 23 insertions(+), 17 deletions(-) create mode 100644 monkey/monkey_island/cc/ui/src/components/ui-components/CountBadge.js diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsButton.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsButton.js index 95fe93c4d..9f36c2b38 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsButton.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsButton.js @@ -1,10 +1,11 @@ import React, {Component, Fragment} from 'react'; import EventsModal from './EventsModal'; -import {Badge, Button} from 'react-bootstrap'; +import {Button} from 'react-bootstrap'; import * as PropTypes from 'prop-types'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faList } from '@fortawesome/free-solid-svg-icons/faList'; +import CountBadge from '../../ui-components/CountBadge'; export default class EventsButton extends Component { constructor(props) { @@ -33,16 +34,11 @@ export default class EventsButton extends Component { exportFilename={this.props.exportFilename}/>
; } - - createEventsAmountBadge() { - const eventsAmountBadgeContent = this.props.event_count > 9 ? '9+' : this.props.event_count; - return {eventsAmountBadgeContent}; - } } EventsButton.propTypes = { diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/scoutsuite/ScoutSuiteRuleButton.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/scoutsuite/ScoutSuiteRuleButton.js index 4b46c7c3b..b3809ec27 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/scoutsuite/ScoutSuiteRuleButton.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/scoutsuite/ScoutSuiteRuleButton.js @@ -5,6 +5,7 @@ import * as PropTypes from 'prop-types'; import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'; import {faList} from '@fortawesome/free-solid-svg-icons/faList'; import ScoutSuiteRuleModal from './ScoutSuiteRuleModal'; +import CountBadge from '../../../ui-components/CountBadge'; export default class ScoutSuiteRuleButton extends Component { constructor(props) { @@ -28,7 +29,7 @@ export default class ScoutSuiteRuleButton extends Component {
); @@ -39,15 +40,6 @@ export default class ScoutSuiteRuleButton extends Component { } } -function RuleCountBadge(props) { - const MAX_RULE_COUNT_TO_SHOW = 9; - const TEXT_FOR_LARGE_RULE_COUNT = MAX_RULE_COUNT_TO_SHOW + '+'; - - const ruleCountText = props.count > MAX_RULE_COUNT_TO_SHOW ? - TEXT_FOR_LARGE_RULE_COUNT : props.count; - return {ruleCountText}; -} - ScoutSuiteRuleButton.propTypes = { scoutsuite_rules: PropTypes.array, scoutsuite_data: PropTypes.object diff --git a/monkey/monkey_island/cc/ui/src/components/ui-components/CountBadge.js b/monkey/monkey_island/cc/ui/src/components/ui-components/CountBadge.js new file mode 100644 index 000000000..43b2ec518 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/ui-components/CountBadge.js @@ -0,0 +1,18 @@ +import {Badge} from 'react-bootstrap'; +import React from 'react'; + + +function CountBadge(props) { + const TEXT_FOR_LARGE_RULE_COUNT = props.maxCount + '+'; + + const ruleCountText = props.count > props.maxCount ? + TEXT_FOR_LARGE_RULE_COUNT : props.count; + + return {ruleCountText}; +} + +CountBadge.defaultProps = { + maxCount: 9 +} + +export default CountBadge; From 944406725042daa6080ac3a3ae053a3d039b9d54 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Fri, 5 Feb 2021 11:05:22 +0200 Subject: [PATCH 137/152] Added comments, type hints and other minor changes in the scoutsuite code --- .../cc/resources/zero_trust/zero_trust_report.py | 1 + .../monkey_findings/monkey_zt_finding_service.py | 2 +- .../scoutsuite/consts/scoutsuite_findings.py | 2 +- .../zero_trust/test_common/monkey_finding_data.py | 2 +- .../zerotrust/scoutsuite/RuleDisplay.js | 4 ++++ .../zerotrust/scoutsuite/ScoutSuiteDataParser.js | 11 +++++++++++ 6 files changed, 19 insertions(+), 3 deletions(-) diff --git a/monkey/monkey_island/cc/resources/zero_trust/zero_trust_report.py b/monkey/monkey_island/cc/resources/zero_trust/zero_trust_report.py index a85499b2f..a69ea50c0 100644 --- a/monkey/monkey_island/cc/resources/zero_trust/zero_trust_report.py +++ b/monkey/monkey_island/cc/resources/zero_trust/zero_trust_report.py @@ -26,6 +26,7 @@ class ZeroTrustReport(flask_restful.Resource): elif report_data == REPORT_DATA_FINDINGS: return jsonify(FindingService.get_all_findings()) elif report_data == REPORT_DATA_SCOUTSUITE: + # Raw ScoutSuite data is already solved as json, no need to jsonify return Response(ScoutSuiteRawDataService.get_scoutsuite_data_json(), mimetype='application/json') diff --git a/monkey/monkey_island/cc/services/zero_trust/monkey_findings/monkey_zt_finding_service.py b/monkey/monkey_island/cc/services/zero_trust/monkey_findings/monkey_zt_finding_service.py index c3c45e69e..7ed184559 100644 --- a/monkey/monkey_island/cc/services/zero_trust/monkey_findings/monkey_zt_finding_service.py +++ b/monkey/monkey_island/cc/services/zero_trust/monkey_findings/monkey_zt_finding_service.py @@ -11,7 +11,7 @@ from monkey_island.cc.models.zero_trust.monkey_finding_details import MonkeyFind class MonkeyZTFindingService: @staticmethod - def create_or_add_to_existing(test, status, events): + def create_or_add_to_existing(test: str, status: str, events: str): """ Create a new finding or add the events to an existing one if it's the same (same meaning same status and same test). diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/scoutsuite_findings.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/scoutsuite_findings.py index 3368cbbdf..0881b4733 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/scoutsuite_findings.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/scoutsuite_findings.py @@ -22,7 +22,7 @@ from .rule_names.vpc_rules import VPCRules class ScoutSuiteFinding(ABC): @property @abstractmethod - def rules(self) -> List[str]: + def rules(self) -> List[EC2Rules]: pass @property diff --git a/monkey/monkey_island/cc/services/zero_trust/test_common/monkey_finding_data.py b/monkey/monkey_island/cc/services/zero_trust/test_common/monkey_finding_data.py index d727ea9e8..b0050a8c9 100644 --- a/monkey/monkey_island/cc/services/zero_trust/test_common/monkey_finding_data.py +++ b/monkey/monkey_island/cc/services/zero_trust/test_common/monkey_finding_data.py @@ -5,7 +5,7 @@ EVENTS = [ { "timestamp": "2021-01-20T15:40:28.357Z", "title": "Process list", - "message": "Monkey on gc-pc-244 scanned the process list", + "message": "Monkey on pc-24 scanned the process list", "event_type": "monkey_local" }, { diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/scoutsuite/RuleDisplay.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/scoutsuite/RuleDisplay.js index ac267e193..dc81ff183 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/scoutsuite/RuleDisplay.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/scoutsuite/RuleDisplay.js @@ -36,6 +36,8 @@ export default function RuleDisplay(props) {

References:

{references}
) + } else { + return null; } } @@ -56,6 +58,8 @@ export default function RuleDisplay(props) {

Flagged resources ({props.rule.flagged_items}):

{resources} ) + } else { + return null; } } } diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/scoutsuite/ScoutSuiteDataParser.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/scoutsuite/ScoutSuiteDataParser.js index 9657c0bba..be5599d99 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/scoutsuite/ScoutSuiteDataParser.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/scoutsuite/ScoutSuiteDataParser.js @@ -15,6 +15,12 @@ export default class ScoutSuiteDataParser { return this.getObjectValueByPath(resourcePath, this.runResults); } + /** + * Replaces id's in template path with id's from item path to form actual path to the object + * @param itemPath e.g. s3.buckets.da1e7081077ce92.secure_transport_enabled + * @param templatePath e.g. s3.buckets.id + * @returns {*} e.g. s3.buckets.da1e7081077ce92 + */ fillTemplatePath(itemPath, templatePath) { let itemPathArray = itemPath.split('.'); let templatePathArray = templatePath.split('.'); @@ -42,6 +48,11 @@ export default class ScoutSuiteDataParser { return source; } + /** + * Gets next key from the path + * @param path e.g. s3.buckets.id + * @returns {string|*} s3 + */ getNextKeyInPath(path) { if (path.indexOf('.') !== -1) { return path.substr(0, path.indexOf('.')); From 80e743557256a2a7e49347a5d24e918acacb28fb Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Mon, 8 Feb 2021 17:38:45 +0200 Subject: [PATCH 138/152] Refactored Finding DTO into ScoutSuiteFinding and MonkeyFinding DTO which inherit from more abstract Finding. --- .../common/common_consts/zero_trust_consts.py | 4 -- .../cc/models/zero_trust/finding.py | 40 +++++------------- .../cc/models/zero_trust/monkey_finding.py | 18 ++++++++ .../models/zero_trust/scoutsuite_finding.py | 18 ++++++++ ...test_finding.py => test_monkey_finding.py} | 25 +++++------ .../zero_trust/test_scoutsuite_finding.py | 41 +++++++++++++++++++ .../resources/zero_trust/zero_trust_report.py | 2 +- .../monkey_zt_finding_service.py | 12 +++--- .../test_monkey_zt_finding_service.py | 3 +- .../scoutsuite_zt_finding_service.py | 16 ++++---- .../test_scoutsuite_zt_finding_service.py | 9 ++-- .../zero_trust/test_common/finding_data.py | 20 ++++----- .../zero_trust_report/finding_service.py | 18 ++++---- .../zero_trust_report/pillar_service.py | 6 +-- .../zero_trust_report/test_finding_service.py | 9 ++-- .../zerotrust/FindingsTable.js | 4 +- 16 files changed, 150 insertions(+), 95 deletions(-) create mode 100644 monkey/monkey_island/cc/models/zero_trust/monkey_finding.py create mode 100644 monkey/monkey_island/cc/models/zero_trust/scoutsuite_finding.py rename monkey/monkey_island/cc/models/zero_trust/{test_finding.py => test_monkey_finding.py} (56%) create mode 100644 monkey/monkey_island/cc/models/zero_trust/test_scoutsuite_finding.py diff --git a/monkey/common/common_consts/zero_trust_consts.py b/monkey/common/common_consts/zero_trust_consts.py index 3d3602b80..f0a624bdf 100644 --- a/monkey/common/common_consts/zero_trust_consts.py +++ b/monkey/common/common_consts/zero_trust_consts.py @@ -22,10 +22,6 @@ STATUS_FAILED = "Failed" # Don't change order! The statuses are ordered by importance/severity. ORDERED_TEST_STATUSES = [STATUS_FAILED, STATUS_VERIFY, STATUS_PASSED, STATUS_UNEXECUTED] -MONKEY_FINDING = "monkey_finding" -SCOUTSUITE_FINDING = "scoutsuite_finding" -FINDING_TYPES = [MONKEY_FINDING, SCOUTSUITE_FINDING] - TEST_DATA_ENDPOINT_ELASTIC = "unencrypted_data_endpoint_elastic" TEST_DATA_ENDPOINT_HTTP = "unencrypted_data_endpoint_http" TEST_MACHINE_EXPLOITED = "machine_exploited" diff --git a/monkey/monkey_island/cc/models/zero_trust/finding.py b/monkey/monkey_island/cc/models/zero_trust/finding.py index b9aefb42a..4d108ddde 100644 --- a/monkey/monkey_island/cc/models/zero_trust/finding.py +++ b/monkey/monkey_island/cc/models/zero_trust/finding.py @@ -4,7 +4,8 @@ Define a Document Schema for Zero Trust findings. """ from __future__ import annotations -from typing import Union + +import abc from mongoengine import Document, GenericLazyReferenceField, StringField @@ -12,7 +13,6 @@ import common.common_consts.zero_trust_consts as zero_trust_consts # Dummy import for mongoengine. # noinspection PyUnresolvedReferences from monkey_island.cc.models.zero_trust.monkey_finding_details import MonkeyFindingDetails -from monkey_island.cc.models.zero_trust.scoutsuite_finding_details import ScoutSuiteFindingDetails class Finding(Document): @@ -33,39 +33,21 @@ class Finding(Document): * The logic section defines complex questions we can ask about a single document which are asked multiple times, or complex action we will perform - somewhat like an API. """ - # SCHEMA - test = StringField(required=True, choices=zero_trust_consts.TESTS) - status = StringField(required=True, choices=zero_trust_consts.ORDERED_TEST_STATUSES) - finding_type = StringField(required=True, choices=zero_trust_consts.FINDING_TYPES) - - # Details are in a separate document in order to discourage pulling them when not needed - # due to performance. - details = GenericLazyReferenceField(choices=[MonkeyFindingDetails, ScoutSuiteFindingDetails], required=True) # http://docs.mongoengine.org/guide/defining-documents.html#document-inheritance meta = {'allow_inheritance': True} - # LOGIC - def get_test_explanation(self): - return zero_trust_consts.TESTS_MAP[self.test][zero_trust_consts.TEST_EXPLANATION_KEY] + # SCHEMA + test = StringField(required=True, choices=zero_trust_consts.TESTS) + status = StringField(required=True, choices=zero_trust_consts.ORDERED_TEST_STATUSES) - def get_pillars(self): - return zero_trust_consts.TESTS_MAP[self.test][zero_trust_consts.PILLARS_KEY] + # Details are in a separate document in order to discourage pulling them when not needed + # due to performance. + details = GenericLazyReferenceField(required=True) # Creation methods @staticmethod + @abc.abstractmethod def save_finding(test: str, status: str, - detail_ref: Union[MonkeyFindingDetails, ScoutSuiteFindingDetails]) -> Finding: - finding = Finding(test=test, - status=status, - details=detail_ref, - finding_type=Finding._get_finding_type_by_details(detail_ref)) - finding.save() - return finding - - @staticmethod - def _get_finding_type_by_details(details: Union[MonkeyFindingDetails, ScoutSuiteFindingDetails]) -> str: - if type(details) == MonkeyFindingDetails: - return zero_trust_consts.MONKEY_FINDING - else: - return zero_trust_consts.SCOUTSUITE_FINDING + detail_ref) -> Finding: + pass diff --git a/monkey/monkey_island/cc/models/zero_trust/monkey_finding.py b/monkey/monkey_island/cc/models/zero_trust/monkey_finding.py new file mode 100644 index 000000000..e063ea5c1 --- /dev/null +++ b/monkey/monkey_island/cc/models/zero_trust/monkey_finding.py @@ -0,0 +1,18 @@ +from mongoengine import LazyReferenceField + +from monkey_island.cc.models.zero_trust.finding import Finding +from monkey_island.cc.models.zero_trust.monkey_finding_details import MonkeyFindingDetails + + +class MonkeyFinding(Finding): + details = LazyReferenceField(MonkeyFindingDetails, required=True) + + @staticmethod + def save_finding(test: str, + status: str, + detail_ref: MonkeyFindingDetails) -> Finding: + monkey_finding = MonkeyFinding(test=test, + status=status, + details=detail_ref) + monkey_finding.save() + return monkey_finding diff --git a/monkey/monkey_island/cc/models/zero_trust/scoutsuite_finding.py b/monkey/monkey_island/cc/models/zero_trust/scoutsuite_finding.py new file mode 100644 index 000000000..8adeef2e9 --- /dev/null +++ b/monkey/monkey_island/cc/models/zero_trust/scoutsuite_finding.py @@ -0,0 +1,18 @@ +from mongoengine import LazyReferenceField + +from monkey_island.cc.models.zero_trust.finding import Finding +from monkey_island.cc.models.zero_trust.scoutsuite_finding_details import ScoutSuiteFindingDetails + + +class ScoutSuiteFinding(Finding): + details = LazyReferenceField(ScoutSuiteFindingDetails, required=True) + + @staticmethod + def save_finding(test: str, + status: str, + detail_ref: ScoutSuiteFindingDetails) -> Finding: + scoutsuite_finding = ScoutSuiteFinding(test=test, + status=status, + details=detail_ref) + scoutsuite_finding.save() + return scoutsuite_finding diff --git a/monkey/monkey_island/cc/models/zero_trust/test_finding.py b/monkey/monkey_island/cc/models/zero_trust/test_monkey_finding.py similarity index 56% rename from monkey/monkey_island/cc/models/zero_trust/test_finding.py rename to monkey/monkey_island/cc/models/zero_trust/test_monkey_finding.py index 4df4b7bab..56a4066e1 100644 --- a/monkey/monkey_island/cc/models/zero_trust/test_finding.py +++ b/monkey/monkey_island/cc/models/zero_trust/test_monkey_finding.py @@ -4,29 +4,22 @@ from mongoengine import ValidationError import common.common_consts.zero_trust_consts as zero_trust_consts from monkey_island.cc.models.zero_trust.event import Event from monkey_island.cc.models.zero_trust.finding import Finding +from monkey_island.cc.models.zero_trust.monkey_finding import MonkeyFinding from monkey_island.cc.models.zero_trust.monkey_finding_details import MonkeyFindingDetails -from monkey_island.cc.models.zero_trust.scoutsuite_finding_details import ScoutSuiteFindingDetails from monkey_island.cc.test_common.fixtures import FixtureEnum MONKEY_FINDING_DETAIL_MOCK = MonkeyFindingDetails() MONKEY_FINDING_DETAIL_MOCK.events = ['mock1', 'mock2'] -SCOUTSUITE_FINDING_DETAIL_MOCK = ScoutSuiteFindingDetails() -SCOUTSUITE_FINDING_DETAIL_MOCK.scoutsuite_rules = [] -class TestFinding: +class TestMonkeyFinding: @pytest.mark.usefixtures(FixtureEnum.USES_DATABASE) def test_save_finding_validation(self): with pytest.raises(ValidationError): - _ = Finding.save_finding(test="bla bla", - status=zero_trust_consts.STATUS_FAILED, - detail_ref=MONKEY_FINDING_DETAIL_MOCK) - - with pytest.raises(ValidationError): - _ = Finding.save_finding(test=zero_trust_consts.TEST_SEGMENTATION, - status="bla bla", - detail_ref=SCOUTSUITE_FINDING_DETAIL_MOCK) + _ = MonkeyFinding.save_finding(test="bla bla", + status=zero_trust_consts.STATUS_FAILED, + detail_ref=MONKEY_FINDING_DETAIL_MOCK) @pytest.mark.usefixtures(FixtureEnum.USES_DATABASE) def test_save_finding_sanity(self): @@ -37,8 +30,10 @@ class TestFinding: monkey_details_example = MonkeyFindingDetails() monkey_details_example.events.append(event_example) monkey_details_example.save() - Finding.save_finding(test=zero_trust_consts.TEST_SEGMENTATION, - status=zero_trust_consts.STATUS_FAILED, detail_ref=monkey_details_example) + MonkeyFinding.save_finding(test=zero_trust_consts.TEST_SEGMENTATION, + status=zero_trust_consts.STATUS_FAILED, + detail_ref=monkey_details_example) - assert len(Finding.objects(test=zero_trust_consts.TEST_SEGMENTATION)) == 1 + assert len(MonkeyFinding.objects(test=zero_trust_consts.TEST_SEGMENTATION)) == 1 + assert len(MonkeyFinding.objects(status=zero_trust_consts.STATUS_FAILED)) == 1 assert len(Finding.objects(status=zero_trust_consts.STATUS_FAILED)) == 1 diff --git a/monkey/monkey_island/cc/models/zero_trust/test_scoutsuite_finding.py b/monkey/monkey_island/cc/models/zero_trust/test_scoutsuite_finding.py new file mode 100644 index 000000000..723b428ff --- /dev/null +++ b/monkey/monkey_island/cc/models/zero_trust/test_scoutsuite_finding.py @@ -0,0 +1,41 @@ +import pytest +from mongoengine import ValidationError + +import common.common_consts.zero_trust_consts as zero_trust_consts +from monkey_island.cc.models.zero_trust.finding import Finding +from monkey_island.cc.models.zero_trust.monkey_finding_details import MonkeyFindingDetails +from monkey_island.cc.models.zero_trust.scoutsuite_finding import ScoutSuiteFinding +from monkey_island.cc.models.zero_trust.scoutsuite_finding_details import ScoutSuiteFindingDetails +from monkey_island.cc.services.zero_trust.test_common.scoutsuite_finding_data import RULES +from monkey_island.cc.test_common.fixtures import FixtureEnum + +MONKEY_FINDING_DETAIL_MOCK = MonkeyFindingDetails() +MONKEY_FINDING_DETAIL_MOCK.events = ['mock1', 'mock2'] +SCOUTSUITE_FINDING_DETAIL_MOCK = ScoutSuiteFindingDetails() +SCOUTSUITE_FINDING_DETAIL_MOCK.scoutsuite_rules = [] + + +class TestScoutSuiteFinding: + + @pytest.mark.usefixtures(FixtureEnum.USES_DATABASE) + def test_save_finding_validation(self): + with pytest.raises(ValidationError): + _ = ScoutSuiteFinding.save_finding(test=zero_trust_consts.TEST_SEGMENTATION, + status="bla bla", + detail_ref=SCOUTSUITE_FINDING_DETAIL_MOCK) + + @pytest.mark.usefixtures(FixtureEnum.USES_DATABASE) + def test_save_finding_sanity(self): + assert len(Finding.objects(test=zero_trust_consts.TEST_SEGMENTATION)) == 0 + + rule_example = RULES[0] + scoutsuite_details_example = ScoutSuiteFindingDetails() + scoutsuite_details_example.scoutsuite_rules.append(rule_example) + scoutsuite_details_example.save() + ScoutSuiteFinding.save_finding(test=zero_trust_consts.TEST_SEGMENTATION, + status=zero_trust_consts.STATUS_FAILED, + detail_ref=scoutsuite_details_example) + + assert len(ScoutSuiteFinding.objects(test=zero_trust_consts.TEST_SEGMENTATION)) == 1 + assert len(ScoutSuiteFinding.objects(status=zero_trust_consts.STATUS_FAILED)) == 1 + assert len(Finding.objects(status=zero_trust_consts.STATUS_FAILED)) == 1 diff --git a/monkey/monkey_island/cc/resources/zero_trust/zero_trust_report.py b/monkey/monkey_island/cc/resources/zero_trust/zero_trust_report.py index a69ea50c0..433bf4631 100644 --- a/monkey/monkey_island/cc/resources/zero_trust/zero_trust_report.py +++ b/monkey/monkey_island/cc/resources/zero_trust/zero_trust_report.py @@ -24,7 +24,7 @@ class ZeroTrustReport(flask_restful.Resource): elif report_data == REPORT_DATA_PRINCIPLES_STATUS: return jsonify(PrincipleService.get_principles_status()) elif report_data == REPORT_DATA_FINDINGS: - return jsonify(FindingService.get_all_findings()) + return jsonify(FindingService.get_all_findings_for_ui()) elif report_data == REPORT_DATA_SCOUTSUITE: # Raw ScoutSuite data is already solved as json, no need to jsonify return Response(ScoutSuiteRawDataService.get_scoutsuite_data_json(), diff --git a/monkey/monkey_island/cc/services/zero_trust/monkey_findings/monkey_zt_finding_service.py b/monkey/monkey_island/cc/services/zero_trust/monkey_findings/monkey_zt_finding_service.py index 7ed184559..72e9f7861 100644 --- a/monkey/monkey_island/cc/services/zero_trust/monkey_findings/monkey_zt_finding_service.py +++ b/monkey/monkey_island/cc/services/zero_trust/monkey_findings/monkey_zt_finding_service.py @@ -4,14 +4,14 @@ from bson import ObjectId from common.common_consts import zero_trust_consts from monkey_island.cc.models.zero_trust.event import Event -from monkey_island.cc.models.zero_trust.finding import Finding +from monkey_island.cc.models.zero_trust.monkey_finding import MonkeyFinding from monkey_island.cc.models.zero_trust.monkey_finding_details import MonkeyFindingDetails class MonkeyZTFindingService: @staticmethod - def create_or_add_to_existing(test: str, status: str, events: str): + def create_or_add_to_existing(test: str, status: str, events: List[Event]): """ Create a new finding or add the events to an existing one if it's the same (same meaning same status and same test). @@ -19,7 +19,7 @@ class MonkeyZTFindingService: :raises: Assertion error if this is used when there's more then one finding which fits the query - this is not when this function should be used. """ - existing_findings = Finding.objects(test=test, status=status) + existing_findings = MonkeyFinding.objects(test=test, status=status) assert (len(existing_findings) < 2), "More than one finding exists for {}:{}".format(test, status) if len(existing_findings) == 0: @@ -33,15 +33,15 @@ class MonkeyZTFindingService: details = MonkeyFindingDetails() details.events = events details.save() - Finding.save_finding(test, status, details) + MonkeyFinding.save_finding(test, status, details) @staticmethod - def add_events(finding: Finding, events: List[Event]): + def add_events(finding: MonkeyFinding, events: List[Event]): finding.details.fetch().add_events(events).save() @staticmethod def get_events_by_finding(finding_id: str) -> List[object]: - finding = Finding.objects.get(id=finding_id) + finding = MonkeyFinding.objects.get(id=finding_id) pipeline = [{'$match': {'_id': ObjectId(finding.details.id)}}, {'$unwind': '$events'}, {'$project': {'events': '$events'}}, diff --git a/monkey/monkey_island/cc/services/zero_trust/monkey_findings/test_monkey_zt_finding_service.py b/monkey/monkey_island/cc/services/zero_trust/monkey_findings/test_monkey_zt_finding_service.py index c3db9ea39..80df71786 100644 --- a/monkey/monkey_island/cc/services/zero_trust/monkey_findings/test_monkey_zt_finding_service.py +++ b/monkey/monkey_island/cc/services/zero_trust/monkey_findings/test_monkey_zt_finding_service.py @@ -5,6 +5,7 @@ import pytest from common.common_consts import zero_trust_consts from monkey_island.cc.models.zero_trust.event import Event from monkey_island.cc.models.zero_trust.finding import Finding +from monkey_island.cc.models.zero_trust.monkey_finding import MonkeyFinding from monkey_island.cc.services.zero_trust.monkey_findings.monkey_zt_finding_service import MonkeyZTFindingService from monkey_island.cc.test_common.fixtures import FixtureEnum @@ -67,4 +68,4 @@ class TestMonkeyZTFindingService: # Create new finding MonkeyZTFindingService.create_or_add_to_existing(test=TESTS[1], status=STATUS[1], events=[EVENTS[1]]) # Assert there was a new finding created, because test and status is different - assert len(Finding.objects()) == 2 + assert len(MonkeyFinding.objects()) == 2 diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_zt_finding_service.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_zt_finding_service.py index 27b1b82b3..63befc808 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_zt_finding_service.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_zt_finding_service.py @@ -1,18 +1,18 @@ from typing import List from common.common_consts import zero_trust_consts -from monkey_island.cc.models.zero_trust.finding import Finding +from monkey_island.cc.models.zero_trust.scoutsuite_finding import ScoutSuiteFinding from monkey_island.cc.models.zero_trust.scoutsuite_finding_details import ScoutSuiteFindingDetails from monkey_island.cc.models.zero_trust.scoutsuite_rule import ScoutSuiteRule -from monkey_island.cc.services.zero_trust.scoutsuite.consts.scoutsuite_findings import ScoutSuiteFinding +from monkey_island.cc.services.zero_trust.scoutsuite.consts.scoutsuite_finding_maps import ScoutSuiteFindingMap from monkey_island.cc.services.zero_trust.scoutsuite.scoutsuite_rule_service import ScoutSuiteRuleService class ScoutSuiteZTFindingService: @staticmethod - def process_rule(finding: ScoutSuiteFinding, rule: ScoutSuiteRule): - existing_findings = Finding.objects(test=finding.test, finding_type=zero_trust_consts.SCOUTSUITE_FINDING) + def process_rule(finding: ScoutSuiteFindingMap, rule: ScoutSuiteRule): + existing_findings = ScoutSuiteFinding.objects(test=finding.test) assert (len(existing_findings) < 2), "More than one finding exists for {}".format(finding.test) if len(existing_findings) == 0: @@ -21,12 +21,12 @@ class ScoutSuiteZTFindingService: ScoutSuiteZTFindingService.add_rule(existing_findings[0], rule) @staticmethod - def _create_new_finding_from_rule(finding: ScoutSuiteFinding, rule: ScoutSuiteRule): + def _create_new_finding_from_rule(finding: ScoutSuiteFindingMap, rule: ScoutSuiteRule): details = ScoutSuiteFindingDetails() details.scoutsuite_rules = [rule] details.save() status = ScoutSuiteZTFindingService.get_finding_status_from_rules(details.scoutsuite_rules) - Finding.save_finding(finding.test, status, details) + ScoutSuiteFinding.save_finding(finding.test, status, details) @staticmethod def get_finding_status_from_rules(rules: List[ScoutSuiteRule]) -> str: @@ -40,13 +40,13 @@ class ScoutSuiteZTFindingService: return zero_trust_consts.STATUS_PASSED @staticmethod - def add_rule(finding: Finding, rule: ScoutSuiteRule): + def add_rule(finding: ScoutSuiteFinding, rule: ScoutSuiteRule): ScoutSuiteZTFindingService.change_finding_status_by_rule(finding, rule) finding.save() finding.details.fetch().add_rule(rule) @staticmethod - def change_finding_status_by_rule(finding: Finding, rule: ScoutSuiteRule): + def change_finding_status_by_rule(finding: ScoutSuiteFinding, rule: ScoutSuiteRule): rule_status = ScoutSuiteZTFindingService.get_finding_status_from_rules([rule]) finding_status = finding.status new_finding_status = ScoutSuiteZTFindingService.get_finding_status_from_rule_status(finding_status, rule_status) diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_zt_finding_service.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_zt_finding_service.py index 0350bd2f3..00e9d1e32 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_zt_finding_service.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_zt_finding_service.py @@ -2,6 +2,7 @@ import pytest from common.common_consts import zero_trust_consts from monkey_island.cc.models.zero_trust.finding import Finding +from monkey_island.cc.models.zero_trust.scoutsuite_finding import ScoutSuiteFinding from monkey_island.cc.services.zero_trust.scoutsuite.scoutsuite_zt_finding_service import ScoutSuiteZTFindingService from monkey_island.cc.services.zero_trust.test_common.scoutsuite_finding_data import RULES, SCOUTSUITE_FINDINGS from monkey_island.cc.test_common.fixtures import FixtureEnum @@ -15,7 +16,7 @@ class TestScoutSuiteZTFindingService: ScoutSuiteZTFindingService.process_rule(SCOUTSUITE_FINDINGS[0], RULES[0]) findings = list(Finding.objects()) assert len(findings) == 1 - assert findings[0].finding_type == zero_trust_consts.SCOUTSUITE_FINDING + assert type(findings[0]) == ScoutSuiteFinding # Assert that details were created properly details = findings[0].details.fetch() assert len(details.scoutsuite_rules) == 1 @@ -23,9 +24,9 @@ class TestScoutSuiteZTFindingService: # Rule processing should add rule to an already existing finding ScoutSuiteZTFindingService.process_rule(SCOUTSUITE_FINDINGS[0], RULES[1]) - findings = list(Finding.objects()) + findings = list(ScoutSuiteFinding.objects()) assert len(findings) == 1 - assert findings[0].finding_type == zero_trust_consts.SCOUTSUITE_FINDING + assert type(findings[0]) == ScoutSuiteFinding # Assert that details were created properly details = findings[0].details.fetch() assert len(details.scoutsuite_rules) == 2 @@ -35,7 +36,7 @@ class TestScoutSuiteZTFindingService: ScoutSuiteZTFindingService.process_rule(SCOUTSUITE_FINDINGS[1], RULES[1]) findings = list(Finding.objects()) assert len(findings) == 2 - assert findings[1].finding_type == zero_trust_consts.SCOUTSUITE_FINDING + assert type(findings[0]) == ScoutSuiteFinding # Assert that details were created properly details = findings[1].details.fetch() assert len(details.scoutsuite_rules) == 1 diff --git a/monkey/monkey_island/cc/services/zero_trust/test_common/finding_data.py b/monkey/monkey_island/cc/services/zero_trust/test_common/finding_data.py index 0b8de9221..aaea95031 100644 --- a/monkey/monkey_island/cc/services/zero_trust/test_common/finding_data.py +++ b/monkey/monkey_island/cc/services/zero_trust/test_common/finding_data.py @@ -1,6 +1,8 @@ -from common.common_consts.zero_trust_consts import TEST_SCOUTSUITE_SERVICE_SECURITY, STATUS_FAILED, SCOUTSUITE_FINDING, \ - TEST_ENDPOINT_SECURITY_EXISTS, STATUS_PASSED, MONKEY_FINDING +from common.common_consts.zero_trust_consts import TEST_SCOUTSUITE_SERVICE_SECURITY, STATUS_FAILED, \ + TEST_ENDPOINT_SECURITY_EXISTS, STATUS_PASSED from monkey_island.cc.models.zero_trust.finding import Finding +from monkey_island.cc.models.zero_trust.monkey_finding import MonkeyFinding +from monkey_island.cc.models.zero_trust.scoutsuite_finding import ScoutSuiteFinding from monkey_island.cc.services.zero_trust.test_common.monkey_finding_data import get_monkey_details_dto from monkey_island.cc.services.zero_trust.test_common.scoutsuite_finding_data import get_scoutsuite_details_dto @@ -8,16 +10,14 @@ from monkey_island.cc.services.zero_trust.test_common.scoutsuite_finding_data im def get_scoutsuite_finding_dto() -> Finding: scoutsuite_details = get_scoutsuite_details_dto() scoutsuite_details.save() - return Finding(test=TEST_SCOUTSUITE_SERVICE_SECURITY, - status=STATUS_FAILED, - finding_type=SCOUTSUITE_FINDING, - details=scoutsuite_details) + return ScoutSuiteFinding(test=TEST_SCOUTSUITE_SERVICE_SECURITY, + status=STATUS_FAILED, + details=scoutsuite_details) def get_monkey_finding_dto() -> Finding: monkey_details = get_monkey_details_dto() monkey_details.save() - return Finding(test=TEST_ENDPOINT_SECURITY_EXISTS, - status=STATUS_PASSED, - finding_type=MONKEY_FINDING, - details=monkey_details) + return MonkeyFinding(test=TEST_ENDPOINT_SECURITY_EXISTS, + status=STATUS_PASSED, + details=monkey_details) diff --git a/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/finding_service.py b/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/finding_service.py index 3f972bb4c..5b69d6ad9 100644 --- a/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/finding_service.py +++ b/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/finding_service.py @@ -6,6 +6,8 @@ from bson import SON from common.common_consts import zero_trust_consts from common.utils.exceptions import UnknownFindingError from monkey_island.cc.models.zero_trust.finding import Finding +from monkey_island.cc.models.zero_trust.monkey_finding import MonkeyFinding +from monkey_island.cc.models.zero_trust.scoutsuite_finding import ScoutSuiteFinding from monkey_island.cc.services.zero_trust.monkey_findings.monkey_zt_details_service import MonkeyZTDetailsService @@ -16,15 +18,18 @@ class EnrichedFinding: test_key: str pillars: List[str] status: str - finding_type: str details: Union[dict, None] class FindingService: @staticmethod - def get_all_findings() -> List[EnrichedFinding]: - findings = list(Finding.objects) + def get_all_findings_from_db() -> List[Finding]: + return list(Finding.objects) + + @staticmethod + def get_all_findings_for_ui() -> List[EnrichedFinding]: + findings = FindingService.get_all_findings_from_db() for i in range(len(findings)): details = FindingService._get_finding_details(findings[i]) findings[i] = findings[i].to_mongo() @@ -41,16 +46,15 @@ class FindingService: test_key=finding['test'], pillars=test_info[zero_trust_consts.PILLARS_KEY], status=finding['status'], - finding_type=finding['finding_type'], details=None ) return enriched_finding @staticmethod def _get_finding_details(finding: Finding) -> Union[dict, SON]: - if finding.finding_type == zero_trust_consts.MONKEY_FINDING: + if type(finding) == MonkeyFinding: return MonkeyZTDetailsService.fetch_details_for_display(finding.details.id) - elif finding.finding_type == zero_trust_consts.SCOUTSUITE_FINDING: + elif type(finding) == ScoutSuiteFinding: return finding.details.fetch().to_mongo() else: - raise UnknownFindingError(f"Unknown finding type {finding.finding_type}") + raise UnknownFindingError(f"Unknown finding type {str(type(finding))}") diff --git a/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/pillar_service.py b/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/pillar_service.py index 9ec584571..4f9c067f6 100644 --- a/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/pillar_service.py +++ b/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/pillar_service.py @@ -1,5 +1,5 @@ import common.common_consts.zero_trust_consts as zero_trust_consts -from monkey_island.cc.models.zero_trust.finding import Finding +from monkey_island.cc.services.zero_trust.zero_trust_report.finding_service import FindingService class PillarService: @@ -13,7 +13,7 @@ class PillarService: @staticmethod def _get_pillars_grades(): pillars_grades = [] - all_findings = Finding.objects() + all_findings = FindingService.get_all_findings_from_db() for pillar in zero_trust_consts.PILLARS: pillars_grades.append(PillarService.__get_pillar_grade(pillar, all_findings)) return pillars_grades @@ -67,7 +67,7 @@ class PillarService: @staticmethod def __get_status_of_single_pillar(pillar): - all_findings = Finding.objects() + all_findings = FindingService.get_all_findings_from_db() grade = PillarService.__get_pillar_grade(pillar, all_findings) for status in zero_trust_consts.ORDERED_TEST_STATUSES: if grade[status] > 0: diff --git a/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/test_finding_service.py b/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/test_finding_service.py index 36ef060fd..fdbe6df39 100644 --- a/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/test_finding_service.py +++ b/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/test_finding_service.py @@ -3,9 +3,10 @@ from unittest.mock import MagicMock import pytest from common.common_consts.zero_trust_consts import TESTS_MAP, TEST_SCOUTSUITE_SERVICE_SECURITY, STATUS_FAILED, \ - SCOUTSUITE_FINDING, DEVICES, NETWORKS, MONKEY_FINDING, STATUS_PASSED, TEST_ENDPOINT_SECURITY_EXISTS + DEVICES, NETWORKS, STATUS_PASSED, TEST_ENDPOINT_SECURITY_EXISTS from monkey_island.cc.services.zero_trust.monkey_findings.monkey_zt_details_service import MonkeyZTDetailsService -from monkey_island.cc.services.zero_trust.test_common.finding_data import get_scoutsuite_finding_dto, get_monkey_finding_dto +from monkey_island.cc.services.zero_trust.test_common.finding_data import get_scoutsuite_finding_dto, \ + get_monkey_finding_dto from monkey_island.cc.services.zero_trust.zero_trust_report.finding_service import FindingService, EnrichedFinding from monkey_island.cc.test_common.fixtures.fixture_enum import FixtureEnum @@ -18,11 +19,10 @@ def test_get_all_findings(): # This method fails due to mongomock not being able to simulate $unset, so don't test details MonkeyZTDetailsService.fetch_details_for_display = MagicMock(return_value=None) - findings = FindingService.get_all_findings() + findings = FindingService.get_all_findings_for_ui() description = TESTS_MAP[TEST_SCOUTSUITE_SERVICE_SECURITY]['finding_explanation'][STATUS_FAILED] expected_finding0 = EnrichedFinding(finding_id=findings[0].finding_id, - finding_type=SCOUTSUITE_FINDING, pillars=[DEVICES, NETWORKS], status=STATUS_FAILED, test=description, @@ -31,7 +31,6 @@ def test_get_all_findings(): description = TESTS_MAP[TEST_ENDPOINT_SECURITY_EXISTS]['finding_explanation'][STATUS_PASSED] expected_finding1 = EnrichedFinding(finding_id=findings[1].finding_id, - finding_type=MONKEY_FINDING, pillars=[DEVICES], status=STATUS_PASSED, test=description, diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/FindingsTable.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/FindingsTable.js index efd5a6ac4..657ad741e 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/FindingsTable.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/FindingsTable.js @@ -36,10 +36,10 @@ export class FindingsTable extends Component { ]; getFindingDetails(finding) { - if (finding.finding_type === 'scoutsuite_finding') { + if ('scoutsuite_rules' in finding.details) { return ; - } else if (finding.finding_type === 'monkey_finding') { + } else { return Date: Mon, 8 Feb 2021 17:41:57 +0200 Subject: [PATCH 139/152] Added ScoutSuite rule parsing unit test and example of raw ScoutSuite data received. --- .../data_parsing/test_rule_parser.py | 35 +++++++ .../test_common/raw_scoutsute_data.py | 93 +++++++++++++++++++ 2 files changed, 128 insertions(+) create mode 100644 monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/test_rule_parser.py create mode 100644 monkey/monkey_island/cc/services/zero_trust/test_common/raw_scoutsute_data.py diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/test_rule_parser.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/test_rule_parser.py new file mode 100644 index 000000000..cd217882d --- /dev/null +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/test_rule_parser.py @@ -0,0 +1,35 @@ +from enum import Enum + +import pytest + +from common.utils.exceptions import RulePathCreatorNotFound +from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.ec2_rules import EC2Rules +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_parser import RuleParser +from monkey_island.cc.services.zero_trust.test_common.raw_scoutsute_data import RAW_SCOUTSUITE_DATA + + +class ExampleRules(Enum): + NON_EXSISTENT_RULE = 'bogus_rule' + +ALL_PORTS_OPEN = EC2Rules.SECURITY_GROUP_ALL_PORTS_TO_ALL + +EXPECTED_RESULT = {'description': 'Security Group Opens All Ports to All', + 'path': 'ec2.regions.id.vpcs.id.security_groups.id.rules.id.protocols.id.ports.id.cidrs.id.CIDR', + 'level': 'danger', + 'display_path': 'ec2.regions.id.vpcs.id.security_groups.id', + 'items': [ + 'ec2.regions.ap-northeast-1.vpcs.vpc-abc.security_groups.' + 'sg-abc.rules.ingress.protocols.ALL.ports.1-65535.cidrs.0.CIDR'], + 'dashboard_name': 'Rules', 'checked_items': 179, 'flagged_items': 2, 'service': 'EC2', + 'rationale': 'It was detected that all ports in the security group are open <...>', + 'remediation': None, 'compliance': None, 'references': None} + + +def test_get_rule_data(): + # Test proper parsing of the raw data to rule + results = RuleParser.get_rule_data(RAW_SCOUTSUITE_DATA, ALL_PORTS_OPEN) + assert results == EXPECTED_RESULT + + with pytest.raises(RulePathCreatorNotFound): + RuleParser.get_rule_data(RAW_SCOUTSUITE_DATA, ExampleRules.NON_EXSISTENT_RULE) + pass diff --git a/monkey/monkey_island/cc/services/zero_trust/test_common/raw_scoutsute_data.py b/monkey/monkey_island/cc/services/zero_trust/test_common/raw_scoutsute_data.py new file mode 100644 index 000000000..317697632 --- /dev/null +++ b/monkey/monkey_island/cc/services/zero_trust/test_common/raw_scoutsute_data.py @@ -0,0 +1,93 @@ +# This is what our codebase receives after running ScoutSuite module. +# Object '...': {'...': '...'} represents continuation of similar objects as above +RAW_SCOUTSUITE_DATA = { + 'sg_map': { + 'sg-abc': {'region': 'ap-northeast-1', 'vpc_id': 'vpc-abc'}, + 'sg-abcd': {'region': 'ap-northeast-2', 'vpc_id': 'vpc-abc'}, + '...': {'...': '...'}}, + 'subnet_map': { + 'subnet-abc': {'region': 'ap-northeast-1', 'vpc_id': 'vpc-abc'}, + 'subnet-abcd': {'region': 'ap-northeast-1', 'vpc_id': 'vpc-abc'}, + '...': {'...': '...'} + }, + 'provider_code': 'aws', + 'provider_name': 'Amazon Web Services', + 'environment': None, + 'result_format': 'json', + 'partition': 'aws', + 'account_id': '125686982355', + 'last_run': { + 'time': '2021-02-05 16:03:04+0200', + 'run_parameters': {'services': [], 'skipped_services': [], 'regions': [], 'excluded_regions': []}, + 'version': '5.10.0', + 'ruleset_name': 'default', + 'ruleset_about': 'This ruleset', + 'summary': {'ec2': {'checked_items': 3747, 'flagged_items': 262, 'max_level': 'warning', 'rules_count': 28, + 'resources_count': 176}, + 's3': {'checked_items': 88, 'flagged_items': 25, 'max_level': 'danger', 'rules_count': 18, + 'resources_count': 5}, + '...': {'...': '...'}}}, + 'metadata': { + 'compute': { + 'summaries': {'external attack surface': {'cols': 1, + 'path': 'service_groups.compute.summaries.external_attack_surface', + 'callbacks': [ + ['merge', {'attribute': 'external_attack_surface'}]]}}, + '...': {'...': '...'} + }, + '...': {'...': '...'} + }, + + # This is the important part, which we parse to get resources + 'services': { + 'ec2': {'regions': { + 'ap-northeast-1': { + 'vpcs': { + 'vpc-abc': { + 'id': 'vpc-abc', + 'security_groups': { + 'sg-abc': { + 'name': 'default', + 'rules': { + 'ingress': {'protocols': { + 'ALL': {'ports': {'1-65535': {'cidrs': [{'CIDR': '0.0.0.0/0'}]}}}}, + 'count': 1}, + 'egress': {'protocols': { + 'ALL': {'ports': {'1-65535': {'cidrs': [{'CIDR': '0.0.0.0/0'}]}}}}, + 'count': 1}} + } + }}}, + '...': {'...': '...'} + }}, + # Interesting info, maybe could be used somewhere in the report + 'external_attack_surface': { + '52.52.52.52': {'protocols': {'TCP': {'ports': {'22': {'cidrs': [{'CIDR': '0.0.0.0/0'}]}}}}, + 'InstanceName': 'InstanceName', + 'PublicDnsName': 'ec2-52-52-52-52.eu-central-1.compute.amazonaws.com'}}, + # We parse these into ScoutSuite security rules + 'findings': { + 'ec2-security-group-opens-all-ports-to-all': { + 'description': 'Security Group Opens All Ports to All', + 'path': 'ec2.regions.id.vpcs.id.security_groups' + '.id.rules.id.protocols.id.ports.id.cidrs.id.CIDR', + 'level': 'danger', + 'display_path': 'ec2.regions.id.vpcs.id.security_groups.id', + 'items': [ + 'ec2.regions.ap-northeast-1.vpcs.vpc-abc.security_groups' + '.sg-abc.rules.ingress.protocols.ALL.ports.1-65535.cidrs.0.CIDR'], + 'dashboard_name': 'Rules', + 'checked_items': 179, + 'flagged_items': 2, + 'service': 'EC2', + 'rationale': 'It was detected that all ports in the security group are open <...>', + 'remediation': None, 'compliance': None, 'references': None}, + '...': {'...': '...'} + } + }, + '...': {'...': '...'} + }, + 'service_list': ['acm', 'awslambda', 'cloudformation', 'cloudtrail', 'cloudwatch', 'config', 'directconnect', + 'dynamodb', 'ec2', 'efs', 'elasticache', 'elb', 'elbv2', 'emr', 'iam', 'kms', 'rds', 'redshift', + 'route53', 's3', 'ses', 'sns', 'sqs', 'vpc', 'secretsmanager'], + 'service_groups': {'...': {'...': '...'}} +} From a0bb0bc7fea88c811772af8b538eb6ef4f93eea7 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Mon, 8 Feb 2021 17:42:57 +0200 Subject: [PATCH 140/152] Small renamings and minor improvements --- ...findings.py => scoutsuite_finding_maps.py} | 17 +++++----- .../consts/scoutsuite_findings_list.py | 10 +++--- .../scoutsuite/data_parsing/rule_parser.py | 33 +++++++++++++------ .../abstract_rule_path_creator.py | 3 +- .../test_common/scoutsuite_finding_data.py | 2 +- 5 files changed, 40 insertions(+), 25 deletions(-) rename monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/{scoutsuite_findings.py => scoutsuite_finding_maps.py} (95%) diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/scoutsuite_findings.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/scoutsuite_finding_maps.py similarity index 95% rename from monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/scoutsuite_findings.py rename to monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/scoutsuite_finding_maps.py index 0881b4733..a7ef79fdf 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/scoutsuite_findings.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/scoutsuite_finding_maps.py @@ -19,7 +19,8 @@ from .rule_names.sqs_rules import SQSRules from .rule_names.vpc_rules import VPCRules -class ScoutSuiteFinding(ABC): +# Class which links ZT tests and rules to ScoutSuite finding +class ScoutSuiteFindingMap(ABC): @property @abstractmethod def rules(self) -> List[EC2Rules]: @@ -31,7 +32,7 @@ class ScoutSuiteFinding(ABC): pass -class PermissiveFirewallRules(ScoutSuiteFinding): +class PermissiveFirewallRules(ScoutSuiteFindingMap): rules = [EC2Rules.SECURITY_GROUP_ALL_PORTS_TO_ALL, EC2Rules.SECURITY_GROUP_OPENS_TCP_PORT_TO_ALL, EC2Rules.SECURITY_GROUP_OPENS_UDP_PORT_TO_ALL, EC2Rules.SECURITY_GROUP_OPENS_RDP_PORT_TO_ALL, EC2Rules.SECURITY_GROUP_OPENS_SSH_PORT_TO_ALL, EC2Rules.SECURITY_GROUP_OPENS_MYSQL_PORT_TO_ALL, @@ -56,7 +57,7 @@ class PermissiveFirewallRules(ScoutSuiteFinding): test = zero_trust_consts.TEST_SCOUTSUITE_PERMISSIVE_FIREWALL_RULES -class UnencryptedData(ScoutSuiteFinding): +class UnencryptedData(ScoutSuiteFindingMap): rules = [EC2Rules.EBS_SNAPSHOT_NOT_ENCRYPTED, EC2Rules.EBS_VOLUME_NOT_ENCRYPTED, EC2Rules.EC2_INSTANCE_WITH_USER_DATA_SECRETS, ELBv2Rules.ELBV2_LISTENER_ALLOWING_CLEARTEXT, ELBv2Rules.ELBV2_OLDER_SSL_POLICY, @@ -69,7 +70,7 @@ class UnencryptedData(ScoutSuiteFinding): test = zero_trust_consts.TEST_SCOUTSUITE_UNENCRYPTED_DATA -class DataLossPrevention(ScoutSuiteFinding): +class DataLossPrevention(ScoutSuiteFindingMap): rules = [RDSRules.RDS_INSTANCE_BACKUP_DISABLED, RDSRules.RDS_INSTANCE_SHORT_BACKUP_RETENTION_PERIOD, RDSRules.RDS_INSTANCE_SINGLE_AZ, S3Rules.S3_BUCKET_NO_MFA_DELETE, S3Rules.S3_BUCKET_NO_VERSIONING, ELBv2Rules.ELBV2_NO_DELETION_PROTECTION] @@ -77,7 +78,7 @@ class DataLossPrevention(ScoutSuiteFinding): test = zero_trust_consts.TEST_SCOUTSUITE_DATA_LOSS_PREVENTION -class SecureAuthentication(ScoutSuiteFinding): +class SecureAuthentication(ScoutSuiteFindingMap): rules = [ IAMRules.IAM_USER_NO_ACTIVE_KEY_ROTATION, IAMRules.IAM_PASSWORD_POLICY_MINIMUM_LENGTH, @@ -95,7 +96,7 @@ class SecureAuthentication(ScoutSuiteFinding): test = zero_trust_consts.TEST_SCOUTSUITE_SECURE_AUTHENTICATION -class RestrictivePolicies(ScoutSuiteFinding): +class RestrictivePolicies(ScoutSuiteFindingMap): rules = [ IAMRules.IAM_ASSUME_ROLE_POLICY_ALLOWS_ALL, IAMRules.IAM_EC2_ROLE_WITHOUT_INSTANCES, @@ -157,7 +158,7 @@ class RestrictivePolicies(ScoutSuiteFinding): test = zero_trust_consts.TEST_SCOUTSUITE_RESTRICTIVE_POLICIES -class Logging(ScoutSuiteFinding): +class Logging(ScoutSuiteFindingMap): rules = [ CloudTrailRules.CLOUDTRAIL_DUPLICATED_GLOBAL_SERVICES_LOGGING, CloudTrailRules.CLOUDTRAIL_NO_DATA_LOGGING, @@ -177,7 +178,7 @@ class Logging(ScoutSuiteFinding): test = zero_trust_consts.TEST_SCOUTSUITE_LOGGING -class ServiceSecurity(ScoutSuiteFinding): +class ServiceSecurity(ScoutSuiteFindingMap): rules = [ CloudformationRules.CLOUDFORMATION_STACK_WITH_ROLE, ELBv2Rules.ELBV2_HTTP_REQUEST_SMUGGLING, diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/scoutsuite_findings_list.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/scoutsuite_findings_list.py index b123e720e..a531a4476 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/scoutsuite_findings_list.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/scoutsuite_findings_list.py @@ -1,8 +1,8 @@ -from .scoutsuite_findings import (DataLossPrevention, Logging, - PermissiveFirewallRules, - RestrictivePolicies, - SecureAuthentication, ServiceSecurity, - UnencryptedData) +from .scoutsuite_finding_maps import (DataLossPrevention, Logging, + PermissiveFirewallRules, + RestrictivePolicies, + SecureAuthentication, ServiceSecurity, + UnencryptedData) SCOUTSUITE_FINDINGS = [PermissiveFirewallRules, UnencryptedData, DataLossPrevention, SecureAuthentication, RestrictivePolicies, Logging, ServiceSecurity] diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_parser.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_parser.py index 8da69a9bb..e07431541 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_parser.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_parser.py @@ -1,3 +1,5 @@ +from enum import Enum + import dpath.util from common.utils.exceptions import RulePathCreatorNotFound @@ -5,22 +7,33 @@ from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_buil RULE_PATH_CREATORS_LIST +def __build_rule_to_rule_path_creator_hashmap(): + hashmap = {} + for rule_path_creator in RULE_PATH_CREATORS_LIST: + for rule_name in rule_path_creator.supported_rules: + hashmap[rule_name] = rule_path_creator + return hashmap + + +RULE_TO_RULE_PATH_CREATOR_HASHMAP = __build_rule_to_rule_path_creator_hashmap() + + class RuleParser: @staticmethod - def get_rule_data(scoutsuite_data, rule_name): - rule_path = RuleParser.get_rule_path(rule_name) + def get_rule_data(scoutsuite_data: dict, rule_name: Enum) -> dict: + rule_path = RuleParser._get_rule_path(rule_name) return dpath.util.get(scoutsuite_data, rule_path) @staticmethod - def get_rule_path(rule_name): - creator = RuleParser.get_rule_path_creator(rule_name) + def _get_rule_path(rule_name: Enum): + creator = RuleParser._get_rule_path_creator(rule_name) return creator.build_rule_path(rule_name) @staticmethod - def get_rule_path_creator(rule_name): - for rule_path_creator in RULE_PATH_CREATORS_LIST: - if rule_name in rule_path_creator.supported_rules: - return rule_path_creator - raise RulePathCreatorNotFound(f"Rule path creator not found for rule {rule_name.value}. Make sure to assign" - f"this rule to any rule path creators.") + def _get_rule_path_creator(rule_name: Enum): + try: + return RULE_TO_RULE_PATH_CREATOR_HASHMAP[rule_name] + except KeyError: + raise RulePathCreatorNotFound(f"Rule path creator not found for rule {rule_name.value}. Make sure to assign" + f"this rule to any rule path creators.") diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/abstract_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/abstract_rule_path_creator.py index 78c505d92..1d014dcd4 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/abstract_rule_path_creator.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/abstract_rule_path_creator.py @@ -1,4 +1,5 @@ from abc import ABC, abstractmethod +from enum import Enum from typing import List from ...consts.service_consts import FINDINGS, SERVICE_TYPES, SERVICES @@ -17,6 +18,6 @@ class AbstractRulePathCreator(ABC): pass @classmethod - def build_rule_path(cls, rule_name) -> List[str]: + def build_rule_path(cls, rule_name: Enum) -> List[str]: assert(rule_name in cls.supported_rules) return [SERVICES, cls.service_type.value, FINDINGS, rule_name.value] diff --git a/monkey/monkey_island/cc/services/zero_trust/test_common/scoutsuite_finding_data.py b/monkey/monkey_island/cc/services/zero_trust/test_common/scoutsuite_finding_data.py index b82a53260..1dfad750e 100644 --- a/monkey/monkey_island/cc/services/zero_trust/test_common/scoutsuite_finding_data.py +++ b/monkey/monkey_island/cc/services/zero_trust/test_common/scoutsuite_finding_data.py @@ -1,6 +1,6 @@ from monkey_island.cc.models.zero_trust.scoutsuite_finding_details import ScoutSuiteFindingDetails from monkey_island.cc.models.zero_trust.scoutsuite_rule import ScoutSuiteRule -from ..scoutsuite.consts.scoutsuite_findings import PermissiveFirewallRules, UnencryptedData +from ..scoutsuite.consts.scoutsuite_finding_maps import PermissiveFirewallRules, UnencryptedData SCOUTSUITE_FINDINGS = [ PermissiveFirewallRules, From 6f16ba431cecc054695c8e2572fa15985484d548 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 9 Feb 2021 14:21:55 +0200 Subject: [PATCH 141/152] Minor refactorings of code style in zero trust code --- monkey/common/utils/exceptions.py | 4 ++++ monkey/infection_monkey/exploit/HostExploiter.py | 8 ++++---- .../collectors/scoutsuite_collector/__init__.py | 15 +++++++++++++++ .../scoutsuite_collector/scoutsuite_api.py | 16 ---------------- .../scoutsuite_collector/scoutsuite_collector.py | 7 ++++--- .../telemetry/scoutsuite_telem.py | 4 ++-- .../monkey_findings/monkey_zt_details_service.py | 6 +++--- 7 files changed, 32 insertions(+), 28 deletions(-) diff --git a/monkey/common/utils/exceptions.py b/monkey/common/utils/exceptions.py index 6b992b557..2c7121942 100644 --- a/monkey/common/utils/exceptions.py +++ b/monkey/common/utils/exceptions.py @@ -44,3 +44,7 @@ class UnknownFindingError(Exception): class VersionServerConnectionError(Exception): """ Raise to indicate that connection to version update server failed """ + + +class FindingWithoutDetailsError(Exception): + """ Raise when pulling events for a finding, but get none """ diff --git a/monkey/infection_monkey/exploit/HostExploiter.py b/monkey/infection_monkey/exploit/HostExploiter.py index f8fda6d18..c48cadcf0 100644 --- a/monkey/infection_monkey/exploit/HostExploiter.py +++ b/monkey/infection_monkey/exploit/HostExploiter.py @@ -74,10 +74,10 @@ class HostExploiter(Plugin): result = None try: result = self._exploit_host() - except FailedExploitationError as e: - logger.debug(e) - except Exception as _: - logger.error(f'Exception in exploit_host', exc_info=True) + except FailedExploitationError: + logger.debug('Exploiter failed.', exc_info=True) + except Exception: + logger.error('Exception in exploit_host', exc_info=True) finally: self.post_exploit() return result diff --git a/monkey/infection_monkey/system_info/collectors/scoutsuite_collector/__init__.py b/monkey/infection_monkey/system_info/collectors/scoutsuite_collector/__init__.py index e69de29bb..97e736b4b 100644 --- a/monkey/infection_monkey/system_info/collectors/scoutsuite_collector/__init__.py +++ b/monkey/infection_monkey/system_info/collectors/scoutsuite_collector/__init__.py @@ -0,0 +1,15 @@ +import pkgutil +import sys +from pathlib import PurePath + +_scoutsuite_api_package = pkgutil.get_loader('common.cloud.scoutsuite.ScoutSuite.__main__') + + +def _add_scoutsuite_to_python_path(): + scoutsuite_path = PurePath(_scoutsuite_api_package.path).parent.parent.__str__() + sys.path.append(scoutsuite_path) + + +# Add ScoutSuite to python path because this way +# we don't need to change any imports in ScoutSuite code +_add_scoutsuite_to_python_path() diff --git a/monkey/infection_monkey/system_info/collectors/scoutsuite_collector/scoutsuite_api.py b/monkey/infection_monkey/system_info/collectors/scoutsuite_collector/scoutsuite_api.py index 575058946..88ef32293 100644 --- a/monkey/infection_monkey/system_info/collectors/scoutsuite_collector/scoutsuite_api.py +++ b/monkey/infection_monkey/system_info/collectors/scoutsuite_collector/scoutsuite_api.py @@ -1,19 +1,3 @@ -import pkgutil -import sys -from pathlib import PurePath - -_scoutsuite_api_package = pkgutil.get_loader('common.cloud.scoutsuite.ScoutSuite.__main__') - - -def _add_scoutsuite_to_python_path(): - scoutsuite_path = PurePath(_scoutsuite_api_package.path).parent.parent.__str__() - sys.path.append(scoutsuite_path) - - -# Add ScoutSuite to python path because this way -# we don't need to change any imports in ScoutSuite code -_add_scoutsuite_to_python_path() - import common.cloud.scoutsuite.ScoutSuite.api_run as scoutsuite_api diff --git a/monkey/infection_monkey/system_info/collectors/scoutsuite_collector/scoutsuite_collector.py b/monkey/infection_monkey/system_info/collectors/scoutsuite_collector/scoutsuite_collector.py index 7726e980e..0664b9b0f 100644 --- a/monkey/infection_monkey/system_info/collectors/scoutsuite_collector/scoutsuite_collector.py +++ b/monkey/infection_monkey/system_info/collectors/scoutsuite_collector/scoutsuite_collector.py @@ -1,7 +1,8 @@ import logging +from typing import Union import infection_monkey.system_info.collectors.scoutsuite_collector.scoutsuite_api as scoutsuite_api -from common.cloud.scoutsuite.ScoutSuite.providers.aws.provider import AWSProvider +from common.cloud.scoutsuite.ScoutSuite.providers.base.provider import BaseProvider from common.cloud.scoutsuite_consts import CloudProviders from common.utils.exceptions import ScoutSuiteScanError from infection_monkey.config import WormConfiguration @@ -20,12 +21,12 @@ def scan_cloud_security(cloud_type: CloudProviders): logger.error(f"ScoutSuite didn't scan {cloud_type.value} security because: {e}") -def run_scoutsuite(cloud_type: str): +def run_scoutsuite(cloud_type: str) -> Union[BaseProvider, dict]: return scoutsuite_api.run(provider=cloud_type, aws_access_key_id=WormConfiguration.aws_access_key_id, aws_secret_access_key=WormConfiguration.aws_secret_access_key, aws_session_token=WormConfiguration.aws_session_token) -def send_results(results: AWSProvider): +def send_results(results: BaseProvider): ScoutSuiteTelem(results).send() diff --git a/monkey/infection_monkey/telemetry/scoutsuite_telem.py b/monkey/infection_monkey/telemetry/scoutsuite_telem.py index 4e49c0695..5ad553e94 100644 --- a/monkey/infection_monkey/telemetry/scoutsuite_telem.py +++ b/monkey/infection_monkey/telemetry/scoutsuite_telem.py @@ -1,12 +1,12 @@ from common.cloud.scoutsuite.ScoutSuite.output.result_encoder import ScoutJsonEncoder -from common.cloud.scoutsuite.ScoutSuite.providers.aws.provider import AWSProvider +from common.cloud.scoutsuite.ScoutSuite.providers.base.provider import BaseProvider from common.common_consts.telem_categories import TelemCategoryEnum from infection_monkey.telemetry.base_telem import BaseTelem class ScoutSuiteTelem(BaseTelem): - def __init__(self, data: AWSProvider): + def __init__(self, data: BaseProvider): """ Default ScoutSuite telemetry constructor :param data: Data gathered via ScoutSuite diff --git a/monkey/monkey_island/cc/services/zero_trust/monkey_findings/monkey_zt_details_service.py b/monkey/monkey_island/cc/services/zero_trust/monkey_findings/monkey_zt_details_service.py index 63f809fee..3b2a0eed0 100644 --- a/monkey/monkey_island/cc/services/zero_trust/monkey_findings/monkey_zt_details_service.py +++ b/monkey/monkey_island/cc/services/zero_trust/monkey_findings/monkey_zt_details_service.py @@ -2,6 +2,7 @@ from typing import List from bson import ObjectId +from common.utils.exceptions import FindingWithoutDetailsError from monkey_island.cc.models.zero_trust.monkey_finding_details import MonkeyFindingDetails @@ -19,14 +20,13 @@ class MonkeyZTDetailsService: 'latest_events': {'$slice': ['$events', int(-1 * MAX_EVENT_FETCH_CNT / 2)]}, 'event_count': {'$size': '$events'}}}, {'$unset': ['events']}] - details = list(MonkeyFindingDetails.objects.aggregate(*pipeline)) + details = list(MonkeyFindingDetails.objects.aggregate(*pipeline))[0] if details: - details = details[0] details['latest_events'] = MonkeyZTDetailsService._remove_redundant_events(details['event_count'], details['latest_events']) return details else: - return {} + raise FindingWithoutDetailsError(f"Finding {finding_id} had no details.") @staticmethod def _remove_redundant_events(fetched_event_count: int, latest_events: List[object]) -> List[object]: From baadb241e8bb419e5983be7728cef91415a7af8b Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 9 Feb 2021 15:51:34 +0200 Subject: [PATCH 142/152] Reverted relative paths in zero trust code back to absolute --- .../telemetry/processing/scoutsuite.py | 8 ++--- .../system_info_telemetry_dispatcher.py | 9 +++--- .../consts/scoutsuite_finding_maps.py | 30 +++++++++---------- .../consts/scoutsuite_findings_list.py | 7 ++--- .../abstract_rule_path_creator.py | 2 +- .../cloudformation_rule_path_creator.py | 7 +++-- .../cloudtrail_rule_path_creator.py | 7 +++-- .../cloudwatch_rule_path_creator.py | 7 +++-- .../config_rule_path_creator.py | 7 +++-- .../ec2_rule_path_creator.py | 7 +++-- .../elb_rule_path_creator.py | 7 +++-- .../elbv2_rule_path_creator.py | 7 +++-- .../iam_rule_path_creator.py | 7 +++-- .../rds_rule_path_creator.py | 7 +++-- .../redshift_rule_path_creator.py | 7 +++-- .../s3_rule_path_creator.py | 7 +++-- .../ses_rule_path_creator.py | 7 +++-- .../sns_rule_path_creator.py | 7 +++-- .../sqs_rule_path_creator.py | 7 +++-- .../vpc_rule_path_creator.py | 7 +++-- .../test_common/scoutsuite_finding_data.py | 3 +- 21 files changed, 89 insertions(+), 75 deletions(-) diff --git a/monkey/monkey_island/cc/services/telemetry/processing/scoutsuite.py b/monkey/monkey_island/cc/services/telemetry/processing/scoutsuite.py index d3822da27..8ee4737e8 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/scoutsuite.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/scoutsuite.py @@ -2,10 +2,10 @@ import json from monkey_island.cc.database import mongo from monkey_island.cc.models.zero_trust.scoutsuite_data_json import ScoutSuiteRawDataJson -from ...zero_trust.scoutsuite.consts.scoutsuite_findings_list import SCOUTSUITE_FINDINGS -from ...zero_trust.scoutsuite.data_parsing.rule_parser import RuleParser -from ...zero_trust.scoutsuite.scoutsuite_zt_finding_service import ScoutSuiteZTFindingService -from ...zero_trust.scoutsuite.scoutsuite_rule_service import ScoutSuiteRuleService +from monkey_island.cc.services.zero_trust.scoutsuite.consts.scoutsuite_findings_list import SCOUTSUITE_FINDINGS +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_parser import RuleParser +from monkey_island.cc.services.zero_trust.scoutsuite.scoutsuite_rule_service import ScoutSuiteRuleService +from monkey_island.cc.services.zero_trust.scoutsuite.scoutsuite_zt_finding_service import ScoutSuiteZTFindingService def process_scoutsuite_telemetry(telemetry_json): diff --git a/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/system_info_telemetry_dispatcher.py b/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/system_info_telemetry_dispatcher.py index c84704a0b..6d9ec8492 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/system_info_telemetry_dispatcher.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/system_info_telemetry_dispatcher.py @@ -2,10 +2,11 @@ import logging import typing from common.common_consts.system_info_collectors_names import (AWS_COLLECTOR, ENVIRONMENT_COLLECTOR, HOSTNAME_COLLECTOR, - PROCESS_LIST_COLLECTOR, SCOUTSUITE_COLLECTOR) -from .aws import process_aws_telemetry -from .environment import process_environment_telemetry -from .hostname import process_hostname_telemetry + PROCESS_LIST_COLLECTOR) +from monkey_island.cc.services.telemetry.processing.system_info_collectors.aws import process_aws_telemetry +from monkey_island.cc.services.telemetry.processing.system_info_collectors.environment import \ + process_environment_telemetry +from monkey_island.cc.services.telemetry.processing.system_info_collectors.hostname import process_hostname_telemetry from monkey_island.cc.services.telemetry.zero_trust_checks.antivirus_existence import check_antivirus_existence logger = logging.getLogger(__name__) diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/scoutsuite_finding_maps.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/scoutsuite_finding_maps.py index a7ef79fdf..de0c7aa45 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/scoutsuite_finding_maps.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/scoutsuite_finding_maps.py @@ -2,21 +2,21 @@ from abc import ABC, abstractmethod from typing import List from common.common_consts import zero_trust_consts -from .rule_names.cloudformation_rules import CloudformationRules -from .rule_names.cloudtrail_rules import CloudTrailRules -from .rule_names.cloudwatch_rules import CloudWatchRules -from .rule_names.config_rules import ConfigRules -from .rule_names.ec2_rules import EC2Rules -from .rule_names.elb_rules import ELBRules -from .rule_names.elbv2_rules import ELBv2Rules -from .rule_names.iam_rules import IAMRules -from .rule_names.rds_rules import RDSRules -from .rule_names.redshift_rules import RedshiftRules -from .rule_names.s3_rules import S3Rules -from .rule_names.ses_rules import SESRules -from .rule_names.sns_rules import SNSRules -from .rule_names.sqs_rules import SQSRules -from .rule_names.vpc_rules import VPCRules +from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.cloudformation_rules import CloudformationRules +from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.cloudtrail_rules import CloudTrailRules +from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.cloudwatch_rules import CloudWatchRules +from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.config_rules import ConfigRules +from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.ec2_rules import EC2Rules +from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.elb_rules import ELBRules +from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.elbv2_rules import ELBv2Rules +from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.iam_rules import IAMRules +from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rds_rules import RDSRules +from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.redshift_rules import RedshiftRules +from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.s3_rules import S3Rules +from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.ses_rules import SESRules +from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.sns_rules import SNSRules +from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.sqs_rules import SQSRules +from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.vpc_rules import VPCRules # Class which links ZT tests and rules to ScoutSuite finding diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/scoutsuite_findings_list.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/scoutsuite_findings_list.py index a531a4476..d19c2b216 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/scoutsuite_findings_list.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/scoutsuite_findings_list.py @@ -1,8 +1,5 @@ -from .scoutsuite_finding_maps import (DataLossPrevention, Logging, - PermissiveFirewallRules, - RestrictivePolicies, - SecureAuthentication, ServiceSecurity, - UnencryptedData) +from monkey_island.cc.services.zero_trust.scoutsuite.consts.scoutsuite_finding_maps import RestrictivePolicies, \ + SecureAuthentication, DataLossPrevention, UnencryptedData, PermissiveFirewallRules, ServiceSecurity, Logging SCOUTSUITE_FINDINGS = [PermissiveFirewallRules, UnencryptedData, DataLossPrevention, SecureAuthentication, RestrictivePolicies, Logging, ServiceSecurity] diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/abstract_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/abstract_rule_path_creator.py index 1d014dcd4..a5c3f7345 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/abstract_rule_path_creator.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/abstract_rule_path_creator.py @@ -2,7 +2,7 @@ from abc import ABC, abstractmethod from enum import Enum from typing import List -from ...consts.service_consts import FINDINGS, SERVICE_TYPES, SERVICES +from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import FINDINGS, SERVICES, SERVICE_TYPES class AbstractRulePathCreator(ABC): diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/cloudformation_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/cloudformation_rule_path_creator.py index 28a550527..10adb474c 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/cloudformation_rule_path_creator.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/cloudformation_rule_path_creator.py @@ -1,6 +1,7 @@ -from ..abstract_rule_path_creator import AbstractRulePathCreator -from ....consts.rule_names.cloudformation_rules import CloudformationRules -from ....consts.service_consts import SERVICE_TYPES +from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.cloudformation_rules import CloudformationRules +from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import \ + AbstractRulePathCreator class CloudformationRulePathCreator(AbstractRulePathCreator): diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/cloudtrail_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/cloudtrail_rule_path_creator.py index e0734fb42..2f626dfd5 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/cloudtrail_rule_path_creator.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/cloudtrail_rule_path_creator.py @@ -1,6 +1,7 @@ -from ..abstract_rule_path_creator import AbstractRulePathCreator -from ....consts.rule_names.cloudtrail_rules import CloudTrailRules -from ....consts.service_consts import SERVICE_TYPES +from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.cloudtrail_rules import CloudTrailRules +from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import \ + AbstractRulePathCreator class CloudTrailRulePathCreator(AbstractRulePathCreator): diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/cloudwatch_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/cloudwatch_rule_path_creator.py index acbb66611..f6d4d673d 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/cloudwatch_rule_path_creator.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/cloudwatch_rule_path_creator.py @@ -1,6 +1,7 @@ -from ..abstract_rule_path_creator import AbstractRulePathCreator -from ....consts.rule_names.cloudwatch_rules import CloudWatchRules -from ....consts.service_consts import SERVICE_TYPES +from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.cloudwatch_rules import CloudWatchRules +from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import \ + AbstractRulePathCreator class CloudWatchRulePathCreator(AbstractRulePathCreator): diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/config_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/config_rule_path_creator.py index aded2d3c6..59a2e49eb 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/config_rule_path_creator.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/config_rule_path_creator.py @@ -1,6 +1,7 @@ -from ..abstract_rule_path_creator import AbstractRulePathCreator -from ....consts.rule_names.config_rules import ConfigRules -from ....consts.service_consts import SERVICE_TYPES +from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.config_rules import ConfigRules +from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import \ + AbstractRulePathCreator class ConfigRulePathCreator(AbstractRulePathCreator): diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/ec2_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/ec2_rule_path_creator.py index 3692df963..4a37b0a7e 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/ec2_rule_path_creator.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/ec2_rule_path_creator.py @@ -1,6 +1,7 @@ -from ..abstract_rule_path_creator import AbstractRulePathCreator -from ....consts.rule_names.ec2_rules import EC2Rules -from ....consts.service_consts import SERVICE_TYPES +from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.ec2_rules import EC2Rules +from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import \ + AbstractRulePathCreator class EC2RulePathCreator(AbstractRulePathCreator): diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/elb_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/elb_rule_path_creator.py index 055c61744..a38ae2881 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/elb_rule_path_creator.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/elb_rule_path_creator.py @@ -1,6 +1,7 @@ -from ..abstract_rule_path_creator import AbstractRulePathCreator -from ....consts.rule_names.elb_rules import ELBRules -from ....consts.service_consts import SERVICE_TYPES +from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.elb_rules import ELBRules +from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import \ + AbstractRulePathCreator class ELBRulePathCreator(AbstractRulePathCreator): diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/elbv2_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/elbv2_rule_path_creator.py index d45303f88..2472bf076 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/elbv2_rule_path_creator.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/elbv2_rule_path_creator.py @@ -1,6 +1,7 @@ -from ..abstract_rule_path_creator import AbstractRulePathCreator -from ....consts.rule_names.elbv2_rules import ELBv2Rules -from ....consts.service_consts import SERVICE_TYPES +from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.elbv2_rules import ELBv2Rules +from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import \ + AbstractRulePathCreator class ELBv2RulePathCreator(AbstractRulePathCreator): diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/iam_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/iam_rule_path_creator.py index b131cd43b..a601cb9cd 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/iam_rule_path_creator.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/iam_rule_path_creator.py @@ -1,6 +1,7 @@ -from ..abstract_rule_path_creator import AbstractRulePathCreator -from ....consts.rule_names.iam_rules import IAMRules -from ....consts.service_consts import SERVICE_TYPES +from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.iam_rules import IAMRules +from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import \ + AbstractRulePathCreator class IAMRulePathCreator(AbstractRulePathCreator): diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/rds_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/rds_rule_path_creator.py index ac08a51b5..0b8bf54af 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/rds_rule_path_creator.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/rds_rule_path_creator.py @@ -1,6 +1,7 @@ -from ..abstract_rule_path_creator import AbstractRulePathCreator -from ....consts.rule_names.rds_rules import RDSRules -from ....consts.service_consts import SERVICE_TYPES +from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rds_rules import RDSRules +from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import \ + AbstractRulePathCreator class RDSRulePathCreator(AbstractRulePathCreator): diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/redshift_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/redshift_rule_path_creator.py index e567dec35..4de7016a4 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/redshift_rule_path_creator.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/redshift_rule_path_creator.py @@ -1,6 +1,7 @@ -from ..abstract_rule_path_creator import AbstractRulePathCreator -from ....consts.rule_names.redshift_rules import RedshiftRules -from ....consts.service_consts import SERVICE_TYPES +from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.redshift_rules import RedshiftRules +from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import \ + AbstractRulePathCreator class RedshiftRulePathCreator(AbstractRulePathCreator): diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/s3_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/s3_rule_path_creator.py index 67be3914b..4c0a0dccc 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/s3_rule_path_creator.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/s3_rule_path_creator.py @@ -1,6 +1,7 @@ -from ..abstract_rule_path_creator import AbstractRulePathCreator -from ....consts.rule_names.s3_rules import S3Rules -from ....consts.service_consts import SERVICE_TYPES +from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.s3_rules import S3Rules +from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import \ + AbstractRulePathCreator class S3RulePathCreator(AbstractRulePathCreator): diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/ses_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/ses_rule_path_creator.py index 664dc9bc5..c7cac2bce 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/ses_rule_path_creator.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/ses_rule_path_creator.py @@ -1,6 +1,7 @@ -from ..abstract_rule_path_creator import AbstractRulePathCreator -from ....consts.rule_names.ses_rules import SESRules -from ....consts.service_consts import SERVICE_TYPES +from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.ses_rules import SESRules +from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import \ + AbstractRulePathCreator class SESRulePathCreator(AbstractRulePathCreator): diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/sns_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/sns_rule_path_creator.py index 19189d258..60a2f5b1c 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/sns_rule_path_creator.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/sns_rule_path_creator.py @@ -1,6 +1,7 @@ -from ..abstract_rule_path_creator import AbstractRulePathCreator -from ....consts.rule_names.sns_rules import SNSRules -from ....consts.service_consts import SERVICE_TYPES +from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.sns_rules import SNSRules +from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import \ + AbstractRulePathCreator class SNSRulePathCreator(AbstractRulePathCreator): diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/sqs_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/sqs_rule_path_creator.py index 214d19127..619cf2ddb 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/sqs_rule_path_creator.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/sqs_rule_path_creator.py @@ -1,6 +1,7 @@ -from ..abstract_rule_path_creator import AbstractRulePathCreator -from ....consts.rule_names.sqs_rules import SQSRules -from ....consts.service_consts import SERVICE_TYPES +from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.sqs_rules import SQSRules +from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import \ + AbstractRulePathCreator class SQSRulePathCreator(AbstractRulePathCreator): diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/vpc_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/vpc_rule_path_creator.py index 53abe3932..280d0933e 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/vpc_rule_path_creator.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/vpc_rule_path_creator.py @@ -1,6 +1,7 @@ -from ..abstract_rule_path_creator import AbstractRulePathCreator -from ....consts.rule_names.vpc_rules import VPCRules -from ....consts.service_consts import SERVICE_TYPES +from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.vpc_rules import VPCRules +from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import \ + AbstractRulePathCreator class VPCRulePathCreator(AbstractRulePathCreator): diff --git a/monkey/monkey_island/cc/services/zero_trust/test_common/scoutsuite_finding_data.py b/monkey/monkey_island/cc/services/zero_trust/test_common/scoutsuite_finding_data.py index 1dfad750e..4ce33a8f3 100644 --- a/monkey/monkey_island/cc/services/zero_trust/test_common/scoutsuite_finding_data.py +++ b/monkey/monkey_island/cc/services/zero_trust/test_common/scoutsuite_finding_data.py @@ -1,6 +1,7 @@ from monkey_island.cc.models.zero_trust.scoutsuite_finding_details import ScoutSuiteFindingDetails from monkey_island.cc.models.zero_trust.scoutsuite_rule import ScoutSuiteRule -from ..scoutsuite.consts.scoutsuite_finding_maps import PermissiveFirewallRules, UnencryptedData +from monkey_island.cc.services.zero_trust.scoutsuite.consts.scoutsuite_finding_maps import PermissiveFirewallRules, \ + UnencryptedData SCOUTSUITE_FINDINGS = [ PermissiveFirewallRules, From 3cb2a63a9d098c034f63f1c9adfc77919e2f43bc Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Thu, 11 Feb 2021 15:44:20 +0200 Subject: [PATCH 143/152] Changed rule name classes to inherit from RuleNameEnum to add a more specific type hints --- .../scoutsuite/consts/rule_names/cloudformation_rules.py | 4 ++-- .../scoutsuite/consts/rule_names/cloudtrail_rules.py | 4 ++-- .../scoutsuite/consts/rule_names/cloudwatch_rules.py | 4 ++-- .../zero_trust/scoutsuite/consts/rule_names/config_rules.py | 4 ++-- .../zero_trust/scoutsuite/consts/rule_names/ec2_rules.py | 4 ++-- .../zero_trust/scoutsuite/consts/rule_names/elb_rules.py | 4 ++-- .../zero_trust/scoutsuite/consts/rule_names/elbv2_rules.py | 4 ++-- .../zero_trust/scoutsuite/consts/rule_names/iam_rules.py | 4 ++-- .../zero_trust/scoutsuite/consts/rule_names/rds_rules.py | 4 ++-- .../zero_trust/scoutsuite/consts/rule_names/redshift_rules.py | 4 ++-- .../zero_trust/scoutsuite/consts/rule_names/s3_rules.py | 4 ++-- .../zero_trust/scoutsuite/consts/rule_names/ses_rules.py | 4 ++-- .../zero_trust/scoutsuite/consts/rule_names/sns_rules.py | 4 ++-- .../zero_trust/scoutsuite/consts/rule_names/sqs_rules.py | 4 ++-- .../zero_trust/scoutsuite/consts/rule_names/vpc_rules.py | 4 ++-- .../zero_trust/scoutsuite/consts/scoutsuite_finding_maps.py | 3 ++- .../rule_path_building/abstract_rule_path_creator.py | 4 ++-- 17 files changed, 34 insertions(+), 33 deletions(-) diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/cloudformation_rules.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/cloudformation_rules.py index f5c069b4f..f8c87083e 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/cloudformation_rules.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/cloudformation_rules.py @@ -1,7 +1,7 @@ -from enum import Enum +from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rule_name_enum import RuleNameEnum -class CloudformationRules(Enum): +class CloudformationRules(RuleNameEnum): # Service Security CLOUDFORMATION_STACK_WITH_ROLE = 'cloudformation-stack-with-role' diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/cloudtrail_rules.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/cloudtrail_rules.py index ac75ef203..886999341 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/cloudtrail_rules.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/cloudtrail_rules.py @@ -1,7 +1,7 @@ -from enum import Enum +from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rule_name_enum import RuleNameEnum -class CloudTrailRules(Enum): +class CloudTrailRules(RuleNameEnum): # Logging CLOUDTRAIL_DUPLICATED_GLOBAL_SERVICES_LOGGING = 'cloudtrail-duplicated-global-services-logging' CLOUDTRAIL_NO_DATA_LOGGING = 'cloudtrail-no-data-logging' diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/cloudwatch_rules.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/cloudwatch_rules.py index 2209f7788..d22baafc7 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/cloudwatch_rules.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/cloudwatch_rules.py @@ -1,6 +1,6 @@ -from enum import Enum +from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rule_name_enum import RuleNameEnum -class CloudWatchRules(Enum): +class CloudWatchRules(RuleNameEnum): # Logging CLOUDWATCH_ALARM_WITHOUT_ACTIONS = 'cloudwatch-alarm-without-actions' diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/config_rules.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/config_rules.py index 81e2574f8..5d86b0b3e 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/config_rules.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/config_rules.py @@ -1,6 +1,6 @@ -from enum import Enum +from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rule_name_enum import RuleNameEnum -class ConfigRules(Enum): +class ConfigRules(RuleNameEnum): # Logging CONFIG_RECORDER_NOT_CONFIGURED = 'config-recorder-not-configured' diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/ec2_rules.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/ec2_rules.py index 4d7cc8075..dddf18b99 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/ec2_rules.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/ec2_rules.py @@ -1,7 +1,7 @@ -from enum import Enum +from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rule_name_enum import RuleNameEnum -class EC2Rules(Enum): +class EC2Rules(RuleNameEnum): # Permissive firewall rules SECURITY_GROUP_ALL_PORTS_TO_ALL = 'ec2-security-group-opens-all-ports-to-all' SECURITY_GROUP_OPENS_TCP_PORT_TO_ALL = 'ec2-security-group-opens-TCP-port-to-all' diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/elb_rules.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/elb_rules.py index 51a8f9d55..0d1d4e5d9 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/elb_rules.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/elb_rules.py @@ -1,7 +1,7 @@ -from enum import Enum +from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rule_name_enum import RuleNameEnum -class ELBRules(Enum): +class ELBRules(RuleNameEnum): # Logging ELB_NO_ACCESS_LOGS = 'elb-no-access-logs' diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/elbv2_rules.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/elbv2_rules.py index 0d2d97681..f7a264cf3 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/elbv2_rules.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/elbv2_rules.py @@ -1,7 +1,7 @@ -from enum import Enum +from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rule_name_enum import RuleNameEnum -class ELBv2Rules(Enum): +class ELBv2Rules(RuleNameEnum): # Encryption ELBV2_LISTENER_ALLOWING_CLEARTEXT = 'elbv2-listener-allowing-cleartext' ELBV2_OLDER_SSL_POLICY = 'elbv2-older-ssl-policy' diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/iam_rules.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/iam_rules.py index 39b7ec4be..fef58e066 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/iam_rules.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/iam_rules.py @@ -1,7 +1,7 @@ -from enum import Enum +from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rule_name_enum import RuleNameEnum -class IAMRules(Enum): +class IAMRules(RuleNameEnum): # Authentication/authorization IAM_USER_NO_ACTIVE_KEY_ROTATION = 'iam-user-no-Active-key-rotation' IAM_PASSWORD_POLICY_MINIMUM_LENGTH = 'iam-password-policy-minimum-length' diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/rds_rules.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/rds_rules.py index fc7af9876..b303c8573 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/rds_rules.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/rds_rules.py @@ -1,7 +1,7 @@ -from enum import Enum +from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rule_name_enum import RuleNameEnum -class RDSRules(Enum): +class RDSRules(RuleNameEnum): # Encryption RDS_INSTANCE_STORAGE_NOT_ENCRYPTED = 'rds-instance-storage-not-encrypted' diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/redshift_rules.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/redshift_rules.py index 5df21d981..2538cf54d 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/redshift_rules.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/redshift_rules.py @@ -1,7 +1,7 @@ -from enum import Enum +from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rule_name_enum import RuleNameEnum -class RedshiftRules(Enum): +class RedshiftRules(RuleNameEnum): # Encryption REDSHIFT_CLUSTER_DATABASE_NOT_ENCRYPTED = 'redshift-cluster-database-not-encrypted' REDSHIFT_PARAMETER_GROUP_SSL_NOT_REQUIRED = 'redshift-parameter-group-ssl-not-required' diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/s3_rules.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/s3_rules.py index b606fb050..4ba27a57a 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/s3_rules.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/s3_rules.py @@ -1,7 +1,7 @@ -from enum import Enum +from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rule_name_enum import RuleNameEnum -class S3Rules(Enum): +class S3Rules(RuleNameEnum): # Encryption S3_BUCKET_ALLOWING_CLEARTEXT = 's3-bucket-allowing-cleartext' S3_BUCKET_NO_DEFAULT_ENCRYPTION = 's3-bucket-no-default-encryption' diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/ses_rules.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/ses_rules.py index 94a9c9034..4cb875c6d 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/ses_rules.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/ses_rules.py @@ -1,7 +1,7 @@ -from enum import Enum +from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rule_name_enum import RuleNameEnum -class SESRules(Enum): +class SESRules(RuleNameEnum): # Permissive policies SES_IDENTITY_WORLD_SENDRAWEMAIL_POLICY = 'ses-identity-world-SendRawEmail-policy' diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/sns_rules.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/sns_rules.py index 1193388e1..9fb847114 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/sns_rules.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/sns_rules.py @@ -1,7 +1,7 @@ -from enum import Enum +from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rule_name_enum import RuleNameEnum -class SNSRules(Enum): +class SNSRules(RuleNameEnum): # Permissive policies SNS_TOPIC_WORLD_SUBSCRIBE_POLICY = 'sns-topic-world-Subscribe-policy' diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/sqs_rules.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/sqs_rules.py index d3f512416..cc5c774e3 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/sqs_rules.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/sqs_rules.py @@ -1,7 +1,7 @@ -from enum import Enum +from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rule_name_enum import RuleNameEnum -class SQSRules(Enum): +class SQSRules(RuleNameEnum): # Permissive policies SQS_QUEUE_WORLD_SENDMESSAGE_POLICY = 'sqs-queue-world-SendMessage-policy' diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/vpc_rules.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/vpc_rules.py index 300a76dbb..4dcbd4f1a 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/vpc_rules.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/vpc_rules.py @@ -1,7 +1,7 @@ -from enum import Enum +from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rule_name_enum import RuleNameEnum -class VPCRules(Enum): +class VPCRules(RuleNameEnum): # Logging SUBNET_WITHOUT_FLOW_LOG = 'vpc-subnet-without-flow-log' diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/scoutsuite_finding_maps.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/scoutsuite_finding_maps.py index de0c7aa45..251e57324 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/scoutsuite_finding_maps.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/scoutsuite_finding_maps.py @@ -12,6 +12,7 @@ from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.elbv2_rul from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.iam_rules import IAMRules from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rds_rules import RDSRules from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.redshift_rules import RedshiftRules +from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rule_name_enum import RuleNameEnum from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.s3_rules import S3Rules from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.ses_rules import SESRules from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.sns_rules import SNSRules @@ -23,7 +24,7 @@ from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.vpc_rules class ScoutSuiteFindingMap(ABC): @property @abstractmethod - def rules(self) -> List[EC2Rules]: + def rules(self) -> List[RuleNameEnum]: pass @property diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/abstract_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/abstract_rule_path_creator.py index a5c3f7345..7a2b25863 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/abstract_rule_path_creator.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/abstract_rule_path_creator.py @@ -1,5 +1,5 @@ from abc import ABC, abstractmethod -from enum import Enum +from enum import Enum, EnumMeta from typing import List from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import FINDINGS, SERVICES, SERVICE_TYPES @@ -14,7 +14,7 @@ class AbstractRulePathCreator(ABC): @property @abstractmethod - def supported_rules(self) -> List: + def supported_rules(self) -> EnumMeta: pass @classmethod From e96ee305fb2ad59691a89649bc1a754b4581a638 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Thu, 11 Feb 2021 16:52:20 +0200 Subject: [PATCH 144/152] Minor variable and method refactoring to improve readability in scoutsuite code --- .../scoutsuite_collector/scoutsuite_collector.py | 6 +++--- monkey/infection_monkey/telemetry/scoutsuite_telem.py | 10 +++------- .../monkey_findings/monkey_zt_details_service.py | 5 +++-- 3 files changed, 9 insertions(+), 12 deletions(-) diff --git a/monkey/infection_monkey/system_info/collectors/scoutsuite_collector/scoutsuite_collector.py b/monkey/infection_monkey/system_info/collectors/scoutsuite_collector/scoutsuite_collector.py index 0664b9b0f..c637e3593 100644 --- a/monkey/infection_monkey/system_info/collectors/scoutsuite_collector/scoutsuite_collector.py +++ b/monkey/infection_monkey/system_info/collectors/scoutsuite_collector/scoutsuite_collector.py @@ -16,7 +16,7 @@ def scan_cloud_security(cloud_type: CloudProviders): results = run_scoutsuite(cloud_type.value) if isinstance(results, dict) and 'error' in results and results['error']: raise ScoutSuiteScanError(results['error']) - send_results(results) + send_scoutsuite_run_results(results) except (Exception, ScoutSuiteScanError) as e: logger.error(f"ScoutSuite didn't scan {cloud_type.value} security because: {e}") @@ -28,5 +28,5 @@ def run_scoutsuite(cloud_type: str) -> Union[BaseProvider, dict]: aws_session_token=WormConfiguration.aws_session_token) -def send_results(results: BaseProvider): - ScoutSuiteTelem(results).send() +def send_scoutsuite_run_results(run_results: BaseProvider): + ScoutSuiteTelem(run_results).send() diff --git a/monkey/infection_monkey/telemetry/scoutsuite_telem.py b/monkey/infection_monkey/telemetry/scoutsuite_telem.py index 5ad553e94..16cf47bdd 100644 --- a/monkey/infection_monkey/telemetry/scoutsuite_telem.py +++ b/monkey/infection_monkey/telemetry/scoutsuite_telem.py @@ -6,18 +6,14 @@ from infection_monkey.telemetry.base_telem import BaseTelem class ScoutSuiteTelem(BaseTelem): - def __init__(self, data: BaseProvider): - """ - Default ScoutSuite telemetry constructor - :param data: Data gathered via ScoutSuite - """ + def __init__(self, provider: BaseProvider): super().__init__() - self.data = data + self.provider_data = provider json_encoder = ScoutJsonEncoder telem_category = TelemCategoryEnum.SCOUTSUITE def get_data(self): return { - 'data': self.data + 'data': self.provider_data } diff --git a/monkey/monkey_island/cc/services/zero_trust/monkey_findings/monkey_zt_details_service.py b/monkey/monkey_island/cc/services/zero_trust/monkey_findings/monkey_zt_details_service.py index 3b2a0eed0..167934d29 100644 --- a/monkey/monkey_island/cc/services/zero_trust/monkey_findings/monkey_zt_details_service.py +++ b/monkey/monkey_island/cc/services/zero_trust/monkey_findings/monkey_zt_details_service.py @@ -20,8 +20,9 @@ class MonkeyZTDetailsService: 'latest_events': {'$slice': ['$events', int(-1 * MAX_EVENT_FETCH_CNT / 2)]}, 'event_count': {'$size': '$events'}}}, {'$unset': ['events']}] - details = list(MonkeyFindingDetails.objects.aggregate(*pipeline))[0] - if details: + detail_list = list(MonkeyFindingDetails.objects.aggregate(*pipeline)) + if detail_list: + details = detail_list[0] details['latest_events'] = MonkeyZTDetailsService._remove_redundant_events(details['event_count'], details['latest_events']) return details From 25704b74c9ff56067b929e0414703af854988857 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Thu, 11 Feb 2021 16:53:19 +0200 Subject: [PATCH 145/152] Zero Trust finding saving method refactored to remove code duplication between children --- monkey/monkey_island/cc/models/zero_trust/finding.py | 12 ++++++------ .../cc/models/zero_trust/monkey_finding.py | 10 ---------- .../cc/models/zero_trust/scoutsuite_finding.py | 10 ---------- 3 files changed, 6 insertions(+), 26 deletions(-) diff --git a/monkey/monkey_island/cc/models/zero_trust/finding.py b/monkey/monkey_island/cc/models/zero_trust/finding.py index 4d108ddde..2e6b9c172 100644 --- a/monkey/monkey_island/cc/models/zero_trust/finding.py +++ b/monkey/monkey_island/cc/models/zero_trust/finding.py @@ -5,8 +5,6 @@ Define a Document Schema for Zero Trust findings. from __future__ import annotations -import abc - from mongoengine import Document, GenericLazyReferenceField, StringField import common.common_consts.zero_trust_consts as zero_trust_consts @@ -45,9 +43,11 @@ class Finding(Document): details = GenericLazyReferenceField(required=True) # Creation methods - @staticmethod - @abc.abstractmethod - def save_finding(test: str, + @classmethod + def save_finding(cls, + test: str, status: str, detail_ref) -> Finding: - pass + finding = cls(test=test, status=status, details=detail_ref) + finding.save() + return finding diff --git a/monkey/monkey_island/cc/models/zero_trust/monkey_finding.py b/monkey/monkey_island/cc/models/zero_trust/monkey_finding.py index e063ea5c1..1de81303c 100644 --- a/monkey/monkey_island/cc/models/zero_trust/monkey_finding.py +++ b/monkey/monkey_island/cc/models/zero_trust/monkey_finding.py @@ -6,13 +6,3 @@ from monkey_island.cc.models.zero_trust.monkey_finding_details import MonkeyFind class MonkeyFinding(Finding): details = LazyReferenceField(MonkeyFindingDetails, required=True) - - @staticmethod - def save_finding(test: str, - status: str, - detail_ref: MonkeyFindingDetails) -> Finding: - monkey_finding = MonkeyFinding(test=test, - status=status, - details=detail_ref) - monkey_finding.save() - return monkey_finding diff --git a/monkey/monkey_island/cc/models/zero_trust/scoutsuite_finding.py b/monkey/monkey_island/cc/models/zero_trust/scoutsuite_finding.py index 8adeef2e9..dd323a73c 100644 --- a/monkey/monkey_island/cc/models/zero_trust/scoutsuite_finding.py +++ b/monkey/monkey_island/cc/models/zero_trust/scoutsuite_finding.py @@ -6,13 +6,3 @@ from monkey_island.cc.models.zero_trust.scoutsuite_finding_details import ScoutS class ScoutSuiteFinding(Finding): details = LazyReferenceField(ScoutSuiteFindingDetails, required=True) - - @staticmethod - def save_finding(test: str, - status: str, - detail_ref: ScoutSuiteFindingDetails) -> Finding: - scoutsuite_finding = ScoutSuiteFinding(test=test, - status=status, - details=detail_ref) - scoutsuite_finding.save() - return scoutsuite_finding From 8b8c5f95908f8c27a92cdc47e6d1429de6b5effe Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 17 Feb 2021 16:20:13 +0200 Subject: [PATCH 146/152] Added RuleNameEnum class and a type hint related to it in abstract class --- .../scoutsuite/consts/rule_names/rule_name_enum.py | 5 +++++ .../rule_path_building/abstract_rule_path_creator.py | 7 ++++--- 2 files changed, 9 insertions(+), 3 deletions(-) create mode 100644 monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/rule_name_enum.py diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/rule_name_enum.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/rule_name_enum.py new file mode 100644 index 000000000..5ad382c3d --- /dev/null +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/rule_name_enum.py @@ -0,0 +1,5 @@ +from enum import Enum + + +class RuleNameEnum(Enum): + pass diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/abstract_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/abstract_rule_path_creator.py index 7a2b25863..b4767124b 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/abstract_rule_path_creator.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/abstract_rule_path_creator.py @@ -1,7 +1,8 @@ from abc import ABC, abstractmethod -from enum import Enum, EnumMeta -from typing import List +from enum import Enum +from typing import List, Type +from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rule_name_enum import RuleNameEnum from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import FINDINGS, SERVICES, SERVICE_TYPES @@ -14,7 +15,7 @@ class AbstractRulePathCreator(ABC): @property @abstractmethod - def supported_rules(self) -> EnumMeta: + def supported_rules(self) -> Type[RuleNameEnum]: pass @classmethod From 01c775e9557b1da981ab1697606949c4d83f2121 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 17 Feb 2021 16:53:59 +0200 Subject: [PATCH 147/152] Refactored details out of abstract finding document. --- .../cc/models/zero_trust/finding.py | 16 +--------------- .../cc/models/zero_trust/monkey_finding.py | 12 ++++++++++++ .../cc/models/zero_trust/scoutsuite_finding.py | 12 ++++++++++++ .../monkey_findings/monkey_zt_finding_service.py | 2 +- 4 files changed, 26 insertions(+), 16 deletions(-) diff --git a/monkey/monkey_island/cc/models/zero_trust/finding.py b/monkey/monkey_island/cc/models/zero_trust/finding.py index 2e6b9c172..5c8223e49 100644 --- a/monkey/monkey_island/cc/models/zero_trust/finding.py +++ b/monkey/monkey_island/cc/models/zero_trust/finding.py @@ -5,7 +5,7 @@ Define a Document Schema for Zero Trust findings. from __future__ import annotations -from mongoengine import Document, GenericLazyReferenceField, StringField +from mongoengine import Document, StringField import common.common_consts.zero_trust_consts as zero_trust_consts # Dummy import for mongoengine. @@ -37,17 +37,3 @@ class Finding(Document): # SCHEMA test = StringField(required=True, choices=zero_trust_consts.TESTS) status = StringField(required=True, choices=zero_trust_consts.ORDERED_TEST_STATUSES) - - # Details are in a separate document in order to discourage pulling them when not needed - # due to performance. - details = GenericLazyReferenceField(required=True) - - # Creation methods - @classmethod - def save_finding(cls, - test: str, - status: str, - detail_ref) -> Finding: - finding = cls(test=test, status=status, details=detail_ref) - finding.save() - return finding diff --git a/monkey/monkey_island/cc/models/zero_trust/monkey_finding.py b/monkey/monkey_island/cc/models/zero_trust/monkey_finding.py index 1de81303c..479b9b244 100644 --- a/monkey/monkey_island/cc/models/zero_trust/monkey_finding.py +++ b/monkey/monkey_island/cc/models/zero_trust/monkey_finding.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from mongoengine import LazyReferenceField from monkey_island.cc.models.zero_trust.finding import Finding @@ -5,4 +7,14 @@ from monkey_island.cc.models.zero_trust.monkey_finding_details import MonkeyFind class MonkeyFinding(Finding): + # We put additional info into a lazy reference field, because this info should be only + # pulled when explicitly needed due to performance details = LazyReferenceField(MonkeyFindingDetails, required=True) + + @staticmethod + def save_finding(test: str, + status: str, + detail_ref: MonkeyFindingDetails) -> MonkeyFinding: + finding = MonkeyFinding(test=test, status=status, details=detail_ref) + finding.save() + return finding diff --git a/monkey/monkey_island/cc/models/zero_trust/scoutsuite_finding.py b/monkey/monkey_island/cc/models/zero_trust/scoutsuite_finding.py index dd323a73c..9e36e46c5 100644 --- a/monkey/monkey_island/cc/models/zero_trust/scoutsuite_finding.py +++ b/monkey/monkey_island/cc/models/zero_trust/scoutsuite_finding.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from mongoengine import LazyReferenceField from monkey_island.cc.models.zero_trust.finding import Finding @@ -5,4 +7,14 @@ from monkey_island.cc.models.zero_trust.scoutsuite_finding_details import ScoutS class ScoutSuiteFinding(Finding): + # We put additional info into a lazy reference field, because this info should be only + # pulled when explicitly needed due to performance details = LazyReferenceField(ScoutSuiteFindingDetails, required=True) + + @staticmethod + def save_finding(test: str, + status: str, + detail_ref: ScoutSuiteFindingDetails) -> ScoutSuiteFinding: + finding = ScoutSuiteFinding(test=test, status=status, details=detail_ref) + finding.save() + return finding diff --git a/monkey/monkey_island/cc/services/zero_trust/monkey_findings/monkey_zt_finding_service.py b/monkey/monkey_island/cc/services/zero_trust/monkey_findings/monkey_zt_finding_service.py index 72e9f7861..d8e439c71 100644 --- a/monkey/monkey_island/cc/services/zero_trust/monkey_findings/monkey_zt_finding_service.py +++ b/monkey/monkey_island/cc/services/zero_trust/monkey_findings/monkey_zt_finding_service.py @@ -19,7 +19,7 @@ class MonkeyZTFindingService: :raises: Assertion error if this is used when there's more then one finding which fits the query - this is not when this function should be used. """ - existing_findings = MonkeyFinding.objects(test=test, status=status) + existing_findings = list(MonkeyFinding.objects(test=test, status=status)) assert (len(existing_findings) < 2), "More than one finding exists for {}:{}".format(test, status) if len(existing_findings) == 0: From 81ea0575099e8b4cf386712add3de61dc5b3e34b Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 17 Feb 2021 16:58:26 +0200 Subject: [PATCH 148/152] Fixed a bug in MonkeyFindingDetails, where updating events using update was failing due to some internal mongoengine error. --- .../cc/models/zero_trust/monkey_finding_details.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/monkey/monkey_island/cc/models/zero_trust/monkey_finding_details.py b/monkey/monkey_island/cc/models/zero_trust/monkey_finding_details.py index bec29fb21..62cfda504 100644 --- a/monkey/monkey_island/cc/models/zero_trust/monkey_finding_details.py +++ b/monkey/monkey_island/cc/models/zero_trust/monkey_finding_details.py @@ -14,5 +14,6 @@ class MonkeyFindingDetails(Document): # LOGIC def add_events(self, events: List[Event]) -> MonkeyFindingDetails: - self.update(push_all__events=events) + self.events.extend(events) + self.save() return self From a977ec4397751794152cad7879fcdd8184bcf30b Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Thu, 18 Feb 2021 10:13:23 +0200 Subject: [PATCH 149/152] Cleaned up imports and added no inspection comments to pass flake --- monkey/monkey_island/cc/conftest.py | 4 +++- monkey/monkey_island/cc/models/zero_trust/finding.py | 3 --- monkey/monkey_island/cc/resources/auth/auth.py | 3 ++- .../telemetry/zero_trust_checks/communicate_as_new_user.py | 6 +++--- .../zero_trust/scoutsuite/data_parsing/test_rule_parser.py | 1 + .../scoutsuite/test_scoutsuite_zt_finding_service.py | 1 - .../zero_trust/test_common/scoutsuite_finding_data.py | 2 -- .../zero_trust/zero_trust_report/test_finding_service.py | 3 --- monkey/monkey_island/cc/test_common/fixtures/__init__.py | 6 ++++-- .../monkey_island/cc/test_common/fixtures/fixture_enum.py | 2 -- 10 files changed, 13 insertions(+), 18 deletions(-) diff --git a/monkey/monkey_island/cc/conftest.py b/monkey/monkey_island/cc/conftest.py index ad683d49e..0ed1533ab 100644 --- a/monkey/monkey_island/cc/conftest.py +++ b/monkey/monkey_island/cc/conftest.py @@ -1 +1,3 @@ -from monkey_island.cc.test_common.fixtures import * +# Without these imports pytests can't use fixtures, +# because they are not found +from monkey_island.cc.test_common.fixtures import * # noqa: F401,F403 diff --git a/monkey/monkey_island/cc/models/zero_trust/finding.py b/monkey/monkey_island/cc/models/zero_trust/finding.py index 5c8223e49..f65d39af7 100644 --- a/monkey/monkey_island/cc/models/zero_trust/finding.py +++ b/monkey/monkey_island/cc/models/zero_trust/finding.py @@ -8,9 +8,6 @@ from __future__ import annotations from mongoengine import Document, StringField import common.common_consts.zero_trust_consts as zero_trust_consts -# Dummy import for mongoengine. -# noinspection PyUnresolvedReferences -from monkey_island.cc.models.zero_trust.monkey_finding_details import MonkeyFindingDetails class Finding(Document): diff --git a/monkey/monkey_island/cc/resources/auth/auth.py b/monkey/monkey_island/cc/resources/auth/auth.py index 71611221c..b188955d8 100644 --- a/monkey/monkey_island/cc/resources/auth/auth.py +++ b/monkey/monkey_island/cc/resources/auth/auth.py @@ -46,7 +46,8 @@ class Authenticate(flask_restful.Resource): secret = credentials["password"] # If the user and password have been previously registered if self._authenticate(username, secret): - access_token = flask_jwt_extended.create_access_token(identity=user_store.UserStore.username_table[username].id) + access_token = flask_jwt_extended.create_access_token( + identity=user_store.UserStore.username_table[username].id) logger.debug(f"Created access token for user {username} that begins with {access_token[:4]}") return make_response({"access_token": access_token, "error": ""}, 200) else: diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/communicate_as_new_user.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/communicate_as_new_user.py index 94412b3ba..2ef914786 100644 --- a/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/communicate_as_new_user.py +++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/communicate_as_new_user.py @@ -12,9 +12,9 @@ def check_new_user_communication(current_monkey, success, message): MonkeyZTFindingService.create_or_add_to_existing(test=zero_trust_consts.TEST_COMMUNICATE_AS_NEW_USER, status=status, events=[ - get_attempt_event(current_monkey), - get_result_event(current_monkey, message, success) - ]) + get_attempt_event(current_monkey), + get_result_event(current_monkey, message, success) + ]) def get_attempt_event(current_monkey): diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/test_rule_parser.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/test_rule_parser.py index cd217882d..5a7572eb0 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/test_rule_parser.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/test_rule_parser.py @@ -11,6 +11,7 @@ from monkey_island.cc.services.zero_trust.test_common.raw_scoutsute_data import class ExampleRules(Enum): NON_EXSISTENT_RULE = 'bogus_rule' + ALL_PORTS_OPEN = EC2Rules.SECURITY_GROUP_ALL_PORTS_TO_ALL EXPECTED_RESULT = {'description': 'Security Group Opens All Ports to All', diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_zt_finding_service.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_zt_finding_service.py index 00e9d1e32..549d3161e 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_zt_finding_service.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_zt_finding_service.py @@ -1,6 +1,5 @@ import pytest -from common.common_consts import zero_trust_consts from monkey_island.cc.models.zero_trust.finding import Finding from monkey_island.cc.models.zero_trust.scoutsuite_finding import ScoutSuiteFinding from monkey_island.cc.services.zero_trust.scoutsuite.scoutsuite_zt_finding_service import ScoutSuiteZTFindingService diff --git a/monkey/monkey_island/cc/services/zero_trust/test_common/scoutsuite_finding_data.py b/monkey/monkey_island/cc/services/zero_trust/test_common/scoutsuite_finding_data.py index 4ce33a8f3..fb9722ca2 100644 --- a/monkey/monkey_island/cc/services/zero_trust/test_common/scoutsuite_finding_data.py +++ b/monkey/monkey_island/cc/services/zero_trust/test_common/scoutsuite_finding_data.py @@ -73,5 +73,3 @@ def get_scoutsuite_details_dto() -> ScoutSuiteFindingDetails: scoutsuite_details.scoutsuite_rules.append(RULES[0]) scoutsuite_details.scoutsuite_rules.append(RULES[1]) return scoutsuite_details - - diff --git a/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/test_finding_service.py b/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/test_finding_service.py index fdbe6df39..9d832e106 100644 --- a/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/test_finding_service.py +++ b/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/test_finding_service.py @@ -45,6 +45,3 @@ def test_get_all_findings(): assert findings[0] == expected_finding0 assert findings[1] == expected_finding1 - - - diff --git a/monkey/monkey_island/cc/test_common/fixtures/__init__.py b/monkey/monkey_island/cc/test_common/fixtures/__init__.py index 1188586b1..fd208655a 100644 --- a/monkey/monkey_island/cc/test_common/fixtures/__init__.py +++ b/monkey/monkey_island/cc/test_common/fixtures/__init__.py @@ -1,2 +1,4 @@ -from .fixture_enum import FixtureEnum -from .mongomock_fixtures import * +# Without these imports pytests can't use fixtures, +# because they are not found +from .fixture_enum import FixtureEnum # noqa: F401 +from .mongomock_fixtures import * # noqa: F401,F403 diff --git a/monkey/monkey_island/cc/test_common/fixtures/fixture_enum.py b/monkey/monkey_island/cc/test_common/fixtures/fixture_enum.py index 00ab2905f..17c115079 100644 --- a/monkey/monkey_island/cc/test_common/fixtures/fixture_enum.py +++ b/monkey/monkey_island/cc/test_common/fixtures/fixture_enum.py @@ -1,4 +1,2 @@ - - class FixtureEnum: USES_DATABASE = 'uses_database' From 6d31afacd0094cc2a0bceaf370fcb3a3d33cadce Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Thu, 18 Feb 2021 16:45:34 +0200 Subject: [PATCH 150/152] Swimm: update unit Add a new System Info Collector (id: OwcKMnALpn7tuBaJY1US). --- .swm/OwcKMnALpn7tuBaJY1US.swm | 422 ++++++++++++++++------------------ 1 file changed, 195 insertions(+), 227 deletions(-) diff --git a/.swm/OwcKMnALpn7tuBaJY1US.swm b/.swm/OwcKMnALpn7tuBaJY1US.swm index a243319d2..0640f1c37 100644 --- a/.swm/OwcKMnALpn7tuBaJY1US.swm +++ b/.swm/OwcKMnALpn7tuBaJY1US.swm @@ -1,232 +1,200 @@ { "id": "OwcKMnALpn7tuBaJY1US", "name": "Add a new System Info Collector", - "dod": "Add a system info collector that collects the machine hostname.", - "description": "# What are system info collectors?\n\nWell, the name pretty much explains it. They are Monkey classes which collect various information regarding the victim system, such as Environment, SSH Info, Process List, Netstat and more. \n\n## What should I add? \n\nA system info collector which collects the hostname of the system.\n\n## Test manually\n\nOnce you're done, make sure that your collector:\n* Appears in the Island configuration, and is enabled by default\n* The collector actually runs when executing a Monkey.\n* Results show up in the relevant places:\n * The infection map.\n * The security report.\n * The relevant MITRE techniques.\n\n**There are a lot of hints for this unit - don't be afraid to use them!**", - "summary": "System info collectors are useful to get more data for various things, such as ZT tests or MITRE techniques. Take a look at some other techniques!", - "hunksOrder": [ - "monkey/common/data/system_info_collectors_names.py_0", - "monkey/infection_monkey/system_info/collectors/hostname_collector.py_0", - "monkey/monkey_island/cc/services/config_schema/definitions/system_info_collector_classes.py_0", - "monkey/monkey_island/cc/services/config_schema/definitions/system_info_collector_classes.py_1", - "monkey/monkey_island/cc/services/config_schema/monkey.py_0", - "monkey/monkey_island/cc/services/config_schema/monkey.py_1", - "monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/hostname.py_0", - "monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/system_info_telemetry_dispatcher.py_0", - "monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/system_info_telemetry_dispatcher.py_1" - ], - "tests": [], - "hints": [ - "First thing you should do is take a look at a different collector (like EnvironmentCollector) and 100% understand how it runs, how results are relayed back to the server, and how the server processes the data.", - "Try to run \"socket.getfqdn()\".", - "Take a look at SystemInfoCollector - that's the base class you'll need to implement.", - "Make sure you add the new collector to the configuration in all relevant places, including making it ON by default!" - ], - "play_mode": "all", - "swimmPatch": { - "monkey/common/data/system_info_collectors_names.py": { - "diffType": "MODIFIED", - "fileDiffHeader": "diff --git a/monkey/common/data/system_info_collectors_names.py b/monkey/common/data/system_info_collectors_names.py\nindex 175a054e..3b478dc9 100644\n--- a/monkey/common/data/system_info_collectors_names.py\n+++ b/monkey/common/data/system_info_collectors_names.py", - "hunks": [ - { - "swimmHunkMetadata": { - "hunkComments": [] - }, - "hunkDiffLines": [ - "@@ -1,5 +1,5 @@", - " AWS_COLLECTOR = \"AwsCollector\"", - "-HOSTNAME_COLLECTOR = \"HostnameCollector\"", - "+# SWIMMER: Collector name goes here.", - " ENVIRONMENT_COLLECTOR = \"EnvironmentCollector\"", - " PROCESS_LIST_COLLECTOR = \"ProcessListCollector\"", - " MIMIKATZ_COLLECTOR = \"MimikatzCollector\"" - ] - } - ] - }, - "monkey/infection_monkey/system_info/collectors/hostname_collector.py": { - "diffType": "MODIFIED", - "fileDiffHeader": "diff --git a/monkey/infection_monkey/system_info/collectors/hostname_collector.py b/monkey/infection_monkey/system_info/collectors/hostname_collector.py\nindex ae956081..bdeb5033 100644\n--- a/monkey/infection_monkey/system_info/collectors/hostname_collector.py\n+++ b/monkey/infection_monkey/system_info/collectors/hostname_collector.py", - "hunks": [ - { - "swimmHunkMetadata": { - "hunkComments": [] - }, - "hunkDiffLines": [ - "@@ -1,16 +1,5 @@", - " import logging", - "-import socket", - "-", - "-from common.data.system_info_collectors_names import HOSTNAME_COLLECTOR", - "-from infection_monkey.system_info.system_info_collector import \\", - "- SystemInfoCollector", - " ", - " logger = logging.getLogger(__name__)", - " ", - "-", - "+# SWIMMER: The collector class goes here.", - "-class HostnameCollector(SystemInfoCollector):", - "- def __init__(self):", - "- super().__init__(name=HOSTNAME_COLLECTOR)", - "-", - "- def collect(self) -> dict:", - "- return {\"hostname\": socket.getfqdn()}" - ] - } - ] - }, - "monkey/monkey_island/cc/services/config_schema/definitions/system_info_collector_classes.py": { - "diffType": "MODIFIED", - "fileDiffHeader": "diff --git a/monkey/monkey_island/cc/services/config_schema/definitions/system_info_collector_classes.py b/monkey/monkey_island/cc/services/config_schema/definitions/system_info_collector_classes.py\nindex 174133f4..de961fbd 100644\n--- a/monkey/monkey_island/cc/services/config_schema/definitions/system_info_collector_classes.py\n+++ b/monkey/monkey_island/cc/services/config_schema/definitions/system_info_collector_classes.py", - "hunks": [ - { - "swimmHunkMetadata": { - "hunkComments": [] - }, - "hunkDiffLines": [ - "@@ -1,7 +1,6 @@", - " from common.data.system_info_collectors_names import (AWS_COLLECTOR,", - " AZURE_CRED_COLLECTOR,", - " ENVIRONMENT_COLLECTOR,", - "- HOSTNAME_COLLECTOR,", - " MIMIKATZ_COLLECTOR,", - " PROCESS_LIST_COLLECTOR)", - " " - ] - }, - { - "swimmHunkMetadata": { - "hunkComments": [] - }, - "hunkDiffLines": [ - "@@ -40,16 +39,7 @@", - " \"info\": \"If on AWS, collects more information about the AWS instance currently running on.\",", - " \"attack_techniques\": [\"T1082\"]", - " },", - "- {", - "+ # SWIMMER: Collector config goes here. Tip: Hostname collection relates to the T1082 and T1016 techniques.", - "- \"type\": \"string\",", - "- \"enum\": [", - "- HOSTNAME_COLLECTOR", - "- ],", - "- \"title\": \"Hostname collector\",", - "- \"safe\": True,", - "- \"info\": \"Collects machine's hostname.\",", - "- \"attack_techniques\": [\"T1082\", \"T1016\"]", - "- },", - " {", - " \"type\": \"string\",", - " \"enum\": [" - ] - } - ] - }, - "monkey/monkey_island/cc/services/config_schema/monkey.py": { - "diffType": "MODIFIED", - "fileDiffHeader": "diff --git a/monkey/monkey_island/cc/services/config_schema/monkey.py b/monkey/monkey_island/cc/services/config_schema/monkey.py\nindex b47d6a15..1b1962a4 100644\n--- a/monkey/monkey_island/cc/services/config_schema/monkey.py\n+++ b/monkey/monkey_island/cc/services/config_schema/monkey.py", - "hunks": [ - { - "swimmHunkMetadata": { - "hunkComments": [] - }, - "hunkDiffLines": [ - "@@ -1,7 +1,6 @@", - " from common.data.system_info_collectors_names import (AWS_COLLECTOR,", - " AZURE_CRED_COLLECTOR,", - " ENVIRONMENT_COLLECTOR,", - "- HOSTNAME_COLLECTOR,", - " MIMIKATZ_COLLECTOR,", - " PROCESS_LIST_COLLECTOR)", - " " - ] - }, - { - "swimmHunkMetadata": { - "hunkComments": [] - }, - "hunkDiffLines": [ - "@@ -88,7 +87,6 @@", - " \"default\": [", - " ENVIRONMENT_COLLECTOR,", - " AWS_COLLECTOR,", - "- HOSTNAME_COLLECTOR,", - " PROCESS_LIST_COLLECTOR,", - " MIMIKATZ_COLLECTOR,", - " AZURE_CRED_COLLECTOR" - ] - } - ] - }, - "monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/hostname.py": { - "diffType": "MODIFIED", - "fileDiffHeader": "diff --git a/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/hostname.py b/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/hostname.py\nindex e2de4519..04bc3556 100644\n--- a/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/hostname.py\n+++ b/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/hostname.py", - "hunks": [ - { - "swimmHunkMetadata": { - "hunkComments": [] - }, - "hunkDiffLines": [ - "@@ -1,9 +1,9 @@", - " import logging", - " ", - "-from monkey_island.cc.models.monkey import Monkey", - "+# SWIMMER: This will be useful :) monkey_island.cc.models.monkey.Monkey has the useful", - "+# \"get_single_monkey_by_guid\" and \"set_hostname\" methods.", - " ", - " logger = logging.getLogger(__name__)", - " ", - " ", - "-def process_hostname_telemetry(collector_results, monkey_guid):", - "+# SWIMMER: Processing function goes here.", - "- Monkey.get_single_monkey_by_guid(monkey_guid).set_hostname(collector_results[\"hostname\"])" - ] - } - ] - }, - "monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/system_info_telemetry_dispatcher.py": { - "diffType": "MODIFIED", - "fileDiffHeader": "diff --git a/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/system_info_telemetry_dispatcher.py b/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/system_info_telemetry_dispatcher.py\nindex 639a392c..7aa6d3a6 100644\n--- a/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/system_info_telemetry_dispatcher.py\n+++ b/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/system_info_telemetry_dispatcher.py", - "hunks": [ - { - "swimmHunkMetadata": { - "hunkComments": [] - }, - "hunkDiffLines": [ - "@@ -3,14 +3,11 @@", - " ", - " from common.data.system_info_collectors_names import (AWS_COLLECTOR,", - " ENVIRONMENT_COLLECTOR,", - "- HOSTNAME_COLLECTOR,", - " PROCESS_LIST_COLLECTOR)", - " from monkey_island.cc.services.telemetry.processing.system_info_collectors.aws import \\", - " process_aws_telemetry", - " from monkey_island.cc.services.telemetry.processing.system_info_collectors.environment import \\", - " process_environment_telemetry", - "-from monkey_island.cc.services.telemetry.processing.system_info_collectors.hostname import \\", - "- process_hostname_telemetry", - " from monkey_island.cc.services.telemetry.zero_trust_tests.antivirus_existence import \\", - " test_antivirus_existence", - " " - ] - }, - { - "swimmHunkMetadata": { - "hunkComments": [] - }, - "hunkDiffLines": [ - "@@ -19,7 +16,6 @@", - " SYSTEM_INFO_COLLECTOR_TO_TELEMETRY_PROCESSORS = {", - " AWS_COLLECTOR: [process_aws_telemetry],", - " ENVIRONMENT_COLLECTOR: [process_environment_telemetry],", - "- HOSTNAME_COLLECTOR: [process_hostname_telemetry],", - " PROCESS_LIST_COLLECTOR: [test_antivirus_existence]", - " }", - " " - ] - } - ] - } + "task": { + "dod": "Add a system info collector that collects the machine hostname.", + "tests": [], + "hints": [ + "First thing you should do is take a look at a different collector (like EnvironmentCollector) and 100% understand how it runs, how results are relayed back to the server, and how the server processes the data.", + "Try to run \"socket.getfqdn()\".", + "Take a look at SystemInfoCollector - that's the base class you'll need to implement.", + "Make sure you add the new collector to the configuration in all relevant places, including making it ON by default!" + ] }, - "app_version": "0.3.5-1", - "file_version": "1.0.4" + "content": [ + { + "type": "text", + "text": "# What are system info collectors?\n\nWell, the name pretty much explains it. They are Monkey classes which collect various information regarding the victim system, such as Environment, SSH Info, Process List, Netstat and more. \n\n## What should I add? \n\nA system info collector which collects the hostname of the system.\n\n## Test manually\n\nOnce you're done, make sure that your collector:\n* Appears in the Island configuration, and is enabled by default\n* The collector actually runs when executing a Monkey.\n* Results show up in the relevant places:\n * The infection map.\n * The security report.\n * The relevant MITRE techniques.\n\n**There are a lot of hints for this unit - don't be afraid to use them!**" + }, + { + "type": "snippet", + "path": "monkey/common/common_consts/system_info_collectors_names.py", + "comments": [], + "firstLineNumber": 1, + "lines": [ + " AWS_COLLECTOR = \"AwsCollector\"", + "*HOSTNAME_COLLECTOR = \"HostnameCollector\"", + "+# SWIMMER: Collector name goes here.", + " ENVIRONMENT_COLLECTOR = \"EnvironmentCollector\"", + " PROCESS_LIST_COLLECTOR = \"ProcessListCollector\"", + " MIMIKATZ_COLLECTOR = \"MimikatzCollector\"" + ] + }, + { + "type": "snippet", + "path": "monkey/infection_monkey/system_info/collectors/hostname_collector.py", + "comments": [], + "firstLineNumber": 1, + "lines": [ + " import logging", + "*import socket", + "*", + "*from common.common_consts.system_info_collectors_names import HOSTNAME_COLLECTOR", + "*from infection_monkey.system_info.system_info_collector import SystemInfoCollector", + " ", + " logger = logging.getLogger(__name__)", + " ", + "*", + "+# SWIMMER: The collector class goes here.", + "*class HostnameCollector(SystemInfoCollector):", + "* def __init__(self):", + "* super().__init__(name=HOSTNAME_COLLECTOR)", + "*", + "* def collect(self) -> dict:", + "* return {\"hostname\": socket.getfqdn()}" + ] + }, + { + "type": "snippet", + "path": "monkey/monkey_island/cc/services/config_schema/definitions/system_info_collector_classes.py", + "comments": [], + "firstLineNumber": 1, + "lines": [ + " from common.common_consts.system_info_collectors_names import (AWS_COLLECTOR, AZURE_CRED_COLLECTOR,\r", + "* ENVIRONMENT_COLLECTOR, HOSTNAME_COLLECTOR,\r", + " MIMIKATZ_COLLECTOR, PROCESS_LIST_COLLECTOR)\r", + " \r", + " SYSTEM_INFO_COLLECTOR_CLASSES = {\r" + ] + }, + { + "type": "snippet", + "path": "monkey/monkey_island/cc/services/config_schema/definitions/system_info_collector_classes.py", + "comments": [], + "firstLineNumber": 37, + "lines": [ + " \"info\": \"If on AWS, collects more information about the AWS instance currently running on.\",", + " \"attack_techniques\": [\"T1082\"]", + " },", + "* {", + "+ # SWIMMER: Collector config goes here. Tip: Hostname collection relates to the T1082 and T1016 techniques.", + "* \"type\": \"string\",", + "* \"enum\": [", + "* HOSTNAME_COLLECTOR", + "* ],", + "* \"title\": \"Hostname collector\",", + "* \"safe\": True,", + "* \"info\": \"Collects machine's hostname.\",", + "* \"attack_techniques\": [\"T1082\", \"T1016\"]", + "* },", + " {", + " \"type\": \"string\",", + " \"enum\": [" + ] + }, + { + "type": "snippet", + "path": "monkey/monkey_island/cc/services/config_schema/monkey.py", + "comments": [], + "firstLineNumber": 1, + "lines": [ + " from common.common_consts.system_info_collectors_names import (AWS_COLLECTOR, AZURE_CRED_COLLECTOR,", + " ENVIRONMENT_COLLECTOR, HOSTNAME_COLLECTOR,", + " MIMIKATZ_COLLECTOR, PROCESS_LIST_COLLECTOR)", + "* HOSTNAME_COLLECTOR,", + " MONKEY = {", + " \"title\": \"Monkey\",", + " \"type\": \"object\"," + ] + }, + { + "type": "snippet", + "path": "monkey/monkey_island/cc/services/config_schema/monkey.py", + "comments": [], + "firstLineNumber": 85, + "lines": [ + " \"default\": [", + " ENVIRONMENT_COLLECTOR,", + " AWS_COLLECTOR,", + "* HOSTNAME_COLLECTOR,", + " PROCESS_LIST_COLLECTOR,", + " MIMIKATZ_COLLECTOR,", + " AZURE_CRED_COLLECTOR" + ] + }, + { + "type": "snippet", + "path": "monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/hostname.py", + "comments": [], + "firstLineNumber": 1, + "lines": [ + " import logging", + " ", + "*from monkey_island.cc.models.monkey import Monkey", + "+# SWIMMER: This will be useful :) monkey_island.cc.models.monkey.Monkey has the useful", + "+# \"get_single_monkey_by_guid\" and \"set_hostname\" methods.", + " ", + " logger = logging.getLogger(__name__)", + " ", + " ", + "*def process_hostname_telemetry(collector_results, monkey_guid):", + "+# SWIMMER: Processing function goes here.", + "* Monkey.get_single_monkey_by_guid(monkey_guid).set_hostname(collector_results[\"hostname\"])" + ] + }, + { + "type": "snippet", + "path": "monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/system_info_telemetry_dispatcher.py", + "comments": [], + "firstLineNumber": 1, + "lines": [ + " import logging\r", + " import typing\r", + " \r", + "*from common.common_consts.system_info_collectors_names import (AWS_COLLECTOR, ENVIRONMENT_COLLECTOR, HOSTNAME_COLLECTOR,\r", + " PROCESS_LIST_COLLECTOR)\r", + " from monkey_island.cc.services.telemetry.processing.system_info_collectors.aws import process_aws_telemetry\r", + " from monkey_island.cc.services.telemetry.processing.system_info_collectors.environment import \\\r" + ] + }, + { + "type": "snippet", + "path": "monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/system_info_telemetry_dispatcher.py", + "comments": [], + "firstLineNumber": 14, + "lines": [ + " SYSTEM_INFO_COLLECTOR_TO_TELEMETRY_PROCESSORS = {", + " AWS_COLLECTOR: [process_aws_telemetry],", + " ENVIRONMENT_COLLECTOR: [process_environment_telemetry],", + "* HOSTNAME_COLLECTOR: [process_hostname_telemetry],", + " PROCESS_LIST_COLLECTOR: [check_antivirus_existence]", + " }", + " " + ] + }, + { + "type": "snippet", + "lines": [ + " from monkey_island.cc.services.telemetry.processing.system_info_collectors.aws import process_aws_telemetry\r", + " from monkey_island.cc.services.telemetry.processing.system_info_collectors.environment import \\\r", + " process_environment_telemetry\r", + "*from monkey_island.cc.services.telemetry.processing.system_info_collectors.hostname import process_hostname_telemetry\r", + " from monkey_island.cc.services.telemetry.zero_trust_checks.antivirus_existence import check_antivirus_existence\r", + " \r", + " logger = logging.getLogger(__name__)\r" + ], + "firstLineNumber": 6, + "path": "monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/system_info_telemetry_dispatcher.py", + "comments": [] + }, + { + "type": "text", + "text": "System info collectors are useful to get more data for various things, such as ZT tests or MITRE techniques. Take a look at some other techniques!" + } + ], + "file_version": "2.0.0", + "meta": { + "app_version": "0.3.7-0", + "file_blobs": {} + } } \ No newline at end of file From 51abb5dacb4cd62b6b43969defaa3a7e3bc00c8c Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Thu, 18 Feb 2021 16:49:44 +0200 Subject: [PATCH 151/152] Swimm: update unit Add a simple Post Breach action (id: tbxb2cGgUiJQ8Btma0fp). --- .swm/tbxb2cGgUiJQ8Btma0fp.swm | 256 +++++++++++++++------------------- 1 file changed, 115 insertions(+), 141 deletions(-) diff --git a/.swm/tbxb2cGgUiJQ8Btma0fp.swm b/.swm/tbxb2cGgUiJQ8Btma0fp.swm index 78a13c872..255e44a9b 100644 --- a/.swm/tbxb2cGgUiJQ8Btma0fp.swm +++ b/.swm/tbxb2cGgUiJQ8Btma0fp.swm @@ -1,147 +1,121 @@ { "id": "tbxb2cGgUiJQ8Btma0fp", "name": "Add a simple Post Breach action", - "dod": "You should add a new PBA to the Monkey which creates a new user on the machine.", - "description": "Read [our documentation about adding a new PBA](https://www.guardicore.com/infectionmonkey/docs/development/adding-post-breach-actions/).\n\nAfter that we want you to add the BackdoorUser PBA. The commands that add users for Win and Linux can be retrieved from `get_commands_to_add_user` - make sure you see how to use this function correctly. \n\nNote that the PBA should impact the T1136 MITRE technique as well! \n\n# Manual test to confirm\n\n1. Run the Monkey Island\n2. Make sure your new PBA is enabled by default in the config - for this test, disable network scanning, exploiting, and all other PBAs\n3. Run Monkey\n4. See the PBA in the security report\n5, See the PBA in the MITRE report in the relevant technique\n", - "summary": "Take a look at the configuration of the island again - see the \"command to run after breach\" option we offer the user? It's implemented exactly like you did right now but each user can do it for themselves. \n\nHowever, what if the PBA needs to do stuff which is more complex than just running a few commands? In that case... ", - "hunksOrder": [ - "monkey/common/data/post_breach_consts.py_0", - "monkey/infection_monkey/post_breach/actions/add_user.py_0", - "monkey/monkey_island/cc/services/attack/technique_reports/T1136.py_0", - "monkey/monkey_island/cc/services/attack/technique_reports/T1136.py_1", - "monkey/monkey_island/cc/services/config_schema/definitions/post_breach_actions.py_0" - ], - "tests": [], - "hints": [ - "See `ScheduleJobs` PBA for an example of a PBA which only uses shell commands.", - "Make sure to add the PBA to the configuration as well.", - "MITRE ATT&CK technique T1136 articulates that adversaries may create an account to maintain access to victim systems, therefore, the BackdoorUser PBA is relevant to it. Make sure to map this PBA to the MITRE ATT&CK configuration and report." - ], - "play_mode": "all", - "swimmPatch": { - "monkey/common/data/post_breach_consts.py": { - "diffType": "MODIFIED", - "fileDiffHeader": "diff --git a/monkey/common/data/post_breach_consts.py b/monkey/common/data/post_breach_consts.py\nindex 25e6679c..05980288 100644\n--- a/monkey/common/data/post_breach_consts.py\n+++ b/monkey/common/data/post_breach_consts.py", - "hunks": [ - { - "swimmHunkMetadata": { - "hunkComments": [] - }, - "hunkDiffLines": [ - "@@ -1,5 +1,5 @@", - " POST_BREACH_COMMUNICATE_AS_NEW_USER = \"Communicate as new user\"", - "-POST_BREACH_BACKDOOR_USER = \"Backdoor user\"", - "+# Swimmer: PUT THE NEW CONST HERE!", - " POST_BREACH_FILE_EXECUTION = \"File execution\"", - " POST_BREACH_SHELL_STARTUP_FILE_MODIFICATION = \"Modify shell startup file\"", - " POST_BREACH_HIDDEN_FILES = \"Hide files and directories\"" - ] - } - ] - }, - "monkey/infection_monkey/post_breach/actions/add_user.py": { - "diffType": "MODIFIED", - "fileDiffHeader": "diff --git a/monkey/infection_monkey/post_breach/actions/add_user.py b/monkey/infection_monkey/post_breach/actions/add_user.py\nindex 58be89a1..d8476a97 100644\n--- a/monkey/infection_monkey/post_breach/actions/add_user.py\n+++ b/monkey/infection_monkey/post_breach/actions/add_user.py", - "hunks": [ - { - "swimmHunkMetadata": { - "hunkComments": [] - }, - "hunkDiffLines": [ - "@@ -1,15 +1,7 @@", - "-from common.data.post_breach_consts import POST_BREACH_BACKDOOR_USER", - "-from infection_monkey.config import WormConfiguration", - " from infection_monkey.post_breach.pba import PBA", - " from infection_monkey.utils.users import get_commands_to_add_user", - " ", - " ", - " class BackdoorUser(PBA):", - " def __init__(self):", - "- linux_cmds, windows_cmds = get_commands_to_add_user(", - "+ pass # Swimmer: Impl here!", - "- WormConfiguration.user_to_add,", - "- WormConfiguration.remote_user_pass)", - "- super(BackdoorUser, self).__init__(", - "- POST_BREACH_BACKDOOR_USER,", - "- linux_cmd=' '.join(linux_cmds),", - "- windows_cmd=windows_cmds)" - ] - } - ] - }, - "monkey/monkey_island/cc/services/attack/technique_reports/T1136.py": { - "diffType": "MODIFIED", - "fileDiffHeader": "diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1136.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1136.py\nindex 086a1c13..9f23bb8d 100644\n--- a/monkey/monkey_island/cc/services/attack/technique_reports/T1136.py\n+++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1136.py", - "hunks": [ - { - "swimmHunkMetadata": { - "hunkComments": [] - }, - "hunkDiffLines": [ - "@@ -1,5 +1,5 @@", - " from common.data.post_breach_consts import (", - "- POST_BREACH_BACKDOOR_USER, POST_BREACH_COMMUNICATE_AS_NEW_USER)", - "+ POST_BREACH_COMMUNICATE_AS_NEW_USER)", - " from monkey_island.cc.services.attack.technique_reports.pba_technique import \\", - " PostBreachTechnique", - " " - ] - }, - { - "swimmHunkMetadata": { - "hunkComments": [] - }, - "hunkDiffLines": [ - "@@ -11,4 +11,4 @@", - " unscanned_msg = \"Monkey didn't try creating a new user on the network's systems.\"", - " scanned_msg = \"Monkey tried creating a new user on the network's systems, but failed.\"", - " used_msg = \"Monkey created a new user on the network's systems.\"", - "- pba_names = [POST_BREACH_BACKDOOR_USER, POST_BREACH_COMMUNICATE_AS_NEW_USER]", - "+ pba_names = [POST_BREACH_COMMUNICATE_AS_NEW_USER]" - ] - } - ] - }, - "monkey/monkey_island/cc/services/config_schema/definitions/post_breach_actions.py": { - "diffType": "MODIFIED", - "fileDiffHeader": "diff --git a/monkey/monkey_island/cc/services/config_schema/definitions/post_breach_actions.py b/monkey/monkey_island/cc/services/config_schema/definitions/post_breach_actions.py\nindex f1fe0f6f..39ebd33a 100644\n--- a/monkey/monkey_island/cc/services/config_schema/definitions/post_breach_actions.py\n+++ b/monkey/monkey_island/cc/services/config_schema/definitions/post_breach_actions.py", - "hunks": [ - { - "swimmHunkMetadata": { - "hunkComments": [] - }, - "hunkDiffLines": [ - "@@ -4,16 +4,7 @@", - " \"might do after breaching a new machine. Used in ATT&CK and Zero trust reports.\",", - " \"type\": \"string\",", - " \"anyOf\": [", - "- {", - "+ # Swimmer: Add new PBA here to config!", - "- \"type\": \"string\",", - "- \"enum\": [", - "- \"BackdoorUser\"", - "- ],", - "- \"title\": \"Back door user\",", - "- \"safe\": True,", - "- \"info\": \"Attempts to create a new user on the system and delete it afterwards.\",", - "- \"attack_techniques\": [\"T1136\"]", - "- },", - " {", - " \"type\": \"string\",", - " \"enum\": [" - ] - } - ] - } + "task": { + "dod": "You should add a new PBA to the Monkey which creates a new user on the machine.", + "tests": [], + "hints": [ + "See `ScheduleJobs` PBA for an example of a PBA which only uses shell commands.", + "Make sure to add the PBA to the configuration as well.", + "MITRE ATT&CK technique T1136 articulates that adversaries may create an account to maintain access to victim systems, therefore, the BackdoorUser PBA is relevant to it. Make sure to map this PBA to the MITRE ATT&CK configuration and report." + ] }, - "app_version": "0.3.5-1", - "file_version": "1.0.4", - "hunksOrder": [ - "monkey/common/data/post_breach_consts.py_0", - "monkey/infection_monkey/post_breach/actions/add_user.py_0", - "monkey/monkey_island/cc/services/attack/technique_reports/T1136.py_0", - "monkey/monkey_island/cc/services/attack/technique_reports/T1136.py_1", - "monkey/monkey_island/cc/services/config_schema/definitions/post_breach_actions.py_0" + "content": [ + { + "type": "text", + "text": "Read [our documentation about adding a new PBA](https://www.guardicore.com/infectionmonkey/docs/development/adding-post-breach-actions/).\n\nAfter that we want you to add the BackdoorUser PBA. The commands that add users for Win and Linux can be retrieved from `get_commands_to_add_user` - make sure you see how to use this function correctly. \n\nNote that the PBA should impact the T1136 MITRE technique as well! \n\n# Manual test to confirm\n\n1. Run the Monkey Island\n2. Make sure your new PBA is enabled by default in the config - for this test, disable network scanning, exploiting, and all other PBAs\n3. Run Monkey\n4. See the PBA in the security report\n5, See the PBA in the MITRE report in the relevant technique\n" + }, + { + "type": "snippet", + "path": "monkey/common/common_consts/post_breach_consts.py", + "comments": [], + "firstLineNumber": 1, + "lines": [ + " POST_BREACH_COMMUNICATE_AS_NEW_USER = \"Communicate as new user\"", + "*POST_BREACH_BACKDOOR_USER = \"Backdoor user\"", + "+# Swimmer: PUT THE NEW CONST HERE!", + " POST_BREACH_FILE_EXECUTION = \"File execution\"", + " POST_BREACH_SHELL_STARTUP_FILE_MODIFICATION = \"Modify shell startup file\"", + " POST_BREACH_HIDDEN_FILES = \"Hide files and directories\"" + ] + }, + { + "type": "snippet", + "path": "monkey/infection_monkey/post_breach/actions/add_user.py", + "comments": [], + "firstLineNumber": 1, + "lines": [ + "*from common.common_consts.post_breach_consts import POST_BREACH_BACKDOOR_USER", + "*from infection_monkey.config import WormConfiguration", + " from infection_monkey.post_breach.pba import PBA", + " from infection_monkey.utils.users import get_commands_to_add_user", + " ", + " ", + " class BackdoorUser(PBA):", + " def __init__(self):", + "* linux_cmds, windows_cmds = get_commands_to_add_user(", + "+ pass # Swimmer: Impl here!", + "* WormConfiguration.user_to_add,", + "* WormConfiguration.remote_user_pass)", + "* super(BackdoorUser, self).__init__(", + "* POST_BREACH_BACKDOOR_USER,", + "* linux_cmd=' '.join(linux_cmds),", + "* windows_cmd=windows_cmds)" + ] + }, + { + "type": "snippet", + "path": "monkey/monkey_island/cc/services/attack/technique_reports/T1136.py", + "comments": [], + "firstLineNumber": 1, + "lines": [ + "*from common.common_consts.post_breach_consts import POST_BREACH_BACKDOOR_USER, POST_BREACH_COMMUNICATE_AS_NEW_USER\r", + " from monkey_island.cc.services.attack.technique_reports.pba_technique import PostBreachTechnique\r", + " \r", + " __author__ = \"shreyamalviya\"\r" + ] + }, + { + "type": "snippet", + "path": "monkey/monkey_island/cc/services/attack/technique_reports/T1136.py", + "comments": [], + "firstLineNumber": 9, + "lines": [ + " unscanned_msg = \"Monkey didn't try creating a new user on the network's systems.\"", + " scanned_msg = \"Monkey tried creating a new user on the network's systems, but failed.\"", + " used_msg = \"Monkey created a new user on the network's systems.\"", + "* pba_names = [POST_BREACH_BACKDOOR_USER, POST_BREACH_COMMUNICATE_AS_NEW_USER]", + "+ pba_names = [POST_BREACH_COMMUNICATE_AS_NEW_USER]" + ] + }, + { + "type": "snippet", + "path": "monkey/monkey_island/cc/services/config_schema/definitions/post_breach_actions.py", + "comments": [], + "firstLineNumber": 4, + "lines": [ + " \"might do after breaching a new machine. Used in ATT&CK and Zero trust reports.\",", + " \"type\": \"string\",", + " \"anyOf\": [", + "* {", + "+ # Swimmer: Add new PBA here to config!", + "* \"type\": \"string\",", + "* \"enum\": [", + "* \"BackdoorUser\"", + "* ],", + "* \"title\": \"Back door user\",", + "* \"safe\": True,", + "* \"info\": \"Attempts to create a new user on the system and delete it afterwards.\",", + "* \"attack_techniques\": [\"T1136\"]", + "* },", + " {", + " \"type\": \"string\",", + " \"enum\": [" + ] + }, + { + "type": "text", + "text": "Take a look at the configuration of the island again - see the \"command to run after breach\" option we offer the user? It's implemented exactly like you did right now but each user can do it for themselves. \n\nHowever, what if the PBA needs to do stuff which is more complex than just running a few commands? In that case... " + } ], - "last_commit_sha_for_swimm_patch": "9d9e8168fb2c23367b9947273aa1a041687b3e2e" + "file_version": "2.0.0", + "meta": { + "app_version": "0.3.7-0", + "file_blobs": { + "monkey/common/common_consts/post_breach_consts.py": "25e6679cb1623aae1a732deb05cc011a452743e3", + "monkey/infection_monkey/post_breach/actions/add_user.py": "a85845840d9cb37529ad367e159cd9001929e759", + "monkey/monkey_island/cc/services/attack/technique_reports/T1136.py": "d9d86e08ea4aeb0a6bee3f483e4fea50ee6cd200", + "monkey/monkey_island/cc/services/config_schema/definitions/post_breach_actions.py": "857e80da477ab31dbc00ed0a3f1cd49b69b505fa" + } + } } \ No newline at end of file From 522000d1698219e694039a225a199898f1636f90 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Thu, 18 Feb 2021 16:51:52 +0200 Subject: [PATCH 152/152] Swimm: update unit Define what your new PBA does (id: xYkxB76pK0peJj2tSxBJ). --- .swm/xYkxB76pK0peJj2tSxBJ.swm | 64 +++++++++++++++++++++-------------- 1 file changed, 39 insertions(+), 25 deletions(-) diff --git a/.swm/xYkxB76pK0peJj2tSxBJ.swm b/.swm/xYkxB76pK0peJj2tSxBJ.swm index cf34817e6..3dd341f8e 100644 --- a/.swm/xYkxB76pK0peJj2tSxBJ.swm +++ b/.swm/xYkxB76pK0peJj2tSxBJ.swm @@ -1,30 +1,44 @@ { "id": "xYkxB76pK0peJj2tSxBJ", "name": "Define what your new PBA does", - "dod": "WW91JTIwc2hvdWxkJTIwYWRkJTIwYSUyMG5ldyUyMFBCQSUyMGNvbnN0JTIwdGhhdCUyMGRlZmluZXMlMjB3aGF0JTIwdGhlJTIwUEJBJTIwZG9lcy4=", - "description": "VGhlJTIwbmFtZSUyMG9mJTIweW91ciUyMG5ldyUyMFBCQSUyMCh3aGljaCUyMGNyZWF0ZXMlMjBzY2hlZHVsZWQlMjBqb2JzJTIwb24lMjB0aGUlMjBtYWNoaW5lKSUyMHdpbGwlMjBiZSUyMHVzZWQlMjBpbiUyMGElMjBmZXclMjBwbGFjZXMlMkMlMjBpbmNsdWRpbmclMjB0aGUlMjByZXBvcnQuJTIwJTNDYnIlM0UlM0NiciUzRSUwQVlvdSUyMHNob3VsZCUyMGJyaWVmbHklMjBkZWZpbmUlMjB3aGF0JTIweW91ciUyMFBCQSUyMGRvZXMlMjBpbiUyMGElMjBjb25zdGFudCUyMHZhcmlhYmxlJTJDJTIwc3VjaCUyMHRoYXQlMjBpdCUyMGNhbiUyMGJlJTIwdXNlZCUyMGJ5JTIwYm90aCUyMHRoZSUyME1vbmtleSUyMGFuZCUyMHRoZSUyME1vbmtleSUyMElzbGFuZC4lMEElMEElMjMlMjMlMjBNYW51YWwlMjB0ZXN0JTIwJTIwJTBBT25jZSUyMHlvdSUyMHRoaW5rJTIweW91J3JlJTIwZG9uZS4uLiUwQS0lMjBSdW4lMjB0aGUlMjBNb25rZXklMjBJc2xhbmQlMEEtJTIwTWFrZSUyMHN1cmUlMjB0aGUlMjAlMjJKb2IlMjBzY2hlZHVsaW5nJTIyJTIwUEJBJTIwaXMlMjBlbmFibGVkJTIwaW4lMjB0aGUlMjAlMjJNb25rZXklMjIlMjB0YWIlMjBpbiUyMHRoZSUyMGNvbmZpZ3VyYXRpb24lMjAlRTIlODAlOTQlMjBmb3IlMjB0aGlzJTIwdGVzdCUyQyUyMGRpc2FibGUlMjBuZXR3b3JrJTIwc2Nhbm5pbmclMkMlMjBleHBsb2l0aW5nJTJDJTIwYW5kJTIwYWxsJTIwb3RoZXIlMjBQQkFzJTBBLSUyMFJ1biUyMHRoZSUyME1vbmtleSUwQS0lMjBDaGVjayUyMHRoZSUyMFBCQSUyMHNlY3Rpb24lMjBpbiUyMHRoZSUyMFNlY3VyaXR5JTIwcmVwb3J0JTIwZm9yJTIwdGhlJTIwbmFtZSUyMHlvdSUyMGdhdmUlMjB0byUyMHRoZSUyMG5ldyUyMFBCQSUyMCUyMCUwQSUwQSUzQ2ltZyUyMHNyYyUzRCUyMmh0dHBzJTNBJTJGJTJGZmlyZWJhc2VzdG9yYWdlLmdvb2dsZWFwaXMuY29tJTJGdjAlMkZiJTJGc3dpbW1pby1jb250ZW50JTJGbyUyRnJlcG9zaXRvcmllcyUyNTJGNk5sYjk5TnRZNUZjM2JTZDhzdUglMjUyRmltZyUyNTJGZjBlNTNlNmMtOWRiZS00MWQ4LTk0NTQtMmI1NzYxYzNmNTNhLnBuZyUzRmFsdCUzRG1lZGlhJTI2dG9rZW4lM0QyMWFhNGJiOC03ZWJlLTRkYWItYTczOS1jNzdlMDU5MTQ0ZGQlMjIlMjBoZWlnaHQlM0Q0MDAlM0U=", - "summary": "LSUyMFRoZSUyMG5hbWUlMjBkZWZpbmVkJTIwaGVyZSUyMGZvciUyMHlvdXIlMjBQQkElMjBjYW4lMjBiZSUyMHNlZW4lMjBvbiUyMHRoZSUyME1vbmtleSUyMElzbGFuZCUyMGluJTIwdGhlJTIwUEJBJTIwc2VjdGlvbiUyMGluJTIwdGhlJTIwU2VjdXJpdHklMjByZXBvcnQuJTBBLSUyMFRoZSUyMHJlc3VsdHMlMjBvZiUyMGVhY2glMjBQQkElMjBzdG9yZWQlMjBpbiUyMHRoZSUyMHRlbGVtZXRyeSUyMGFyZSUyMGFsc28lMjBpZGVudGlmaWVkJTIwYnklMjB0aGUlMjBzdHJpbmclMjBkZWZpbmVkJTIwaGVyZSUyMGZvciUyMHRoYXQlMjBQQkEu", - "diff": "ZGlmZiUyMC0tZ2l0JTIwYSUyRm1vbmtleSUyRmNvbW1vbiUyRmRhdGElMkZwb3N0X2JyZWFjaF9jb25zdHMucHklMjBiJTJGbW9ua2V5JTJGY29tbW9uJTJGZGF0YSUyRnBvc3RfYnJlYWNoX2NvbnN0cy5weSUwQWluZGV4JTIwMjVlNjY3OWMuLjQ2ZDgwMmRlJTIwMTAwNjQ0JTBBLS0tJTIwYSUyRm1vbmtleSUyRmNvbW1vbiUyRmRhdGElMkZwb3N0X2JyZWFjaF9jb25zdHMucHklMEElMkIlMkIlMkIlMjBiJTJGbW9ua2V5JTJGY29tbW9uJTJGZGF0YSUyRnBvc3RfYnJlYWNoX2NvbnN0cy5weSUwQSU0MCU0MCUyMC01JTJDNyUyMCUyQjUlMkM3JTIwJTQwJTQwJTIwUE9TVF9CUkVBQ0hfU0hFTExfU1RBUlRVUF9GSUxFX01PRElGSUNBVElPTiUyMCUzRCUyMCUyMk1vZGlmeSUyMHNoZWxsJTIwc3RhcnR1cCUyMGZpbGUlMjIlMEElMjBQT1NUX0JSRUFDSF9ISURERU5fRklMRVMlMjAlM0QlMjAlMjJIaWRlJTIwZmlsZXMlMjBhbmQlMjBkaXJlY3RvcmllcyUyMiUwQSUyMFBPU1RfQlJFQUNIX1RSQVBfQ09NTUFORCUyMCUzRCUyMCUyMkV4ZWN1dGUlMjBjb21tYW5kJTIwd2hlbiUyMGElMjBwYXJ0aWN1bGFyJTIwc2lnbmFsJTIwaXMlMjByZWNlaXZlZCUyMiUwQSUyMFBPU1RfQlJFQUNIX1NFVFVJRF9TRVRHSUQlMjAlM0QlMjAlMjJTZXR1aWQlMjBhbmQlMjBTZXRnaWQlMjIlMEEtUE9TVF9CUkVBQ0hfSk9CX1NDSEVEVUxJTkclMjAlM0QlMjAlMjJTY2hlZHVsZSUyMGpvYnMlMjIlMEElMkIlMjMlMjBTd2ltbWVyJTNBJTIwUFVUJTIwVEhFJTIwTkVXJTIwQ09OU1QlMjBIRVJFISUwQSUyMFBPU1RfQlJFQUNIX1RJTUVTVE9NUElORyUyMCUzRCUyMCUyMk1vZGlmeSUyMGZpbGVzJyUyMHRpbWVzdGFtcHMlMjIlMEElMjBQT1NUX0JSRUFDSF9TSUdORURfU0NSSVBUX1BST1hZX0VYRUMlMjAlM0QlMjAlMjJTaWduZWQlMjBzY3JpcHQlMjBwcm94eSUyMGV4ZWN1dGlvbiUyMiUwQSUyMFBPU1RfQlJFQUNIX0FDQ09VTlRfRElTQ09WRVJZJTIwJTNEJTIwJTIyQWNjb3VudCUyMGRpc2NvdmVyeSUyMiUwQQ==", - "tests": [], - "hints": [ - "See the `Timestomping` PBA. How is the name of the PBA set?" - ], - "files": { - "monkey/common/data/post_breach_consts.py": { - "index": [ - "25e6679c..46d802de", - "100644" - ], - "fileA": "monkey/common/data/post_breach_consts.py", - "fileB": "monkey/common/data/post_breach_consts.py", - "status": "MODIFIED", - "numLineDeletions": 1, - "numLineAdditions": 1, - "hunkContainers": [ - "JTdCJTIyaHVuayUyMiUzQSU3QiUyMmhlYWRlciUyMiUzQSUyMiU0MCU0MCUyMC01JTJDNyUyMCUyQjUlMkM3JTIwJTQwJTQwJTIwUE9TVF9CUkVBQ0hfU0hFTExfU1RBUlRVUF9GSUxFX01PRElGSUNBVElPTiUyMCUzRCUyMCU1QyUyMk1vZGlmeSUyMHNoZWxsJTIwc3RhcnR1cCUyMGZpbGUlNUMlMjIlMjIlMkMlMjJjaGFuZ2VzJTIyJTNBJTVCJTdCJTIydHlwZSUyMiUzQSUyMmNvbnRleHQlMjIlMkMlMjJkYXRhJTIyJTNBJTIyJTIwUE9TVF9CUkVBQ0hfSElEREVOX0ZJTEVTJTIwJTNEJTIwJTVDJTIySGlkZSUyMGZpbGVzJTIwYW5kJTIwZGlyZWN0b3JpZXMlNUMlMjIlMjIlMkMlMjJsaW5lTnVtYmVycyUyMiUzQSU3QiUyMmElMjIlM0E1JTJDJTIyYiUyMiUzQTUlN0QlN0QlMkMlN0IlMjJ0eXBlJTIyJTNBJTIyY29udGV4dCUyMiUyQyUyMmRhdGElMjIlM0ElMjIlMjBQT1NUX0JSRUFDSF9UUkFQX0NPTU1BTkQlMjAlM0QlMjAlNUMlMjJFeGVjdXRlJTIwY29tbWFuZCUyMHdoZW4lMjBhJTIwcGFydGljdWxhciUyMHNpZ25hbCUyMGlzJTIwcmVjZWl2ZWQlNUMlMjIlMjIlMkMlMjJsaW5lTnVtYmVycyUyMiUzQSU3QiUyMmElMjIlM0E2JTJDJTIyYiUyMiUzQTYlN0QlN0QlMkMlN0IlMjJ0eXBlJTIyJTNBJTIyY29udGV4dCUyMiUyQyUyMmRhdGElMjIlM0ElMjIlMjBQT1NUX0JSRUFDSF9TRVRVSURfU0VUR0lEJTIwJTNEJTIwJTVDJTIyU2V0dWlkJTIwYW5kJTIwU2V0Z2lkJTVDJTIyJTIyJTJDJTIybGluZU51bWJlcnMlMjIlM0ElN0IlMjJhJTIyJTNBNyUyQyUyMmIlMjIlM0E3JTdEJTdEJTJDJTdCJTIydHlwZSUyMiUzQSUyMmRlbCUyMiUyQyUyMm1hcmslMjIlM0ElMjItJTIyJTJDJTIyZGF0YSUyMiUzQSUyMlBPU1RfQlJFQUNIX0pPQl9TQ0hFRFVMSU5HJTIwJTNEJTIwJTVDJTIyU2NoZWR1bGUlMjBqb2JzJTVDJTIyJTIyJTJDJTIybGluZU51bWJlcnMlMjIlM0ElN0IlMjJhJTIyJTNBOCU3RCU3RCUyQyU3QiUyMnR5cGUlMjIlM0ElMjJhZGQlMjIlMkMlMjJtYXJrJTIyJTNBJTIyJTJCJTIyJTJDJTIyZGF0YSUyMiUzQSUyMiUyMyUyMFN3aW1tZXIlM0ElMjBQVVQlMjBUSEUlMjBORVclMjBDT05TVCUyMEhFUkUhJTIyJTJDJTIybGluZU51bWJlcnMlMjIlM0ElN0IlMjJiJTIyJTNBOCU3RCU3RCUyQyU3QiUyMnR5cGUlMjIlM0ElMjJjb250ZXh0JTIyJTJDJTIyZGF0YSUyMiUzQSUyMiUyMFBPU1RfQlJFQUNIX1RJTUVTVE9NUElORyUyMCUzRCUyMCU1QyUyMk1vZGlmeSUyMGZpbGVzJyUyMHRpbWVzdGFtcHMlNUMlMjIlMjIlMkMlMjJsaW5lTnVtYmVycyUyMiUzQSU3QiUyMmElMjIlM0E5JTJDJTIyYiUyMiUzQTklN0QlN0QlMkMlN0IlMjJ0eXBlJTIyJTNBJTIyY29udGV4dCUyMiUyQyUyMmRhdGElMjIlM0ElMjIlMjBQT1NUX0JSRUFDSF9TSUdORURfU0NSSVBUX1BST1hZX0VYRUMlMjAlM0QlMjAlNUMlMjJTaWduZWQlMjBzY3JpcHQlMjBwcm94eSUyMGV4ZWN1dGlvbiU1QyUyMiUyMiUyQyUyMmxpbmVOdW1iZXJzJTIyJTNBJTdCJTIyYSUyMiUzQTEwJTJDJTIyYiUyMiUzQTEwJTdEJTdEJTJDJTdCJTIydHlwZSUyMiUzQSUyMmNvbnRleHQlMjIlMkMlMjJkYXRhJTIyJTNBJTIyJTIwUE9TVF9CUkVBQ0hfQUNDT1VOVF9ESVNDT1ZFUlklMjAlM0QlMjAlNUMlMjJBY2NvdW50JTIwZGlzY292ZXJ5JTVDJTIyJTIyJTJDJTIybGluZU51bWJlcnMlMjIlM0ElN0IlMjJhJTIyJTNBMTElMkMlMjJiJTIyJTNBMTElN0QlN0QlNUQlMkMlMjJsaW5lTnVtYmVycyUyMiUzQSU3QiUyMmElMjIlM0ElN0IlMjJzdGFydExpbmUlMjIlM0E1JTJDJTIybGluZXNDb3VudCUyMiUzQTclN0QlMkMlMjJiJTIyJTNBJTdCJTIyc3RhcnRMaW5lJTIyJTNBNSUyQyUyMmxpbmVzQ291bnQlMjIlM0E3JTdEJTdEJTdEJTdE" - ] - } + "task": { + "dod": "You should add a new PBA const that defines what the PBA does.", + "tests": [], + "hints": [ + "See the `Timestomping` PBA. How is the name of the PBA set?" + ] }, - "app_version": "0.1.90", - "file_version": "1.0.2" + "content": [ + { + "type": "text", + "text": "The name of your new PBA (which creates scheduled jobs on the machine) will be used in a few places, including the report.

\nYou should briefly define what your PBA does in a constant variable, such that it can be used by both the Monkey and the Monkey Island.\n\n## Manual test \nOnce you think you're done...\n- Run the Monkey Island\n- Make sure the \"Job scheduling\" PBA is enabled in the \"Monkey\" tab in the configuration — for this test, disable network scanning, exploiting, and all other PBAs\n- Run the Monkey\n- Check the PBA section in the Security report for the name you gave to the new PBA \n\n" + }, + { + "firstLineNumber": 5, + "path": "monkey/common/common_consts/post_breach_consts.py", + "type": "snippet", + "lines": [ + " POST_BREACH_HIDDEN_FILES = \"Hide files and directories\"", + " POST_BREACH_TRAP_COMMAND = \"Execute command when a particular signal is received\"", + " POST_BREACH_SETUID_SETGID = \"Setuid and Setgid\"", + "*POST_BREACH_JOB_SCHEDULING = \"Schedule jobs\"", + "+# Swimmer: PUT THE NEW CONST HERE!", + " POST_BREACH_TIMESTOMPING = \"Modify files' timestamps\"", + " POST_BREACH_SIGNED_SCRIPT_PROXY_EXEC = \"Signed script proxy execution\"", + " POST_BREACH_ACCOUNT_DISCOVERY = \"Account discovery\"" + ], + "comments": [] + }, + { + "type": "text", + "text": "- The name defined here for your PBA can be seen on the Monkey Island in the PBA section in the Security report.\n- The results of each PBA stored in the telemetry are also identified by the string defined here for that PBA." + } + ], + "file_version": "2.0.0", + "meta": { + "app_version": "0.3.7-0", + "file_blobs": { + "monkey/common/common_consts/post_breach_consts.py": "25e6679cb1623aae1a732deb05cc011a452743e3" + } + } } \ No newline at end of file