CR comments fixed

This commit is contained in:
VakarisZ 2019-10-01 10:42:51 +03:00
parent 72e30bb631
commit 73d434119d
15 changed files with 105 additions and 72 deletions

View File

@ -1,4 +1,5 @@
# Automatic blackbox tests # Automatic blackbox tests
### Prerequisites ### Prerequisites
1. Download google sdk: https://cloud.google.com/sdk/docs/ 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)

View File

@ -1,6 +1,7 @@
from time import sleep from time import sleep
import json import json
import logging
from bson import json_util 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
@ -30,9 +31,9 @@ class MonkeyIslandClient(object):
def run_monkey_local(self): def run_monkey_local(self):
response = self.requests.post_json("api/local-monkey", dict_data={"action": "run"}) response = self.requests.post_json("api/local-monkey", dict_data={"action": "run"})
if MonkeyIslandClient.monkey_ran_successfully(response): if MonkeyIslandClient.monkey_ran_successfully(response):
print("Running the monkey.") logging.info("Running the monkey.")
else: else:
print("Failed to run the monkey.") logging.error("Failed to run the monkey.")
assert False assert False
@staticmethod @staticmethod
@ -42,36 +43,35 @@ class MonkeyIslandClient(object):
@avoid_race_condition @avoid_race_condition
def kill_all_monkeys(self): def kill_all_monkeys(self):
if self.requests.get("api", {"action": "killall"}).ok: if self.requests.get("api", {"action": "killall"}).ok:
print("Killing all monkeys after the test.") logging.info("Killing all monkeys after the test.")
else: else:
print("Failed to kill all monkeys.") logging.error("Failed to kill all monkeys.")
assert False assert False
@avoid_race_condition @avoid_race_condition
def reset_env(self): def reset_env(self):
if self.requests.get("api", {"action": "reset"}).ok: if self.requests.get("api", {"action": "reset"}).ok:
print("Resetting environment after the test.") logging.info("Resetting environment after the test.")
else: else:
print("Failed to reset the environment.") logging.error("Failed to reset the environment.")
assert False assert False
def find_monkeys_in_db(self, query): def find_monkeys_in_db(self, query):
if query is None:
raise TypeError
response = self.requests.get(MONKEY_TEST_ENDPOINT, response = self.requests.get(MONKEY_TEST_ENDPOINT,
MonkeyIslandClient.form_find_query_for_request(query)) MonkeyIslandClient.form_find_query_for_request(query))
try: return MonkeyIslandClient.get_test_query_results(response)
return MonkeyIslandClient.get_test_query_results(response)
except Exception: def get_all_monkeys_from_db(self):
print("Ran into trouble parsing response for monkey query") response = self.requests.get(MONKEY_TEST_ENDPOINT,
raise MonkeyIslandClient.form_find_query_for_request(None))
return MonkeyIslandClient.get_test_query_results(response)
def find_log_in_db(self, query): def find_log_in_db(self, query):
response = self.requests.get(LOG_TEST_ENDPOINT, response = self.requests.get(LOG_TEST_ENDPOINT,
MonkeyIslandClient.form_find_query_for_request(query)) MonkeyIslandClient.form_find_query_for_request(query))
try: return MonkeyIslandClient.get_test_query_results(response)
return MonkeyIslandClient.get_test_query_results(response)
except Exception:
print("Ran into trouble parsing response for log query")
raise
@staticmethod @staticmethod
def form_find_query_for_request(query): def form_find_query_for_request(query):

View File

@ -1,6 +1,8 @@
import requests import requests
# SHA3-512 of '1234567890!@#$%^&*()_nothing_up_my_sleeve_1234567890!@#$%^&*()' # SHA3-512 of '1234567890!@#$%^&*()_nothing_up_my_sleeve_1234567890!@#$%^&*()'
import logging
NO_AUTH_CREDS = '55e97c9dcfd22b8079189ddaeea9bce8125887e3237b800c6176c9afa80d2062' \ NO_AUTH_CREDS = '55e97c9dcfd22b8079189ddaeea9bce8125887e3237b800c6176c9afa80d2062' \
'8d2c8d0b1538d2208c1444ac66535b764a3d902b35e751df3faec1e477ed3557' '8d2c8d0b1538d2208c1444ac66535b764a3d902b35e751df3faec1e477ed3557'
@ -14,7 +16,7 @@ class MonkeyIslandRequests(object):
try: try:
return self.get_jwt_from_server() return self.get_jwt_from_server()
except requests.ConnectionError: except requests.ConnectionError:
print("Unable to connect to island, aborting!") logging.error("Unable to connect to island, aborting!")
assert False assert False
def get_jwt_from_server(self): def get_jwt_from_server(self):

