diff --git a/.gitmodules b/.gitmodules index 714716a9b..b898f160a 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,7 +1,9 @@ - [submodule "monkey/monkey_island/cc/services/attack/attack_data"] path = monkey/monkey_island/cc/services/attack/attack_data url = https://github.com/guardicore/cti [submodule "docs/themes/learn"] path = docs/themes/learn url = https://github.com/guardicode/hugo-theme-learn.git +[submodule "monkey/infection_monkey/system_info/collectors/scoutsuite"] + path = monkey/common/cloud/scoutsuite + url = https://github.com/guardicode/ScoutSuite.git 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 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 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 diff --git a/.travis.yml b/.travis.yml index 26482dcd5..29b455ef7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -27,6 +27,7 @@ install: - pip install flake8 pytest pytest-cov dlint isort # for next stages - pip install coverage # for code coverage - pip install -r monkey/infection_monkey/requirements.txt # for unit tests +- pip install -r monkey/common/cloud/scoutsuite/requirements.txt - pip install pipdeptree # Fail builds on possible conflicting dependencies. - pipdeptree --warn fail @@ -52,15 +53,10 @@ 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 -- flake8 ./monkey --config=./ci_scripts/flake8_syntax_check.ini +## Check syntax errors and fail the build if any are found. +- flake8 ./monkey --exclude=monkey/common/cloud/scoutsuite --config=./ci_scripts/flake8_syntax_check.ini ## 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. diff --git a/deployment_scripts/config.ps1 b/deployment_scripts/config.ps1 index 21b9beca6..d140eb711 100644 --- a/deployment_scripts/config.ps1 +++ b/deployment_scripts/config.ps1 @@ -30,6 +30,7 @@ $TRACEROUTE_32_BINARY_URL = $MONKEY_DOWNLOAD_URL + "traceroute32" # Other directories and paths ( most likely you dont need to configure) $MONKEY_ISLAND_DIR = Join-Path "\monkey" -ChildPath "monkey_island" $MONKEY_DIR = Join-Path "\monkey" -ChildPath "infection_monkey" +$SCOUTSUITE_DIR = Join-Path "\monkey" "common" "cloud" "scoutsuite" $SAMBA_BINARIES_DIR = Join-Path -Path $MONKEY_DIR -ChildPath "\bin" $TEMP_PYTHON_INSTALLER = ".\python.exe" $TEMP_MONGODB_ZIP = ".\mongodb.zip" diff --git a/deployment_scripts/deploy_linux.sh b/deployment_scripts/deploy_linux.sh index 728e2f52d..718773fad 100755 --- a/deployment_scripts/deploy_linux.sh +++ b/deployment_scripts/deploy_linux.sh @@ -66,6 +66,7 @@ MONGO_PATH="$ISLAND_PATH/bin/mongodb" ISLAND_BINARIES_PATH="$ISLAND_PATH/cc/binaries" INFECTION_MONKEY_DIR="$monkey_home/monkey/infection_monkey" MONKEY_BIN_DIR="$INFECTION_MONKEY_DIR/bin" +SCOUTSUITE_DIR="$monkey_home/monkey/common/cloud/scoutsuite" if ! has_sudo; then log_message "You need root permissions for some of this script operations. \ @@ -141,6 +142,9 @@ sudo apt-get install -y libffi-dev upx libssl-dev libc++1 requirements_monkey="$INFECTION_MONKEY_DIR/requirements.txt" ${python_cmd} -m pip install -r "${requirements_monkey}" --user --upgrade || handle_error +log_message "Installing ScoutSuite requirements" +requirements_scoutsuite="$SCOUTSUITE_DIR/requirements.txt" +${python_cmd} -m pip install -r "${requirements_scoutsuite}" --user --upgrade || handle_error agents=${3:-true} # Download binaries diff --git a/deployment_scripts/deploy_windows.ps1 b/deployment_scripts/deploy_windows.ps1 index 3a57e9dcb..85a3f0698 100644 --- a/deployment_scripts/deploy_windows.ps1 +++ b/deployment_scripts/deploy_windows.ps1 @@ -115,6 +115,9 @@ function Deploy-Windows([String] $monkey_home = (Get-Item -Path ".\").FullName, "Installing python packages for monkey" $monkeyRequirements = Join-Path -Path $monkey_home -ChildPath $MONKEY_DIR | Join-Path -ChildPath "\requirements.txt" & python -m pip install --user -r $monkeyRequirements + "Installing python packages for ScoutSuite" + $scoutsuiteRequirements = Join-Path -Path $monkey_home -ChildPath $SCOUTSUITE_DIR | Join-Path -ChildPath "\requirements.txt" + & python -m pip install --user -r $scoutsuiteRequirements $user_python_dir = cmd.exe /c 'py -m site --user-site' $user_python_dir = Join-Path (Split-Path $user_python_dir) -ChildPath "\Scripts" 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", diff --git a/monkey/common/cloud/scoutsuite b/monkey/common/cloud/scoutsuite new file mode 160000 index 000000000..e5dd01dab --- /dev/null +++ b/monkey/common/cloud/scoutsuite @@ -0,0 +1 @@ +Subproject commit e5dd01dab08ed8d887736cfcee068c1b8895d4a1 diff --git a/monkey/common/cloud/scoutsuite_consts.py b/monkey/common/cloud/scoutsuite_consts.py new file mode 100644 index 000000000..4db862a4a --- /dev/null +++ b/monkey/common/cloud/scoutsuite_consts.py @@ -0,0 +1,9 @@ +from enum import Enum + + +class CloudProviders(Enum): + AWS = 'aws' + AZURE = 'azure' + GCP = 'gcp' + ALIBABA = 'aliyun' + ORACLE = 'oci' 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 84% rename from monkey/common/data/system_info_collectors_names.py rename to monkey/common/common_consts/system_info_collectors_names.py index 175a054e1..c93cb2537 100644 --- a/monkey/common/data/system_info_collectors_names.py +++ b/monkey/common/common_consts/system_info_collectors_names.py @@ -4,3 +4,4 @@ ENVIRONMENT_COLLECTOR = "EnvironmentCollector" PROCESS_LIST_COLLECTOR = "ProcessListCollector" MIMIKATZ_COLLECTOR = "MimikatzCollector" AZURE_CRED_COLLECTOR = "AzureCollector" +SCOUTSUITE_COLLECTOR = "ScoutSuiteCollector" diff --git a/monkey/common/common_consts/telem_categories.py b/monkey/common/common_consts/telem_categories.py new file mode 100644 index 000000000..70066d290 --- /dev/null +++ b/monkey/common/common_consts/telem_categories.py @@ -0,0 +1,10 @@ +class TelemCategoryEnum: + EXPLOIT = 'exploit' + POST_BREACH = 'post_breach' + SCAN = 'scan' + SCOUTSUITE = 'scoutsuite' + STATE = 'state' + SYSTEM_INFO = 'system_info' + TRACE = 'trace' + TUNNEL = 'tunnel' + ATTACK = 'attack' 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 64% rename from monkey/common/data/zero_trust_consts.py rename to monkey/common/common_consts/zero_trust_consts.py index 8d55bc320..f0a624bdf 100644 --- a/monkey/common/data/zero_trust_consts.py +++ b/monkey/common/common_consts/zero_trust_consts.py @@ -31,6 +31,14 @@ 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" +TEST_SCOUTSUITE_UNENCRYPTED_DATA = "scoutsuite_unencrypted_data" +TEST_SCOUTSUITE_DATA_LOSS_PREVENTION = "scoutsuite_data_loss_prevention" +TEST_SCOUTSUITE_SECURE_AUTHENTICATION = "scoutsuite_secure_authentication" +TEST_SCOUTSUITE_RESTRICTIVE_POLICIES = "scoutsuite_unrestrictive_policies" +TEST_SCOUTSUITE_LOGGING = "scoutsuite_logging" +TEST_SCOUTSUITE_SERVICE_SECURITY = "scoutsuite_service_security" + TESTS = ( TEST_SEGMENTATION, TEST_MALICIOUS_ACTIVITY_TIMELINE, @@ -40,25 +48,38 @@ 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, + TEST_SCOUTSUITE_UNENCRYPTED_DATA, + TEST_SCOUTSUITE_DATA_LOSS_PREVENTION, + TEST_SCOUTSUITE_SECURE_AUTHENTICATION, + TEST_SCOUTSUITE_RESTRICTIVE_POLICIES, + TEST_SCOUTSUITE_LOGGING, + TEST_SCOUTSUITE_SERVICE_SECURITY ) -PRINCIPLE_DATA_TRANSIT = "data_transit" +PRINCIPLE_DATA_CONFIDENTIALITY = "data_transit" PRINCIPLE_ENDPOINT_SECURITY = "endpoint_security" PRINCIPLE_USER_BEHAVIOUR = "user_behaviour" PRINCIPLE_ANALYZE_NETWORK_TRAFFIC = "analyze_network_traffic" PRINCIPLE_SEGMENTATION = "segmentation" PRINCIPLE_RESTRICTIVE_NETWORK_POLICIES = "network_policies" PRINCIPLE_USERS_MAC_POLICIES = "users_mac_policies" +PRINCIPLE_DISASTER_RECOVERY = "data_backup" +PRINCIPLE_SECURE_AUTHENTICATION = "secure_authentication" +PRINCIPLE_MONITORING_AND_LOGGING = "monitoring_and_logging" PRINCIPLES = { PRINCIPLE_SEGMENTATION: "Apply segmentation and micro-segmentation inside your network.", PRINCIPLE_ANALYZE_NETWORK_TRAFFIC: "Analyze network traffic for malicious activity.", PRINCIPLE_USER_BEHAVIOUR: "Adopt security user behavior analytics.", PRINCIPLE_ENDPOINT_SECURITY: "Use anti-virus and other traditional endpoint security solutions.", - PRINCIPLE_DATA_TRANSIT: "Secure data at transit by encrypting it.", + PRINCIPLE_DATA_CONFIDENTIALITY: "Ensure data's confidentiality by encrypting it.", PRINCIPLE_RESTRICTIVE_NETWORK_POLICIES: "Configure network policies to be as restrictive as possible.", PRINCIPLE_USERS_MAC_POLICIES: "Users' permissions to the network and to resources should be MAC (Mandatory " "Access Control) only.", + PRINCIPLE_DISASTER_RECOVERY: "Ensure data and infrastructure backups for disaster recovery scenarios.", + PRINCIPLE_SECURE_AUTHENTICATION: "Ensure secure authentication process's.", + PRINCIPLE_MONITORING_AND_LOGGING: "Ensure monitoring and logging in network resources." } POSSIBLE_STATUSES_KEY = "possible_statuses" @@ -129,7 +150,7 @@ TESTS_MAP = { STATUS_PASSED: "Monkey didn't find open ElasticSearch instances. If you have such instances, look for alerts " "that indicate attempts to access them. " }, - PRINCIPLE_KEY: PRINCIPLE_DATA_TRANSIT, + PRINCIPLE_KEY: PRINCIPLE_DATA_CONFIDENTIALITY, PILLARS_KEY: [DATA], POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED] }, @@ -140,7 +161,7 @@ TESTS_MAP = { STATUS_PASSED: "Monkey didn't find open HTTP servers. If you have such servers, look for alerts that indicate " "attempts to access them. " }, - PRINCIPLE_KEY: PRINCIPLE_DATA_TRANSIT, + PRINCIPLE_KEY: PRINCIPLE_DATA_CONFIDENTIALITY, PILLARS_KEY: [DATA], POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED] }, @@ -165,6 +186,76 @@ 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] + }, + TEST_SCOUTSUITE_UNENCRYPTED_DATA: { + TEST_EXPLANATION_KEY: "ScoutSuite searched for resources containing unencrypted data.", + FINDING_EXPLANATION_BY_STATUS_KEY: { + STATUS_FAILED: "ScoutSuite found resources with unencrypted data.", + STATUS_PASSED: "ScoutSuite found no resources with unencrypted data." + }, + PRINCIPLE_KEY: PRINCIPLE_DATA_CONFIDENTIALITY, + PILLARS_KEY: [DATA], + POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED] + }, + TEST_SCOUTSUITE_DATA_LOSS_PREVENTION: { + TEST_EXPLANATION_KEY: "ScoutSuite searched for resources which are not protected against data loss.", + FINDING_EXPLANATION_BY_STATUS_KEY: { + STATUS_FAILED: "ScoutSuite found resources not protected against data loss.", + STATUS_PASSED: "ScoutSuite found that all resources are secured against data loss." + }, + PRINCIPLE_KEY: PRINCIPLE_DISASTER_RECOVERY, + PILLARS_KEY: [DATA], + POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED] + }, + TEST_SCOUTSUITE_SECURE_AUTHENTICATION: { + TEST_EXPLANATION_KEY: "ScoutSuite searched for issues related to users' authentication.", + FINDING_EXPLANATION_BY_STATUS_KEY: { + STATUS_FAILED: "ScoutSuite found issues related to users' authentication.", + STATUS_PASSED: "ScoutSuite found no issues related to users' authentication." + }, + PRINCIPLE_KEY: PRINCIPLE_SECURE_AUTHENTICATION, + PILLARS_KEY: [PEOPLE, WORKLOADS], + POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED] + }, + TEST_SCOUTSUITE_RESTRICTIVE_POLICIES: { + TEST_EXPLANATION_KEY: "ScoutSuite searched for permissive user access policies.", + FINDING_EXPLANATION_BY_STATUS_KEY: { + STATUS_FAILED: "ScoutSuite found permissive user access policies.", + STATUS_PASSED: "ScoutSuite found no issues related to user access policies." + }, + PRINCIPLE_KEY: PRINCIPLE_USERS_MAC_POLICIES, + PILLARS_KEY: [PEOPLE, WORKLOADS], + POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED] + }, + TEST_SCOUTSUITE_LOGGING: { + TEST_EXPLANATION_KEY: "ScoutSuite searched for issues, related to logging.", + FINDING_EXPLANATION_BY_STATUS_KEY: { + STATUS_FAILED: "ScoutSuite found logging issues.", + STATUS_PASSED: "ScoutSuite found no logging issues." + }, + PRINCIPLE_KEY: PRINCIPLE_MONITORING_AND_LOGGING, + PILLARS_KEY: [AUTOMATION_ORCHESTRATION, VISIBILITY_ANALYTICS], + POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED] + }, + TEST_SCOUTSUITE_SERVICE_SECURITY: { + TEST_EXPLANATION_KEY: "ScoutSuite searched for service security issues.", + FINDING_EXPLANATION_BY_STATUS_KEY: { + STATUS_FAILED: "ScoutSuite found service security issues.", + STATUS_PASSED: "ScoutSuite found no service security issues." + }, + PRINCIPLE_KEY: PRINCIPLE_MONITORING_AND_LOGGING, + PILLARS_KEY: [DEVICES, NETWORKS], + POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED] + } } EVENT_TYPE_MONKEY_NETWORK = "monkey_network" diff --git a/monkey/common/network/test_network_utils.py b/monkey/common/network/test_network_utils.py index 3ee696a93..396bc1c0a 100644 --- a/monkey/common/network/test_network_utils.py +++ b/monkey/common/network/test_network_utils.py @@ -1,7 +1,6 @@ from unittest import TestCase -from common.network.network_utils import (get_host_from_network_location, - remove_port) +from common.network.network_utils import get_host_from_network_location, remove_port class TestNetworkUtils(TestCase): @@ -11,7 +10,7 @@ class TestNetworkUtils(TestCase): assert get_host_from_network_location("127.0.0.1") == "127.0.0.1" assert get_host_from_network_location("www.google.com:8080") == "www.google.com" assert get_host_from_network_location("user:password@host:8080") == "host" - + def test_remove_port_from_url(self): assert remove_port('https://google.com:80') == 'https://google.com' assert remove_port('https://8.8.8.8:65336') == 'https://8.8.8.8' 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/common/utils/exceptions.py b/monkey/common/utils/exceptions.py index 6b36402a0..2c7121942 100644 --- a/monkey/common/utils/exceptions.py +++ b/monkey/common/utils/exceptions.py @@ -22,5 +22,29 @@ 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""" + + +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""" + + +class ScoutSuiteScanError(Exception): + """ Raise to indicate problems ScoutSuite encountered during scanning""" + + +class UnknownFindingError(Exception): + """ Raise when provided finding is of unknown type""" + + 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/config.py b/monkey/infection_monkey/config.py index 1fbcb876b..018f3aacc 100644 --- a/monkey/infection_monkey/config.py +++ b/monkey/infection_monkey/config.py @@ -1,5 +1,4 @@ import hashlib -import json import os import sys import uuid @@ -12,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" @@ -36,16 +36,6 @@ class Configuration(object): self.max_depth = self.depth return unknown_items - def from_json(self, json_data): - """ - Gets a json data object, parses it and applies it to the configuration - :param json_data: - :return: - """ - formatted_data = json.loads(json_data) - result = self.from_kv(formatted_data) - return result - @staticmethod def hide_sensitive_info(config_dict): for field in SENSITIVE_FIELDS: @@ -256,6 +246,10 @@ class Configuration(object): exploit_ntlm_hash_list = [] exploit_ssh_keys = [] + aws_access_key_id = '' + aws_secret_access_key = '' + aws_session_token = '' + # smb/wmi exploiter smb_download_timeout = 300 # timeout in seconds smb_service_name = "InfectionMonkey" diff --git a/monkey/infection_monkey/control.py b/monkey/infection_monkey/control.py index 50eb3f656..611166afa 100644 --- a/monkey/infection_monkey/control.py +++ b/monkey/infection_monkey/control.py @@ -12,13 +12,12 @@ import infection_monkey.tunnel as tunnel from common.common_consts.timeouts import (LONG_REQUEST_TIMEOUT, MEDIUM_REQUEST_TIMEOUT, SHORT_REQUEST_TIMEOUT) -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 from infection_monkey.transport.tcp import TcpProxy -from infection_monkey.utils.exceptions.planned_shutdown_exception import \ - PlannedShutdownException +from infection_monkey.utils.exceptions.planned_shutdown_exception import PlannedShutdownException __author__ = 'hoffer' @@ -132,12 +131,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/dropper.py b/monkey/infection_monkey/dropper.py index 53aaf6c99..cb7be181d 100644 --- a/monkey/infection_monkey/dropper.py +++ b/monkey/infection_monkey/dropper.py @@ -12,11 +12,8 @@ from ctypes import c_char_p from common.utils.attack_utils import ScanStatus, UsageEnum from infection_monkey.config import WormConfiguration -from infection_monkey.exploit.tools.helpers import \ - build_monkey_commandline_explicitly -from infection_monkey.model import (GENERAL_CMDLINE_LINUX, - MONKEY_CMDLINE_LINUX, - MONKEY_CMDLINE_WINDOWS) +from infection_monkey.exploit.tools.helpers import build_monkey_commandline_explicitly +from infection_monkey.model import GENERAL_CMDLINE_LINUX, MONKEY_CMDLINE_LINUX, MONKEY_CMDLINE_WINDOWS from infection_monkey.system_info import OperatingSystem, SystemInfoCollector from infection_monkey.telemetry.attack.t1106_telem import T1106Telem diff --git a/monkey/infection_monkey/exploit/HostExploiter.py b/monkey/infection_monkey/exploit/HostExploiter.py index 274d07329..c48cadcf0 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,8 +74,10 @@ class HostExploiter(Plugin): result = None try: result = self._exploit_host() - 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/exploit/elasticgroovy.py b/monkey/infection_monkey/exploit/elasticgroovy.py index fff71024d..dfaffac6a 100644 --- a/monkey/infection_monkey/exploit/elasticgroovy.py +++ b/monkey/infection_monkey/exploit/elasticgroovy.py @@ -10,11 +10,10 @@ 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, - CMD_PREFIX, DOWNLOAD_TIMEOUT, ID_STRING, +from infection_monkey.model import (BITSADMIN_CMDLINE_HTTP, CHECK_COMMAND, CMD_PREFIX, DOWNLOAD_TIMEOUT, ID_STRING, WGET_HTTP_UPLOAD) from infection_monkey.network.elasticfinger import ES_PORT from infection_monkey.telemetry.attack.t1197_telem import T1197Telem diff --git a/monkey/infection_monkey/exploit/hadoop.py b/monkey/infection_monkey/exploit/hadoop.py index eaf5d007b..36da16379 100644 --- a/monkey/infection_monkey/exploit/hadoop.py +++ b/monkey/infection_monkey/exploit/hadoop.py @@ -11,14 +11,11 @@ import string import requests +from infection_monkey.exploit.tools.helpers import build_monkey_commandline, get_monkey_depth from common.common_consts.timeouts import LONG_REQUEST_TIMEOUT -from infection_monkey.exploit.tools.helpers import (build_monkey_commandline, - get_monkey_depth) from infection_monkey.exploit.tools.http_tools import HTTPTools from infection_monkey.exploit.web_rce import WebRCE -from infection_monkey.model import (HADOOP_LINUX_COMMAND, - HADOOP_WINDOWS_COMMAND, ID_STRING, - MONKEY_ARG) +from infection_monkey.model import HADOOP_LINUX_COMMAND, HADOOP_WINDOWS_COMMAND, ID_STRING, MONKEY_ARG __author__ = 'VakarisZ' diff --git a/monkey/infection_monkey/exploit/mssqlexec.py b/monkey/infection_monkey/exploit/mssqlexec.py index 6bff6aced..c51acc3b8 100644 --- a/monkey/infection_monkey/exploit/mssqlexec.py +++ b/monkey/infection_monkey/exploit/mssqlexec.py @@ -5,13 +5,10 @@ from time import sleep import pymssql -from common.utils.exceptions import (ExploitingVulnerableMachineError, - FailedExploitationError) +from common.utils.exceptions import ExploitingVulnerableMachineError, FailedExploitationError from common.utils.exploit_enum import ExploitType from infection_monkey.exploit.HostExploiter import HostExploiter -from infection_monkey.exploit.tools.helpers import (build_monkey_commandline, - get_monkey_depth, - get_monkey_dest_path) +from infection_monkey.exploit.tools.helpers import build_monkey_commandline, get_monkey_depth, get_monkey_dest_path from infection_monkey.exploit.tools.http_tools import MonkeyHTTPServer from infection_monkey.exploit.tools.payload_parsing import LimitedSizePayload from infection_monkey.model import DROPPER_ARG diff --git a/monkey/infection_monkey/exploit/sambacry.py b/monkey/infection_monkey/exploit/sambacry.py index b61f32ef1..797ff6633 100644 --- a/monkey/infection_monkey/exploit/sambacry.py +++ b/monkey/infection_monkey/exploit/sambacry.py @@ -8,23 +8,17 @@ from io import BytesIO import impacket.smbconnection from impacket.nmb import NetBIOSError from impacket.nt_errors import STATUS_SUCCESS -from impacket.smb import (FILE_DIRECTORY_FILE, FILE_NON_DIRECTORY_FILE, - FILE_OPEN, FILE_READ_DATA, FILE_SHARE_READ, - FILE_WRITE_DATA, SMB, SMB_DIALECT, SessionError, - SMBCommand, SMBNtCreateAndX_Data, +from impacket.smb import (FILE_DIRECTORY_FILE, FILE_NON_DIRECTORY_FILE, FILE_OPEN, FILE_READ_DATA, FILE_SHARE_READ, + FILE_WRITE_DATA, SMB, SMB_DIALECT, SessionError, SMBCommand, SMBNtCreateAndX_Data, SMBNtCreateAndX_Parameters) -from impacket.smb3structs import (SMB2_CREATE, SMB2_FLAGS_DFS_OPERATIONS, - SMB2_IL_IMPERSONATION, - SMB2_OPLOCK_LEVEL_NONE, SMB2Create, - SMB2Create_Response, SMB2Packet) +from impacket.smb3structs import (SMB2_CREATE, SMB2_FLAGS_DFS_OPERATIONS, SMB2_IL_IMPERSONATION, SMB2_OPLOCK_LEVEL_NONE, + SMB2Create, SMB2Create_Response, SMB2Packet) from impacket.smbconnection import SMBConnection import infection_monkey.monkeyfs as monkeyfs from common.utils.attack_utils import ScanStatus from infection_monkey.exploit.HostExploiter import HostExploiter -from infection_monkey.exploit.tools.helpers import (build_monkey_commandline, - get_monkey_depth, - get_target_monkey_by_os) +from infection_monkey.exploit.tools.helpers import build_monkey_commandline, get_monkey_depth, get_target_monkey_by_os from infection_monkey.model import DROPPER_ARG from infection_monkey.network.smbfinger import SMB_SERVICE from infection_monkey.network.tools import get_interface_to_target diff --git a/monkey/infection_monkey/exploit/shellshock.py b/monkey/infection_monkey/exploit/shellshock.py index edec8018e..4caa7441f 100644 --- a/monkey/infection_monkey/exploit/shellshock.py +++ b/monkey/infection_monkey/exploit/shellshock.py @@ -9,9 +9,7 @@ import requests from common.utils.attack_utils import ScanStatus from infection_monkey.exploit.HostExploiter import HostExploiter from infection_monkey.exploit.shellshock_resources import CGI_FILES -from infection_monkey.exploit.tools.helpers import (build_monkey_commandline, - get_monkey_depth, - get_target_monkey) +from infection_monkey.exploit.tools.helpers import build_monkey_commandline, get_monkey_depth, get_target_monkey from infection_monkey.exploit.tools.http_tools import HTTPTools from infection_monkey.model import DROPPER_ARG from infection_monkey.telemetry.attack.t1222_telem import T1222Telem diff --git a/monkey/infection_monkey/exploit/smbexec.py b/monkey/infection_monkey/exploit/smbexec.py index c28887820..a9776136b 100644 --- a/monkey/infection_monkey/exploit/smbexec.py +++ b/monkey/infection_monkey/exploit/smbexec.py @@ -5,13 +5,9 @@ from impacket.dcerpc.v5 import scmr, transport from common.utils.attack_utils import ScanStatus, UsageEnum from common.utils.exploit_enum import ExploitType from infection_monkey.exploit.HostExploiter import HostExploiter -from infection_monkey.exploit.tools.helpers import (build_monkey_commandline, - get_monkey_depth, - get_target_monkey) +from infection_monkey.exploit.tools.helpers import build_monkey_commandline, get_monkey_depth, get_target_monkey from infection_monkey.exploit.tools.smb_tools import SmbTools -from infection_monkey.model import (DROPPER_CMDLINE_DETACHED_WINDOWS, - MONKEY_CMDLINE_DETACHED_WINDOWS, - VictimHost) +from infection_monkey.model import DROPPER_CMDLINE_DETACHED_WINDOWS, MONKEY_CMDLINE_DETACHED_WINDOWS, VictimHost from infection_monkey.network.smbfinger import SMBFinger from infection_monkey.network.tools import check_tcp_port from infection_monkey.telemetry.attack.t1035_telem import T1035Telem diff --git a/monkey/infection_monkey/exploit/sshexec.py b/monkey/infection_monkey/exploit/sshexec.py index cff86dbfb..b96a6c2b6 100644 --- a/monkey/infection_monkey/exploit/sshexec.py +++ b/monkey/infection_monkey/exploit/sshexec.py @@ -9,12 +9,9 @@ from common.utils.attack_utils import ScanStatus from common.utils.exceptions import FailedExploitationError from common.utils.exploit_enum import ExploitType from infection_monkey.exploit.HostExploiter import HostExploiter -from infection_monkey.exploit.tools.helpers import (build_monkey_commandline, - get_monkey_depth, - get_target_monkey) +from infection_monkey.exploit.tools.helpers import build_monkey_commandline, get_monkey_depth, get_target_monkey from infection_monkey.model import MONKEY_ARG -from infection_monkey.network.tools import (check_tcp_port, - get_interface_to_target) +from infection_monkey.network.tools import check_tcp_port, get_interface_to_target from infection_monkey.telemetry.attack.t1105_telem import T1105Telem from infection_monkey.telemetry.attack.t1222_telem import T1222Telem diff --git a/monkey/infection_monkey/exploit/tools/test_helpers.py b/monkey/infection_monkey/exploit/tools/test_helpers.py index deb16643b..5d7dd422d 100644 --- a/monkey/infection_monkey/exploit/tools/test_helpers.py +++ b/monkey/infection_monkey/exploit/tools/test_helpers.py @@ -1,7 +1,6 @@ import unittest -from infection_monkey.exploit.tools.helpers import \ - build_monkey_commandline_explicitly +from infection_monkey.exploit.tools.helpers import build_monkey_commandline_explicitly class TestHelpers(unittest.TestCase): diff --git a/monkey/infection_monkey/exploit/vsftpd.py b/monkey/infection_monkey/exploit/vsftpd.py index 8ecac35f1..f2e355802 100644 --- a/monkey/infection_monkey/exploit/vsftpd.py +++ b/monkey/infection_monkey/exploit/vsftpd.py @@ -10,12 +10,9 @@ from logging import getLogger from common.utils.attack_utils import ScanStatus from infection_monkey.exploit.HostExploiter import HostExploiter -from infection_monkey.exploit.tools.helpers import (build_monkey_commandline, - get_monkey_depth, - get_target_monkey) +from infection_monkey.exploit.tools.helpers import build_monkey_commandline, get_monkey_depth, get_target_monkey from infection_monkey.exploit.tools.http_tools import HTTPTools -from infection_monkey.model import (CHMOD_MONKEY, DOWNLOAD_TIMEOUT, MONKEY_ARG, - RUN_MONKEY, WGET_HTTP_UPLOAD) +from infection_monkey.model import CHMOD_MONKEY, DOWNLOAD_TIMEOUT, MONKEY_ARG, RUN_MONKEY, WGET_HTTP_UPLOAD from infection_monkey.telemetry.attack.t1222_telem import T1222Telem LOG = getLogger(__name__) diff --git a/monkey/infection_monkey/exploit/web_rce.py b/monkey/infection_monkey/exploit/web_rce.py index f5db3e92f..d12e4eaa9 100644 --- a/monkey/infection_monkey/exploit/web_rce.py +++ b/monkey/infection_monkey/exploit/web_rce.py @@ -5,16 +5,11 @@ from posixpath import join from common.utils.attack_utils import BITS_UPLOAD_STRING, ScanStatus from infection_monkey.exploit.HostExploiter import HostExploiter -from infection_monkey.exploit.tools.helpers import (build_monkey_commandline, - get_monkey_depth, - get_target_monkey) +from infection_monkey.exploit.tools.helpers import build_monkey_commandline, get_monkey_depth, get_target_monkey from infection_monkey.exploit.tools.http_tools import HTTPTools -from infection_monkey.model import (BITSADMIN_CMDLINE_HTTP, CHECK_COMMAND, - CHMOD_MONKEY, DOWNLOAD_TIMEOUT, - DROPPER_ARG, GET_ARCH_LINUX, - GET_ARCH_WINDOWS, ID_STRING, MONKEY_ARG, - POWERSHELL_HTTP_UPLOAD, RUN_MONKEY, - WGET_HTTP_UPLOAD) +from infection_monkey.model import (BITSADMIN_CMDLINE_HTTP, CHECK_COMMAND, CHMOD_MONKEY, DOWNLOAD_TIMEOUT, DROPPER_ARG, + GET_ARCH_LINUX, GET_ARCH_WINDOWS, ID_STRING, MONKEY_ARG, POWERSHELL_HTTP_UPLOAD, + RUN_MONKEY, WGET_HTTP_UPLOAD) from infection_monkey.network.tools import check_tcp_port, tcp_port_to_service from infection_monkey.telemetry.attack.t1197_telem import T1197Telem from infection_monkey.telemetry.attack.t1222_telem import T1222Telem diff --git a/monkey/infection_monkey/exploit/win_ms08_067.py b/monkey/infection_monkey/exploit/win_ms08_067.py index 43a86bfff..7690f33c1 100644 --- a/monkey/infection_monkey/exploit/win_ms08_067.py +++ b/monkey/infection_monkey/exploit/win_ms08_067.py @@ -16,12 +16,9 @@ from impacket.dcerpc.v5 import transport from common.utils.shellcode_obfuscator import clarify from infection_monkey.exploit.HostExploiter import HostExploiter -from infection_monkey.exploit.tools.helpers import (build_monkey_commandline, - get_monkey_depth, - get_target_monkey) +from infection_monkey.exploit.tools.helpers import build_monkey_commandline, get_monkey_depth, get_target_monkey from infection_monkey.exploit.tools.smb_tools import SmbTools -from infection_monkey.model import (DROPPER_CMDLINE_WINDOWS, - MONKEY_CMDLINE_WINDOWS) +from infection_monkey.model import DROPPER_CMDLINE_WINDOWS, MONKEY_CMDLINE_WINDOWS from infection_monkey.network.smbfinger import SMBFinger from infection_monkey.network.tools import check_tcp_port diff --git a/monkey/infection_monkey/exploit/wmiexec.py b/monkey/infection_monkey/exploit/wmiexec.py index 4aabe366d..348fd230c 100644 --- a/monkey/infection_monkey/exploit/wmiexec.py +++ b/monkey/infection_monkey/exploit/wmiexec.py @@ -7,14 +7,10 @@ from impacket.dcerpc.v5.rpcrt import DCERPCException from common.utils.exploit_enum import ExploitType from infection_monkey.exploit.HostExploiter import HostExploiter -from infection_monkey.exploit.tools.helpers import (build_monkey_commandline, - get_monkey_depth, - get_target_monkey) +from infection_monkey.exploit.tools.helpers import build_monkey_commandline, get_monkey_depth, get_target_monkey from infection_monkey.exploit.tools.smb_tools import SmbTools -from infection_monkey.exploit.tools.wmi_tools import (AccessDeniedException, - WmiTools) -from infection_monkey.model import (DROPPER_CMDLINE_WINDOWS, - MONKEY_CMDLINE_WINDOWS) +from infection_monkey.exploit.tools.wmi_tools import AccessDeniedException, WmiTools +from infection_monkey.model import DROPPER_CMDLINE_WINDOWS, MONKEY_CMDLINE_WINDOWS LOG = logging.getLogger(__name__) diff --git a/monkey/infection_monkey/main.py b/monkey/infection_monkey/main.py index e4698a462..945ccd8cf 100644 --- a/monkey/infection_monkey/main.py +++ b/monkey/infection_monkey/main.py @@ -15,8 +15,7 @@ from infection_monkey.config import EXTERNAL_CONFIG_FILE, WormConfiguration from infection_monkey.dropper import MonkeyDrops from infection_monkey.model import DROPPER_ARG, MONKEY_ARG from infection_monkey.monkey import InfectionMonkey -from infection_monkey.utils.monkey_log_path import (get_dropper_log_path, - get_monkey_log_path) +from infection_monkey.utils.monkey_log_path import get_dropper_log_path, get_monkey_log_path __author__ = 'itamar' diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index 3a19e40ac..5827cfbd2 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -7,10 +7,9 @@ import time from threading import Thread import infection_monkey.tunnel as tunnel -from common.network.network_utils import get_host_from_network_location +from infection_monkey.network.tools import is_running_on_island from common.utils.attack_utils import ScanStatus, UsageEnum -from common.utils.exceptions import (ExploitingVulnerableMachineError, - FailedExploitationError) +from common.utils.exceptions import ExploitingVulnerableMachineError, FailedExploitationError from common.version import get_version from infection_monkey.config import WormConfiguration from infection_monkey.control import ControlClient @@ -19,8 +18,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 @@ -33,11 +31,8 @@ from infection_monkey.telemetry.system_info_telem import SystemInfoTelem from infection_monkey.telemetry.trace_telem import TraceTelem from infection_monkey.telemetry.tunnel_telem import TunnelTelem from infection_monkey.utils.environment import is_windows_os -from infection_monkey.utils.exceptions.planned_shutdown_exception import \ - PlannedShutdownException -from infection_monkey.utils.monkey_dir import (create_monkey_dir, - get_monkey_dir_path, - remove_monkey_dir) +from infection_monkey.utils.exceptions.planned_shutdown_exception import PlannedShutdownException +from infection_monkey.utils.monkey_dir import create_monkey_dir, get_monkey_dir_path, remove_monkey_dir from infection_monkey.utils.monkey_log_path import get_monkey_log_path from infection_monkey.windows_upgrader import WindowsUpgrader @@ -125,7 +120,8 @@ 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) @@ -195,8 +191,8 @@ class InfectionMonkey(object): if self._default_server: if self._network.on_island(self._default_server): machine.set_default_server(get_interface_to_target(machine.ip_addr) + - ( - ':' + self._default_server_port if self._default_server_port else '')) + (':' + self._default_server_port + if self._default_server_port else '')) else: machine.set_default_server(self._default_server) LOG.debug("Default server for machine: %r set to %s" % (machine, machine.default_server)) @@ -254,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() @@ -306,8 +302,7 @@ class InfectionMonkey(object): try: status = None if "win32" == sys.platform: - from subprocess import (CREATE_NEW_CONSOLE, - STARTF_USESHOWWINDOW, SW_HIDE) + from subprocess import CREATE_NEW_CONSOLE, STARTF_USESHOWWINDOW, SW_HIDE startupinfo = subprocess.STARTUPINFO() startupinfo.dwFlags = CREATE_NEW_CONSOLE | STARTF_USESHOWWINDOW startupinfo.wShowWindow = SW_HIDE @@ -403,10 +398,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/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/network/mysqlfinger.py b/monkey/infection_monkey/network/mysqlfinger.py index c303692ad..968e5361f 100644 --- a/monkey/infection_monkey/network/mysqlfinger.py +++ b/monkey/infection_monkey/network/mysqlfinger.py @@ -3,8 +3,7 @@ import socket import infection_monkey.config from infection_monkey.network.HostFinger import HostFinger -from infection_monkey.network.tools import (struct_unpack_tracker, - struct_unpack_tracker_string) +from infection_monkey.network.tools import struct_unpack_tracker, struct_unpack_tracker_string MYSQL_PORT = 3306 SQL_SERVICE = 'mysqld-3306' diff --git a/monkey/infection_monkey/network/tools.py b/monkey/infection_monkey/network/tools.py index 99b21d81c..7f7a211fc 100644 --- a/monkey/infection_monkey/network/tools.py +++ b/monkey/infection_monkey/network/tools.py @@ -7,6 +7,8 @@ import subprocess import sys import time +from common.network.network_utils import get_host_from_network_location +from infection_monkey.config import WormConfiguration from infection_monkey.network.info import get_routes, local_ips from infection_monkey.pyinstaller_utils import get_binary_file_path from infection_monkey.utils.environment import is_64bit_python @@ -311,5 +313,11 @@ def get_interface_to_target(dst): return ret[1] +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 + + def is_running_on_server(ip: str) -> bool: return ip in local_ips() 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..7d0207b44 100644 --- a/monkey/infection_monkey/post_breach/actions/change_file_privileges.py +++ b/monkey/infection_monkey/post_breach/actions/change_file_privileges.py @@ -1,7 +1,6 @@ -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 +from infection_monkey.post_breach.setuid_setgid.setuid_setgid import get_commands_to_change_setuid_setgid class ChangeSetuidSetgid(PBA): 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 ce85c74c1..6e990dbc3 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..4d6e5f87d 100644 --- a/monkey/infection_monkey/post_breach/actions/discover_accounts.py +++ b/monkey/infection_monkey/post_breach/actions/discover_accounts.py @@ -1,6 +1,5 @@ -from common.data.post_breach_consts import POST_BREACH_ACCOUNT_DISCOVERY -from infection_monkey.post_breach.account_discovery.account_discovery import \ - get_commands_to_discover_accounts +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 9dfe875eb..baba3afea 100644 --- a/monkey/infection_monkey/post_breach/actions/hide_files.py +++ b/monkey/infection_monkey/post_breach/actions/hide_files.py @@ -1,12 +1,10 @@ -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 -from infection_monkey.utils.hidden_files import (cleanup_hidden_files, - get_commands_to_hide_files, +from infection_monkey.utils.hidden_files import (cleanup_hidden_files, get_commands_to_hide_files, get_commands_to_hide_folders) -from infection_monkey.utils.windows.hidden_files import \ - get_winAPI_to_hide_files +from infection_monkey.utils.windows.hidden_files import get_winAPI_to_hide_files HIDDEN_FSO_CREATION_COMMANDS = [get_commands_to_hide_files, get_commands_to_hide_folders] 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..c10575d39 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,7 +1,6 @@ import subprocess -from common.data.post_breach_consts import \ - POST_BREACH_SHELL_STARTUP_FILE_MODIFICATION +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 \ get_commands_to_modify_shell_startup_files diff --git a/monkey/infection_monkey/post_breach/actions/schedule_jobs.py b/monkey/infection_monkey/post_breach/actions/schedule_jobs.py index f7d8d8056..97ad75923 100644 --- a/monkey/infection_monkey/post_breach/actions/schedule_jobs.py +++ b/monkey/infection_monkey/post_breach/actions/schedule_jobs.py @@ -1,6 +1,6 @@ -from common.data.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 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/timestomping.py b/monkey/infection_monkey/post_breach/actions/timestomping.py index 50a940524..bf02664eb 100644 --- a/monkey/infection_monkey/post_breach/actions/timestomping.py +++ b/monkey/infection_monkey/post_breach/actions/timestomping.py @@ -1,7 +1,6 @@ -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 +from infection_monkey.post_breach.timestomping.timestomping import get_timestomping_commands class Timestomping(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..7afd2e631 100644 --- a/monkey/infection_monkey/post_breach/actions/use_trap_command.py +++ b/monkey/infection_monkey/post_breach/actions/use_trap_command.py @@ -1,7 +1,6 @@ -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 +from infection_monkey.post_breach.trap_command.trap_command import get_trap_commands class TrapCommand(PBA): 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/post_breach/clear_command_history/clear_command_history.py b/monkey/infection_monkey/post_breach/clear_command_history/clear_command_history.py index 67c600a5d..a5e8d7d44 100644 --- a/monkey/infection_monkey/post_breach/clear_command_history/clear_command_history.py +++ b/monkey/infection_monkey/post_breach/clear_command_history/clear_command_history.py @@ -1,6 +1,5 @@ from infection_monkey.post_breach.clear_command_history.linux_clear_command_history import ( - get_linux_command_history_files, - get_linux_commands_to_clear_command_history, get_linux_usernames) + get_linux_command_history_files, get_linux_commands_to_clear_command_history, get_linux_usernames) def get_commands_to_clear_command_history(): diff --git a/monkey/infection_monkey/post_breach/job_scheduling/job_scheduling.py b/monkey/infection_monkey/post_breach/job_scheduling/job_scheduling.py index 886070dbb..f7bceef72 100644 --- a/monkey/infection_monkey/post_breach/job_scheduling/job_scheduling.py +++ b/monkey/infection_monkey/post_breach/job_scheduling/job_scheduling.py @@ -1,10 +1,8 @@ import subprocess -from infection_monkey.post_breach.job_scheduling.linux_job_scheduling import \ - get_linux_commands_to_schedule_jobs +from infection_monkey.post_breach.job_scheduling.linux_job_scheduling import get_linux_commands_to_schedule_jobs from infection_monkey.post_breach.job_scheduling.windows_job_scheduling import ( - get_windows_commands_to_remove_scheduled_jobs, - get_windows_commands_to_schedule_jobs) + get_windows_commands_to_remove_scheduled_jobs, get_windows_commands_to_schedule_jobs) from infection_monkey.utils.environment import is_windows_os diff --git a/monkey/infection_monkey/post_breach/setuid_setgid/setuid_setgid.py b/monkey/infection_monkey/post_breach/setuid_setgid/setuid_setgid.py index 47c8cc476..7760ab900 100644 --- a/monkey/infection_monkey/post_breach/setuid_setgid/setuid_setgid.py +++ b/monkey/infection_monkey/post_breach/setuid_setgid/setuid_setgid.py @@ -1,5 +1,4 @@ -from infection_monkey.post_breach.setuid_setgid.linux_setuid_setgid import \ - get_linux_commands_to_setuid_setgid +from infection_monkey.post_breach.setuid_setgid.linux_setuid_setgid import get_linux_commands_to_setuid_setgid def get_commands_to_change_setuid_setgid(): diff --git a/monkey/infection_monkey/post_breach/signed_script_proxy/signed_script_proxy.py b/monkey/infection_monkey/post_breach/signed_script_proxy/signed_script_proxy.py index f39343577..5db88cfc4 100644 --- a/monkey/infection_monkey/post_breach/signed_script_proxy/signed_script_proxy.py +++ b/monkey/infection_monkey/post_breach/signed_script_proxy/signed_script_proxy.py @@ -1,8 +1,7 @@ import subprocess from infection_monkey.post_breach.signed_script_proxy.windows.signed_script_proxy import ( - get_windows_commands_to_delete_temp_comspec, - get_windows_commands_to_proxy_execution_using_signed_script, + get_windows_commands_to_delete_temp_comspec, get_windows_commands_to_proxy_execution_using_signed_script, get_windows_commands_to_reset_comspec) from infection_monkey.utils.environment import is_windows_os diff --git a/monkey/infection_monkey/post_breach/timestomping/timestomping.py b/monkey/infection_monkey/post_breach/timestomping/timestomping.py index 04ed5cb6d..321904c41 100644 --- a/monkey/infection_monkey/post_breach/timestomping/timestomping.py +++ b/monkey/infection_monkey/post_breach/timestomping/timestomping.py @@ -1,7 +1,5 @@ -from infection_monkey.post_breach.timestomping.linux.timestomping import \ - get_linux_timestomping_commands -from infection_monkey.post_breach.timestomping.windows.timestomping import \ - get_windows_timestomping_commands +from infection_monkey.post_breach.timestomping.linux.timestomping import get_linux_timestomping_commands +from infection_monkey.post_breach.timestomping.windows.timestomping import get_windows_timestomping_commands def get_timestomping_commands(): diff --git a/monkey/infection_monkey/post_breach/trap_command/trap_command.py b/monkey/infection_monkey/post_breach/trap_command/trap_command.py index bd25e3d11..641be0300 100644 --- a/monkey/infection_monkey/post_breach/trap_command/trap_command.py +++ b/monkey/infection_monkey/post_breach/trap_command/trap_command.py @@ -1,5 +1,4 @@ -from infection_monkey.post_breach.trap_command.linux_trap_command import \ - get_linux_trap_commands +from infection_monkey.post_breach.trap_command.linux_trap_command import get_linux_trap_commands def get_trap_commands(): diff --git a/monkey/infection_monkey/system_info/__init__.py b/monkey/infection_monkey/system_info/__init__.py index 05bb3a4d0..a5502a2c0 100644 --- a/monkey/infection_monkey/system_info/__init__.py +++ b/monkey/infection_monkey/system_info/__init__.py @@ -4,12 +4,11 @@ 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 -from infection_monkey.system_info.system_info_collectors_handler import \ - SystemInfoCollectorsHandler +from infection_monkey.system_info.system_info_collectors_handler import SystemInfoCollectorsHandler LOG = logging.getLogger(__name__) diff --git a/monkey/infection_monkey/system_info/collectors/aws_collector.py b/monkey/infection_monkey/system_info/collectors/aws_collector.py index bdf470735..94a7baf2a 100644 --- a/monkey/infection_monkey/system_info/collectors/aws_collector.py +++ b/monkey/infection_monkey/system_info/collectors/aws_collector.py @@ -1,9 +1,11 @@ import logging from common.cloud.aws.aws_instance import AwsInstance -from common.data.system_info_collectors_names import AWS_COLLECTOR -from infection_monkey.system_info.system_info_collector import \ - SystemInfoCollector +from common.cloud.scoutsuite_consts import CloudProviders +from common.common_consts.system_info_collectors_names import AWS_COLLECTOR +from infection_monkey.network.tools 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 logger = logging.getLogger(__name__) @@ -17,6 +19,11 @@ class AwsCollector(SystemInfoCollector): def collect(self) -> dict: logger.info("Collecting AWS info") + if is_running_on_island(): + logger.info("Attempting to scan AWS security with ScoutSuite.") + scan_cloud_security(cloud_type=CloudProviders.AWS) + else: + logger.info("Didn't scan AWS security with ScoutSuite, because not on island.") aws = AwsInstance() info = {} if aws.is_instance(): diff --git a/monkey/infection_monkey/system_info/collectors/environment_collector.py b/monkey/infection_monkey/system_info/collectors/environment_collector.py index 9bcd917ee..039ede6f5 100644 --- a/monkey/infection_monkey/system_info/collectors/environment_collector.py +++ b/monkey/infection_monkey/system_info/collectors/environment_collector.py @@ -1,8 +1,7 @@ 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 infection_monkey.system_info.system_info_collector import \ - SystemInfoCollector +from common.common_consts.system_info_collectors_names import ENVIRONMENT_COLLECTOR +from infection_monkey.system_info.system_info_collector import SystemInfoCollector def get_monkey_environment() -> str: diff --git a/monkey/infection_monkey/system_info/collectors/hostname_collector.py b/monkey/infection_monkey/system_info/collectors/hostname_collector.py index ae9560815..0aeecd9fb 100644 --- a/monkey/infection_monkey/system_info/collectors/hostname_collector.py +++ b/monkey/infection_monkey/system_info/collectors/hostname_collector.py @@ -1,9 +1,8 @@ import logging import socket -from common.data.system_info_collectors_names import HOSTNAME_COLLECTOR -from infection_monkey.system_info.system_info_collector import \ - SystemInfoCollector +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__) 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..cdb5bc045 100644 --- a/monkey/infection_monkey/system_info/collectors/process_list_collector.py +++ b/monkey/infection_monkey/system_info/collectors/process_list_collector.py @@ -2,9 +2,8 @@ import logging import psutil -from common.data.system_info_collectors_names import PROCESS_LIST_COLLECTOR -from infection_monkey.system_info.system_info_collector import \ - SystemInfoCollector +from common.common_consts.system_info_collectors_names import PROCESS_LIST_COLLECTOR +from infection_monkey.system_info.system_info_collector import SystemInfoCollector logger = logging.getLogger(__name__) 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..97e736b4b --- /dev/null +++ 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 new file mode 100644 index 000000000..88ef32293 --- /dev/null +++ b/monkey/infection_monkey/system_info/collectors/scoutsuite_collector/scoutsuite_api.py @@ -0,0 +1,5 @@ +import common.cloud.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..c637e3593 --- /dev/null +++ b/monkey/infection_monkey/system_info/collectors/scoutsuite_collector/scoutsuite_collector.py @@ -0,0 +1,32 @@ +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.base.provider import BaseProvider +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 + +logger = logging.getLogger(__name__) + + +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 ScoutSuiteScanError(results['error']) + send_scoutsuite_run_results(results) + except (Exception, ScoutSuiteScanError) as e: + logger.error(f"ScoutSuite didn't scan {cloud_type.value} security because: {e}") + + +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_scoutsuite_run_results(run_results: BaseProvider): + ScoutSuiteTelem(run_results).send() diff --git a/monkey/infection_monkey/system_info/system_info_collectors_handler.py b/monkey/infection_monkey/system_info/system_info_collectors_handler.py index 4e8281e7a..cc007ff86 100644 --- a/monkey/infection_monkey/system_info/system_info_collectors_handler.py +++ b/monkey/infection_monkey/system_info/system_info_collectors_handler.py @@ -1,8 +1,7 @@ import logging from typing import Sequence -from infection_monkey.system_info.system_info_collector import \ - SystemInfoCollector +from infection_monkey.system_info.system_info_collector import SystemInfoCollector from infection_monkey.telemetry.system_info_telem import SystemInfoTelem LOG = logging.getLogger(__name__) diff --git a/monkey/infection_monkey/system_info/windows_cred_collector/mimikatz_cred_collector.py b/monkey/infection_monkey/system_info/windows_cred_collector/mimikatz_cred_collector.py index 506c92412..96d3912e3 100644 --- a/monkey/infection_monkey/system_info/windows_cred_collector/mimikatz_cred_collector.py +++ b/monkey/infection_monkey/system_info/windows_cred_collector/mimikatz_cred_collector.py @@ -1,10 +1,8 @@ import logging from typing import List -from infection_monkey.system_info.windows_cred_collector import \ - pypykatz_handler -from infection_monkey.system_info.windows_cred_collector.windows_credentials import \ - WindowsCredentials +from infection_monkey.system_info.windows_cred_collector import pypykatz_handler +from infection_monkey.system_info.windows_cred_collector.windows_credentials import WindowsCredentials LOG = logging.getLogger(__name__) diff --git a/monkey/infection_monkey/system_info/windows_cred_collector/pypykatz_handler.py b/monkey/infection_monkey/system_info/windows_cred_collector/pypykatz_handler.py index 35ff67678..ca146573f 100644 --- a/monkey/infection_monkey/system_info/windows_cred_collector/pypykatz_handler.py +++ b/monkey/infection_monkey/system_info/windows_cred_collector/pypykatz_handler.py @@ -3,8 +3,7 @@ from typing import Any, Dict, List, NewType from pypykatz.pypykatz import pypykatz -from infection_monkey.system_info.windows_cred_collector.windows_credentials import \ - WindowsCredentials +from infection_monkey.system_info.windows_cred_collector.windows_credentials import WindowsCredentials CREDENTIAL_TYPES = ['msv_creds', 'wdigest_creds', 'ssp_creds', 'livessp_creds', 'dpapi_creds', 'kerberos_creds', 'credman_creds', 'tspkg_creds'] diff --git a/monkey/infection_monkey/system_info/windows_cred_collector/test_pypykatz_handler.py b/monkey/infection_monkey/system_info/windows_cred_collector/test_pypykatz_handler.py index 2ad76a4e5..165b00cf2 100644 --- a/monkey/infection_monkey/system_info/windows_cred_collector/test_pypykatz_handler.py +++ b/monkey/infection_monkey/system_info/windows_cred_collector/test_pypykatz_handler.py @@ -1,7 +1,6 @@ from unittest import TestCase -from infection_monkey.system_info.windows_cred_collector.pypykatz_handler import \ - _get_creds_from_pypykatz_session +from infection_monkey.system_info.windows_cred_collector.pypykatz_handler import _get_creds_from_pypykatz_session class TestPypykatzHandler(TestCase): diff --git a/monkey/infection_monkey/system_info/windows_info_collector.py b/monkey/infection_monkey/system_info/windows_info_collector.py index 3e7b2bfff..38feb6815 100644 --- a/monkey/infection_monkey/system_info/windows_info_collector.py +++ b/monkey/infection_monkey/system_info/windows_info_collector.py @@ -2,9 +2,8 @@ import logging import os import sys -from common.data.system_info_collectors_names import MIMIKATZ_COLLECTOR -from infection_monkey.system_info.windows_cred_collector.mimikatz_cred_collector import \ - MimikatzCredentialCollector +from common.common_consts.system_info_collectors_names import MIMIKATZ_COLLECTOR +from infection_monkey.system_info.windows_cred_collector.mimikatz_cred_collector import MimikatzCredentialCollector sys.coinit_flags = 0 # needed for proper destruction of the wmi python module import infection_monkey.config # noqa: E402 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/infection_monkey/telemetry/base_telem.py b/monkey/infection_monkey/telemetry/base_telem.py index 7617ab4e3..5627cfee1 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/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/scoutsuite_telem.py b/monkey/infection_monkey/telemetry/scoutsuite_telem.py new file mode 100644 index 000000000..16cf47bdd --- /dev/null +++ b/monkey/infection_monkey/telemetry/scoutsuite_telem.py @@ -0,0 +1,19 @@ +from common.cloud.scoutsuite.ScoutSuite.output.result_encoder import ScoutJsonEncoder +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, provider: BaseProvider): + super().__init__() + self.provider_data = provider + + json_encoder = ScoutJsonEncoder + telem_category = TelemCategoryEnum.SCOUTSUITE + + def get_data(self): + return { + 'data': self.provider_data + } 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/infection_monkey/transport/http.py b/monkey/infection_monkey/transport/http.py index 57dd2450e..1502e844c 100644 --- a/monkey/infection_monkey/transport/http.py +++ b/monkey/infection_monkey/transport/http.py @@ -13,8 +13,7 @@ import infection_monkey.control import infection_monkey.monkeyfs as monkeyfs from common.common_consts.timeouts import SHORT_REQUEST_TIMEOUT from infection_monkey.network.tools import get_interface_to_target -from infection_monkey.transport.base import (TransportProxyBase, - update_last_serve_time) +from infection_monkey.transport.base import TransportProxyBase, update_last_serve_time __author__ = 'hoffer' @@ -193,8 +192,7 @@ class HTTPServer(threading.Thread): def run(self): class TempHandler(FileServHTTPRequestHandler): from common.utils.attack_utils import ScanStatus - from infection_monkey.telemetry.attack.t1105_telem import \ - T1105Telem + from infection_monkey.telemetry.attack.t1105_telem import T1105Telem filename = self._filename @@ -248,8 +246,7 @@ class LockedHTTPServer(threading.Thread): def run(self): class TempHandler(FileServHTTPRequestHandler): from common.utils.attack_utils import ScanStatus - from infection_monkey.telemetry.attack.t1105_telem import \ - T1105Telem + from infection_monkey.telemetry.attack.t1105_telem import T1105Telem filename = self._filename @staticmethod diff --git a/monkey/infection_monkey/transport/tcp.py b/monkey/infection_monkey/transport/tcp.py index 928f4b079..329ef1875 100644 --- a/monkey/infection_monkey/transport/tcp.py +++ b/monkey/infection_monkey/transport/tcp.py @@ -3,8 +3,7 @@ import socket from logging import getLogger from threading import Thread -from infection_monkey.transport.base import (TransportProxyBase, - update_last_serve_time) +from infection_monkey.transport.base import TransportProxyBase, update_last_serve_time READ_BUFFER_SIZE = 8192 DEFAULT_TIMEOUT = 30 diff --git a/monkey/infection_monkey/tunnel.py b/monkey/infection_monkey/tunnel.py index 49224f155..6d261ce2b 100644 --- a/monkey/infection_monkey/tunnel.py +++ b/monkey/infection_monkey/tunnel.py @@ -7,8 +7,7 @@ from threading import Thread from infection_monkey.model import VictimHost from infection_monkey.network.firewall import app as firewall from infection_monkey.network.info import get_free_tcp_port, local_ips -from infection_monkey.network.tools import (check_tcp_port, - get_interface_to_target) +from infection_monkey.network.tools import check_tcp_port, get_interface_to_target from infection_monkey.transport.base import get_last_serve_time __author__ = 'hoffer' diff --git a/monkey/infection_monkey/utils/hidden_files.py b/monkey/infection_monkey/utils/hidden_files.py index 863680085..863d1a277 100644 --- a/monkey/infection_monkey/utils/hidden_files.py +++ b/monkey/infection_monkey/utils/hidden_files.py @@ -1,12 +1,11 @@ import subprocess from infection_monkey.utils.environment import is_windows_os -from infection_monkey.utils.linux.hidden_files import ( - get_linux_commands_to_delete, get_linux_commands_to_hide_files, - get_linux_commands_to_hide_folders) -from infection_monkey.utils.windows.hidden_files import ( - get_windows_commands_to_delete, get_windows_commands_to_hide_files, - get_windows_commands_to_hide_folders) +from infection_monkey.utils.linux.hidden_files import (get_linux_commands_to_delete, get_linux_commands_to_hide_files, + get_linux_commands_to_hide_folders) +from infection_monkey.utils.windows.hidden_files import (get_windows_commands_to_delete, + get_windows_commands_to_hide_files, + get_windows_commands_to_hide_folders) def get_commands_to_hide_files(): diff --git a/monkey/infection_monkey/utils/plugins/pluginTests/BadImport.py b/monkey/infection_monkey/utils/plugins/pluginTests/BadImport.py index d28a91a89..ffd3ebb2d 100644 --- a/monkey/infection_monkey/utils/plugins/pluginTests/BadImport.py +++ b/monkey/infection_monkey/utils/plugins/pluginTests/BadImport.py @@ -1,5 +1,4 @@ -from infection_monkey.utils.plugins.pluginTests.PluginTestClass import \ - TestPlugin # noqa: F401 +from infection_monkey.utils.plugins.pluginTests.PluginTestClass import TestPlugin # noqa: F401 class SomeDummyPlugin: diff --git a/monkey/infection_monkey/utils/plugins/pluginTests/BadInit.py b/monkey/infection_monkey/utils/plugins/pluginTests/BadInit.py index 658992469..18e83c052 100644 --- a/monkey/infection_monkey/utils/plugins/pluginTests/BadInit.py +++ b/monkey/infection_monkey/utils/plugins/pluginTests/BadInit.py @@ -1,5 +1,4 @@ -from infection_monkey.utils.plugins.pluginTests.PluginTestClass import \ - TestPlugin +from infection_monkey.utils.plugins.pluginTests.PluginTestClass import TestPlugin class BadPluginInit(TestPlugin): diff --git a/monkey/infection_monkey/utils/plugins/pluginTests/ComboFile.py b/monkey/infection_monkey/utils/plugins/pluginTests/ComboFile.py index 47418df31..2d73cd65b 100644 --- a/monkey/infection_monkey/utils/plugins/pluginTests/ComboFile.py +++ b/monkey/infection_monkey/utils/plugins/pluginTests/ComboFile.py @@ -1,5 +1,4 @@ -from infection_monkey.utils.plugins.pluginTests.PluginTestClass import \ - TestPlugin +from infection_monkey.utils.plugins.pluginTests.PluginTestClass import TestPlugin class NoInheritance: diff --git a/monkey/infection_monkey/utils/plugins/pluginTests/PluginWorking.py b/monkey/infection_monkey/utils/plugins/pluginTests/PluginWorking.py index b200bd835..a3fe237b6 100644 --- a/monkey/infection_monkey/utils/plugins/pluginTests/PluginWorking.py +++ b/monkey/infection_monkey/utils/plugins/pluginTests/PluginWorking.py @@ -1,5 +1,4 @@ -from infection_monkey.utils.plugins.pluginTests.PluginTestClass import \ - TestPlugin +from infection_monkey.utils.plugins.pluginTests.PluginTestClass import TestPlugin class PluginWorking(TestPlugin): diff --git a/monkey/infection_monkey/utils/plugins/plugin_test.py b/monkey/infection_monkey/utils/plugins/plugin_test.py index 3bbf59b90..c587bfed2 100644 --- a/monkey/infection_monkey/utils/plugins/plugin_test.py +++ b/monkey/infection_monkey/utils/plugins/plugin_test.py @@ -1,14 +1,10 @@ from unittest import TestCase -from infection_monkey.utils.plugins.pluginTests.BadImport import \ - SomeDummyPlugin +from infection_monkey.utils.plugins.pluginTests.BadImport import SomeDummyPlugin from infection_monkey.utils.plugins.pluginTests.BadInit import BadPluginInit -from infection_monkey.utils.plugins.pluginTests.ComboFile import (BadInit, - ProperClass) -from infection_monkey.utils.plugins.pluginTests.PluginTestClass import \ - TestPlugin -from infection_monkey.utils.plugins.pluginTests.PluginWorking import \ - PluginWorking +from infection_monkey.utils.plugins.pluginTests.ComboFile import BadInit, ProperClass +from infection_monkey.utils.plugins.pluginTests.PluginTestClass import TestPlugin +from infection_monkey.utils.plugins.pluginTests.PluginWorking import PluginWorking class PluginTester(TestCase): diff --git a/monkey/infection_monkey/utils/users.py b/monkey/infection_monkey/utils/users.py index 37dd93e4f..b2f29db85 100644 --- a/monkey/infection_monkey/utils/users.py +++ b/monkey/infection_monkey/utils/users.py @@ -1,6 +1,5 @@ from infection_monkey.utils.linux.users import get_linux_commands_to_add_user -from infection_monkey.utils.windows.users import \ - get_windows_commands_to_add_user +from infection_monkey.utils.windows.users import get_windows_commands_to_add_user def get_commands_to_add_user(username, password): diff --git a/monkey/infection_monkey/windows_upgrader.py b/monkey/infection_monkey/windows_upgrader.py index f28a30d41..8b9ec7f80 100644 --- a/monkey/infection_monkey/windows_upgrader.py +++ b/monkey/infection_monkey/windows_upgrader.py @@ -7,12 +7,9 @@ import time import infection_monkey.monkeyfs as monkeyfs from infection_monkey.config import WormConfiguration from infection_monkey.control import ControlClient -from infection_monkey.exploit.tools.helpers import \ - build_monkey_commandline_explicitly +from infection_monkey.exploit.tools.helpers import build_monkey_commandline_explicitly from infection_monkey.model import MONKEY_CMDLINE_WINDOWS -from infection_monkey.utils.environment import (is_64bit_python, - is_64bit_windows_os, - is_windows_os) +from infection_monkey.utils.environment import is_64bit_python, is_64bit_windows_os, is_windows_os __author__ = 'itay.mizeretz' diff --git a/monkey/monkey_island/cc/app.py b/monkey/monkey_island/cc/app.py index e8dfd2cfc..c53c04caa 100644 --- a/monkey/monkey_island/cc/app.py +++ b/monkey/monkey_island/cc/app.py @@ -6,8 +6,10 @@ 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 monkey_island.cc.consts import MONKEY_ISLAND_ABS_PATH +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 from monkey_island.cc.resources.attack.attack_config import AttackConfiguration from monkey_island.cc.resources.attack.attack_report import AttackReport @@ -23,10 +25,8 @@ from monkey_island.cc.resources.local_run import LocalRun from monkey_island.cc.resources.log import Log from monkey_island.cc.resources.monkey import Monkey from monkey_island.cc.resources.monkey_configuration import MonkeyConfiguration -from monkey_island.cc.resources.monkey_control.remote_port_check import \ - RemotePortCheck -from monkey_island.cc.resources.monkey_control.started_on_island import \ - StartedOnIsland +from monkey_island.cc.resources.monkey_control.remote_port_check import RemotePortCheck +from monkey_island.cc.resources.monkey_control.started_on_island import StartedOnIsland from monkey_island.cc.resources.monkey_download import MonkeyDownload from monkey_island.cc.resources.netmap import NetMap from monkey_island.cc.resources.node import Node @@ -34,18 +34,18 @@ 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.T1216_pba_file_download import T1216PBAFileDownload from monkey_island.cc.resources.telemetry import Telemetry from monkey_island.cc.resources.telemetry_feed import TelemetryFeed from monkey_island.cc.resources.test.clear_caches import ClearCaches from monkey_island.cc.resources.test.log_test import LogTest from monkey_island.cc.resources.test.monkey_test import MonkeyTest from monkey_island.cc.resources.version_update import VersionUpdate -from monkey_island.cc.resources.zero_trust.finding_event import \ - ZeroTrustFindingEvent +from monkey_island.cc.resources.zero_trust.finding_event import ZeroTrustFindingEvent +from monkey_island.cc.resources.zero_trust.scoutsuite_auth.aws_keys import AWSKeys +from monkey_island.cc.resources.zero_trust.scoutsuite_auth.scoutsuite_auth import ScoutSuiteAuth from monkey_island.cc.services.database import Database from monkey_island.cc.services.remote_run_aws import RemoteRunAwsService from monkey_island.cc.services.representations import output_json @@ -85,6 +85,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) @@ -122,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/') @@ -139,10 +139,11 @@ 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') + api.add_resource(ScoutSuiteAuth, '/api/scoutsuite_auth/') + api.add_resource(AWSKeys, '/api/aws_keys') api.add_resource(MonkeyTest, '/api/test/monkey') api.add_resource(ClearCaches, '/api/test/clear_caches') diff --git a/monkey/monkey_island/cc/conftest.py b/monkey/monkey_island/cc/conftest.py new file mode 100644 index 000000000..0ed1533ab --- /dev/null +++ b/monkey/monkey_island/cc/conftest.py @@ -0,0 +1,3 @@ +# 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/environment/__init__.py b/monkey/monkey_island/cc/environment/__init__.py index fcaa4e156..75012183f 100644 --- a/monkey/monkey_island/cc/environment/__init__.py +++ b/monkey/monkey_island/cc/environment/__init__.py @@ -6,8 +6,7 @@ from datetime import timedelta __author__ = 'itay.mizeretz' -from common.utils.exceptions import (AlreadyRegisteredError, - CredentialsNotRequiredError, +from common.utils.exceptions import (AlreadyRegisteredError, CredentialsNotRequiredError, InvalidRegistrationCredentialsError) 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/environment/environment_config.py b/monkey/monkey_island/cc/environment/environment_config.py index 79182d56b..35dbafc8e 100644 --- a/monkey/monkey_island/cc/environment/environment_config.py +++ b/monkey/monkey_island/cc/environment/environment_config.py @@ -6,7 +6,7 @@ from pathlib import Path from typing import Dict, List import monkey_island.cc.environment.server_config_generator as server_config_generator -from monkey_island.cc.consts import MONKEY_ISLAND_ABS_PATH +from monkey_island.cc.server_utils.consts import MONKEY_ISLAND_ABS_PATH from monkey_island.cc.environment.user_creds import UserCreds from monkey_island.cc.resources.auth.auth_user import User from monkey_island.cc.resources.auth.user_store import UserStore diff --git a/monkey/monkey_island/cc/environment/environment_singleton.py b/monkey/monkey_island/cc/environment/environment_singleton.py index 194337384..6b98d0b7c 100644 --- a/monkey/monkey_island/cc/environment/environment_singleton.py +++ b/monkey/monkey_island/cc/environment/environment_singleton.py @@ -1,8 +1,7 @@ import logging import monkey_island.cc.resources.auth.user_store as user_store -from monkey_island.cc.environment import (EnvironmentConfig, aws, password, - standard, testing) +from monkey_island.cc.environment import EnvironmentConfig, aws, password, standard, testing __author__ = 'itay.mizeretz' @@ -11,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 8c5c12b63..168fe13cd 100644 --- a/monkey/monkey_island/cc/environment/set_server_config.py +++ b/monkey/monkey_island/cc/environment/set_server_config.py @@ -53,7 +53,7 @@ def main(): def parse_args(): parser = argparse.ArgumentParser() - parser.add_argument("server_config", choices=["standard", "testing", "password", "restore"]) + parser.add_argument("server_config", choices=["standard", "password", "restore"]) args = parser.parse_args() return args diff --git a/monkey/monkey_island/cc/environment/test__init__.py b/monkey/monkey_island/cc/environment/test__init__.py index 3637d6dd2..c55e1b65b 100644 --- a/monkey/monkey_island/cc/environment/test__init__.py +++ b/monkey/monkey_island/cc/environment/test__init__.py @@ -4,13 +4,10 @@ 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 -from common.utils.exceptions import (AlreadyRegisteredError, - CredentialsNotRequiredError, - InvalidRegistrationCredentialsError, - RegistrationNotNeededError) -from monkey_island.cc.environment import (Environment, EnvironmentConfig, - UserCreds) +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 def get_server_config_file_path_test_version(): diff --git a/monkey/monkey_island/cc/environment/test_environment_config.py b/monkey/monkey_island/cc/environment/test_environment_config.py index d4978a18a..ed9b0ef96 100644 --- a/monkey/monkey_island/cc/environment/test_environment_config.py +++ b/monkey/monkey_island/cc/environment/test_environment_config.py @@ -5,8 +5,8 @@ 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 -from monkey_island.cc.consts import MONKEY_ISLAND_ABS_PATH +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/main.py b/monkey/monkey_island/cc/main.py index cf58f48c5..ce142edcc 100644 --- a/monkey/monkey_island/cc/main.py +++ b/monkey/monkey_island/cc/main.py @@ -12,8 +12,8 @@ MONKEY_ISLAND_DIR_BASE_PATH = str(Path(__file__).parent.parent) if str(MONKEY_ISLAND_DIR_BASE_PATH) not in sys.path: sys.path.insert(0, MONKEY_ISLAND_DIR_BASE_PATH) -from monkey_island.cc.consts import MONKEY_ISLAND_ABS_PATH # noqa: E402 -from monkey_island.cc.island_logger import json_setup_logging # noqa: E402 +from monkey_island.cc.server_utils.consts import MONKEY_ISLAND_ABS_PATH # noqa: E402 +from monkey_island.cc.server_utils.island_logger import json_setup_logging # noqa: E402 # This is here in order to catch EVERYTHING, some functions are being called on imports the log init needs to be on top. json_setup_logging(default_path=Path(MONKEY_ISLAND_ABS_PATH, 'cc', 'island_logger_default_config.json'), @@ -23,15 +23,12 @@ logger = logging.getLogger(__name__) import monkey_island.cc.environment.environment_singleton as env_singleton # noqa: E402 from common.version import get_version # noqa: E402 from monkey_island.cc.app import init_app # noqa: E402 -from monkey_island.cc.bootloader_server import \ - BootloaderHttpServer # noqa: E402 +from monkey_island.cc.server_utils.bootloader_server import BootloaderHttpServer # noqa: E402 from monkey_island.cc.database import get_db_version # noqa: E402 from monkey_island.cc.database import is_db_server_up # noqa: E402 -from monkey_island.cc.network_utils import local_ip_addresses # noqa: E402 -from monkey_island.cc.resources.monkey_download import \ - MonkeyDownload # noqa: E402 -from monkey_island.cc.services.reporting.exporter_init import \ - populate_exporter_list # noqa: E402 +from monkey_island.cc.services.utils.network_utils import local_ip_addresses # noqa: E402 +from monkey_island.cc.resources.monkey_download import MonkeyDownload # noqa: E402 +from monkey_island.cc.services.reporting.exporter_init import populate_exporter_list # noqa: E402 from monkey_island.cc.setup import setup # noqa: E402 MINIMUM_MONGO_DB_VERSION_REQUIRED = "4.2.0" 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/attack/attack_mitigations.py b/monkey/monkey_island/cc/models/attack/attack_mitigations.py index d2641561d..0c38ecbeb 100644 --- a/monkey/monkey_island/cc/models/attack/attack_mitigations.py +++ b/monkey/monkey_island/cc/models/attack/attack_mitigations.py @@ -1,12 +1,10 @@ from typing import Dict -from mongoengine import (Document, DoesNotExist, EmbeddedDocumentField, - ListField, StringField) +from mongoengine import Document, DoesNotExist, EmbeddedDocumentField, ListField, StringField from stix2 import AttackPattern, CourseOfAction from monkey_island.cc.models.attack.mitigation import Mitigation -from monkey_island.cc.services.attack.test_mitre_api_interface import \ - MitreApiInterface +from monkey_island.cc.services.attack.test_mitre_api_interface import MitreApiInterface class AttackMitigations(Document): diff --git a/monkey/monkey_island/cc/models/attack/mitigation.py b/monkey/monkey_island/cc/models/attack/mitigation.py index a67945ca9..03c8bafef 100644 --- a/monkey/monkey_island/cc/models/attack/mitigation.py +++ b/monkey/monkey_island/cc/models/attack/mitigation.py @@ -1,8 +1,7 @@ from mongoengine import EmbeddedDocument, StringField from stix2 import CourseOfAction -from monkey_island.cc.services.attack.test_mitre_api_interface import \ - MitreApiInterface +from monkey_island.cc.services.attack.test_mitre_api_interface import MitreApiInterface class Mitigation(EmbeddedDocument): diff --git a/monkey/monkey_island/cc/models/edge.py b/monkey/monkey_island/cc/models/edge.py index 24a520265..78fb91d6e 100644 --- a/monkey/monkey_island/cc/models/edge.py +++ b/monkey/monkey_island/cc/models/edge.py @@ -1,5 +1,4 @@ -from mongoengine import (BooleanField, Document, DynamicField, ListField, - ObjectIdField, StringField) +from mongoengine import BooleanField, Document, DynamicField, ListField, ObjectIdField, StringField class Edge(Document): diff --git a/monkey/monkey_island/cc/models/monkey.py b/monkey/monkey_island/cc/models/monkey.py index bc6202e65..b0009a335 100644 --- a/monkey/monkey_island/cc/models/monkey.py +++ b/monkey/monkey_island/cc/models/monkey.py @@ -2,18 +2,14 @@ Define a Document Schema for the Monkey document. """ import ring -from mongoengine import (BooleanField, DateTimeField, Document, DoesNotExist, - DynamicField, EmbeddedDocumentField, ListField, - ReferenceField, StringField) +from mongoengine import (BooleanField, DateTimeField, Document, DoesNotExist, DynamicField, EmbeddedDocumentField, + ListField, ReferenceField, StringField) from common.cloud import environment_names -from monkey_island.cc.consts import \ - DEFAULT_MONKEY_TTL_EXPIRY_DURATION_IN_SECONDS -from monkey_island.cc.models.command_control_channel import \ - CommandControlChannel -from monkey_island.cc.models.monkey_ttl import (MonkeyTtl, - create_monkey_ttl_document) -from monkey_island.cc.network_utils import local_ip_addresses +from monkey_island.cc.server_utils.consts import DEFAULT_MONKEY_TTL_EXPIRY_DURATION_IN_SECONDS +from monkey_island.cc.models.command_control_channel import CommandControlChannel +from monkey_island.cc.models.monkey_ttl import MonkeyTtl, create_monkey_ttl_document +from monkey_island.cc.services.utils.network_utils import local_ip_addresses MAX_MONKEYS_AMOUNT_TO_CACHE = 100 diff --git a/monkey/monkey_island/cc/models/test_monkey.py b/monkey/monkey_island/cc/models/test_monkey.py index b2bba9aa0..7860de20e 100644 --- a/monkey/monkey_island/cc/models/test_monkey.py +++ b/monkey/monkey_island/cc/models/test_monkey.py @@ -5,26 +5,17 @@ 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 +from ..test_common.fixtures import FixtureEnum 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(FixtureEnum.USES_DATABASE) 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() @@ -47,43 +38,37 @@ 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(FixtureEnum.USES_DATABASE) def test_ttl_renewal(self): - self.fail_if_not_testing_env() - self.clean_monkey_db() - # 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(FixtureEnum.USES_DATABASE) 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() # 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(FixtureEnum.USES_DATABASE) 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()), @@ -94,14 +79,12 @@ 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(FixtureEnum.USES_DATABASE) 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()), @@ -118,12 +101,10 @@ 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 + @pytest.mark.usefixtures(FixtureEnum.USES_DATABASE) 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()), @@ -135,26 +116,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") @@ -165,27 +146,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(FixtureEnum.USES_DATABASE) 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() 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/aggregate_finding.py b/monkey/monkey_island/cc/models/zero_trust/aggregate_finding.py deleted file mode 100644 index c3817313f..000000000 --- a/monkey/monkey_island/cc/models/zero_trust/aggregate_finding.py +++ /dev/null @@ -1,31 +0,0 @@ -import common.data.zero_trust_consts as zero_trust_consts -from monkey_island.cc.models.zero_trust.finding import Finding - - -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).exclude('events') - 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) - else: - # Now we know for sure this is the only one - orig_finding = existing_findings[0] - orig_finding.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/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..f65d39af7 100644 --- a/monkey/monkey_island/cc/models/zero_trust/finding.py +++ b/monkey/monkey_island/cc/models/zero_trust/finding.py @@ -2,14 +2,12 @@ """ Define a Document Schema for Zero Trust findings. """ -from typing import List -from mongoengine import Document, EmbeddedDocumentListField, StringField +from __future__ import annotations -import common.data.zero_trust_consts as zero_trust_consts -# Dummy import for mongoengine. -# noinspection PyUnresolvedReferences -from monkey_island.cc.models.zero_trust.event import Event +from mongoengine import Document, StringField + +import common.common_consts.zero_trust_consts as zero_trust_consts class Finding(Document): @@ -30,31 +28,9 @@ 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) - events = EmbeddedDocumentListField(document_type=Event) # 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] - - def get_pillars(self): - return zero_trust_consts.TESTS_MAP[self.test][zero_trust_consts.PILLARS_KEY] - - # Creation methods - @staticmethod - def save_finding(test, status, events): - finding = Finding( - test=test, - status=status, - events=events) - - finding.save() - - return finding - - def add_events(self, events: List) -> None: - self.update(push_all__events=events) + # SCHEMA + test = StringField(required=True, choices=zero_trust_consts.TESTS) + status = StringField(required=True, choices=zero_trust_consts.ORDERED_TEST_STATUSES) 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..479b9b244 --- /dev/null +++ b/monkey/monkey_island/cc/models/zero_trust/monkey_finding.py @@ -0,0 +1,20 @@ +from __future__ import annotations + +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): + # 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/monkey_finding_details.py b/monkey/monkey_island/cc/models/zero_trust/monkey_finding_details.py new file mode 100644 index 000000000..62cfda504 --- /dev/null +++ b/monkey/monkey_island/cc/models/zero_trust/monkey_finding_details.py @@ -0,0 +1,19 @@ +from __future__ import annotations + +from typing import List + +from mongoengine import Document, EmbeddedDocumentListField + +from monkey_island.cc.models.zero_trust.event import Event + + +class MonkeyFindingDetails(Document): + + # SCHEMA + events = EmbeddedDocumentListField(document_type=Event, required=False) + + # LOGIC + def add_events(self, events: List[Event]) -> MonkeyFindingDetails: + self.events.extend(events) + self.save() + return self 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..166c247bf --- /dev/null +++ b/monkey/monkey_island/cc/models/zero_trust/scoutsuite_data_json.py @@ -0,0 +1,20 @@ +from mongoengine import Document, DynamicField + + +class ScoutSuiteRawDataJson(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: + try: + current_data = ScoutSuiteRawDataJson.objects()[0] + except IndexError: + current_data = ScoutSuiteRawDataJson() + 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 new file mode 100644 index 000000000..9e36e46c5 --- /dev/null +++ b/monkey/monkey_island/cc/models/zero_trust/scoutsuite_finding.py @@ -0,0 +1,20 @@ +from __future__ import annotations + +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): + # 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/models/zero_trust/scoutsuite_finding_details.py b/monkey/monkey_island/cc/models/zero_trust/scoutsuite_finding_details.py new file mode 100644 index 000000000..cbc8c5f29 --- /dev/null +++ b/monkey/monkey_island/cc/models/zero_trust/scoutsuite_finding_details.py @@ -0,0 +1,14 @@ +from mongoengine import Document, EmbeddedDocumentListField + +from monkey_island.cc.models.zero_trust.scoutsuite_rule import ScoutSuiteRule + + +class ScoutSuiteFindingDetails(Document): + + # SCHEMA + scoutsuite_rules = EmbeddedDocumentListField(document_type=ScoutSuiteRule, required=False) + + 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..fcf09df9c --- /dev/null +++ b/monkey/monkey_island/cc/models/zero_trust/scoutsuite_rule.py @@ -0,0 +1,25 @@ +from mongoengine import DynamicField, EmbeddedDocument, IntField, ListField, StringField + +from monkey_island.cc.services.zero_trust.scoutsuite.consts import rule_consts + + +class ScoutSuiteRule(EmbeddedDocument): + """ + This model represents ScoutSuite security rule check results: + how many resources break the security rule + security rule description and remediation and etc. + """ + + # 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 = DynamicField(required=False) + references = ListField(required=False) 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 60262fbfd..000000000 --- a/monkey/monkey_island/cc/models/zero_trust/segmentation_finding.py +++ /dev/null @@ -1,50 +0,0 @@ -from mongoengine import StringField - -import common.data.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/test_aggregate_finding.py b/monkey/monkey_island/cc/models/zero_trust/test_aggregate_finding.py deleted file mode 100644 index 91452dc0e..000000000 --- a/monkey/monkey_island/cc/models/zero_trust/test_aggregate_finding.py +++ /dev/null @@ -1,64 +0,0 @@ -import unittest - -import mongomock -from packaging import version - -import common.data.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 -from monkey_island.cc.models.zero_trust.finding import Finding -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) - - AggregateFinding.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) - - 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) - - AggregateFinding.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): - AggregateFinding.create_or_add_to_existing(test, status, events) 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..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,23 +1,20 @@ +import pytest 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 -class TestEvent(IslandTestCase): +class TestEvent: def test_create_event(self): - self.fail_if_not_testing_env() - self.clean_finding_db() - - 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 deleted file mode 100644 index e221dacb1..000000000 --- a/monkey/monkey_island/cc/models/zero_trust/test_finding.py +++ /dev/null @@ -1,40 +0,0 @@ -from mongoengine import ValidationError - -import common.data.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 - - -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. - - 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. - """ - - 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=[]) - - with self.assertRaises(ValidationError): - _ = Finding.save_finding(test=zero_trust_consts.TEST_SEGMENTATION, status="bla bla", events=[]) - - 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( - title="Event Title", message="event message", event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK) - Finding.save_finding(test=zero_trust_consts.TEST_SEGMENTATION, - status=zero_trust_consts.STATUS_FAILED, events=[event_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/models/zero_trust/test_monkey_finding.py b/monkey/monkey_island/cc/models/zero_trust/test_monkey_finding.py new file mode 100644 index 000000000..56a4066e1 --- /dev/null +++ b/monkey/monkey_island/cc/models/zero_trust/test_monkey_finding.py @@ -0,0 +1,39 @@ +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.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.test_common.fixtures import FixtureEnum + +MONKEY_FINDING_DETAIL_MOCK = MonkeyFindingDetails() +MONKEY_FINDING_DETAIL_MOCK.events = ['mock1', 'mock2'] + + +class TestMonkeyFinding: + + @pytest.mark.usefixtures(FixtureEnum.USES_DATABASE) + def test_save_finding_validation(self): + with pytest.raises(ValidationError): + _ = 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): + 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) + monkey_details_example = MonkeyFindingDetails() + monkey_details_example.events.append(event_example) + monkey_details_example.save() + MonkeyFinding.save_finding(test=zero_trust_consts.TEST_SEGMENTATION, + status=zero_trust_consts.STATUS_FAILED, + detail_ref=monkey_details_example) + + 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/models/zero_trust/test_segmentation_finding.py b/monkey/monkey_island/cc/models/zero_trust/test_segmentation_finding.py deleted file mode 100644 index b375d97a9..000000000 --- a/monkey/monkey_island/cc/models/zero_trust/test_segmentation_finding.py +++ /dev/null @@ -1,53 +0,0 @@ -import common.data.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 -from monkey_island.cc.testing.IslandTestCase import IslandTestCase - - -class TestSegmentationFinding(IslandTestCase): - def test_create_or_add_to_existing_finding(self): - self.fail_if_not_testing_env() - self.clean_finding_db() - - first_segment = "1.1.1.0/24" - second_segment = "2.2.2.0-2.2.2.254" - third_segment = "3.3.3.3" - event = Event.create_event("bla", "bla", zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK) - - SegmentationFinding.create_or_add_to_existing_finding( - subnets=[first_segment, second_segment], - status=zero_trust_consts.STATUS_FAILED, - segmentation_event=event - ) - - self.assertEqual(len(SegmentationFinding.objects()), 1) - self.assertEqual(len(SegmentationFinding.objects()[0].events), 1) - - SegmentationFinding.create_or_add_to_existing_finding( - # !!! REVERSE ORDER - subnets=[second_segment, first_segment], - status=zero_trust_consts.STATUS_FAILED, - segmentation_event=event - ) - - self.assertEqual(len(SegmentationFinding.objects()), 1) - self.assertEqual(len(SegmentationFinding.objects()[0].events), 2) - - SegmentationFinding.create_or_add_to_existing_finding( - # !!! REVERSE ORDER - subnets=[first_segment, third_segment], - status=zero_trust_consts.STATUS_FAILED, - segmentation_event=event - ) - - self.assertEqual(len(SegmentationFinding.objects()), 2) - - SegmentationFinding.create_or_add_to_existing_finding( - # !!! REVERSE ORDER - subnets=[second_segment, third_segment], - status=zero_trust_consts.STATUS_FAILED, - segmentation_event=event - ) - - self.assertEqual(len(SegmentationFinding.objects()), 3) diff --git a/monkey/monkey_island/cc/resources/T1216_pba_file_download.py b/monkey/monkey_island/cc/resources/T1216_pba_file_download.py index 104c113f4..ac52b77f8 100644 --- a/monkey/monkey_island/cc/resources/T1216_pba_file_download.py +++ b/monkey/monkey_island/cc/resources/T1216_pba_file_download.py @@ -3,7 +3,7 @@ import os import flask_restful from flask import send_from_directory -from monkey_island.cc.consts import MONKEY_ISLAND_ABS_PATH +from monkey_island.cc.server_utils.consts import MONKEY_ISLAND_ABS_PATH class T1216PBAFileDownload(flask_restful.Resource): 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/resources/auth/registration.py b/monkey/monkey_island/cc/resources/auth/registration.py index 4e80d5bf9..b27116aa9 100644 --- a/monkey/monkey_island/cc/resources/auth/registration.py +++ b/monkey/monkey_island/cc/resources/auth/registration.py @@ -2,8 +2,7 @@ import flask_restful from flask import make_response, request import monkey_island.cc.environment.environment_singleton as env_singleton -from common.utils.exceptions import (InvalidRegistrationCredentialsError, - RegistrationNotNeededError) +from common.utils.exceptions import InvalidRegistrationCredentialsError, RegistrationNotNeededError from monkey_island.cc.environment.user_creds import UserCreds diff --git a/monkey/monkey_island/cc/resources/local_run.py b/monkey/monkey_island/cc/resources/local_run.py index 045729787..c0e690a4f 100644 --- a/monkey/monkey_island/cc/resources/local_run.py +++ b/monkey/monkey_island/cc/resources/local_run.py @@ -8,9 +8,9 @@ import flask_restful from flask import jsonify, make_response, request import monkey_island.cc.environment.environment_singleton as env_singleton -from monkey_island.cc.consts import MONKEY_ISLAND_ABS_PATH +from monkey_island.cc.server_utils.consts import MONKEY_ISLAND_ABS_PATH from monkey_island.cc.models import Monkey -from monkey_island.cc.network_utils import local_ip_addresses +from monkey_island.cc.services.utils.network_utils import local_ip_addresses from monkey_island.cc.resources.monkey_download import get_monkey_executable from monkey_island.cc.services.node import NodeService @@ -46,7 +46,7 @@ def run_local_monkey(): args = ['"%s" m0nk3y -s %s:%s' % (target_path, local_ip_addresses()[0], env_singleton.env.get_island_port())] if sys.platform == "win32": args = "".join(args) - pid = subprocess.Popen(args, shell=True).pid + subprocess.Popen(args, shell=True).pid except Exception as exc: logger.error('popen failed', exc_info=True) return False, "popen failed: %s" % exc diff --git a/monkey/monkey_island/cc/resources/monkey.py b/monkey/monkey_island/cc/resources/monkey.py index 0962e6dd5..0e6fe0370 100644 --- a/monkey/monkey_island/cc/resources/monkey.py +++ b/monkey/monkey_island/cc/resources/monkey.py @@ -5,8 +5,7 @@ import dateutil.parser import flask_restful from flask import request -from monkey_island.cc.consts import \ - DEFAULT_MONKEY_TTL_EXPIRY_DURATION_IN_SECONDS +from monkey_island.cc.server_utils.consts import DEFAULT_MONKEY_TTL_EXPIRY_DURATION_IN_SECONDS from monkey_island.cc.database import mongo from monkey_island.cc.models.monkey_ttl import create_monkey_ttl_document from monkey_island.cc.resources.test.utils.telem_store import TestTelemStore diff --git a/monkey/monkey_island/cc/resources/monkey_download.py b/monkey/monkey_island/cc/resources/monkey_download.py index d5b30e9a8..c9d3127a4 100644 --- a/monkey/monkey_island/cc/resources/monkey_download.py +++ b/monkey/monkey_island/cc/resources/monkey_download.py @@ -6,7 +6,7 @@ import os import flask_restful from flask import request, send_from_directory -from monkey_island.cc.consts import MONKEY_ISLAND_ABS_PATH +from monkey_island.cc.server_utils.consts import MONKEY_ISLAND_ABS_PATH __author__ = 'Barak' diff --git a/monkey/monkey_island/cc/resources/node_states.py b/monkey/monkey_island/cc/resources/node_states.py index 0b50ac34c..87be11ab5 100644 --- a/monkey/monkey_island/cc/resources/node_states.py +++ b/monkey/monkey_island/cc/resources/node_states.py @@ -1,8 +1,7 @@ import flask_restful from monkey_island.cc.resources.auth.auth import jwt_required -from monkey_island.cc.services.utils.node_states import \ - NodeStates as NodeStateList +from monkey_island.cc.services.utils.node_states import NodeStates as NodeStateList class NodeStates(flask_restful.Resource): diff --git a/monkey/monkey_island/cc/resources/pba_file_upload.py b/monkey/monkey_island/cc/resources/pba_file_upload.py index 5f75eaaa1..6d6795f74 100644 --- a/monkey/monkey_island/cc/resources/pba_file_upload.py +++ b/monkey/monkey_island/cc/resources/pba_file_upload.py @@ -8,8 +8,8 @@ from werkzeug.utils import secure_filename from monkey_island.cc.resources.auth.auth import jwt_required from monkey_island.cc.services.config import ConfigService -from monkey_island.cc.services.post_breach_files import ( - ABS_UPLOAD_PATH, PBA_LINUX_FILENAME_PATH, PBA_WINDOWS_FILENAME_PATH) +from monkey_island.cc.services.post_breach_files import (ABS_UPLOAD_PATH, PBA_LINUX_FILENAME_PATH, + PBA_WINDOWS_FILENAME_PATH) __author__ = 'VakarisZ' 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 a0ea8b0b9..000000000 --- a/monkey/monkey_island/cc/resources/reporting/report.py +++ /dev/null @@ -1,41 +0,0 @@ -import http.client - -import flask_restful -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 \ - 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" - -__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(ZeroTrustService.get_all_findings()) - - flask_restful.abort(http.client.NOT_FOUND) diff --git a/monkey/monkey_island/cc/resources/root.py b/monkey/monkey_island/cc/resources/root.py index 7463a9857..041d38b5e 100644 --- a/monkey/monkey_island/cc/resources/root.py +++ b/monkey/monkey_island/cc/resources/root.py @@ -5,7 +5,7 @@ import flask_restful from flask import jsonify, make_response, request from monkey_island.cc.database import mongo -from monkey_island.cc.network_utils import local_ip_addresses +from monkey_island.cc.services.utils.network_utils import local_ip_addresses from monkey_island.cc.resources.auth.auth import jwt_required from monkey_island.cc.services.database import Database from monkey_island.cc.services.infection_lifecycle import InfectionLifecycle 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/telemetry.py b/monkey/monkey_island/cc/resources/telemetry.py index efdeb34b3..75feb20a4 100644 --- a/monkey/monkey_island/cc/resources/telemetry.py +++ b/monkey/monkey_island/cc/resources/telemetry.py @@ -6,13 +6,13 @@ 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 from monkey_island.cc.resources.test.utils.telem_store import TestTelemStore from monkey_island.cc.services.node import NodeService -from monkey_island.cc.services.telemetry.processing.processing import \ - process_telemetry +from monkey_island.cc.services.telemetry.processing.processing import process_telemetry __author__ = 'Barak' @@ -45,6 +45,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} @@ -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/resources/zero_trust/finding_event.py b/monkey/monkey_island/cc/resources/zero_trust/finding_event.py index 8a1879c9c..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,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_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(ZeroTrustService.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 new file mode 100644 index 000000000..53e757f11 --- /dev/null +++ b/monkey/monkey_island/cc/resources/zero_trust/scoutsuite_auth/aws_keys.py @@ -0,0 +1,11 @@ +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 + + +class AWSKeys(flask_restful.Resource): + + @jwt_required + def get(self): + return get_aws_keys() 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 new file mode 100644 index 000000000..dbed4dd51 --- /dev/null +++ b/monkey/monkey_island/cc/resources/zero_trust/scoutsuite_auth/scoutsuite_auth.py @@ -0,0 +1,34 @@ +import json + +import flask_restful +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) + + +class ScoutSuiteAuth(flask_restful.Resource): + + @jwt_required + def get(self, provider: CloudProviders): + if provider == CloudProviders.AWS.value: + is_setup, message = is_cloud_authentication_setup(provider) + return {'is_setup': is_setup, 'message': message} + else: + return {'is_setup': False, 'message': ''} + + @jwt_required + def post(self, provider: CloudProviders): + key_info = json.loads(request.data) + error_msg = '' + if provider == CloudProviders.AWS.value: + try: + set_aws_keys(access_key_id=key_info['accessKeyId'], + secret_access_key=key_info['secretAccessKey'], + session_token=key_info['sessionToken']) + except InvalidAWSKeys as e: + error_msg = str(e) + return {'error_msg': error_msg} 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..433bf4631 --- /dev/null +++ b/monkey/monkey_island/cc/resources/zero_trust/zero_trust_report.py @@ -0,0 +1,33 @@ +import http.client + +import flask_restful +from flask import Response, jsonify + +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" +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(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_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(), + mimetype='application/json') + + flask_restful.abort(http.client.NOT_FOUND) diff --git a/monkey/monkey_island/cc/resources/reporting/__init__.py b/monkey/monkey_island/cc/server_utils/__init__.py similarity index 100% rename from monkey/monkey_island/cc/resources/reporting/__init__.py rename to monkey/monkey_island/cc/server_utils/__init__.py diff --git a/monkey/monkey_island/cc/bootloader_server.py b/monkey/monkey_island/cc/server_utils/bootloader_server.py similarity index 100% rename from monkey/monkey_island/cc/bootloader_server.py rename to monkey/monkey_island/cc/server_utils/bootloader_server.py diff --git a/monkey/monkey_island/cc/consts.py b/monkey/monkey_island/cc/server_utils/consts.py similarity index 100% rename from monkey/monkey_island/cc/consts.py rename to monkey/monkey_island/cc/server_utils/consts.py diff --git a/monkey/monkey_island/cc/server_utils/custom_json_encoder.py b/monkey/monkey_island/cc/server_utils/custom_json_encoder.py new file mode 100644 index 000000000..3c53586d1 --- /dev/null +++ b/monkey/monkey_island/cc/server_utils/custom_json_encoder.py @@ -0,0 +1,13 @@ +from bson import ObjectId, json_util +from flask.json import JSONEncoder + + +class CustomJSONEncoder(JSONEncoder): + + def default(self, obj): + try: + if isinstance(obj, ObjectId): + return json_util.dumps(obj) + except TypeError: + pass + return JSONEncoder.default(self, obj) diff --git a/monkey/monkey_island/cc/encryptor.py b/monkey/monkey_island/cc/server_utils/encryptor.py similarity index 96% rename from monkey/monkey_island/cc/encryptor.py rename to monkey/monkey_island/cc/server_utils/encryptor.py index 8c59ca58b..cce7d464a 100644 --- a/monkey/monkey_island/cc/encryptor.py +++ b/monkey/monkey_island/cc/server_utils/encryptor.py @@ -6,7 +6,7 @@ import os from Crypto import Random # noqa: DUO133 # nosec: B413 from Crypto.Cipher import AES # noqa: DUO133 # nosec: B413 -from monkey_island.cc.consts import MONKEY_ISLAND_ABS_PATH +from monkey_island.cc.server_utils.consts import MONKEY_ISLAND_ABS_PATH __author__ = "itay.mizeretz" diff --git a/monkey/monkey_island/cc/island_logger.py b/monkey/monkey_island/cc/server_utils/island_logger.py similarity index 100% rename from monkey/monkey_island/cc/island_logger.py rename to monkey/monkey_island/cc/server_utils/island_logger.py diff --git a/monkey/monkey_island/cc/services/attack/attack_report.py b/monkey/monkey_island/cc/services/attack/attack_report.py index 64ebf01b9..572b469c5 100644 --- a/monkey/monkey_island/cc/services/attack/attack_report.py +++ b/monkey/monkey_island/cc/services/attack/attack_report.py @@ -3,26 +3,12 @@ import logging from monkey_island.cc.database import mongo from monkey_island.cc.models import Monkey from monkey_island.cc.services.attack.attack_config import AttackConfig -from monkey_island.cc.services.attack.technique_reports import (T1003, T1005, - T1016, T1018, - T1021, T1035, - T1041, T1053, - T1059, T1064, - T1065, T1075, - T1082, T1086, - T1087, T1090, - T1099, T1105, - T1106, T1107, - T1110, T1129, - T1136, T1145, - T1146, T1154, - T1156, T1158, - T1166, T1168, - T1188, T1197, - T1210, T1216, - T1222, T1504) -from monkey_island.cc.services.reporting.report_generation_synchronisation import \ - safe_generate_attack_report +from monkey_island.cc.services.attack.technique_reports import (T1003, T1005, T1016, T1018, T1021, T1035, T1041, T1053, + T1059, T1064, T1065, T1075, T1082, T1086, T1087, T1090, + T1099, T1105, T1106, T1107, T1110, T1129, T1136, T1145, + T1146, T1154, T1156, T1158, T1166, T1168, T1188, T1197, + T1210, T1216, T1222, T1504) +from monkey_island.cc.services.reporting.report_generation_synchronisation import safe_generate_attack_report __author__ = "VakarisZ" diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1021.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1021.py index a43c76479..b017e7c85 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1021.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1021.py @@ -1,8 +1,7 @@ from common.utils.attack_utils import ScanStatus from monkey_island.cc.database import mongo from monkey_island.cc.services.attack.technique_reports import AttackTechnique -from monkey_island.cc.services.attack.technique_reports.technique_report_tools import \ - parse_creds +from monkey_island.cc.services.attack.technique_reports.technique_report_tools import parse_creds __author__ = "VakarisZ" diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1035.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1035.py index f56b3e23f..e0694f3b4 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1035.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1035.py @@ -1,5 +1,4 @@ -from monkey_island.cc.services.attack.technique_reports.usage_technique import \ - UsageTechnique +from monkey_island.cc.services.attack.technique_reports.usage_technique import UsageTechnique __author__ = "VakarisZ" 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..7ab1b5607 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1053.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1053.py @@ -1,6 +1,5 @@ -from common.data.post_breach_consts import POST_BREACH_JOB_SCHEDULING -from monkey_island.cc.services.attack.technique_reports.pba_technique import \ - PostBreachTechnique +from common.common_consts.post_breach_consts import POST_BREACH_JOB_SCHEDULING +from monkey_island.cc.services.attack.technique_reports.pba_technique import PostBreachTechnique __author__ = "shreyamalviya" diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1064.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1064.py index 9bd492829..2c68c9ae4 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1064.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1064.py @@ -1,6 +1,5 @@ from monkey_island.cc.database import mongo -from monkey_island.cc.services.attack.technique_reports.usage_technique import \ - UsageTechnique +from monkey_island.cc.services.attack.technique_reports.usage_technique import UsageTechnique __author__ = "VakarisZ" 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/attack/technique_reports/T1087.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1087.py index de0a6a470..91e1204f4 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1087.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1087.py @@ -1,6 +1,5 @@ -from common.data.post_breach_consts import POST_BREACH_ACCOUNT_DISCOVERY -from monkey_island.cc.services.attack.technique_reports.pba_technique import \ - PostBreachTechnique +from common.common_consts.post_breach_consts import POST_BREACH_ACCOUNT_DISCOVERY +from monkey_island.cc.services.attack.technique_reports.pba_technique import PostBreachTechnique __author__ = "shreyamalviya" 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..a16d3423d 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1099.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1099.py @@ -1,6 +1,5 @@ -from common.data.post_breach_consts import POST_BREACH_TIMESTOMPING -from monkey_island.cc.services.attack.technique_reports.pba_technique import \ - PostBreachTechnique +from common.common_consts.post_breach_consts import POST_BREACH_TIMESTOMPING +from monkey_island.cc.services.attack.technique_reports.pba_technique import PostBreachTechnique __author__ = "shreyamalviya" diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1106.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1106.py index d98449464..d07a66038 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1106.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1106.py @@ -1,5 +1,4 @@ -from monkey_island.cc.services.attack.technique_reports.usage_technique import \ - UsageTechnique +from monkey_island.cc.services.attack.technique_reports.usage_technique import UsageTechnique __author__ = "VakarisZ" diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1110.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1110.py index c2d6fc8d5..63e6ba26f 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1110.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1110.py @@ -1,8 +1,7 @@ from common.utils.attack_utils import ScanStatus from monkey_island.cc.database import mongo from monkey_island.cc.services.attack.technique_reports import AttackTechnique -from monkey_island.cc.services.attack.technique_reports.technique_report_tools import \ - parse_creds +from monkey_island.cc.services.attack.technique_reports.technique_report_tools import parse_creds __author__ = "VakarisZ" diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1129.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1129.py index e84698058..3a13c5101 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1129.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1129.py @@ -1,5 +1,4 @@ -from monkey_island.cc.services.attack.technique_reports.usage_technique import \ - UsageTechnique +from monkey_island.cc.services.attack.technique_reports.usage_technique import UsageTechnique __author__ = "VakarisZ" 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..d9d86e08e 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1136.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1136.py @@ -1,7 +1,5 @@ -from common.data.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 +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 __author__ = "shreyamalviya" 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..e1ca3423f 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1146.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1146.py @@ -1,6 +1,5 @@ -from common.data.post_breach_consts import POST_BREACH_CLEAR_CMD_HISTORY -from monkey_island.cc.services.attack.technique_reports.pba_technique import \ - PostBreachTechnique +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 __author__ = "shreyamalviya" 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..d775fcffc 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1154.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1154.py @@ -1,6 +1,5 @@ -from common.data.post_breach_consts import POST_BREACH_TRAP_COMMAND -from monkey_island.cc.services.attack.technique_reports.pba_technique import \ - PostBreachTechnique +from common.common_consts.post_breach_consts import POST_BREACH_TRAP_COMMAND +from monkey_island.cc.services.attack.technique_reports.pba_technique import PostBreachTechnique __author__ = "shreyamalviya" 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..0b2fdf41e 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1156.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1156.py @@ -1,7 +1,5 @@ -from common.data.post_breach_consts import \ - POST_BREACH_SHELL_STARTUP_FILE_MODIFICATION -from monkey_island.cc.services.attack.technique_reports.pba_technique import \ - PostBreachTechnique +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 __author__ = "shreyamalviya" 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..9e688fd37 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1158.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1158.py @@ -1,6 +1,5 @@ -from common.data.post_breach_consts import POST_BREACH_HIDDEN_FILES -from monkey_island.cc.services.attack.technique_reports.pba_technique import \ - PostBreachTechnique +from common.common_consts.post_breach_consts import POST_BREACH_HIDDEN_FILES +from monkey_island.cc.services.attack.technique_reports.pba_technique import PostBreachTechnique __author__ = "shreyamalviya" 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..ab482f0f6 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1166.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1166.py @@ -1,6 +1,5 @@ -from common.data.post_breach_consts import POST_BREACH_SETUID_SETGID -from monkey_island.cc.services.attack.technique_reports.pba_technique import \ - PostBreachTechnique +from common.common_consts.post_breach_consts import POST_BREACH_SETUID_SETGID +from monkey_island.cc.services.attack.technique_reports.pba_technique import PostBreachTechnique __author__ = "shreyamalviya" 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..a690086dc 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1168.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1168.py @@ -1,6 +1,5 @@ -from common.data.post_breach_consts import POST_BREACH_JOB_SCHEDULING -from monkey_island.cc.services.attack.technique_reports.pba_technique import \ - PostBreachTechnique +from common.common_consts.post_breach_consts import POST_BREACH_JOB_SCHEDULING +from monkey_island.cc.services.attack.technique_reports.pba_technique import PostBreachTechnique __author__ = "shreyamalviya" 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..796c1a043 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1216.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1216.py @@ -1,6 +1,5 @@ -from common.data.post_breach_consts import POST_BREACH_SIGNED_SCRIPT_PROXY_EXEC -from monkey_island.cc.services.attack.technique_reports.pba_technique import \ - PostBreachTechnique +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 __author__ = "shreyamalviya" 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..c2ed8d3f8 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1504.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1504.py @@ -1,7 +1,5 @@ -from common.data.post_breach_consts import \ - POST_BREACH_SHELL_STARTUP_FILE_MODIFICATION -from monkey_island.cc.services.attack.technique_reports.pba_technique import \ - PostBreachTechnique +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 __author__ = "shreyamalviya" diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/technique_report_tools.py b/monkey/monkey_island/cc/services/attack/technique_reports/technique_report_tools.py index 80bfb952d..34be687a4 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/technique_report_tools.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/technique_report_tools.py @@ -1,4 +1,4 @@ -from monkey_island.cc.encryptor import encryptor +from monkey_island.cc.server_utils.encryptor import encryptor def parse_creds(attempt): diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/usage_technique.py b/monkey/monkey_island/cc/services/attack/technique_reports/usage_technique.py index cd061a050..862207505 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/usage_technique.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/usage_technique.py @@ -2,8 +2,7 @@ import abc from common.utils.attack_utils import UsageEnum from monkey_island.cc.database import mongo -from monkey_island.cc.services.attack.technique_reports import ( - AttackTechnique, logger) +from monkey_island.cc.services.attack.technique_reports import AttackTechnique, logger class UsageTechnique(AttackTechnique, metaclass=abc.ABCMeta): diff --git a/monkey/monkey_island/cc/services/attack/test_mitre_api_interface.py b/monkey/monkey_island/cc/services/attack/test_mitre_api_interface.py index 297c68cce..4866a6729 100644 --- a/monkey/monkey_island/cc/services/attack/test_mitre_api_interface.py +++ b/monkey/monkey_island/cc/services/attack/test_mitre_api_interface.py @@ -1,7 +1,6 @@ from unittest import TestCase -from monkey_island.cc.services.attack.mitre_api_interface import \ - MitreApiInterface +from monkey_island.cc.services.attack.mitre_api_interface import MitreApiInterface class TestMitreApiInterface(TestCase): diff --git a/monkey/monkey_island/cc/services/bootloader.py b/monkey/monkey_island/cc/services/bootloader.py index 8790f7c1e..2d8a14055 100644 --- a/monkey/monkey_island/cc/services/bootloader.py +++ b/monkey/monkey_island/cc/services/bootloader.py @@ -4,8 +4,7 @@ from bson import ObjectId from monkey_island.cc.database import mongo from monkey_island.cc.services.node import NodeCreationException, NodeService -from monkey_island.cc.services.utils.bootloader_config import ( - MIN_GLIBC_VERSION, SUPPORTED_WINDOWS_VERSIONS) +from monkey_island.cc.services.utils.bootloader_config import MIN_GLIBC_VERSION, SUPPORTED_WINDOWS_VERSIONS from monkey_island.cc.services.utils.node_states import NodeStates diff --git a/monkey/monkey_island/cc/services/config.py b/monkey/monkey_island/cc/services/config.py index 10ce690c0..b4370a63b 100644 --- a/monkey/monkey_island/cc/services/config.py +++ b/monkey/monkey_island/cc/services/config.py @@ -8,21 +8,29 @@ from jsonschema import Draft4Validator, validators import monkey_island.cc.environment.environment_singleton as env_singleton import monkey_island.cc.services.post_breach_files from monkey_island.cc.database import mongo -from monkey_island.cc.encryptor import encryptor -from monkey_island.cc.network_utils import local_ip_addresses +from monkey_island.cc.server_utils.encryptor import encryptor +from monkey_island.cc.services.utils.network_utils import local_ip_addresses from monkey_island.cc.services.config_schema.config_schema import SCHEMA __author__ = "itay.mizeretz" +from monkey_island.cc.services.config_schema.config_value_paths import (AWS_KEYS_PATH, EXPORT_MONKEY_TELEMS_PATH, + LM_HASH_LIST_PATH, NTLM_HASH_LIST_PATH, + PASSWORD_LIST_PATH, SSH_KEYS_PATH, + STARTED_ON_ISLAND_PATH, USER_LIST_PATH) + 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 + ['aws_access_key_id'], + AWS_KEYS_PATH + ['aws_secret_access_key'], + AWS_KEYS_PATH + ['aws_session_token'] ] @@ -65,8 +73,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 @@ -75,12 +86,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) @@ -88,7 +93,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 @@ -97,8 +106,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 @@ -118,34 +127,34 @@ class ConfigService: @staticmethod def creds_add_username(username): - ConfigService.add_item_to_config_set_if_dont_exist('basic.credentials.exploit_user_list', + ConfigService.add_item_to_config_set_if_dont_exist(USER_LIST_PATH, username, should_encrypt=False) @staticmethod def creds_add_password(password): - ConfigService.add_item_to_config_set_if_dont_exist('basic.credentials.exploit_password_list', + ConfigService.add_item_to_config_set_if_dont_exist(PASSWORD_LIST_PATH, password, should_encrypt=True) @staticmethod def creds_add_lm_hash(lm_hash): - ConfigService.add_item_to_config_set_if_dont_exist('internal.exploits.exploit_lm_hash_list', + ConfigService.add_item_to_config_set_if_dont_exist(LM_HASH_LIST_PATH, lm_hash, should_encrypt=True) @staticmethod def creds_add_ntlm_hash(ntlm_hash): - ConfigService.add_item_to_config_set_if_dont_exist('internal.exploits.exploit_ntlm_hash_list', + ConfigService.add_item_to_config_set_if_dont_exist(NTLM_HASH_LIST_PATH, ntlm_hash, should_encrypt=True) @staticmethod def ssh_add_keys(public_key, private_key, user, ip): if not ConfigService.ssh_key_exists( - ConfigService.get_config_value(['internal', 'exploits', 'exploit_ssh_keys'], False, False), user, ip): + ConfigService.get_config_value(SSH_KEYS_PATH, False, False), user, ip): ConfigService.add_item_to_config_set_if_dont_exist( - 'internal.exploits.exploit_ssh_keys', + SSH_KEYS_PATH, { "public_key": public_key, "private_key": private_key, @@ -280,7 +289,7 @@ class ConfigService: """ Same as decrypt_config but for a flat configuration """ - keys = [config_arr_as_array[2] for config_arr_as_array in ENCRYPTED_CONFIG_ARRAYS] + keys = [config_arr_as_array[-1] for config_arr_as_array in ENCRYPTED_CONFIG_VALUES] for key in keys: if isinstance(flat_config[key], collections.Sequence) and not isinstance(flat_config[key], str): @@ -295,7 +304,7 @@ class ConfigService: @staticmethod def _encrypt_or_decrypt_config(config, is_decrypt=False): - for config_arr_as_array in ENCRYPTED_CONFIG_ARRAYS: + for config_arr_as_array in ENCRYPTED_CONFIG_VALUES: config_arr = config parent_config_arr = None @@ -328,8 +337,8 @@ class ConfigService: @staticmethod def is_test_telem_export_enabled(): - return ConfigService.get_config_value(['internal', 'testing', 'export_monkey_telems']) + return ConfigService.get_config_value(EXPORT_MONKEY_TELEMS_PATH) @staticmethod def set_started_on_island(value: bool): - ConfigService.set_config_value(['internal', 'general', 'started_on_island'], value) + ConfigService.set_config_value(STARTED_ON_ISLAND_PATH, value) 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/config_schema.py b/monkey/monkey_island/cc/services/config_schema/config_schema.py index 04e586e71..d1cd7a68c 100644 --- a/monkey/monkey_island/cc/services/config_schema/config_schema.py +++ b/monkey/monkey_island/cc/services/config_schema/config_schema.py @@ -1,11 +1,8 @@ from monkey_island.cc.services.config_schema.basic import BASIC from monkey_island.cc.services.config_schema.basic_network import BASIC_NETWORK -from monkey_island.cc.services.config_schema.definitions.exploiter_classes import \ - EXPLOITER_CLASSES -from monkey_island.cc.services.config_schema.definitions.finger_classes import \ - FINGER_CLASSES -from monkey_island.cc.services.config_schema.definitions.post_breach_actions import \ - POST_BREACH_ACTIONS +from monkey_island.cc.services.config_schema.definitions.exploiter_classes import EXPLOITER_CLASSES +from monkey_island.cc.services.config_schema.definitions.finger_classes import FINGER_CLASSES +from monkey_island.cc.services.config_schema.definitions.post_breach_actions import POST_BREACH_ACTIONS from monkey_island.cc.services.config_schema.definitions.system_info_collector_classes import \ SYSTEM_INFO_COLLECTOR_CLASSES from monkey_island.cc.services.config_schema.internal import INTERNAL diff --git a/monkey/monkey_island/cc/services/config_schema/config_value_paths.py b/monkey/monkey_island/cc/services/config_schema/config_value_paths.py new file mode 100644 index 000000000..5ddbe8605 --- /dev/null +++ b/monkey/monkey_island/cc/services/config_schema/config_value_paths.py @@ -0,0 +1,13 @@ +AWS_KEYS_PATH = ['internal', 'monkey', 'aws_keys'] +STARTED_ON_ISLAND_PATH = ['internal', 'general', 'started_on_island'] +EXPORT_MONKEY_TELEMS_PATH = ['internal', 'testing', 'export_monkey_telems'] +CURRENT_SERVER_PATH = ['internal', 'island_server', 'current_server'] +SSH_KEYS_PATH = ['internal', 'exploits', 'exploit_ssh_keys'] +INACCESSIBLE_SUBNETS_PATH = ['basic_network', 'network_analysis', 'inaccessible_subnets'] +USER_LIST_PATH = ['basic', 'credentials', 'exploit_user_list'] +PASSWORD_LIST_PATH = ['basic', 'credentials', 'exploit_password_list'] +EXPLOITER_CLASSES_PATH = ['basic', 'exploiters', 'exploiter_classes'] +SUBNET_SCAN_LIST_PATH = ['basic_network', 'scope', 'subnet_scan_list'] +LOCAL_NETWORK_SCAN_PATH = ['basic_network', 'scope', 'local_network_scan'] +LM_HASH_LIST_PATH = ['internal', 'exploits', 'exploit_lm_hash_list'] +NTLM_HASH_LIST_PATH = ['internal', 'exploits', 'exploit_ntlm_hash_list'] 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 174133f43..cd756ed61 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,6 @@ -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/internal.py b/monkey/monkey_island/cc/services/config_schema/internal.py index fae309ad5..c70d5f0e5 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": { + "aws_access_key_id": { + "type": "string", + "default": "" + }, + "aws_secret_access_key": { + "type": "string", + "default": "" + }, + "aws_session_token": { + "type": "string", + "default": "" + } + } } } }, diff --git a/monkey/monkey_island/cc/services/config_schema/monkey.py b/monkey/monkey_island/cc/services/config_schema/monkey.py index b47d6a15b..01d463672 100644 --- a/monkey/monkey_island/cc/services/config_schema/monkey.py +++ b/monkey/monkey_island/cc/services/config_schema/monkey.py @@ -1,9 +1,6 @@ -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/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)] 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..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,9 +44,8 @@ EXPLOIT_DATA_MOCK = [{ }] -class TestDisplayedEdgeService(IslandTestCase): +class TestDisplayedEdgeService: def test_get_displayed_edges_by_to(self): - self.clean_edge_db() dst_id = ObjectId() @@ -58,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() @@ -75,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 8ebf45343..f327bc2d1 100644 --- a/monkey/monkey_island/cc/services/edge/test_edge.py +++ b/monkey/monkey_island/cc/services/edge/test_edge.py @@ -1,60 +1,52 @@ 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 +from monkey_island.cc.test_common.fixtures import FixtureEnum 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(FixtureEnum.USES_DATABASE) def test_get_or_create_edge(self): - self.fail_if_not_testing_env() - self.clean_edge_db() - 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/infection_lifecycle.py b/monkey/monkey_island/cc/services/infection_lifecycle.py index f29b9ba71..44d303fc3 100644 --- a/monkey/monkey_island/cc/services/infection_lifecycle.py +++ b/monkey/monkey_island/cc/services/infection_lifecycle.py @@ -8,8 +8,8 @@ from monkey_island.cc.resources.test.utils.telem_store import TestTelemStore from monkey_island.cc.services.config import ConfigService from monkey_island.cc.services.node import NodeService from monkey_island.cc.services.reporting.report import ReportService -from monkey_island.cc.services.reporting.report_generation_synchronisation import ( - is_report_being_generated, safe_generate_reports) +from monkey_island.cc.services.reporting.report_generation_synchronisation import (is_report_being_generated, + safe_generate_reports) logger = logging.getLogger(__name__) diff --git a/monkey/monkey_island/cc/services/node.py b/monkey/monkey_island/cc/services/node.py index a537e4909..1c2c0f9f1 100644 --- a/monkey/monkey_island/cc/services/node.py +++ b/monkey/monkey_island/cc/services/node.py @@ -8,7 +8,7 @@ import monkey_island.cc.services.log from monkey_island.cc import models from monkey_island.cc.database import mongo from monkey_island.cc.models import Monkey -from monkey_island.cc.network_utils import is_local_ips, local_ip_addresses +from monkey_island.cc.services.utils.network_utils import is_local_ips, local_ip_addresses from monkey_island.cc.services.edge.displayed_edge import DisplayedEdgeService from monkey_island.cc.services.edge.edge import EdgeService from monkey_island.cc.services.utils.node_states import NodeStates 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/reporting/exporter_init.py b/monkey/monkey_island/cc/services/reporting/exporter_init.py index 66413a064..391b23cf1 100644 --- a/monkey/monkey_island/cc/services/reporting/exporter_init.py +++ b/monkey/monkey_island/cc/services/reporting/exporter_init.py @@ -2,8 +2,7 @@ import logging from monkey_island.cc.services.remote_run_aws import RemoteRunAwsService from monkey_island.cc.services.reporting.aws_exporter import AWSExporter -from monkey_island.cc.services.reporting.report_exporter_manager import \ - ReportExporterManager +from monkey_island.cc.services.reporting.report_exporter_manager import ReportExporterManager logger = logging.getLogger(__name__) diff --git a/monkey/monkey_island/cc/services/reporting/report.py b/monkey/monkey_island/cc/services/reporting/report.py index 1e77065d4..bc192a5bc 100644 --- a/monkey/monkey_island/cc/services/reporting/report.py +++ b/monkey/monkey_island/cc/services/reporting/report.py @@ -10,16 +10,16 @@ from common.network.network_range import NetworkRange from common.network.segmentation_utils import get_ip_in_src_and_not_in_dst from monkey_island.cc.database import mongo from monkey_island.cc.models import Monkey -from monkey_island.cc.network_utils import get_subnets, local_ip_addresses +from monkey_island.cc.services.utils.network_utils import get_subnets, local_ip_addresses from monkey_island.cc.services.config import ConfigService -from monkey_island.cc.services.configuration.utils import \ - get_config_network_segments_as_subnet_groups +from monkey_island.cc.services.config_schema.config_value_paths import (EXPLOITER_CLASSES_PATH, LOCAL_NETWORK_SCAN_PATH, + PASSWORD_LIST_PATH, SUBNET_SCAN_LIST_PATH, + USER_LIST_PATH) +from monkey_island.cc.services.configuration.utils import get_config_network_segments_as_subnet_groups from monkey_island.cc.services.node import NodeService from monkey_island.cc.services.reporting.pth_report import PTHReportService -from monkey_island.cc.services.reporting.report_exporter_manager import \ - ReportExporterManager -from monkey_island.cc.services.reporting.report_generation_synchronisation import \ - safe_generate_regular_report +from monkey_island.cc.services.reporting.report_exporter_manager import ReportExporterManager +from monkey_island.cc.services.reporting.report_generation_synchronisation import safe_generate_regular_report __author__ = "itay.mizeretz" @@ -620,15 +620,15 @@ class ReportService: @staticmethod def get_config_users(): - return ConfigService.get_config_value(['basic', 'credentials', 'exploit_user_list'], True, True) + return ConfigService.get_config_value(USER_LIST_PATH, True, True) @staticmethod def get_config_passwords(): - return ConfigService.get_config_value(['basic', 'credentials', 'exploit_password_list'], True, True) + return ConfigService.get_config_value(PASSWORD_LIST_PATH, True, True) @staticmethod def get_config_exploits(): - exploits_config_value = ['basic', 'exploiters', 'exploiter_classes'] + exploits_config_value = EXPLOITER_CLASSES_PATH default_exploits = ConfigService.get_default_config(False) for namespace in exploits_config_value: default_exploits = default_exploits[namespace] @@ -642,11 +642,11 @@ class ReportService: @staticmethod def get_config_ips(): - return ConfigService.get_config_value(['basic_network', 'scope', 'subnet_scan_list'], True, True) + return ConfigService.get_config_value(SUBNET_SCAN_LIST_PATH, True, True) @staticmethod def get_config_scan(): - return ConfigService.get_config_value(['basic_network', 'scope', 'local_network_scan'], True, True) + return ConfigService.get_config_value(LOCAL_NETWORK_SCAN_PATH, True, True) @staticmethod def get_issues_overview(issues, config_users, config_passwords): diff --git a/monkey/monkey_island/cc/services/reporting/report_generation_synchronisation.py b/monkey/monkey_island/cc/services/reporting/report_generation_synchronisation.py index 990df24e4..30e406e9f 100644 --- a/monkey/monkey_island/cc/services/reporting/report_generation_synchronisation.py +++ b/monkey/monkey_island/cc/services/reporting/report_generation_synchronisation.py @@ -38,8 +38,7 @@ def safe_generate_regular_report(): def safe_generate_attack_report(): # Local import to avoid circular imports - from monkey_island.cc.services.attack.attack_report import \ - AttackReportService + from monkey_island.cc.services.attack.attack_report import AttackReportService try: __attack_report_generating_lock.acquire() attack_report = AttackReportService.generate_new_report() 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 b5a628fb1..000000000 --- a/monkey/monkey_island/cc/services/reporting/test_pth_report.py +++ /dev/null @@ -1,66 +0,0 @@ -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): - 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( - 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() - - 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( - 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() - - 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) 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 deleted file mode 100644 index dbadffb55..000000000 --- a/monkey/monkey_island/cc/services/reporting/test_zero_trust_service.py +++ /dev/null @@ -1,335 +0,0 @@ -import common.data.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 \ - 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_TRANSIT], - "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): - 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) - - 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)) - - 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) - - 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/services/reporting/zero_trust_service.py b/monkey/monkey_island/cc/services/reporting/zero_trust_service.py deleted file mode 100644 index 7c31fc59a..000000000 --- a/monkey/monkey_island/cc/services/reporting/zero_trust_service.py +++ /dev/null @@ -1,185 +0,0 @@ -from typing import List - -from bson.objectid import ObjectId - -import common.data.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 -EVENT_FETCH_CNT = 50 - - -class ZeroTrustService(object): - @staticmethod - def get_pillars_grades(): - pillars_grades = [] - all_findings = Finding.objects().exclude('events') - 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] = sum(1 for condition in list(test_unexecuted.values()) if condition) - - return pillar_grade - - @staticmethod - def get_principles_status(): - all_principles_statuses = {} - - # init with empty lists - for pillar in zero_trust_consts.PILLARS: - all_principles_statuses[pillar] = [] - - for principle, principle_tests in list(zero_trust_consts.PRINCIPLES_TO_TESTS.items()): - for pillar in zero_trust_consts.PRINCIPLES_TO_PILLARS[principle]: - 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) - } - ) - - return all_principles_statuses - - @staticmethod - def __get_principle_status(principle_tests): - 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")) - - for status in all_statuses: - if zero_trust_consts.ORDERED_TEST_STATUSES.index(status) \ - < zero_trust_consts.ORDERED_TEST_STATUSES.index(worst_status): - worst_status = status - - return worst_status - - @staticmethod - def __get_tests_status(principle_tests): - results = [] - for test in principle_tests: - test_findings = Finding.objects(test=test).exclude('events') - 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) - } - ) - return results - - @staticmethod - def __get_lcd_worst_status_for_test(all_findings_for_test): - """ - :param all_findings_for_test: All findings of a specific test (get this using Finding.objects(test={A_TEST})) - :return: the "worst" (i.e. most severe) status out of the given findings. - lcd stands for lowest common denominator. - """ - current_worst_status = zero_trust_consts.STATUS_UNEXECUTED - for finding in all_findings_for_test: - if zero_trust_consts.ORDERED_TEST_STATUSES.index(finding.status) \ - < zero_trust_consts.ORDERED_TEST_STATUSES.index(current_worst_status): - current_worst_status = finding.status - - return current_worst_status - - @staticmethod - def get_all_findings(): - pipeline = [{'$addFields': {'oldest_events': {'$slice': ['$events', 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 - - @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'], - 'latest_events': finding['latest_events'], - 'oldest_events': finding['oldest_events'], - 'event_count': finding['event_count'] - } - return enriched_finding - - @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().exclude('events') - 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 - - @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)) diff --git a/monkey/monkey_island/cc/services/telemetry/processing/exploit.py b/monkey/monkey_island/cc/services/telemetry/processing/exploit.py index e67b4182a..9b8c1427c 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/exploit.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/exploit.py @@ -2,14 +2,12 @@ import copy import dateutil -from monkey_island.cc.encryptor import encryptor +from monkey_island.cc.server_utils.encryptor import encryptor from monkey_island.cc.models import Monkey 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.processing.utils import get_edge_by_scan_or_exploit_telemetry +from monkey_island.cc.services.telemetry.zero_trust_checks.machine_exploited import check_machine_exploited def process_exploit_telemetry(telemetry_json): @@ -18,7 +16,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 367ca87d9..b06b638c8 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/post_breach.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/post_breach.py @@ -1,10 +1,9 @@ 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 \ - 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 +12,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/processing.py b/monkey/monkey_island/cc/services/telemetry/processing/processing.py index 566c11dcc..151fd672f 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/processing.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/processing.py @@ -1,31 +1,28 @@ import logging -from monkey_island.cc.services.telemetry.processing.exploit import \ - process_exploit_telemetry -from monkey_island.cc.services.telemetry.processing.post_breach import \ - process_post_breach_telemetry -from monkey_island.cc.services.telemetry.processing.scan import \ - process_scan_telemetry -from monkey_island.cc.services.telemetry.processing.state import \ - process_state_telemetry -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 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 process_post_breach_telemetry +from monkey_island.cc.services.telemetry.processing.scan import process_scan_telemetry +from monkey_island.cc.services.telemetry.processing.scoutsuite import process_scoutsuite_telemetry +from monkey_island.cc.services.telemetry.processing.state import process_state_telemetry +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 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, + TelemCategoryEnum.SCOUTSUITE: process_scoutsuite_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, } diff --git a/monkey/monkey_island/cc/services/telemetry/processing/scan.py b/monkey/monkey_island/cc/services/telemetry/processing/scan.py index 70675c995..d0b204d16 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/scan.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/scan.py @@ -1,21 +1,18 @@ from monkey_island.cc.database import mongo from monkey_island.cc.models import Monkey 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.processing.utils import get_edge_by_scan_or_exploit_telemetry +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/scoutsuite.py b/monkey/monkey_island/cc/services/telemetry/processing/scoutsuite.py new file mode 100644 index 000000000..8ee4737e8 --- /dev/null +++ b/monkey/monkey_island/cc/services/telemetry/processing/scoutsuite.py @@ -0,0 +1,31 @@ +import json + +from monkey_island.cc.database import mongo +from monkey_island.cc.models.zero_trust.scoutsuite_data_json import ScoutSuiteRawDataJson +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): + # Encode data to json, because mongo can't save it as document (invalid document keys) + telemetry_json['data'] = json.dumps(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) + + +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) + ScoutSuiteZTFindingService.process_rule(finding, rule) + + +def update_data(telemetry_json): + mongo.db.scoutsuite.insert_one( + {'guid': telemetry_json['monkey_guid']}, + {'results': telemetry_json['data']}) 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.py b/monkey/monkey_island/cc/services/telemetry/processing/system_info.py index 88eb9285c..d3e7cfb54 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/system_info.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/system_info.py @@ -1,6 +1,6 @@ import logging -from monkey_island.cc.encryptor import encryptor +from monkey_island.cc.server_utils.encryptor import encryptor from monkey_island.cc.services.config import ConfigService from monkey_island.cc.services.node import NodeService from monkey_island.cc.services.telemetry.processing.system_info_collectors.system_info_telemetry_dispatcher 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 639a392ce..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 @@ -1,18 +1,13 @@ import logging import typing -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 common.common_consts.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 +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__) @@ -20,7 +15,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] } 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..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,14 +3,10 @@ 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): - self.fail_if_not_testing_env() - self.clean_monkey_db() - # Arrange monkey_guid = str(uuid.uuid4()) a_monkey = Monkey(guid=monkey_guid) @@ -28,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 2af2d5970..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"}} @@ -36,9 +37,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() @@ -56,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/processing/tunnel.py b/monkey/monkey_island/cc/services/telemetry/processing/tunnel.py index ef5ea0ff9..1e20e5443 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/tunnel.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/tunnel.py @@ -1,12 +1,10 @@ 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.processing.utils import get_tunnel_host_ip_from_proxy_field +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_tests/__init__.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/__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/telemetry/zero_trust_checks/__init__.py 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 72% 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 336567c7c..a6b90cc45 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 @@ -1,15 +1,13 @@ 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 from monkey_island.cc.models.zero_trust.event import Event -from monkey_island.cc.services.telemetry.zero_trust_tests.known_anti_viruses import \ - ANTI_VIRUS_KNOWN_PROCESS_NAMES +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_findings.monkey_zt_finding_service import MonkeyZTFindingService -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,9 +30,8 @@ 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( - 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_tests/communicate_as_new_user.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/communicate_as_new_user.py similarity index 55% 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 d822206af..2ef914786 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,23 +1,20 @@ -import common.data.zero_trust_consts as zero_trust_consts -from monkey_island.cc.models.zero_trust.aggregate_finding import \ - AggregateFinding +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_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 = \ "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( - 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) - ] - ) +def check_new_user_communication(current_monkey, success, message): + 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) + ]) def get_attempt_event(current_monkey): 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 75% 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 447b2dee8..a5d42ef2c 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 @@ -1,16 +1,15 @@ 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) from monkey_island.cc.models.zero_trust.event import Event +from monkey_island.cc.services.zero_trust.monkey_findings.monkey_zt_finding_service import MonkeyZTFindingService 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,10 @@ def test_open_data_endpoints(telemetry_json): event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK )) - AggregateFinding.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) - AggregateFinding.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) - add_malicious_activity_to_timeline(events) + MonkeyZTFindingService.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 64% 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 06d97d66d..d6813259c 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.data.zero_trust_consts as zero_trust_consts -from monkey_island.cc.models.zero_trust.aggregate_finding import ( - AggregateFinding, add_malicious_activity_to_timeline) +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_findings.monkey_zt_finding_service import MonkeyZTFindingService -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,7 @@ def test_machine_exploited(current_monkey, exploit_successful, exploiter, target ) status = zero_trust_consts.STATUS_FAILED - AggregateFinding.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) - add_malicious_activity_to_timeline(events) + MonkeyZTFindingService.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 82% 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 a46dbc4a3..d5a56b36d 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 @@ -1,15 +1,12 @@ 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) +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.configuration.utils import get_config_network_segments_as_subnet_groups +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." @@ -19,7 +16,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: @@ -29,10 +26,10 @@ def test_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], + MonkeyZTFindingService.create_or_add_to_existing( + test=zero_trust_consts.TEST_SEGMENTATION, status=zero_trust_consts.STATUS_FAILED, - segmentation_event=event + events=[event] ) @@ -73,7 +70,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) @@ -93,10 +90,10 @@ 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), + MonkeyZTFindingService.create_or_add_to_existing( 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/telemetry/zero_trust_checks/test_segmentation_checks.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/test_segmentation_checks.py new file mode 100644 index 000000000..ca58549d1 --- /dev/null +++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/test_segmentation_checks.py @@ -0,0 +1,56 @@ +import uuid + +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 +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 + +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: + + def test_create_findings_for_all_done_pairs(self): + all_subnets = [FIRST_SUBNET, SECOND_SUBNET, THIRD_SUBNET] + + monkey = Monkey( + guid=str(uuid.uuid4()), + ip_addresses=[FIRST_SUBNET]) + + # no findings + 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 + zt_seg_findings = Finding.objects(test=zero_trust_consts.TEST_SEGMENTATION, + status=zero_trust_consts.STATUS_PASSED) + + # 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( + status=zero_trust_consts.STATUS_FAILED, + test=zero_trust_consts.TEST_SEGMENTATION, + 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, + 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/telemetry/zero_trust_tests/tunneling.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/tunneling.py similarity index 59% 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 f4d508156..4b755be98 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,11 @@ -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 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.telemetry.processing.utils import get_tunnel_host_ip_from_proxy_field +from monkey_island.cc.services.zero_trust.monkey_findings.monkey_zt_finding_service import MonkeyZTFindingService -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 +18,7 @@ def test_tunneling_violation(tunnel_telemetry_json): timestamp=tunnel_telemetry_json['timestamp'] )] - AggregateFinding.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) - add_malicious_activity_to_timeline(tunneling_events) + MonkeyZTFindingService.add_malicious_activity_to_timeline(tunneling_events) 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 deleted file mode 100644 index b2aeaf524..000000000 --- a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/test_segmentation_zt_tests.py +++ /dev/null @@ -1,55 +0,0 @@ -import uuid - -import common.data.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 -from monkey_island.cc.models.zero_trust.segmentation_finding import \ - SegmentationFinding -from monkey_island.cc.services.telemetry.zero_trust_tests.segmentation import \ - create_or_add_findings_for_all_pairs -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 TestSegmentationTests(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( - guid=str(uuid.uuid4()), - ip_addresses=[FIRST_SUBNET]) - - # no findings - self.assertEqual(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) - - # This is a monkey from 2nd subnet communicated with 1st subnet. - SegmentationFinding.create_or_add_to_existing_finding( - [FIRST_SUBNET, SECOND_SUBNET], - zero_trust_consts.STATUS_FAILED, - Event.create_event(title="sdf", message="asd", event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK) - ) - - 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) diff --git a/monkey/monkey_island/cc/network_utils.py b/monkey/monkey_island/cc/services/utils/network_utils.py similarity index 100% rename from monkey/monkey_island/cc/network_utils.py rename to monkey/monkey_island/cc/services/utils/network_utils.py diff --git a/monkey/monkey_island/cc/services/utils/node_states_test.py b/monkey/monkey_island/cc/services/utils/node_states_test.py index 203ccb551..1204cb881 100644 --- a/monkey/monkey_island/cc/services/utils/node_states_test.py +++ b/monkey/monkey_island/cc/services/utils/node_states_test.py @@ -1,7 +1,6 @@ from unittest import TestCase -from monkey_island.cc.services.utils.node_states import ( - NodeStates, NoGroupsFoundException) +from monkey_island.cc.services.utils.node_states import NodeStates, NoGroupsFoundException class TestNodeGroups(TestCase): diff --git a/monkey/monkey_island/cc/services/wmi_handler.py b/monkey/monkey_island/cc/services/wmi_handler.py index cf67d6c7f..284ae95df 100644 --- a/monkey/monkey_island/cc/services/wmi_handler.py +++ b/monkey/monkey_island/cc/services/wmi_handler.py @@ -1,6 +1,5 @@ from monkey_island.cc.database import mongo -from monkey_island.cc.services.groups_and_users_consts import (GROUPTYPE, - USERTYPE) +from monkey_island.cc.services.groups_and_users_consts import GROUPTYPE, USERTYPE __author__ = 'maor.rayzin' diff --git a/monkey/monkey_island/cc/testing/__init__.py b/monkey/monkey_island/cc/services/zero_trust/__init__.py similarity index 100% rename from monkey/monkey_island/cc/testing/__init__.py rename to monkey/monkey_island/cc/services/zero_trust/__init__.py 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_findings/monkey_zt_details_service.py b/monkey/monkey_island/cc/services/zero_trust/monkey_findings/monkey_zt_details_service.py new file mode 100644 index 000000000..167934d29 --- /dev/null +++ b/monkey/monkey_island/cc/services/zero_trust/monkey_findings/monkey_zt_details_service.py @@ -0,0 +1,44 @@ +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 + + +# How many events of a single finding to return to UI. +# 100 will return 50 latest and 50 oldest events from a finding +MAX_EVENT_FETCH_CNT = 100 + + +class MonkeyZTDetailsService: + + @staticmethod + def fetch_details_for_display(finding_id: ObjectId) -> dict: + pipeline = [{'$match': {'_id': finding_id}}, + {'$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']}] + 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 + else: + raise FindingWithoutDetailsError(f"Finding {finding_id} had no details.") + + @staticmethod + 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 latest_events[-1 * overlap_count:] 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 new file mode 100644 index 000000000..d8e439c71 --- /dev/null +++ b/monkey/monkey_island/cc/services/zero_trust/monkey_findings/monkey_zt_finding_service.py @@ -0,0 +1,54 @@ +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.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: 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). + + :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 = 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: + MonkeyZTFindingService.create_new_finding(test, status, events) + else: + # Now we know for sure this is the only one + MonkeyZTFindingService.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() + MonkeyFinding.save_finding(test, status, details) + + @staticmethod + 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 = MonkeyFinding.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): + 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/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 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..80df71786 --- /dev/null +++ b/monkey/monkey_island/cc/services/zero_trust/monkey_findings/test_monkey_zt_finding_service.py @@ -0,0 +1,71 @@ +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.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 + +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: + + @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]]) + # 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]]) + # 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]]) + # Assert there was a new finding created, because test and status is different + assert len(MonkeyFinding.objects()) == 2 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/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/rule_names/cloudformation_rules.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/cloudformation_rules.py new file mode 100644 index 000000000..f8c87083e --- /dev/null +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/cloudformation_rules.py @@ -0,0 +1,7 @@ +from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rule_name_enum import RuleNameEnum + + +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 new file mode 100644 index 000000000..886999341 --- /dev/null +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/cloudtrail_rules.py @@ -0,0 +1,11 @@ +from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rule_name_enum import RuleNameEnum + + +class CloudTrailRules(RuleNameEnum): + # Logging + CLOUDTRAIL_DUPLICATED_GLOBAL_SERVICES_LOGGING = 'cloudtrail-duplicated-global-services-logging' + CLOUDTRAIL_NO_DATA_LOGGING = 'cloudtrail-no-data-logging' + CLOUDTRAIL_NO_GLOBAL_SERVICES_LOGGING = 'cloudtrail-no-global-services-logging' + CLOUDTRAIL_NO_LOG_FILE_VALIDATION = 'cloudtrail-no-log-file-validation' + CLOUDTRAIL_NO_LOGGING = 'cloudtrail-no-logging' + CLOUDTRAIL_NOT_CONFIGURED = 'cloudtrail-not-configured' 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 new file mode 100644 index 000000000..d22baafc7 --- /dev/null +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/cloudwatch_rules.py @@ -0,0 +1,6 @@ +from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rule_name_enum import RuleNameEnum + + +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 new file mode 100644 index 000000000..5d86b0b3e --- /dev/null +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/config_rules.py @@ -0,0 +1,6 @@ +from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rule_name_enum import RuleNameEnum + + +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 new file mode 100644 index 000000000..dddf18b99 --- /dev/null +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/ec2_rules.py @@ -0,0 +1,35 @@ +from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rule_name_enum import RuleNameEnum + + +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' + 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' + EC2_SECURITY_GROUP_WHITELISTS_AWS = 'ec2-security-group-whitelists-aws' + + # Encryption + EBS_SNAPSHOT_NOT_ENCRYPTED = 'ec2-ebs-snapshot-not-encrypted' + EBS_VOLUME_NOT_ENCRYPTED = 'ec2-ebs-volume-not-encrypted' + EC2_INSTANCE_WITH_USER_DATA_SECRETS = 'ec2-instance-with-user-data-secrets' + + # Permissive policies + AMI_PUBLIC = 'ec2-ami-public' + EC2_DEFAULT_SECURITY_GROUP_IN_USE = 'ec2-default-security-group-in-use' + EC2_DEFAULT_SECURITY_GROUP_WITH_RULES = 'ec2-default-security-group-with-rules' + EC2_EBS_SNAPSHOT_PUBLIC = 'ec2-ebs-snapshot-public' 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 new file mode 100644 index 000000000..0d1d4e5d9 --- /dev/null +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/elb_rules.py @@ -0,0 +1,10 @@ +from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rule_name_enum import RuleNameEnum + + +class ELBRules(RuleNameEnum): + # Logging + ELB_NO_ACCESS_LOGS = 'elb-no-access-logs' + + # Encryption + ELB_LISTENER_ALLOWING_CLEARTEXT = 'elb-listener-allowing-cleartext' + ELB_OLDER_SSL_POLICY = 'elb-older-ssl-policy' 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 new file mode 100644 index 000000000..f7a264cf3 --- /dev/null +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/elbv2_rules.py @@ -0,0 +1,16 @@ +from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rule_name_enum import RuleNameEnum + + +class ELBv2Rules(RuleNameEnum): + # Encryption + ELBV2_LISTENER_ALLOWING_CLEARTEXT = 'elbv2-listener-allowing-cleartext' + ELBV2_OLDER_SSL_POLICY = 'elbv2-older-ssl-policy' + + # Logging + ELBV2_NO_ACCESS_LOGS = 'elbv2-no-access-logs' + + # Data loss prevention + ELBV2_NO_DELETION_PROTECTION = 'elbv2-no-deletion-protection' + + # Service security + ELBV2_HTTP_REQUEST_SMUGGLING = 'elbv2-http-request-smuggling' 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 new file mode 100644 index 000000000..fef58e066 --- /dev/null +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/iam_rules.py @@ -0,0 +1,39 @@ +from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rule_name_enum import RuleNameEnum + + +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' + IAM_PASSWORD_POLICY_NO_EXPIRATION = 'iam-password-policy-no-expiration' + IAM_PASSWORD_POLICY_REUSE_ENABLED = 'iam-password-policy-reuse-enabled' + IAM_USER_WITH_PASSWORD_AND_KEY = 'iam-user-with-password-and-key' + IAM_ASSUME_ROLE_LACKS_EXTERNAL_ID_AND_MFA = 'iam-assume-role-lacks-external-id-and-mfa' + IAM_USER_WITHOUT_MFA = 'iam-user-without-mfa' + IAM_ROOT_ACCOUNT_NO_MFA = 'iam-root-account-no-mfa' + IAM_ROOT_ACCOUNT_WITH_ACTIVE_KEYS = 'iam-root-account-with-active-keys' + IAM_USER_NO_INACTIVE_KEY_ROTATION = 'iam-user-no-Inactive-key-rotation' + IAM_USER_WITH_MULTIPLE_ACCESS_KEYS = 'iam-user-with-multiple-access-keys' + + # Least privilege + IAM_ASSUME_ROLE_POLICY_ALLOWS_ALL = 'iam-assume-role-policy-allows-all' + IAM_EC2_ROLE_WITHOUT_INSTANCES = 'iam-ec2-role-without-instances' + IAM_GROUP_WITH_INLINE_POLICIES = 'iam-group-with-inline-policies' + IAM_GROUP_WITH_NO_USERS = 'iam-group-with-no-users' + IAM_INLINE_GROUP_POLICY_ALLOWS_IAM_PASSROLE = 'iam-inline-group-policy-allows-iam-PassRole' + IAM_INLINE_GROUP_POLICY_ALLOWS_NOTACTIONS = 'iam-inline-group-policy-allows-NotActions' + IAM_INLINE_GROUP_POLICY_ALLOWS_STS_ASSUMEROLE = 'iam-inline-group-policy-allows-sts-AssumeRole' + IAM_INLINE_ROLE_POLICY_ALLOWS_IAM_PASSROLE = 'iam-inline-role-policy-allows-iam-PassRole' + IAM_INLINE_ROLE_POLICY_ALLOWS_NOTACTIONS = 'iam-inline-role-policy-allows-NotActions' + IAM_INLINE_ROLE_POLICY_ALLOWS_STS_ASSUMEROLE = 'iam-inline-role-policy-allows-sts-AssumeRole' + IAM_INLINE_USER_POLICY_ALLOWS_IAM_PASSROLE = 'iam-inline-user-policy-allows-iam-PassRole' + IAM_INLINE_USER_POLICY_ALLOWS_NOTACTIONS = 'iam-inline-user-policy-allows-NotActions' + IAM_INLINE_USER_POLICY_ALLOWS_STS_ASSUMEROLE = 'iam-inline-user-policy-allows-sts-AssumeRole' + IAM_MANAGED_POLICY_ALLOWS_IAM_PASSROLE = 'iam-managed-policy-allows-iam-PassRole' + IAM_MANAGED_POLICY_ALLOWS_NOTACTIONS = 'iam-managed-policy-allows-NotActions' + IAM_MANAGED_POLICY_ALLOWS_STS_ASSUMEROLE = 'iam-managed-policy-allows-sts-AssumeRole' + IAM_MANAGED_POLICY_NO_ATTACHMENTS = 'iam-managed-policy-no-attachments' + IAM_ROLE_WITH_INLINE_POLICIES = 'iam-role-with-inline-policies' + IAM_ROOT_ACCOUNT_USED_RECENTLY = 'iam-root-account-used-recently' + IAM_ROOT_ACCOUNT_WITH_ACTIVE_CERTS = 'iam-root-account-with-active-certs' + IAM_USER_WITH_INLINE_POLICIES = 'iam-user-with-inline-policies' 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 new file mode 100644 index 000000000..b303c8573 --- /dev/null +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/rds_rules.py @@ -0,0 +1,19 @@ +from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rule_name_enum import RuleNameEnum + + +class RDSRules(RuleNameEnum): + # Encryption + RDS_INSTANCE_STORAGE_NOT_ENCRYPTED = 'rds-instance-storage-not-encrypted' + + # Data loss prevention + RDS_INSTANCE_BACKUP_DISABLED = 'rds-instance-backup-disabled' + RDS_INSTANCE_SHORT_BACKUP_RETENTION_PERIOD = 'rds-instance-short-backup-retention-period' + RDS_INSTANCE_SINGLE_AZ = 'rds-instance-single-az' + + # Firewalls + RDS_SECURITY_GROUP_ALLOWS_ALL = 'rds-security-group-allows-all' + RDS_SNAPSHOT_PUBLIC = 'rds-snapshot-public' + + # Service security + RDS_INSTANCE_CA_CERTIFICATE_DEPRECATED = 'rds-instance-ca-certificate-deprecated' + RDS_INSTANCE_NO_MINOR_UPGRADE = 'rds-instance-no-minor-upgrade' 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 new file mode 100644 index 000000000..2538cf54d --- /dev/null +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/redshift_rules.py @@ -0,0 +1,19 @@ +from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rule_name_enum import RuleNameEnum + + +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' + + # Firewalls + REDSHIFT_SECURITY_GROUP_WHITELISTS_ALL = 'redshift-security-group-whitelists-all' + + # Restrictive Policies + REDSHIFT_CLUSTER_PUBLICLY_ACCESSIBLE = 'redshift-cluster-publicly-accessible' + + # Logging + REDSHIFT_PARAMETER_GROUP_LOGGING_DISABLED = 'redshift-parameter-group-logging-disabled' + + # Service security + REDSHIFT_CLUSTER_NO_VERSION_UPGRADE = 'redshift-cluster-no-version-upgrade' 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/consts/rule_names/s3_rules.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/s3_rules.py new file mode 100644 index 000000000..4ba27a57a --- /dev/null +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/s3_rules.py @@ -0,0 +1,29 @@ +from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rule_name_enum import RuleNameEnum + + +class S3Rules(RuleNameEnum): + # Encryption + S3_BUCKET_ALLOWING_CLEARTEXT = 's3-bucket-allowing-cleartext' + S3_BUCKET_NO_DEFAULT_ENCRYPTION = 's3-bucket-no-default-encryption' + + # Data loss prevention + S3_BUCKET_NO_MFA_DELETE = 's3-bucket-no-mfa-delete' + S3_BUCKET_NO_VERSIONING = 's3-bucket-no-versioning' + + # Logging + S3_BUCKET_NO_LOGGING = 's3-bucket-no-logging' + + # Permissive access rules + S3_BUCKET_AUTHENTICATEDUSERS_WRITE_ACP = 's3-bucket-AuthenticatedUsers-write_acp' + S3_BUCKET_AUTHENTICATEDUSERS_WRITE = 's3-bucket-AuthenticatedUsers-write' + S3_BUCKET_AUTHENTICATEDUSERS_READ_ACP = 's3-bucket-AuthenticatedUsers-read_acp' + S3_BUCKET_AUTHENTICATEDUSERS_READ = 's3-bucket-AuthenticatedUsers-read' + S3_BUCKET_ALLUSERS_WRITE_ACP = 's3-bucket-AllUsers-write_acp' + S3_BUCKET_ALLUSERS_WRITE = 's3-bucket-AllUsers-write' + S3_BUCKET_ALLUSERS_READ_ACP = 's3-bucket-AllUsers-read_acp' + S3_BUCKET_ALLUSERS_READ = 's3-bucket-AllUsers-read' + S3_BUCKET_WORLD_PUT_POLICY = 's3-bucket-world-Put-policy' + S3_BUCKET_WORLD_POLICY_STAR = 's3-bucket-world-policy-star' + S3_BUCKET_WORLD_LIST_POLICY = 's3-bucket-world-List-policy' + S3_BUCKET_WORLD_GET_POLICY = 's3-bucket-world-Get-policy' + S3_BUCKET_WORLD_DELETE_POLICY = 's3-bucket-world-Delete-policy' 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 new file mode 100644 index 000000000..4cb875c6d --- /dev/null +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/ses_rules.py @@ -0,0 +1,8 @@ +from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rule_name_enum import RuleNameEnum + + +class SESRules(RuleNameEnum): + + # Permissive policies + SES_IDENTITY_WORLD_SENDRAWEMAIL_POLICY = 'ses-identity-world-SendRawEmail-policy' + SES_IDENTITY_WORLD_SENDEMAIL_POLICY = 'ses-identity-world-SendEmail-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 new file mode 100644 index 000000000..9fb847114 --- /dev/null +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/sns_rules.py @@ -0,0 +1,13 @@ +from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rule_name_enum import RuleNameEnum + + +class SNSRules(RuleNameEnum): + + # Permissive policies + SNS_TOPIC_WORLD_SUBSCRIBE_POLICY = 'sns-topic-world-Subscribe-policy' + SNS_TOPIC_WORLD_SETTOPICATTRIBUTES_POLICY = 'sns-topic-world-SetTopicAttributes-policy' + SNS_TOPIC_WORLD_REMOVEPERMISSION_POLICY = 'sns-topic-world-RemovePermission-policy' + SNS_TOPIC_WORLD_RECEIVE_POLICY = 'sns-topic-world-Receive-policy' + SNS_TOPIC_WORLD_PUBLISH_POLICY = 'sns-topic-world-Publish-policy' + SNS_TOPIC_WORLD_DELETETOPIC_POLICY = 'sns-topic-world-DeleteTopic-policy' + SNS_TOPIC_WORLD_ADDPERMISSION_POLICY = 'sns-topic-world-AddPermission-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 new file mode 100644 index 000000000..cc5c774e3 --- /dev/null +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/sqs_rules.py @@ -0,0 +1,13 @@ +from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rule_name_enum import RuleNameEnum + + +class SQSRules(RuleNameEnum): + + # Permissive policies + SQS_QUEUE_WORLD_SENDMESSAGE_POLICY = 'sqs-queue-world-SendMessage-policy' + SQS_QUEUE_WORLD_RECEIVEMESSAGE_POLICY = 'sqs-queue-world-ReceiveMessage-policy' + SQS_QUEUE_WORLD_PURGEQUEUE_POLICY = 'sqs-queue-world-PurgeQueue-policy' + SQS_QUEUE_WORLD_GETQUEUEURL_POLICY = 'sqs-queue-world-GetQueueUrl-policy' + SQS_QUEUE_WORLD_GETQUEUEATTRIBUTES_POLICY = 'sqs-queue-world-GetQueueAttributes-policy' + SQS_QUEUE_WORLD_DELETEMESSAGE_POLICY = 'sqs-queue-world-DeleteMessage-policy' + SQS_QUEUE_WORLD_CHANGEMESSAGEVISIBILITY_POLICY = 'sqs-queue-world-ChangeMessageVisibility-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 new file mode 100644 index 000000000..4dcbd4f1a --- /dev/null +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/vpc_rules.py @@ -0,0 +1,15 @@ +from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rule_name_enum import RuleNameEnum + + +class VPCRules(RuleNameEnum): + # Logging + SUBNET_WITHOUT_FLOW_LOG = 'vpc-subnet-without-flow-log' + + # Firewalls + SUBNET_WITH_ALLOW_ALL_INGRESS_ACLS = 'vpc-subnet-with-allow-all-ingress-acls' + SUBNET_WITH_ALLOW_ALL_EGRESS_ACLS = 'vpc-subnet-with-allow-all-egress-acls' + NETWORK_ACL_NOT_USED = 'vpc-network-acl-not-used' + DEFAULT_NETWORK_ACLS_ALLOW_ALL_INGRESS = 'vpc-default-network-acls-allow-all-ingress' + DEFAULT_NETWORK_ACLS_ALLOW_ALL_EGRESS = 'vpc-default-network-acls-allow-all-egress' + CUSTOM_NETWORK_ACLS_ALLOW_ALL_INGRESS = 'vpc-custom-network-acls-allow-all-ingress' + CUSTOM_NETWORK_ACLS_ALLOW_ALL_EGRESS = 'vpc-custom-network-acls-allow-all-egress' 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 new file mode 100644 index 000000000..251e57324 --- /dev/null +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/scoutsuite_finding_maps.py @@ -0,0 +1,191 @@ +from abc import ABC, abstractmethod +from typing import List + +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.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 +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 +class ScoutSuiteFindingMap(ABC): + @property + @abstractmethod + def rules(self) -> List[RuleNameEnum]: + pass + + @property + @abstractmethod + def test(self) -> str: + pass + + +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, + 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, + EC2Rules.EC2_SECURITY_GROUP_WHITELISTS_AWS, + VPCRules.SUBNET_WITH_ALLOW_ALL_INGRESS_ACLS, + VPCRules.SUBNET_WITH_ALLOW_ALL_EGRESS_ACLS, + VPCRules.NETWORK_ACL_NOT_USED, + VPCRules.DEFAULT_NETWORK_ACLS_ALLOW_ALL_INGRESS, + VPCRules.DEFAULT_NETWORK_ACLS_ALLOW_ALL_EGRESS, + VPCRules.CUSTOM_NETWORK_ACLS_ALLOW_ALL_INGRESS, + VPCRules.CUSTOM_NETWORK_ACLS_ALLOW_ALL_EGRESS, + RDSRules.RDS_SECURITY_GROUP_ALLOWS_ALL, + RedshiftRules.REDSHIFT_SECURITY_GROUP_WHITELISTS_ALL + ] + + test = zero_trust_consts.TEST_SCOUTSUITE_PERMISSIVE_FIREWALL_RULES + + +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, + RDSRules.RDS_INSTANCE_STORAGE_NOT_ENCRYPTED, RedshiftRules.REDSHIFT_CLUSTER_DATABASE_NOT_ENCRYPTED, + RedshiftRules.REDSHIFT_PARAMETER_GROUP_SSL_NOT_REQUIRED, + S3Rules.S3_BUCKET_ALLOWING_CLEARTEXT, S3Rules.S3_BUCKET_NO_DEFAULT_ENCRYPTION, + ELBRules.ELB_LISTENER_ALLOWING_CLEARTEXT, + ELBRules.ELB_OLDER_SSL_POLICY] + + test = zero_trust_consts.TEST_SCOUTSUITE_UNENCRYPTED_DATA + + +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] + + test = zero_trust_consts.TEST_SCOUTSUITE_DATA_LOSS_PREVENTION + + +class SecureAuthentication(ScoutSuiteFindingMap): + rules = [ + IAMRules.IAM_USER_NO_ACTIVE_KEY_ROTATION, + IAMRules.IAM_PASSWORD_POLICY_MINIMUM_LENGTH, + IAMRules.IAM_PASSWORD_POLICY_NO_EXPIRATION, + IAMRules.IAM_PASSWORD_POLICY_REUSE_ENABLED, + IAMRules.IAM_USER_WITH_PASSWORD_AND_KEY, + IAMRules.IAM_ASSUME_ROLE_LACKS_EXTERNAL_ID_AND_MFA, + IAMRules.IAM_USER_WITHOUT_MFA, + IAMRules.IAM_ROOT_ACCOUNT_NO_MFA, + IAMRules.IAM_ROOT_ACCOUNT_WITH_ACTIVE_KEYS, + IAMRules.IAM_USER_NO_INACTIVE_KEY_ROTATION, + IAMRules.IAM_USER_WITH_MULTIPLE_ACCESS_KEYS + ] + + test = zero_trust_consts.TEST_SCOUTSUITE_SECURE_AUTHENTICATION + + +class RestrictivePolicies(ScoutSuiteFindingMap): + rules = [ + IAMRules.IAM_ASSUME_ROLE_POLICY_ALLOWS_ALL, + IAMRules.IAM_EC2_ROLE_WITHOUT_INSTANCES, + IAMRules.IAM_GROUP_WITH_INLINE_POLICIES, + IAMRules.IAM_GROUP_WITH_NO_USERS, + IAMRules.IAM_INLINE_GROUP_POLICY_ALLOWS_IAM_PASSROLE, + IAMRules.IAM_INLINE_GROUP_POLICY_ALLOWS_NOTACTIONS, + IAMRules.IAM_INLINE_GROUP_POLICY_ALLOWS_STS_ASSUMEROLE, + IAMRules.IAM_INLINE_ROLE_POLICY_ALLOWS_IAM_PASSROLE, + IAMRules.IAM_INLINE_ROLE_POLICY_ALLOWS_NOTACTIONS, + IAMRules.IAM_INLINE_ROLE_POLICY_ALLOWS_STS_ASSUMEROLE, + IAMRules.IAM_INLINE_USER_POLICY_ALLOWS_IAM_PASSROLE, + IAMRules.IAM_INLINE_USER_POLICY_ALLOWS_NOTACTIONS, + IAMRules.IAM_INLINE_USER_POLICY_ALLOWS_STS_ASSUMEROLE, + IAMRules.IAM_MANAGED_POLICY_ALLOWS_IAM_PASSROLE, + IAMRules.IAM_MANAGED_POLICY_ALLOWS_NOTACTIONS, + IAMRules.IAM_MANAGED_POLICY_ALLOWS_STS_ASSUMEROLE, + IAMRules.IAM_MANAGED_POLICY_NO_ATTACHMENTS, + IAMRules.IAM_ROLE_WITH_INLINE_POLICIES, + IAMRules.IAM_ROOT_ACCOUNT_USED_RECENTLY, + IAMRules.IAM_ROOT_ACCOUNT_WITH_ACTIVE_CERTS, + IAMRules.IAM_USER_WITH_INLINE_POLICIES, + EC2Rules.AMI_PUBLIC, + S3Rules.S3_BUCKET_AUTHENTICATEDUSERS_WRITE_ACP, + S3Rules.S3_BUCKET_AUTHENTICATEDUSERS_WRITE, + S3Rules.S3_BUCKET_AUTHENTICATEDUSERS_READ_ACP, + S3Rules.S3_BUCKET_AUTHENTICATEDUSERS_READ, + S3Rules.S3_BUCKET_ALLUSERS_WRITE_ACP, + S3Rules.S3_BUCKET_ALLUSERS_WRITE, + S3Rules.S3_BUCKET_ALLUSERS_READ_ACP, + S3Rules.S3_BUCKET_ALLUSERS_READ, + S3Rules.S3_BUCKET_WORLD_PUT_POLICY, + S3Rules.S3_BUCKET_WORLD_POLICY_STAR, + S3Rules.S3_BUCKET_WORLD_LIST_POLICY, + S3Rules.S3_BUCKET_WORLD_GET_POLICY, + S3Rules.S3_BUCKET_WORLD_DELETE_POLICY, + EC2Rules.EC2_DEFAULT_SECURITY_GROUP_IN_USE, + EC2Rules.EC2_DEFAULT_SECURITY_GROUP_WITH_RULES, + EC2Rules.EC2_EBS_SNAPSHOT_PUBLIC, + SQSRules.SQS_QUEUE_WORLD_SENDMESSAGE_POLICY, + SQSRules.SQS_QUEUE_WORLD_RECEIVEMESSAGE_POLICY, + SQSRules.SQS_QUEUE_WORLD_PURGEQUEUE_POLICY, + SQSRules.SQS_QUEUE_WORLD_GETQUEUEURL_POLICY, + SQSRules.SQS_QUEUE_WORLD_GETQUEUEATTRIBUTES_POLICY, + SQSRules.SQS_QUEUE_WORLD_DELETEMESSAGE_POLICY, + SQSRules.SQS_QUEUE_WORLD_CHANGEMESSAGEVISIBILITY_POLICY, + SNSRules.SNS_TOPIC_WORLD_SUBSCRIBE_POLICY, + SNSRules.SNS_TOPIC_WORLD_SETTOPICATTRIBUTES_POLICY, + SNSRules.SNS_TOPIC_WORLD_REMOVEPERMISSION_POLICY, + SNSRules.SNS_TOPIC_WORLD_RECEIVE_POLICY, + SNSRules.SNS_TOPIC_WORLD_PUBLISH_POLICY, + SNSRules.SNS_TOPIC_WORLD_DELETETOPIC_POLICY, + SNSRules.SNS_TOPIC_WORLD_ADDPERMISSION_POLICY, + SESRules.SES_IDENTITY_WORLD_SENDRAWEMAIL_POLICY, + SESRules.SES_IDENTITY_WORLD_SENDEMAIL_POLICY, + RedshiftRules.REDSHIFT_CLUSTER_PUBLICLY_ACCESSIBLE + ] + + test = zero_trust_consts.TEST_SCOUTSUITE_RESTRICTIVE_POLICIES + + +class Logging(ScoutSuiteFindingMap): + rules = [ + CloudTrailRules.CLOUDTRAIL_DUPLICATED_GLOBAL_SERVICES_LOGGING, + CloudTrailRules.CLOUDTRAIL_NO_DATA_LOGGING, + CloudTrailRules.CLOUDTRAIL_NO_GLOBAL_SERVICES_LOGGING, + CloudTrailRules.CLOUDTRAIL_NO_LOG_FILE_VALIDATION, + CloudTrailRules.CLOUDTRAIL_NO_LOGGING, + CloudTrailRules.CLOUDTRAIL_NOT_CONFIGURED, + CloudWatchRules.CLOUDWATCH_ALARM_WITHOUT_ACTIONS, + ELBRules.ELB_NO_ACCESS_LOGS, + S3Rules.S3_BUCKET_NO_LOGGING, + ELBv2Rules.ELBV2_NO_ACCESS_LOGS, + VPCRules.SUBNET_WITHOUT_FLOW_LOG, + ConfigRules.CONFIG_RECORDER_NOT_CONFIGURED, + RedshiftRules.REDSHIFT_PARAMETER_GROUP_LOGGING_DISABLED + ] + + test = zero_trust_consts.TEST_SCOUTSUITE_LOGGING + + +class ServiceSecurity(ScoutSuiteFindingMap): + rules = [ + CloudformationRules.CLOUDFORMATION_STACK_WITH_ROLE, + ELBv2Rules.ELBV2_HTTP_REQUEST_SMUGGLING, + RDSRules.RDS_INSTANCE_CA_CERTIFICATE_DEPRECATED, + RDSRules.RDS_INSTANCE_NO_MINOR_UPGRADE, + RedshiftRules.REDSHIFT_CLUSTER_NO_VERSION_UPGRADE + ] + + test = zero_trust_consts.TEST_SCOUTSUITE_SERVICE_SECURITY 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..d19c2b216 --- /dev/null +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/scoutsuite_findings_list.py @@ -0,0 +1,5 @@ +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/consts/service_consts.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/service_consts.py new file mode 100644 index 000000000..a31c83d3e --- /dev/null +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/service_consts.py @@ -0,0 +1,31 @@ +from enum import Enum + +SERVICES = 'services' +FINDINGS = 'findings' + + +class SERVICE_TYPES(Enum): + ACM = 'acm' + AWSLAMBDA = 'awslambda' + CLOUDFORMATION = 'cloudformation' + CLOUDTRAIL = 'cloudtrail' + CLOUDWATCH = 'cloudwatch' + CONFIG = 'config' + DIRECTCONNECT = 'directconnect' + EC2 = 'ec2' + EFS = 'efs' + ELASTICACHE = 'elasticache' + ELB = 'elb' + ELB_V2 = 'elbv2' + EMR = 'emr' + IAM = 'iam' + KMS = 'kms' + RDS = 'rds' + REDSHIFT = 'redshift' + ROUTE53 = 'route53' + S3 = 's3' + SES = 'ses' + SNS = 'sns' + SQS = 'sqs' + VPC = 'vpc' + SECRETSMANAGER = 'secretsmanager' 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 new file mode 100644 index 000000000..e07431541 --- /dev/null +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_parser.py @@ -0,0 +1,39 @@ +from enum import Enum + +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 + + +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: 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: Enum): + creator = RuleParser._get_rule_path_creator(rule_name) + return creator.build_rule_path(rule_name) + + @staticmethod + 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 new file mode 100644 index 000000000..b4767124b --- /dev/null +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/abstract_rule_path_creator.py @@ -0,0 +1,24 @@ +from abc import ABC, abstractmethod +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 + + +class AbstractRulePathCreator(ABC): + + @property + @abstractmethod + def service_type(self) -> SERVICE_TYPES: + pass + + @property + @abstractmethod + def supported_rules(self) -> Type[RuleNameEnum]: + pass + + @classmethod + 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/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 new file mode 100644 index 000000000..10adb474c --- /dev/null +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/cloudformation_rule_path_creator.py @@ -0,0 +1,10 @@ +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 new file mode 100644 index 000000000..2f626dfd5 --- /dev/null +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/cloudtrail_rule_path_creator.py @@ -0,0 +1,10 @@ +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 new file mode 100644 index 000000000..f6d4d673d --- /dev/null +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/cloudwatch_rule_path_creator.py @@ -0,0 +1,10 @@ +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 new file mode 100644 index 000000000..59a2e49eb --- /dev/null +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/config_rule_path_creator.py @@ -0,0 +1,10 @@ +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 new file mode 100644 index 000000000..4a37b0a7e --- /dev/null +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/ec2_rule_path_creator.py @@ -0,0 +1,10 @@ +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 new file mode 100644 index 000000000..a38ae2881 --- /dev/null +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/elb_rule_path_creator.py @@ -0,0 +1,10 @@ +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 new file mode 100644 index 000000000..2472bf076 --- /dev/null +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/elbv2_rule_path_creator.py @@ -0,0 +1,10 @@ +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 new file mode 100644 index 000000000..a601cb9cd --- /dev/null +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/iam_rule_path_creator.py @@ -0,0 +1,10 @@ +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 new file mode 100644 index 000000000..0b8bf54af --- /dev/null +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/rds_rule_path_creator.py @@ -0,0 +1,10 @@ +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 new file mode 100644 index 000000000..4de7016a4 --- /dev/null +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/redshift_rule_path_creator.py @@ -0,0 +1,10 @@ +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 new file mode 100644 index 000000000..4c0a0dccc --- /dev/null +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/s3_rule_path_creator.py @@ -0,0 +1,10 @@ +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 new file mode 100644 index 000000000..c7cac2bce --- /dev/null +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/ses_rule_path_creator.py @@ -0,0 +1,10 @@ +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 new file mode 100644 index 000000000..60a2f5b1c --- /dev/null +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/sns_rule_path_creator.py @@ -0,0 +1,10 @@ +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 new file mode 100644 index 000000000..619cf2ddb --- /dev/null +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/sqs_rule_path_creator.py @@ -0,0 +1,10 @@ +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 new file mode 100644 index 000000000..280d0933e --- /dev/null +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/vpc_rule_path_creator.py @@ -0,0 +1,10 @@ +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 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/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..5a7572eb0 --- /dev/null +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/test_rule_parser.py @@ -0,0 +1,36 @@ +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/scoutsuite/scoutsuite_auth_service.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_auth_service.py new file mode 100644 index 000000000..dc3f8d5ee --- /dev/null +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_auth_service.py @@ -0,0 +1,52 @@ +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 + + +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." + + 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. " + except AuthenticationException: + return False, "" + + +def is_aws_keys_setup(): + 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('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): + 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('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): + 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/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/scoutsuite_zt_finding_service.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_zt_finding_service.py new file mode 100644 index 000000000..63befc808 --- /dev/null +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_zt_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.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_finding_maps import ScoutSuiteFindingMap +from monkey_island.cc.services.zero_trust.scoutsuite.scoutsuite_rule_service import ScoutSuiteRuleService + + +class ScoutSuiteZTFindingService: + + @staticmethod + 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: + ScoutSuiteZTFindingService._create_new_finding_from_rule(finding, rule) + else: + ScoutSuiteZTFindingService.add_rule(existing_findings[0], rule) + + @staticmethod + 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) + ScoutSuiteFinding.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: 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: 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) + 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/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..24e700ce6 --- /dev/null +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_auth_service.py @@ -0,0 +1,33 @@ +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) 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..549d3161e --- /dev/null +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_zt_finding_service.py @@ -0,0 +1,42 @@ +import pytest + +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 + + +class TestScoutSuiteZTFindingService: + + @pytest.mark.usefixtures(FixtureEnum.USES_DATABASE) + def test_process_rule(self): + # Creates new PermissiveFirewallRules finding with a rule + ScoutSuiteZTFindingService.process_rule(SCOUTSUITE_FINDINGS[0], RULES[0]) + findings = list(Finding.objects()) + assert len(findings) == 1 + assert type(findings[0]) == ScoutSuiteFinding + # Assert that details were created properly + details = findings[0].details.fetch() + 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(SCOUTSUITE_FINDINGS[0], RULES[1]) + findings = list(ScoutSuiteFinding.objects()) + assert len(findings) == 1 + assert type(findings[0]) == ScoutSuiteFinding + # Assert that details were created properly + details = findings[0].details.fetch() + assert len(details.scoutsuite_rules) == 2 + assert details.scoutsuite_rules[1] == RULES[1] + + # New finding created + ScoutSuiteZTFindingService.process_rule(SCOUTSUITE_FINDINGS[1], RULES[1]) + findings = list(Finding.objects()) + assert len(findings) == 2 + assert type(findings[0]) == ScoutSuiteFinding + # Assert that details were created properly + details = findings[1].details.fetch() + assert len(details.scoutsuite_rules) == 1 + assert details.scoutsuite_rules[0] == 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 new file mode 100644 index 000000000..aaea95031 --- /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, \ + 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 + + +def get_scoutsuite_finding_dto() -> Finding: + scoutsuite_details = get_scoutsuite_details_dto() + scoutsuite_details.save() + 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 MonkeyFinding(test=TEST_ENDPOINT_SECURITY_EXISTS, + status=STATUS_PASSED, + 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..b0050a8c9 --- /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 pc-24 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/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': {'...': {'...': '...'}} +} 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..fb9722ca2 --- /dev/null +++ b/monkey/monkey_island/cc/services/zero_trust/test_common/scoutsuite_finding_data.py @@ -0,0 +1,75 @@ +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_finding_maps 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/__init__.py b/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/__init__.py new file mode 100644 index 000000000..e69de29bb 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 new file mode 100644 index 000000000..5b69d6ad9 --- /dev/null +++ b/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/finding_service.py @@ -0,0 +1,60 @@ +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 +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 + + +@dataclass +class EnrichedFinding: + finding_id: str + test: str + test_key: str + pillars: List[str] + status: str + details: Union[dict, None] + + +class FindingService: + + @staticmethod + 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() + findings[i] = FindingService._get_enriched_finding(findings[i]) + findings[i].details = details + return findings + + @staticmethod + def _get_enriched_finding(finding: Finding) -> EnrichedFinding: + test_info = zero_trust_consts.TESTS_MAP[finding['test']] + 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'], + details=None + ) + return enriched_finding + + @staticmethod + def _get_finding_details(finding: Finding) -> Union[dict, SON]: + if type(finding) == MonkeyFinding: + return MonkeyZTDetailsService.fetch_details_for_display(finding.details.id) + elif type(finding) == ScoutSuiteFinding: + return finding.details.fetch().to_mongo() + else: + 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 new file mode 100644 index 000000000..4f9c067f6 --- /dev/null +++ b/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/pillar_service.py @@ -0,0 +1,75 @@ +import common.common_consts.zero_trust_consts as zero_trust_consts +from monkey_island.cc.services.zero_trust.zero_trust_report.finding_service import FindingService + + +class PillarService: + + @staticmethod + 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 = 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 + + @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 = 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: + return status + return zero_trust_consts.STATUS_UNEXECUTED diff --git a/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/principle_service.py b/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/principle_service.py new file mode 100644 index 000000000..006cb053e --- /dev/null +++ b/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/principle_service.py @@ -0,0 +1,67 @@ +import common.common_consts.zero_trust_consts as zero_trust_consts +from monkey_island.cc.models.zero_trust.finding import Finding + + +class PrincipleService: + + @staticmethod + def get_principles_status(): + all_principles_statuses = {} + + # init with empty lists + for pillar in zero_trust_consts.PILLARS: + all_principles_statuses[pillar] = [] + + for principle, principle_tests in list(zero_trust_consts.PRINCIPLES_TO_TESTS.items()): + for pillar in zero_trust_consts.PRINCIPLES_TO_PILLARS[principle]: + all_principles_statuses[pillar].append( + { + "principle": zero_trust_consts.PRINCIPLES[principle], + "tests": PrincipleService.__get_tests_status(principle_tests), + "status": PrincipleService.__get_principle_status(principle_tests) + } + ) + + return all_principles_statuses + + @staticmethod + def __get_principle_status(principle_tests): + worst_status = zero_trust_consts.STATUS_UNEXECUTED + all_statuses = set() + for test in principle_tests: + all_statuses |= set(Finding.objects(test=test).distinct('status')) + + for status in all_statuses: + if zero_trust_consts.ORDERED_TEST_STATUSES.index(status) \ + < zero_trust_consts.ORDERED_TEST_STATUSES.index(worst_status): + worst_status = status + + return worst_status + + @staticmethod + def __get_tests_status(principle_tests): + results = [] + for test in principle_tests: + test_findings = Finding.objects(test=test) + results.append( + { + "test": zero_trust_consts.TESTS_MAP[test][zero_trust_consts.TEST_EXPLANATION_KEY], + "status": PrincipleService.__get_lcd_worst_status_for_test(test_findings) + } + ) + return results + + @staticmethod + def __get_lcd_worst_status_for_test(all_findings_for_test): + """ + :param all_findings_for_test: All findings of a specific test (get this using Finding.objects(test={A_TEST})) + :return: the "worst" (i.e. most severe) status out of the given findings. + lcd stands for lowest common denominator. + """ + current_worst_status = zero_trust_consts.STATUS_UNEXECUTED + for finding in all_findings_for_test: + if zero_trust_consts.ORDERED_TEST_STATUSES.index(finding.status) \ + < zero_trust_consts.ORDERED_TEST_STATUSES.index(current_worst_status): + current_worst_status = finding.status + + return current_worst_status 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..3a3c06452 --- /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 ScoutSuiteRawDataJson + + +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 ScoutSuiteRawDataJson.objects.get().scoutsuite_data + except Exception: + return "{}" 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..9d832e106 --- /dev/null +++ b/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/test_finding_service.py @@ -0,0 +1,47 @@ +from unittest.mock import MagicMock + +import pytest + +from common.common_consts.zero_trust_consts import TESTS_MAP, TEST_SCOUTSUITE_SERVICE_SECURITY, STATUS_FAILED, \ + 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.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_for_ui() + + description = TESTS_MAP[TEST_SCOUTSUITE_SERVICE_SECURITY]['finding_explanation'][STATUS_FAILED] + expected_finding0 = EnrichedFinding(finding_id=findings[0].finding_id, + 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, + 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..bf2bbe1a5 --- /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 diff --git a/monkey/monkey_island/cc/setup.py b/monkey/monkey_island/cc/setup.py index 5518532fd..213a62e6b 100644 --- a/monkey/monkey_island/cc/setup.py +++ b/monkey/monkey_island/cc/setup.py @@ -4,8 +4,7 @@ from pymongo import errors from monkey_island.cc.database import mongo from monkey_island.cc.models.attack.attack_mitigations import AttackMitigations -from monkey_island.cc.services.attack.mitre_api_interface import \ - MitreApiInterface +from monkey_island.cc.services.attack.mitre_api_interface import MitreApiInterface logger = logging.getLogger(__name__) diff --git a/monkey/monkey_island/cc/test_common/__init__.py b/monkey/monkey_island/cc/test_common/__init__.py new file mode 100644 index 000000000..e69de29bb 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/fixtures/__init__.py b/monkey/monkey_island/cc/test_common/fixtures/__init__.py new file mode 100644 index 000000000..fd208655a --- /dev/null +++ b/monkey/monkey_island/cc/test_common/fixtures/__init__.py @@ -0,0 +1,4 @@ +# 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 new file mode 100644 index 000000000..17c115079 --- /dev/null +++ b/monkey/monkey_island/cc/test_common/fixtures/fixture_enum.py @@ -0,0 +1,2 @@ +class FixtureEnum: + USES_DATABASE = 'uses_database' diff --git a/monkey/monkey_island/cc/test_common/fixtures/mongomock_fixtures.py b/monkey/monkey_island/cc/test_common/fixtures/mongomock_fixtures.py new file mode 100644 index 000000000..8a49d0254 --- /dev/null +++ b/monkey/monkey_island/cc/test_common/fixtures/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 b260f62c9..000000000 --- a/monkey/monkey_island/cc/testing/IslandTestCase.py +++ /dev/null @@ -1,23 +0,0 @@ -import unittest - -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 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(): - Monkey.objects().delete() - - @staticmethod - def clean_edge_db(): - Edge.objects().delete() - - @staticmethod - def clean_finding_db(): - Finding.objects().delete() 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' } } } diff --git a/monkey/monkey_island/cc/ui/src/components/pages/LoginPage.js b/monkey/monkey_island/cc/ui/src/components/pages/LoginPage.js index cbe3867ee..961c1899c 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/LoginPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/LoginPage.js @@ -69,7 +69,7 @@ class LoginPageComponent 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/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/ReportPage.js b/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js index cb30ba117..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({ @@ -60,22 +60,27 @@ class ReportPageComponent extends AuthComponent { } getZeroTrustReportFromServer = async () => { - let ztReport = {findings: {}, principles: {}, pillars: {}}; - await this.authFetch('/api/report/zero_trust/findings') + let ztReport = {findings: {}, principles: {}, pillars: {}, scoutsuite_data: {}}; + 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') + .then(res => res.json()) + .then(res => { + ztReport.scoutsuite_data = res; + }); return ztReport }; 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 379aa647d..bcfe7d66c 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,9 +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 AWSRunButton from './RunOnAWS/AWSRunButton'; +import CloudOptions from './scoutsuite-setup/CloudOptions'; function RunOptions(props) { @@ -62,7 +63,14 @@ 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/AWSConfiguration/AWSCLISetup.js b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/scoutsuite-setup/AWSConfiguration/AWSCLISetup.js new file mode 100644 index 000000000..178c60d8b --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/scoutsuite-setup/AWSConfiguration/AWSCLISetup.js @@ -0,0 +1,63 @@ +import {Button} from 'react-bootstrap'; +import React from 'react'; +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 AWSCLISetup(props) { + return InlineSelection(getContents, { + ...props, + collumnSize: COLUMN_SIZES.LARGE, + onBackButtonClick: () => { + props.setComponent(AWSSetupOptions, props); + } + }) +} + + +const getContents = (props) => { + return ( +
+

