Skip to content

Commit a286d02

Browse files
Island: Modify backend report to send remediation suggestions
Issue: #2857 PR: #2884
1 parent 07e3d4a commit a286d02

File tree

2 files changed

+102
-13
lines changed
  • monkey

2 files changed

+102
-13
lines changed

monkey/monkey_island/cc/services/reporting/report.py

+32-9
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
PingScanEvent,
2020
TCPScanEvent,
2121
)
22-
from common.agent_plugins import AgentPluginType
22+
from common.agent_plugins import AgentPluginManifest, AgentPluginType
2323
from common.network.network_range import NetworkRange
2424
from common.network.network_utils import get_my_ip_addresses_legacy, get_network_interfaces
2525
from common.network.segmentation_utils import get_ip_if_in_subnet
@@ -443,13 +443,7 @@ def get_config_exploits(cls) -> List[str]:
443443

444444
agent_configuration = cls._agent_configuration_repository.get_configuration() # type: ignore[union-attr] # noqa: E501
445445
exploitation_configuration = agent_configuration.propagation.exploitation
446-
exploiter_manifests = cls._agent_plugin_repository.get_all_plugin_manifests().get( # type: ignore[union-attr] # noqa: E501
447-
AgentPluginType.EXPLOITER, {}
448-
)
449-
if not exploiter_manifests:
450-
logger.debug("No plugin exploiter manifests were found")
451-
452-
exploiter_manifests.update(HARD_CODED_EXPLOITER_MANIFESTS)
446+
exploiter_manifests = cls._get_exploiter_manifests()
453447

454448
for exploiter_name, manifest in exploiter_manifests.items():
455449
if exploiter_name not in exploitation_configuration.exploiters:
@@ -508,6 +502,8 @@ def generate_report(cls):
508502
cross_segment_issues = ReportService.get_cross_segment_issues()
509503
latest_event_timestamp = ReportService.get_latest_event_timestamp()
510504

505+
remediation_suggestions = ReportService.get_remediation_suggestions(issue_set)
506+
511507
scanned_nodes = ReportService.get_scanned()
512508
exploited_cnt = len(
513509
get_monkey_exploited(
@@ -530,7 +526,10 @@ def generate_report(cls):
530526
"scanned": scanned_nodes,
531527
"exploited_cnt": exploited_cnt,
532528
},
533-
"recommendations": {"issues": issues},
529+
"recommendations": {
530+
"issues": issues,
531+
"remediation_suggestions": remediation_suggestions,
532+
},
534533
"meta_info": {"latest_event_timestamp": latest_event_timestamp},
535534
}
536535

@@ -566,6 +565,30 @@ def get_latest_event_timestamp(cls) -> Optional[float]:
566565

567566
return latest_timestamp
568567

