Merged PR 368: #2550 Backport changes into core

## Summary
Backport changes from sensitive into core.

Mainly the application install and a couple of test changes.

## Test process
pytest

## Checklist
- [ ] PR is linked to a **work item**
- [ ] **acceptance criteria** of linked ticket are met
- [ ] performed **self-review** of the code
- [ ] written **tests** for any new functionality added with this PR
- [ ] updated the **documentation** if this PR changes or adds functionality
- [ ] written/updated **design docs** if this PR implements new functionality
- [ ] updated the **change log**
- [ ] ran **pre-commit** checks for code style
- [ ] attended to any **TO-DOs** left in the code

#2550 Backport changes into core

Related work items: #2549, #2550
This commit is contained in:
Marek Wolan
2024-05-08 08:38:44 +00:00
9 changed files with 151 additions and 121 deletions

View File

@@ -45,15 +45,9 @@
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [],
"source": [ "source": [
"print(cfg['agents'][2]['agent_settings'])" "for agent in cfg['agents']:\n",
] " if agent[\"ref\"] == \"defender\":\n",
}, " agent['agent_settings']['flatten_obs'] = True\n",
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"env_config = cfg\n", "env_config = cfg\n",
"\n", "\n",
"config = (\n", "config = (\n",
@@ -80,7 +74,7 @@
"tune.Tuner(\n", "tune.Tuner(\n",
" \"PPO\",\n", " \"PPO\",\n",
" run_config=air.RunConfig(\n", " run_config=air.RunConfig(\n",
" stop={\"timesteps_total\": 512}\n", " stop={\"timesteps_total\": 1e3 * 128}\n",
" ),\n", " ),\n",
" param_space=config\n", " param_space=config\n",
").fit()\n" ").fit()\n"

View File

@@ -261,7 +261,7 @@
"name": "python", "name": "python",
"nbconvert_exporter": "python", "nbconvert_exporter": "python",
"pygments_lexer": "ipython3", "pygments_lexer": "ipython3",
"version": "3.10.11" "version": "3.10.12"
} }
}, },
"nbformat": 4, "nbformat": 4,

View File

