From 9bbd2192bef08c4ad32a2bcacf0e86d4bc341e01 Mon Sep 17 00:00:00 2001 From: Shreya Malviya Date: Mon, 21 Aug 2023 13:34:07 +0530 Subject: [PATCH 1/8] Island: Add InstalledAgentPluginsManifests resource --- .../flask_resources/__init__.py | 1 + .../installed_agent_plugins_manifests.py | 38 +++++++++++++++++++ .../flask_resources/register.py | 2 + 3 files changed, 41 insertions(+) create mode 100644 monkey/monkey_island/cc/services/agent_plugin_service/flask_resources/installed_agent_plugins_manifests.py diff --git a/monkey/monkey_island/cc/services/agent_plugin_service/flask_resources/__init__.py b/monkey/monkey_island/cc/services/agent_plugin_service/flask_resources/__init__.py index 6b872bc2776..30b9df0f580 100644 --- a/monkey/monkey_island/cc/services/agent_plugin_service/flask_resources/__init__.py +++ b/monkey/monkey_island/cc/services/agent_plugin_service/flask_resources/__init__.py @@ -1,3 +1,4 @@ from .agent_plugins import AgentPlugins from .agent_plugins_manifest import AgentPluginsManifest +from .installed_agent_plugins_manifests import InstalledAgentPluginsManifests from .register import register_resources diff --git a/monkey/monkey_island/cc/services/agent_plugin_service/flask_resources/installed_agent_plugins_manifests.py b/monkey/monkey_island/cc/services/agent_plugin_service/flask_resources/installed_agent_plugins_manifests.py new file mode 100644 index 00000000000..039005f4a14 --- /dev/null +++ b/monkey/monkey_island/cc/services/agent_plugin_service/flask_resources/installed_agent_plugins_manifests.py @@ -0,0 +1,38 @@ +import logging +from http import HTTPStatus + +from flask import make_response +from flask_security import auth_token_required, roles_accepted + +from monkey_island.cc.flask_utils import AbstractResource +from monkey_island.cc.repositories import UnknownRecordError +from monkey_island.cc.services.authentication_service import AccountRole + +from .. import IAgentPluginService + +logger = logging.getLogger(__name__) + + +class InstalledAgentPluginsManifests(AbstractResource): + urls = ["/api/agent-plugins/installed/manifests"] + + def __init__(self, agent_plugin_service: IAgentPluginService): + self._agent_plugin_service = agent_plugin_service + + @auth_token_required + @roles_accepted(AccountRole.ISLAND_INTERFACE.name) + def get(self): + """ + Get manifests of all installed plugins + """ + try: + installed_agent_plugins_manifests = ( + self._agent_plugin_service.get_all_plugin_manifests() + ) + return make_response( + installed_agent_plugins_manifests.dict(simplify=True), HTTPStatus.OK + ) + except UnknownRecordError: + message = "Could not retrieve manifests for installed plugins" + logger.warning(message) + return make_response({"message": message}, HTTPStatus.INTERNAL_SERVER_ERROR) diff --git a/monkey/monkey_island/cc/services/agent_plugin_service/flask_resources/register.py b/monkey/monkey_island/cc/services/agent_plugin_service/flask_resources/register.py index 7ce66b5b9c0..334e082770e 100644 --- a/monkey/monkey_island/cc/services/agent_plugin_service/flask_resources/register.py +++ b/monkey/monkey_island/cc/services/agent_plugin_service/flask_resources/register.py @@ -3,9 +3,11 @@ from .agent_plugins import AgentPlugins from .agent_plugins_manifest import AgentPluginsManifest from .install_agent_plugin import InstallAgentPlugin +from .installed_agent_plugins_manifests import InstalledAgentPluginsManifests def register_resources(api: FlaskDIWrapper): api.add_resource(AgentPlugins) api.add_resource(AgentPluginsManifest) api.add_resource(InstallAgentPlugin) + api.add_resource(InstalledAgentPluginsManifests) From 386ee9ca2f5f7c781f214d8a0ef6d67a0339d7ed Mon Sep 17 00:00:00 2001 From: Shreya Malviya Date: Mon, 21 Aug 2023 17:27:54 +0530 Subject: [PATCH 2/8] Island: Fix logic in InstalledAgentPluginsManifests GET --- .../installed_agent_plugins_manifests.py | 24 +++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/monkey/monkey_island/cc/services/agent_plugin_service/flask_resources/installed_agent_plugins_manifests.py b/monkey/monkey_island/cc/services/agent_plugin_service/flask_resources/installed_agent_plugins_manifests.py index 039005f4a14..c6f3e5ff7b3 100644 --- a/monkey/monkey_island/cc/services/agent_plugin_service/flask_resources/installed_agent_plugins_manifests.py +++ b/monkey/monkey_island/cc/services/agent_plugin_service/flask_resources/installed_agent_plugins_manifests.py @@ -1,9 +1,11 @@ import logging from http import HTTPStatus +from typing import Any, Dict from flask import make_response from flask_security import auth_token_required, roles_accepted +from common.agent_plugins import AgentPluginManifest, AgentPluginType from monkey_island.cc.flask_utils import AbstractResource from monkey_island.cc.repositories import UnknownRecordError from monkey_island.cc.services.authentication_service import AccountRole @@ -25,14 +27,32 @@ def get(self): """ Get manifests of all installed plugins """ + try: installed_agent_plugins_manifests = ( self._agent_plugin_service.get_all_plugin_manifests() ) - return make_response( - installed_agent_plugins_manifests.dict(simplify=True), HTTPStatus.OK + installed_agent_plugins_manifests_simplified = ( + self._get_simplified_installed_agent_plugins_manifests( + installed_agent_plugins_manifests + ) ) + + return make_response(installed_agent_plugins_manifests_simplified, HTTPStatus.OK) except UnknownRecordError: message = "Could not retrieve manifests for installed plugins" logger.warning(message) return make_response({"message": message}, HTTPStatus.INTERNAL_SERVER_ERROR) + + def _get_simplified_installed_agent_plugins_manifests( + self, manifests: Dict[AgentPluginType, Dict[str, AgentPluginManifest]] + ) -> Dict[str, Dict[str, Dict[str, Any]]]: + simplified = {} + for plugin_type in manifests: + simplified[plugin_type.value] = {} + for plugin_name in manifests[plugin_type]: + simplified[plugin_type.value][plugin_name] = manifests[plugin_type][ + plugin_name + ].dict(simplify=True) + + return simplified From 30e8cdd1ca5e1ef7b997598f6fcb6d139175fc6e Mon Sep 17 00:00:00 2001 From: Shreya Malviya Date: Mon, 21 Aug 2023 17:28:58 +0530 Subject: [PATCH 3/8] UT: Add tests for InstalledAgentPluginsManifests --- .../test_installed_agent_plugins_manifests.py | 81 +++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 monkey/tests/unit_tests/monkey_island/cc/services/agent_plugin_service/flask_resources/test_installed_agent_plugins_manifests.py diff --git a/monkey/tests/unit_tests/monkey_island/cc/services/agent_plugin_service/flask_resources/test_installed_agent_plugins_manifests.py b/monkey/tests/unit_tests/monkey_island/cc/services/agent_plugin_service/flask_resources/test_installed_agent_plugins_manifests.py new file mode 100644 index 00000000000..8e61e838305 --- /dev/null +++ b/monkey/tests/unit_tests/monkey_island/cc/services/agent_plugin_service/flask_resources/test_installed_agent_plugins_manifests.py @@ -0,0 +1,81 @@ +from http import HTTPStatus + +import pytest +from tests.common import StubDIContainer +from tests.monkey_island import InMemoryAgentPluginRepository +from tests.unit_tests.common.agent_plugins.test_agent_plugin_manifest import FAKE_NAME, FAKE_TYPE +from tests.unit_tests.monkey_island.cc.fake_agent_plugin_data import FAKE_AGENT_PLUGIN_1 +from tests.unit_tests.monkey_island.conftest import get_url_for_resource + +from common import OperatingSystem +from monkey_island.cc.repositories import RetrievalError +from monkey_island.cc.services.agent_plugin_service import IAgentPluginService +from monkey_island.cc.services.agent_plugin_service.agent_plugin_service import AgentPluginService +from monkey_island.cc.services.agent_plugin_service.flask_resources import ( + InstalledAgentPluginsManifests, +) + +FAKE_PLUGIN_NAME = "plugin_abc" + + +@pytest.fixture +def agent_plugin_repository(): + return InMemoryAgentPluginRepository() + + +@pytest.fixture +def agent_plugin_service(agent_plugin_repository): + return AgentPluginService(agent_plugin_repository) + + +@pytest.fixture +def flask_client(build_flask_client, agent_plugin_service): + container = StubDIContainer() + container.register_instance(IAgentPluginService, agent_plugin_service) + + with build_flask_client(container) as flask_client: + yield flask_client + + +def test_get_installed_plugins_manifests(flask_client, agent_plugin_repository): + agent_plugin_repository.store_agent_plugin(OperatingSystem.LINUX, FAKE_AGENT_PLUGIN_1) + + expected_response = { + "Exploiter": { + FAKE_NAME: { + "description": None, + "link_to_documentation": "http://www.beefface.com", + "name": FAKE_NAME, + "plugin_type": FAKE_TYPE, + "version": "1.0.0", + "safe": False, + "remediation_suggestion": None, + "supported_operating_systems": ["linux", "windows"], + "target_operating_systems": ["linux"], + "title": "Remote Desktop Protocol exploiter", + } + } + } + + resp = flask_client.get(get_url_for_resource(InstalledAgentPluginsManifests)) + + assert resp.status_code == HTTPStatus.OK + assert resp.json == expected_response + + +def test_get_installed_plugins_manifests__empty(flask_client): + resp = flask_client.get(get_url_for_resource(InstalledAgentPluginsManifests)) + + assert resp.status_code == HTTPStatus.OK + assert resp.json == {} + + +def test_get_installed_plugins_manifests__server_error(flask_client, agent_plugin_repository): + def raise_retrieval_error(): + raise RetrievalError + + agent_plugin_repository.get_all_plugin_manifests = raise_retrieval_error + + resp = flask_client.get(get_url_for_resource(InstalledAgentPluginsManifests)) + + assert resp.status_code == HTTPStatus.INTERNAL_SERVER_ERROR From 37b1fe0bf50388ae69bb39e99be6b23a2aa67bb2 Mon Sep 17 00:00:00 2001 From: Shreya Malviya Date: Mon, 21 Aug 2023 18:03:59 +0530 Subject: [PATCH 4/8] Changelog: Add entry for InstalledAgentPluginsManifests --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 64186d94901..6eadbfd263c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ Changelog](https://keepachangelog.com/en/1.0.0/). - HTTPRequestEvent. #3411 - RDP exploiter plugin. #3425 - A cryptojacker payload to simulate cryptojacker attacks. #3411 +- `GET /api/agent-plugins/installed/manifests`. #3424 ### Changed - Plugin source is now gzipped. #3392 From 3eb2bc5867e18ae66566e675ff2c8126bfc06b55 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 21 Aug 2023 08:40:21 -0400 Subject: [PATCH 5/8] Changelog: Add an entry for #3417 --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6eadbfd263c..b93e898d837 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ Changelog](https://keepachangelog.com/en/1.0.0/). - HTTPRequestEvent. #3411 - RDP exploiter plugin. #3425 - A cryptojacker payload to simulate cryptojacker attacks. #3411 +- `PUT /api/install-agent-plugin`. #3417 - `GET /api/agent-plugins/installed/manifests`. #3424 ### Changed From dced8e913631a92488c007a15ae6f238d2ac14ec Mon Sep 17 00:00:00 2001 From: Shreya Malviya Date: Mon, 21 Aug 2023 18:12:58 +0530 Subject: [PATCH 6/8] UT: Improve test for InstalledAgentPluginsManifests --- .../test_installed_agent_plugins_manifests.py | 26 ++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/monkey/tests/unit_tests/monkey_island/cc/services/agent_plugin_service/flask_resources/test_installed_agent_plugins_manifests.py b/monkey/tests/unit_tests/monkey_island/cc/services/agent_plugin_service/flask_resources/test_installed_agent_plugins_manifests.py index 8e61e838305..06073ac2fa8 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/services/agent_plugin_service/flask_resources/test_installed_agent_plugins_manifests.py +++ b/monkey/tests/unit_tests/monkey_island/cc/services/agent_plugin_service/flask_resources/test_installed_agent_plugins_manifests.py @@ -3,8 +3,15 @@ import pytest from tests.common import StubDIContainer from tests.monkey_island import InMemoryAgentPluginRepository -from tests.unit_tests.common.agent_plugins.test_agent_plugin_manifest import FAKE_NAME, FAKE_TYPE -from tests.unit_tests.monkey_island.cc.fake_agent_plugin_data import FAKE_AGENT_PLUGIN_1 +from tests.unit_tests.common.agent_plugins.test_agent_plugin_manifest import ( + FAKE_NAME, + FAKE_NAME2, + FAKE_TYPE, +) +from tests.unit_tests.monkey_island.cc.fake_agent_plugin_data import ( + FAKE_AGENT_PLUGIN_1, + FAKE_AGENT_PLUGIN_2, +) from tests.unit_tests.monkey_island.conftest import get_url_for_resource from common import OperatingSystem @@ -39,6 +46,7 @@ def flask_client(build_flask_client, agent_plugin_service): def test_get_installed_plugins_manifests(flask_client, agent_plugin_repository): agent_plugin_repository.store_agent_plugin(OperatingSystem.LINUX, FAKE_AGENT_PLUGIN_1) + agent_plugin_repository.store_agent_plugin(OperatingSystem.WINDOWS, FAKE_AGENT_PLUGIN_2) expected_response = { "Exploiter": { @@ -53,7 +61,19 @@ def test_get_installed_plugins_manifests(flask_client, agent_plugin_repository): "supported_operating_systems": ["linux", "windows"], "target_operating_systems": ["linux"], "title": "Remote Desktop Protocol exploiter", - } + }, + FAKE_NAME2: { + "description": None, + "link_to_documentation": "http://www.beefface.com", + "name": FAKE_NAME2, + "plugin_type": FAKE_TYPE, + "version": "1.0.0", + "safe": False, + "remediation_suggestion": None, + "supported_operating_systems": ["linux", "windows"], + "target_operating_systems": ["linux"], + "title": "Remote Desktop Protocol exploiter", + }, } } From d22f71acc756c87751e244be305713906446f41d Mon Sep 17 00:00:00 2001 From: Shreya Malviya Date: Mon, 21 Aug 2023 18:16:24 +0530 Subject: [PATCH 7/8] Island: Rename function in InstalledAgentPluginsManifests --- .../flask_resources/installed_agent_plugins_manifests.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/monkey/monkey_island/cc/services/agent_plugin_service/flask_resources/installed_agent_plugins_manifests.py b/monkey/monkey_island/cc/services/agent_plugin_service/flask_resources/installed_agent_plugins_manifests.py index c6f3e5ff7b3..0ab82dd5640 100644 --- a/monkey/monkey_island/cc/services/agent_plugin_service/flask_resources/installed_agent_plugins_manifests.py +++ b/monkey/monkey_island/cc/services/agent_plugin_service/flask_resources/installed_agent_plugins_manifests.py @@ -33,9 +33,7 @@ def get(self): self._agent_plugin_service.get_all_plugin_manifests() ) installed_agent_plugins_manifests_simplified = ( - self._get_simplified_installed_agent_plugins_manifests( - installed_agent_plugins_manifests - ) + self._simplify_installed_agent_plugins_manifests(installed_agent_plugins_manifests) ) return make_response(installed_agent_plugins_manifests_simplified, HTTPStatus.OK) @@ -44,7 +42,7 @@ def get(self): logger.warning(message) return make_response({"message": message}, HTTPStatus.INTERNAL_SERVER_ERROR) - def _get_simplified_installed_agent_plugins_manifests( + def _simplify_installed_agent_plugins_manifests( self, manifests: Dict[AgentPluginType, Dict[str, AgentPluginManifest]] ) -> Dict[str, Dict[str, Dict[str, Any]]]: simplified = {} From ff4a972f772b2816027b4f8f4b34dd102cf67d7d Mon Sep 17 00:00:00 2001 From: Shreya Malviya Date: Mon, 21 Aug 2023 18:21:31 +0530 Subject: [PATCH 8/8] Island: Remove try/except in InstalledAgentPluginsManifests GET --- .../installed_agent_plugins_manifests.py | 20 ++++++------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/monkey/monkey_island/cc/services/agent_plugin_service/flask_resources/installed_agent_plugins_manifests.py b/monkey/monkey_island/cc/services/agent_plugin_service/flask_resources/installed_agent_plugins_manifests.py index 0ab82dd5640..dd06d819cf7 100644 --- a/monkey/monkey_island/cc/services/agent_plugin_service/flask_resources/installed_agent_plugins_manifests.py +++ b/monkey/monkey_island/cc/services/agent_plugin_service/flask_resources/installed_agent_plugins_manifests.py @@ -7,7 +7,6 @@ from common.agent_plugins import AgentPluginManifest, AgentPluginType from monkey_island.cc.flask_utils import AbstractResource -from monkey_island.cc.repositories import UnknownRecordError from monkey_island.cc.services.authentication_service import AccountRole from .. import IAgentPluginService @@ -28,24 +27,17 @@ def get(self): Get manifests of all installed plugins """ - try: - installed_agent_plugins_manifests = ( - self._agent_plugin_service.get_all_plugin_manifests() - ) - installed_agent_plugins_manifests_simplified = ( - self._simplify_installed_agent_plugins_manifests(installed_agent_plugins_manifests) - ) + installed_agent_plugins_manifests = self._agent_plugin_service.get_all_plugin_manifests() + installed_agent_plugins_manifests_simplified = ( + self._simplify_installed_agent_plugins_manifests(installed_agent_plugins_manifests) + ) - return make_response(installed_agent_plugins_manifests_simplified, HTTPStatus.OK) - except UnknownRecordError: - message = "Could not retrieve manifests for installed plugins" - logger.warning(message) - return make_response({"message": message}, HTTPStatus.INTERNAL_SERVER_ERROR) + return make_response(installed_agent_plugins_manifests_simplified, HTTPStatus.OK) def _simplify_installed_agent_plugins_manifests( self, manifests: Dict[AgentPluginType, Dict[str, AgentPluginManifest]] ) -> Dict[str, Dict[str, Dict[str, Any]]]: - simplified = {} + simplified: Dict[str, Dict[str, Dict[str, Any]]] = {} for plugin_type in manifests: simplified[plugin_type.value] = {} for plugin_name in manifests[plugin_type]: