From c8cf7d52a4653f0bb0c11eaac4a98138c6e0f3c3 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Thu, 11 Jun 2020 18:20:32 +0300 Subject: [PATCH] Backend refactoring: server environment and authentication --- monkey/common/utils/exceptions.py | 2 +- monkey/monkey_island/cc/app.py | 4 +- monkey/monkey_island/cc/auth.py | 12 +- monkey/monkey_island/cc/auth_user.py | 11 ++ .../monkey_island/cc/environment/__init__.py | 67 ++++----- monkey/monkey_island/cc/environment/aws.py | 4 +- .../cc/environment/environment_config.py | 66 +++++++++ .../cc/environment/environment_singleton.py | 21 +-- .../monkey_island/cc/environment/password.py | 4 +- .../monkey_island/cc/environment/standard.py | 6 +- .../cc/environment/test__init__.py | 103 ++++--------- .../monkey_island/cc/environment/test_aws.py | 2 +- .../cc/environment/test_environment_config.py | 139 ++++++++++++++++++ .../cc/environment/test_user_creds.py | 38 +++++ .../cc/environment/user_creds.py | 41 ++++++ monkey/monkey_island/cc/main.py | 2 +- .../cc/resources/registration.py | 20 +++ .../cc/services/reporting/aws_exporter.py | 4 +- 18 files changed, 394 insertions(+), 152 deletions(-) create mode 100644 monkey/monkey_island/cc/auth_user.py create mode 100644 monkey/monkey_island/cc/environment/environment_config.py create mode 100644 monkey/monkey_island/cc/environment/test_environment_config.py create mode 100644 monkey/monkey_island/cc/environment/test_user_creds.py create mode 100644 monkey/monkey_island/cc/environment/user_creds.py create mode 100644 monkey/monkey_island/cc/resources/registration.py diff --git a/monkey/common/utils/exceptions.py b/monkey/common/utils/exceptions.py index 6abfeaa9a..5be984dfc 100644 --- a/monkey/common/utils/exceptions.py +++ b/monkey/common/utils/exceptions.py @@ -6,5 +6,5 @@ class FailedExploitationError(Exception): """ Raise when exploiter fails instead of returning False """ -class ServerConfigFileChanged(Exception): +class InvalidRegistrationCredentials(Exception): """ Raise when server config file changed and island needs to restart """ diff --git a/monkey/monkey_island/cc/app.py b/monkey/monkey_island/cc/app.py index 505220a78..4b887d94b 100644 --- a/monkey/monkey_island/cc/app.py +++ b/monkey/monkey_island/cc/app.py @@ -7,7 +7,7 @@ from werkzeug.exceptions import NotFound from monkey_island.cc.auth import init_jwt from monkey_island.cc.database import mongo, database -from monkey_island.cc.environment.environment import env +from monkey_island.cc.environment.environment_singleton import env from monkey_island.cc.resources.client_run import ClientRun from monkey_island.cc.resources.edge import Edge from monkey_island.cc.resources.local_run import LocalRun @@ -22,6 +22,7 @@ from monkey_island.cc.resources.netmap import NetMap from monkey_island.cc.resources.node import Node from monkey_island.cc.resources.node_states import NodeStates from monkey_island.cc.resources.monkey_control.remote_port_check import RemotePortCheck +from monkey_island.cc.resources.registration import Registration from monkey_island.cc.resources.remote_run import RemoteRun from monkey_island.cc.resources.reporting.report import Report from monkey_island.cc.resources.root import Root @@ -91,6 +92,7 @@ def init_app_url_rules(app): def init_api_resources(api): api.add_resource(Root, '/api') + api.add_resource(Registration, '/api/registration') api.add_resource(Monkey, '/api/monkey', '/api/monkey/', '/api/monkey/') api.add_resource(Bootloader, '/api/bootloader/') api.add_resource(LocalRun, '/api/local-monkey', '/api/local-monkey/') diff --git a/monkey/monkey_island/cc/auth.py b/monkey/monkey_island/cc/auth.py index 7f15cb45e..0bbb385b0 100644 --- a/monkey/monkey_island/cc/auth.py +++ b/monkey/monkey_island/cc/auth.py @@ -4,21 +4,11 @@ from flask import current_app, abort from flask_jwt import JWT, _jwt_required, JWTError from werkzeug.security import safe_str_cmp -from monkey_island.cc.environment.environment import env +from monkey_island.cc.environment.environment_singleton import env __author__ = 'itay.mizeretz' -class User(object): - def __init__(self, user_id, username, secret): - self.id = user_id - self.username = username - self.secret = secret - - def __str__(self): - return "User(id='%s')" % self.id - - def init_jwt(app): users = env.get_auth_users() username_table = {u.username: u for u in users} diff --git a/monkey/monkey_island/cc/auth_user.py b/monkey/monkey_island/cc/auth_user.py new file mode 100644 index 000000000..d75c751ea --- /dev/null +++ b/monkey/monkey_island/cc/auth_user.py @@ -0,0 +1,11 @@ +__author__ = 'itay.mizeretz' + + +class User(object): + def __init__(self, user_id, username, secret): + self.id = user_id + self.username = username + self.secret = secret + + def __str__(self): + return "User(id='%s')" % self.id diff --git a/monkey/monkey_island/cc/environment/__init__.py b/monkey/monkey_island/cc/environment/__init__.py index ec3614736..62a702d73 100644 --- a/monkey/monkey_island/cc/environment/__init__.py +++ b/monkey/monkey_island/cc/environment/__init__.py @@ -1,15 +1,15 @@ -import json +import hashlib +import os from abc import ABCMeta, abstractmethod from datetime import timedelta -import os -import hashlib + +__author__ = 'itay.mizeretz' from typing import Dict -from common.utils.exceptions import ServerConfigFileChanged -from monkey_island.cc.consts import MONKEY_ISLAND_ABS_PATH - -__author__ = 'itay.mizeretz' +from common.utils.exceptions import InvalidRegistrationCredentials +from monkey_island.cc.environment.environment_config import EnvironmentConfig +from monkey_island.cc.environment.user_creds import UserCreds class Environment(object, metaclass=ABCMeta): @@ -24,6 +24,10 @@ class Environment(object, metaclass=ABCMeta): _testing = False + def __init__(self): + self._config = None + self._testing = False # Assume env is not for unit testing. + @property @abstractmethod def _credentials_required(self) -> bool: @@ -31,22 +35,17 @@ class Environment(object, metaclass=ABCMeta): @abstractmethod def get_auth_users(self): - return + pass - @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 try_add_user(self, credentials: UserCreds): + if self._credentials_required: + if credentials: + self._config.add_user(credentials) + else: + raise InvalidRegistrationCredentials("Missing part of credentials.") + else: + raise InvalidRegistrationCredentials("Can't add user because credentials are not required " + "for current environment.") def needs_registration(self) -> bool: if not self._credentials_required: @@ -58,7 +57,7 @@ class Environment(object, metaclass=ABCMeta): 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: + if self._config and self._config.user_creds: return True else: return False @@ -71,12 +70,8 @@ class Environment(object, metaclass=ABCMeta): def testing(self, value): self._testing = value - def __init__(self): - self.config = None - self._testing = False # Assume env is not for unit testing. - - def set_config(self, config): - self.config = config + def set_config(self, config: EnvironmentConfig): + self._config = config def get_island_port(self): return self._ISLAND_PORT @@ -97,16 +92,10 @@ class Environment(object, metaclass=ABCMeta): return hash_obj.hexdigest() def get_deployment(self): - return self._get_from_config('deployment', 'unknown') - - def is_develop(self): - return self.get_deployment() == 'develop' - - def _get_from_config(self, key, default_value=None): - val = default_value - if self.config is not None: - val = self.config.get(key, val) - return val + deployment = 'unknown' + if self._config and self._config.deployment: + deployment = self._config.deployment + return deployment @property def mongo_db_name(self): diff --git a/monkey/monkey_island/cc/environment/aws.py b/monkey/monkey_island/cc/environment/aws.py index d984123be..96b74556d 100644 --- a/monkey/monkey_island/cc/environment/aws.py +++ b/monkey/monkey_island/cc/environment/aws.py @@ -1,4 +1,5 @@ import monkey_island.cc.auth +from monkey_island.cc.auth_user import User from monkey_island.cc.environment import Environment from common.cloud.aws.aws_instance import AwsInstance @@ -24,5 +25,6 @@ class AwsEnvironment(Environment): def get_auth_users(self): return [ - monkey_island.cc.auth.User(1, 'monkey', self.hash_secret(self._instance_id)) + # TODO change this to propper registration? + User(1, 'monkey', self.hash_secret(self._instance_id)) ] diff --git a/monkey/monkey_island/cc/environment/environment_config.py b/monkey/monkey_island/cc/environment/environment_config.py new file mode 100644 index 000000000..20d70ef9e --- /dev/null +++ b/monkey/monkey_island/cc/environment/environment_config.py @@ -0,0 +1,66 @@ +from __future__ import annotations + +import json +import os +from typing import List, Dict + +from monkey_island.cc.auth_user import User +from monkey_island.cc.consts import MONKEY_ISLAND_ABS_PATH +from monkey_island.cc.environment.user_creds import UserCreds + + +class EnvironmentConfig: + def __init__(self, + server_config: str, + deployment: str, + user_creds: UserCreds, + aws=None): + self.server_config = server_config + self.deployment = deployment + self.user_creds = user_creds + self.aws = aws + + @staticmethod + def get_from_json(config_json: str) -> EnvironmentConfig: + data = json.loads(config_json) + return EnvironmentConfig.get_from_dict(data) + + @staticmethod + def get_from_dict(dict_data: Dict) -> EnvironmentConfig: + user_creds = UserCreds.get_from_dict(dict_data) + aws = dict_data['aws'] if 'aws' in dict_data else None + return EnvironmentConfig(server_config=dict_data['server_config'], + deployment=dict_data['deployment'], + user_creds=user_creds, + aws=aws) + + def save_to_file(self): + file_path = EnvironmentConfig.get_config_file_path() + with open(file_path, 'w') as f: + f.write(json.dumps(self.to_dict(), indent=2)) + + @staticmethod + def get_from_file() -> EnvironmentConfig: + file_path = EnvironmentConfig.get_config_file_path() + with open(file_path, 'r') as f: + config_content = f.read() + return EnvironmentConfig.get_from_json(config_content) + + @staticmethod + def get_config_file_path() -> str: + return os.path.join(MONKEY_ISLAND_ABS_PATH, 'cc/server_config.json') + + def to_dict(self) -> Dict: + config_dict = {'server_config': self.server_config, + 'deployment': self.deployment} + if self.aws: + config_dict.update({'aws': self.aws}) + config_dict.update(self.user_creds.to_dict()) + return config_dict + + def add_user(self, credentials: UserCreds): + self.user_creds = credentials + + def get_users(self) -> List[User]: + auth_user = self.user_creds.to_auth_user() + return [auth_user] if auth_user else [] diff --git a/monkey/monkey_island/cc/environment/environment_singleton.py b/monkey/monkey_island/cc/environment/environment_singleton.py index b4c22a765..2b3159248 100644 --- a/monkey/monkey_island/cc/environment/environment_singleton.py +++ b/monkey/monkey_island/cc/environment/environment_singleton.py @@ -3,7 +3,7 @@ import logging env = None -from monkey_island.cc.environment import standard, Environment +from monkey_island.cc.environment import standard, EnvironmentConfig from monkey_island.cc.environment import testing from monkey_island.cc.environment import aws from monkey_island.cc.environment import password @@ -24,24 +24,11 @@ ENV_DICT = { TESTING: testing.TestingEnvironment } - -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'] + config = EnvironmentConfig.get_from_file() + __env_type = config.server_config env = ENV_DICT[__env_type]() - env.set_config(config_json) + env.set_config(config) logger.info('Monkey\'s env is: {0}'.format(env.__class__.__name__)) except Exception: logger.error('Failed initializing environment', exc_info=True) diff --git a/monkey/monkey_island/cc/environment/password.py b/monkey/monkey_island/cc/environment/password.py index ef8c4fa37..e181feb7c 100644 --- a/monkey/monkey_island/cc/environment/password.py +++ b/monkey/monkey_island/cc/environment/password.py @@ -9,7 +9,7 @@ class PasswordEnvironment(Environment): _credentials_required = True def get_auth_users(self): - if 'user' in self.config and 'hash' in self.config: - return [monkey_island.cc.auth.User(1, self.config['user'], self.config['hash'])] + if self._is_registered(): + return self._config.get_users else: return [] diff --git a/monkey/monkey_island/cc/environment/standard.py b/monkey/monkey_island/cc/environment/standard.py index df06e0bf9..8f18824db 100644 --- a/monkey/monkey_island/cc/environment/standard.py +++ b/monkey/monkey_island/cc/environment/standard.py @@ -1,4 +1,4 @@ -import monkey_island.cc.auth +from monkey_island.cc.auth_user import User from monkey_island.cc.environment import Environment __author__ = 'itay.mizeretz' @@ -13,6 +13,4 @@ class StandardEnvironment(Environment): '8d2c8d0b1538d2208c1444ac66535b764a3d902b35e751df3faec1e477ed3557' def get_auth_users(self): - return [ - monkey_island.cc.auth.User(1, StandardEnvironment.NO_AUTH_CREDS, StandardEnvironment.NO_AUTH_CREDS) - ] + return [User(1, StandardEnvironment.NO_AUTH_CREDS, StandardEnvironment.NO_AUTH_CREDS)] diff --git a/monkey/monkey_island/cc/environment/test__init__.py b/monkey/monkey_island/cc/environment/test__init__.py index 03bbae864..ddca7e535 100644 --- a/monkey/monkey_island/cc/environment/test__init__.py +++ b/monkey/monkey_island/cc/environment/test__init__.py @@ -1,11 +1,9 @@ import json import os +from typing import Dict 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 +from monkey_island.cc.environment import Environment, EnvironmentConfig def get_server_config_file_path_test_version(): @@ -27,111 +25,72 @@ class TestEnvironment(TestCase): return [] # Username:test Password:test - CONFIG_JSON_WITH_CREDENTIALS = { + CONFIG_WITH_CREDENTIALS = { "server_config": "password", "deployment": "develop", "user": "test", - "hash": "9ece086e9bac491fac5c1d1046ca11d737b92a2b2ebd93f005d7b710110c0a678288166e7fbe796883a" + "password_hash": "9ece086e9bac491fac5c1d1046ca11d737b92a2b2ebd93f005d7b710110c0a678288166e7fbe796883a" "4f2e9b3ca9f484f521d0ce464345cc1aec96779149c14" } - CONFIG_JSON_NO_CREDENTIALS = { + CONFIG_NO_CREDENTIALS = { "server_config": "password", "deployment": "develop" } - CONFIG_JSON_PARTIAL_CREDENTIALS = { + CONFIG_PARTIAL_CREDENTIALS = { "server_config": "password", "deployment": "develop", "user": "test" } - CONFIG_JSON_STANDARD_ENV = { + CONFIG_STANDARD_ENV = { "server_config": "standard", "deployment": "develop" } - CONFIG_JSON_STANDARD_WITH_CREDENTIALS = { + CONFIG_STANDARD_WITH_CREDENTIALS = { "server_config": "standard", "deployment": "develop", "user": "test", - "hash": "9ece086e9bac491fac5c1d1046ca11d737b92a2b2ebd93f005d7b710110c0a678288166e7fbe796883a" + "password_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()) + self._test_bool_env_method("needs_registration", env, TestEnvironment.CONFIG_WITH_CREDENTIALS, False) + self._test_bool_env_method("needs_registration", env, TestEnvironment.CONFIG_NO_CREDENTIALS, True) + self._test_bool_env_method("needs_registration", env, TestEnvironment.CONFIG_PARTIAL_CREDENTIALS, True) 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()) + self._test_bool_env_method("needs_registration", env, TestEnvironment.CONFIG_STANDARD_ENV, False) + self._test_bool_env_method("needs_registration", env, TestEnvironment.CONFIG_STANDARD_WITH_CREDENTIALS, False) 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()) + self._test_bool_env_method("_is_registered", env, TestEnvironment.CONFIG_WITH_CREDENTIALS, True) + self._test_bool_env_method("_is_registered", env, TestEnvironment.CONFIG_NO_CREDENTIALS, False) + self._test_bool_env_method("_is_registered", env, TestEnvironment.CONFIG_PARTIAL_CREDENTIALS, False) 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()) + self._test_bool_env_method("_is_registered", env, TestEnvironment.CONFIG_STANDARD_ENV, False) + self._test_bool_env_method("_is_registered", env, TestEnvironment.CONFIG_STANDARD_WITH_CREDENTIALS, False) 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()) + self._test_bool_env_method("_is_credentials_set_up", env, TestEnvironment.CONFIG_NO_CREDENTIALS, False) + self._test_bool_env_method("_is_credentials_set_up", env, TestEnvironment.CONFIG_WITH_CREDENTIALS, True) + self._test_bool_env_method("_is_credentials_set_up", env, TestEnvironment.CONFIG_PARTIAL_CREDENTIALS, False) env = TestEnvironment.EnvironmentNoCredentials() + self._test_bool_env_method("_is_credentials_set_up", env, TestEnvironment.CONFIG_STANDARD_ENV, False) + + def _test_bool_env_method(self, method_name: str, env: Environment, config: Dict, expected_result: bool): + env._config = EnvironmentConfig.get_from_json(json.dumps(config)) + method = getattr(env, method_name) + if expected_result: + self.assertTrue(method()) + else: + self.assertFalse(method()) - env.config = TestEnvironment.CONFIG_JSON_STANDARD_ENV - self.assertFalse(env._is_credentials_set_up()) diff --git a/monkey/monkey_island/cc/environment/test_aws.py b/monkey/monkey_island/cc/environment/test_aws.py index 1a52fb171..5cd37501a 100644 --- a/monkey/monkey_island/cc/environment/test_aws.py +++ b/monkey/monkey_island/cc/environment/test_aws.py @@ -1,4 +1,4 @@ -from monkey_island.cc.auth import User +from monkey_island.cc.auth_user import User from monkey_island.cc.testing.IslandTestCase import IslandTestCase from monkey_island.cc.environment.aws import AwsEnvironment diff --git a/monkey/monkey_island/cc/environment/test_environment_config.py b/monkey/monkey_island/cc/environment/test_environment_config.py new file mode 100644 index 000000000..2def7c4d3 --- /dev/null +++ b/monkey/monkey_island/cc/environment/test_environment_config.py @@ -0,0 +1,139 @@ +import json +import os +import platform +from typing import Dict +from unittest import TestCase +from unittest.mock import patch, MagicMock + +from monkey_island.cc.consts import MONKEY_ISLAND_ABS_PATH +from monkey_island.cc.environment.environment_config import EnvironmentConfig +from monkey_island.cc.environment.user_creds import UserCreds + + +def get_server_config_file_path_test_version(): + return os.path.join(os.getcwd(), 'test_config.json') + + +class TestEnvironmentConfig(TestCase): + # Username:test Password:test + CONFIG_WITH_CREDENTIALS = { + "server_config": "password", + "deployment": "develop", + "user": "test", + "password_hash": "9ece086e9bac491fac5c1d1046ca11d737b92a2b2ebd93f005d7b710110c0a678288166e7fbe796883a" + "4f2e9b3ca9f484f521d0ce464345cc1aec96779149c14" + } + + CONFIG_NO_CREDENTIALS = { + "server_config": "password", + "deployment": "develop" + } + + CONFIG_PARTIAL_CREDENTIALS = { + "server_config": "password", + "deployment": "develop", + "user": "test" + } + + CONFIG_BOGUS_VALUES = { + "server_config": "password", + "deployment": "develop", + "user": "test", + "aws": "test", + "test": "test", + "test2": "test2" + } + + CONFIG_STANDARD_ENV = { + "server_config": "standard", + "deployment": "develop" + } + + CONFIG_STANDARD_WITH_CREDENTIALS = { + "server_config": "standard", + "deployment": "develop", + "user": "test", + "password_hash": "9ece086e9bac491fac5c1d1046ca11d737b92a2b2ebd93f005d7b710110c0a678288166e7fbe796883a" + "4f2e9b3ca9f484f521d0ce464345cc1aec96779149c14" + } + + def test_get_from_json(self): + self._test_get_from_json(TestEnvironmentConfig.CONFIG_WITH_CREDENTIALS) + self._test_get_from_json(TestEnvironmentConfig.CONFIG_NO_CREDENTIALS) + self._test_get_from_json(TestEnvironmentConfig.CONFIG_PARTIAL_CREDENTIALS) + + def _test_get_from_json(self, config: Dict): + config_json = json.dumps(config) + env_config_object = EnvironmentConfig.get_from_json(config_json) + self.assertEqual(config['server_config'], env_config_object.server_config) + self.assertEqual(config['deployment'], env_config_object.deployment) + if 'user' in config: + self.assertEqual(config['user'], env_config_object.user_creds.username) + if 'password_hash' in config: + self.assertEqual(config['password_hash'], env_config_object.user_creds.password_hash) + if 'aws' in config: + self.assertEqual(config['aws'], env_config_object.aws) + + def test_save_to_file(self): + self._test_save_to_file(TestEnvironmentConfig.CONFIG_WITH_CREDENTIALS) + self._test_save_to_file(TestEnvironmentConfig.CONFIG_NO_CREDENTIALS) + self._test_save_to_file(TestEnvironmentConfig.CONFIG_PARTIAL_CREDENTIALS) + + @patch.object(target=EnvironmentConfig, attribute="get_config_file_path", + new=MagicMock(return_value=get_server_config_file_path_test_version())) + def _test_save_to_file(self, config: Dict): + user_creds = UserCreds.get_from_dict(config) + env_config = EnvironmentConfig(server_config=config['server_config'], + deployment=config['deployment'], + user_creds=user_creds) + + env_config.save_to_file() + 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(config, json.loads(content_from_file)) + + def test_get_server_config_file_path(self): + if platform.system() == "Windows": + server_file_path = MONKEY_ISLAND_ABS_PATH + "\cc/server_config.json" + else: + server_file_path = MONKEY_ISLAND_ABS_PATH + "/cc/server_config.json" + self.assertEqual(EnvironmentConfig.get_config_file_path(), server_file_path) + + def test_get_from_dict(self): + config_dict = TestEnvironmentConfig.CONFIG_WITH_CREDENTIALS + env_conf = EnvironmentConfig.get_from_dict(config_dict) + self.assertEqual(env_conf.server_config, config_dict['server_config']) + self.assertEqual(env_conf.deployment, config_dict['deployment']) + self.assertEqual(env_conf.user_creds.username, config_dict['user']) + self.assertEqual(env_conf.aws, None) + + config_dict = TestEnvironmentConfig.CONFIG_BOGUS_VALUES + env_conf = EnvironmentConfig.get_from_dict(config_dict) + self.assertEqual(env_conf.server_config, config_dict['server_config']) + self.assertEqual(env_conf.deployment, config_dict['deployment']) + self.assertEqual(env_conf.user_creds.username, config_dict['user']) + self.assertEqual(env_conf.aws, config_dict['aws']) + + def test_to_dict(self): + conf_json1 = json.dumps(TestEnvironmentConfig.CONFIG_WITH_CREDENTIALS) + self._test_to_dict(EnvironmentConfig.get_from_json(conf_json1)) + + conf_json2 = json.dumps(TestEnvironmentConfig.CONFIG_NO_CREDENTIALS) + self._test_to_dict(EnvironmentConfig.get_from_json(conf_json2)) + + conf_json3 = json.dumps(TestEnvironmentConfig.CONFIG_PARTIAL_CREDENTIALS) + self._test_to_dict(EnvironmentConfig.get_from_json(conf_json3)) + + def _test_to_dict(self, env_config_object: EnvironmentConfig): + test_dict = {'server_config': env_config_object.server_config, + 'deployment': env_config_object.deployment} + user_creds = env_config_object.user_creds + if user_creds.username: + test_dict.update({'user': user_creds.username}) + if user_creds.password_hash: + test_dict.update({'password_hash': user_creds.password_hash}) + + self.assertDictEqual(test_dict, env_config_object.to_dict()) diff --git a/monkey/monkey_island/cc/environment/test_user_creds.py b/monkey/monkey_island/cc/environment/test_user_creds.py new file mode 100644 index 000000000..18c052526 --- /dev/null +++ b/monkey/monkey_island/cc/environment/test_user_creds.py @@ -0,0 +1,38 @@ +from unittest import TestCase + +from monkey_island.cc.environment.user_creds import UserCreds + + +class TestUserCreds(TestCase): + + def test_to_dict(self): + user_creds = UserCreds() + self.assertDictEqual(user_creds.to_dict(), {}) + + user_creds = UserCreds(username="Test") + self.assertDictEqual(user_creds.to_dict(), {'user': "Test"}) + + user_creds = UserCreds(password_hash="abc1231234") + self.assertDictEqual(user_creds.to_dict(), {'password_hash': "abc1231234"}) + + user_creds = UserCreds(username="Test", password_hash="abc1231234") + self.assertDictEqual(user_creds.to_dict(), {'user': "Test", 'password_hash': "abc1231234"}) + + def test_to_auth_user(self): + user_creds = UserCreds(username="Test", password_hash="abc1231234") + auth_user = user_creds.to_auth_user() + self.assertEqual(auth_user.id, 1) + self.assertEqual(auth_user.username, "Test") + self.assertEqual(auth_user.secret, "abc1231234") + + user_creds = UserCreds(username="Test") + auth_user = user_creds.to_auth_user() + self.assertEqual(auth_user.id, 1) + self.assertEqual(auth_user.username, "Test") + self.assertEqual(auth_user.secret, "") + + user_creds = UserCreds(password_hash="abc1231234") + auth_user = user_creds.to_auth_user() + self.assertEqual(auth_user.id, 1) + self.assertEqual(auth_user.username, "") + self.assertEqual(auth_user.secret, "abc1231234") diff --git a/monkey/monkey_island/cc/environment/user_creds.py b/monkey/monkey_island/cc/environment/user_creds.py new file mode 100644 index 000000000..d60c61720 --- /dev/null +++ b/monkey/monkey_island/cc/environment/user_creds.py @@ -0,0 +1,41 @@ +from __future__ import annotations + +import json +from typing import Dict + +from monkey_island.cc.auth_user import User + + +class UserCreds: + + def __init__(self, username="", password_hash=""): + self.username = username + self.password_hash = password_hash + + def __bool__(self) -> bool: + return bool(self.username and self.password_hash) + + def to_dict(self) -> Dict: + cred_dict = {} + if self.username: + cred_dict.update({'user': self.username}) + if self.password_hash: + cred_dict.update({'password_hash': self.password_hash}) + return cred_dict + + def to_auth_user(self) -> User: + return User(1, self.username, self.password_hash) + + @staticmethod + def get_from_dict(data_dict: Dict) -> UserCreds: + creds = UserCreds() + if 'user' in data_dict: + creds.username = data_dict['user'] + if 'password_hash' in data_dict: + creds.password_hash = data_dict['password_hash'] + return creds + + @staticmethod + def get_from_json(json_data: bytes) -> UserCreds: + cred_dict = json.loads(json_data) + return UserCreds.get_from_dict(cred_dict) diff --git a/monkey/monkey_island/cc/main.py b/monkey/monkey_island/cc/main.py index 0bc52e54f..0b19789e1 100644 --- a/monkey/monkey_island/cc/main.py +++ b/monkey/monkey_island/cc/main.py @@ -23,7 +23,7 @@ logger = logging.getLogger(__name__) from monkey_island.cc.app import init_app from monkey_island.cc.services.reporting.exporter_init import populate_exporter_list from monkey_island.cc.network_utils import local_ip_addresses -from monkey_island.cc.environment.environment import env +from monkey_island.cc.environment.environment_singleton import env from monkey_island.cc.database import is_db_server_up, get_db_version from monkey_island.cc.resources.monkey_download import MonkeyDownload from common.version import get_version diff --git a/monkey/monkey_island/cc/resources/registration.py b/monkey/monkey_island/cc/resources/registration.py new file mode 100644 index 000000000..0e55b697a --- /dev/null +++ b/monkey/monkey_island/cc/resources/registration.py @@ -0,0 +1,20 @@ +import flask_restful +from flask import request, make_response + +from common.utils.exceptions import InvalidRegistrationCredentials +from monkey_island.cc.environment.environment_singleton import env +from monkey_island.cc.environment.user_creds import UserCreds + + +class Registration(flask_restful.Resource): + def get(self): + return {'needs_registration': env.needs_registration()} + + def post(self): + credentials = UserCreds.get_from_json(request.data) + try: + env.try_add_user(credentials) + return make_response({"error": ""}, 300) + except InvalidRegistrationCredentials as e: + return make_response({"error": e}, 400) + diff --git a/monkey/monkey_island/cc/services/reporting/aws_exporter.py b/monkey/monkey_island/cc/services/reporting/aws_exporter.py index 460a1fd72..de8950042 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_singleton import load_server_configuration_from_file +from monkey_island.cc.environment import EnvironmentConfig from monkey_island.cc.services.reporting.exporter import Exporter __authors__ = ['maor.rayzin', 'shay.nehmad'] @@ -68,7 +68,7 @@ class AWSExporter(Exporter): # azure and conficker are not relevant issues for an AWS env } - configured_product_arn = load_server_configuration_from_file()['aws'].get('sec_hub_product_arn', '') + configured_product_arn = EnvironmentConfig.get_from_file().aws.get('sec_hub_product_arn', '') product_arn = 'arn:aws:securityhub:{region}:{arn}'.format(region=region, arn=configured_product_arn) instance_arn = 'arn:aws:ec2:' + str(region) + ':instance:{instance_id}' # Not suppressing error here on purpose.