568+
@classmethod
569+
def get_remediation_suggestions(cls, issues_set: Set[str]) -> Dict[str, Any]:
570+
exploiter_manifests = cls._get_exploiter_manifests()
571+
572+
remediation_suggestions = {
573+
issue: exploiter_manifests.get(issue).remediation_suggestion # type: ignore[union-attr]
574+
for issue in issues_set
575+
if issue in exploiter_manifests
576+
}
577+
578+
return remediation_suggestions
579+
580+
@classmethod
581+
def _get_exploiter_manifests(cls) -> Dict[str, AgentPluginManifest]:
582+
exploiter_manifests = cls._agent_plugin_repository.get_all_plugin_manifests().get( # type: ignore[union-attr] # noqa: E501
583+
AgentPluginType.EXPLOITER, {}
584+
)
585+
if not exploiter_manifests:
586+
logger.debug("No plugin exploiter manifests were found")
587+
588+
exploiter_manifests.update(HARD_CODED_EXPLOITER_MANIFESTS)
589+
590+
return exploiter_manifests
591+
569592
@classmethod
570593
def report_is_outdated(cls) -> bool:
571594
"""

monkey/tests/unit_tests/monkey_island/cc/services/reporting/test_report.py

+70-4
Original file line numberDiff line numberDiff line change
@@ -8,19 +8,25 @@
88
from tests.monkey_island import (
99
InMemoryAgentConfigurationRepository,
1010
InMemoryAgentEventRepository,
11-
InMemoryAgentPluginRepository,
1211
InMemoryAgentRepository,
1312
)
1413

14+
from common import OperatingSystem
1515
from common.agent_events import (
1616
AgentShutdownEvent,
1717
ExploitationEvent,
1818
PasswordRestorationEvent,
1919
PropagationEvent,
2020
)
21+
from common.agent_plugins import AgentPluginManifest, AgentPluginType
22+
from common.hard_coded_exploiter_manifests import HARD_CODED_EXPLOITER_MANIFESTS
2123
from common.types import SocketAddress
2224
from monkey_island.cc.models import Agent, CommunicationType, Machine, Node
23-
from monkey_island.cc.repositories import IAgentEventRepository, IAgentRepository
25+
from monkey_island.cc.repositories import (
26+
IAgentEventRepository,
27+
IAgentPluginRepository,
28+
IAgentRepository,
29+
)
2430
from monkey_island.cc.services.reporting.report import ReportService
2531

2632
EVENT_1 = AgentShutdownEvent(source=UUID("2d56f972-78a8-4026-9f47-2dfd550ee207"), timestamp=10)
@@ -159,6 +165,26 @@
159165
},
160166
]
161167

168+
REMEDIATION_SUGGESTION = "Fix it like this!"
169+
170+
MOCK_EXPLOITER_NAME = "MockExploiter"
171+
172+
PLUGIN_MANIFESTS = {
173+
AgentPluginType.EXPLOITER: {
174+
MOCK_EXPLOITER_NAME: AgentPluginManifest(
175+
name=MOCK_EXPLOITER_NAME,
176+
plugin_type=AgentPluginType.EXPLOITER,
177+
supported_operating_systems=(OperatingSystem.WINDOWS,),
178+
target_operating_systems=(OperatingSystem.WINDOWS,),
179+
title="Mock Exploiter",
180+
description="Description for mock exploiter",
181+
remediation_suggestion=REMEDIATION_SUGGESTION,
182+
link_to_documentation="htp:/no_link",
183+
safe=True,
184+
)
185+
}
186+
}
187+
162188

163189
def get_machine_by_id(machine_id):
164190
return [machine for machine in MACHINES if machine_id == machine.id][0]
@@ -182,9 +208,16 @@ def agent_event_repository() -> IAgentEventRepository:
182208
return repo
183209

184210

211+
@pytest.fixture
212+
def mock_agent_plugin_repository() -> IAgentPluginRepository:
213+
return MagicMock(spec=IAgentPluginRepository)
214+
215+
185216
@pytest.fixture(autouse=True)
186217
def report_service(
187-
agent_repository: IAgentRepository, agent_event_repository: IAgentEventRepository
218+
agent_repository: IAgentRepository,
219+
agent_event_repository: IAgentEventRepository,
220+
mock_agent_plugin_repository: IAgentPluginRepository,
188221
):
189222
ReportService._machine_repository = MagicMock()
190223
ReportService._machine_repository.get_machines.return_value = MACHINES
@@ -194,7 +227,7 @@ def report_service(
194227
ReportService._node_repository.get_nodes.return_value = NODES
195228
ReportService._agent_event_repository = agent_event_repository
196229
ReportService._agent_configuration_repository = InMemoryAgentConfigurationRepository()
197-
ReportService._agent_plugin_repository = InMemoryAgentPluginRepository()
230+
ReportService._agent_plugin_repository = mock_agent_plugin_repository
198231

199232

200233
def test_get_scanned():
@@ -238,6 +271,7 @@ def test_report_service_get_latest_event_timestamp():
238271
def test_report_generation(monkeypatch, agent_event_repository):
239272
monkeypatch.setattr(ReportService, "get_issues", lambda: [])
240273
monkeypatch.setattr(ReportService, "get_cross_segment_issues", lambda: [])
274+
monkeypatch.setattr(ReportService, "get_remediation_suggestions", lambda _: {})
241275

242276
ReportService.update_report()
243277
actual_report = ReportService.get_report()
@@ -260,3 +294,35 @@ def test_report_generation__no_agent_repository():
260294

261295
with pytest.raises(RuntimeError):
262296
ReportService.update_report()
297+
298+
299+
@pytest.mark.parametrize(
300+
"issues_set, expected_remediation_suggestions",
301+
[
302+
({MOCK_EXPLOITER_NAME}, {MOCK_EXPLOITER_NAME: REMEDIATION_SUGGESTION}),
303+
({"NonExistingExploiter"}, {}),
304+
({}, {}),
305+
(
306+
{"SSHExploiter"},
307+
{"SSHExploiter": HARD_CODED_EXPLOITER_MANIFESTS["SSHExploiter"].remediation_suggestion},
308+
),
309+
(
310+
{MOCK_EXPLOITER_NAME, "SSHExploiter"},
311+
{
312+
"SSHExploiter": HARD_CODED_EXPLOITER_MANIFESTS[
313+
"SSHExploiter"
314+
].remediation_suggestion,
315+
MOCK_EXPLOITER_NAME: REMEDIATION_SUGGESTION,
316+
},
317+
),
318+
],
319+
)
320+
def test_report__get_remediation_suggestions(
321+
issues_set,
322+
expected_remediation_suggestions,
323+
mock_agent_plugin_repository,
324+
):
325+
mock_agent_plugin_repository.get_all_plugin_manifests.return_value = PLUGIN_MANIFESTS
326+
actual_remediation_suggestion = ReportService.get_remediation_suggestions(issues_set)
327+
328+
assert actual_remediation_suggestion == expected_remediation_suggestions

0 commit comments

Comments
 (0)