diff --git a/envs/monkey_zoo/blackbox/README.md b/envs/monkey_zoo/blackbox/README.md index 0c66ba58a..12b33ebce 100644 --- a/envs/monkey_zoo/blackbox/README.md +++ b/envs/monkey_zoo/blackbox/README.md @@ -1,4 +1,5 @@ # Automatic blackbox tests ### Prerequisites 1. Download google sdk: https://cloud.google.com/sdk/docs/ -2. Download service account key GCP console -> IAM -> service accounts(you can use the same key used to authenticate terraform scripts) +2. Download service account key for MonkeyZoo project (if you deployed MonkeyZoo via terraform scripts then you already have it). +GCP console -> IAM -> service accounts(you can use the same key used to authenticate terraform scripts) 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 a06617b08..1388e0842 100644 --- a/envs/monkey_zoo/blackbox/island_client/monkey_island_client.py +++ b/envs/monkey_zoo/blackbox/island_client/monkey_island_client.py @@ -1,6 +1,7 @@ from time import sleep import json +import logging from bson import json_util from envs.monkey_zoo.blackbox.island_client.monkey_island_requests import MonkeyIslandRequests @@ -30,9 +31,9 @@ class MonkeyIslandClient(object): def run_monkey_local(self): response = self.requests.post_json("api/local-monkey", dict_data={"action": "run"}) if MonkeyIslandClient.monkey_ran_successfully(response): - print("Running the monkey.") + logging.info("Running the monkey.") else: - print("Failed to run the monkey.") + logging.error("Failed to run the monkey.") assert False @staticmethod @@ -42,36 +43,35 @@ class MonkeyIslandClient(object): @avoid_race_condition def kill_all_monkeys(self): if self.requests.get("api", {"action": "killall"}).ok: - print("Killing all monkeys after the test.") + logging.info("Killing all monkeys after the test.") else: - print("Failed to kill all monkeys.") + logging.error("Failed to kill all monkeys.") assert False @avoid_race_condition def reset_env(self): if self.requests.get("api", {"action": "reset"}).ok: - print("Resetting environment after the test.") + logging.info("Resetting environment after the test.") else: - print("Failed to reset the environment.") + logging.error("Failed to reset the environment.") assert False def find_monkeys_in_db(self, query): + if query is None: + raise TypeError response = self.requests.get(MONKEY_TEST_ENDPOINT, MonkeyIslandClient.form_find_query_for_request(query)) - try: - return MonkeyIslandClient.get_test_query_results(response) - except Exception: - print("Ran into trouble parsing response for monkey query") - raise + return MonkeyIslandClient.get_test_query_results(response) + + def get_all_monkeys_from_db(self): + response = self.requests.get(MONKEY_TEST_ENDPOINT, + MonkeyIslandClient.form_find_query_for_request(None)) + return MonkeyIslandClient.get_test_query_results(response) def find_log_in_db(self, query): response = self.requests.get(LOG_TEST_ENDPOINT, MonkeyIslandClient.form_find_query_for_request(query)) - try: - return MonkeyIslandClient.get_test_query_results(response) - except Exception: - print("Ran into trouble parsing response for log query") - raise + return MonkeyIslandClient.get_test_query_results(response) @staticmethod def form_find_query_for_request(query): 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 8452f2562..5cf4d7b6a 100644 --- a/envs/monkey_zoo/blackbox/island_client/monkey_island_requests.py +++ b/envs/monkey_zoo/blackbox/island_client/monkey_island_requests.py @@ -1,6 +1,8 @@ import requests # SHA3-512 of '1234567890!@#$%^&*()_nothing_up_my_sleeve_1234567890!@#$%^&*()' +import logging + NO_AUTH_CREDS = '55e97c9dcfd22b8079189ddaeea9bce8125887e3237b800c6176c9afa80d2062' \ '8d2c8d0b1538d2208c1444ac66535b764a3d902b35e751df3faec1e477ed3557' @@ -14,7 +16,7 @@ class MonkeyIslandRequests(object): try: return self.get_jwt_from_server() except requests.ConnectionError: - print("Unable to connect to island, aborting!") + logging.error("Unable to connect to island, aborting!") assert False def get_jwt_from_server(self): diff --git a/envs/monkey_zoo/blackbox/log_handlers/monkey_log.py b/envs/monkey_zoo/blackbox/log_handlers/monkey_log.py index bcc304535..a0cf236fc 100644 --- a/envs/monkey_zoo/blackbox/log_handlers/monkey_log.py +++ b/envs/monkey_zoo/blackbox/log_handlers/monkey_log.py @@ -1,5 +1,6 @@ import os +import logging from bson import ObjectId @@ -11,7 +12,7 @@ class MonkeyLog(object): def download_log(self, island_client): log = island_client.find_log_in_db({'monkey_id': ObjectId(self.monkey['id'])}) if not log: - print("Log for monkey {} not found".format(self.monkey['ip_addresses'][0])) + logging.error("Log for monkey {} not found".format(self.monkey['ip_addresses'][0])) return False else: self.write_log_to_file(log) diff --git a/envs/monkey_zoo/blackbox/log_handlers/log_parser.py b/envs/monkey_zoo/blackbox/log_handlers/monkey_log_parser.py similarity index 80% rename from envs/monkey_zoo/blackbox/log_handlers/log_parser.py rename to envs/monkey_zoo/blackbox/log_handlers/monkey_log_parser.py index 9f3c34f57..d12db4f58 100644 --- a/envs/monkey_zoo/blackbox/log_handlers/log_parser.py +++ b/envs/monkey_zoo/blackbox/log_handlers/monkey_log_parser.py @@ -1,7 +1,7 @@ import re -class LogParser(object): +class MonkeyLogParser(object): def __init__(self, log_path): self.log_path = log_path @@ -13,7 +13,7 @@ class LogParser(object): def print_errors(self): print("Errors:") - for error_line in LogParser.get_errors(self.log_contents): + for error_line in MonkeyLogParser.get_errors(self.log_contents): print(error_line) @staticmethod @@ -23,7 +23,7 @@ class LogParser(object): def print_warnings(self): print("Warnings:") - for warning_line in LogParser.get_warnings(self.log_contents): + for warning_line in MonkeyLogParser.get_warnings(self.log_contents): print(warning_line) @staticmethod diff --git a/envs/monkey_zoo/blackbox/log_handlers/logs_downloader.py b/envs/monkey_zoo/blackbox/log_handlers/monkey_logs_downloader.py similarity index 81% rename from envs/monkey_zoo/blackbox/log_handlers/logs_downloader.py rename to envs/monkey_zoo/blackbox/log_handlers/monkey_logs_downloader.py index 446960147..c3bd3ac18 100644 --- a/envs/monkey_zoo/blackbox/log_handlers/logs_downloader.py +++ b/envs/monkey_zoo/blackbox/log_handlers/monkey_logs_downloader.py @@ -1,7 +1,9 @@ +import logging + from envs.monkey_zoo.blackbox.log_handlers.monkey_log import MonkeyLog -class LogsDownloader(object): +class MonkeyLogsDownloader(object): def __init__(self, island_client, log_dir_path): self.island_client = island_client @@ -9,8 +11,8 @@ class LogsDownloader(object): self.monkey_log_paths = [] def download_monkey_logs(self): - print("Downloading each monkey log.") - all_monkeys = self.island_client.find_monkeys_in_db(None) + logging.info("Downloading each monkey log.") + all_monkeys = self.island_client.get_all_monkeys_from_db() for monkey in all_monkeys: downloaded_log_path = self._download_monkey_log(monkey) if downloaded_log_path: 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 e2c87a320..6a7c2f284 100644 --- a/envs/monkey_zoo/blackbox/log_handlers/test_logs_handler.py +++ b/envs/monkey_zoo/blackbox/log_handlers/test_logs_handler.py @@ -1,28 +1,31 @@ import os import shutil -from envs.monkey_zoo.blackbox.log_handlers.log_parser import LogParser -from envs.monkey_zoo.blackbox.log_handlers.logs_downloader import LogsDownloader +import logging + +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' class TestLogsHandler(object): - def __init__(self, test_name, island_client): + def __init__(self, test_name, island_client, log_dir_path): self.test_name = test_name self.island_client = island_client - self.log_dir_path = os.path.join(TestLogsHandler.get_log_dir_path(), self.test_name) + self.log_dir_path = os.path.join(log_dir_path, self.test_name) def parse_test_logs(self): log_paths = self.download_logs() if not log_paths: - print("No logs were downloaded, maybe no monkeys were ran?") + logging.error("No logs were downloaded. Maybe no monkeys were ran " + "or early exception prevented log download?") return TestLogsHandler.parse_logs(log_paths) def download_logs(self): self.try_create_log_dir_for_test() - downloader = LogsDownloader(self.island_client, self.log_dir_path) + downloader = MonkeyLogsDownloader(self.island_client, self.log_dir_path) downloader.download_monkey_logs() return downloader.monkey_log_paths @@ -30,21 +33,17 @@ class TestLogsHandler(object): try: os.mkdir(self.log_dir_path) except Exception as e: - print("Can't create a dir for test logs: {}".format(e)) + logging.error("Can't create a dir for test logs: {}".format(e)) @staticmethod - def get_log_dir_path(): - return os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), LOG_DIR_NAME) - - @staticmethod - def delete_log_folder_contents(): - shutil.rmtree(TestLogsHandler.get_log_dir_path(), ignore_errors=True) - os.mkdir(TestLogsHandler.get_log_dir_path()) + def delete_log_folder_contents(log_dir_path): + shutil.rmtree(log_dir_path, ignore_errors=True) + os.mkdir(log_dir_path) @staticmethod def parse_logs(log_paths): for log_path in log_paths: - print("Info from log at {}".format(log_path)) - log_parser = LogParser(log_path) + logging.info("Info from log at {}".format(log_path)) + log_parser = MonkeyLogParser(log_path) log_parser.print_errors() log_parser.print_warnings() diff --git a/envs/monkey_zoo/blackbox/requirements.txt b/envs/monkey_zoo/blackbox/requirements.txt index 88d363f95..c60d08124 100644 --- a/envs/monkey_zoo/blackbox/requirements.txt +++ b/envs/monkey_zoo/blackbox/requirements.txt @@ -1,2 +1,3 @@ pytest -unittest \ No newline at end of file +unittest +pytest-random-order diff --git a/envs/monkey_zoo/blackbox/test_blackbox.py b/envs/monkey_zoo/blackbox/test_blackbox.py index e8819b375..e9be96796 100644 --- a/envs/monkey_zoo/blackbox/test_blackbox.py +++ b/envs/monkey_zoo/blackbox/test_blackbox.py @@ -1,3 +1,6 @@ +import os +import logging + import pytest from time import sleep @@ -13,6 +16,7 @@ MACHINE_BOOTUP_WAIT_SECONDS = 30 GCP_TEST_MACHINE_LIST = ['sshkeys-11', 'sshkeys-12', 'elastic-4', 'elastic-5', 'haddop-2-v3', 'hadoop-3', 'mssql-16', 'mimikatz-14', 'mimikatz-15', 'final-test-struts2-23', 'final-test-struts2-24', 'tunneling-9', 'tunneling-10', 'tunneling-11', 'weblogic-18', 'weblogic-19', 'shellshock-8'] +LOG_DIR_PATH = "./logs" @pytest.fixture(autouse=True, scope='session') @@ -29,8 +33,8 @@ def GCPHandler(request): @pytest.fixture(autouse=True, scope='session') def delete_logs(): - print("Deleting monkey logs before new tests.") - TestLogsHandler.delete_log_folder_contents() + logging.info("Deleting monkey logs before new tests.") + TestLogsHandler.delete_log_folder_contents(TestMonkeyBlackbox.get_log_dir_path()) def wait_machine_bootup(): @@ -52,11 +56,17 @@ class TestMonkeyBlackbox(object): def run_basic_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(test_name, island_client, config_parser, [analyzer], - timeout_in_seconds).run() + timeout_in_seconds, + log_handler).run() + + @staticmethod + def get_log_dir_path(): + return os.path.abspath(LOG_DIR_PATH) def test_server_online(self, island_client): assert island_client.get_api_status() is not None @@ -75,7 +85,7 @@ class TestMonkeyBlackbox(object): def test_smb_pth(self, island_client): TestMonkeyBlackbox.run_basic_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") diff --git a/envs/monkey_zoo/blackbox/tests/basic_test.py b/envs/monkey_zoo/blackbox/tests/basic_test.py index dfe0e87ee..71c53b841 100644 --- a/envs/monkey_zoo/blackbox/tests/basic_test.py +++ b/envs/monkey_zoo/blackbox/tests/basic_test.py @@ -1,21 +1,25 @@ from time import sleep +import logging + from envs.monkey_zoo.blackbox.utils.test_timer import TestTimer from envs.monkey_zoo.blackbox.log_handlers.test_logs_handler import TestLogsHandler MAX_TIME_FOR_MONKEYS_TO_DIE = 5*60 WAIT_TIME_BETWEEN_REQUESTS = 10 TIME_FOR_MONKEY_PROCESS_TO_FINISH = 40 +DELAY_BETWEEN_ANALYSIS = 3 class BasicTest(object): - def __init__(self, name, island_client, config_parser, analyzers, timeout): + 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) @@ -31,29 +35,29 @@ class BasicTest(object): self.island_client.reset_env() def print_test_starting_info(self): - print("Started {} test".format(self.name)) - print("Machines participating in test:") - for target_ip in self.config_parser.get_ips_of_targets(): - print(" "+target_ip) - print("") + logging.info("Started {} test".format(self.name)) + logging.info("Machines participating in test:") + logging.info(" ".join(self.config_parser.get_ips_of_targets())) + logging.info("") def test_until_timeout(self): timer = TestTimer(self.timeout) - while not timer.timed_out(): + while not timer.is_timed_out(): if self.all_analyzers_pass(): self.log_success(timer) return + sleep(DELAY_BETWEEN_ANALYSIS) self.log_failure(timer) assert False def log_success(self, timer): - print(self.get_analyzer_logs()) - print("{} test passed, time taken: {:.1f} seconds.".format(self.name, timer.get_time_taken())) + logging.info(self.get_analyzer_logs()) + logging.info("{} test passed, time taken: {:.1f} seconds.".format(self.name, timer.get_time_taken())) def log_failure(self, timer): - print(self.get_analyzer_logs()) - print("{} test failed because of timeout. Time taken: {:.1f} seconds.".format(self.name, - timer.get_time_taken())) + logging.info(self.get_analyzer_logs()) + logging.error("{} test failed because of timeout. Time taken: {:.1f} seconds.".format(self.name, + timer.get_time_taken())) def all_analyzers_pass(self): for analyzer in self.analyzers: @@ -73,13 +77,18 @@ class BasicTest(object): sleep(WAIT_TIME_BETWEEN_REQUESTS) time_passed += WAIT_TIME_BETWEEN_REQUESTS if time_passed > MAX_TIME_FOR_MONKEYS_TO_DIE: - print("Some monkeys didn't die after the test, passing") + logging.error("Some monkeys didn't die after the test, failing") assert False def parse_logs(self): - print("\nParsing test logs:") - TestLogsHandler(self.name, self.island_client).parse_test_logs() + logging.info("\nParsing 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. + """ sleep(TIME_FOR_MONKEY_PROCESS_TO_FINISH) diff --git a/envs/monkey_zoo/blackbox/utils/gcp_machine_handlers.py b/envs/monkey_zoo/blackbox/utils/gcp_machine_handlers.py index 3d6e75bf4..cba6c8235 100644 --- a/envs/monkey_zoo/blackbox/utils/gcp_machine_handlers.py +++ b/envs/monkey_zoo/blackbox/utils/gcp_machine_handlers.py @@ -1,5 +1,7 @@ import subprocess +import logging + class GCPHandler(object): @@ -13,28 +15,28 @@ class GCPHandler(object): try: # pass the key file to gcp subprocess.call(GCPHandler.get_auth_command(key_path), shell=True) - print("GCP Handler passed key") + logging.info("GCP Handler passed key") # set project subprocess.call(GCPHandler.get_set_project_command(project_id), shell=True) - print("GCP Handler set project") - print("GCP Handler initialized successfully") + logging.info("GCP Handler set project") + logging.info("GCP Handler initialized successfully") except Exception as e: - print("GCP Handler failed to initialize: %s." % e) + logging.error("GCP Handler failed to initialize: %s." % e) def start_machines(self, machine_list): - print("Setting up all GCP machines...") + logging.info("Setting up all GCP machines...") try: subprocess.call((GCPHandler.MACHINE_STARTING_COMMAND % (machine_list, self.zone)), shell=True) - print("GCP machines successfully started.") + logging.info("GCP machines successfully started.") except Exception as e: - print("GCP Handler failed to start GCP machines: %s" % e) + logging.error("GCP Handler failed to start GCP machines: %s" % e) def stop_machines(self, machine_list): try: subprocess.call((GCPHandler.MACHINE_STOPPING_COMMAND % (machine_list, self.zone)), shell=True) - print("GCP machines stopped successfully.") + logging.info("GCP machines stopped successfully.") except Exception as e: - print("GCP Handler failed to stop network machines: %s" % e) + logging.error("GCP Handler failed to stop network machines: %s" % e) @staticmethod def get_auth_command(key_path): diff --git a/envs/monkey_zoo/blackbox/utils/test_timer.py b/envs/monkey_zoo/blackbox/utils/test_timer.py index c403d8a35..2c0ca490a 100644 --- a/envs/monkey_zoo/blackbox/utils/test_timer.py +++ b/envs/monkey_zoo/blackbox/utils/test_timer.py @@ -6,7 +6,7 @@ class TestTimer(object): self.timeout_time = TestTimer.get_timeout_time(timeout) self.start_time = time() - def timed_out(self): + def is_timed_out(self): return time() > self.timeout_time def get_time_taken(self): diff --git a/monkey/infection_monkey/build_windows.bat b/monkey/infection_monkey/build_windows.bat index e5ff5a805..fb12f2c0e 100644 --- a/monkey/infection_monkey/build_windows.bat +++ b/monkey/infection_monkey/build_windows.bat @@ -1 +1 @@ -pyinstaller -F --log-level=DEBUG --clean --upx-dir=.\bin monkey.spec \ No newline at end of file +C:\Programos\Python27\Scripts\pyinstaller.exe -F --log-level=DEBUG --clean --upx-dir=.\bin monkey.spec \ No newline at end of file diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index cd8df4705..11c0cf636 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -90,6 +90,8 @@ class InfectionMonkey(object): return self.set_default_port() + fbts + # Create a dir for monkey files if there isn't one utils.create_monkey_dir() diff --git a/monkey/monkey_island/cc/resources/test/__init__.py b/monkey/monkey_island/cc/resources/test/__init__.py index e69de29bb..28550f830 100644 --- a/monkey/monkey_island/cc/resources/test/__init__.py +++ b/monkey/monkey_island/cc/resources/test/__init__.py @@ -0,0 +1,4 @@ +""" +This package contains resources used by blackbox tests +to analize test results, download logs and so on. +"""