View File

@ -1,5 +1,6 @@
import os import os
import logging
from bson import ObjectId from bson import ObjectId
@ -11,7 +12,7 @@ class MonkeyLog(object):
def download_log(self, island_client): def download_log(self, island_client):
log = island_client.find_log_in_db({'monkey_id': ObjectId(self.monkey['id'])}) log = island_client.find_log_in_db({'monkey_id': ObjectId(self.monkey['id'])})
if not log: 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 return False
else: else:
self.write_log_to_file(log) self.write_log_to_file(log)

View File

@ -1,7 +1,7 @@
import re import re
class LogParser(object): class MonkeyLogParser(object):
def __init__(self, log_path): def __init__(self, log_path):
self.log_path = log_path self.log_path = log_path
@ -13,7 +13,7 @@ class LogParser(object):
def print_errors(self): def print_errors(self):
print("Errors:") 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) print(error_line)
@staticmethod @staticmethod
@ -23,7 +23,7 @@ class LogParser(object):
def print_warnings(self): def print_warnings(self):
print("Warnings:") 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) print(warning_line)
@staticmethod @staticmethod

View File

@ -1,7 +1,9 @@
import logging
from envs.monkey_zoo.blackbox.log_handlers.monkey_log import MonkeyLog 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): def __init__(self, island_client, log_dir_path):
self.island_client = island_client self.island_client = island_client
@ -9,8 +11,8 @@ class LogsDownloader(object):
self.monkey_log_paths = [] self.monkey_log_paths = []
def download_monkey_logs(self): def download_monkey_logs(self):
print("Downloading each monkey log.") logging.info("Downloading each monkey log.")
all_monkeys = self.island_client.find_monkeys_in_db(None) all_monkeys = self.island_client.get_all_monkeys_from_db()
for monkey in all_monkeys: for monkey in all_monkeys:
downloaded_log_path = self._download_monkey_log(monkey) downloaded_log_path = self._download_monkey_log(monkey)
if downloaded_log_path: if downloaded_log_path:

View File

@ -1,28 +1,31 @@
import os import os
import shutil import shutil
from envs.monkey_zoo.blackbox.log_handlers.log_parser import LogParser import logging
from envs.monkey_zoo.blackbox.log_handlers.logs_downloader import LogsDownloader
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' LOG_DIR_NAME = 'logs'
class TestLogsHandler(object): 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.test_name = test_name
self.island_client = island_client 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): def parse_test_logs(self):
log_paths = self.download_logs() log_paths = self.download_logs()
if not log_paths: 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 return
TestLogsHandler.parse_logs(log_paths) TestLogsHandler.parse_logs(log_paths)
def download_logs(self): def download_logs(self):
self.try_create_log_dir_for_test() 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() downloader.download_monkey_logs()
return downloader.monkey_log_paths return downloader.monkey_log_paths
@ -30,21 +33,17 @@ class TestLogsHandler(object):
try: try:
os.mkdir(self.log_dir_path) os.mkdir(self.log_dir_path)
except Exception as e: 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 @staticmethod
def get_log_dir_path(): def delete_log_folder_contents(log_dir_path):
return os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), LOG_DIR_NAME) shutil.rmtree(log_dir_path, ignore_errors=True)
os.mkdir(log_dir_path)
@staticmethod
def delete_log_folder_contents():
shutil.rmtree(TestLogsHandler.get_log_dir_path(), ignore_errors=True)
os.mkdir(TestLogsHandler.get_log_dir_path())
@staticmethod @staticmethod
def parse_logs(log_paths): def parse_logs(log_paths):
for log_path in log_paths: for log_path in log_paths:
print("Info from log at {}".format(log_path)) logging.info("Info from log at {}".format(log_path))
log_parser = LogParser(log_path) log_parser = MonkeyLogParser(log_path)
log_parser.print_errors() log_parser.print_errors()
log_parser.print_warnings() log_parser.print_warnings()

View File

@ -1,2 +1,3 @@
pytest pytest
unittest unittest
pytest-random-order

View File