@@ -256,9 +256,11 @@
{ {
"cell_type": "markdown", "cell_type": "markdown",
"id": "22", "id": "22",
"metadata": {}, "metadata": {
"tags": []
},
"source": [ "source": [
"Calling `switch.sys_log.show()` displays the Switch system log. By default, only the last 10 log entries are displayed, this can be changed by passing `last_n=<number of log entries>`." "Calling `switch.arp.show()` displays the Switch ARP Cache."
] ]
}, },
{ {
@@ -270,13 +272,33 @@
}, },
"outputs": [], "outputs": [],
"source": [ "source": [
"network.get_node_by_hostname(\"switch_1\").sys_log.show()" "network.get_node_by_hostname(\"switch_1\").arp.show()"
] ]
}, },
{ {
"cell_type": "markdown", "cell_type": "markdown",
"id": "24", "id": "24",
"metadata": {}, "metadata": {},
"source": [
"Calling `switch.sys_log.show()` displays the Switch system log. By default, only the last 10 log entries are displayed, this can be changed by passing `last_n=<number of log entries>`."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "25",
"metadata": {
"tags": []
},
"outputs": [],
"source": [
"network.get_node_by_hostname(\"switch_1\").sys_log.show()"
]
},
{
"cell_type": "markdown",
"id": "26",
"metadata": {},
"source": [ "source": [
"### Computer/Server Nodes\n", "### Computer/Server Nodes\n",
"\n", "\n",
@@ -285,7 +307,7 @@
}, },
{ {
"cell_type": "markdown", "cell_type": "markdown",
"id": "25", "id": "27",
"metadata": { "metadata": {
"tags": [] "tags": []
}, },
@@ -293,26 +315,6 @@
"Calling `computer.show()` displays the NICs on the Computer/Server." "Calling `computer.show()` displays the NICs on the Computer/Server."
] ]
}, },
{
"cell_type": "code",
"execution_count": null,
"id": "26",
"metadata": {
"tags": []
},
"outputs": [],
"source": [
"network.get_node_by_hostname(\"security_suite\").show()"
]
},
{
"cell_type": "markdown",
"id": "27",
"metadata": {},
"source": [
"Calling `computer.arp.show()` displays the Computer/Server ARP Cache."
]
},
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": null, "execution_count": null,
@@ -322,7 +324,7 @@
}, },
"outputs": [], "outputs": [],
"source": [ "source": [
"network.get_node_by_hostname(\"security_suite\").arp.show()" "network.get_node_by_hostname(\"security_suite\").show()"
] ]
}, },
{ {
@@ -330,7 +332,7 @@
"id": "29", "id": "29",
"metadata": {}, "metadata": {},
"source": [ "source": [
"Calling `computer.sys_log.show()` displays the Computer/Server system log. By default, only the last 10 log entries are displayed, this can be changed by passing `last_n=<number of log entries>`." "Calling `computer.arp.show()` displays the Computer/Server ARP Cache."
] ]
}, },
{ {
@@ -342,7 +344,7 @@
}, },
"outputs": [], "outputs": [],
"source": [ "source": [
"network.get_node_by_hostname(\"security_suite\").sys_log.show()" "network.get_node_by_hostname(\"security_suite\").arp.show()"
] ]
}, },
{ {
@@ -350,9 +352,7 @@
"id": "31", "id": "31",
"metadata": {}, "metadata": {},
"source": [ "source": [
"## Basic Network Comms Check\n", "Calling `computer.sys_log.show()` displays the Computer/Server system log. By default, only the last 10 log entries are displayed, this can be changed by passing `last_n=<number of log entries>`."
"\n",
"We can perform a good old ping to check that Nodes are able to communicate with each other."
] ]
}, },
{ {
@@ -364,7 +364,7 @@
}, },
"outputs": [], "outputs": [],
"source": [ "source": [
"network.show(nodes=False, links=False)" "network.get_node_by_hostname(\"security_suite\").sys_log.show()"
] ]
}, },
{ {
@@ -372,7 +372,9 @@
"id": "33", "id": "33",
"metadata": {}, "metadata": {},
"source": [ "source": [
"We'll first ping client_1's default gateway." "## Basic Network Comms Check\n",
"\n",
"We can perform a good old ping to check that Nodes are able to communicate with each other."
] ]
}, },
{ {
@@ -384,27 +386,27 @@
}, },
"outputs": [], "outputs": [],
"source": [ "source": [
"network.get_node_by_hostname(\"client_1\").ping(\"192.168.10.1\")" "network.show(nodes=False, links=False)"
]
},
{
"cell_type": "markdown",
"id": "35",
"metadata": {},
"source": [
"We'll first ping client_1's default gateway."
] ]
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": null, "execution_count": null,
"id": "35", "id": "36",
"metadata": { "metadata": {
"tags": [] "tags": []
}, },
"outputs": [], "outputs": [],
"source": [ "source": [
"network.get_node_by_hostname(\"client_1\").sys_log.show(15)" "network.get_node_by_hostname(\"client_1\").ping(\"192.168.10.1\")"
]
},
{
"cell_type": "markdown",
"id": "36",
"metadata": {},
"source": [
"Next, we'll ping the interface of the 192.168.1.0/24 Network on the Router (port 1)."
] ]
}, },
{ {
@@ -416,7 +418,7 @@
}, },
"outputs": [], "outputs": [],
"source": [ "source": [
"network.get_node_by_hostname(\"client_1\").ping(\"192.168.1.1\")" "network.get_node_by_hostname(\"client_1\").sys_log.show(15)"
] ]
}, },
{ {
@@ -424,7 +426,7 @@
"id": "38", "id": "38",
"metadata": {}, "metadata": {},
"source": [ "source": [
"And finally, we'll ping the web server." "Next, we'll ping the interface of the 192.168.1.0/24 Network on the Router (port 1)."
] ]
}, },
{ {
@@ -436,7 +438,7 @@
}, },
"outputs": [], "outputs": [],
"source": [ "source": [
"network.get_node_by_hostname(\"client_1\").ping(\"192.168.1.12\")" "network.get_node_by_hostname(\"client_1\").ping(\"192.168.1.1\")"
] ]
}, },
{ {
@@ -444,7 +446,7 @@
"id": "40", "id": "40",
"metadata": {}, "metadata": {},
"source": [ "source": [
"To confirm that the ping was received and processed by the web_server, we can view the sys log" "And finally, we'll ping the web server."
] ]
}, },
{ {
@@ -456,45 +458,45 @@
}, },
"outputs": [], "outputs": [],
"source": [ "source": [
"network.get_node_by_hostname(\"web_server\").sys_log.show()" "network.get_node_by_hostname(\"client_1\").ping(\"192.168.1.12\")"
] ]
}, },
{ {
"cell_type": "markdown", "cell_type": "markdown",
"id": "42", "id": "42",
"metadata": {}, "metadata": {},
"source": [
"To confirm that the ping was received and processed by the web_server, we can view the sys log"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "43",
"metadata": {
"tags": []
},
"outputs": [],
"source": [
"network.get_node_by_hostname(\"web_server\").sys_log.show()"
]
},
{
"cell_type": "markdown",
"id": "44",
"metadata": {},
"source": [ "source": [
"## Advanced Network Usage\n", "## Advanced Network Usage\n",
"\n", "\n",
"We can now use the Network to perform some more advanced things." "We can now use the Network to perform some more advanced things."
] ]
}, },
{
"cell_type": "markdown",
"id": "43",
"metadata": {},
"source": [
"Let's attempt to prevent client_2 from being able to ping the web server. First, we'll confirm that it can ping the server first..."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "44",
"metadata": {
"tags": []
},
"outputs": [],
"source": [
"network.get_node_by_hostname(\"client_2\").ping(\"192.168.1.12\")"
]
},
{ {
"cell_type": "markdown", "cell_type": "markdown",
"id": "45", "id": "45",
"metadata": {}, "metadata": {},
"source": [ "source": [
"If we look at the client_2 sys log we can see that the four ICMP echo requests were sent and four ICMP each replies were received:" "Let's attempt to prevent client_2 from being able to ping the web server. First, we'll confirm that it can ping the server first..."
] ]
}, },
{ {
@@ -506,13 +508,33 @@
}, },
"outputs": [], "outputs": [],
"source": [ "source": [
"network.get_node_by_hostname(\"client_2\").sys_log.show()" "network.get_node_by_hostname(\"client_2\").ping(\"192.168.1.12\")"
] ]
}, },
{ {
"cell_type": "markdown", "cell_type": "markdown",
"id": "47", "id": "47",
"metadata": {}, "metadata": {},
"source": [
"If we look at the client_2 sys log we can see that the four ICMP echo requests were sent and four ICMP each replies were received:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "48",
"metadata": {
"tags": []
},
"outputs": [],
"source": [
"network.get_node_by_hostname(\"client_2\").sys_log.show()"
]
},
{
"cell_type": "markdown",
"id": "49",
"metadata": {},
"source": [ "source": [
"Now we'll add an ACL to block ICMP from 192.168.10.22" "Now we'll add an ACL to block ICMP from 192.168.10.22"
] ]
@@ -520,7 +542,7 @@
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": null, "execution_count": null,
"id": "48", "id": "50",
"metadata": { "metadata": {
"tags": [] "tags": []
}, },
@@ -540,7 +562,7 @@
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": null, "execution_count": null,
"id": "49", "id": "51",
"metadata": { "metadata": {
"tags": [] "tags": []
}, },
@@ -549,32 +571,12 @@
"network.get_node_by_hostname(\"router_1\").acl.show()" "network.get_node_by_hostname(\"router_1\").acl.show()"
] ]
}, },
{
"cell_type": "markdown",
"id": "50",
"metadata": {},
"source": [
"Now we attempt (and fail) to ping the web server"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "51",
"metadata": {
"tags": []
},
"outputs": [],
"source": [
"network.get_node_by_hostname(\"client_2\").ping(\"192.168.1.12\")"
]
},
{ {
"cell_type": "markdown", "cell_type": "markdown",
"id": "52", "id": "52",
"metadata": {}, "metadata": {},
"source": [ "source": [
"We can check that the ping was actually sent by client_2 by viewing the sys log" "Now we attempt (and fail) to ping the web server"
] ]
}, },
{ {
@@ -586,7 +588,7 @@
}, },
"outputs": [], "outputs": [],
"source": [ "source": [
"network.get_node_by_hostname(\"client_2\").sys_log.show()" "network.get_node_by_hostname(\"client_2\").ping(\"192.168.1.12\")"
] ]
}, },
{ {
@@ -594,7 +596,7 @@
"id": "54", "id": "54",
"metadata": {}, "metadata": {},
"source": [ "source": [
"We can check the router sys log to see why the traffic was blocked" "We can check that the ping was actually sent by client_2 by viewing the sys log"
] ]
}, },
{ {
@@ -606,7 +608,7 @@
}, },
"outputs": [], "outputs": [],
"source": [ "source": [
"network.get_node_by_hostname(\"router_1\").sys_log.show()" "network.get_node_by_hostname(\"client_2\").sys_log.show()"
] ]
}, },
{ {
@@ -614,7 +616,7 @@
"id": "56", "id": "56",
"metadata": {}, "metadata": {},
"source": [ "source": [
"Now a final check to ensure that client_1 can still ping the web_server." "We can check the router sys log to see why the traffic was blocked"
] ]
}, },
{ {
@@ -625,6 +627,26 @@
"tags": [] "tags": []
}, },
"outputs": [], "outputs": [],
"source": [
"network.get_node_by_hostname(\"router_1\").sys_log.show()"
]
},
{
"cell_type": "markdown",
"id": "58",
"metadata": {},
"source": [
"Now a final check to ensure that client_1 can still ping the web_server."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "59",
"metadata": {
"tags": []
},
"outputs": [],
"source": [ "source": [
"network.get_node_by_hostname(\"client_1\").ping(\"192.168.1.12\")" "network.get_node_by_hostname(\"client_1\").ping(\"192.168.1.12\")"
] ]
@@ -632,7 +654,7 @@
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": null, "execution_count": null,
"id": "58", "id": "60",
"metadata": { "metadata": {
"tags": [] "tags": []
}, },

View File

@@ -192,7 +192,6 @@ class WirelessNetworkInterface(NetworkInterface, ABC):
# Cannot send Frame as the network interface is not enabled # Cannot send Frame as the network interface is not enabled
return False return False
@abstractmethod
def receive_frame(self, frame: Frame) -> bool: def receive_frame(self, frame: Frame) -> bool:
""" """
Receives a network frame on the network interface. Receives a network frame on the network interface.
@@ -200,7 +199,13 @@ class WirelessNetworkInterface(NetworkInterface, ABC):
:param frame: The network frame being received. :param frame: The network frame being received.
:return: A boolean indicating whether the frame was successfully received. :return: A boolean indicating whether the frame was successfully received.
""" """
pass if self.enabled:
frame.set_sent_timestamp()
self.pcap.capture_inbound(frame)
self._connected_node.receive_frame(frame, self)
return True
# Cannot receive Frame as the network interface is not enabled
return False
class IPWirelessNetworkInterface(WirelessNetworkInterface, Layer3Interface, ABC): class IPWirelessNetworkInterface(WirelessNetworkInterface, Layer3Interface, ABC):

View File

@@ -1378,7 +1378,7 @@ class Node(SimComponent):
application_instance.configure(server_ip_address=IPv4Address(ip_address)) application_instance.configure(server_ip_address=IPv4Address(ip_address))
else: else:
pass pass
application_instance.install()
if application_instance.name in self.software_manager.software: if application_instance.name in self.software_manager.software:
return True return True
else: else:

View File

@@ -1,6 +1,6 @@
from abc import abstractmethod from abc import abstractmethod
from enum import Enum from enum import Enum
from typing import Any, Dict, Set from typing import Any, Dict, Optional, Set
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
@@ -33,6 +33,10 @@ class Application(IOSoftware):
"The number of times the application has been executed. Default is 0." "The number of times the application has been executed. Default is 0."
groups: Set[str] = set() groups: Set[str] = set()
"The set of groups to which the application belongs." "The set of groups to which the application belongs."
install_duration: int = 2
"How long it takes to install the application."
install_countdown: Optional[int] = None
"The countdown to the end of the installation process. None if not currently installing"
def __init__(self, **kwargs): def __init__(self, **kwargs):
super().__init__(**kwargs) super().__init__(**kwargs)
@@ -76,6 +80,12 @@ class Application(IOSoftware):
:param timestep: The current timestep of the simulation. :param timestep: The current timestep of the simulation.
""" """
super().apply_timestep(timestep=timestep) super().apply_timestep(timestep=timestep)
if self.operating_state is ApplicationOperatingState.INSTALLING:
self.install_countdown -= 1
if self.install_countdown <= 0:
self.operating_state = ApplicationOperatingState.RUNNING
self.health_state_actual = SoftwareHealthState.GOOD
self.install_countdown = None
def pre_timestep(self, timestep: int) -> None: def pre_timestep(self, timestep: int) -> None:
"""Apply pre-timestep logic.""" """Apply pre-timestep logic."""
@@ -129,6 +139,7 @@ class Application(IOSoftware):
super().install() super().install()
if self.operating_state == ApplicationOperatingState.CLOSED: if self.operating_state == ApplicationOperatingState.CLOSED:
self.operating_state = ApplicationOperatingState.INSTALLING self.operating_state = ApplicationOperatingState.INSTALLING
self.install_countdown = self.install_duration
def receive(self, payload: Any, session_id: str, **kwargs) -> bool: def receive(self, payload: Any, session_id: str, **kwargs) -> bool:
""" """

View File

@@ -177,4 +177,5 @@ class DoSBot(DatabaseClient):
:param timestep: The timestep value to update the bot's state. :param timestep: The timestep value to update the bot's state.
""" """
super().apply_timestep(timestep=timestep)
self._application_loop() self._application_loop()

View File

@@ -118,14 +118,6 @@ class RansomwareScript(Application):
self.sys_log.info(f"{self.name}: Activated!") self.sys_log.info(f"{self.name}: Activated!")
self.attack_stage = RansomwareAttackStage.ACTIVATE self.attack_stage = RansomwareAttackStage.ACTIVATE
def apply_timestep(self, timestep: int) -> None:
"""
Apply a timestep to the bot, triggering the application loop.
:param timestep: The timestep value to update the bot's state.
"""
pass
def run(self) -> bool: def run(self) -> bool:
"""Calls the parent classes execute method before starting the application loop.""" """Calls the parent classes execute method before starting the application loop."""
super().run() super().run()

View File

@@ -62,6 +62,11 @@ def test_application_install_uninstall_on_uc2():
_, _, _, _, info = env.step(78) _, _, _, _, info = env.step(78)
assert "DoSBot" in domcon.software_manager.software assert "DoSBot" in domcon.software_manager.software
# installing takes 3 steps so let's wait for 3 steps
env.step(0)
env.step(0)
env.step(0)
# Test we can now execute the DoSBot app # Test we can now execute the DoSBot app
_, _, _, _, info = env.step(81) _, _, _, _, info = env.step(81)
assert info["agent_actions"]["defender"].response.status == "success" assert info["agent_actions"]["defender"].response.status == "success"