Merged PR 192: Implement missing actions
## Summary #1947 Implementation of actions from the QTSL-820-2450 - ARCD Track 2 Common Interface Definition for Environments UC2 document Covers: - [X] BAS-12 NODE SERVICE SCAN - [X] BAS-15 NODE SERVICE STOP - [X] BAS-16 NODE SERVICE START - [X] BAS-17 NODE SERVICE PAUSE - [X] BAS-18 NODE SERVICE RESUME - [X] BAS-19 NODE SERVICE RESTART - [X] BAS-20 NODE SERVICE DISABLE - [X] BAS-21 NODE SERVICE ENABLE - [X] BAS-29 NODE FILE SCAN - [X] BAS-30 NODE FILE CHECKHASH - [X] BAS-33 NODE FILE DELETE - [X] BAS-34 NODE FILE REPAIR - [X] BAS-38 NODE FOLDER SCAN - [X] BAS-39 NODE FOLDER CHECKHASH - [X] BAS-41 NODE FOLDER REPAIR - [X] BAS-44 NODE SHUTDOWN - [X] BAS-45 NODE STARTUP - [X] RAS-30 NODE FILE DELETE - [X] RAS-31 NODE FILE CORRUPT - [X] RAS-36 NODE FOLDER DELETE - [X] RAS-37 NODE FOLDER CORRUPT ## Test process ### node actions test https://dev.azure.com/ma-dev-uk/PrimAITE/_git/PrimAITE/pullrequest/192?_a=files&path=/tests/unit_tests/_primaite/_simulator/_network/_hardware/test_node_actions.py ### service actions https://dev.azure.com/ma-dev-uk/PrimAITE/_git/PrimAITE/pullrequest/192?_a=files&path=/tests/unit_tests/_primaite/_simulator/_system/_services/test_service_actions.py ### file/folder actions https://dev.azure.com/ma-dev-uk/PrimAITE/_git/PrimAITE/pullrequest/192?_a=files&path=/tests/unit_tests/_primaite/_simulator/_file_system/test_file_system_actions.py ## Checklist - [X] PR is linked to a **work item** - [X] **acceptance criteria** of linked ticket are met - [X] performed **self-review** of the code - [X] 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** - [X] ran **pre-commit** checks for code style - [X] attended to any **TO-DOs** left in the code Related work items: #1947
This commit is contained in:
@@ -27,7 +27,7 @@ Just like other aspects of SimComponent, the actions are not managed centrally f
|
|||||||
4. ``Service`` receives ``['restart']``.
|
4. ``Service`` receives ``['restart']``.
|
||||||
Since ``restart`` is a defined action in the service's own RequestManager, the service performs a restart.
|
Since ``restart`` is a defined action in the service's own RequestManager, the service performs a restart.
|
||||||
|
|
||||||
Techincal Detail
|
Technical Detail
|
||||||
================
|
================
|
||||||
|
|
||||||
This system was achieved by implementing two classes, :py:class:`primaite.simulator.core.Action`, and :py:class:`primaite.simulator.core.RequestManager`.
|
This system was achieved by implementing two classes, :py:class:`primaite.simulator.core.Action`, and :py:class:`primaite.simulator.core.RequestManager`.
|
||||||
@@ -35,12 +35,12 @@ This system was achieved by implementing two classes, :py:class:`primaite.simula
|
|||||||
Action
|
Action
|
||||||
------
|
------
|
||||||
|
|
||||||
The ``Action`` object stores a reference to a method that performs the action, for example a node could have an action that stores a reference to ``self.turn_on()``. Techincally, this can be any callable that accepts `request, context` as it's parameters. In practice, this is often defined using ``lambda`` functions within a component's ``self._init_request_manager()`` method. Optionally, the ``Action`` object can also hold a validator that will permit/deny the action depending on context.
|
The ``Action`` object stores a reference to a method that performs the action, for example a node could have an action that stores a reference to ``self.turn_on()``. Technically, this can be any callable that accepts `request, context` as it's parameters. In practice, this is often defined using ``lambda`` functions within a component's ``self._init_request_manager()`` method. Optionally, the ``Action`` object can also hold a validator that will permit/deny the action depending on context.
|
||||||
|
|
||||||
RequestManager
|
RequestManager
|
||||||
-------------
|
-------------
|
||||||
|
|
||||||
The ``RequestManager`` object stores a mapping between strings and actions. It is responsible for processing the ``request`` and passing it down the ownership tree. Techincally, the ``RequestManager`` is itself a callable that accepts `request, context` tuple, and so it can be chained with other action managers.
|
The ``RequestManager`` object stores a mapping between strings and actions. It is responsible for processing the ``request`` and passing it down the ownership tree. Technically, the ``RequestManager`` is itself a callable that accepts `request, context` tuple, and so it can be chained with other action managers.
|
||||||
|
|
||||||
A simple example without chaining can be seen in the :py:class:`primaite.simulator.file_system.file_system.File` class.
|
A simple example without chaining can be seen in the :py:class:`primaite.simulator.file_system.file_system.File` class.
|
||||||
|
|
||||||
@@ -50,9 +50,9 @@ A simple example without chaining can be seen in the :py:class:`primaite.simulat
|
|||||||
...
|
...
|
||||||
def _init_request_manager(self):
|
def _init_request_manager(self):
|
||||||
...
|
...
|
||||||
request_manager.add_action("scan", Action(func=lambda request, context: self.scan()))
|
request_manager.add_request("scan", Action(func=lambda request, context: self.scan()))
|
||||||
request_manager.add_action("repair", Action(func=lambda request, context: self.repair()))
|
request_manager.add_request("repair", Action(func=lambda request, context: self.repair()))
|
||||||
request_manager.add_action("restore", Action(func=lambda request, context: self.restore()))
|
request_manager.add_request("restore", Action(func=lambda request, context: self.restore()))
|
||||||
|
|
||||||
*ellipses (``...``) used to omit code impertinent to this explanation*
|
*ellipses (``...``) used to omit code impertinent to this explanation*
|
||||||
|
|
||||||
@@ -70,7 +70,7 @@ An example of how this works is in the :py:class:`primaite.simulator.network.har
|
|||||||
def _init_request_manager(self):
|
def _init_request_manager(self):
|
||||||
...
|
...
|
||||||
# a regular action which is processed by the Node itself
|
# a regular action which is processed by the Node itself
|
||||||
request_manager.add_action("turn_on", Action(func=lambda request, context: self.turn_on()))
|
request_manager.add_request("turn_on", Action(func=lambda request, context: self.turn_on()))
|
||||||
|
|
||||||
# if the Node receives a request where the first word is 'service', it will use a dummy manager
|
# if the Node receives a request where the first word is 'service', it will use a dummy manager
|
||||||
# called self._service_request_manager to pass on the reqeust to the relevant service. This dummy
|
# called self._service_request_manager to pass on the reqeust to the relevant service. This dummy
|
||||||
@@ -78,11 +78,11 @@ An example of how this works is in the :py:class:`primaite.simulator.network.har
|
|||||||
# done because the next string after "service" is always the uuid of that service, so we need an
|
# done because the next string after "service" is always the uuid of that service, so we need an
|
||||||
# RequestManager to pop that string before sending it onto the relevant service's RequestManager.
|
# RequestManager to pop that string before sending it onto the relevant service's RequestManager.
|
||||||
self._service_request_manager = RequestManager()
|
self._service_request_manager = RequestManager()
|
||||||
request_manager.add_action("service", Action(func=self._service_request_manager))
|
request_manager.add_request("service", Action(func=self._service_request_manager))
|
||||||
...
|
...
|
||||||
|
|
||||||
def install_service(self, service):
|
def install_service(self, service):
|
||||||
self.services[service.uuid] = service
|
self.services[service.uuid] = service
|
||||||
...
|
...
|
||||||
# Here, the service UUID is registered to allow passing actions between the node and the service.
|
# Here, the service UUID is registered to allow passing actions between the node and the service.
|
||||||
self._service_request_manager.add_action(service.uuid, Action(func=service._request_manager))
|
self._service_request_manager.add_request(service.uuid, Action(func=service._request_manager))
|
||||||
|
|||||||
@@ -110,11 +110,9 @@ Clone & Install PrimAITE for Development
|
|||||||
To be able to extend PrimAITE further, or to build wheels manually before install, clone the repository to a location
|
To be able to extend PrimAITE further, or to build wheels manually before install, clone the repository to a location
|
||||||
of your choice:
|
of your choice:
|
||||||
|
|
||||||
.. TODO:: Add repo path once we know what it is
|
|
||||||
|
|
||||||
.. code-block:: bash
|
.. code-block:: bash
|
||||||
|
|
||||||
git clone <repo path>
|
git clone https://github.com/Autonomous-Resilient-Cyber-Defence/PrimAITE
|
||||||
cd primaite
|
cd primaite
|
||||||
|
|
||||||
Create and activate your Python virtual environment (venv)
|
Create and activate your Python virtual environment (venv)
|
||||||
|
|||||||
@@ -2,20 +2,24 @@
|
|||||||
|
|
||||||
© Crown-owned copyright 2023, Defence Science and Technology Laboratory UK
|
© Crown-owned copyright 2023, Defence Science and Technology Laboratory UK
|
||||||
|
|
||||||
|
#############
|
||||||
Base Hardware
|
Base Hardware
|
||||||
=============
|
#############
|
||||||
|
|
||||||
The physical layer components are models of a NIC (Network Interface Card), SwitchPort, Node, Switch, and a Link.
|
The physical layer components are models of a NIC (Network Interface Card), SwitchPort, Node, Switch, and a Link.
|
||||||
These components allow modelling of layer 1 (physical layer) in the OSI model and the nodes that connect to and
|
These components allow modelling of layer 1 (physical layer) in the OSI model and the nodes that connect to and
|
||||||
transmit across layer 1.
|
transmit across layer 1.
|
||||||
|
|
||||||
|
===
|
||||||
NIC
|
NIC
|
||||||
###
|
===
|
||||||
|
|
||||||
The NIC class provides a realistic model of a Network Interface Card. The NIC acts as the interface between a Node and
|
The NIC class provides a realistic model of a Network Interface Card. The NIC acts as the interface between a Node and
|
||||||
a Link, handling IP and MAC addressing, status, and sending/receiving frames.
|
a Link, handling IP and MAC addressing, status, and sending/receiving frames.
|
||||||
|
|
||||||
|
----------
|
||||||
Addressing
|
Addressing
|
||||||
**********
|
----------
|
||||||
|
|
||||||
A NIC has both an IPv4 address and MAC address assigned:
|
A NIC has both an IPv4 address and MAC address assigned:
|
||||||
|
|
||||||
@@ -24,8 +28,10 @@ A NIC has both an IPv4 address and MAC address assigned:
|
|||||||
- **gateway** - The default gateway IP address for routing traffic beyond the local network.
|
- **gateway** - The default gateway IP address for routing traffic beyond the local network.
|
||||||
- **mac_address** - A unique MAC address assigned to the NIC by the manufacturer.
|
- **mac_address** - A unique MAC address assigned to the NIC by the manufacturer.
|
||||||
|
|
||||||
|
|
||||||
|
------
|
||||||
Status
|
Status
|
||||||
******
|
------
|
||||||
|
|
||||||
The status of the NIC is represented by:
|
The status of the NIC is represented by:
|
||||||
|
|
||||||
@@ -33,14 +39,17 @@ The status of the NIC is represented by:
|
|||||||
- **connected_node** - The Node instance the NIC is attached to.
|
- **connected_node** - The Node instance the NIC is attached to.
|
||||||
- **connected_link** - The Link instance the NIC is wired to.
|
- **connected_link** - The Link instance the NIC is wired to.
|
||||||
|
|
||||||
|
|
||||||
|
--------------
|
||||||
Packet Capture
|
Packet Capture
|
||||||
**************
|
--------------
|
||||||
|
|
||||||
- **pcap** - A PacketCapture instance attached to the NIC for capturing all frames sent and received. This allows packet
|
- **pcap** - A PacketCapture instance attached to the NIC for capturing all frames sent and received. This allows packet
|
||||||
capture and analysis.
|
capture and analysis.
|
||||||
|
|
||||||
|
------------------------
|
||||||
Sending/Receiving Frames
|
Sending/Receiving Frames
|
||||||
************************
|
------------------------
|
||||||
|
|
||||||
The NIC can send and receive Frames to/from the connected Link:
|
The NIC can send and receive Frames to/from the connected Link:
|
||||||
|
|
||||||
@@ -50,8 +59,9 @@ The NIC can send and receive Frames to/from the connected Link:
|
|||||||
This allows a NIC to handle sending, receiving, and forwarding of network traffic at layer 2 of the OSI model.
|
This allows a NIC to handle sending, receiving, and forwarding of network traffic at layer 2 of the OSI model.
|
||||||
The Frames contain network data encapsulated with various protocol headers.
|
The Frames contain network data encapsulated with various protocol headers.
|
||||||
|
|
||||||
|
-----------
|
||||||
Basic Usage
|
Basic Usage
|
||||||
***********
|
-----------
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
@@ -64,8 +74,9 @@ Basic Usage
|
|||||||
frame = Frame(...)
|
frame = Frame(...)
|
||||||
nic1.send_frame(frame)
|
nic1.send_frame(frame)
|
||||||
|
|
||||||
|
==========
|
||||||
SwitchPort
|
SwitchPort
|
||||||
##########
|
==========
|
||||||
|
|
||||||
The SwitchPort models a port on a network switch. It has similar attributes and methods to NIC for addressing, status,
|
The SwitchPort models a port on a network switch. It has similar attributes and methods to NIC for addressing, status,
|
||||||
packet capture, sending/receiving frames, etc.
|
packet capture, sending/receiving frames, etc.
|
||||||
@@ -75,26 +86,47 @@ Key attributes:
|
|||||||
- **port_num**: The port number on the switch.
|
- **port_num**: The port number on the switch.
|
||||||
- **connected_switch**: The switch to which this port belongs.
|
- **connected_switch**: The switch to which this port belongs.
|
||||||
|
|
||||||
|
====
|
||||||
Node
|
Node
|
||||||
####
|
====
|
||||||
|
|
||||||
The Node class represents a base node that communicates on the Network.
|
The Node class represents a base node that communicates on the Network.
|
||||||
|
|
||||||
|
Nodes take more than 1 time step to power on (3 time steps by default).
|
||||||
|
To create a Node that is already powered on, the Node's operating state can be overriden.
|
||||||
|
Otherwise, the node ``start_up_duration`` (and ``shut_down_duration``) can be set to 0 if
|
||||||
|
the node will be powered off or on multiple times. This will still need ``power_on()`` to
|
||||||
|
be called to turn the node on.
|
||||||
|
|
||||||
|
e.g.
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
active_node = Node(hostname='server1', operating_state=NodeOperatingState.ON)
|
||||||
|
# node is already on, no need to call power_on()
|
||||||
|
|
||||||
|
|
||||||
|
instant_start_node = Node(hostname="client", start_up_duration=0, shut_down_duration=0)
|
||||||
|
instant_start_node.power_on() # node will still need to be powered on
|
||||||
|
|
||||||
|
------------------
|
||||||
Network Interfaces
|
Network Interfaces
|
||||||
******************
|
------------------
|
||||||
|
|
||||||
A Node will typically have one or more NICs attached to it for network connectivity:
|
A Node will typically have one or more NICs attached to it for network connectivity:
|
||||||
|
|
||||||
- **nics** - A dictionary containing the NIC instances attached to the Node. NICs can be added/removed.
|
- **nics** - A dictionary containing the NIC instances attached to the Node. NICs can be added/removed.
|
||||||
|
|
||||||
|
-------------
|
||||||
Configuration
|
Configuration
|
||||||
*************
|
-------------
|
||||||
|
|
||||||
- **hostname** - Configured hostname of the Node.
|
- **hostname** - Configured hostname of the Node.
|
||||||
- **operating_state** - Current operating state like ON or OFF. The NICs will be enabled/disabled based on this.
|
- **operating_state** - Current operating state like ON or OFF. The NICs will be enabled/disabled based on this.
|
||||||
|
|
||||||
|
----------------
|
||||||
Network Services
|
Network Services
|
||||||
****************
|
----------------
|
||||||
|
|
||||||
A Node runs various network services and components for handling traffic:
|
A Node runs various network services and components for handling traffic:
|
||||||
|
|
||||||
@@ -110,8 +142,9 @@ The SysLog records informational, warning, and error events that occur on the No
|
|||||||
debugging and tracing program execution and network activity for each simulated Node. Other Node services like ARP and
|
debugging and tracing program execution and network activity for each simulated Node. Other Node services like ARP and
|
||||||
ICMP, along with custom Applications, services, and Processes will log to the SysLog.
|
ICMP, along with custom Applications, services, and Processes will log to the SysLog.
|
||||||
|
|
||||||
|
-----------------
|
||||||
Sending/Receiving
|
Sending/Receiving
|
||||||
*****************
|
-----------------
|
||||||
|
|
||||||
The Node handles sending and receiving Frames via its attached NICs:
|
The Node handles sending and receiving Frames via its attached NICs:
|
||||||
|
|
||||||
@@ -119,8 +152,9 @@ The Node handles sending and receiving Frames via its attached NICs:
|
|||||||
- **receive_frame()** - Receives a Frame from the network through a NIC. The Node then processes it appropriately based
|
- **receive_frame()** - Receives a Frame from the network through a NIC. The Node then processes it appropriately based
|
||||||
on the protocols and payload.
|
on the protocols and payload.
|
||||||
|
|
||||||
|
-----------
|
||||||
Basic Usage
|
Basic Usage
|
||||||
***********
|
-----------
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
@@ -137,15 +171,16 @@ Basic Usage
|
|||||||
The Node class brings together the NICs, configuration, and services to model a full network node that can send,
|
The Node class brings together the NICs, configuration, and services to model a full network node that can send,
|
||||||
receive, process, and forward traffic on a simulated network.
|
receive, process, and forward traffic on a simulated network.
|
||||||
|
|
||||||
|
======
|
||||||
Switch
|
Switch
|
||||||
######
|
======
|
||||||
|
|
||||||
The Switch subclass models a network switch. It inherits from Node and acts at layer 2 of the OSI model to forward
|
The Switch subclass models a network switch. It inherits from Node and acts at layer 2 of the OSI model to forward
|
||||||
frames based on MAC addresses.
|
frames based on MAC addresses.
|
||||||
|
|
||||||
|
--------------------------
|
||||||
Inherits Node Capabilities
|
Inherits Node Capabilities
|
||||||
**************************
|
--------------------------
|
||||||
|
|
||||||
Since Switch subclasses Node, it inherits all capabilities from Node like:
|
Since Switch subclasses Node, it inherits all capabilities from Node like:
|
||||||
|
|
||||||
@@ -154,16 +189,18 @@ Since Switch subclasses Node, it inherits all capabilities from Node like:
|
|||||||
- **Sending and receiving frames**
|
- **Sending and receiving frames**
|
||||||
- **Maintaining system logs**
|
- **Maintaining system logs**
|
||||||
|
|
||||||
|
-----
|
||||||
Ports
|
Ports
|
||||||
*****
|
-----
|
||||||
|
|
||||||
A Switch has multiple ports implemented using SwitchPort instances:
|
A Switch has multiple ports implemented using SwitchPort instances:
|
||||||
|
|
||||||
- **switch_ports** - A dictionary mapping port numbers to SwitchPort instances.
|
- **switch_ports** - A dictionary mapping port numbers to SwitchPort instances.
|
||||||
- **num_ports** - The number of ports the Switch has.
|
- **num_ports** - The number of ports the Switch has.
|
||||||
|
|
||||||
|
----------
|
||||||
Forwarding
|
Forwarding
|
||||||
**********
|
----------
|
||||||
|
|
||||||
A Switch forwards frames between ports based on the destination MAC:
|
A Switch forwards frames between ports based on the destination MAC:
|
||||||
|
|
||||||
@@ -179,21 +216,24 @@ When a frame is received on a SwitchPort:
|
|||||||
This allows the Switch to dynamically build up a mapping table between MAC addresses and SwitchPorts based on traffic
|
This allows the Switch to dynamically build up a mapping table between MAC addresses and SwitchPorts based on traffic
|
||||||
received. If no entry exists for a destination MAC, it floods the frame out all ports.
|
received. If no entry exists for a destination MAC, it floods the frame out all ports.
|
||||||
|
|
||||||
|
====
|
||||||
Link
|
Link
|
||||||
####
|
====
|
||||||
|
|
||||||
The Link class represents a physical link or connection between two network endpoints like NICs or SwitchPorts.
|
The Link class represents a physical link or connection between two network endpoints like NICs or SwitchPorts.
|
||||||
|
|
||||||
|
---------
|
||||||
Endpoints
|
Endpoints
|
||||||
*********
|
---------
|
||||||
|
|
||||||
A Link connects two endpoints:
|
A Link connects two endpoints:
|
||||||
|
|
||||||
- **endpoint_a** - The first endpoint, a NIC or SwitchPort.
|
- **endpoint_a** - The first endpoint, a NIC or SwitchPort.
|
||||||
- **endpoint_b** - The second endpoint, a NIC or SwitchPort.
|
- **endpoint_b** - The second endpoint, a NIC or SwitchPort.
|
||||||
|
|
||||||
|
------------
|
||||||
Transmission
|
Transmission
|
||||||
************
|
------------
|
||||||
|
|
||||||
Links transmit Frames between the endpoints:
|
Links transmit Frames between the endpoints:
|
||||||
|
|
||||||
@@ -201,8 +241,9 @@ Links transmit Frames between the endpoints:
|
|||||||
|
|
||||||
Uses bandwidth/load properties to determine if transmission is possible.
|
Uses bandwidth/load properties to determine if transmission is possible.
|
||||||
|
|
||||||
|
----------------
|
||||||
Bandwidth & Load
|
Bandwidth & Load
|
||||||
****************
|
----------------
|
||||||
|
|
||||||
- **bandwidth** - The total capacity of the Link in Mbps.
|
- **bandwidth** - The total capacity of the Link in Mbps.
|
||||||
- **current_load** - The current bandwidth utilization of the Link in Mbps.
|
- **current_load** - The current bandwidth utilization of the Link in Mbps.
|
||||||
@@ -210,16 +251,18 @@ Bandwidth & Load
|
|||||||
As Frames are sent over the Link, the load increases. The Link tracks if there is enough unused capacity to transmit a
|
As Frames are sent over the Link, the load increases. The Link tracks if there is enough unused capacity to transmit a
|
||||||
Frame based on its size and the current load.
|
Frame based on its size and the current load.
|
||||||
|
|
||||||
|
------
|
||||||
Status
|
Status
|
||||||
******
|
------
|
||||||
|
|
||||||
- **up** - Boolean indicating if the Link is currently up/active based on the endpoint status.
|
- **up** - Boolean indicating if the Link is currently up/active based on the endpoint status.
|
||||||
- **endpoint_up()/down()** - Notifies the Link when an endpoint goes up or down.
|
- **endpoint_up()/down()** - Notifies the Link when an endpoint goes up or down.
|
||||||
|
|
||||||
This allows the Link to realistically model the connection and transmission characteristics between two endpoints.
|
This allows the Link to realistically model the connection and transmission characteristics between two endpoints.
|
||||||
|
|
||||||
|
=======================
|
||||||
Putting it all Together
|
Putting it all Together
|
||||||
#######################
|
=======================
|
||||||
|
|
||||||
We'll now demonstrate how the nodes, NICs, switches, and links connect in a network, including full code examples and
|
We'll now demonstrate how the nodes, NICs, switches, and links connect in a network, including full code examples and
|
||||||
syslog extracts to illustrate the step-by-step process.
|
syslog extracts to illustrate the step-by-step process.
|
||||||
@@ -230,35 +273,33 @@ PC's and two switches.
|
|||||||
|
|
||||||
.. image:: ../../../_static/four_node_two_switch_network.png
|
.. image:: ../../../_static/four_node_two_switch_network.png
|
||||||
|
|
||||||
|
-------------------
|
||||||
Create Nodes & NICs
|
Create Nodes & NICs
|
||||||
*******************
|
-------------------
|
||||||
|
|
||||||
First, we'll create the four nodes, each with a single NIC.
|
First, we'll create the four nodes, each with a single NIC.
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
pc_a = Node(hostname="pc_a")
|
from primaite.simulator.network.hardware.base import Node, NodeOperatingState, NIC
|
||||||
|
|
||||||
|
pc_a = Node(hostname="pc_a", operating_state=NodeOperatingState.ON)
|
||||||
nic_a = NIC(ip_address="192.168.0.10", subnet_mask="255.255.255.0", gateway="192.168.0.1")
|
nic_a = NIC(ip_address="192.168.0.10", subnet_mask="255.255.255.0", gateway="192.168.0.1")
|
||||||
pc_a.connect_nic(nic_a)
|
pc_a.connect_nic(nic_a)
|
||||||
pc_a.power_on()
|
|
||||||
|
|
||||||
pc_b = Node(hostname="pc_b")
|
pc_b = Node(hostname="pc_b", operating_state=NodeOperatingState.ON)
|
||||||
nic_b = NIC(ip_address="192.168.0.11", subnet_mask="255.255.255.0", gateway="192.168.0.1")
|
nic_b = NIC(ip_address="192.168.0.11", subnet_mask="255.255.255.0", gateway="192.168.0.1")
|
||||||
pc_b.connect_nic(nic_b)
|
pc_b.connect_nic(nic_b)
|
||||||
pc_b.power_on()
|
|
||||||
|
|
||||||
pc_c = Node(hostname="pc_c")
|
pc_c = Node(hostname="pc_c", operating_state=NodeOperatingState.ON)
|
||||||
nic_c = NIC(ip_address="192.168.0.12", subnet_mask="255.255.255.0", gateway="192.168.0.1")
|
nic_c = NIC(ip_address="192.168.0.12", subnet_mask="255.255.255.0", gateway="192.168.0.1")
|
||||||
pc_c.connect_nic(nic_c)
|
pc_c.connect_nic(nic_c)
|
||||||
pc_c.power_on()
|
|
||||||
|
|
||||||
pc_d = Node(hostname="pc_d")
|
pc_d = Node(hostname="pc_d", operating_state=NodeOperatingState.ON)
|
||||||
nic_d = NIC(ip_address="192.168.0.13", subnet_mask="255.255.255.0", gateway="192.168.0.1")
|
nic_d = NIC(ip_address="192.168.0.13", subnet_mask="255.255.255.0", gateway="192.168.0.1")
|
||||||
pc_d.connect_nic(nic_d)
|
pc_d.connect_nic(nic_d)
|
||||||
pc_d.power_on()
|
|
||||||
|
|
||||||
|
Creating the four nodes results in:
|
||||||
This produces:
|
|
||||||
|
|
||||||
**node_a NIC table**
|
**node_a NIC table**
|
||||||
|
|
||||||
@@ -273,7 +314,6 @@ This produces:
|
|||||||
.. code-block::
|
.. code-block::
|
||||||
|
|
||||||
2023-08-08 15:50:08,355 INFO: Connected NIC 80:af:f2:f6:58:b7/192.168.0.10
|
2023-08-08 15:50:08,355 INFO: Connected NIC 80:af:f2:f6:58:b7/192.168.0.10
|
||||||
2023-08-08 15:50:08,355 INFO: Turned on
|
|
||||||
|
|
||||||
**node_b NIC table**
|
**node_b NIC table**
|
||||||
|
|
||||||
@@ -288,7 +328,6 @@ This produces:
|
|||||||
.. code-block::
|
.. code-block::
|
||||||
|
|
||||||
2023-08-08 15:50:08,357 INFO: Connected NIC 98:ad:eb:7c:dc:cb/192.168.0.11
|
2023-08-08 15:50:08,357 INFO: Connected NIC 98:ad:eb:7c:dc:cb/192.168.0.11
|
||||||
2023-08-08 15:50:08,357 INFO: Turned on
|
|
||||||
|
|
||||||
**node_c NIC table**
|
**node_c NIC table**
|
||||||
|
|
||||||
@@ -303,7 +342,6 @@ This produces:
|
|||||||
.. code-block::
|
.. code-block::
|
||||||
|
|
||||||
2023-08-08 15:50:08,358 INFO: Connected NIC bc:72:82:5d:82:a4/192.168.0.12
|
2023-08-08 15:50:08,358 INFO: Connected NIC bc:72:82:5d:82:a4/192.168.0.12
|
||||||
2023-08-08 15:50:08,358 INFO: Turned on
|
|
||||||
|
|
||||||
**node_d NIC table**
|
**node_d NIC table**
|
||||||
|
|
||||||
@@ -318,21 +356,19 @@ This produces:
|
|||||||
.. code-block::
|
.. code-block::
|
||||||
|
|
||||||
2023-08-08 15:50:08,359 INFO: Connected NIC 84:20:7c:ec:a5:c6/192.168.0.13
|
2023-08-08 15:50:08,359 INFO: Connected NIC 84:20:7c:ec:a5:c6/192.168.0.13
|
||||||
2023-08-08 15:50:08,360 INFO: Turned on
|
|
||||||
|
|
||||||
|
|
||||||
|
---------------
|
||||||
Create Switches
|
Create Switches
|
||||||
***************
|
---------------
|
||||||
|
|
||||||
Next, we'll create two six-port switches:
|
Next, we'll create two six-port switches:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
switch_1 = Switch(hostname="switch_1", num_ports=6)
|
switch_1 = Switch(hostname="switch_1", num_ports=6, operating_state=NodeOperatingState.ON)
|
||||||
switch_1.power_on()
|
|
||||||
|
|
||||||
switch_2 = Switch(hostname="switch_2", num_ports=6)
|
switch_2 = Switch(hostname="switch_2", num_ports=6, operating_state=NodeOperatingState.ON)
|
||||||
switch_2.power_on()
|
|
||||||
|
|
||||||
This produces:
|
This produces:
|
||||||
|
|
||||||
@@ -384,8 +420,9 @@ This produces:
|
|||||||
|
|
||||||
2023-08-08 15:50:08,374 INFO: Turned on
|
2023-08-08 15:50:08,374 INFO: Turned on
|
||||||
|
|
||||||
|
------------
|
||||||
Create Links
|
Create Links
|
||||||
************
|
------------
|
||||||
|
|
||||||
Finally, we'll create the five links that connect the nodes and the switches:
|
Finally, we'll create the five links that connect the nodes and the switches:
|
||||||
|
|
||||||
@@ -523,8 +560,9 @@ This produces:
|
|||||||
2023-08-08 15:50:08,384 INFO: SwitchPort 96:77:39:d1:de:44 enabled
|
2023-08-08 15:50:08,384 INFO: SwitchPort 96:77:39:d1:de:44 enabled
|
||||||
|
|
||||||
|
|
||||||
|
------------
|
||||||
Perform Ping
|
Perform Ping
|
||||||
************
|
------------
|
||||||
|
|
||||||
Now with the network setup and operational, we can perform a ping to confirm that communication between nodes over a
|
Now with the network setup and operational, we can perform a ping to confirm that communication between nodes over a
|
||||||
switched network is possible. In the below example, we ping 192.168.0.13 (node_d) from node_a:
|
switched network is possible. In the below example, we ping 192.168.0.13 (node_d) from node_a:
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ snippet demonstrates usage of the ``ActionPermissionValidator``.
|
|||||||
|
|
||||||
def _init_request_manager(self) -> RequestManager:
|
def _init_request_manager(self) -> RequestManager:
|
||||||
am = super()._init_request_manager()
|
am = super()._init_request_manager()
|
||||||
am.add_action(
|
am.add_request(
|
||||||
"reset_factory_settings",
|
"reset_factory_settings",
|
||||||
Action(
|
Action(
|
||||||
func = lambda request, context: self.reset_factory_settings(),
|
func = lambda request, context: self.reset_factory_settings(),
|
||||||
|
|||||||
@@ -173,9 +173,9 @@ class SimComponent(BaseModel):
|
|||||||
|
|
||||||
class WebBrowser(Application):
|
class WebBrowser(Application):
|
||||||
def _init_request_manager(self) -> RequestManager:
|
def _init_request_manager(self) -> RequestManager:
|
||||||
am = 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
|
||||||
am.add_request(...) # initialise any requests specific to the web browser
|
rm.add_request(...) # initialise any requests specific to the web browser
|
||||||
return am
|
return rm
|
||||||
|
|
||||||
:return: Request manager object belonging to this sim component.
|
:return: Request manager object belonging to this sim component.
|
||||||
:rtype: RequestManager
|
:rtype: RequestManager
|
||||||
|
|||||||
@@ -80,17 +80,17 @@ class DomainController(SimComponent):
|
|||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
|
|
||||||
def _init_request_manager(self) -> RequestManager:
|
def _init_request_manager(self) -> RequestManager:
|
||||||
am = super()._init_request_manager()
|
rm = super()._init_request_manager()
|
||||||
# Action 'account' matches requests like:
|
# Action 'account' matches requests like:
|
||||||
# ['account', '<account-uuid>', *account_action]
|
# ['account', '<account-uuid>', *account_action]
|
||||||
am.add_request(
|
rm.add_request(
|
||||||
"account",
|
"account",
|
||||||
RequestType(
|
RequestType(
|
||||||
func=lambda request, context: self.accounts[request.pop(0)].apply_request(request, context),
|
func=lambda request, context: self.accounts[request.pop(0)].apply_request(request, context),
|
||||||
validator=GroupMembershipValidator(allowed_groups=[AccountGroup.DOMAIN_ADMIN]),
|
validator=GroupMembershipValidator(allowed_groups=[AccountGroup.DOMAIN_ADMIN]),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
return am
|
return rm
|
||||||
|
|
||||||
def describe_state(self) -> Dict:
|
def describe_state(self) -> Dict:
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -1,8 +1,12 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import hashlib
|
||||||
|
import json
|
||||||
import math
|
import math
|
||||||
import os.path
|
import os.path
|
||||||
import shutil
|
import shutil
|
||||||
|
from abc import abstractmethod
|
||||||
|
from enum import Enum
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Dict, Optional
|
from typing import Dict, Optional
|
||||||
|
|
||||||
@@ -42,6 +46,25 @@ def convert_size(size_bytes: int) -> str:
|
|||||||
return f"{s} {size_name[i]}"
|
return f"{s} {size_name[i]}"
|
||||||
|
|
||||||
|
|
||||||
|
class FileSystemItemHealthStatus(Enum):
|
||||||
|
"""Status of the FileSystemItem."""
|
||||||
|
|
||||||
|
GOOD = 0
|
||||||
|
"""File/Folder is OK."""
|
||||||
|
|
||||||
|
COMPROMISED = 1
|
||||||
|
"""File/Folder is quarantined."""
|
||||||
|
|
||||||
|
CORRUPT = 2
|
||||||
|
"""File/Folder is corrupted."""
|
||||||
|
|
||||||
|
RESTORING = 3
|
||||||
|
"""File/Folder is in the process of being restored."""
|
||||||
|
|
||||||
|
REPAIRING = 3
|
||||||
|
"""File/Folder is in the process of being repaired."""
|
||||||
|
|
||||||
|
|
||||||
class FileSystemItemABC(SimComponent):
|
class FileSystemItemABC(SimComponent):
|
||||||
"""
|
"""
|
||||||
Abstract base class for file system items used in the file system simulation.
|
Abstract base class for file system items used in the file system simulation.
|
||||||
@@ -52,6 +75,15 @@ class FileSystemItemABC(SimComponent):
|
|||||||
name: str
|
name: str
|
||||||
"The name of the FileSystemItemABC."
|
"The name of the FileSystemItemABC."
|
||||||
|
|
||||||
|
health_status: FileSystemItemHealthStatus = FileSystemItemHealthStatus.GOOD
|
||||||
|
"Actual status of the current FileSystemItem"
|
||||||
|
|
||||||
|
visible_health_status: FileSystemItemHealthStatus = FileSystemItemHealthStatus.GOOD
|
||||||
|
"Visible status of the current FileSystemItem"
|
||||||
|
|
||||||
|
previous_hash: Optional[str] = None
|
||||||
|
"Hash of the file contents or the description state"
|
||||||
|
|
||||||
def describe_state(self) -> Dict:
|
def describe_state(self) -> Dict:
|
||||||
"""
|
"""
|
||||||
Produce a dictionary describing the current state of this object.
|
Produce a dictionary describing the current state of this object.
|
||||||
@@ -59,13 +91,24 @@ class FileSystemItemABC(SimComponent):
|
|||||||
:return: Current state of this object and child objects.
|
:return: Current state of this object and child objects.
|
||||||
"""
|
"""
|
||||||
state = super().describe_state()
|
state = super().describe_state()
|
||||||
state.update(
|
state["name"] = self.name
|
||||||
{
|
state["status"] = self.health_status.name
|
||||||
"name": self.name,
|
state["visible_status"] = self.visible_health_status.name
|
||||||
}
|
state["previous_hash"] = self.previous_hash
|
||||||
)
|
|
||||||
return state
|
return state
|
||||||
|
|
||||||
|
def _init_request_manager(self) -> RequestManager:
|
||||||
|
rm = super()._init_request_manager()
|
||||||
|
|
||||||
|
rm.add_request(name="scan", request_type=RequestType(func=lambda request, context: self.scan()))
|
||||||
|
rm.add_request(name="checkhash", request_type=RequestType(func=lambda request, context: self.check_hash()))
|
||||||
|
rm.add_request(name="repair", request_type=RequestType(func=lambda request, context: self.repair()))
|
||||||
|
rm.add_request(name="restore", request_type=RequestType(func=lambda request, context: self.restore()))
|
||||||
|
|
||||||
|
rm.add_request(name="corrupt", request_type=RequestType(func=lambda request, context: self.corrupt()))
|
||||||
|
|
||||||
|
return rm
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def size_str(self) -> str:
|
def size_str(self) -> str:
|
||||||
"""
|
"""
|
||||||
@@ -78,6 +121,39 @@ class FileSystemItemABC(SimComponent):
|
|||||||
"""
|
"""
|
||||||
return convert_size(self.size)
|
return convert_size(self.size)
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def check_hash(self) -> bool:
|
||||||
|
"""
|
||||||
|
Checks the has of the file to detect any changes.
|
||||||
|
|
||||||
|
For current implementation, any change in file hash means it is compromised.
|
||||||
|
|
||||||
|
Return False if corruption is detected, otherwise True
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def repair(self) -> bool:
|
||||||
|
"""
|
||||||
|
Repair the FileSystemItem.
|
||||||
|
|
||||||
|
True if successfully repaired. False otherwise.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def corrupt(self) -> bool:
|
||||||
|
"""
|
||||||
|
Corrupt the FileSystemItem.
|
||||||
|
|
||||||
|
True if successfully corrupted. False otherwise.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def restore(self) -> None:
|
||||||
|
"""Restore the file/folder to the state before it got ruined."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class FileSystem(SimComponent):
|
class FileSystem(SimComponent):
|
||||||
"""Class that contains all the simulation File System."""
|
"""Class that contains all the simulation File System."""
|
||||||
@@ -95,15 +171,20 @@ class FileSystem(SimComponent):
|
|||||||
self.create_folder("root")
|
self.create_folder("root")
|
||||||
|
|
||||||
def _init_request_manager(self) -> RequestManager:
|
def _init_request_manager(self) -> RequestManager:
|
||||||
am = super()._init_request_manager()
|
rm = super()._init_request_manager()
|
||||||
|
|
||||||
|
rm.add_request(
|
||||||
|
name="delete",
|
||||||
|
request_type=RequestType(func=lambda request, context: self.delete_folder_by_id(folder_uuid=request[0])),
|
||||||
|
)
|
||||||
|
|
||||||
self._folder_request_manager = RequestManager()
|
self._folder_request_manager = RequestManager()
|
||||||
am.add_request("folder", RequestType(func=self._folder_request_manager))
|
rm.add_request("folder", RequestType(func=self._folder_request_manager))
|
||||||
|
|
||||||
self._file_request_manager = RequestManager()
|
self._file_request_manager = RequestManager()
|
||||||
am.add_request("file", RequestType(func=self._file_request_manager))
|
rm.add_request("file", RequestType(func=self._file_request_manager))
|
||||||
|
|
||||||
return am
|
return rm
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def size(self) -> int:
|
def size(self) -> int:
|
||||||
@@ -165,7 +246,9 @@ class FileSystem(SimComponent):
|
|||||||
self.folders[folder.uuid] = folder
|
self.folders[folder.uuid] = folder
|
||||||
self._folders_by_name[folder.name] = folder
|
self._folders_by_name[folder.name] = folder
|
||||||
self.sys_log.info(f"Created folder /{folder.name}")
|
self.sys_log.info(f"Created folder /{folder.name}")
|
||||||
self._folder_request_manager.add_request(folder.uuid, RequestType(func=folder._request_manager))
|
self._folder_request_manager.add_request(
|
||||||
|
name=folder.uuid, request_type=RequestType(func=folder._request_manager)
|
||||||
|
)
|
||||||
return folder
|
return folder
|
||||||
|
|
||||||
def delete_folder(self, folder_name: str):
|
def delete_folder(self, folder_name: str):
|
||||||
@@ -179,15 +262,29 @@ class FileSystem(SimComponent):
|
|||||||
return
|
return
|
||||||
folder = self._folders_by_name.get(folder_name)
|
folder = self._folders_by_name.get(folder_name)
|
||||||
if folder:
|
if folder:
|
||||||
for file in folder.files.values():
|
# remove from folder list
|
||||||
self.delete_file(file)
|
|
||||||
self.folders.pop(folder.uuid)
|
self.folders.pop(folder.uuid)
|
||||||
self._folders_by_name.pop(folder.name)
|
self._folders_by_name.pop(folder.name)
|
||||||
self.sys_log.info(f"Deleted folder /{folder.name} and its contents")
|
self.sys_log.info(f"Deleted folder /{folder.name} and its contents")
|
||||||
self._folder_request_manager.remove_request(folder.uuid)
|
self._folder_request_manager.remove_request(folder.uuid)
|
||||||
|
folder.remove_all_files()
|
||||||
|
|
||||||
else:
|
else:
|
||||||
_LOGGER.debug(f"Cannot delete folder as it does not exist: {folder_name}")
|
_LOGGER.debug(f"Cannot delete folder as it does not exist: {folder_name}")
|
||||||
|
|
||||||
|
def delete_folder_by_id(self, folder_uuid: str):
|
||||||
|
"""
|
||||||
|
Deletes a folder via its uuid.
|
||||||
|
|
||||||
|
:param: folder_uuid: UUID of the folder to delete
|
||||||
|
"""
|
||||||
|
folder = self.get_folder_by_id(folder_uuid=folder_uuid)
|
||||||
|
self.delete_folder(folder_name=folder.name)
|
||||||
|
|
||||||
|
def restore_folder(self, folder_id: str):
|
||||||
|
"""TODO."""
|
||||||
|
pass
|
||||||
|
|
||||||
def create_file(
|
def create_file(
|
||||||
self,
|
self,
|
||||||
file_name: str,
|
file_name: str,
|
||||||
@@ -226,7 +323,7 @@ class FileSystem(SimComponent):
|
|||||||
)
|
)
|
||||||
folder.add_file(file)
|
folder.add_file(file)
|
||||||
self.sys_log.info(f"Created file /{file.path}")
|
self.sys_log.info(f"Created file /{file.path}")
|
||||||
self._file_request_manager.add_request(file.uuid, RequestType(func=file._request_manager))
|
self._file_request_manager.add_request(name=file.uuid, request_type=RequestType(func=file._request_manager))
|
||||||
return file
|
return file
|
||||||
|
|
||||||
def get_file(self, folder_name: str, file_name: str) -> Optional[File]:
|
def get_file(self, folder_name: str, file_name: str) -> Optional[File]:
|
||||||
@@ -301,6 +398,20 @@ class FileSystem(SimComponent):
|
|||||||
new_file.sim_path.parent.mkdir(exist_ok=True)
|
new_file.sim_path.parent.mkdir(exist_ok=True)
|
||||||
shutil.copy2(file.sim_path, new_file.sim_path)
|
shutil.copy2(file.sim_path, new_file.sim_path)
|
||||||
|
|
||||||
|
def restore_file(self, folder_id: str, file_id: str):
|
||||||
|
"""
|
||||||
|
Restore a file.
|
||||||
|
|
||||||
|
Checks the current file's status and applies the correct fix for the file.
|
||||||
|
|
||||||
|
:param: folder_id: id of the folder where the file is stored
|
||||||
|
:type: folder_id: str
|
||||||
|
|
||||||
|
:param: folder_id: id of the file to restore
|
||||||
|
:type: folder_id: str
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
def get_folder(self, folder_name: str) -> Optional[Folder]:
|
def get_folder(self, folder_name: str) -> Optional[Folder]:
|
||||||
"""
|
"""
|
||||||
Get a folder by its name if it exists.
|
Get a folder by its name if it exists.
|
||||||
@@ -314,7 +425,7 @@ class FileSystem(SimComponent):
|
|||||||
"""
|
"""
|
||||||
Get a folder by its uuid if it exists.
|
Get a folder by its uuid if it exists.
|
||||||
|
|
||||||
:param folder_uuid: The folder uuid.
|
:param: folder_uuid: The folder uuid.
|
||||||
:return: The matching Folder.
|
:return: The matching Folder.
|
||||||
"""
|
"""
|
||||||
return self.folders.get(folder_uuid)
|
return self.folders.get(folder_uuid)
|
||||||
@@ -329,20 +440,17 @@ class Folder(FileSystemItemABC):
|
|||||||
"Files stored in the folder."
|
"Files stored in the folder."
|
||||||
_files_by_name: Dict[str, File] = {}
|
_files_by_name: Dict[str, File] = {}
|
||||||
"Files by their name as <file name>.<file type>."
|
"Files by their name as <file name>.<file type>."
|
||||||
is_quarantined: bool = False
|
|
||||||
"Flag that marks the folder as quarantined if true."
|
scan_duration: int = -1
|
||||||
|
"How many timesteps to complete a scan."
|
||||||
|
|
||||||
def _init_request_manager(self) -> RequestManager:
|
def _init_request_manager(self) -> RequestManager:
|
||||||
am = super()._init_request_manager()
|
rm = super()._init_request_manager()
|
||||||
|
rm.add_request(
|
||||||
am.add_request("scan", RequestType(func=lambda request, context: ...)) # TODO implement request
|
name="delete",
|
||||||
am.add_request("checkhash", RequestType(func=lambda request, context: ...)) # TODO implement request
|
request_type=RequestType(func=lambda request, context: self.remove_file_by_id(file_uuid=request[0])),
|
||||||
am.add_request("repair", RequestType(func=lambda request, context: ...)) # TODO implement request
|
)
|
||||||
am.add_request("restore", RequestType(func=lambda request, context: ...)) # TODO implement request
|
return rm
|
||||||
am.add_request("delete", RequestType(func=lambda request, context: ...)) # TODO implement request
|
|
||||||
am.add_request("corrupt", RequestType(func=lambda request, context: ...)) # TODO implement request
|
|
||||||
|
|
||||||
return am
|
|
||||||
|
|
||||||
def describe_state(self) -> Dict:
|
def describe_state(self) -> Dict:
|
||||||
"""
|
"""
|
||||||
@@ -352,7 +460,6 @@ class Folder(FileSystemItemABC):
|
|||||||
"""
|
"""
|
||||||
state = super().describe_state()
|
state = super().describe_state()
|
||||||
state["files"] = {file.name: file.describe_state() for uuid, file in self.files.items()}
|
state["files"] = {file.name: file.describe_state() for uuid, file in self.files.items()}
|
||||||
state["is_quarantined"] = self.is_quarantined
|
|
||||||
return state
|
return state
|
||||||
|
|
||||||
def show(self, markdown: bool = False):
|
def show(self, markdown: bool = False):
|
||||||
@@ -380,6 +487,27 @@ class Folder(FileSystemItemABC):
|
|||||||
"""
|
"""
|
||||||
return sum(file.size for file in self.files.values() if file.size is not None)
|
return sum(file.size for file in self.files.values() if file.size is not None)
|
||||||
|
|
||||||
|
def apply_timestep(self, timestep: int):
|
||||||
|
"""
|
||||||
|
Apply a single timestep of simulation dynamics to this service.
|
||||||
|
|
||||||
|
In this instance, if any multi-timestep processes are currently occurring (such as scanning),
|
||||||
|
then they are brought one step closer to being finished.
|
||||||
|
|
||||||
|
:param timestep: The current timestep number. (Amount of time since simulation episode began)
|
||||||
|
:type timestep: int
|
||||||
|
"""
|
||||||
|
super().apply_timestep(timestep=timestep)
|
||||||
|
|
||||||
|
# scan files each timestep
|
||||||
|
if self.scan_duration > -1:
|
||||||
|
# scan one file per timestep
|
||||||
|
file = self.get_file_by_id(file_uuid=list(self.files)[self.scan_duration - 1])
|
||||||
|
file.scan()
|
||||||
|
if file.visible_health_status == FileSystemItemHealthStatus.CORRUPT:
|
||||||
|
self.visible_health_status = FileSystemItemHealthStatus.CORRUPT
|
||||||
|
self.scan_duration -= 1
|
||||||
|
|
||||||
def get_file(self, file_name: str) -> Optional[File]:
|
def get_file(self, file_name: str) -> Optional[File]:
|
||||||
"""
|
"""
|
||||||
Get a file by its name.
|
Get a file by its name.
|
||||||
@@ -396,7 +524,7 @@ class Folder(FileSystemItemABC):
|
|||||||
"""
|
"""
|
||||||
Get a file by its uuid.
|
Get a file by its uuid.
|
||||||
|
|
||||||
:param file_uuid: The file uuid.
|
:param: file_uuid: The file uuid.
|
||||||
:return: The matching File.
|
:return: The matching File.
|
||||||
"""
|
"""
|
||||||
return self.files.get(file_uuid)
|
return self.files.get(file_uuid)
|
||||||
@@ -438,21 +566,121 @@ class Folder(FileSystemItemABC):
|
|||||||
else:
|
else:
|
||||||
_LOGGER.debug(f"File with UUID {file.uuid} was not found.")
|
_LOGGER.debug(f"File with UUID {file.uuid} was not found.")
|
||||||
|
|
||||||
|
def remove_file_by_id(self, file_uuid: str):
|
||||||
|
"""
|
||||||
|
Remove a file using id.
|
||||||
|
|
||||||
|
:param: file_uuid: The UUID of the file to remove.
|
||||||
|
"""
|
||||||
|
file = self.get_file_by_id(file_uuid=file_uuid)
|
||||||
|
self.remove_file(file=file)
|
||||||
|
|
||||||
|
def remove_all_files(self):
|
||||||
|
"""Removes all the files in the folder."""
|
||||||
|
self.files = {}
|
||||||
|
self._files_by_name = {}
|
||||||
|
|
||||||
|
def restore_file(self, file: Optional[File]):
|
||||||
|
"""
|
||||||
|
Restores a file.
|
||||||
|
|
||||||
|
The method can take a File object or a file id.
|
||||||
|
|
||||||
|
:param file: The file to remove
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
def quarantine(self):
|
def quarantine(self):
|
||||||
"""Quarantines the File System Folder."""
|
"""Quarantines the File System Folder."""
|
||||||
if not self.is_quarantined:
|
pass
|
||||||
self.is_quarantined = True
|
|
||||||
self.fs.sys_log.info(f"Quarantined folder ./{self.name}")
|
|
||||||
|
|
||||||
def unquarantine(self):
|
def unquarantine(self):
|
||||||
"""Unquarantine of the File System Folder."""
|
"""Unquarantine of the File System Folder."""
|
||||||
if self.is_quarantined:
|
pass
|
||||||
self.is_quarantined = False
|
|
||||||
self.fs.sys_log.info(f"Quarantined folder ./{self.name}")
|
|
||||||
|
|
||||||
def quarantine_status(self) -> bool:
|
def quarantine_status(self) -> bool:
|
||||||
"""Returns true if the folder is being quarantined."""
|
"""Returns true if the folder is being quarantined."""
|
||||||
return self.is_quarantined
|
pass
|
||||||
|
|
||||||
|
def scan(self) -> None:
|
||||||
|
"""Update Folder visible status."""
|
||||||
|
if self.scan_duration <= -1:
|
||||||
|
# scan one file per timestep
|
||||||
|
self.scan_duration = len(self.files)
|
||||||
|
self.fs.sys_log.info(f"Scanning folder {self.name} (id: {self.uuid})")
|
||||||
|
else:
|
||||||
|
# scan already in progress
|
||||||
|
self.fs.sys_log.info(f"Scan is already in progress {self.name} (id: {self.uuid})")
|
||||||
|
|
||||||
|
def check_hash(self) -> bool:
|
||||||
|
"""
|
||||||
|
Runs a :func:`check_hash` on all files in the folder.
|
||||||
|
|
||||||
|
If a file under the folder is corrupted, the whole folder is considered corrupted.
|
||||||
|
|
||||||
|
TODO: For now this will just iterate through the files and run :func:`check_hash` and ignores
|
||||||
|
any other changes to the folder
|
||||||
|
|
||||||
|
Return False if corruption is detected, otherwise True
|
||||||
|
"""
|
||||||
|
super().check_hash()
|
||||||
|
|
||||||
|
# iterate through the files and run a check hash
|
||||||
|
no_corrupted_files = True
|
||||||
|
|
||||||
|
for file_id in self.files:
|
||||||
|
file = self.get_file_by_id(file_uuid=file_id)
|
||||||
|
no_corrupted_files = file.check_hash()
|
||||||
|
|
||||||
|
# if one file in the folder is corrupted, set the folder status to corrupted
|
||||||
|
if not no_corrupted_files:
|
||||||
|
self.corrupt()
|
||||||
|
|
||||||
|
self.fs.sys_log.info(f"Checking hash of folder {self.name} (id: {self.uuid})")
|
||||||
|
|
||||||
|
return no_corrupted_files
|
||||||
|
|
||||||
|
def repair(self) -> bool:
|
||||||
|
"""Repair a corrupted Folder by setting the folder and containing files status to FileSystemItemStatus.GOOD."""
|
||||||
|
super().repair()
|
||||||
|
|
||||||
|
repaired = False
|
||||||
|
|
||||||
|
# iterate through the files in the folder
|
||||||
|
for file_id in self.files:
|
||||||
|
file = self.get_file_by_id(file_uuid=file_id)
|
||||||
|
repaired = file.repair()
|
||||||
|
|
||||||
|
# set file status to good if corrupt
|
||||||
|
if self.health_status == FileSystemItemHealthStatus.CORRUPT:
|
||||||
|
self.health_status = FileSystemItemHealthStatus.GOOD
|
||||||
|
repaired = True
|
||||||
|
|
||||||
|
self.fs.sys_log.info(f"Repaired folder {self.name} (id: {self.uuid})")
|
||||||
|
return repaired
|
||||||
|
|
||||||
|
def restore(self) -> None:
|
||||||
|
"""TODO."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def corrupt(self) -> bool:
|
||||||
|
"""Corrupt a File by setting the folder and containing files status to FileSystemItemStatus.CORRUPT."""
|
||||||
|
super().corrupt()
|
||||||
|
|
||||||
|
corrupted = False
|
||||||
|
|
||||||
|
# iterate through the files in the folder
|
||||||
|
for file_id in self.files:
|
||||||
|
file = self.get_file_by_id(file_uuid=file_id)
|
||||||
|
corrupted = file.corrupt()
|
||||||
|
|
||||||
|
# set file status to corrupt if good
|
||||||
|
if self.health_status == FileSystemItemHealthStatus.GOOD:
|
||||||
|
self.health_status = FileSystemItemHealthStatus.CORRUPT
|
||||||
|
corrupted = True
|
||||||
|
|
||||||
|
self.fs.sys_log.info(f"Corrupted folder {self.name} (id: {self.uuid})")
|
||||||
|
return corrupted
|
||||||
|
|
||||||
|
|
||||||
class File(FileSystemItemABC):
|
class File(FileSystemItemABC):
|
||||||
@@ -509,18 +737,6 @@ class File(FileSystemItemABC):
|
|||||||
with open(self.sim_path, mode="a"):
|
with open(self.sim_path, mode="a"):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def _init_request_manager(self) -> RequestManager:
|
|
||||||
am = super()._init_request_manager()
|
|
||||||
|
|
||||||
am.add_request("scan", RequestType(func=lambda request, context: ...)) # TODO implement request
|
|
||||||
am.add_request("checkhash", RequestType(func=lambda request, context: ...)) # TODO implement request
|
|
||||||
am.add_request("delete", RequestType(func=lambda request, context: ...)) # TODO implement request
|
|
||||||
am.add_request("repair", RequestType(func=lambda request, context: ...)) # TODO implement request
|
|
||||||
am.add_request("restore", RequestType(func=lambda request, context: ...)) # TODO implement request
|
|
||||||
am.add_request("corrupt", RequestType(func=lambda request, context: ...)) # TODO implement request
|
|
||||||
|
|
||||||
return am
|
|
||||||
|
|
||||||
def make_copy(self, dst_folder: Folder) -> File:
|
def make_copy(self, dst_folder: Folder) -> File:
|
||||||
"""
|
"""
|
||||||
Create a copy of the current File object in the given destination folder.
|
Create a copy of the current File object in the given destination folder.
|
||||||
@@ -556,3 +772,73 @@ class File(FileSystemItemABC):
|
|||||||
state["size"] = self.size
|
state["size"] = self.size
|
||||||
state["file_type"] = self.file_type.name
|
state["file_type"] = self.file_type.name
|
||||||
return state
|
return state
|
||||||
|
|
||||||
|
def scan(self) -> None:
|
||||||
|
"""Updates the visible statuses of the file."""
|
||||||
|
path = self.folder.name + "/" + self.name
|
||||||
|
self.folder.fs.sys_log.info(f"Scanning file {self.sim_path if self.sim_path else path}")
|
||||||
|
self.visible_health_status = self.health_status
|
||||||
|
|
||||||
|
def check_hash(self) -> bool:
|
||||||
|
"""
|
||||||
|
Check if the file has been changed.
|
||||||
|
|
||||||
|
If changed, the file is considered corrupted.
|
||||||
|
|
||||||
|
Return False if corruption is detected, otherwise True
|
||||||
|
"""
|
||||||
|
current_hash = None
|
||||||
|
|
||||||
|
# if file is real, read the file contents
|
||||||
|
if self.real:
|
||||||
|
with open(self.sim_path, "rb") as f:
|
||||||
|
file_hash = hashlib.blake2b()
|
||||||
|
while chunk := f.read(8192):
|
||||||
|
file_hash.update(chunk)
|
||||||
|
|
||||||
|
current_hash = file_hash.hexdigest()
|
||||||
|
else:
|
||||||
|
# otherwise get describe_state dict and hash that
|
||||||
|
current_hash = hashlib.blake2b(json.dumps(self.describe_state(), sort_keys=True).encode()).hexdigest()
|
||||||
|
|
||||||
|
# if the previous hash is None, set the current hash to previous
|
||||||
|
if self.previous_hash is None:
|
||||||
|
self.previous_hash = current_hash
|
||||||
|
|
||||||
|
# if the previous hash and current hash do not match, mark file as corrupted
|
||||||
|
if self.previous_hash is not current_hash:
|
||||||
|
self.corrupt()
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def repair(self) -> bool:
|
||||||
|
"""Repair a corrupted File by setting the status to FileSystemItemStatus.GOOD."""
|
||||||
|
super().repair()
|
||||||
|
|
||||||
|
# set file status to good if corrupt
|
||||||
|
if self.health_status == FileSystemItemHealthStatus.CORRUPT:
|
||||||
|
self.health_status = FileSystemItemHealthStatus.GOOD
|
||||||
|
|
||||||
|
path = self.folder.name + "/" + self.name
|
||||||
|
self.folder.fs.sys_log.info(f"Repaired file {self.sim_path if self.sim_path else path}")
|
||||||
|
return True
|
||||||
|
|
||||||
|
def restore(self) -> None:
|
||||||
|
"""Restore a corrupted File by setting the status to FileSystemItemStatus.GOOD."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def corrupt(self) -> bool:
|
||||||
|
"""Corrupt a File by setting the status to FileSystemItemStatus.CORRUPT."""
|
||||||
|
super().corrupt()
|
||||||
|
|
||||||
|
corrupted = False
|
||||||
|
|
||||||
|
# set file status to good if corrupt
|
||||||
|
if self.health_status == FileSystemItemHealthStatus.GOOD:
|
||||||
|
self.health_status = FileSystemItemHealthStatus.CORRUPT
|
||||||
|
corrupted = True
|
||||||
|
|
||||||
|
path = self.folder.name + "/" + self.name
|
||||||
|
self.folder.fs.sys_log.info(f"Corrupted file {self.sim_path if self.sim_path else path}")
|
||||||
|
return corrupted
|
||||||
|
|||||||
@@ -44,13 +44,13 @@ class Network(SimComponent):
|
|||||||
self._nx_graph = MultiGraph()
|
self._nx_graph = MultiGraph()
|
||||||
|
|
||||||
def _init_request_manager(self) -> RequestManager:
|
def _init_request_manager(self) -> RequestManager:
|
||||||
am = super()._init_request_manager()
|
rm = super()._init_request_manager()
|
||||||
self._node_request_manager = RequestManager()
|
self._node_request_manager = RequestManager()
|
||||||
am.add_request(
|
rm.add_request(
|
||||||
"node",
|
"node",
|
||||||
RequestType(func=self._node_request_manager),
|
RequestType(func=self._node_request_manager),
|
||||||
)
|
)
|
||||||
return am
|
return rm
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def routers(self) -> List[Router]:
|
def routers(self) -> List[Router]:
|
||||||
|
|||||||
@@ -145,12 +145,12 @@ class NIC(SimComponent):
|
|||||||
return state
|
return state
|
||||||
|
|
||||||
def _init_request_manager(self) -> RequestManager:
|
def _init_request_manager(self) -> RequestManager:
|
||||||
am = super()._init_request_manager()
|
rm = super()._init_request_manager()
|
||||||
|
|
||||||
am.add_request("enable", RequestType(func=lambda request, context: self.enable()))
|
rm.add_request("enable", RequestType(func=lambda request, context: self.enable()))
|
||||||
am.add_request("disable", RequestType(func=lambda request, context: self.disable()))
|
rm.add_request("disable", RequestType(func=lambda request, context: self.disable()))
|
||||||
|
|
||||||
return am
|
return rm
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def ip_network(self) -> IPv4Network:
|
def ip_network(self) -> IPv4Network:
|
||||||
@@ -914,6 +914,18 @@ class Node(SimComponent):
|
|||||||
revealed_to_red: bool = False
|
revealed_to_red: bool = False
|
||||||
"Informs whether the node has been revealed to a red agent."
|
"Informs whether the node has been revealed to a red agent."
|
||||||
|
|
||||||
|
start_up_duration: int = 3
|
||||||
|
"Time steps needed for the node to start up."
|
||||||
|
|
||||||
|
start_up_countdown: int = 0
|
||||||
|
"Time steps needed until node is booted up."
|
||||||
|
|
||||||
|
shut_down_duration: int = 3
|
||||||
|
"Time steps needed for the node to shut down."
|
||||||
|
|
||||||
|
shut_down_countdown: int = 0
|
||||||
|
"Time steps needed until node is shut down."
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
"""
|
"""
|
||||||
Initialize the Node with various components and managers.
|
Initialize the Node with various components and managers.
|
||||||
@@ -951,30 +963,30 @@ class Node(SimComponent):
|
|||||||
def _init_request_manager(self) -> RequestManager:
|
def _init_request_manager(self) -> RequestManager:
|
||||||
# TODO: I see that this code is really confusing and hard to read right now... I think some of these things will
|
# TODO: I see that this code is really confusing and hard to read right now... I think some of these things will
|
||||||
# need a better name and better documentation.
|
# need a better name and better documentation.
|
||||||
am = super()._init_request_manager()
|
rm = super()._init_request_manager()
|
||||||
# since there are potentially many services, create an request manager that can map service name
|
# since there are potentially many services, create an request manager that can map service name
|
||||||
self._service_request_manager = RequestManager()
|
self._service_request_manager = RequestManager()
|
||||||
am.add_request("service", RequestType(func=self._service_request_manager))
|
rm.add_request("service", RequestType(func=self._service_request_manager))
|
||||||
self._nic_request_manager = RequestManager()
|
self._nic_request_manager = RequestManager()
|
||||||
am.add_request("nic", RequestType(func=self._nic_request_manager))
|
rm.add_request("nic", RequestType(func=self._nic_request_manager))
|
||||||
|
|
||||||
am.add_request("file_system", RequestType(func=self.file_system._request_manager))
|
rm.add_request("file_system", RequestType(func=self.file_system._request_manager))
|
||||||
|
|
||||||
# currently we don't have any applications nor processes, so these will be empty
|
# currently we don't have any applications nor processes, so these will be empty
|
||||||
self._process_request_manager = RequestManager()
|
self._process_request_manager = RequestManager()
|
||||||
am.add_request("process", RequestType(func=self._process_request_manager))
|
rm.add_request("process", RequestType(func=self._process_request_manager))
|
||||||
self._application_request_manager = RequestManager()
|
self._application_request_manager = RequestManager()
|
||||||
am.add_request("application", RequestType(func=self._application_request_manager))
|
rm.add_request("application", RequestType(func=self._application_request_manager))
|
||||||
|
|
||||||
am.add_request("scan", RequestType(func=lambda request, context: ...)) # TODO implement OS scan
|
rm.add_request("scan", RequestType(func=lambda request, context: ...)) # TODO implement OS scan
|
||||||
|
|
||||||
am.add_request("shutdown", RequestType(func=lambda request, context: self.power_off()))
|
rm.add_request("shutdown", RequestType(func=lambda request, context: self.power_off()))
|
||||||
am.add_request("startup", RequestType(func=lambda request, context: self.power_on()))
|
rm.add_request("startup", RequestType(func=lambda request, context: self.power_on()))
|
||||||
am.add_request("reset", RequestType(func=lambda request, context: ...)) # TODO implement node reset
|
rm.add_request("reset", RequestType(func=lambda request, context: ...)) # TODO implement node reset
|
||||||
am.add_request("logon", RequestType(func=lambda request, context: ...)) # TODO implement logon request
|
rm.add_request("logon", RequestType(func=lambda request, context: ...)) # TODO implement logon request
|
||||||
am.add_request("logoff", RequestType(func=lambda request, context: ...)) # TODO implement logoff request
|
rm.add_request("logoff", RequestType(func=lambda request, context: ...)) # TODO implement logoff request
|
||||||
|
|
||||||
return am
|
return rm
|
||||||
|
|
||||||
def _install_system_software(self):
|
def _install_system_software(self):
|
||||||
"""Install System Software - software that is usually provided with the OS."""
|
"""Install System Software - software that is usually provided with the OS."""
|
||||||
@@ -1000,6 +1012,7 @@ class Node(SimComponent):
|
|||||||
"applications": {uuid: app.describe_state() for uuid, app in self.applications.items()},
|
"applications": {uuid: app.describe_state() for uuid, app in self.applications.items()},
|
||||||
"services": {uuid: svc.describe_state() for uuid, svc in self.services.items()},
|
"services": {uuid: svc.describe_state() for uuid, svc in self.services.items()},
|
||||||
"process": {uuid: proc.describe_state() for uuid, proc in self.processes.items()},
|
"process": {uuid: proc.describe_state() for uuid, proc in self.processes.items()},
|
||||||
|
"revealed_to_red": self.revealed_to_red,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
return state
|
return state
|
||||||
@@ -1041,9 +1054,45 @@ class Node(SimComponent):
|
|||||||
)
|
)
|
||||||
print(table)
|
print(table)
|
||||||
|
|
||||||
|
def apply_timestep(self, timestep: int):
|
||||||
|
"""
|
||||||
|
Apply a single timestep of simulation dynamics to this service.
|
||||||
|
|
||||||
|
In this instance, if any multi-timestep processes are currently occurring
|
||||||
|
(such as starting up or shutting down), then they are brought one step closer to
|
||||||
|
being finished.
|
||||||
|
|
||||||
|
:param timestep: The current timestep number. (Amount of time since simulation episode began)
|
||||||
|
:type timestep: int
|
||||||
|
"""
|
||||||
|
super().apply_timestep(timestep=timestep)
|
||||||
|
|
||||||
|
# count down to boot up
|
||||||
|
if self.start_up_countdown > 0:
|
||||||
|
self.start_up_countdown -= 1
|
||||||
|
else:
|
||||||
|
if self.operating_state == NodeOperatingState.BOOTING:
|
||||||
|
self.operating_state = NodeOperatingState.ON
|
||||||
|
self.sys_log.info("Turned on")
|
||||||
|
for nic in self.nics.values():
|
||||||
|
if nic._connected_link:
|
||||||
|
nic.enable()
|
||||||
|
|
||||||
|
# count down to shut down
|
||||||
|
if self.shut_down_countdown > 0:
|
||||||
|
self.shut_down_countdown -= 1
|
||||||
|
else:
|
||||||
|
if self.operating_state == NodeOperatingState.SHUTTING_DOWN:
|
||||||
|
self.operating_state = NodeOperatingState.OFF
|
||||||
|
self.sys_log.info("Turned off")
|
||||||
|
|
||||||
def power_on(self):
|
def power_on(self):
|
||||||
"""Power on the Node, enabling its NICs if it is in the OFF state."""
|
"""Power on the Node, enabling its NICs if it is in the OFF state."""
|
||||||
if self.operating_state == NodeOperatingState.OFF:
|
if self.operating_state == NodeOperatingState.OFF:
|
||||||
|
self.operating_state = NodeOperatingState.BOOTING
|
||||||
|
self.start_up_countdown = self.start_up_duration
|
||||||
|
|
||||||
|
if self.start_up_duration <= 0:
|
||||||
self.operating_state = NodeOperatingState.ON
|
self.operating_state = NodeOperatingState.ON
|
||||||
self.sys_log.info("Turned on")
|
self.sys_log.info("Turned on")
|
||||||
for nic in self.nics.values():
|
for nic in self.nics.values():
|
||||||
@@ -1055,6 +1104,10 @@ class Node(SimComponent):
|
|||||||
if self.operating_state == NodeOperatingState.ON:
|
if self.operating_state == NodeOperatingState.ON:
|
||||||
for nic in self.nics.values():
|
for nic in self.nics.values():
|
||||||
nic.disable()
|
nic.disable()
|
||||||
|
self.operating_state = NodeOperatingState.SHUTTING_DOWN
|
||||||
|
self.shut_down_countdown = self.shut_down_duration
|
||||||
|
|
||||||
|
if self.shut_down_duration <= 0:
|
||||||
self.operating_state = NodeOperatingState.OFF
|
self.operating_state = NodeOperatingState.OFF
|
||||||
self.sys_log.info("Turned off")
|
self.sys_log.info("Turned off")
|
||||||
|
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ class AccessControlList(SimComponent):
|
|||||||
self._acl = [None] * (self.max_acl_rules - 1)
|
self._acl = [None] * (self.max_acl_rules - 1)
|
||||||
|
|
||||||
def _init_request_manager(self) -> RequestManager:
|
def _init_request_manager(self) -> RequestManager:
|
||||||
am = super()._init_request_manager()
|
rm = super()._init_request_manager()
|
||||||
|
|
||||||
# When the request reaches this action, it should now contain solely positional args for the 'add_rule' action.
|
# When the request reaches this action, it should now contain solely positional args for the 'add_rule' action.
|
||||||
# POSITIONAL ARGUMENTS:
|
# POSITIONAL ARGUMENTS:
|
||||||
@@ -99,7 +99,7 @@ class AccessControlList(SimComponent):
|
|||||||
# 4: destination ip address (str castable to IPV4Address (e.g. '10.10.1.2'))
|
# 4: destination ip address (str castable to IPV4Address (e.g. '10.10.1.2'))
|
||||||
# 5: destination port (str name of a Port (e.g. "HTTP"))
|
# 5: destination port (str name of a Port (e.g. "HTTP"))
|
||||||
# 6: position (int)
|
# 6: position (int)
|
||||||
am.add_request(
|
rm.add_request(
|
||||||
"add_rule",
|
"add_rule",
|
||||||
RequestType(
|
RequestType(
|
||||||
func=lambda request, context: self.add_rule(
|
func=lambda request, context: self.add_rule(
|
||||||
@@ -114,8 +114,8 @@ class AccessControlList(SimComponent):
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
am.add_request("remove_rule", RequestType(func=lambda request, context: self.remove_rule(int(request[0]))))
|
rm.add_request("remove_rule", RequestType(func=lambda request, context: self.remove_rule(int(request[0]))))
|
||||||
return am
|
return rm
|
||||||
|
|
||||||
def describe_state(self) -> Dict:
|
def describe_state(self) -> Dict:
|
||||||
"""
|
"""
|
||||||
@@ -627,9 +627,9 @@ class Router(Node):
|
|||||||
self.icmp.arp = self.arp
|
self.icmp.arp = self.arp
|
||||||
|
|
||||||
def _init_request_manager(self) -> RequestManager:
|
def _init_request_manager(self) -> RequestManager:
|
||||||
am = super()._init_request_manager()
|
rm = super()._init_request_manager()
|
||||||
am.add_request("acl", RequestType(func=self.acl._request_manager))
|
rm.add_request("acl", RequestType(func=self.acl._request_manager))
|
||||||
return am
|
return rm
|
||||||
|
|
||||||
def _get_port_of_nic(self, target_nic: NIC) -> Optional[int]:
|
def _get_port_of_nic(self, target_nic: NIC) -> Optional[int]:
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
from ipaddress import IPv4Address
|
from ipaddress import IPv4Address
|
||||||
|
|
||||||
from primaite.simulator.network.container import Network
|
from primaite.simulator.network.container import Network
|
||||||
from primaite.simulator.network.hardware.base import NIC
|
from primaite.simulator.network.hardware.base import NIC, NodeOperatingState
|
||||||
from primaite.simulator.network.hardware.nodes.computer import Computer
|
from primaite.simulator.network.hardware.nodes.computer import Computer
|
||||||
from primaite.simulator.network.hardware.nodes.router import ACLAction, Router
|
from primaite.simulator.network.hardware.nodes.router import ACLAction, Router
|
||||||
from primaite.simulator.network.hardware.nodes.server import Server
|
from primaite.simulator.network.hardware.nodes.server import Server
|
||||||
@@ -110,19 +110,19 @@ def arcd_uc2_network() -> Network:
|
|||||||
network = Network()
|
network = Network()
|
||||||
|
|
||||||
# Router 1
|
# Router 1
|
||||||
router_1 = Router(hostname="router_1", num_ports=5)
|
router_1 = Router(hostname="router_1", num_ports=5, operating_state=NodeOperatingState.ON)
|
||||||
router_1.power_on()
|
router_1.power_on()
|
||||||
router_1.configure_port(port=1, ip_address="192.168.1.1", subnet_mask="255.255.255.0")
|
router_1.configure_port(port=1, ip_address="192.168.1.1", subnet_mask="255.255.255.0")
|
||||||
router_1.configure_port(port=2, ip_address="192.168.10.1", subnet_mask="255.255.255.0")
|
router_1.configure_port(port=2, ip_address="192.168.10.1", subnet_mask="255.255.255.0")
|
||||||
|
|
||||||
# Switch 1
|
# Switch 1
|
||||||
switch_1 = Switch(hostname="switch_1", num_ports=8)
|
switch_1 = Switch(hostname="switch_1", num_ports=8, operating_state=NodeOperatingState.ON)
|
||||||
switch_1.power_on()
|
switch_1.power_on()
|
||||||
network.connect(endpoint_a=router_1.ethernet_ports[1], endpoint_b=switch_1.switch_ports[8])
|
network.connect(endpoint_a=router_1.ethernet_ports[1], endpoint_b=switch_1.switch_ports[8])
|
||||||
router_1.enable_port(1)
|
router_1.enable_port(1)
|
||||||
|
|
||||||
# Switch 2
|
# Switch 2
|
||||||
switch_2 = Switch(hostname="switch_2", num_ports=8)
|
switch_2 = Switch(hostname="switch_2", num_ports=8, operating_state=NodeOperatingState.ON)
|
||||||
switch_2.power_on()
|
switch_2.power_on()
|
||||||
network.connect(endpoint_a=router_1.ethernet_ports[2], endpoint_b=switch_2.switch_ports[8])
|
network.connect(endpoint_a=router_1.ethernet_ports[2], endpoint_b=switch_2.switch_ports[8])
|
||||||
router_1.enable_port(2)
|
router_1.enable_port(2)
|
||||||
@@ -134,6 +134,7 @@ def arcd_uc2_network() -> Network:
|
|||||||
subnet_mask="255.255.255.0",
|
subnet_mask="255.255.255.0",
|
||||||
default_gateway="192.168.10.1",
|
default_gateway="192.168.10.1",
|
||||||
dns_server=IPv4Address("192.168.1.10"),
|
dns_server=IPv4Address("192.168.1.10"),
|
||||||
|
operating_state=NodeOperatingState.ON,
|
||||||
)
|
)
|
||||||
client_1.power_on()
|
client_1.power_on()
|
||||||
network.connect(endpoint_b=client_1.ethernet_port[1], endpoint_a=switch_2.switch_ports[1])
|
network.connect(endpoint_b=client_1.ethernet_port[1], endpoint_a=switch_2.switch_ports[1])
|
||||||
@@ -148,6 +149,7 @@ def arcd_uc2_network() -> Network:
|
|||||||
subnet_mask="255.255.255.0",
|
subnet_mask="255.255.255.0",
|
||||||
default_gateway="192.168.10.1",
|
default_gateway="192.168.10.1",
|
||||||
dns_server=IPv4Address("192.168.1.10"),
|
dns_server=IPv4Address("192.168.1.10"),
|
||||||
|
operating_state=NodeOperatingState.ON,
|
||||||
)
|
)
|
||||||
client_2.power_on()
|
client_2.power_on()
|
||||||
network.connect(endpoint_b=client_2.ethernet_port[1], endpoint_a=switch_2.switch_ports[2])
|
network.connect(endpoint_b=client_2.ethernet_port[1], endpoint_a=switch_2.switch_ports[2])
|
||||||
@@ -158,6 +160,7 @@ def arcd_uc2_network() -> Network:
|
|||||||
ip_address="192.168.1.10",
|
ip_address="192.168.1.10",
|
||||||
subnet_mask="255.255.255.0",
|
subnet_mask="255.255.255.0",
|
||||||
default_gateway="192.168.1.1",
|
default_gateway="192.168.1.1",
|
||||||
|
operating_state=NodeOperatingState.ON,
|
||||||
)
|
)
|
||||||
domain_controller.power_on()
|
domain_controller.power_on()
|
||||||
domain_controller.software_manager.install(DNSServer)
|
domain_controller.software_manager.install(DNSServer)
|
||||||
@@ -171,6 +174,7 @@ def arcd_uc2_network() -> Network:
|
|||||||
subnet_mask="255.255.255.0",
|
subnet_mask="255.255.255.0",
|
||||||
default_gateway="192.168.1.1",
|
default_gateway="192.168.1.1",
|
||||||
dns_server=IPv4Address("192.168.1.10"),
|
dns_server=IPv4Address("192.168.1.10"),
|
||||||
|
operating_state=NodeOperatingState.ON,
|
||||||
)
|
)
|
||||||
database_server.power_on()
|
database_server.power_on()
|
||||||
network.connect(endpoint_b=database_server.ethernet_port[1], endpoint_a=switch_1.switch_ports[3])
|
network.connect(endpoint_b=database_server.ethernet_port[1], endpoint_a=switch_1.switch_ports[3])
|
||||||
@@ -244,6 +248,7 @@ def arcd_uc2_network() -> Network:
|
|||||||
subnet_mask="255.255.255.0",
|
subnet_mask="255.255.255.0",
|
||||||
default_gateway="192.168.1.1",
|
default_gateway="192.168.1.1",
|
||||||
dns_server=IPv4Address("192.168.1.10"),
|
dns_server=IPv4Address("192.168.1.10"),
|
||||||
|
operating_state=NodeOperatingState.ON,
|
||||||
)
|
)
|
||||||
web_server.power_on()
|
web_server.power_on()
|
||||||
web_server.software_manager.install(DatabaseClient)
|
web_server.software_manager.install(DatabaseClient)
|
||||||
@@ -267,6 +272,7 @@ def arcd_uc2_network() -> Network:
|
|||||||
subnet_mask="255.255.255.0",
|
subnet_mask="255.255.255.0",
|
||||||
default_gateway="192.168.1.1",
|
default_gateway="192.168.1.1",
|
||||||
dns_server=IPv4Address("192.168.1.10"),
|
dns_server=IPv4Address("192.168.1.10"),
|
||||||
|
operating_state=NodeOperatingState.ON,
|
||||||
)
|
)
|
||||||
backup_server.power_on()
|
backup_server.power_on()
|
||||||
backup_server.software_manager.install(FTPServer)
|
backup_server.software_manager.install(FTPServer)
|
||||||
@@ -279,6 +285,7 @@ def arcd_uc2_network() -> Network:
|
|||||||
subnet_mask="255.255.255.0",
|
subnet_mask="255.255.255.0",
|
||||||
default_gateway="192.168.1.1",
|
default_gateway="192.168.1.1",
|
||||||
dns_server=IPv4Address("192.168.1.10"),
|
dns_server=IPv4Address("192.168.1.10"),
|
||||||
|
operating_state=NodeOperatingState.ON,
|
||||||
)
|
)
|
||||||
security_suite.power_on()
|
security_suite.power_on()
|
||||||
network.connect(endpoint_b=security_suite.ethernet_port[1], endpoint_a=switch_1.switch_ports[7])
|
network.connect(endpoint_b=security_suite.ethernet_port[1], endpoint_a=switch_1.switch_ports[7])
|
||||||
|
|||||||
@@ -22,12 +22,12 @@ class Simulation(SimComponent):
|
|||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
|
|
||||||
def _init_request_manager(self) -> RequestManager:
|
def _init_request_manager(self) -> RequestManager:
|
||||||
am = super()._init_request_manager()
|
rm = super()._init_request_manager()
|
||||||
# pass through network requests to the network objects
|
# pass through network requests to the network objects
|
||||||
am.add_request("network", RequestType(func=self.network._request_manager))
|
rm.add_request("network", RequestType(func=self.network._request_manager))
|
||||||
# pass through domain requests to the domain object
|
# pass through domain requests to the domain object
|
||||||
am.add_request("domain", RequestType(func=self.domain._request_manager))
|
rm.add_request("domain", RequestType(func=self.domain._request_manager))
|
||||||
return am
|
return rm
|
||||||
|
|
||||||
def describe_state(self) -> Dict:
|
def describe_state(self) -> Dict:
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ from typing import Dict, Optional
|
|||||||
|
|
||||||
from primaite import getLogger
|
from primaite import getLogger
|
||||||
from primaite.simulator.core import RequestManager, RequestType
|
from primaite.simulator.core import RequestManager, RequestType
|
||||||
from primaite.simulator.system.software import IOSoftware
|
from primaite.simulator.system.software import IOSoftware, SoftwareHealthState
|
||||||
|
|
||||||
_LOGGER = getLogger(__name__)
|
_LOGGER = getLogger(__name__)
|
||||||
|
|
||||||
@@ -34,21 +34,29 @@ class Service(IOSoftware):
|
|||||||
|
|
||||||
operating_state: ServiceOperatingState = ServiceOperatingState.STOPPED
|
operating_state: ServiceOperatingState = ServiceOperatingState.STOPPED
|
||||||
"The current operating state of the Service."
|
"The current operating state of the Service."
|
||||||
|
|
||||||
restart_duration: int = 5
|
restart_duration: int = 5
|
||||||
"How many timesteps does it take to restart this service."
|
"How many timesteps does it take to restart this service."
|
||||||
_restart_countdown: Optional[int] = None
|
restart_countdown: Optional[int] = None
|
||||||
"If currently restarting, how many timesteps remain until the restart is finished."
|
"If currently restarting, how many timesteps remain until the restart is finished."
|
||||||
|
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
|
||||||
|
self.health_state_visible = SoftwareHealthState.UNUSED
|
||||||
|
self.health_state_actual = SoftwareHealthState.UNUSED
|
||||||
|
|
||||||
def _init_request_manager(self) -> RequestManager:
|
def _init_request_manager(self) -> RequestManager:
|
||||||
am = super()._init_request_manager()
|
rm = super()._init_request_manager()
|
||||||
am.add_request("stop", RequestType(func=lambda request, context: self.stop()))
|
rm.add_request("scan", RequestType(func=lambda request, context: self.scan()))
|
||||||
am.add_request("start", RequestType(func=lambda request, context: self.start()))
|
rm.add_request("stop", RequestType(func=lambda request, context: self.stop()))
|
||||||
am.add_request("pause", RequestType(func=lambda request, context: self.pause()))
|
rm.add_request("start", RequestType(func=lambda request, context: self.start()))
|
||||||
am.add_request("resume", RequestType(func=lambda request, context: self.resume()))
|
rm.add_request("pause", RequestType(func=lambda request, context: self.pause()))
|
||||||
am.add_request("restart", RequestType(func=lambda request, context: self.restart()))
|
rm.add_request("resume", RequestType(func=lambda request, context: self.resume()))
|
||||||
am.add_request("disable", RequestType(func=lambda request, context: self.disable()))
|
rm.add_request("restart", RequestType(func=lambda request, context: self.restart()))
|
||||||
am.add_request("enable", RequestType(func=lambda request, context: self.enable()))
|
rm.add_request("disable", RequestType(func=lambda request, context: self.disable()))
|
||||||
return am
|
rm.add_request("enable", RequestType(func=lambda request, context: self.enable()))
|
||||||
|
return rm
|
||||||
|
|
||||||
def describe_state(self) -> Dict:
|
def describe_state(self) -> Dict:
|
||||||
"""
|
"""
|
||||||
@@ -60,7 +68,9 @@ class Service(IOSoftware):
|
|||||||
:rtype: Dict
|
:rtype: Dict
|
||||||
"""
|
"""
|
||||||
state = super().describe_state()
|
state = super().describe_state()
|
||||||
state.update({"operating_state": self.operating_state.name})
|
state["operating_state"] = self.operating_state.name
|
||||||
|
state["health_state_actual"] = self.health_state_actual
|
||||||
|
state["health_state_visible"] = self.health_state_visible
|
||||||
return state
|
return state
|
||||||
|
|
||||||
def reset_component_for_episode(self, episode: int):
|
def reset_component_for_episode(self, episode: int):
|
||||||
@@ -72,47 +82,59 @@ class Service(IOSoftware):
|
|||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def scan(self) -> None:
|
||||||
|
"""Update the service visible states."""
|
||||||
|
# update the visible operating state
|
||||||
|
self.health_state_visible = self.health_state_actual
|
||||||
|
|
||||||
def stop(self) -> None:
|
def stop(self) -> None:
|
||||||
"""Stop the service."""
|
"""Stop the service."""
|
||||||
if self.operating_state in [ServiceOperatingState.RUNNING, ServiceOperatingState.PAUSED]:
|
if self.operating_state in [ServiceOperatingState.RUNNING, ServiceOperatingState.PAUSED]:
|
||||||
self.sys_log.info(f"Stopping service {self.name}")
|
self.sys_log.info(f"Stopping service {self.name}")
|
||||||
self.operating_state = ServiceOperatingState.STOPPED
|
self.operating_state = ServiceOperatingState.STOPPED
|
||||||
|
self.health_state_actual = SoftwareHealthState.UNUSED
|
||||||
|
|
||||||
def start(self, **kwargs) -> None:
|
def start(self, **kwargs) -> None:
|
||||||
"""Start the service."""
|
"""Start the service."""
|
||||||
if self.operating_state == ServiceOperatingState.STOPPED:
|
if self.operating_state == ServiceOperatingState.STOPPED:
|
||||||
self.sys_log.info(f"Starting service {self.name}")
|
self.sys_log.info(f"Starting service {self.name}")
|
||||||
self.operating_state = ServiceOperatingState.RUNNING
|
self.operating_state = ServiceOperatingState.RUNNING
|
||||||
|
self.health_state_actual = SoftwareHealthState.GOOD
|
||||||
|
|
||||||
def pause(self) -> None:
|
def pause(self) -> None:
|
||||||
"""Pause the service."""
|
"""Pause the service."""
|
||||||
if self.operating_state == ServiceOperatingState.RUNNING:
|
if self.operating_state == ServiceOperatingState.RUNNING:
|
||||||
self.sys_log.info(f"Pausing service {self.name}")
|
self.sys_log.info(f"Pausing service {self.name}")
|
||||||
self.operating_state = ServiceOperatingState.PAUSED
|
self.operating_state = ServiceOperatingState.PAUSED
|
||||||
|
self.health_state_actual = SoftwareHealthState.OVERWHELMED
|
||||||
|
|
||||||
def resume(self) -> None:
|
def resume(self) -> None:
|
||||||
"""Resume paused service."""
|
"""Resume paused service."""
|
||||||
if self.operating_state == ServiceOperatingState.PAUSED:
|
if self.operating_state == ServiceOperatingState.PAUSED:
|
||||||
self.sys_log.info(f"Resuming service {self.name}")
|
self.sys_log.info(f"Resuming service {self.name}")
|
||||||
self.operating_state = ServiceOperatingState.RUNNING
|
self.operating_state = ServiceOperatingState.RUNNING
|
||||||
|
self.health_state_actual = SoftwareHealthState.GOOD
|
||||||
|
|
||||||
def restart(self) -> None:
|
def restart(self) -> None:
|
||||||
"""Restart running service."""
|
"""Restart running service."""
|
||||||
if self.operating_state in [ServiceOperatingState.RUNNING, ServiceOperatingState.PAUSED]:
|
if self.operating_state in [ServiceOperatingState.RUNNING, ServiceOperatingState.PAUSED]:
|
||||||
self.sys_log.info(f"Pausing service {self.name}")
|
self.sys_log.info(f"Pausing service {self.name}")
|
||||||
self.operating_state = ServiceOperatingState.RESTARTING
|
self.operating_state = ServiceOperatingState.RESTARTING
|
||||||
self.restart_countdown = self.restarting_duration
|
self.health_state_actual = SoftwareHealthState.OVERWHELMED
|
||||||
|
self.restart_countdown = self.restart_duration
|
||||||
|
|
||||||
def disable(self) -> None:
|
def disable(self) -> None:
|
||||||
"""Disable the service."""
|
"""Disable the service."""
|
||||||
self.sys_log.info(f"Disabling Application {self.name}")
|
self.sys_log.info(f"Disabling Application {self.name}")
|
||||||
self.operating_state = ServiceOperatingState.DISABLED
|
self.operating_state = ServiceOperatingState.DISABLED
|
||||||
|
self.health_state_actual = SoftwareHealthState.OVERWHELMED
|
||||||
|
|
||||||
def enable(self) -> None:
|
def enable(self) -> None:
|
||||||
"""Enable the disabled service."""
|
"""Enable the disabled service."""
|
||||||
if self.operating_state == ServiceOperatingState.DISABLED:
|
if self.operating_state == ServiceOperatingState.DISABLED:
|
||||||
self.sys_log.info(f"Enabling Application {self.name}")
|
self.sys_log.info(f"Enabling Application {self.name}")
|
||||||
self.operating_state = ServiceOperatingState.STOPPED
|
self.operating_state = ServiceOperatingState.STOPPED
|
||||||
|
self.health_state_actual = SoftwareHealthState.OVERWHELMED
|
||||||
|
|
||||||
def apply_timestep(self, timestep: int) -> None:
|
def apply_timestep(self, timestep: int) -> None:
|
||||||
"""
|
"""
|
||||||
@@ -129,4 +151,5 @@ class Service(IOSoftware):
|
|||||||
if self.restart_countdown <= 0:
|
if self.restart_countdown <= 0:
|
||||||
_LOGGER.debug(f"Restarting finished for service {self.name}")
|
_LOGGER.debug(f"Restarting finished for service {self.name}")
|
||||||
self.operating_state = ServiceOperatingState.RUNNING
|
self.operating_state = ServiceOperatingState.RUNNING
|
||||||
|
self.health_state_actual = SoftwareHealthState.GOOD
|
||||||
self.restart_countdown -= 1
|
self.restart_countdown -= 1
|
||||||
|
|||||||
@@ -31,6 +31,8 @@ class SoftwareType(Enum):
|
|||||||
class SoftwareHealthState(Enum):
|
class SoftwareHealthState(Enum):
|
||||||
"""Enumeration of the Software Health States."""
|
"""Enumeration of the Software Health States."""
|
||||||
|
|
||||||
|
UNUSED = 0
|
||||||
|
"Unused state."
|
||||||
GOOD = 1
|
GOOD = 1
|
||||||
"The software is in a good and healthy condition."
|
"The software is in a good and healthy condition."
|
||||||
COMPROMISED = 2
|
COMPROMISED = 2
|
||||||
@@ -88,15 +90,15 @@ class Software(SimComponent):
|
|||||||
"The folder on the file system the Software uses."
|
"The folder on the file system the Software uses."
|
||||||
|
|
||||||
def _init_request_manager(self) -> RequestManager:
|
def _init_request_manager(self) -> RequestManager:
|
||||||
am = super()._init_request_manager()
|
rm = super()._init_request_manager()
|
||||||
am.add_request(
|
rm.add_request(
|
||||||
"compromise",
|
"compromise",
|
||||||
RequestType(
|
RequestType(
|
||||||
func=lambda request, context: self.set_health_state(SoftwareHealthState.COMPROMISED),
|
func=lambda request, context: self.set_health_state(SoftwareHealthState.COMPROMISED),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
am.add_request("scan", RequestType(func=lambda request, context: self.scan()))
|
rm.add_request("scan", RequestType(func=lambda request, context: self.scan()))
|
||||||
return am
|
return rm
|
||||||
|
|
||||||
def _get_session_details(self, session_id: str) -> Session:
|
def _get_session_details(self, session_id: str) -> Session:
|
||||||
"""
|
"""
|
||||||
@@ -241,6 +243,7 @@ class IOSoftware(Software):
|
|||||||
payload=payload, dest_ip_address=dest_ip_address, dest_port=dest_port, session_id=session_id
|
payload=payload, dest_ip_address=dest_ip_address, dest_port=dest_port, session_id=session_id
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
def receive(self, payload: Any, session_id: str, **kwargs) -> bool:
|
def receive(self, payload: Any, session_id: str, **kwargs) -> bool:
|
||||||
"""
|
"""
|
||||||
Receives a payload from the SessionManager.
|
Receives a payload from the SessionManager.
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import shutil
|
|||||||
import tempfile
|
import tempfile
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Union
|
from typing import Any, Union
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
@@ -14,6 +14,9 @@ from primaite.environment.primaite_env import Primaite
|
|||||||
from primaite.primaite_session import PrimaiteSession
|
from primaite.primaite_session import PrimaiteSession
|
||||||
from primaite.simulator.network.container import Network
|
from primaite.simulator.network.container import Network
|
||||||
from primaite.simulator.network.networks import arcd_uc2_network
|
from primaite.simulator.network.networks import arcd_uc2_network
|
||||||
|
from primaite.simulator.network.transmission.transport_layer import Port
|
||||||
|
from primaite.simulator.system.core.sys_log import SysLog
|
||||||
|
from primaite.simulator.system.services.service import Service
|
||||||
from tests.mock_and_patch.get_session_path_mock import get_temp_session_path
|
from tests.mock_and_patch.get_session_path_mock import get_temp_session_path
|
||||||
|
|
||||||
ACTION_SPACE_NODE_VALUES = 1
|
ACTION_SPACE_NODE_VALUES = 1
|
||||||
@@ -26,11 +29,25 @@ from primaite.simulator.file_system.file_system import FileSystem
|
|||||||
from primaite.simulator.network.hardware.base import Node
|
from primaite.simulator.network.hardware.base import Node
|
||||||
|
|
||||||
|
|
||||||
|
class TestService(Service):
|
||||||
|
"""Test Service class"""
|
||||||
|
|
||||||
|
def receive(self, payload: Any, session_id: str, **kwargs) -> bool:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="function")
|
@pytest.fixture(scope="function")
|
||||||
def uc2_network() -> Network:
|
def uc2_network() -> Network:
|
||||||
return arcd_uc2_network()
|
return arcd_uc2_network()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="function")
|
||||||
|
def service(file_system) -> TestService:
|
||||||
|
return TestService(
|
||||||
|
name="TestService", port=Port.ARP, file_system=file_system, sys_log=SysLog(hostname="test_service")
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="function")
|
@pytest.fixture(scope="function")
|
||||||
def file_system() -> FileSystem:
|
def file_system() -> FileSystem:
|
||||||
return Node(hostname="fs_node").file_system
|
return Node(hostname="fs_node").file_system
|
||||||
|
|||||||
@@ -1,17 +1,15 @@
|
|||||||
from primaite.simulator.network.hardware.base import Link, NIC, Node
|
from primaite.simulator.network.hardware.base import Link, NIC, Node, NodeOperatingState
|
||||||
|
|
||||||
|
|
||||||
def test_node_to_node_ping():
|
def test_node_to_node_ping():
|
||||||
"""Tests two Nodes are able to ping each other."""
|
"""Tests two Nodes are able to ping each other."""
|
||||||
node_a = Node(hostname="node_a")
|
node_a = Node(hostname="node_a", operating_state=NodeOperatingState.ON)
|
||||||
nic_a = NIC(ip_address="192.168.0.10", subnet_mask="255.255.255.0")
|
nic_a = NIC(ip_address="192.168.0.10", subnet_mask="255.255.255.0", operating_state=NodeOperatingState.ON)
|
||||||
node_a.connect_nic(nic_a)
|
node_a.connect_nic(nic_a)
|
||||||
node_a.power_on()
|
|
||||||
|
|
||||||
node_b = Node(hostname="node_b")
|
node_b = Node(hostname="node_b", operating_state=NodeOperatingState.ON)
|
||||||
nic_b = NIC(ip_address="192.168.0.11", subnet_mask="255.255.255.0")
|
nic_b = NIC(ip_address="192.168.0.11", subnet_mask="255.255.255.0")
|
||||||
node_b.connect_nic(nic_b)
|
node_b.connect_nic(nic_b)
|
||||||
node_b.power_on()
|
|
||||||
|
|
||||||
Link(endpoint_a=nic_a, endpoint_b=nic_b)
|
Link(endpoint_a=nic_a, endpoint_b=nic_b)
|
||||||
|
|
||||||
@@ -20,22 +18,19 @@ def test_node_to_node_ping():
|
|||||||
|
|
||||||
def test_multi_nic():
|
def test_multi_nic():
|
||||||
"""Tests that Nodes with multiple NICs can ping each other and the data go across the correct links."""
|
"""Tests that Nodes with multiple NICs can ping each other and the data go across the correct links."""
|
||||||
node_a = Node(hostname="node_a")
|
node_a = Node(hostname="node_a", operating_state=NodeOperatingState.ON)
|
||||||
nic_a = NIC(ip_address="192.168.0.10", subnet_mask="255.255.255.0")
|
nic_a = NIC(ip_address="192.168.0.10", subnet_mask="255.255.255.0")
|
||||||
node_a.connect_nic(nic_a)
|
node_a.connect_nic(nic_a)
|
||||||
node_a.power_on()
|
|
||||||
|
|
||||||
node_b = Node(hostname="node_b")
|
node_b = Node(hostname="node_b", operating_state=NodeOperatingState.ON)
|
||||||
nic_b1 = NIC(ip_address="192.168.0.11", subnet_mask="255.255.255.0")
|
nic_b1 = NIC(ip_address="192.168.0.11", subnet_mask="255.255.255.0")
|
||||||
nic_b2 = NIC(ip_address="10.0.0.12", subnet_mask="255.0.0.0")
|
nic_b2 = NIC(ip_address="10.0.0.12", subnet_mask="255.0.0.0")
|
||||||
node_b.connect_nic(nic_b1)
|
node_b.connect_nic(nic_b1)
|
||||||
node_b.connect_nic(nic_b2)
|
node_b.connect_nic(nic_b2)
|
||||||
node_b.power_on()
|
|
||||||
|
|
||||||
node_c = Node(hostname="node_c")
|
node_c = Node(hostname="node_c", operating_state=NodeOperatingState.ON)
|
||||||
nic_c = NIC(ip_address="10.0.0.13", subnet_mask="255.0.0.0")
|
nic_c = NIC(ip_address="10.0.0.13", subnet_mask="255.0.0.0")
|
||||||
node_c.connect_nic(nic_c)
|
node_c.connect_nic(nic_c)
|
||||||
node_c.power_on()
|
|
||||||
|
|
||||||
Link(endpoint_a=nic_a, endpoint_b=nic_b1)
|
Link(endpoint_a=nic_a, endpoint_b=nic_b1)
|
||||||
|
|
||||||
|
|||||||
@@ -1,17 +1,15 @@
|
|||||||
from primaite.simulator.network.hardware.base import Link, NIC, Node
|
from primaite.simulator.network.hardware.base import Link, NIC, Node, NodeOperatingState
|
||||||
|
|
||||||
|
|
||||||
def test_link_up():
|
def test_link_up():
|
||||||
"""Tests Nodes, NICs, and Links can all be connected and be in an enabled/up state."""
|
"""Tests Nodes, NICs, and Links can all be connected and be in an enabled/up state."""
|
||||||
node_a = Node(hostname="node_a")
|
node_a = Node(hostname="node_a", operating_state=NodeOperatingState.ON)
|
||||||
nic_a = NIC(ip_address="192.168.0.10", subnet_mask="255.255.255.0")
|
nic_a = NIC(ip_address="192.168.0.10", subnet_mask="255.255.255.0")
|
||||||
node_a.connect_nic(nic_a)
|
node_a.connect_nic(nic_a)
|
||||||
node_a.power_on()
|
|
||||||
|
|
||||||
node_b = Node(hostname="node_b")
|
node_b = Node(hostname="node_b", operating_state=NodeOperatingState.ON)
|
||||||
nic_b = NIC(ip_address="192.168.0.11", subnet_mask="255.255.255.0")
|
nic_b = NIC(ip_address="192.168.0.11", subnet_mask="255.255.255.0")
|
||||||
node_b.connect_nic(nic_b)
|
node_b.connect_nic(nic_b)
|
||||||
node_b.power_on()
|
|
||||||
|
|
||||||
link = Link(endpoint_a=nic_a, endpoint_b=nic_b)
|
link = Link(endpoint_a=nic_a, endpoint_b=nic_b)
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ from typing import Tuple
|
|||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from primaite.simulator.network.hardware.base import Link, NIC, Node
|
from primaite.simulator.network.hardware.base import Link, NIC, Node, NodeOperatingState
|
||||||
from primaite.simulator.network.hardware.nodes.router import ACLAction, Router
|
from primaite.simulator.network.hardware.nodes.router import ACLAction, Router
|
||||||
from primaite.simulator.network.transmission.network_layer import IPProtocol
|
from primaite.simulator.network.transmission.network_layer import IPProtocol
|
||||||
from primaite.simulator.network.transmission.transport_layer import Port
|
from primaite.simulator.network.transmission.transport_layer import Port
|
||||||
@@ -10,18 +10,15 @@ from primaite.simulator.network.transmission.transport_layer import Port
|
|||||||
|
|
||||||
@pytest.fixture(scope="function")
|
@pytest.fixture(scope="function")
|
||||||
def pc_a_pc_b_router_1() -> Tuple[Node, Node, Router]:
|
def pc_a_pc_b_router_1() -> Tuple[Node, Node, Router]:
|
||||||
pc_a = Node(hostname="pc_a", default_gateway="192.168.0.1")
|
pc_a = Node(hostname="pc_a", default_gateway="192.168.0.1", operating_state=NodeOperatingState.ON)
|
||||||
nic_a = NIC(ip_address="192.168.0.10", subnet_mask="255.255.255.0")
|
nic_a = NIC(ip_address="192.168.0.10", subnet_mask="255.255.255.0")
|
||||||
pc_a.connect_nic(nic_a)
|
pc_a.connect_nic(nic_a)
|
||||||
pc_a.power_on()
|
|
||||||
|
|
||||||
pc_b = Node(hostname="pc_b", default_gateway="192.168.1.1")
|
pc_b = Node(hostname="pc_b", default_gateway="192.168.1.1", operating_state=NodeOperatingState.ON)
|
||||||
nic_b = NIC(ip_address="192.168.1.10", subnet_mask="255.255.255.0")
|
nic_b = NIC(ip_address="192.168.1.10", subnet_mask="255.255.255.0")
|
||||||
pc_b.connect_nic(nic_b)
|
pc_b.connect_nic(nic_b)
|
||||||
pc_b.power_on()
|
|
||||||
|
|
||||||
router_1 = Router(hostname="router_1")
|
router_1 = Router(hostname="router_1", operating_state=NodeOperatingState.ON)
|
||||||
router_1.power_on()
|
|
||||||
|
|
||||||
router_1.configure_port(1, "192.168.0.1", "255.255.255.0")
|
router_1.configure_port(1, "192.168.0.1", "255.255.255.0")
|
||||||
router_1.configure_port(2, "192.168.1.1", "255.255.255.0")
|
router_1.configure_port(2, "192.168.1.1", "255.255.255.0")
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
from primaite.simulator.network.hardware.base import Link
|
from primaite.simulator.network.hardware.base import Link, NodeOperatingState
|
||||||
from primaite.simulator.network.hardware.nodes.computer import Computer
|
from primaite.simulator.network.hardware.nodes.computer import Computer
|
||||||
from primaite.simulator.network.hardware.nodes.server import Server
|
from primaite.simulator.network.hardware.nodes.server import Server
|
||||||
from primaite.simulator.network.hardware.nodes.switch import Switch
|
from primaite.simulator.network.hardware.nodes.switch import Switch
|
||||||
@@ -7,17 +7,22 @@ from primaite.simulator.network.hardware.nodes.switch import Switch
|
|||||||
def test_switched_network():
|
def test_switched_network():
|
||||||
"""Tests a node can ping another node via the switch."""
|
"""Tests a node can ping another node via the switch."""
|
||||||
client_1 = Computer(
|
client_1 = Computer(
|
||||||
hostname="client_1", ip_address="192.168.1.10", subnet_mask="255.255.255.0", default_gateway="192.168.1.0"
|
hostname="client_1",
|
||||||
|
ip_address="192.168.1.10",
|
||||||
|
subnet_mask="255.255.255.0",
|
||||||
|
default_gateway="192.168.1.0",
|
||||||
|
operating_state=NodeOperatingState.ON,
|
||||||
)
|
)
|
||||||
client_1.power_on()
|
|
||||||
|
|
||||||
server_1 = Server(
|
server_1 = Server(
|
||||||
hostname=" server_1", ip_address="192.168.1.11", subnet_mask="255.255.255.0", default_gateway="192.168.1.11"
|
hostname=" server_1",
|
||||||
|
ip_address="192.168.1.11",
|
||||||
|
subnet_mask="255.255.255.0",
|
||||||
|
default_gateway="192.168.1.11",
|
||||||
|
operating_state=NodeOperatingState.ON,
|
||||||
)
|
)
|
||||||
server_1.power_on()
|
|
||||||
|
|
||||||
switch_1 = Switch(hostname="switch_1", num_ports=6)
|
switch_1 = Switch(hostname="switch_1", num_ports=6, operating_state=NodeOperatingState.ON)
|
||||||
switch_1.power_on()
|
|
||||||
|
|
||||||
Link(endpoint_a=client_1.ethernet_port[1], endpoint_b=switch_1.switch_ports[1])
|
Link(endpoint_a=client_1.ethernet_port[1], endpoint_b=switch_1.switch_ports[1])
|
||||||
Link(endpoint_a=server_1.ethernet_port[1], endpoint_b=switch_1.switch_ports[2])
|
Link(endpoint_a=server_1.ethernet_port[1], endpoint_b=switch_1.switch_ports[2])
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from primaite.simulator.file_system.file_system import FileSystem
|
from primaite.simulator.file_system.file_system import File, FileSystem, FileSystemItemHealthStatus, Folder
|
||||||
from primaite.simulator.file_system.file_type import FileType
|
from primaite.simulator.file_system.file_type import FileType
|
||||||
|
|
||||||
|
|
||||||
@@ -122,6 +122,7 @@ def test_copy_file(file_system):
|
|||||||
assert file_system.get_file("dst_folder", "test_file.txt").uuid != original_uuid
|
assert file_system.get_file("dst_folder", "test_file.txt").uuid != original_uuid
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.skip(reason="Implementation for quarantine not needed yet")
|
||||||
def test_folder_quarantine_state(file_system):
|
def test_folder_quarantine_state(file_system):
|
||||||
"""Tests the changing of folder quarantine status."""
|
"""Tests the changing of folder quarantine status."""
|
||||||
folder = file_system.get_folder("root")
|
folder = file_system.get_folder("root")
|
||||||
@@ -135,6 +136,158 @@ def test_folder_quarantine_state(file_system):
|
|||||||
assert folder.quarantine_status() is False
|
assert folder.quarantine_status() is False
|
||||||
|
|
||||||
|
|
||||||
|
def test_file_corrupt_repair(file_system):
|
||||||
|
"""Test the ability to corrupt and repair files."""
|
||||||
|
folder: Folder = file_system.create_folder(folder_name="test_folder")
|
||||||
|
file: File = file_system.create_file(file_name="test_file.txt", folder_name="test_folder")
|
||||||
|
|
||||||
|
file.corrupt()
|
||||||
|
|
||||||
|
assert folder.health_status == FileSystemItemHealthStatus.GOOD
|
||||||
|
assert file.health_status == FileSystemItemHealthStatus.CORRUPT
|
||||||
|
|
||||||
|
file.repair()
|
||||||
|
|
||||||
|
assert folder.health_status == FileSystemItemHealthStatus.GOOD
|
||||||
|
assert file.health_status == FileSystemItemHealthStatus.GOOD
|
||||||
|
|
||||||
|
|
||||||
|
def test_folder_corrupt_repair(file_system):
|
||||||
|
"""Test the ability to corrupt and repair folders."""
|
||||||
|
folder: Folder = file_system.create_folder(folder_name="test_folder")
|
||||||
|
file_system.create_file(file_name="test_file.txt", folder_name="test_folder")
|
||||||
|
|
||||||
|
folder.corrupt()
|
||||||
|
|
||||||
|
file = folder.get_file(file_name="test_file.txt")
|
||||||
|
assert folder.health_status == FileSystemItemHealthStatus.CORRUPT
|
||||||
|
assert file.health_status == FileSystemItemHealthStatus.CORRUPT
|
||||||
|
|
||||||
|
folder.repair()
|
||||||
|
|
||||||
|
file = folder.get_file(file_name="test_file.txt")
|
||||||
|
assert folder.health_status == FileSystemItemHealthStatus.GOOD
|
||||||
|
assert file.health_status == FileSystemItemHealthStatus.GOOD
|
||||||
|
|
||||||
|
|
||||||
|
def test_file_scan(file_system):
|
||||||
|
"""Test the ability to update visible status."""
|
||||||
|
folder: Folder = file_system.create_folder(folder_name="test_folder")
|
||||||
|
file: File = file_system.create_file(file_name="test_file.txt", folder_name="test_folder")
|
||||||
|
|
||||||
|
assert file.health_status == FileSystemItemHealthStatus.GOOD
|
||||||
|
assert file.visible_health_status == FileSystemItemHealthStatus.GOOD
|
||||||
|
|
||||||
|
file.corrupt()
|
||||||
|
|
||||||
|
assert file.health_status == FileSystemItemHealthStatus.CORRUPT
|
||||||
|
assert file.visible_health_status == FileSystemItemHealthStatus.GOOD
|
||||||
|
|
||||||
|
file.scan()
|
||||||
|
|
||||||
|
assert file.health_status == FileSystemItemHealthStatus.CORRUPT
|
||||||
|
assert file.visible_health_status == FileSystemItemHealthStatus.CORRUPT
|
||||||
|
|
||||||
|
|
||||||
|
def test_folder_scan(file_system):
|
||||||
|
"""Test the ability to update visible status."""
|
||||||
|
folder: Folder = file_system.create_folder(folder_name="test_folder")
|
||||||
|
file_system.create_file(file_name="test_file.txt", folder_name="test_folder")
|
||||||
|
file_system.create_file(file_name="test_file2.txt", folder_name="test_folder")
|
||||||
|
|
||||||
|
file1: File = folder.get_file_by_id(file_uuid=list(folder.files)[1])
|
||||||
|
file2: File = folder.get_file_by_id(file_uuid=list(folder.files)[0])
|
||||||
|
|
||||||
|
assert folder.health_status == FileSystemItemHealthStatus.GOOD
|
||||||
|
assert folder.visible_health_status == FileSystemItemHealthStatus.GOOD
|
||||||
|
assert file1.visible_health_status == FileSystemItemHealthStatus.GOOD
|
||||||
|
assert file2.visible_health_status == FileSystemItemHealthStatus.GOOD
|
||||||
|
|
||||||
|
folder.corrupt()
|
||||||
|
|
||||||
|
assert folder.health_status == FileSystemItemHealthStatus.CORRUPT
|
||||||
|
assert folder.visible_health_status == FileSystemItemHealthStatus.GOOD
|
||||||
|
assert file1.visible_health_status == FileSystemItemHealthStatus.GOOD
|
||||||
|
assert file2.visible_health_status == FileSystemItemHealthStatus.GOOD
|
||||||
|
|
||||||
|
folder.scan()
|
||||||
|
|
||||||
|
folder.apply_timestep(timestep=0)
|
||||||
|
|
||||||
|
assert folder.health_status == FileSystemItemHealthStatus.CORRUPT
|
||||||
|
assert folder.visible_health_status == FileSystemItemHealthStatus.CORRUPT
|
||||||
|
assert file1.visible_health_status == FileSystemItemHealthStatus.CORRUPT
|
||||||
|
assert file2.visible_health_status == FileSystemItemHealthStatus.GOOD
|
||||||
|
|
||||||
|
folder.apply_timestep(timestep=1)
|
||||||
|
|
||||||
|
assert folder.health_status == FileSystemItemHealthStatus.CORRUPT
|
||||||
|
assert folder.visible_health_status == FileSystemItemHealthStatus.CORRUPT
|
||||||
|
assert file1.visible_health_status == FileSystemItemHealthStatus.CORRUPT
|
||||||
|
assert file2.visible_health_status == FileSystemItemHealthStatus.CORRUPT
|
||||||
|
|
||||||
|
folder.apply_timestep(timestep=2)
|
||||||
|
|
||||||
|
assert folder.health_status == FileSystemItemHealthStatus.CORRUPT
|
||||||
|
assert folder.visible_health_status == FileSystemItemHealthStatus.CORRUPT
|
||||||
|
assert file1.visible_health_status == FileSystemItemHealthStatus.CORRUPT
|
||||||
|
assert file2.visible_health_status == FileSystemItemHealthStatus.CORRUPT
|
||||||
|
|
||||||
|
|
||||||
|
def test_simulated_file_check_hash(file_system):
|
||||||
|
file: File = file_system.create_file(file_name="test_file.txt", folder_name="test_folder")
|
||||||
|
|
||||||
|
assert file.check_hash() is True
|
||||||
|
|
||||||
|
# change simulated file size
|
||||||
|
file.sim_size = 0
|
||||||
|
assert file.check_hash() is False
|
||||||
|
assert file.health_status == FileSystemItemHealthStatus.CORRUPT
|
||||||
|
|
||||||
|
|
||||||
|
def test_real_file_check_hash(file_system):
|
||||||
|
file: File = file_system.create_file(file_name="test_file.txt", folder_name="test_folder", real=True)
|
||||||
|
|
||||||
|
assert file.check_hash() is True
|
||||||
|
|
||||||
|
# change file content
|
||||||
|
with open(file.sim_path, "a") as f:
|
||||||
|
f.write("get hacked scrub lol xD\n")
|
||||||
|
|
||||||
|
assert file.check_hash() is False
|
||||||
|
assert file.health_status == FileSystemItemHealthStatus.CORRUPT
|
||||||
|
|
||||||
|
|
||||||
|
def test_simulated_folder_check_hash(file_system):
|
||||||
|
folder: Folder = file_system.create_folder(folder_name="test_folder")
|
||||||
|
file_system.create_file(file_name="test_file.txt", folder_name="test_folder")
|
||||||
|
|
||||||
|
assert folder.check_hash() is True
|
||||||
|
|
||||||
|
# change simulated file size
|
||||||
|
file = folder.get_file(file_name="test_file.txt")
|
||||||
|
file.sim_size = 0
|
||||||
|
assert folder.check_hash() is False
|
||||||
|
assert folder.health_status == FileSystemItemHealthStatus.CORRUPT
|
||||||
|
|
||||||
|
|
||||||
|
def test_real_folder_check_hash(file_system):
|
||||||
|
folder: Folder = file_system.create_folder(folder_name="test_folder")
|
||||||
|
file_system.create_file(file_name="test_file.txt", folder_name="test_folder", real=True)
|
||||||
|
|
||||||
|
assert folder.check_hash() is True
|
||||||
|
|
||||||
|
# change simulated file size
|
||||||
|
file = folder.get_file(file_name="test_file.txt")
|
||||||
|
|
||||||
|
# change file content
|
||||||
|
with open(file.sim_path, "a") as f:
|
||||||
|
f.write("get hacked scrub lol xD\n")
|
||||||
|
|
||||||
|
assert folder.check_hash() is False
|
||||||
|
assert folder.health_status == FileSystemItemHealthStatus.CORRUPT
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skip(reason="Skipping until we tackle serialisation")
|
@pytest.mark.skip(reason="Skipping until we tackle serialisation")
|
||||||
def test_serialisation(file_system):
|
def test_serialisation(file_system):
|
||||||
"""Test to check that the object serialisation works correctly."""
|
"""Test to check that the object serialisation works correctly."""
|
||||||
|
|||||||
@@ -0,0 +1,160 @@
|
|||||||
|
from typing import Tuple
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from primaite.simulator.file_system.file_system import File, FileSystem, FileSystemItemHealthStatus, Folder
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="function")
|
||||||
|
def populated_file_system(file_system) -> Tuple[FileSystem, Folder, File]:
|
||||||
|
"""Test that an agent can request a file scan."""
|
||||||
|
folder = file_system.create_folder(folder_name="test_folder")
|
||||||
|
file = file_system.create_file(folder_name="test_folder", file_name="test_file.txt")
|
||||||
|
|
||||||
|
return file_system, folder, file
|
||||||
|
|
||||||
|
|
||||||
|
def test_file_scan_request(populated_file_system):
|
||||||
|
"""Test that an agent can request a file scan."""
|
||||||
|
fs, folder, file = populated_file_system
|
||||||
|
|
||||||
|
file.corrupt()
|
||||||
|
assert file.health_status == FileSystemItemHealthStatus.CORRUPT
|
||||||
|
assert file.visible_health_status == FileSystemItemHealthStatus.GOOD
|
||||||
|
|
||||||
|
fs.apply_request(request=["file", file.uuid, "scan"])
|
||||||
|
|
||||||
|
assert file.health_status == FileSystemItemHealthStatus.CORRUPT
|
||||||
|
assert file.visible_health_status == FileSystemItemHealthStatus.CORRUPT
|
||||||
|
|
||||||
|
|
||||||
|
def test_folder_scan_request(populated_file_system):
|
||||||
|
"""Test that an agent can request a folder scan."""
|
||||||
|
fs, folder, file = populated_file_system
|
||||||
|
fs.create_file(file_name="test_file2.txt", folder_name="test_folder")
|
||||||
|
|
||||||
|
file1: File = folder.get_file_by_id(file_uuid=list(folder.files)[1])
|
||||||
|
file2: File = folder.get_file_by_id(file_uuid=list(folder.files)[0])
|
||||||
|
|
||||||
|
folder.corrupt()
|
||||||
|
assert folder.health_status == FileSystemItemHealthStatus.CORRUPT
|
||||||
|
assert folder.visible_health_status == FileSystemItemHealthStatus.GOOD
|
||||||
|
assert file1.visible_health_status == FileSystemItemHealthStatus.GOOD
|
||||||
|
assert file2.visible_health_status == FileSystemItemHealthStatus.GOOD
|
||||||
|
|
||||||
|
fs.apply_request(request=["folder", folder.uuid, "scan"])
|
||||||
|
|
||||||
|
folder.apply_timestep(timestep=0)
|
||||||
|
|
||||||
|
assert folder.health_status == FileSystemItemHealthStatus.CORRUPT
|
||||||
|
assert folder.visible_health_status == FileSystemItemHealthStatus.CORRUPT
|
||||||
|
assert file1.visible_health_status == FileSystemItemHealthStatus.CORRUPT
|
||||||
|
assert file2.visible_health_status == FileSystemItemHealthStatus.GOOD
|
||||||
|
|
||||||
|
folder.apply_timestep(timestep=1)
|
||||||
|
|
||||||
|
assert folder.health_status == FileSystemItemHealthStatus.CORRUPT
|
||||||
|
assert folder.visible_health_status == FileSystemItemHealthStatus.CORRUPT
|
||||||
|
assert file1.visible_health_status == FileSystemItemHealthStatus.CORRUPT
|
||||||
|
assert file2.visible_health_status == FileSystemItemHealthStatus.CORRUPT
|
||||||
|
|
||||||
|
folder.apply_timestep(timestep=2)
|
||||||
|
|
||||||
|
assert folder.health_status == FileSystemItemHealthStatus.CORRUPT
|
||||||
|
assert folder.visible_health_status == FileSystemItemHealthStatus.CORRUPT
|
||||||
|
assert file1.visible_health_status == FileSystemItemHealthStatus.CORRUPT
|
||||||
|
assert file2.visible_health_status == FileSystemItemHealthStatus.CORRUPT
|
||||||
|
|
||||||
|
|
||||||
|
def test_file_checkhash_request(populated_file_system):
|
||||||
|
"""Test that an agent can request a file hash check."""
|
||||||
|
fs, folder, file = populated_file_system
|
||||||
|
|
||||||
|
fs.apply_request(request=["file", file.uuid, "checkhash"])
|
||||||
|
|
||||||
|
assert file.health_status == FileSystemItemHealthStatus.GOOD
|
||||||
|
file.sim_size = 0
|
||||||
|
|
||||||
|
fs.apply_request(request=["file", file.uuid, "checkhash"])
|
||||||
|
|
||||||
|
assert file.health_status == FileSystemItemHealthStatus.CORRUPT
|
||||||
|
|
||||||
|
|
||||||
|
def test_folder_checkhash_request(populated_file_system):
|
||||||
|
"""Test that an agent can request a folder hash check."""
|
||||||
|
fs, folder, file = populated_file_system
|
||||||
|
|
||||||
|
fs.apply_request(request=["folder", folder.uuid, "checkhash"])
|
||||||
|
|
||||||
|
assert folder.health_status == FileSystemItemHealthStatus.GOOD
|
||||||
|
file.sim_size = 0
|
||||||
|
|
||||||
|
fs.apply_request(request=["folder", folder.uuid, "checkhash"])
|
||||||
|
assert folder.health_status == FileSystemItemHealthStatus.CORRUPT
|
||||||
|
|
||||||
|
|
||||||
|
def test_file_repair_request(populated_file_system):
|
||||||
|
"""Test that an agent can request a file repair."""
|
||||||
|
fs, folder, file = populated_file_system
|
||||||
|
|
||||||
|
file.corrupt()
|
||||||
|
assert file.health_status == FileSystemItemHealthStatus.CORRUPT
|
||||||
|
|
||||||
|
fs.apply_request(request=["file", file.uuid, "repair"])
|
||||||
|
assert file.health_status == FileSystemItemHealthStatus.GOOD
|
||||||
|
|
||||||
|
|
||||||
|
def test_folder_repair_request(populated_file_system):
|
||||||
|
"""Test that an agent can request a folder repair."""
|
||||||
|
fs, folder, file = populated_file_system
|
||||||
|
|
||||||
|
folder.corrupt()
|
||||||
|
assert file.health_status == FileSystemItemHealthStatus.CORRUPT
|
||||||
|
assert folder.health_status == FileSystemItemHealthStatus.CORRUPT
|
||||||
|
|
||||||
|
fs.apply_request(request=["folder", folder.uuid, "repair"])
|
||||||
|
assert file.health_status == FileSystemItemHealthStatus.GOOD
|
||||||
|
assert folder.health_status == FileSystemItemHealthStatus.GOOD
|
||||||
|
|
||||||
|
|
||||||
|
def test_file_restore_request(populated_file_system):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def test_folder_restore_request(populated_file_system):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def test_file_corrupt_request(populated_file_system):
|
||||||
|
"""Test that an agent can request a file corruption."""
|
||||||
|
fs, folder, file = populated_file_system
|
||||||
|
fs.apply_request(request=["file", file.uuid, "corrupt"])
|
||||||
|
assert file.health_status == FileSystemItemHealthStatus.CORRUPT
|
||||||
|
|
||||||
|
|
||||||
|
def test_folder_corrupt_request(populated_file_system):
|
||||||
|
"""Test that an agent can request a folder corruption."""
|
||||||
|
fs, folder, file = populated_file_system
|
||||||
|
fs.apply_request(request=["folder", folder.uuid, "corrupt"])
|
||||||
|
assert file.health_status == FileSystemItemHealthStatus.CORRUPT
|
||||||
|
assert folder.health_status == FileSystemItemHealthStatus.CORRUPT
|
||||||
|
|
||||||
|
|
||||||
|
def test_file_delete_request(populated_file_system):
|
||||||
|
"""Test that an agent can request a file deletion."""
|
||||||
|
fs, folder, file = populated_file_system
|
||||||
|
assert folder.get_file_by_id(file_uuid=file.uuid) is not None
|
||||||
|
|
||||||
|
fs.apply_request(request=["folder", folder.uuid, "delete", file.uuid])
|
||||||
|
assert folder.get_file_by_id(file_uuid=file.uuid) is None
|
||||||
|
|
||||||
|
|
||||||
|
def test_folder_delete_request(populated_file_system):
|
||||||
|
"""Test that an agent can request a folder deletion."""
|
||||||
|
fs, folder, file = populated_file_system
|
||||||
|
assert folder.get_file_by_id(file_uuid=file.uuid) is not None
|
||||||
|
assert fs.get_folder_by_id(folder_uuid=folder.uuid) is not None
|
||||||
|
|
||||||
|
fs.apply_request(request=["delete", folder.uuid])
|
||||||
|
assert fs.get_folder_by_id(folder_uuid=folder.uuid) is None
|
||||||
|
assert folder.get_file_by_id(file_uuid=file.uuid) is None
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
import pytest
|
||||||
|
|
||||||
|
from primaite.simulator.network.hardware.base import Node, NodeOperatingState
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def node() -> Node:
|
||||||
|
return Node(hostname="test")
|
||||||
|
|
||||||
|
|
||||||
|
def test_node_startup(node):
|
||||||
|
assert node.operating_state == NodeOperatingState.OFF
|
||||||
|
node.apply_request(["startup"])
|
||||||
|
assert node.operating_state == NodeOperatingState.BOOTING
|
||||||
|
|
||||||
|
idx = 0
|
||||||
|
while node.operating_state == NodeOperatingState.BOOTING:
|
||||||
|
node.apply_timestep(timestep=idx)
|
||||||
|
idx += 1
|
||||||
|
|
||||||
|
assert node.operating_state == NodeOperatingState.ON
|
||||||
|
|
||||||
|
|
||||||
|
def test_node_shutdown(node):
|
||||||
|
assert node.operating_state == NodeOperatingState.OFF
|
||||||
|
node.apply_request(["startup"])
|
||||||
|
idx = 0
|
||||||
|
while node.operating_state == NodeOperatingState.BOOTING:
|
||||||
|
node.apply_timestep(timestep=idx)
|
||||||
|
idx += 1
|
||||||
|
|
||||||
|
assert node.operating_state == NodeOperatingState.ON
|
||||||
|
|
||||||
|
node.apply_request(["shutdown"])
|
||||||
|
|
||||||
|
idx = 0
|
||||||
|
while node.operating_state == NodeOperatingState.SHUTTING_DOWN:
|
||||||
|
node.apply_timestep(timestep=idx)
|
||||||
|
idx += 1
|
||||||
|
|
||||||
|
assert node.operating_state == NodeOperatingState.OFF
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
from primaite.simulator.system.services.service import ServiceOperatingState
|
||||||
|
from primaite.simulator.system.software import SoftwareHealthState
|
||||||
|
|
||||||
|
|
||||||
|
def test_service_scan(service):
|
||||||
|
"""Test that an agent can request a service scan."""
|
||||||
|
service.start()
|
||||||
|
assert service.operating_state == ServiceOperatingState.RUNNING
|
||||||
|
assert service.health_state_visible == SoftwareHealthState.UNUSED
|
||||||
|
|
||||||
|
service.apply_request(["scan"])
|
||||||
|
assert service.operating_state == ServiceOperatingState.RUNNING
|
||||||
|
assert service.health_state_visible == SoftwareHealthState.GOOD
|
||||||
|
|
||||||
|
|
||||||
|
def test_service_stop(service):
|
||||||
|
"""Test that an agent can request to stop a service."""
|
||||||
|
service.start()
|
||||||
|
assert service.operating_state == ServiceOperatingState.RUNNING
|
||||||
|
|
||||||
|
service.apply_request(["stop"])
|
||||||
|
assert service.operating_state == ServiceOperatingState.STOPPED
|
||||||
|
|
||||||
|
|
||||||
|
def test_service_start(service):
|
||||||
|
"""Test that an agent can request to start a service."""
|
||||||
|
assert service.operating_state == ServiceOperatingState.STOPPED
|
||||||
|
service.apply_request(["start"])
|
||||||
|
assert service.operating_state == ServiceOperatingState.RUNNING
|
||||||
|
|
||||||
|
|
||||||
|
def test_service_pause(service):
|
||||||
|
"""Test that an agent can request to pause a service."""
|
||||||
|
service.start()
|
||||||
|
assert service.operating_state == ServiceOperatingState.RUNNING
|
||||||
|
|
||||||
|
service.apply_request(["pause"])
|
||||||
|
assert service.operating_state == ServiceOperatingState.PAUSED
|
||||||
|
|
||||||
|
|
||||||
|
def test_service_resume(service):
|
||||||
|
"""Test that an agent can request to resume a service."""
|
||||||
|
service.start()
|
||||||
|
assert service.operating_state == ServiceOperatingState.RUNNING
|
||||||
|
|
||||||
|
service.apply_request(["pause"])
|
||||||
|
assert service.operating_state == ServiceOperatingState.PAUSED
|
||||||
|
|
||||||
|
service.apply_request(["resume"])
|
||||||
|
assert service.operating_state == ServiceOperatingState.RUNNING
|
||||||
|
|
||||||
|
|
||||||
|
def test_service_restart(service):
|
||||||
|
"""Test that an agent can request to restart a service."""
|
||||||
|
service.start()
|
||||||
|
assert service.operating_state == ServiceOperatingState.RUNNING
|
||||||
|
|
||||||
|
service.apply_request(["restart"])
|
||||||
|
assert service.operating_state == ServiceOperatingState.RESTARTING
|
||||||
|
|
||||||
|
|
||||||
|
def test_service_disable(service):
|
||||||
|
"""Test that an agent can request to disable a service."""
|
||||||
|
service.start()
|
||||||
|
assert service.operating_state == ServiceOperatingState.RUNNING
|
||||||
|
|
||||||
|
service.apply_request(["disable"])
|
||||||
|
assert service.operating_state == ServiceOperatingState.DISABLED
|
||||||
|
|
||||||
|
|
||||||
|
def test_service_enable(service):
|
||||||
|
"""Test that an agent can request to enable a service."""
|
||||||
|
service.start()
|
||||||
|
assert service.operating_state == ServiceOperatingState.RUNNING
|
||||||
|
|
||||||
|
service.apply_request(["disable"])
|
||||||
|
assert service.operating_state == ServiceOperatingState.DISABLED
|
||||||
|
|
||||||
|
service.apply_request(["enable"])
|
||||||
|
assert service.operating_state == ServiceOperatingState.STOPPED
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
from primaite.simulator.system.services.service import ServiceOperatingState
|
||||||
|
from primaite.simulator.system.software import SoftwareHealthState
|
||||||
|
|
||||||
|
|
||||||
|
def test_scan(service):
|
||||||
|
assert service.operating_state == ServiceOperatingState.STOPPED
|
||||||
|
assert service.health_state_visible == SoftwareHealthState.UNUSED
|
||||||
|
|
||||||
|
service.start()
|
||||||
|
assert service.operating_state == ServiceOperatingState.RUNNING
|
||||||
|
assert service.health_state_visible == SoftwareHealthState.UNUSED
|
||||||
|
|
||||||
|
service.scan()
|
||||||
|
assert service.operating_state == ServiceOperatingState.RUNNING
|
||||||
|
assert service.health_state_visible == SoftwareHealthState.GOOD
|
||||||
|
|
||||||
|
|
||||||
|
def test_start_service(service):
|
||||||
|
assert service.operating_state == ServiceOperatingState.STOPPED
|
||||||
|
service.start()
|
||||||
|
|
||||||
|
assert service.operating_state == ServiceOperatingState.RUNNING
|
||||||
|
|
||||||
|
|
||||||
|
def test_stop_service(service):
|
||||||
|
service.start()
|
||||||
|
assert service.operating_state == ServiceOperatingState.RUNNING
|
||||||
|
|
||||||
|
service.stop()
|
||||||
|
assert service.operating_state == ServiceOperatingState.STOPPED
|
||||||
|
|
||||||
|
|
||||||
|
def test_pause_and_resume_service(service):
|
||||||
|
assert service.operating_state == ServiceOperatingState.STOPPED
|
||||||
|
service.resume()
|
||||||
|
assert service.operating_state == ServiceOperatingState.STOPPED
|
||||||
|
|
||||||
|
service.start()
|
||||||
|
service.pause()
|
||||||
|
assert service.operating_state == ServiceOperatingState.PAUSED
|
||||||
|
|
||||||
|
service.resume()
|
||||||
|
assert service.operating_state == ServiceOperatingState.RUNNING
|
||||||
|
|
||||||
|
|
||||||
|
def test_restart(service):
|
||||||
|
assert service.operating_state == ServiceOperatingState.STOPPED
|
||||||
|
service.restart()
|
||||||
|
assert service.operating_state == ServiceOperatingState.STOPPED
|
||||||
|
|
||||||
|
service.start()
|
||||||
|
service.restart()
|
||||||
|
assert service.operating_state == ServiceOperatingState.RESTARTING
|
||||||
|
|
||||||
|
timestep = 0
|
||||||
|
while service.operating_state == ServiceOperatingState.RESTARTING:
|
||||||
|
service.apply_timestep(timestep)
|
||||||
|
timestep += 1
|
||||||
|
|
||||||
|
assert service.operating_state == ServiceOperatingState.RUNNING
|
||||||
|
|
||||||
|
|
||||||
|
def test_enable_disable(service):
|
||||||
|
service.disable()
|
||||||
|
assert service.operating_state == ServiceOperatingState.DISABLED
|
||||||
|
|
||||||
|
service.enable()
|
||||||
|
assert service.operating_state == ServiceOperatingState.STOPPED
|
||||||
Reference in New Issue
Block a user