From 18dec0c6520620c3d9bbbeaa6f204c826908ce16 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 10 Jun 2020 11:52:55 +0300 Subject: [PATCH] Password setup: Backed environment changes and unit tests --- monkey/common/utils/exceptions.py | 10 ++ monkey/infection_monkey/exploit/mssqlexec.py | 2 +- monkey/infection_monkey/exploit/sshexec.py | 2 +- .../exploit/tools/exceptions.py | 6 - monkey/infection_monkey/monkey.py | 2 +- .../monkey_island/cc/environment/__init__.py | 49 ++++++- monkey/monkey_island/cc/environment/aws.py | 3 + ...nvironment.py => environment_singleton.py} | 17 +-- .../monkey_island/cc/environment/password.py | 9 +- .../monkey_island/cc/environment/standard.py | 3 + .../cc/environment/test__init__.py | 137 ++++++++++++++++++ .../monkey_island/cc/environment/testing.py | 2 + monkey/monkey_island/cc/models/__init__.py | 2 +- .../monkey_island/cc/resources/local_run.py | 2 +- monkey/monkey_island/cc/services/config.py | 2 +- .../cc/services/reporting/aws_exporter.py | 2 +- .../cc/services/reporting/exporter_init.py | 2 +- .../cc/services/version_update.py | 2 +- .../cc/testing/IslandTestCase.py | 2 +- 19 files changed, 224 insertions(+), 32 deletions(-) create mode 100644 monkey/common/utils/exceptions.py delete mode 100644 monkey/infection_monkey/exploit/tools/exceptions.py rename monkey/monkey_island/cc/environment/{environment.py => environment_singleton.py} (84%) create mode 100644 monkey/monkey_island/cc/environment/test__init__.py diff --git a/monkey/common/utils/exceptions.py b/monkey/common/utils/exceptions.py new file mode 100644 index 000000000..6abfeaa9a --- /dev/null +++ b/monkey/common/utils/exceptions.py @@ -0,0 +1,10 @@ +class ExploitingVulnerableMachineError(Exception): + """ Raise when exploiter failed, but machine is vulnerable """ + + +class FailedExploitationError(Exception): + """ Raise when exploiter fails instead of returning False """ + + +class ServerConfigFileChanged(Exception): + """ Raise when server config file changed and island needs to restart """ diff --git a/monkey/infection_monkey/exploit/mssqlexec.py b/monkey/infection_monkey/exploit/mssqlexec.py index 36833c5bc..2efc25825 100644 --- a/monkey/infection_monkey/exploit/mssqlexec.py +++ b/monkey/infection_monkey/exploit/mssqlexec.py @@ -11,7 +11,7 @@ from infection_monkey.exploit.tools.http_tools import MonkeyHTTPServer from infection_monkey.exploit.tools.helpers import get_monkey_dest_path, build_monkey_commandline, get_monkey_depth from infection_monkey.model import DROPPER_ARG from infection_monkey.exploit.tools.payload_parsing import LimitedSizePayload -from infection_monkey.exploit.tools.exceptions import ExploitingVulnerableMachineError, FailedExploitationError +from common.utils.exceptions import ExploitingVulnerableMachineError, FailedExploitationError LOG = logging.getLogger(__name__) diff --git a/monkey/infection_monkey/exploit/sshexec.py b/monkey/infection_monkey/exploit/sshexec.py index 5e58f1f54..3966a7330 100644 --- a/monkey/infection_monkey/exploit/sshexec.py +++ b/monkey/infection_monkey/exploit/sshexec.py @@ -9,7 +9,7 @@ from infection_monkey.exploit.HostExploiter import HostExploiter from infection_monkey.exploit.tools.helpers import get_target_monkey, get_monkey_depth, build_monkey_commandline from infection_monkey.model import MONKEY_ARG from infection_monkey.network.tools import check_tcp_port, get_interface_to_target -from infection_monkey.exploit.tools.exceptions import FailedExploitationError +from common.utils.exceptions import FailedExploitationError from common.utils.exploit_enum import ExploitType from common.utils.attack_utils import ScanStatus from infection_monkey.telemetry.attack.t1105_telem import T1105Telem diff --git a/monkey/infection_monkey/exploit/tools/exceptions.py b/monkey/infection_monkey/exploit/tools/exceptions.py deleted file mode 100644 index 7d6c16366..000000000 --- a/monkey/infection_monkey/exploit/tools/exceptions.py +++ /dev/null @@ -1,6 +0,0 @@ -class ExploitingVulnerableMachineError(Exception): - """ Raise when exploiter failed, but machine is vulnerable""" - - -class FailedExploitationError(Exception): - """ Raise when exploiter fails instead of returning False""" diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index a31ea4d47..63df34e42 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -28,7 +28,7 @@ from infection_monkey.telemetry.tunnel_telem import TunnelTelem from infection_monkey.windows_upgrader import WindowsUpgrader from infection_monkey.post_breach.post_breach_handler import PostBreach from infection_monkey.network.tools import get_interface_to_target, is_running_on_server -from infection_monkey.exploit.tools.exceptions import ExploitingVulnerableMachineError, FailedExploitationError +from common.utils.exceptions import ExploitingVulnerableMachineError, FailedExploitationError from infection_monkey.telemetry.attack.t1106_telem import T1106Telem from common.utils.attack_utils import ScanStatus, UsageEnum from common.version import get_version diff --git a/monkey/monkey_island/cc/environment/__init__.py b/monkey/monkey_island/cc/environment/__init__.py index 195778e16..ec3614736 100644 --- a/monkey/monkey_island/cc/environment/__init__.py +++ b/monkey/monkey_island/cc/environment/__init__.py @@ -1,8 +1,14 @@ +import json from abc import ABCMeta, abstractmethod from datetime import timedelta import os import hashlib +from typing import Dict + +from common.utils.exceptions import ServerConfigFileChanged +from monkey_island.cc.consts import MONKEY_ISLAND_ABS_PATH + __author__ = 'itay.mizeretz' @@ -18,6 +24,45 @@ class Environment(object, metaclass=ABCMeta): _testing = False + @property + @abstractmethod + def _credentials_required(self) -> bool: + pass + + @abstractmethod + def get_auth_users(self): + return + + @staticmethod + def set_server_config(config: Dict): + Environment.upload_server_configuration_to_file(config) + raise ServerConfigFileChanged + + @staticmethod + def upload_server_configuration_to_file(config: Dict): + file_path = Environment.get_server_config_file_path() + with open(file_path, 'w') as f: + f.write(json.dumps(config, indent=2)) + + @staticmethod + def get_server_config_file_path(): + return os.path.join(MONKEY_ISLAND_ABS_PATH, 'cc/server_config.json') + + def needs_registration(self) -> bool: + if not self._credentials_required: + return False + else: + return not self._is_registered() + + def _is_registered(self) -> bool: + return self._credentials_required and self._is_credentials_set_up() + + def _is_credentials_set_up(self) -> bool: + if 'user' in self.config and 'hash' in self.config: + return True + else: + return False + @property def testing(self): return self._testing @@ -63,10 +108,6 @@ class Environment(object, metaclass=ABCMeta): val = self.config.get(key, val) return val - @abstractmethod - def get_auth_users(self): - return - @property def mongo_db_name(self): return self._MONGO_DB_NAME diff --git a/monkey/monkey_island/cc/environment/aws.py b/monkey/monkey_island/cc/environment/aws.py index 5608bddcd..d984123be 100644 --- a/monkey/monkey_island/cc/environment/aws.py +++ b/monkey/monkey_island/cc/environment/aws.py @@ -6,6 +6,9 @@ __author__ = 'itay.mizeretz' class AwsEnvironment(Environment): + + _credentials_required = True + def __init__(self): super(AwsEnvironment, self).__init__() # Not suppressing error here on purpose. This is critical if we're on AWS env. diff --git a/monkey/monkey_island/cc/environment/environment.py b/monkey/monkey_island/cc/environment/environment_singleton.py similarity index 84% rename from monkey/monkey_island/cc/environment/environment.py rename to monkey/monkey_island/cc/environment/environment_singleton.py index 868e6ec36..b4c22a765 100644 --- a/monkey/monkey_island/cc/environment/environment.py +++ b/monkey/monkey_island/cc/environment/environment_singleton.py @@ -1,14 +1,12 @@ import json import logging -import os env = None -from monkey_island.cc.environment import standard +from monkey_island.cc.environment import standard, Environment from monkey_island.cc.environment import testing from monkey_island.cc.environment import aws from monkey_island.cc.environment import password -from monkey_island.cc.consts import MONKEY_ISLAND_ABS_PATH __author__ = 'itay.mizeretz' @@ -27,17 +25,18 @@ ENV_DICT = { } -def load_server_configuration_from_file(): - with open(os.path.join(MONKEY_ISLAND_ABS_PATH, 'cc/server_config.json'), 'r') as f: - config_content = f.read() - return json.loads(config_content) - - def load_env_from_file(): loaded_config_json = load_server_configuration_from_file() return loaded_config_json['server_config'] +def load_server_configuration_from_file(): + file_path = Environment.get_server_config_file_path() + with open(file_path, 'r') as f: + config_content = f.read() + return json.loads(config_content) + + try: config_json = load_server_configuration_from_file() __env_type = config_json['server_config'] diff --git a/monkey/monkey_island/cc/environment/password.py b/monkey/monkey_island/cc/environment/password.py index 89abfea8c..ef8c4fa37 100644 --- a/monkey/monkey_island/cc/environment/password.py +++ b/monkey/monkey_island/cc/environment/password.py @@ -6,7 +6,10 @@ __author__ = 'itay.mizeretz' class PasswordEnvironment(Environment): + _credentials_required = True + def get_auth_users(self): - return [ - monkey_island.cc.auth.User(1, self.config['user'], self.config['hash']) - ] + if 'user' in self.config and 'hash' in self.config: + return [monkey_island.cc.auth.User(1, self.config['user'], self.config['hash'])] + else: + return [] diff --git a/monkey/monkey_island/cc/environment/standard.py b/monkey/monkey_island/cc/environment/standard.py index f84aeaeef..df06e0bf9 100644 --- a/monkey/monkey_island/cc/environment/standard.py +++ b/monkey/monkey_island/cc/environment/standard.py @@ -5,6 +5,9 @@ __author__ = 'itay.mizeretz' class StandardEnvironment(Environment): + + _credentials_required = False + # SHA3-512 of '1234567890!@#$%^&*()_nothing_up_my_sleeve_1234567890!@#$%^&*()' NO_AUTH_CREDS = '55e97c9dcfd22b8079189ddaeea9bce8125887e3237b800c6176c9afa80d2062' \ '8d2c8d0b1538d2208c1444ac66535b764a3d902b35e751df3faec1e477ed3557' diff --git a/monkey/monkey_island/cc/environment/test__init__.py b/monkey/monkey_island/cc/environment/test__init__.py new file mode 100644 index 000000000..03bbae864 --- /dev/null +++ b/monkey/monkey_island/cc/environment/test__init__.py @@ -0,0 +1,137 @@ +import json +import os +from unittest import TestCase +from unittest.mock import MagicMock, patch + +from monkey_island.cc.consts import MONKEY_ISLAND_ABS_PATH +from common.utils.exceptions import ServerConfigFileChanged +from monkey_island.cc.environment import Environment + + +def get_server_config_file_path_test_version(): + return os.path.join(os.getcwd(), 'test_config.json') + + +class TestEnvironment(TestCase): + + class EnvironmentNoCredentials(Environment): + _credentials_required = False + + def get_auth_users(self): + return [] + + class EnvironmentWithCredentials(Environment): + _credentials_required = True + + def get_auth_users(self): + return [] + + # Username:test Password:test + CONFIG_JSON_WITH_CREDENTIALS = { + "server_config": "password", + "deployment": "develop", + "user": "test", + "hash": "9ece086e9bac491fac5c1d1046ca11d737b92a2b2ebd93f005d7b710110c0a678288166e7fbe796883a" + "4f2e9b3ca9f484f521d0ce464345cc1aec96779149c14" + } + + CONFIG_JSON_NO_CREDENTIALS = { + "server_config": "password", + "deployment": "develop" + } + + CONFIG_JSON_PARTIAL_CREDENTIALS = { + "server_config": "password", + "deployment": "develop", + "user": "test" + } + + CONFIG_JSON_STANDARD_ENV = { + "server_config": "standard", + "deployment": "develop" + } + + CONFIG_JSON_STANDARD_WITH_CREDENTIALS = { + "server_config": "standard", + "deployment": "develop", + "user": "test", + "hash": "9ece086e9bac491fac5c1d1046ca11d737b92a2b2ebd93f005d7b710110c0a678288166e7fbe796883a" + "4f2e9b3ca9f484f521d0ce464345cc1aec96779149c14" + } + + @patch.object(target=Environment, attribute="get_server_config_file_path", + new=MagicMock(return_value=get_server_config_file_path_test_version())) + def test_upload_server_configuration_to_file(self): + Environment.upload_server_configuration_to_file(TestEnvironment.CONFIG_JSON_WITH_CREDENTIALS) + file_path = get_server_config_file_path_test_version() + with open(file_path, 'r') as f: + content_from_file = f.read() + os.remove(file_path) + + self.assertDictEqual(TestEnvironment.CONFIG_JSON_WITH_CREDENTIALS, json.loads(content_from_file)) + + def test_get_server_config_file_path(self): + server_file_path = MONKEY_ISLAND_ABS_PATH + "\cc/server_config.json" + self.assertEqual(Environment.get_server_config_file_path(), server_file_path) + + @patch.object(Environment, "upload_server_configuration_to_file", MagicMock()) + def test_set_server_config(self): + with self.assertRaises(ServerConfigFileChanged): + Environment.set_server_config(TestEnvironment.CONFIG_JSON_WITH_CREDENTIALS) + + def test_needs_registration(self): + env = TestEnvironment.EnvironmentWithCredentials() + + env.config = TestEnvironment.CONFIG_JSON_WITH_CREDENTIALS + self.assertFalse(env.needs_registration()) + + env.config = TestEnvironment.CONFIG_JSON_NO_CREDENTIALS + self.assertTrue(env.needs_registration()) + + env.config = TestEnvironment.CONFIG_JSON_PARTIAL_CREDENTIALS + self.assertTrue(env.needs_registration()) + + env = TestEnvironment.EnvironmentNoCredentials() + + env.config = TestEnvironment.CONFIG_JSON_STANDARD_ENV + self.assertFalse(env.needs_registration()) + + env.config = TestEnvironment.CONFIG_JSON_STANDARD_WITH_CREDENTIALS + self.assertFalse(env.needs_registration()) + + def test_is_registered(self): + env = TestEnvironment.EnvironmentWithCredentials() + + env.config = TestEnvironment.CONFIG_JSON_WITH_CREDENTIALS + self.assertTrue(env._is_registered()) + + env.config = TestEnvironment.CONFIG_JSON_NO_CREDENTIALS + self.assertFalse(env._is_registered()) + + env.config = TestEnvironment.CONFIG_JSON_PARTIAL_CREDENTIALS + self.assertFalse(env._is_registered()) + + env = TestEnvironment.EnvironmentNoCredentials() + + env.config = TestEnvironment.CONFIG_JSON_STANDARD_ENV + self.assertFalse(env._is_registered()) + + env.config = TestEnvironment.CONFIG_JSON_STANDARD_WITH_CREDENTIALS + self.assertFalse(env._is_registered()) + + def test_is_credentials_set_up(self): + env = TestEnvironment.EnvironmentWithCredentials() + + env.config = TestEnvironment.CONFIG_JSON_NO_CREDENTIALS + self.assertFalse(env._is_credentials_set_up()) + + env.config = TestEnvironment.CONFIG_JSON_WITH_CREDENTIALS + self.assertTrue(env._is_credentials_set_up()) + + env.config = TestEnvironment.CONFIG_JSON_PARTIAL_CREDENTIALS + self.assertFalse(env._is_credentials_set_up()) + + env = TestEnvironment.EnvironmentNoCredentials() + + env.config = TestEnvironment.CONFIG_JSON_STANDARD_ENV + self.assertFalse(env._is_credentials_set_up()) diff --git a/monkey/monkey_island/cc/environment/testing.py b/monkey/monkey_island/cc/environment/testing.py index e504a8357..a86ec6353 100644 --- a/monkey/monkey_island/cc/environment/testing.py +++ b/monkey/monkey_island/cc/environment/testing.py @@ -7,6 +7,8 @@ class TestingEnvironment(Environment): This will cause all mongo connections to happen via `mongomock` instead of using an actual mongodb instance. """ + _credentials_required = True + def __init__(self): super(TestingEnvironment, self).__init__() self.testing = True diff --git a/monkey/monkey_island/cc/models/__init__.py b/monkey/monkey_island/cc/models/__init__.py index 0a83c00b9..1af3a8ad4 100644 --- a/monkey/monkey_island/cc/models/__init__.py +++ b/monkey/monkey_island/cc/models/__init__.py @@ -1,6 +1,6 @@ from mongoengine import connect -from monkey_island.cc.environment.environment import env +from monkey_island.cc.environment.environment_singleton import env # This section sets up the DB connection according to the environment. # If testing, use mongomock which only emulates mongo. for more information, see diff --git a/monkey/monkey_island/cc/resources/local_run.py b/monkey/monkey_island/cc/resources/local_run.py index d63b21ffb..773b49eca 100644 --- a/monkey/monkey_island/cc/resources/local_run.py +++ b/monkey/monkey_island/cc/resources/local_run.py @@ -6,7 +6,7 @@ import sys from flask import request, jsonify, make_response import flask_restful -from monkey_island.cc.environment.environment import env +from monkey_island.cc.environment.environment_singleton import env from monkey_island.cc.models import Monkey from monkey_island.cc.resources.monkey_download import get_monkey_executable from monkey_island.cc.services.node import NodeService diff --git a/monkey/monkey_island/cc/services/config.py b/monkey/monkey_island/cc/services/config.py index 6aef36174..4aa2c909f 100644 --- a/monkey/monkey_island/cc/services/config.py +++ b/monkey/monkey_island/cc/services/config.py @@ -6,7 +6,7 @@ from jsonschema import Draft4Validator, validators import monkey_island.cc.services.post_breach_files from monkey_island.cc.database import mongo -from monkey_island.cc.environment.environment import env +from monkey_island.cc.environment.environment_singleton import env from monkey_island.cc.network_utils import local_ip_addresses from .config_schema import SCHEMA from monkey_island.cc.encryptor import encryptor diff --git a/monkey/monkey_island/cc/services/reporting/aws_exporter.py b/monkey/monkey_island/cc/services/reporting/aws_exporter.py index 86486b9ba..460a1fd72 100644 --- a/monkey/monkey_island/cc/services/reporting/aws_exporter.py +++ b/monkey/monkey_island/cc/services/reporting/aws_exporter.py @@ -6,7 +6,7 @@ import boto3 from botocore.exceptions import UnknownServiceError from common.cloud.aws.aws_instance import AwsInstance -from monkey_island.cc.environment.environment import load_server_configuration_from_file +from monkey_island.cc.environment.environment_singleton import load_server_configuration_from_file from monkey_island.cc.services.reporting.exporter import Exporter __authors__ = ['maor.rayzin', 'shay.nehmad'] diff --git a/monkey/monkey_island/cc/services/reporting/exporter_init.py b/monkey/monkey_island/cc/services/reporting/exporter_init.py index 903af1628..6a3411bf8 100644 --- a/monkey/monkey_island/cc/services/reporting/exporter_init.py +++ b/monkey/monkey_island/cc/services/reporting/exporter_init.py @@ -3,7 +3,7 @@ import logging from monkey_island.cc.services.reporting.report_exporter_manager import ReportExporterManager from monkey_island.cc.services.reporting.aws_exporter import AWSExporter from monkey_island.cc.services.remote_run_aws import RemoteRunAwsService -from monkey_island.cc.environment.environment import env +from monkey_island.cc.environment.environment_singleton import env logger = logging.getLogger(__name__) diff --git a/monkey/monkey_island/cc/services/version_update.py b/monkey/monkey_island/cc/services/version_update.py index ddd60d5c0..5ec4419a0 100644 --- a/monkey/monkey_island/cc/services/version_update.py +++ b/monkey/monkey_island/cc/services/version_update.py @@ -3,7 +3,7 @@ import logging import requests from common.version import get_version -from monkey_island.cc.environment.environment import env +from monkey_island.cc.environment.environment_singleton import env __author__ = "itay.mizeretz" diff --git a/monkey/monkey_island/cc/testing/IslandTestCase.py b/monkey/monkey_island/cc/testing/IslandTestCase.py index 35e568399..998155b2d 100644 --- a/monkey/monkey_island/cc/testing/IslandTestCase.py +++ b/monkey/monkey_island/cc/testing/IslandTestCase.py @@ -1,5 +1,5 @@ import unittest -from monkey_island.cc.environment.environment import env +from monkey_island.cc.environment.environment_singleton import env from monkey_island.cc.models import Monkey from monkey_island.cc.models.zero_trust.finding import Finding