diff --git a/.github/attack-report.png b/.github/attack-report.png new file mode 100644 index 00000000000..5ba45e9fb89 Binary files /dev/null and b/.github/attack-report.png differ diff --git a/.github/map-full.png b/.github/map-full.png index faa1b7832fc..92902a22132 100644 Binary files a/.github/map-full.png and b/.github/map-full.png differ diff --git a/.github/security-report.png b/.github/security-report.png new file mode 100644 index 00000000000..54a30b13f13 Binary files /dev/null and b/.github/security-report.png differ diff --git a/.github/zero-trust-report.png b/.github/zero-trust-report.png new file mode 100644 index 00000000000..18a0ff7039a Binary files /dev/null and b/.github/zero-trust-report.png differ diff --git a/.gitmodules b/.gitmodules index 92a84cb37ce..63b69ebab1f 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,4 @@ + [submodule "monkey/monkey_island/cc/services/attack/attack_data"] path = monkey/monkey_island/cc/services/attack/attack_data - url = https://github.com/mitre/cti + url = https://github.com/guardicore/cti diff --git a/README.md b/README.md index 83fd878adbf..bf976845938 100644 --- a/README.md +++ b/README.md @@ -13,10 +13,6 @@ Welcome to the Infection Monkey! The Infection Monkey is an open source security tool for testing a data center's resiliency to perimeter breaches and internal server infection. The Monkey uses various methods to self propagate across a data center and reports success to a centralized Monkey Island server. - - - - The Infection Monkey is comprised of two parts: * **Monkey** - A tool which infects other machines and propagates to them. @@ -24,6 +20,20 @@ The Infection Monkey is comprised of two parts: To read more about the Monkey, visit [infectionmonkey.com](https://infectionmonkey.com). +## Screenshots + +### Map + + +### Security report + + +### Zero trust report + + +### ATT&CK report + + ## Main Features The Infection Monkey uses the following techniques and exploits to propagate to other machines. @@ -40,6 +50,8 @@ The Infection Monkey uses the following techniques and exploits to propagate to * Conficker * SambaCry * Elastic Search (CVE-2015-1427) + * Weblogic server + * and more ## Setup Check out the [Setup](https://github.com/guardicore/monkey/wiki/setup) page in the Wiki or a quick getting [started guide](https://www.guardicore.com/infectionmonkey/wt/). diff --git a/docker/Dockerfile b/docker/Dockerfile index 2d0d0b55b6a..aec69fd32e3 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -2,7 +2,7 @@ FROM debian:stretch-slim LABEL MAINTAINER="theonlydoo " -ARG RELEASE=1.6 +ARG RELEASE=1.8.0 ARG DEBIAN_FRONTEND=noninteractive EXPOSE 5000 diff --git a/envs/monkey_zoo/blackbox/analyzers/performance_analyzer.py b/envs/monkey_zoo/blackbox/analyzers/performance_analyzer.py index 3e1c4819943..d9067eeeec3 100644 --- a/envs/monkey_zoo/blackbox/analyzers/performance_analyzer.py +++ b/envs/monkey_zoo/blackbox/analyzers/performance_analyzer.py @@ -1,56 +1,36 @@ import logging from datetime import timedelta +from typing import Dict from envs.monkey_zoo.blackbox.analyzers.analyzer import Analyzer -from envs.monkey_zoo.blackbox.island_client.monkey_island_client import MonkeyIslandClient - -MAX_ALLOWED_SINGLE_PAGE_TIME = timedelta(seconds=2) -MAX_ALLOWED_TOTAL_TIME = timedelta(seconds=5) - -REPORT_URLS = [ - "api/report/security", - "api/attack/report", - "api/report/zero_trust/findings", - "api/report/zero_trust/principles", - "api/report/zero_trust/pillars" -] - -logger = logging.getLogger(__name__) +from envs.monkey_zoo.blackbox.tests.performance.performance_test_config import PerformanceTestConfig +LOGGER = logging.getLogger(__name__) class PerformanceAnalyzer(Analyzer): - def __init__(self, island_client: MonkeyIslandClient, break_if_took_too_long=False): - self.break_if_took_too_long = break_if_took_too_long - self.island_client = island_client - - def analyze_test_results(self) -> bool: - if not self.island_client.is_all_monkeys_dead(): - raise RuntimeError("Can't test report times since not all Monkeys have died.") - - # Collect timings for all pages - self.island_client.clear_caches() - report_resource_to_response_time = {} - for url in REPORT_URLS: - report_resource_to_response_time[url] = self.island_client.get_elapsed_for_get_request(url) + def __init__(self, performance_test_config: PerformanceTestConfig, endpoint_timings: Dict[str, timedelta]): + self.performance_test_config = performance_test_config + self.endpoint_timings = endpoint_timings + def analyze_test_results(self): # Calculate total time and check each page single_page_time_less_then_max = True total_time = timedelta() - for page, elapsed in report_resource_to_response_time.items(): - logger.info(f"page {page} took {str(elapsed)}") + for page, elapsed in self.endpoint_timings.items(): + LOGGER.info(f"page {page} took {str(elapsed)}") total_time += elapsed - if elapsed > MAX_ALLOWED_SINGLE_PAGE_TIME: + if elapsed > self.performance_test_config.max_allowed_single_page_time: single_page_time_less_then_max = False - total_time_less_then_max = total_time < MAX_ALLOWED_TOTAL_TIME + total_time_less_then_max = total_time < self.performance_test_config.max_allowed_total_time - logger.info(f"total time is {str(total_time)}") + LOGGER.info(f"total time is {str(total_time)}") performance_is_good_enough = total_time_less_then_max and single_page_time_less_then_max - if self.break_if_took_too_long and not performance_is_good_enough: - logger.warning( + if self.performance_test_config.break_on_timeout and not performance_is_good_enough: + LOGGER.warning( "Calling breakpoint - pausing to enable investigation of island. Type 'c' to continue once you're done " "investigating. Type 'p timings' and 'p total_time' to see performance information." ) 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 27a54222b79..93780bf3b9f 100644 --- a/envs/monkey_zoo/blackbox/island_client/monkey_island_client.py +++ b/envs/monkey_zoo/blackbox/island_client/monkey_island_client.py @@ -96,13 +96,3 @@ def clear_caches(self): response = self.requests.get("api/test/clear_caches") response.raise_for_status() return response - - def get_elapsed_for_get_request(self, url): - response = self.requests.get(url) - if response.ok: - LOGGER.debug(f"Got ok for {url} content peek:\n{response.content[:120].strip()}") - return response.elapsed - else: - LOGGER.error(f"Trying to get {url} but got unexpected {str(response)}") - # instead of raising for status, mark failed responses as maxtime - return timedelta.max() diff --git a/envs/monkey_zoo/blackbox/test_blackbox.py b/envs/monkey_zoo/blackbox/test_blackbox.py index 7ac70d8e016..04e510c55d3 100644 --- a/envs/monkey_zoo/blackbox/test_blackbox.py +++ b/envs/monkey_zoo/blackbox/test_blackbox.py @@ -4,12 +4,13 @@ import pytest from time import sleep -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.analyzers.communication_analyzer import CommunicationAnalyzer from envs.monkey_zoo.blackbox.island_client.island_config_parser import IslandConfigParser +from envs.monkey_zoo.blackbox.tests.performance.map_generation import MapGenerationTest +from envs.monkey_zoo.blackbox.tests.performance.report_generation import ReportGenerationTest from envs.monkey_zoo.blackbox.utils import gcp_machine_handlers -from envs.monkey_zoo.blackbox.tests.basic_test import BasicTest +from envs.monkey_zoo.blackbox.tests.exploitation import ExploitationTest from envs.monkey_zoo.blackbox.log_handlers.test_logs_handler import TestLogsHandler DEFAULT_TIMEOUT_SECONDS = 5*60 @@ -55,34 +56,32 @@ def island_client(island): class TestMonkeyBlackbox(object): @staticmethod - def run_basic_test(island_client, conf_filename, test_name, timeout_in_seconds=DEFAULT_TIMEOUT_SECONDS): + def run_exploitation_test(island_client, conf_filename, test_name, timeout_in_seconds=DEFAULT_TIMEOUT_SECONDS): config_parser = IslandConfigParser(conf_filename) analyzer = CommunicationAnalyzer(island_client, config_parser.get_ips_of_targets()) log_handler = TestLogsHandler(test_name, island_client, TestMonkeyBlackbox.get_log_dir_path()) - BasicTest( + ExploitationTest( name=test_name, island_client=island_client, config_parser=config_parser, analyzers=[analyzer], timeout=timeout_in_seconds, - post_exec_analyzers=[], log_handler=log_handler).run() @staticmethod - def run_performance_test(island_client, conf_filename, test_name, timeout_in_seconds): + def run_performance_test(performance_test_class, island_client, + conf_filename, timeout_in_seconds, break_on_timeout=False): config_parser = IslandConfigParser(conf_filename) - log_handler = TestLogsHandler(test_name, island_client, TestMonkeyBlackbox.get_log_dir_path()) - BasicTest( - name=test_name, - island_client=island_client, - config_parser=config_parser, - analyzers=[CommunicationAnalyzer(island_client, config_parser.get_ips_of_targets())], - timeout=timeout_in_seconds, - post_exec_analyzers=[PerformanceAnalyzer( - island_client, - break_if_took_too_long=False - )], - log_handler=log_handler).run() + log_handler = TestLogsHandler(performance_test_class.TEST_NAME, + island_client, + TestMonkeyBlackbox.get_log_dir_path()) + analyzers = [CommunicationAnalyzer(island_client, config_parser.get_ips_of_targets())] + performance_test_class(island_client=island_client, + config_parser=config_parser, + analyzers=analyzers, + timeout=timeout_in_seconds, + log_handler=log_handler, + break_on_timeout=break_on_timeout).run() @staticmethod def get_log_dir_path(): @@ -92,43 +91,42 @@ def test_server_online(self, island_client): assert island_client.get_api_status() is not None def test_ssh_exploiter(self, island_client): - TestMonkeyBlackbox.run_basic_test(island_client, "SSH.conf", "SSH_exploiter_and_keys") + TestMonkeyBlackbox.run_exploitation_test(island_client, "SSH.conf", "SSH_exploiter_and_keys") def test_hadoop_exploiter(self, island_client): - TestMonkeyBlackbox.run_basic_test(island_client, "HADOOP.conf", "Hadoop_exploiter", 6*60) + TestMonkeyBlackbox.run_exploitation_test(island_client, "HADOOP.conf", "Hadoop_exploiter", 6 * 60) def test_mssql_exploiter(self, island_client): - TestMonkeyBlackbox.run_basic_test(island_client, "MSSQL.conf", "MSSQL_exploiter") + TestMonkeyBlackbox.run_exploitation_test(island_client, "MSSQL.conf", "MSSQL_exploiter") def test_smb_and_mimikatz_exploiters(self, island_client): - TestMonkeyBlackbox.run_basic_test(island_client, "SMB_MIMIKATZ.conf", "SMB_exploiter_mimikatz") + TestMonkeyBlackbox.run_exploitation_test(island_client, "SMB_MIMIKATZ.conf", "SMB_exploiter_mimikatz") def test_smb_pth(self, island_client): - TestMonkeyBlackbox.run_basic_test(island_client, "SMB_PTH.conf", "SMB_PTH") + TestMonkeyBlackbox.run_exploitation_test(island_client, "SMB_PTH.conf", "SMB_PTH") def test_elastic_exploiter(self, island_client): - TestMonkeyBlackbox.run_basic_test(island_client, "ELASTIC.conf", "Elastic_exploiter") + TestMonkeyBlackbox.run_exploitation_test(island_client, "ELASTIC.conf", "Elastic_exploiter") def test_struts_exploiter(self, island_client): - TestMonkeyBlackbox.run_basic_test(island_client, "STRUTS2.conf", "Strtuts2_exploiter") + TestMonkeyBlackbox.run_exploitation_test(island_client, "STRUTS2.conf", "Strtuts2_exploiter") def test_weblogic_exploiter(self, island_client): - TestMonkeyBlackbox.run_basic_test(island_client, "WEBLOGIC.conf", "Weblogic_exploiter") + TestMonkeyBlackbox.run_exploitation_test(island_client, "WEBLOGIC.conf", "Weblogic_exploiter") def test_shellshock_exploiter(self, island_client): - TestMonkeyBlackbox.run_basic_test(island_client, "SHELLSHOCK.conf", "Shellschock_exploiter") + TestMonkeyBlackbox.run_exploitation_test(island_client, "SHELLSHOCK.conf", "Shellschock_exploiter") def test_tunneling(self, island_client): - TestMonkeyBlackbox.run_basic_test(island_client, "TUNNELING.conf", "Tunneling_exploiter", 15*60) + TestMonkeyBlackbox.run_exploitation_test(island_client, "TUNNELING.conf", "Tunneling_exploiter", 15 * 60) def test_wmi_and_mimikatz_exploiters(self, island_client): - TestMonkeyBlackbox.run_basic_test(island_client, "WMI_MIMIKATZ.conf", "WMI_exploiter,_mimikatz") + TestMonkeyBlackbox.run_exploitation_test(island_client, "WMI_MIMIKATZ.conf", "WMI_exploiter,_mimikatz") def test_wmi_pth(self, island_client): - TestMonkeyBlackbox.run_basic_test(island_client, "WMI_PTH.conf", "WMI_PTH") + TestMonkeyBlackbox.run_exploitation_test(island_client, "WMI_PTH.conf", "WMI_PTH") - @pytest.mark.xfail(reason="Performance is slow, will improve on release 1.9.") - def test_performance(self, island_client): + def test_report_generation_performance(self, island_client): """ This test includes the SSH + Elastic + Hadoop + MSSQL machines all in one test for a total of 8 machines including the Monkey Island. @@ -136,8 +134,14 @@ def test_performance(self, island_client): Is has 2 analyzers - the regular one which checks all the Monkeys and the Timing one which checks how long the report took to execute """ - TestMonkeyBlackbox.run_performance_test( - island_client, - "PERFORMANCE.conf", - "test_report_performance", - timeout_in_seconds=10*60) + TestMonkeyBlackbox.run_performance_test(ReportGenerationTest, + island_client, + "PERFORMANCE.conf", + timeout_in_seconds=10*60) + + def test_map_generation_performance(self, island_client): + TestMonkeyBlackbox.run_performance_test(MapGenerationTest, + island_client, + "PERFORMANCE.conf", + timeout_in_seconds=10*60) + diff --git a/envs/monkey_zoo/blackbox/tests/basic_test.py b/envs/monkey_zoo/blackbox/tests/basic_test.py index a5e71c64c46..fa722ffb759 100644 --- a/envs/monkey_zoo/blackbox/tests/basic_test.py +++ b/envs/monkey_zoo/blackbox/tests/basic_test.py @@ -1,100 +1,8 @@ -from time import sleep +import abc -import logging -from envs.monkey_zoo.blackbox.utils.test_timer import TestTimer - -MAX_TIME_FOR_MONKEYS_TO_DIE = 5 * 60 -WAIT_TIME_BETWEEN_REQUESTS = 10 -TIME_FOR_MONKEY_PROCESS_TO_FINISH = 40 -DELAY_BETWEEN_ANALYSIS = 3 -LOGGER = logging.getLogger(__name__) - - -class BasicTest(object): - - def __init__(self, name, island_client, config_parser, analyzers, timeout, post_exec_analyzers, log_handler): - self.name = name - self.island_client = island_client - self.config_parser = config_parser - self.analyzers = analyzers - self.post_exec_analyzers = post_exec_analyzers - self.timeout = timeout - self.log_handler = log_handler +class BasicTest(abc.ABC): + @abc.abstractmethod def run(self): - self.island_client.import_config(self.config_parser.config_raw) - self.print_test_starting_info() - try: - self.island_client.run_monkey_local() - self.test_until_timeout() - finally: - self.island_client.kill_all_monkeys() - self.wait_until_monkeys_die() - self.wait_for_monkey_process_to_finish() - self.test_post_exec_analyzers() - self.parse_logs() - self.island_client.reset_env() - - def print_test_starting_info(self): - LOGGER.info("Started {} test".format(self.name)) - LOGGER.info("Machines participating in test: " + ", ".join(self.config_parser.get_ips_of_targets())) - print("") - - def test_until_timeout(self): - timer = TestTimer(self.timeout) - while not timer.is_timed_out(): - if self.all_analyzers_pass(): - self.log_success(timer) - return - sleep(DELAY_BETWEEN_ANALYSIS) - LOGGER.debug("Waiting until all analyzers passed. Time passed: {}".format(timer.get_time_taken())) - self.log_failure(timer) - assert False - - def log_success(self, timer): - LOGGER.info(self.get_analyzer_logs()) - LOGGER.info("{} test passed, time taken: {:.1f} seconds.".format(self.name, timer.get_time_taken())) - - def log_failure(self, timer): - LOGGER.info(self.get_analyzer_logs()) - LOGGER.error("{} test failed because of timeout. Time taken: {:.1f} seconds.".format(self.name, - timer.get_time_taken())) - - def all_analyzers_pass(self): - analyzers_results = [analyzer.analyze_test_results() for analyzer in self.analyzers] - return all(analyzers_results) - - def get_analyzer_logs(self): - log = "" - for analyzer in self.analyzers: - log += "\n" + analyzer.log.get_contents() - return log - - def wait_until_monkeys_die(self): - time_passed = 0 - while not self.island_client.is_all_monkeys_dead() and time_passed < MAX_TIME_FOR_MONKEYS_TO_DIE: - sleep(WAIT_TIME_BETWEEN_REQUESTS) - time_passed += WAIT_TIME_BETWEEN_REQUESTS - LOGGER.debug("Waiting for all monkeys to die. Time passed: {}".format(time_passed)) - if time_passed > MAX_TIME_FOR_MONKEYS_TO_DIE: - LOGGER.error("Some monkeys didn't die after the test, failing") - assert False - - def parse_logs(self): - LOGGER.info("Parsing test logs:") - self.log_handler.parse_test_logs() - - @staticmethod - def wait_for_monkey_process_to_finish(): - """ - There is a time period when monkey is set to dead, but the process is still closing. - If we try to launch monkey during that time window monkey will fail to start, that's - why test needs to wait a bit even after all monkeys are dead. - """ - LOGGER.debug("Waiting for Monkey process to close...") - sleep(TIME_FOR_MONKEY_PROCESS_TO_FINISH) - - def test_post_exec_analyzers(self): - post_exec_analyzers_results = [analyzer.analyze_test_results() for analyzer in self.post_exec_analyzers] - assert all(post_exec_analyzers_results) + pass diff --git a/envs/monkey_zoo/blackbox/tests/exploitation.py b/envs/monkey_zoo/blackbox/tests/exploitation.py new file mode 100644 index 00000000000..e731d8f9000 --- /dev/null +++ b/envs/monkey_zoo/blackbox/tests/exploitation.py @@ -0,0 +1,95 @@ +from time import sleep + +import logging + +from envs.monkey_zoo.blackbox.utils.test_timer import TestTimer +from envs.monkey_zoo.blackbox.tests.basic_test import BasicTest + +MAX_TIME_FOR_MONKEYS_TO_DIE = 5 * 60 +WAIT_TIME_BETWEEN_REQUESTS = 10 +TIME_FOR_MONKEY_PROCESS_TO_FINISH = 40 +DELAY_BETWEEN_ANALYSIS = 3 +LOGGER = logging.getLogger(__name__) + + +class ExploitationTest(BasicTest): + + def __init__(self, name, island_client, config_parser, analyzers, timeout, log_handler): + self.name = name + self.island_client = island_client + self.config_parser = config_parser + self.analyzers = analyzers + self.timeout = timeout + self.log_handler = log_handler + + def run(self): + self.island_client.import_config(self.config_parser.config_raw) + self.print_test_starting_info() + try: + self.island_client.run_monkey_local() + self.test_until_timeout() + finally: + self.island_client.kill_all_monkeys() + self.wait_until_monkeys_die() + self.wait_for_monkey_process_to_finish() + self.parse_logs() + self.island_client.reset_env() + + def print_test_starting_info(self): + LOGGER.info("Started {} test".format(self.name)) + LOGGER.info("Machines participating in test: " + ", ".join(self.config_parser.get_ips_of_targets())) + print("") + + def test_until_timeout(self): + timer = TestTimer(self.timeout) + while not timer.is_timed_out(): + if self.all_analyzers_pass(): + self.log_success(timer) + return + sleep(DELAY_BETWEEN_ANALYSIS) + LOGGER.debug("Waiting until all analyzers passed. Time passed: {}".format(timer.get_time_taken())) + self.log_failure(timer) + assert False + + def log_success(self, timer): + LOGGER.info(self.get_analyzer_logs()) + LOGGER.info("{} test passed, time taken: {:.1f} seconds.".format(self.name, timer.get_time_taken())) + + def log_failure(self, timer): + LOGGER.info(self.get_analyzer_logs()) + LOGGER.error("{} test failed because of timeout. Time taken: {:.1f} seconds.".format(self.name, + timer.get_time_taken())) + + def all_analyzers_pass(self): + analyzers_results = [analyzer.analyze_test_results() for analyzer in self.analyzers] + return all(analyzers_results) + + def get_analyzer_logs(self): + log = "" + for analyzer in self.analyzers: + log += "\n" + analyzer.log.get_contents() + return log + + def wait_until_monkeys_die(self): + time_passed = 0 + while not self.island_client.is_all_monkeys_dead() and time_passed < MAX_TIME_FOR_MONKEYS_TO_DIE: + sleep(WAIT_TIME_BETWEEN_REQUESTS) + time_passed += WAIT_TIME_BETWEEN_REQUESTS + LOGGER.debug("Waiting for all monkeys to die. Time passed: {}".format(time_passed)) + if time_passed > MAX_TIME_FOR_MONKEYS_TO_DIE: + LOGGER.error("Some monkeys didn't die after the test, failing") + assert False + + def parse_logs(self): + LOGGER.info("Parsing test logs:") + self.log_handler.parse_test_logs() + + @staticmethod + def wait_for_monkey_process_to_finish(): + """ + There is a time period when monkey is set to dead, but the process is still closing. + If we try to launch monkey during that time window monkey will fail to start, that's + why test needs to wait a bit even after all monkeys are dead. + """ + LOGGER.debug("Waiting for Monkey process to close...") + sleep(TIME_FOR_MONKEY_PROCESS_TO_FINISH) diff --git a/envs/monkey_zoo/blackbox/tests/performance/__init__.py b/envs/monkey_zoo/blackbox/tests/performance/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/envs/monkey_zoo/blackbox/tests/performance/endpoint_performance_test.py b/envs/monkey_zoo/blackbox/tests/performance/endpoint_performance_test.py new file mode 100644 index 00000000000..76a389efdd1 --- /dev/null +++ b/envs/monkey_zoo/blackbox/tests/performance/endpoint_performance_test.py @@ -0,0 +1,42 @@ +import logging +from datetime import timedelta + +from envs.monkey_zoo.blackbox.tests.basic_test import BasicTest +from envs.monkey_zoo.blackbox.island_client.monkey_island_client import MonkeyIslandClient +from envs.monkey_zoo.blackbox.tests.performance.performance_test_config import PerformanceTestConfig +from envs.monkey_zoo.blackbox.analyzers.performance_analyzer import PerformanceAnalyzer + + +LOGGER = logging.getLogger(__name__) + + +class EndpointPerformanceTest(BasicTest): + + def __init__(self, name, test_config: PerformanceTestConfig, island_client: MonkeyIslandClient): + self.name = name + self.test_config = test_config + self.island_client = island_client + + def run(self) -> bool: + if not self.island_client.is_all_monkeys_dead(): + raise RuntimeError("Can't test report times since not all Monkeys have died.") + + # Collect timings for all pages + self.island_client.clear_caches() + endpoint_timings = {} + for endpoint in self.test_config.endpoints_to_test: + endpoint_timings[endpoint] = self.get_elapsed_for_get_request(endpoint) + + analyzer = PerformanceAnalyzer(self.test_config, endpoint_timings) + + return analyzer.analyze_test_results() + + def get_elapsed_for_get_request(self, url): + response = self.island_client.requests.get(url) + if response.ok: + LOGGER.debug(f"Got ok for {url} content peek:\n{response.content[:120].strip()}") + return response.elapsed + else: + LOGGER.error(f"Trying to get {url} but got unexpected {str(response)}") + # instead of raising for status, mark failed responses as maxtime + return timedelta.max diff --git a/envs/monkey_zoo/blackbox/tests/performance/map_generation.py b/envs/monkey_zoo/blackbox/tests/performance/map_generation.py new file mode 100644 index 00000000000..c597907f409 --- /dev/null +++ b/envs/monkey_zoo/blackbox/tests/performance/map_generation.py @@ -0,0 +1,35 @@ +from datetime import timedelta + +from envs.monkey_zoo.blackbox.tests.exploitation import ExploitationTest +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_workflow import PerformanceTestWorkflow + +MAX_ALLOWED_SINGLE_PAGE_TIME = timedelta(seconds=2) +MAX_ALLOWED_TOTAL_TIME = timedelta(seconds=5) + +MAP_RESOURCES = [ + "api/netmap", +] + + +class MapGenerationTest(PerformanceTest): + + TEST_NAME = "Map generation performance test" + + def __init__(self, island_client, config_parser, analyzers, + timeout, log_handler, break_on_timeout): + self.island_client = island_client + self.config_parser = config_parser + exploitation_test = ExploitationTest(MapGenerationTest.TEST_NAME, island_client, + config_parser, analyzers, timeout, log_handler) + performance_config = PerformanceTestConfig(max_allowed_single_page_time=MAX_ALLOWED_SINGLE_PAGE_TIME, + max_allowed_total_time=MAX_ALLOWED_TOTAL_TIME, + endpoints_to_test=MAP_RESOURCES, + break_on_timeout=break_on_timeout) + self.performance_test_workflow = PerformanceTestWorkflow(MapGenerationTest.TEST_NAME, + exploitation_test, + performance_config) + + def run(self): + self.performance_test_workflow.run() diff --git a/envs/monkey_zoo/blackbox/tests/performance/performance_test.py b/envs/monkey_zoo/blackbox/tests/performance/performance_test.py new file mode 100644 index 00000000000..b26c20f9396 --- /dev/null +++ b/envs/monkey_zoo/blackbox/tests/performance/performance_test.py @@ -0,0 +1,16 @@ +from abc import ABCMeta, abstractmethod + +from envs.monkey_zoo.blackbox.tests.basic_test import BasicTest + + +class PerformanceTest(BasicTest, metaclass=ABCMeta): + + @abstractmethod + def __init__(self, island_client, config_parser, analyzers, + timeout, log_handler, break_on_timeout): + pass + + @property + @abstractmethod + def TEST_NAME(self): + pass diff --git a/envs/monkey_zoo/blackbox/tests/performance/performance_test_config.py b/envs/monkey_zoo/blackbox/tests/performance/performance_test_config.py new file mode 100644 index 00000000000..8ed2b5a626a --- /dev/null +++ b/envs/monkey_zoo/blackbox/tests/performance/performance_test_config.py @@ -0,0 +1,12 @@ +from datetime import timedelta +from typing import List + + +class PerformanceTestConfig: + + def __init__(self, max_allowed_single_page_time: timedelta, max_allowed_total_time: timedelta, + endpoints_to_test: List[str], break_on_timeout=False): + self.max_allowed_single_page_time = max_allowed_single_page_time + self.max_allowed_total_time = max_allowed_total_time + self.endpoints_to_test = endpoints_to_test + self.break_on_timeout = break_on_timeout diff --git a/envs/monkey_zoo/blackbox/tests/performance/performance_test_workflow.py b/envs/monkey_zoo/blackbox/tests/performance/performance_test_workflow.py new file mode 100644 index 00000000000..3157140a981 --- /dev/null +++ b/envs/monkey_zoo/blackbox/tests/performance/performance_test_workflow.py @@ -0,0 +1,31 @@ +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.performance_test_config import PerformanceTestConfig +from envs.monkey_zoo.blackbox.tests.performance.endpoint_performance_test import EndpointPerformanceTest + + +class PerformanceTestWorkflow(BasicTest): + + def __init__(self, name, exploitation_test: ExploitationTest, performance_config: PerformanceTestConfig): + self.name = name + self.exploitation_test = exploitation_test + self.island_client = exploitation_test.island_client + self.config_parser = exploitation_test.config_parser + self.performance_config = performance_config + + def run(self): + self.island_client.import_config(self.config_parser.config_raw) + self.exploitation_test.print_test_starting_info() + try: + self.island_client.run_monkey_local() + self.exploitation_test.test_until_timeout() + finally: + self.island_client.kill_all_monkeys() + self.exploitation_test.wait_until_monkeys_die() + self.exploitation_test.wait_for_monkey_process_to_finish() + performance_test = EndpointPerformanceTest(self.name, self.performance_config, self.island_client) + try: + assert performance_test.run() + finally: + self.exploitation_test.parse_logs() + self.island_client.reset_env() diff --git a/envs/monkey_zoo/blackbox/tests/performance/report_generation.py b/envs/monkey_zoo/blackbox/tests/performance/report_generation.py new file mode 100644 index 00000000000..52fe762886c --- /dev/null +++ b/envs/monkey_zoo/blackbox/tests/performance/report_generation.py @@ -0,0 +1,38 @@ +from datetime import timedelta + +from envs.monkey_zoo.blackbox.tests.exploitation import ExploitationTest +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 + +MAX_ALLOWED_SINGLE_PAGE_TIME = timedelta(seconds=2) +MAX_ALLOWED_TOTAL_TIME = timedelta(seconds=5) + +REPORT_RESOURCES = [ + "api/report/security", + "api/attack/report", + "api/report/zero_trust/findings", + "api/report/zero_trust/principles", + "api/report/zero_trust/pillars" +] + + +class ReportGenerationTest(PerformanceTest): + TEST_NAME = "Report generation performance test" + + def __init__(self, island_client, config_parser, analyzers, + timeout, log_handler, break_on_timeout): + self.island_client = island_client + self.config_parser = config_parser + exploitation_test = ExploitationTest(ReportGenerationTest.TEST_NAME, island_client, + config_parser, analyzers, timeout, log_handler) + performance_config = PerformanceTestConfig(max_allowed_single_page_time=MAX_ALLOWED_SINGLE_PAGE_TIME, + max_allowed_total_time=MAX_ALLOWED_TOTAL_TIME, + endpoints_to_test=REPORT_RESOURCES, + break_on_timeout=break_on_timeout) + self.performance_test_workflow = PerformanceTestWorkflow(ReportGenerationTest.TEST_NAME, + exploitation_test, + performance_config) + + def run(self): + self.performance_test_workflow.run() diff --git a/monkey/common/version.py b/monkey/common/version.py index 9d60e636ca1..fd706d90924 100644 --- a/monkey/common/version.py +++ b/monkey/common/version.py @@ -4,7 +4,7 @@ MAJOR = "1" MINOR = "8" -PATCH = "0" +PATCH = "1" build_file_path = Path(__file__).parent.joinpath("BUILD") with open(build_file_path, "r") as build_file: BUILD = build_file.read() diff --git a/monkey/infection_monkey/exploit/vsftpd.py b/monkey/infection_monkey/exploit/vsftpd.py index 82954b99bd1..1305e394632 100644 --- a/monkey/infection_monkey/exploit/vsftpd.py +++ b/monkey/infection_monkey/exploit/vsftpd.py @@ -129,7 +129,7 @@ def _exploit_host(self): change_permission = str.encode(str(change_permission) + '\n') LOG.info("change_permission command is %s", change_permission) backdoor_socket.send(change_permission) - T1222Telem(ScanStatus.USED, change_permission, self.host).send() + T1222Telem(ScanStatus.USED, change_permission.decode(), self.host).send() # Run monkey on the machine parameters = build_monkey_commandline(self.host, get_monkey_depth() - 1) @@ -143,7 +143,7 @@ def _exploit_host(self): if backdoor_socket.send(run_monkey): LOG.info("Executed monkey '%s' on remote victim %r (cmdline=%r)", self._config.dropper_target_path_linux, self.host, run_monkey) - self.add_executed_cmd(run_monkey) + self.add_executed_cmd(run_monkey.decode()) return True else: return False diff --git a/monkey/infection_monkey/requirements.txt b/monkey/infection_monkey/requirements.txt index 7dd61cd19f2..858e5652b18 100644 --- a/monkey/infection_monkey/requirements.txt +++ b/monkey/infection_monkey/requirements.txt @@ -9,7 +9,11 @@ git+https://github.com/guardicore/pyinstaller ecdsa netifaces ipaddress -wmi +# Locking WMI since version 1.5 introduced breaking change on Linux agent compilation. +# See breaking change here: https://github.com/tjguk/wmi/commit/dcf8e3eca79bb8c0101ffb83e25c066b0ba9e16d +# Causes pip to error with: +# Could not find a version that satisfies the requirement pywin32 (from wmi->-r /src/infection_monkey/requirements.txt (line 12)) (from versions: none) +wmi==1.4.9 pywin32 ; sys_platform == 'win32' pymssql<3.0 pyftpdlib diff --git a/monkey/infection_monkey/telemetry/base_telem.py b/monkey/infection_monkey/telemetry/base_telem.py index 2d59b9cbf0e..7617ab4e394 100644 --- a/monkey/infection_monkey/telemetry/base_telem.py +++ b/monkey/infection_monkey/telemetry/base_telem.py @@ -17,12 +17,16 @@ class BaseTelem(object, metaclass=abc.ABCMeta): def __init__(self): pass - def send(self): + def send(self, log_data=True): """ Sends telemetry to island """ data = self.get_data() - logger.debug("Sending {} telemetry. Data: {}".format(self.telem_category, json.dumps(data))) + 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) @property diff --git a/monkey/infection_monkey/telemetry/system_info_telem.py b/monkey/infection_monkey/telemetry/system_info_telem.py index a4b1c0bd0e0..69ee7beda9e 100644 --- a/monkey/infection_monkey/telemetry/system_info_telem.py +++ b/monkey/infection_monkey/telemetry/system_info_telem.py @@ -17,3 +17,6 @@ def __init__(self, system_info): def get_data(self): return self.system_info + + def send(self, log_data=False): + super(SystemInfoTelem, self).send(log_data) diff --git a/monkey/monkey_island/cc/environment/__init__.py b/monkey/monkey_island/cc/environment/__init__.py index ec7c7a0f441..195778e16d8 100644 --- a/monkey/monkey_island/cc/environment/__init__.py +++ b/monkey/monkey_island/cc/environment/__init__.py @@ -1,7 +1,7 @@ from abc import ABCMeta, abstractmethod from datetime import timedelta import os -from Crypto.Hash import SHA3_512 +import hashlib __author__ = 'itay.mizeretz' @@ -45,10 +45,11 @@ def is_debug(self): def get_auth_expiration_time(self): return self._AUTH_EXPIRATION_TIME - def hash_secret(self, secret): - h = SHA3_512.new() - h.update(secret) - return h.hexdigest() + @staticmethod + def hash_secret(secret): + hash_obj = hashlib.sha3_512() + hash_obj.update(secret.encode('utf-8')) + return hash_obj.hexdigest() def get_deployment(self): return self._get_from_config('deployment', 'unknown') diff --git a/monkey/monkey_island/cc/environment/test_aws.py b/monkey/monkey_island/cc/environment/test_aws.py new file mode 100644 index 00000000000..222e975304d --- /dev/null +++ b/monkey/monkey_island/cc/environment/test_aws.py @@ -0,0 +1,26 @@ +from monkey_island.cc.auth import User +from monkey_island.cc.testing.IslandTestCase import IslandTestCase +from monkey_island.cc.environment.aws import AwsEnvironment + +import hashlib + + +class TestAwsEnvironment(IslandTestCase): + def test_get_auth_users(self): + env = AwsEnvironment() + # This is "injecting" the instance id to the env. This is the UTs aren't always executed on the same AWS machine + # (might not be an AWS machine at all). Perhaps it would have been more elegant to create a Mock, but not worth it for + # this small test. + env._instance_id = "i-666" + hash_obj = hashlib.sha3_512() + hash_obj.update(b"i-666") + auth_users = env.get_auth_users() + assert isinstance(auth_users, list) + assert len(auth_users) == 1 + auth_user = auth_users[0] + assert isinstance(auth_user, User) + assert auth_user.id == 1 + assert auth_user.username == "monkey" + assert auth_user.secret == hash_obj.hexdigest() + + diff --git a/monkey/monkey_island/cc/main.py b/monkey/monkey_island/cc/main.py index 09f079c19eb..50e3bdd7c64 100644 --- a/monkey/monkey_island/cc/main.py +++ b/monkey/monkey_island/cc/main.py @@ -31,7 +31,7 @@ from monkey_island.cc.setup import setup -def main(should_setup_only): +def main(should_setup_only=False): logger.info("Starting bootloader server") mongo_url = os.environ.get('MONGO_URL', env.get_mongo_url()) bootloader_server_thread = Thread(target=BootloaderHttpServer(mongo_url).serve_forever, daemon=True) diff --git a/monkey/monkey_island/cc/services/attack/attack_data b/monkey/monkey_island/cc/services/attack/attack_data index c139e37bdc5..fb8942b1a10 160000 --- a/monkey/monkey_island/cc/services/attack/attack_data +++ b/monkey/monkey_island/cc/services/attack/attack_data @@ -1 +1 @@ -Subproject commit c139e37bdc51acbc7d0488a5be48553caffdbbd7 +Subproject commit fb8942b1a10f4e734ed75542f2ccae7cbd72c46d diff --git a/monkey/monkey_island/cc/services/attack/mitre_api_interface.py b/monkey/monkey_island/cc/services/attack/mitre_api_interface.py index ad4419be5df..6390c600ba6 100644 --- a/monkey/monkey_island/cc/services/attack/mitre_api_interface.py +++ b/monkey/monkey_island/cc/services/attack/mitre_api_interface.py @@ -1,6 +1,6 @@ from typing import List, Dict -from stix2 import FileSystemSource, Filter, CourseOfAction, AttackPattern, v20 +from stix2 import FileSystemSource, Filter, CourseOfAction, AttackPattern class MitreApiInterface: @@ -32,14 +32,14 @@ def get_technique_and_mitigation_relationships() -> List[CourseOfAction]: return all_techniques @staticmethod - def get_stix2_external_reference_id(stix2_data: v20._DomainObject) -> str: + def get_stix2_external_reference_id(stix2_data) -> str: for reference in stix2_data['external_references']: if reference['source_name'] == "mitre-attack" and 'external_id' in reference: return reference['external_id'] return '' @staticmethod - def get_stix2_external_reference_url(stix2_data: v20._DomainObject) -> str: + def get_stix2_external_reference_url(stix2_data) -> str: for reference in stix2_data['external_references']: if 'url' in reference: return reference['url'] diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py b/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py index 80dbe75183a..bd4e07c2451 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py @@ -5,7 +5,7 @@ from common.utils.attack_utils import ScanStatus from monkey_island.cc.services.attack.attack_config import AttackConfig from common.utils.code_utils import abstractstatic -from cc.models.attack.attack_mitigations import AttackMitigations +from monkey_island.cc.models.attack.attack_mitigations import AttackMitigations logger = logging.getLogger(__name__) diff --git a/monkey/monkey_island/deb-package/DEBIAN_MONGO/control b/monkey/monkey_island/deb-package/DEBIAN_MONGO/control index a4737100588..a7bc2373eb5 100644 --- a/monkey/monkey_island/deb-package/DEBIAN_MONGO/control +++ b/monkey/monkey_island/deb-package/DEBIAN_MONGO/control @@ -5,4 +5,4 @@ Homepage: https://www.infectionmonkey.com Priority: optional Version: 1.0 Description: Guardicore Infection Monkey Island installation package -Depends: openssl, python3-pip, python3-dev +Depends: openssl, python3.7-dev, python3.7-venv, python3-venv, build-essential diff --git a/monkey/monkey_island/deb-package/DEBIAN_MONGO/postinst b/monkey/monkey_island/deb-package/DEBIAN_MONGO/postinst index f79a71913b9..f12b31b73e2 100644 --- a/monkey/monkey_island/deb-package/DEBIAN_MONGO/postinst +++ b/monkey/monkey_island/deb-package/DEBIAN_MONGO/postinst @@ -1,20 +1,42 @@ #!/bin/bash +# See the "Depends" field of the control file for what packages this scripts depends on. +# Here are the explanations for the current deps: +# Dependency - Why is it required +## openssl - Server certificate generation +## python3.7-dev - Server runtime +## python3.7-venv - For creating virtual env to install all the server pip deps (don't want to pollute system python) +## python3-venv - python3.7-venv doesn't work without it since you need ensure-pip +## build-essential - for compiling python dependencies that don't come in a pre-compiled wheel, like `netifaces` + +echo "Installing Monkey Island (Infection Monkey server)..." + MONKEY_FOLDER=/var/monkey INSTALLATION_FOLDER=/var/monkey/monkey_island/installation PYTHON_FOLDER=/var/monkey/monkey_island/bin/python +PYTHON_VERSION=python3.7 # Prepare python virtualenv -pip3 install virtualenv --no-index --find-links file://$INSTALLATION_FOLDER -python3 -m virtualenv -p python3 ${PYTHON_FOLDER} +# This is using the apt package `python3.7-venv` which is listed in the `control` file as a dependency. +# See https://packages.debian.org/stable/python/python3.7-venv +echo "Using $(command -v $PYTHON_VERSION) as the base for virtualenv creation" +$PYTHON_VERSION -m venv ${PYTHON_FOLDER} +# shellcheck disable=SC1090 +source ${PYTHON_FOLDER}/bin/activate + +echo "Installing Python dependencies using $(command -v python) and $(command -v pip)..." +# First, make sure that pip is updated +python -m pip install --upgrade pip +# Then install the dependecies from the pre-downloaded whl and tar.gz file +python -m pip install -r $MONKEY_FOLDER/monkey_island/requirements.txt --no-index --find-links file://$INSTALLATION_FOLDER -# install pip requirements -${PYTHON_FOLDER}/bin/python -m pip install -r $MONKEY_FOLDER/monkey_island/requirements.txt --no-index --find-links file://$INSTALLATION_FOLDER +deactivate # remove installation folder and unnecessary files rm -rf ${INSTALLATION_FOLDER} rm -f ${MONKEY_FOLDER}/monkey_island/requirements.txt +echo "Installing mongodb..." ${MONKEY_FOLDER}/monkey_island/install_mongo.sh ${MONKEY_FOLDER}/monkey_island/bin/mongodb if [ -d "/etc/systemd/network" ]; then @@ -25,11 +47,17 @@ if [ -d "/etc/systemd/network" ]; then systemctl enable monkey-island fi -${MONKEY_FOLDER}/monkey_island/create_certificate.sh ${MONKEY_FOLDER}/monkey_island/ +echo "Creating server certificate..." +${MONKEY_FOLDER}/monkey_island/create_certificate.sh ${MONKEY_FOLDER}/monkey_island/cc +echo "Starting services..." service monkey-island start service monkey-mongo start -echo Monkey Island installation ended +echo "" +echo "Monkey Island installation ended." +echo "The server should be accessible soon via https://:5000/" +echo "To check the Island's status, run 'sudo service monkey-island status'" +echo "" -exit 0 \ No newline at end of file +exit 0 diff --git a/monkey/monkey_island/linux/create_certificate.sh b/monkey/monkey_island/linux/create_certificate.sh index 7e306a82280..985f607bc94 100644 --- a/monkey/monkey_island/linux/create_certificate.sh +++ b/monkey/monkey_island/linux/create_certificate.sh @@ -2,8 +2,29 @@ server_root=${1:-"./cc"} +echo "Creating server cetificate. Server root: $server_root" +# We override the RANDFILE determined by default openssl.cnf, if it doesn't exist. +# This is a known issue with the current version of openssl on Ubuntu 18.04 - once they release +# a new version, we can delete this command. See +# https://github.com/openssl/openssl/commit/0f58220973a02248ca5c69db59e615378467b9c8#diff-8ce6aaad88b10ed2b3b4592fd5c8e03a +# for more details. +DEFAULT_RND_FILE_PATH=~/.rnd +CREATED_RND_FILE=false +if [ ! -f /tmp/foo.txt ]; then # If the file already exists, assume that the contents are fine, and don't change them. + echo "Creating rand seed file in $DEFAULT_RND_FILE_PATH" + dd bs=1024 count=2 "$DEFAULT_RND_FILE_PATH" + chmod 666 "$DEFAULT_RND_FILE_PATH" + CREATED_RND_FILE=true +fi +echo "Generating key in $server_root/server.key..." openssl genrsa -out "$server_root"/server.key 2048 +echo "Generating csr in $server_root/server.csr..." openssl req -new -key "$server_root"/server.key -out "$server_root"/server.csr -subj "/C=GB/ST=London/L=London/O=Global Security/OU=Monkey Department/CN=monkey.com" -openssl x509 -req -days 366 -in "$server_root"/server.csr -signkey "$server_root"/server.key -out $server_root/server.crt +echo "Generating certificate in $server_root/server.crt..." +openssl x509 -req -days 366 -in "$server_root"/server.csr -signkey "$server_root"/server.key -out "$server_root"/server.crt +# Shove some new random data into the file to override the original seed we put in. +if [ "$CREATED_RND_FILE" = true ] ; then + dd bs=1024 count=2 "$DEFAULT_RND_FILE_PATH" +fi diff --git a/monkey/monkey_island/monkey_island.spec b/monkey/monkey_island/monkey_island.spec index ef6d9c2d33e..59f95e34f91 100644 --- a/monkey/monkey_island/monkey_island.spec +++ b/monkey/monkey_island/monkey_island.spec @@ -1,7 +1,7 @@ # -*- mode: python -*- import os import platform - +import sys __author__ = 'itay.mizeretz' @@ -9,15 +9,20 @@ block_cipher = None def main(): + # These data files and folders will be included in the bundle. + # The format of the tuples is (src, dest_dir). See https://pythonhosted.org/PyInstaller/spec-files.html#adding-data-files + added_datas = [ + ("../common/BUILD", "/common"), + ("../monkey_island/cc/services/attack/attack_data", "/monkey_island/cc/services/attack/attack_data") + ] + a = Analysis(['cc/main.py'], pathex=['..'], hiddenimports=get_hidden_imports(), - hookspath=None, + hookspath=[os.path.join(".", "pyinstaller_hooks")], runtime_hooks=None, binaries=None, - datas=[ - ("../common/BUILD", "/common") - ], + datas=added_datas, excludes=None, win_no_prefer_redirects=None, win_private_assemblies=None, @@ -36,8 +41,7 @@ def main(): name=get_monkey_filename(), debug=False, strip=get_exe_strip(), - upx=True, - upx_exclude=['vcruntime140.dll'], + upx=False, console=True, icon=get_exe_icon()) @@ -74,7 +78,7 @@ def get_linux_only_binaries(): def get_hidden_imports(): - return ['_cffi_backend', 'queue'] if is_windows() else ['_cffi_backend'] + return ['_cffi_backend', 'queue', 'pkg_resources.py2_warn'] if is_windows() else ['_cffi_backend'] def get_msvcr(): diff --git a/monkey/monkey_island/pyinstaller_hooks/__init__.py b/monkey/monkey_island/pyinstaller_hooks/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/monkey/monkey_island/pyinstaller_hooks/hook-stix2.py b/monkey/monkey_island/pyinstaller_hooks/hook-stix2.py new file mode 100644 index 00000000000..22a9c977481 --- /dev/null +++ b/monkey/monkey_island/pyinstaller_hooks/hook-stix2.py @@ -0,0 +1,7 @@ +# Workaround for packaging Monkey Island using PyInstaller. See https://github.com/oasis-open/cti-python-stix2/issues/218 + +import os +from PyInstaller.utils.hooks import get_module_file_attribute + +stix2_dir = os.path.dirname(get_module_file_attribute('stix2')) +datas = [(stix2_dir, 'stix2')] diff --git a/monkey/monkey_island/requirements.txt b/monkey/monkey_island/requirements.txt index cad53d1c8a5..b5baed7f4d5 100644 --- a/monkey/monkey_island/requirements.txt +++ b/monkey/monkey_island/requirements.txt @@ -1,5 +1,4 @@ pytest -bson python-dateutil tornado werkzeug