diff --git a/monkey/monkey_island/cc/resources/island_mode.py b/monkey/monkey_island/cc/resources/island_mode.py index 7698fca9d..6df681fd7 100644 --- a/monkey/monkey_island/cc/resources/island_mode.py +++ b/monkey/monkey_island/cc/resources/island_mode.py @@ -5,10 +5,11 @@ import flask_restful from flask import make_response, request from monkey_island.cc.resources.auth.auth import jwt_required +from monkey_island.cc.services.config_manipulator import update_config_on_mode_set from monkey_island.cc.services.mode.island_mode_service import ModeNotSetError, get_mode, set_mode from monkey_island.cc.services.mode.mode_enum import IslandModeEnum -logger = logging.getLogger(__name__) +LOG = logging.getLogger(__name__) class IslandMode(flask_restful.Resource): @@ -21,6 +22,12 @@ class IslandMode(flask_restful.Resource): mode = IslandModeEnum(mode_str) set_mode(mode) + if not update_config_on_mode_set(mode): + LOG.error( + "Could not apply configuration changes per mode. " + "Using default advanced configuration." + ) + return make_response({}, 200) except (AttributeError, json.decoder.JSONDecodeError): return make_response({}, 400) diff --git a/monkey/monkey_island/cc/services/config.py b/monkey/monkey_island/cc/services/config.py index 55cd70b3b..41eb21fce 100644 --- a/monkey/monkey_island/cc/services/config.py +++ b/monkey/monkey_island/cc/services/config.py @@ -8,7 +8,9 @@ from jsonschema import Draft4Validator, validators import monkey_island.cc.environment.environment_singleton as env_singleton from monkey_island.cc.database import mongo from monkey_island.cc.server_utils.encryptor import get_encryptor +from monkey_island.cc.services.config_manipulator import update_config_per_mode from monkey_island.cc.services.config_schema.config_schema import SCHEMA +from monkey_island.cc.services.mode.island_mode_service import ModeNotSetError, get_mode from monkey_island.cc.services.post_breach_files import PostBreachFilesService from monkey_island.cc.services.utils.network_utils import local_ip_addresses @@ -235,9 +237,12 @@ class ConfigService: def get_default_config(should_encrypt=False): ConfigService.init_default_config() config = copy.deepcopy(ConfigService.default_config) + if should_encrypt: ConfigService.encrypt_config(config) + logger.info("Default config was called") + return config @staticmethod @@ -251,7 +256,11 @@ class ConfigService: PostBreachFilesService.remove_PBA_files() config = ConfigService.get_default_config(True) ConfigService.set_server_ips_in_config(config) - ConfigService.update_config(config, should_encrypt=False) + try: + mode = get_mode() + update_config_per_mode(mode, config, should_encrypt=False) + except ModeNotSetError: + ConfigService.update_config(config, should_encrypt=False) logger.info("Monkey config reset was called") @staticmethod diff --git a/monkey/monkey_island/cc/services/config_manipulator.py b/monkey/monkey_island/cc/services/config_manipulator.py new file mode 100644 index 000000000..41f555be4 --- /dev/null +++ b/monkey/monkey_island/cc/services/config_manipulator.py @@ -0,0 +1,31 @@ +from typing import Dict + +import dpath.util + +import monkey_island.cc.services.config as config_service +from monkey_island.cc.services.config_manipulators import MANIPULATOR_PER_MODE +from monkey_island.cc.services.mode.mode_enum import IslandModeEnum + + +def update_config_on_mode_set(mode: IslandModeEnum) -> bool: + config = config_service.ConfigService.get_config() + return update_config_per_mode(mode.value, config, True) + + +def update_config_per_mode(mode: str, config: Dict, should_encrypt: bool) -> bool: + config = _set_default_config_values_per_mode(mode, config) + return config_service.ConfigService.update_config( + config_json=config, should_encrypt=should_encrypt + ) + + +def _set_default_config_values_per_mode(mode: str, config: Dict) -> Dict: + config_manipulator = MANIPULATOR_PER_MODE[mode] + config = _apply_config_manipulator(config, config_manipulator) + return config + + +def _apply_config_manipulator(config: Dict, config_manipulator: Dict): + for path, value in config_manipulator.items(): + dpath.util.set(config, path, value, ".") + return config diff --git a/monkey/monkey_island/cc/services/config_manipulators.py b/monkey/monkey_island/cc/services/config_manipulators.py new file mode 100644 index 000000000..291892371 --- /dev/null +++ b/monkey/monkey_island/cc/services/config_manipulators.py @@ -0,0 +1,6 @@ +from monkey_island.cc.services.mode.mode_enum import IslandModeEnum + +MANIPULATOR_PER_MODE = { + IslandModeEnum.ADVANCED.value: {}, + IslandModeEnum.RANSOMWARE.value: {"monkey.post_breach.post_breach_actions": []}, +} diff --git a/monkey/monkey_island/cc/services/mode/island_mode_service.py b/monkey/monkey_island/cc/services/mode/island_mode_service.py index b745ebef1..c45e03116 100644 --- a/monkey/monkey_island/cc/services/mode/island_mode_service.py +++ b/monkey/monkey_island/cc/services/mode/island_mode_service.py @@ -1,6 +1,10 @@ +import logging + from monkey_island.cc.models.island_mode_model import IslandMode from monkey_island.cc.services.mode.mode_enum import IslandModeEnum +LOG = logging.getLogger(__name__) + def set_mode(mode: IslandModeEnum): island_mode_model = IslandMode() diff --git a/monkey/tests/unit_tests/monkey_island/cc/resources/test_island_mode.py b/monkey/tests/unit_tests/monkey_island/cc/resources/test_island_mode.py index 823f9dad5..37b09aaed 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/resources/test_island_mode.py +++ b/monkey/tests/unit_tests/monkey_island/cc/resources/test_island_mode.py @@ -13,7 +13,11 @@ def uses_database(): @pytest.mark.parametrize("mode", ["ransomware", "advanced"]) -def test_island_mode_post(flask_client, mode): +def test_island_mode_post(flask_client, mode, monkeypatch): + monkeypatch.setattr( + "monkey_island.cc.resources.island_mode.update_config_on_mode_set", + lambda _: None, + ) resp = flask_client.post( "/api/island-mode", data=json.dumps({"mode": mode}), follow_redirects=True ) diff --git a/monkey/tests/unit_tests/monkey_island/cc/services/conftest.py b/monkey/tests/unit_tests/monkey_island/cc/services/conftest.py new file mode 100644 index 000000000..7b56c0c13 --- /dev/null +++ b/monkey/tests/unit_tests/monkey_island/cc/services/conftest.py @@ -0,0 +1,22 @@ +import pytest + +from monkey_island.cc.environment import Environment +from monkey_island.cc.services.config import ConfigService + + +@pytest.fixture +def IPS(): + return ["0.0.0.0", "9.9.9.9"] + + +@pytest.fixture +def PORT(): + return 9999 + + +@pytest.fixture +def config(monkeypatch, IPS, PORT): + monkeypatch.setattr("monkey_island.cc.services.config.local_ip_addresses", lambda: IPS) + monkeypatch.setattr(Environment, "_ISLAND_PORT", PORT) + config = ConfigService.get_default_config(True) + return config diff --git a/monkey/tests/unit_tests/monkey_island/cc/services/test_config.py b/monkey/tests/unit_tests/monkey_island/cc/services/test_config.py index cc0e94ceb..799fc40e1 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/services/test_config.py +++ b/monkey/tests/unit_tests/monkey_island/cc/services/test_config.py @@ -1,43 +1,30 @@ import pytest -from monkey_island.cc.environment import Environment from monkey_island.cc.services.config import ConfigService -IPS = ["0.0.0.0", "9.9.9.9"] -PORT = 9999 - - # If tests fail because config path is changed, sync with # monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/RunOptions.js -@pytest.fixture -def config(monkeypatch): - monkeypatch.setattr("monkey_island.cc.services.config.local_ip_addresses", lambda: IPS) - monkeypatch.setattr(Environment, "_ISLAND_PORT", PORT) - config = ConfigService.get_default_config(True) - return config - - class MockClass: pass @pytest.fixture(scope="function", autouse=True) -def mock_port_in_env_singleton(monkeypatch): +def mock_port_in_env_singleton(monkeypatch, PORT): mock_singleton = MockClass() mock_singleton.env = MockClass() mock_singleton.env.get_island_port = lambda: PORT monkeypatch.setattr("monkey_island.cc.services.config.env_singleton", mock_singleton) -def test_set_server_ips_in_config_command_servers(config): +def test_set_server_ips_in_config_command_servers(config, IPS, PORT): ConfigService.set_server_ips_in_config(config) expected_config_command_servers = [f"{ip}:{PORT}" for ip in IPS] assert config["internal"]["island_server"]["command_servers"] == expected_config_command_servers -def test_set_server_ips_in_config_current_server(config): +def test_set_server_ips_in_config_current_server(config, IPS, PORT): ConfigService.set_server_ips_in_config(config) expected_config_current_server = f"{IPS[0]}:{PORT}" assert config["internal"]["island_server"]["current_server"] == expected_config_current_server diff --git a/monkey/tests/unit_tests/monkey_island/cc/services/test_config_manipulator.py b/monkey/tests/unit_tests/monkey_island/cc/services/test_config_manipulator.py new file mode 100644 index 000000000..12cd44c10 --- /dev/null +++ b/monkey/tests/unit_tests/monkey_island/cc/services/test_config_manipulator.py @@ -0,0 +1,26 @@ +from monkey_island.cc.services.config_manipulator import update_config_on_mode_set +from monkey_island.cc.services.mode.mode_enum import IslandModeEnum + + +def test_update_config_on_mode_set_advanced(config, monkeypatch): + monkeypatch.setattr("monkey_island.cc.services.config.ConfigService.get_config", lambda: config) + monkeypatch.setattr( + "monkey_island.cc.services.config.ConfigService.update_config", + lambda config_json, should_encrypt: config_json, + ) + + mode = IslandModeEnum.ADVANCED + manipulated_config = update_config_on_mode_set(mode) + assert manipulated_config == config + + +def test_update_config_on_mode_set_ransomware(config, monkeypatch): + monkeypatch.setattr("monkey_island.cc.services.config.ConfigService.get_config", lambda: config) + monkeypatch.setattr( + "monkey_island.cc.services.config.ConfigService.update_config", + lambda config_json, should_encrypt: config_json, + ) + + mode = IslandModeEnum.RANSOMWARE + manipulated_config = update_config_on_mode_set(mode) + assert manipulated_config["monkey"]["post_breach"]["post_breach_actions"] == []