@ -1,3 +1,6 @@
import os
import logging
import pytest import pytest
from time import sleep 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', 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', 'mimikatz-14', 'mimikatz-15', 'final-test-struts2-23', 'final-test-struts2-24',
'tunneling-9', 'tunneling-10', 'tunneling-11', 'weblogic-18', 'weblogic-19', 'shellshock-8'] 'tunneling-9', 'tunneling-10', 'tunneling-11', 'weblogic-18', 'weblogic-19', 'shellshock-8']
LOG_DIR_PATH = "./logs"
@pytest.fixture(autouse=True, scope='session') @pytest.fixture(autouse=True, scope='session')
@ -29,8 +33,8 @@ def GCPHandler(request):
@pytest.fixture(autouse=True, scope='session') @pytest.fixture(autouse=True, scope='session')
def delete_logs(): def delete_logs():
print("Deleting monkey logs before new tests.") logging.info("Deleting monkey logs before new tests.")
TestLogsHandler.delete_log_folder_contents() TestLogsHandler.delete_log_folder_contents(TestMonkeyBlackbox.get_log_dir_path())
def wait_machine_bootup(): 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): def run_basic_test(island_client, conf_filename, test_name, timeout_in_seconds=DEFAULT_TIMEOUT_SECONDS):
config_parser = IslandConfigParser(conf_filename) config_parser = IslandConfigParser(conf_filename)
analyzer = CommunicationAnalyzer(island_client, config_parser.get_ips_of_targets()) 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, BasicTest(test_name,
island_client, island_client,
config_parser, config_parser,
[analyzer], [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): def test_server_online(self, island_client):
assert island_client.get_api_status() is not None assert island_client.get_api_status() is not None
@ -75,7 +85,7 @@ class TestMonkeyBlackbox(object):
def test_smb_pth(self, island_client): def test_smb_pth(self, island_client):
TestMonkeyBlackbox.run_basic_test(island_client, "SMB_PTH.conf", "SMB_PTH") TestMonkeyBlackbox.run_basic_test(island_client, "SMB_PTH.conf", "SMB_PTH")
def test_elastic_exploiter(self, island_client): def test_elastic_exploiter(self, island_client):
TestMonkeyBlackbox.run_basic_test(island_client, "ELASTIC.conf", "Elastic_exploiter") TestMonkeyBlackbox.run_basic_test(island_client, "ELASTIC.conf", "Elastic_exploiter")

View File

@ -1,21 +1,25 @@
from time import sleep from time import sleep
import logging
from envs.monkey_zoo.blackbox.utils.test_timer import TestTimer from envs.monkey_zoo.blackbox.utils.test_timer import TestTimer
from envs.monkey_zoo.blackbox.log_handlers.test_logs_handler import TestLogsHandler from envs.monkey_zoo.blackbox.log_handlers.test_logs_handler import TestLogsHandler
MAX_TIME_FOR_MONKEYS_TO_DIE = 5*60 MAX_TIME_FOR_MONKEYS_TO_DIE = 5*60
WAIT_TIME_BETWEEN_REQUESTS = 10 WAIT_TIME_BETWEEN_REQUESTS = 10
TIME_FOR_MONKEY_PROCESS_TO_FINISH = 40 TIME_FOR_MONKEY_PROCESS_TO_FINISH = 40
DELAY_BETWEEN_ANALYSIS = 3
class BasicTest(object): 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.name = name
self.island_client = island_client self.island_client = island_client
self.config_parser = config_parser self.config_parser = config_parser
self.analyzers = analyzers self.analyzers = analyzers
self.timeout = timeout self.timeout = timeout
self.log_handler = log_handler
def run(self): def run(self):
self.island_client.import_config(self.config_parser.config_raw) self.island_client.import_config(self.config_parser.config_raw)
@ -31,29 +35,29 @@ class BasicTest(object):
self.island_client.reset_env() self.island_client.reset_env()
def print_test_starting_info(self): def print_test_starting_info(self):
print("Started {} test".format(self.name)) logging.info("Started {} test".format(self.name))
print("Machines participating in test:") logging.info("Machines participating in test:")
for target_ip in self.config_parser.get_ips_of_targets(): logging.info(" ".join(self.config_parser.get_ips_of_targets()))
print(" "+target_ip) logging.info("")
print("")
def test_until_timeout(self): def test_until_timeout(self):
timer = TestTimer(self.timeout) timer = TestTimer(self.timeout)
while not timer.timed_out(): while not timer.is_timed_out():
if self.all_analyzers_pass(): if self.all_analyzers_pass():
self.log_success(timer) self.log_success(timer)
return return
sleep(DELAY_BETWEEN_ANALYSIS)
self.log_failure(timer) self.log_failure(timer)
assert False assert False
def log_success(self, timer): def log_success(self, timer):
print(self.get_analyzer_logs()) logging.info(self.get_analyzer_logs())
print("{} test passed, time taken: {:.1f} seconds.".format(self.name, timer.get_time_taken())) logging.info("{} test passed, time taken: {:.1f} seconds.".format(self.name, timer.get_time_taken()))
def log_failure(self, timer): def log_failure(self, timer):
print(self.get_analyzer_logs()) logging.info(self.get_analyzer_logs())
print("{} test failed because of timeout. Time taken: {:.1f} seconds.".format(self.name, logging.error("{} test failed because of timeout. Time taken: {:.1f} seconds.".format(self.name,
timer.get_time_taken())) timer.get_time_taken()))
def all_analyzers_pass(self): def all_analyzers_pass(self):
for analyzer in self.analyzers: for analyzer in self.analyzers:
@ -73,13 +77,18 @@ class BasicTest(object):
sleep(WAIT_TIME_BETWEEN_REQUESTS) sleep(WAIT_TIME_BETWEEN_REQUESTS)
time_passed += WAIT_TIME_BETWEEN_REQUESTS time_passed += WAIT_TIME_BETWEEN_REQUESTS
if time_passed > MAX_TIME_FOR_MONKEYS_TO_DIE: 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 assert False
def parse_logs(self): def parse_logs(self):
print("\nParsing test logs:") logging.info("\nParsing test logs:")
TestLogsHandler(self.name, self.island_client).parse_test_logs() self.log_handler.parse_test_logs()
@staticmethod @staticmethod
def wait_for_monkey_process_to_finish(): 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) sleep(TIME_FOR_MONKEY_PROCESS_TO_FINISH)

