Skip to content

Commit e10a807

Browse files
committed
Merge branch '2786-plugin-manifest-endpoint' into develop
Issue #2786 PR #2814
2 parents ce92453 + c54aab1 commit e10a807

File tree

5 files changed

+128
-0
lines changed

5 files changed

+128
-0
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ Changelog](https://keepachangelog.com/en/1.0.0/).
3232
- Scrollbar to preview pane's exploit timeline in the map page. #2455
3333
- `api/agent-plugins/<string:type>/<string:name>` endpoint. #2578
3434
- `/api/agent-configuration-schema` endpoint. #2710
35+
- `api/agent-plugins/<string:type>/<string:name>/manifest` endpoint. #2786
3536

3637
### Changed
3738
- Reset workflow. Now it's possible to delete data gathered by agents without

monkey/monkey_island/cc/app.py

+2
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
AgentHeartbeat,
1919
AgentLogs,
2020
AgentPlugins,
21+
AgentPluginsManifest,
2122
Agents,
2223
AgentSignals,
2324
ClearSimulationData,
@@ -157,6 +158,7 @@ def init_restful_endpoints(api: FlaskDIWrapper):
157158
api.add_resource(AgentConfigurationSchema)
158159
api.add_resource(AgentBinaries)
159160
api.add_resource(AgentPlugins)
161+
api.add_resource(AgentPluginsManifest)
160162
api.add_resource(Machines)
161163

162164
api.add_resource(SecurityReport)

monkey/monkey_island/cc/resources/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from .agent_configuration import AgentConfiguration
88
from .agent_events import AgentEvents
99
from .agent_plugins import AgentPlugins
10+
from .agent_plugins_manifest import AgentPluginsManifest
1011
from .agents import Agents
1112
from .agent_signals import AgentSignals, TerminateAllAgents
1213
from .agent_logs import AgentLogs
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import logging
2+
from http import HTTPStatus
3+
4+
from flask import make_response
5+
6+
from common.agent_plugins import AgentPluginType
7+
from monkey_island.cc.repositories import IAgentPluginRepository, UnknownRecordError
8+
from monkey_island.cc.resources.AbstractResource import AbstractResource
9+
10+
logger = logging.getLogger(__name__)
11+
12+
13+
class AgentPluginsManifest(AbstractResource):
14+
urls = ["/api/agent-plugins/<string:plugin_type>/<string:name>/manifest"]
15+
16+
def __init__(self, agent_plugin_repository: IAgentPluginRepository):
17+
self._agent_plugin_repository = agent_plugin_repository
18+
19+
# Used by monkey. can't secure.
20+
def get(self, plugin_type: str, name: str):
21+
"""
22+
Get the plugin manifest of the specified type and name.
23+
24+
:param type: The type of plugin (e.g. Exploiter)
25+
:param name: The name of the plugin
26+
"""
27+
try:
28+
agent_plugin_type = AgentPluginType(plugin_type)
29+
except ValueError:
30+
message = f"Invalid type '{plugin_type}'."
31+
logger.warning(message)
32+
return make_response({"message": message}, HTTPStatus.NOT_FOUND)
33+
34+
try:
35+
agent_plugin_manifest = self._agent_plugin_repository.get_plugin(
36+
plugin_type=agent_plugin_type, name=name
37+
).plugin_manifest
38+
return make_response(agent_plugin_manifest.dict(simplify=True), HTTPStatus.OK)
39+
except UnknownRecordError:
40+
message = f"Plugin '{name}' of type '{plugin_type}' not found."
41+
logger.warning(message)
42+
return make_response({"message": message}, HTTPStatus.NOT_FOUND)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
from http import HTTPStatus
2+
3+
import pytest
4+
from tests.common import StubDIContainer
5+
from tests.monkey_island import InMemoryAgentPluginRepository
6+
from tests.unit_tests.common.agent_plugins.test_agent_plugin_manifest import FAKE_TYPE
7+
from tests.unit_tests.monkey_island.cc.fake_agent_plugin_data import FAKE_AGENT_PLUGIN_1
8+
from tests.unit_tests.monkey_island.conftest import get_url_for_resource
9+
10+
from monkey_island.cc.repositories import IAgentPluginRepository, RetrievalError
11+
from monkey_island.cc.resources import AgentPluginsManifest
12+
13+
14+
@pytest.fixture
15+
def agent_plugin_repository():
16+
return InMemoryAgentPluginRepository()
17+
18+
19+
@pytest.fixture
20+
def flask_client(build_flask_client, agent_plugin_repository):
21+
container = StubDIContainer()
22+
container.register_instance(IAgentPluginRepository, agent_plugin_repository)
23+
24+
with build_flask_client(container) as flask_client:
25+
yield flask_client
26+
27+
28+
def test_get_plugin_manifest(flask_client, agent_plugin_repository):
29+
agent_plugin_repository.save_plugin(FAKE_AGENT_PLUGIN_1)
30+
31+
expected_response = {
32+
"description": None,
33+
"link_to_documentation": "www.beefface.com",
34+
"name": "rdp_exploiter",
35+
"plugin_type": "Exploiter",
36+
"safe": False,
37+
"supported_operating_systems": ["linux"],
38+
"title": "Remote Desktop Protocol exploiter",
39+
}
40+
resp = flask_client.get(
41+
get_url_for_resource(
42+
AgentPluginsManifest,
43+
plugin_type=FAKE_TYPE,
44+
name=FAKE_AGENT_PLUGIN_1.plugin_manifest.name,
45+
)
46+
)
47+
48+
assert resp.status_code == HTTPStatus.OK
49+
assert resp.json == expected_response
50+
51+
52+
def test_get_plugins_manifest__not_found_if_name_does_not_exist(flask_client):
53+
resp = flask_client.get(
54+
get_url_for_resource(AgentPluginsManifest, plugin_type="Payload", name="name")
55+
)
56+
57+
assert resp.status_code == HTTPStatus.NOT_FOUND
58+
59+
60+
@pytest.mark.parametrize(
61+
"type_",
62+
["DummyType", "ExploiteR"],
63+
)
64+
def test_get_plugins_manifest__not_found_if_type_is_invalid(flask_client, type_):
65+
resp = flask_client.get(
66+
get_url_for_resource(AgentPluginsManifest, plugin_type=type_, name="name")
67+
)
68+
69+
assert resp.status_code == HTTPStatus.NOT_FOUND
70+
71+
72+
def test_get_plugins_manifest__server_error(flask_client, agent_plugin_repository):
73+
def raise_retrieval_error(plugin_type, name):
74+
raise RetrievalError
75+
76+
agent_plugin_repository.get_plugin = raise_retrieval_error
77+
78+
resp = flask_client.get(
79+
get_url_for_resource(AgentPluginsManifest, plugin_type="Payload", name="name")
80+
)
81+
82+
assert resp.status_code == HTTPStatus.INTERNAL_SERVER_ERROR

0 commit comments

Comments
 (0)