AWS CLI configuration for scan

+

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). +
      +
    1. + a. Download and + install it on the Monkey Island server (machine running this page). +
    2. +
    3. + 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  + . +
    4. +
    +
  2. +
  3. + 2. If you change the configuration, make sure not to disable AWS system info collector. +
  4. +
  5. + 3. Go +  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/AWSConfiguration/AWSKeySetup.js b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/scoutsuite-setup/AWSConfiguration/AWSKeySetup.js new file mode 100644 index 000000000..04a1f490b --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/scoutsuite-setup/AWSConfiguration/AWSKeySetup.js @@ -0,0 +1,179 @@ +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 if (res['message'] === 'Internal Server Error') { + setErrorMessage('Something went wrong, double check keys and contact support if problem persists.'); + } 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 new file mode 100644 index 000000000..bd9c83f04 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/scoutsuite-setup/CloudOptions.js @@ -0,0 +1,65 @@ +import React, {useEffect, useState} from 'react'; +import InlineSelection from '../../../ui-components/inline-selection/InlineSelection'; +import NextSelectionButton from '../../../ui-components/inline-selection/NextSelectionButton'; +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) => { + return InlineSelection(getContents, { + ...props, + onBackButtonClick: () => { + props.setComponent() + } + }) +} + +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(getDescription(res.message)); + setIconType('icon-success'); + setIcon(faCheck); + } else { + setDescription('Setup Amazon Web Services infrastructure scan.'); + setIconType('') + setIcon(faCloud); + } + }); + }, [props]); + + function getDescription(message){ + return ( + <> + {message} Run from the Island to start the scan. Click next to change the configuration. + + ) + } + + return ( + <> + { + props.setComponent(AWSSetupOptions, + {setComponent: props.setComponent}) + }}/> + + ) +} + +export default CloudOptions; diff --git a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/scoutsuite-setup/ProvidersEnum.js b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/scoutsuite-setup/ProvidersEnum.js new file mode 100644 index 000000000..26bb87860 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/scoutsuite-setup/ProvidersEnum.js @@ -0,0 +1,9 @@ +// Should match enum in monkey/common/cloud/scoutsuite_consts.py + +export const PROVIDERS = { + AWS : 'aws', + AZURE : 'azure', + GCP : 'gcp', + ALIBABA : 'aliyun', + ORACLE : 'oci' +} 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/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/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..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 @@ -4,52 +4,63 @@ 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 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'} - } - ] - } -]; +const EVENTS_COLUMN_MAX_WIDTH = 180; +const PILLARS_COLUMN_MAX_WIDTH = 260; export class FindingsTable extends Component { + columns = [ + { + columns: [ + { + Header: 'Finding', accessor: 'test', + style: {'whiteSpace': 'unset'} // This enables word wrap + }, + + { + Header: 'Details', id: 'details', + accessor: x => this.getFindingDetails(x), + maxWidth: EVENTS_COLUMN_MAX_WIDTH + }, + + { + Header: 'Pillars', id: 'pillars', + accessor: x => this.getFindingPillars(x), + maxWidth: PILLARS_COLUMN_MAX_WIDTH, + style: {'whiteSpace': 'unset'} + } + ] + } + ]; + + getFindingDetails(finding) { + if ('scoutsuite_rules' in finding.details) { + return ; + } else { + return ; + } + } + + getFindingPillars(finding) { + const pillars = finding.pillars; + const pillarLabels = pillars.map((pillar) => + + ); + return
{pillarLabels}
; + } + 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..81aee324e --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/scoutsuite/ResourceDropdown.js @@ -0,0 +1,84 @@ +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); + let parser = new ScoutSuiteDataParser(props.scoutsuite_data.data.services); + let resource_value = parser.getResourceValue(props.resource_path, props.template_path); + + 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 getResourceValueDisplay() { + return ( +
+

Value:

+
{prettyPrintJson(resource_value)}
+
+ ); + } + + function getResourceDropdownContents() { + return ( +
+
+

Path:

+

{replacePathDotsWithArrows(props.resource_path)}

+
+ {getResourceValueDisplay()} +
+ ); + } + + return getResourceDropdown(); +} + +ResourceDropdown.propTypes = { + template_path: PropTypes.string, + resource_path: PropTypes.string, + 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..dc81ff183 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/scoutsuite/RuleDisplay.js @@ -0,0 +1,70 @@ +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})

+
+
+

+

+
+

Resources checked:

+

{props.rule.checked_items}

+
+ {getReferences()} + {getResources()} +
); + + function getReferences() { + let references = [] + props.rule.references.forEach(reference => { + references.push({reference}) + }) + if (references.length) { + return ( +
+

References:

+ {references} +
) + } else { + return null; + } + } + + function getResources() { + let resources = [] + for (let i = 0; i < props.rule.items.length; i++) { + let item = props.rule.items[i]; + let template_path = Object.prototype.hasOwnProperty.call(props.rule, 'display_path') + ? props.rule.display_path : props.rule.path; + resources.push() + } + if (resources.length) { + return ( +
+

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

+ {resources} +
) + } else { + return null; + } + } +} + +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..be5599d99 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/scoutsuite/ScoutSuiteDataParser.js @@ -0,0 +1,118 @@ +export default class ScoutSuiteDataParser { + constructor(runResults) { + this.runResults = runResults + } + + /** + * 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 {*[]|*} resource value e.g. {'bucket_id': 123, 'bucket_max_size': '123GB'} + */ + getResourceValue(itemPath, templatePath) { + let resourcePath = this.fillTemplatePath(itemPath, templatePath); + 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('.'); + let resourcePathArray = templatePathArray.map((val, i) => { + return val === 'id' ? itemPathArray[i] : val + }) + return resourcePathArray.join('.'); + } + + /** + * 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; + } + + /** + * 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('.')); + } else { + return path; + } + } + + /** + * 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]; + } + } + + /** + * 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; + } + } + + +} 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..b3809ec27 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/scoutsuite/ScoutSuiteRuleButton.js @@ -0,0 +1,46 @@ +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'; +import CountBadge from '../../../ui-components/CountBadge'; + +export default class ScoutSuiteRuleButton extends Component { + constructor(props) { + super(props); + this.state = { + isModalOpen: false + } + } + + toggleModal = () => { + this.setState({isModalOpen: !this.state.isModalOpen}); + }; + + render() { + return ( + <> + +
+ +
+ ); + } + + createRuleCountBadge() { + + } +} + +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..fd7fa3851 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/scoutsuite/ScoutSuiteRuleModal.js @@ -0,0 +1,94 @@ +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'; +import STATUSES from '../../common/consts/StatusConsts'; +import {getRuleCountByStatus, sortRules} from './rule-parsing/ParsingUtils'; + + +export default function ScoutSuiteRuleModal(props) { + const [openRuleId, setOpenRuleId] = useState(null) + + function toggleRuleDropdown(ruleId) { + let ruleIdToSet = (openRuleId === ruleId) ? null : ruleId; + setOpenRuleId(ruleIdToSet); + } + + function renderRuleDropdowns() { + let dropdowns = []; + 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}/>) + 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'}> + +

+
ScoutSuite rules
+

+
+

+ {getGeneralRuleOverview()} + {getFailedRuleOverview()} + {getPassedRuleOverview()} + {getUnexecutedRuleOverview()} +

+ {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..c396066b4 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/scoutsuite/ScoutSuiteSingleRuleDropdown.js @@ -0,0 +1,79 @@ +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 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) { + + function getRuleCollapse() { + return ( +
+ + +
+ ); + } + + function getRuleIcon() { + let ruleStatus = getRuleStatus(props.rule); + 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(props.rule); + switch (ruleStatus) { + case STATUSES.STATUS_PASSED: + return 'collapse-success'; + case STATUSES.STATUS_VERIFY: + return 'collapse-danger'; + case STATUSES.STATUS_FAILED: + return 'collapse-danger'; + case STATUSES.STATUS_UNEXECUTED: + return 'collapse-default'; + } + } + + 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/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..da1417d1b --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/scoutsuite/rule-parsing/ParsingUtils.js @@ -0,0 +1,40 @@ +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) { + 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] +} 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; 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..bd012944b --- /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.string +} diff --git a/monkey/monkey_island/cc/ui/src/components/ui-components/IslandMonkeyRunErrorModal.js b/monkey/monkey_island/cc/ui/src/components/ui-components/IslandMonkeyRunErrorModal.js index 67e7ee77d..61e0a9c8e 100644 --- a/monkey/monkey_island/cc/ui/src/components/ui-components/IslandMonkeyRunErrorModal.js +++ b/monkey/monkey_island/cc/ui/src/components/ui-components/IslandMonkeyRunErrorModal.js @@ -53,7 +53,7 @@ class IslandMonkeyRunErrorModal extends React.PureComponent { } getDisplayContentByError(errorMsg) { - if (errorMsg.includes('Permission denied:')) { + if (errorMsg.includes('Permission denied:') || errorMsg.includes('Text file busy')) { return this.getMonkeyAlreadyRunningContent() } else if (errorMsg.startsWith('Copy file failed')) { return this.getMissingBinariesContent() 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 ( - +