View File

@ -1,5 +1,7 @@
import subprocess import subprocess
import logging
class GCPHandler(object): class GCPHandler(object):
@ -13,28 +15,28 @@ class GCPHandler(object):
try: try:
# pass the key file to gcp # pass the key file to gcp
subprocess.call(GCPHandler.get_auth_command(key_path), shell=True) subprocess.call(GCPHandler.get_auth_command(key_path), shell=True)
print("GCP Handler passed key") logging.info("GCP Handler passed key")
# set project # set project
subprocess.call(GCPHandler.get_set_project_command(project_id), shell=True) subprocess.call(GCPHandler.get_set_project_command(project_id), shell=True)
print("GCP Handler set project") logging.info("GCP Handler set project")
print("GCP Handler initialized successfully") logging.info("GCP Handler initialized successfully")
except Exception as e: 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): def start_machines(self, machine_list):
print("Setting up all GCP machines...") logging.info("Setting up all GCP machines...")
try: try:
subprocess.call((GCPHandler.MACHINE_STARTING_COMMAND % (machine_list, self.zone)), shell=True) 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: 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): def stop_machines(self, machine_list):
try: try:
subprocess.call((GCPHandler.MACHINE_STOPPING_COMMAND % (machine_list, self.zone)), shell=True) 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: 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 @staticmethod
def get_auth_command(key_path): def get_auth_command(key_path):

View File

@ -6,7 +6,7 @@ class TestTimer(object):
self.timeout_time = TestTimer.get_timeout_time(timeout) self.timeout_time = TestTimer.get_timeout_time(timeout)
self.start_time = time() self.start_time = time()
def timed_out(self): def is_timed_out(self):
return time() > self.timeout_time return time() > self.timeout_time
def get_time_taken(self): def get_time_taken(self):

View File

@ -1 +1 @@
pyinstaller -F --log-level=DEBUG --clean --upx-dir=.\bin monkey.spec C:\Programos\Python27\Scripts\pyinstaller.exe -F --log-level=DEBUG --clean --upx-dir=.\bin monkey.spec

View File

@ -90,6 +90,8 @@ class InfectionMonkey(object):
return return
self.set_default_port() self.set_default_port()
fbts
# Create a dir for monkey files if there isn't one # Create a dir for monkey files if there isn't one
utils.create_monkey_dir() utils.create_monkey_dir()

View File

@ -0,0 +1,4 @@
"""
This package contains resources used by blackbox tests
to analize test results, download logs and so on.
"""