Merge remote-tracking branch 'origin/feature/2438-application-install-action-refactor' into feature/2700-application-configure-actions
This commit is contained in:
@@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
applications/*
|
applications/*
|
||||||
|
|
||||||
More info :py:mod:`primaite.game.game.APPLICATION_TYPES_MAPPING`
|
More info :py:mod:`primaite.simulator.system.applications.application.Application`
|
||||||
|
|
||||||
.. include:: list_of_system_applications.rst
|
.. include:: list_of_system_applications.rst
|
||||||
|
|
||||||
|
|||||||
@@ -26,11 +26,14 @@ from primaite.simulator.network.hardware.nodes.network.wireless_router import Wi
|
|||||||
from primaite.simulator.network.nmne import set_nmne_config
|
from primaite.simulator.network.nmne import set_nmne_config
|
||||||
from primaite.simulator.network.transmission.transport_layer import Port
|
from primaite.simulator.network.transmission.transport_layer import Port
|
||||||
from primaite.simulator.sim_container import Simulation
|
from primaite.simulator.sim_container import Simulation
|
||||||
from primaite.simulator.system.applications.database_client import DatabaseClient
|
from primaite.simulator.system.applications.application import Application
|
||||||
from primaite.simulator.system.applications.red_applications.data_manipulation_bot import DataManipulationBot
|
from primaite.simulator.system.applications.database_client import DatabaseClient # noqa: F401
|
||||||
from primaite.simulator.system.applications.red_applications.dos_bot import DoSBot
|
from primaite.simulator.system.applications.red_applications.data_manipulation_bot import ( # noqa: F401
|
||||||
from primaite.simulator.system.applications.red_applications.ransomware_script import RansomwareScript
|
DataManipulationBot,
|
||||||
from primaite.simulator.system.applications.web_browser import WebBrowser
|
)
|
||||||
|
from primaite.simulator.system.applications.red_applications.dos_bot import DoSBot # noqa: F401
|
||||||
|
from primaite.simulator.system.applications.red_applications.ransomware_script import RansomwareScript # noqa: F401
|
||||||
|
from primaite.simulator.system.applications.web_browser import WebBrowser # noqa: F401
|
||||||
from primaite.simulator.system.services.database.database_service import DatabaseService
|
from primaite.simulator.system.services.database.database_service import DatabaseService
|
||||||
from primaite.simulator.system.services.dns.dns_client import DNSClient
|
from primaite.simulator.system.services.dns.dns_client import DNSClient
|
||||||
from primaite.simulator.system.services.dns.dns_server import DNSServer
|
from primaite.simulator.system.services.dns.dns_server import DNSServer
|
||||||
@@ -42,15 +45,6 @@ from primaite.simulator.system.services.web_server.web_server import WebServer
|
|||||||
|
|
||||||
_LOGGER = getLogger(__name__)
|
_LOGGER = getLogger(__name__)
|
||||||
|
|
||||||
APPLICATION_TYPES_MAPPING = {
|
|
||||||
"WebBrowser": WebBrowser,
|
|
||||||
"DatabaseClient": DatabaseClient,
|
|
||||||
"DataManipulationBot": DataManipulationBot,
|
|
||||||
"DoSBot": DoSBot,
|
|
||||||
"RansomwareScript": RansomwareScript,
|
|
||||||
}
|
|
||||||
"""List of available applications that can be installed on nodes in the PrimAITE Simulation."""
|
|
||||||
|
|
||||||
SERVICE_TYPES_MAPPING = {
|
SERVICE_TYPES_MAPPING = {
|
||||||
"DNSClient": DNSClient,
|
"DNSClient": DNSClient,
|
||||||
"DNSServer": DNSServer,
|
"DNSServer": DNSServer,
|
||||||
@@ -333,9 +327,9 @@ class PrimaiteGame:
|
|||||||
new_application = None
|
new_application = None
|
||||||
application_type = application_cfg["type"]
|
application_type = application_cfg["type"]
|
||||||
|
|
||||||
if application_type in APPLICATION_TYPES_MAPPING:
|
if application_type in Application._application_registry:
|
||||||
new_node.software_manager.install(APPLICATION_TYPES_MAPPING[application_type])
|
new_node.software_manager.install(Application._application_registry[application_type])
|
||||||
new_application = new_node.software_manager.software[application_type]
|
new_application = new_node.software_manager.software[application_type] # grab the instance
|
||||||
else:
|
else:
|
||||||
msg = f"Configuration contains an invalid application type: {application_type}"
|
msg = f"Configuration contains an invalid application type: {application_type}"
|
||||||
_LOGGER.error(msg)
|
_LOGGER.error(msg)
|
||||||
|
|||||||
@@ -171,7 +171,7 @@
|
|||||||
"from primaite.simulator.file_system.file_system import FileSystem\n",
|
"from primaite.simulator.file_system.file_system import FileSystem\n",
|
||||||
"\n",
|
"\n",
|
||||||
"# no applications exist yet so we will create our own.\n",
|
"# no applications exist yet so we will create our own.\n",
|
||||||
"class MSPaint(Application):\n",
|
"class MSPaint(Application, identifier=\"MSPaint\"):\n",
|
||||||
" def describe_state(self):\n",
|
" def describe_state(self):\n",
|
||||||
" return super().describe_state()"
|
" return super().describe_state()"
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -196,7 +196,7 @@ class SimComponent(BaseModel):
|
|||||||
|
|
||||||
..code::python
|
..code::python
|
||||||
|
|
||||||
class WebBrowser(Application):
|
class WebBrowser(Application, identifier="WebBrowser"):
|
||||||
def _init_request_manager(self) -> RequestManager:
|
def _init_request_manager(self) -> RequestManager:
|
||||||
rm = super()._init_request_manager() # all requests generic to any Application get initialised
|
rm = super()._init_request_manager() # all requests generic to any Application get initialised
|
||||||
rm.add_request(...) # initialise any requests specific to the web browser
|
rm.add_request(...) # initialise any requests specific to the web browser
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK
|
# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK
|
||||||
from abc import abstractmethod
|
from abc import abstractmethod
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from typing import Any, Dict, Optional, Set
|
from typing import Any, ClassVar, Dict, Optional, Set, Type
|
||||||
|
|
||||||
from primaite.interface.request import RequestResponse
|
from primaite.interface.request import RequestResponse
|
||||||
from primaite.simulator.core import RequestManager, RequestType
|
from primaite.simulator.core import RequestManager, RequestType
|
||||||
@@ -39,6 +39,22 @@ class Application(IOSoftware):
|
|||||||
install_countdown: Optional[int] = None
|
install_countdown: Optional[int] = None
|
||||||
"The countdown to the end of the installation process. None if not currently installing"
|
"The countdown to the end of the installation process. None if not currently installing"
|
||||||
|
|
||||||
|
_application_registry: ClassVar[Dict[str, Type["Application"]]] = {}
|
||||||
|
"""Registry of application types. Automatically populated when subclasses are defined."""
|
||||||
|
|
||||||
|
def __init_subclass__(cls, identifier: str, **kwargs: Any) -> None:
|
||||||
|
"""
|
||||||
|
Register an application type.
|
||||||
|
|
||||||
|
:param identifier: Uniquely specifies an application class by name. Used for finding items by config.
|
||||||
|
:type identifier: str
|
||||||
|
:raises ValueError: When attempting to register an application with a name that is already allocated.
|
||||||
|
"""
|
||||||
|
super().__init_subclass__(**kwargs)
|
||||||
|
if identifier in cls._application_registry:
|
||||||
|
raise ValueError(f"Tried to define new application {identifier}, but this name is already reserved.")
|
||||||
|
cls._application_registry[identifier] = cls
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
|
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ class DatabaseClientConnection(BaseModel):
|
|||||||
self.client._disconnect(self.connection_id) # noqa
|
self.client._disconnect(self.connection_id) # noqa
|
||||||
|
|
||||||
|
|
||||||
class DatabaseClient(Application):
|
class DatabaseClient(Application, identifier="DatabaseClient"):
|
||||||
"""
|
"""
|
||||||
A DatabaseClient application.
|
A DatabaseClient application.
|
||||||
|
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ class PortScanPayload(SimComponent):
|
|||||||
return state
|
return state
|
||||||
|
|
||||||
|
|
||||||
class NMAP(Application):
|
class NMAP(Application, identifier="NMAP"):
|
||||||
"""
|
"""
|
||||||
A class representing the NMAP application for network scanning.
|
A class representing the NMAP application for network scanning.
|
||||||
|
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ class DataManipulationAttackStage(IntEnum):
|
|||||||
"Signifies that the attack has failed."
|
"Signifies that the attack has failed."
|
||||||
|
|
||||||
|
|
||||||
class DataManipulationBot(Application):
|
class DataManipulationBot(Application, identifier="DataManipulationBot"):
|
||||||
"""A bot that simulates a script which performs a SQL injection attack."""
|
"""A bot that simulates a script which performs a SQL injection attack."""
|
||||||
|
|
||||||
payload: Optional[str] = None
|
payload: Optional[str] = None
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ class DoSAttackStage(IntEnum):
|
|||||||
"Attack is completed."
|
"Attack is completed."
|
||||||
|
|
||||||
|
|
||||||
class DoSBot(DatabaseClient):
|
class DoSBot(DatabaseClient, identifier="DoSBot"):
|
||||||
"""A bot that simulates a Denial of Service attack."""
|
"""A bot that simulates a Denial of Service attack."""
|
||||||
|
|
||||||
target_ip_address: Optional[IPv4Address] = None
|
target_ip_address: Optional[IPv4Address] = None
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ from primaite.simulator.system.applications.application import Application
|
|||||||
from primaite.simulator.system.applications.database_client import DatabaseClient, DatabaseClientConnection
|
from primaite.simulator.system.applications.database_client import DatabaseClient, DatabaseClientConnection
|
||||||
|
|
||||||
|
|
||||||
class RansomwareScript(Application):
|
class RansomwareScript(Application, identifier="RansomwareScript"):
|
||||||
"""Ransomware Kill Chain - Designed to be used by the TAP001 Agent on the example layout Network.
|
"""Ransomware Kill Chain - Designed to be used by the TAP001 Agent on the example layout Network.
|
||||||
|
|
||||||
:ivar payload: The attack stage query payload. (Default ENCRYPT)
|
:ivar payload: The attack stage query payload. (Default ENCRYPT)
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ from primaite.simulator.system.services.dns.dns_client import DNSClient
|
|||||||
_LOGGER = getLogger(__name__)
|
_LOGGER = getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class WebBrowser(Application):
|
class WebBrowser(Application, identifier="WebBrowser"):
|
||||||
"""
|
"""
|
||||||
Represents a web browser in the simulation environment.
|
Represents a web browser in the simulation environment.
|
||||||
|
|
||||||
|
|||||||
@@ -51,11 +51,11 @@ class TestService(Service):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class TestApplication(Application):
|
class DummyApplication(Application, identifier="DummyApplication"):
|
||||||
"""Test Application class"""
|
"""Test Application class"""
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
kwargs["name"] = "TestApplication"
|
kwargs["name"] = "DummyApplication"
|
||||||
kwargs["port"] = Port.HTTP
|
kwargs["port"] = Port.HTTP
|
||||||
kwargs["protocol"] = IPProtocol.TCP
|
kwargs["protocol"] = IPProtocol.TCP
|
||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
@@ -85,15 +85,15 @@ def service_class():
|
|||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="function")
|
@pytest.fixture(scope="function")
|
||||||
def application(file_system) -> TestApplication:
|
def application(file_system) -> DummyApplication:
|
||||||
return TestApplication(
|
return DummyApplication(
|
||||||
name="TestApplication", port=Port.ARP, file_system=file_system, sys_log=SysLog(hostname="test_application")
|
name="DummyApplication", port=Port.ARP, file_system=file_system, sys_log=SysLog(hostname="dummy_application")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="function")
|
@pytest.fixture(scope="function")
|
||||||
def application_class():
|
def application_class():
|
||||||
return TestApplication
|
return DummyApplication
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="function")
|
@pytest.fixture(scope="function")
|
||||||
|
|||||||
@@ -9,9 +9,10 @@ from primaite.config.load import data_manipulation_config_path
|
|||||||
from primaite.game.agent.interface import ProxyAgent
|
from primaite.game.agent.interface import ProxyAgent
|
||||||
from primaite.game.agent.scripted_agents.data_manipulation_bot import DataManipulationAgent
|
from primaite.game.agent.scripted_agents.data_manipulation_bot import DataManipulationAgent
|
||||||
from primaite.game.agent.scripted_agents.probabilistic_agent import ProbabilisticAgent
|
from primaite.game.agent.scripted_agents.probabilistic_agent import ProbabilisticAgent
|
||||||
from primaite.game.game import APPLICATION_TYPES_MAPPING, PrimaiteGame, SERVICE_TYPES_MAPPING
|
from primaite.game.game import PrimaiteGame, SERVICE_TYPES_MAPPING
|
||||||
from primaite.simulator.network.container import Network
|
from primaite.simulator.network.container import Network
|
||||||
from primaite.simulator.network.hardware.nodes.host.computer import Computer
|
from primaite.simulator.network.hardware.nodes.host.computer import Computer
|
||||||
|
from primaite.simulator.system.applications.application import Application
|
||||||
from primaite.simulator.system.applications.database_client import DatabaseClient
|
from primaite.simulator.system.applications.database_client import DatabaseClient
|
||||||
from primaite.simulator.system.applications.red_applications.data_manipulation_bot import DataManipulationBot
|
from primaite.simulator.system.applications.red_applications.data_manipulation_bot import DataManipulationBot
|
||||||
from primaite.simulator.system.applications.red_applications.dos_bot import DoSBot
|
from primaite.simulator.system.applications.red_applications.dos_bot import DoSBot
|
||||||
@@ -85,7 +86,7 @@ def test_node_software_install():
|
|||||||
assert client_2.software_manager.software.get(software.__name__) is not None
|
assert client_2.software_manager.software.get(software.__name__) is not None
|
||||||
|
|
||||||
# check that applications have been installed on client 1
|
# check that applications have been installed on client 1
|
||||||
for applications in APPLICATION_TYPES_MAPPING:
|
for applications in Application._application_registry:
|
||||||
assert client_1.software_manager.software.get(applications) is not None
|
assert client_1.software_manager.software.get(applications) is not None
|
||||||
|
|
||||||
# check that services have been installed on client 1
|
# check that services have been installed on client 1
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ class BroadcastService(Service):
|
|||||||
super().send(payload="broadcast", dest_ip_address=ip_network, dest_port=Port.HTTP, ip_protocol=self.protocol)
|
super().send(payload="broadcast", dest_ip_address=ip_network, dest_port=Port.HTTP, ip_protocol=self.protocol)
|
||||||
|
|
||||||
|
|
||||||
class BroadcastClient(Application):
|
class BroadcastClient(Application, identifier="BroadcastClient"):
|
||||||
"""A client application to receive broadcast and unicast messages."""
|
"""A client application to receive broadcast and unicast messages."""
|
||||||
|
|
||||||
payloads_received: List = []
|
payloads_received: List = []
|
||||||
|
|||||||
@@ -3,9 +3,9 @@ from primaite.simulator.network.hardware.nodes.host.computer import Computer
|
|||||||
from primaite.simulator.network.hardware.nodes.host.server import Server
|
from primaite.simulator.network.hardware.nodes.host.server import Server
|
||||||
from primaite.simulator.network.networks import multi_lan_internet_network_example
|
from primaite.simulator.network.networks import multi_lan_internet_network_example
|
||||||
from primaite.simulator.system.applications.database_client import DatabaseClient
|
from primaite.simulator.system.applications.database_client import DatabaseClient
|
||||||
|
from primaite.simulator.system.applications.web_browser import WebBrowser
|
||||||
from primaite.simulator.system.services.dns.dns_client import DNSClient
|
from primaite.simulator.system.services.dns.dns_client import DNSClient
|
||||||
from primaite.simulator.system.services.ftp.ftp_client import FTPClient
|
from primaite.simulator.system.services.ftp.ftp_client import FTPClient
|
||||||
from src.primaite.simulator.system.applications.web_browser import WebBrowser
|
|
||||||
|
|
||||||
|
|
||||||
def test_all_with_configured_dns_server_ip_can_resolve_url():
|
def test_all_with_configured_dns_server_ip_can_resolve_url():
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ def populated_node(application_class) -> Tuple[Application, Computer]:
|
|||||||
computer.power_on()
|
computer.power_on()
|
||||||
computer.software_manager.install(application_class)
|
computer.software_manager.install(application_class)
|
||||||
|
|
||||||
app = computer.software_manager.software.get("TestApplication")
|
app = computer.software_manager.software.get("DummyApplication")
|
||||||
app.run()
|
app.run()
|
||||||
|
|
||||||
return app, computer
|
return app, computer
|
||||||
@@ -39,7 +39,7 @@ def test_application_on_offline_node(application_class):
|
|||||||
)
|
)
|
||||||
computer.software_manager.install(application_class)
|
computer.software_manager.install(application_class)
|
||||||
|
|
||||||
app: Application = computer.software_manager.software.get("TestApplication")
|
app: Application = computer.software_manager.software.get("DummyApplication")
|
||||||
|
|
||||||
computer.power_off()
|
computer.power_off()
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ from primaite.simulator.network.hardware.node_operating_state import NodeOperati
|
|||||||
from primaite.simulator.network.hardware.nodes.host.host_node import HostNode
|
from primaite.simulator.network.hardware.nodes.host.host_node import HostNode
|
||||||
from primaite.simulator.network.hardware.nodes.network.router import ACLAction, Router
|
from primaite.simulator.network.hardware.nodes.network.router import ACLAction, Router
|
||||||
from primaite.simulator.network.transmission.transport_layer import Port
|
from primaite.simulator.network.transmission.transport_layer import Port
|
||||||
from tests.conftest import TestApplication, TestService
|
from tests.conftest import DummyApplication, TestService
|
||||||
|
|
||||||
|
|
||||||
def test_successful_node_file_system_creation_request(example_network):
|
def test_successful_node_file_system_creation_request(example_network):
|
||||||
@@ -47,14 +47,14 @@ def test_successful_application_requests(example_network):
|
|||||||
net = example_network
|
net = example_network
|
||||||
|
|
||||||
client_1 = net.get_node_by_hostname("client_1")
|
client_1 = net.get_node_by_hostname("client_1")
|
||||||
client_1.software_manager.install(TestApplication)
|
client_1.software_manager.install(DummyApplication)
|
||||||
client_1.software_manager.software.get("TestApplication").run()
|
client_1.software_manager.software.get("DummyApplication").run()
|
||||||
|
|
||||||
resp_1 = net.apply_request(["node", "client_1", "application", "TestApplication", "scan"])
|
resp_1 = net.apply_request(["node", "client_1", "application", "DummyApplication", "scan"])
|
||||||
assert resp_1 == RequestResponse(status="success", data={})
|
assert resp_1 == RequestResponse(status="success", data={})
|
||||||
resp_2 = net.apply_request(["node", "client_1", "application", "TestApplication", "fix"])
|
resp_2 = net.apply_request(["node", "client_1", "application", "DummyApplication", "fix"])
|
||||||
assert resp_2 == RequestResponse(status="success", data={})
|
assert resp_2 == RequestResponse(status="success", data={})
|
||||||
resp_3 = net.apply_request(["node", "client_1", "application", "TestApplication", "compromise"])
|
resp_3 = net.apply_request(["node", "client_1", "application", "DummyApplication", "compromise"])
|
||||||
assert resp_3 == RequestResponse(status="success", data={})
|
assert resp_3 == RequestResponse(status="success", data={})
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from primaite.simulator.system.applications.application import Application
|
||||||
|
|
||||||
|
|
||||||
|
def test_adding_to_app_registry():
|
||||||
|
class temp_application(Application, identifier="temp_app"):
|
||||||
|
pass
|
||||||
|
|
||||||
|
assert Application._application_registry["temp_app"] is temp_application
|
||||||
|
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
|
||||||
|
class another_application(Application, identifier="temp_app"):
|
||||||
|
pass
|
||||||
|
|
||||||
|
# This is kinda evil...
|
||||||
|
# Because pytest doesn't reimport classes from modules, registering this temporary test application will change the
|
||||||
|
# state of the Application registry for all subsequently run tests. So, we have to delete and unregister the class.
|
||||||
|
del temp_application
|
||||||
|
Application._application_registry.pop("temp_app")
|
||||||
Reference in New Issue
Block a user