From 03ec893e97e2499ef49675534d6767856b642f9e Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 30 Jun 2022 12:31:26 -0400 Subject: [PATCH 1/9] Island: Separate initialization of RESTful and RPC endpoints --- monkey/monkey_island/cc/app.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/monkey/monkey_island/cc/app.py b/monkey/monkey_island/cc/app.py index 282f93346..ab09cfea1 100644 --- a/monkey/monkey_island/cc/app.py +++ b/monkey/monkey_island/cc/app.py @@ -143,6 +143,11 @@ class FlaskDIWrapper: def init_api_resources(api: FlaskDIWrapper): + init_restful_endpoints(api) + init_rpc_endpoints(api) + + +def init_restful_endpoints(api: FlaskDIWrapper): api.add_resource(Root) api.add_resource(Registration) api.add_resource(Authenticate) @@ -192,6 +197,10 @@ def init_api_resources(api: FlaskDIWrapper): api.add_resource(TelemetryBlackboxEndpoint) +def init_rpc_endpoints(api: FlaskDIWrapper): + pass + + def init_app(mongo_url: str, container: DIContainer): """ Simple docstirng for init_app From 6d4920e47f1048f13c44b47238f1ad853bb21b26 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 30 Jun 2022 12:33:13 -0400 Subject: [PATCH 2/9] Island: Add /api/reset-agent-configuration RPC endpoint --- monkey/monkey_island/cc/app.py | 4 ++-- monkey/monkey_island/cc/resources/__init__.py | 1 + .../cc/resources/reset_agent_configuration.py | 21 +++++++++++++++++++ 3 files changed, 24 insertions(+), 2 deletions(-) create mode 100644 monkey/monkey_island/cc/resources/reset_agent_configuration.py diff --git a/monkey/monkey_island/cc/app.py b/monkey/monkey_island/cc/app.py index ab09cfea1..4ea4590c7 100644 --- a/monkey/monkey_island/cc/app.py +++ b/monkey/monkey_island/cc/app.py @@ -10,7 +10,7 @@ from werkzeug.exceptions import NotFound from common import DIContainer from monkey_island.cc.database import database, mongo -from monkey_island.cc.resources import AgentBinaries, RemoteRun +from monkey_island.cc.resources import AgentBinaries, RemoteRun, ResetAgentConfiguration from monkey_island.cc.resources.AbstractResource import AbstractResource from monkey_island.cc.resources.agent_configuration import AgentConfiguration from monkey_island.cc.resources.agent_controls import StopAgentCheck, StopAllAgents @@ -198,7 +198,7 @@ def init_restful_endpoints(api: FlaskDIWrapper): def init_rpc_endpoints(api: FlaskDIWrapper): - pass + api.add_resource(ResetAgentConfiguration) def init_app(mongo_url: str, container: DIContainer): diff --git a/monkey/monkey_island/cc/resources/__init__.py b/monkey/monkey_island/cc/resources/__init__.py index 31ed641dd..fd830ed86 100644 --- a/monkey/monkey_island/cc/resources/__init__.py +++ b/monkey/monkey_island/cc/resources/__init__.py @@ -1,2 +1,3 @@ from .remote_run import RemoteRun from .agent_binaries import AgentBinaries +from .reset_agent_configuration import ResetAgentConfiguration diff --git a/monkey/monkey_island/cc/resources/reset_agent_configuration.py b/monkey/monkey_island/cc/resources/reset_agent_configuration.py new file mode 100644 index 000000000..274cad592 --- /dev/null +++ b/monkey/monkey_island/cc/resources/reset_agent_configuration.py @@ -0,0 +1,21 @@ +from flask import make_response + +from monkey_island.cc.repository import IAgentConfigurationRepository +from monkey_island.cc.resources.AbstractResource import AbstractResource +from monkey_island.cc.resources.request_authentication import jwt_required + + +class ResetAgentConfiguration(AbstractResource): + urls = ["/api/reset-agent-configuration"] + + def __init__(self, agent_configuration_repository: IAgentConfigurationRepository): + self._agent_configuration_repository = agent_configuration_repository + + @jwt_required + def post(self): + """ + Reset the agent configuration to its default values + """ + self._agent_configuration_repository.reset_to_default() + + return make_response({}, 200) From d4c7b97229c3e1ba8ac3dc1218f69b5a82b6a14a Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 30 Jun 2022 12:47:40 -0400 Subject: [PATCH 3/9] Island: Add UNSET to IslandModeEnum --- monkey/monkey_island/cc/services/mode/mode_enum.py | 1 + vulture_allowlist.py | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/monkey/monkey_island/cc/services/mode/mode_enum.py b/monkey/monkey_island/cc/services/mode/mode_enum.py index fce46db97..d6258bbff 100644 --- a/monkey/monkey_island/cc/services/mode/mode_enum.py +++ b/monkey/monkey_island/cc/services/mode/mode_enum.py @@ -2,5 +2,6 @@ from enum import Enum class IslandModeEnum(Enum): + UNSET = "unset" RANSOMWARE = "ransomware" ADVANCED = "advanced" diff --git a/vulture_allowlist.py b/vulture_allowlist.py index e1d56f689..126f6c596 100644 --- a/vulture_allowlist.py +++ b/vulture_allowlist.py @@ -149,7 +149,8 @@ MONKEY_LINUX_RUNNING # unused variable (monkey/monkey_island/cc/services/utils/ import_status # monkey_island\cc\resources\configuration_import.py:19 config_schema # monkey_island\cc\resources\configuration_import.py:25 exception_stream # unused attribute (monkey_island/cc/server_setup.py:104) -ADVANCED # unused attribute (monkey/monkey_island/cc/services/mode/mode_enum.py:6:) +ADVANCED # unused attribute (monkey/monkey_island/cc/services/mode/mode_enum.py:7:) +UNSET # unused attribute (monkey/monkey_island/cc/services/mode/mode_enum.py:5:) Report.overview Report.recommendations Report.glance From 6fa52d0637591cdcc27df73048d369e0c53003ba Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 30 Jun 2022 12:56:50 -0400 Subject: [PATCH 4/9] Island: Remove ModeNotSetError --- monkey/monkey_island/cc/resources/island_mode.py | 10 +++------- monkey/monkey_island/cc/services/config.py | 13 ++++++++----- .../cc/services/config_manipulators.py | 1 + .../cc/services/mode/island_mode_service.py | 9 ++------- 4 files changed, 14 insertions(+), 19 deletions(-) diff --git a/monkey/monkey_island/cc/resources/island_mode.py b/monkey/monkey_island/cc/resources/island_mode.py index 6978f3b14..b4d358b66 100644 --- a/monkey/monkey_island/cc/resources/island_mode.py +++ b/monkey/monkey_island/cc/resources/island_mode.py @@ -6,7 +6,7 @@ from flask import make_response, request from monkey_island.cc.resources.AbstractResource import AbstractResource from monkey_island.cc.resources.request_authentication 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.island_mode_service import get_mode, set_mode from monkey_island.cc.services.mode.mode_enum import IslandModeEnum logger = logging.getLogger(__name__) @@ -40,9 +40,5 @@ class IslandMode(AbstractResource): @jwt_required def get(self): - try: - island_mode = get_mode() - return make_response({"mode": island_mode}, 200) - - except ModeNotSetError: - return make_response({"mode": None}, 200) + island_mode = get_mode() + return make_response({"mode": island_mode}, 200) diff --git a/monkey/monkey_island/cc/services/config.py b/monkey/monkey_island/cc/services/config.py index e0be40117..3cb5ce09f 100644 --- a/monkey/monkey_island/cc/services/config.py +++ b/monkey/monkey_island/cc/services/config.py @@ -25,7 +25,8 @@ from monkey_island.cc.server_utils.encryption import ( ) 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.mode.island_mode_service import get_mode +from monkey_island.cc.services.mode.mode_enum import IslandModeEnum from monkey_island.cc.services.post_breach_files import PostBreachFilesService logger = logging.getLogger(__name__) @@ -250,11 +251,13 @@ class ConfigService: def reset_config(): PostBreachFilesService.remove_PBA_files() config = ConfigService.get_default_config(True) - try: - mode = get_mode() - update_config_per_mode(mode, config, should_encrypt=False) - except ModeNotSetError: + + mode = get_mode() + if mode == IslandModeEnum.UNSET.value: ConfigService.update_config(config, should_encrypt=False) + else: + update_config_per_mode(mode, config, should_encrypt=False) + logger.info("Monkey config reset was called") @staticmethod diff --git a/monkey/monkey_island/cc/services/config_manipulators.py b/monkey/monkey_island/cc/services/config_manipulators.py index 291892371..947a32971 100644 --- a/monkey/monkey_island/cc/services/config_manipulators.py +++ b/monkey/monkey_island/cc/services/config_manipulators.py @@ -3,4 +3,5 @@ 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": []}, + IslandModeEnum.UNSET.value: {}, } 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..32a3d943d 100644 --- a/monkey/monkey_island/cc/services/mode/island_mode_service.py +++ b/monkey/monkey_island/cc/services/mode/island_mode_service.py @@ -3,6 +3,7 @@ from monkey_island.cc.services.mode.mode_enum import IslandModeEnum def set_mode(mode: IslandModeEnum): + IslandMode.drop_collection() island_mode_model = IslandMode() island_mode_model.mode = mode.value island_mode_model.save() @@ -13,10 +14,4 @@ def get_mode() -> str: mode = IslandMode.objects[0].mode return mode else: - raise ModeNotSetError - - -class ModeNotSetError(Exception): - """ - Throw this exception when island mode is not set. - """ + return IslandModeEnum.UNSET.value From 2b60b4ed8197ea139fdf61c6643916360d487163 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 30 Jun 2022 13:01:58 -0400 Subject: [PATCH 5/9] Island: Remove comment in IslandMode resource The HTTP response status codes seem reasonable. --- monkey/monkey_island/cc/resources/island_mode.py | 1 - 1 file changed, 1 deletion(-) diff --git a/monkey/monkey_island/cc/resources/island_mode.py b/monkey/monkey_island/cc/resources/island_mode.py index b4d358b66..5d7343459 100644 --- a/monkey/monkey_island/cc/resources/island_mode.py +++ b/monkey/monkey_island/cc/resources/island_mode.py @@ -34,7 +34,6 @@ class IslandMode(AbstractResource): return make_response({}, 200) except (AttributeError, json.decoder.JSONDecodeError): return make_response({}, 400) - # API Spec: Check if HTTP codes make sense except ValueError: return make_response({}, 422) From 301f2fc89ce0d5347bc30767cf165b078b44ac6e Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 30 Jun 2022 13:08:39 -0400 Subject: [PATCH 6/9] UI: Use new "unset" island mode --- monkey/monkey_island/cc/ui/src/components/Main.tsx | 6 +++--- .../cc/ui/src/components/pages/ConfigurePage.js | 2 +- .../monkey_island/cc/resources/test_island_mode.py | 9 +++++---- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/Main.tsx b/monkey/monkey_island/cc/ui/src/components/Main.tsx index e28013df2..4a4370294 100644 --- a/monkey/monkey_island/cc/ui/src/components/Main.tsx +++ b/monkey/monkey_island/cc/ui/src/components/Main.tsx @@ -93,7 +93,7 @@ class AppComponent extends AuthComponent { if (res) { this.setMode() .then(() => { - if (this.state.islandMode === null) { + if (this.state.islandMode === "unset") { return } this.authFetch('/api') @@ -151,12 +151,12 @@ class AppComponent extends AuthComponent { }; needsRedirectionToLandingPage = (route_path) => { - return (this.state.islandMode === null && route_path !== Routes.LandingPage) + return (this.state.islandMode === "unset" && route_path !== Routes.LandingPage) } needsRedirectionToGettingStarted = (route_path) => { return route_path === Routes.LandingPage && - this.state.islandMode !== null && this.state.islandMode !== undefined + this.state.islandMode !== "unset" && this.state.islandMode !== undefined } redirectTo = (userPath, targetPath) => { diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js index b59659ba8..49b46cc43 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js @@ -58,7 +58,7 @@ class ConfigurePageComponent extends AuthComponent { } getSectionsOrder() { - let islandMode = this.props.islandMode ? this.props.islandMode : 'advanced' + let islandMode = this.props.islandMode !== 'unset' ? this.props.islandMode : 'advanced' return CONFIGURATION_TABS_PER_MODE[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 a167468e9..a49512bf3 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 @@ -4,6 +4,7 @@ import pytest from tests.utils import raise_ from monkey_island.cc.models.island_mode_model import IslandMode +from monkey_island.cc.services.mode.mode_enum import IslandModeEnum from monkey_island.cc.resources import island_mode as island_mode_resource from monkey_island.cc.resources.island_mode import IslandMode as IslandModeResource @@ -13,7 +14,7 @@ def uses_database(): IslandMode.objects().delete() -@pytest.mark.parametrize("mode", ["ransomware", "advanced"]) +@pytest.mark.parametrize("mode", [IslandModeEnum.RANSOMWARE.value, IslandModeEnum.ADVANCED.value, IslandModeEnum.UNSET.value]) def test_island_mode_post(flask_client, mode, monkeypatch): monkeypatch.setattr( "monkey_island.cc.resources.island_mode.update_config_on_mode_set", @@ -42,12 +43,12 @@ def test_island_mode_post__internal_server_error(monkeypatch, flask_client): monkeypatch.setattr(island_mode_resource, "set_mode", lambda x: raise_(Exception())) resp = flask_client.post( - IslandModeResource.urls[0], data=json.dumps({"mode": "ransomware"}), follow_redirects=True + IslandModeResource.urls[0], data=json.dumps({"mode":IslandModeEnum.RANSOMWARE.value}), follow_redirects=True ) assert resp.status_code == 500 -@pytest.mark.parametrize("mode", ["ransomware", "advanced"]) +@pytest.mark.parametrize("mode", [IslandModeEnum.RANSOMWARE.value, IslandModeEnum.ADVANCED.value]) def test_island_mode_endpoint(flask_client, uses_database, mode): flask_client.post( IslandModeResource.urls[0], data=json.dumps({"mode": mode}), follow_redirects=True @@ -63,4 +64,4 @@ def test_island_mode_endpoint__invalid_mode(flask_client, uses_database): ) resp_get = flask_client.get(IslandModeResource.urls[0], follow_redirects=True) assert resp_post.status_code == 422 - assert json.loads(resp_get.data)["mode"] is None + assert json.loads(resp_get.data)["mode"] == IslandModeEnum.UNSET.value From 8a52ad8951589992bdf156a72d05efbb37f55370 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 30 Jun 2022 13:22:49 -0400 Subject: [PATCH 7/9] Island: Add /api/clear-simulation-data --- monkey/monkey_island/cc/app.py | 8 +++++++- monkey/monkey_island/cc/resources/__init__.py | 1 + .../cc/resources/clear_simulation_data.py | 18 ++++++++++++++++++ 3 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 monkey/monkey_island/cc/resources/clear_simulation_data.py diff --git a/monkey/monkey_island/cc/app.py b/monkey/monkey_island/cc/app.py index 4ea4590c7..6a8876e21 100644 --- a/monkey/monkey_island/cc/app.py +++ b/monkey/monkey_island/cc/app.py @@ -10,7 +10,12 @@ from werkzeug.exceptions import NotFound from common import DIContainer from monkey_island.cc.database import database, mongo -from monkey_island.cc.resources import AgentBinaries, RemoteRun, ResetAgentConfiguration +from monkey_island.cc.resources import ( + AgentBinaries, + ClearSimulationData, + RemoteRun, + ResetAgentConfiguration, +) from monkey_island.cc.resources.AbstractResource import AbstractResource from monkey_island.cc.resources.agent_configuration import AgentConfiguration from monkey_island.cc.resources.agent_controls import StopAgentCheck, StopAllAgents @@ -199,6 +204,7 @@ def init_restful_endpoints(api: FlaskDIWrapper): def init_rpc_endpoints(api: FlaskDIWrapper): api.add_resource(ResetAgentConfiguration) + api.add_resource(ClearSimulationData) def init_app(mongo_url: str, container: DIContainer): diff --git a/monkey/monkey_island/cc/resources/__init__.py b/monkey/monkey_island/cc/resources/__init__.py index fd830ed86..7f0385c32 100644 --- a/monkey/monkey_island/cc/resources/__init__.py +++ b/monkey/monkey_island/cc/resources/__init__.py @@ -1,3 +1,4 @@ from .remote_run import RemoteRun from .agent_binaries import AgentBinaries +from .clear_simulation_data import ClearSimulationData from .reset_agent_configuration import ResetAgentConfiguration diff --git a/monkey/monkey_island/cc/resources/clear_simulation_data.py b/monkey/monkey_island/cc/resources/clear_simulation_data.py new file mode 100644 index 000000000..c7da0424a --- /dev/null +++ b/monkey/monkey_island/cc/resources/clear_simulation_data.py @@ -0,0 +1,18 @@ +from flask import make_response + +from monkey_island.cc.resources.AbstractResource import AbstractResource +from monkey_island.cc.resources.request_authentication import jwt_required +from monkey_island.cc.services.database import Database + + +class ClearSimulationData(AbstractResource): + urls = ["/api/clear-simulation-data"] + + @jwt_required + def post(self): + """ + Clear all data collected during the simulation + """ + Database.reset_db(reset_config=False) + + return make_response({}, 200) From 9ece3c100b8ae4bc45ebbce20a27a9c2612c7a3c Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 30 Jun 2022 14:48:48 -0400 Subject: [PATCH 8/9] Changelog: Add changelog entries for new endpoints --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b85ed376f..dfdbffab6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,8 @@ Changelog](https://keepachangelog.com/en/1.0.0/). - deployment_scrips/install-infection-monkey-service.sh to install an AppImage as a service. #1552 - The ability to download the Monkey Island logs from the Infection Map page. #1640 +- `/api/reset-agent-configuration` endpoint. #2036 +- `/api/clear-simulation-data` endpoint. #2036 ### Changed - Reset workflow. Now it's possible to delete data gathered by agents without @@ -42,6 +44,7 @@ Changelog](https://keepachangelog.com/en/1.0.0/). the current depth of the agent, not hops remaining). #2033 - Agent configuration structure. #1996, #1998, #1961, #1997, #1994, #1741, #1761, #1695, #1605, #2028 +- `/api/island-mode` to accept and return new "unset" mode. #2036 ### Removed - VSFTPD exploiter. #1533 From 6206196edaad45db9783fd7697132613928a99ce Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 30 Jun 2022 15:07:53 -0400 Subject: [PATCH 9/9] UT: Fix formatting of test_island_mode.py --- .../monkey_island/cc/resources/test_island_mode.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) 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 a49512bf3..2e603864d 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 @@ -4,9 +4,9 @@ import pytest from tests.utils import raise_ from monkey_island.cc.models.island_mode_model import IslandMode -from monkey_island.cc.services.mode.mode_enum import IslandModeEnum from monkey_island.cc.resources import island_mode as island_mode_resource from monkey_island.cc.resources.island_mode import IslandMode as IslandModeResource +from monkey_island.cc.services.mode.mode_enum import IslandModeEnum @pytest.fixture(scope="function") @@ -14,7 +14,10 @@ def uses_database(): IslandMode.objects().delete() -@pytest.mark.parametrize("mode", [IslandModeEnum.RANSOMWARE.value, IslandModeEnum.ADVANCED.value, IslandModeEnum.UNSET.value]) +@pytest.mark.parametrize( + "mode", + [IslandModeEnum.RANSOMWARE.value, IslandModeEnum.ADVANCED.value, IslandModeEnum.UNSET.value], +) def test_island_mode_post(flask_client, mode, monkeypatch): monkeypatch.setattr( "monkey_island.cc.resources.island_mode.update_config_on_mode_set", @@ -43,7 +46,9 @@ def test_island_mode_post__internal_server_error(monkeypatch, flask_client): monkeypatch.setattr(island_mode_resource, "set_mode", lambda x: raise_(Exception())) resp = flask_client.post( - IslandModeResource.urls[0], data=json.dumps({"mode":IslandModeEnum.RANSOMWARE.value}), follow_redirects=True + IslandModeResource.urls[0], + data=json.dumps({"mode": IslandModeEnum.RANSOMWARE.value}), + follow_redirects=True, ) assert resp.status_code == 500