Merged PR 622: Addition of How-To Guides
## Summary Port of How-To guides from an old PR to PrimAITE v4.0, alongside updates for the extensible pages created to be visible in navigation pane. ## Test process Locally generated Documentation has working links ## Checklist - [ ] PR is linked to a **work item** - [ ] **acceptance criteria** of linked ticket are met - [ ] performed **self-review** of the code - [ ] written **tests** for any new functionality added with this PR - [ ] updated the **documentation** if this PR changes or adds functionality - [ ] written/updated **design docs** if this PR implements new functionality - [ ] updated the **change log** - [ ] ran **pre-commit** checks for code style - [ ] attended to any **TO-DOs** left in the code Related work items: #2893
This commit is contained in:
@@ -1,10 +1,10 @@
|
||||
repos:
|
||||
# - repo: local
|
||||
# hooks:
|
||||
# - id: ensure-copyright-clause
|
||||
# name: ensure copyright clause
|
||||
# entry: python copyright_clause_pre_commit_hook.py
|
||||
# language: python
|
||||
- repo: local
|
||||
hooks:
|
||||
- id: ensure-copyright-clause
|
||||
name: ensure copyright clause
|
||||
entry: python copyright_clause_pre_commit_hook.py
|
||||
language: python
|
||||
- repo: http://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v4.4.0
|
||||
hooks:
|
||||
|
||||
@@ -28,6 +28,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- Updated tests that don't use YAMLs to still use the new action and agent schemas
|
||||
- Nodes now use a config schema and are extensible, allowing for plugin support.
|
||||
- Node tests have been updated to use the new node config schemas when not using YAML files.
|
||||
- Documentation has been updated to include details of extensability with PrimAITE.
|
||||
- ACLs are no longer applied to layer-2 traffic.
|
||||
- Random number seed values are recorded in simulation/seed.log if the seed is set in the config file
|
||||
or `generate_seed_value` is set to `true`.
|
||||
|
||||
@@ -17,6 +17,22 @@ What is PrimAITE?
|
||||
source/dependencies
|
||||
source/glossary
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 8
|
||||
:caption: How To
|
||||
:hidden:
|
||||
|
||||
source/how_to
|
||||
source/how_to_guides/custom_actions
|
||||
source/how_to_guides/custom_environments
|
||||
source/how_to_guides/custom_rewards
|
||||
source/how_to_guides/custom_software
|
||||
source/how_to_guides/using_dev_cli
|
||||
source/how_to_guides/extensible_actions
|
||||
source/how_to_guides/extensible_agents
|
||||
source/how_to_guides/extensible_nodes
|
||||
source/how_to_guides/extensible_rewards
|
||||
|
||||
.. toctree::
|
||||
:caption: Usage:
|
||||
:hidden:
|
||||
|
||||
10
docs/source/how_to.rst
Normal file
10
docs/source/how_to.rst
Normal file
@@ -0,0 +1,10 @@
|
||||
.. only:: comment
|
||||
|
||||
© Crown-owned copyright 2025, Defence Science and Technology Laboratory UK
|
||||
|
||||
How-To Guides
|
||||
=============
|
||||
|
||||
These how-to guides aim to provide a starting point for development within PrimAITE, creating your own custom components and environments for use when training agents. More detailed information for each section can be found within the documentation.
|
||||
|
||||
There are also some additional notebooks which provide a walkthrough of established content. It's encouraged to reference these when developing for PrimAITE.
|
||||
58
docs/source/how_to_guides/custom_actions.rst
Normal file
58
docs/source/how_to_guides/custom_actions.rst
Normal file
@@ -0,0 +1,58 @@
|
||||
.. only:: comment
|
||||
|
||||
© Crown-owned copyright 2025, Defence Science and Technology Laboratory UK
|
||||
|
||||
.. _custom_actions:
|
||||
|
||||
Creating Custom Actions in PrimAITE
|
||||
***********************************
|
||||
|
||||
PrimAITE contains a selection of possible actions that can be exercised within a training environment. Actions provided as a part of PrimAITE can be seen within `src/primaite/game/agent/actions`. `Note`: Agents are only able to perform the actions listed within it's action_map, defined within it's configuration YAML. See :ref:`custom_environment` for more information.
|
||||
|
||||
Developing Custom Actions
|
||||
=========================
|
||||
|
||||
Actions within PrimAITE follow a default format, as seen below and in ``actions.py``. It's important that they have an identifier when declared, as this is used when creating the training environment.
|
||||
|
||||
An example of a custom action is seen below, with key information about what is required for new actions in :ref:`extensible_actions`.
|
||||
|
||||
.. code:: Python
|
||||
|
||||
class ExampleActionClass(AbstractAction, identifier="ExampleActions"):
|
||||
"""Example Action Class"""
|
||||
|
||||
config: ExampleAction.ConfigSchema(AbstractAction.ConfigSchema)
|
||||
|
||||
class ConfigSchema(AbstractAction.ConfigSchema)
|
||||
|
||||
node_name: str
|
||||
|
||||
@classmethod
|
||||
def form_request(cls, config: ConfigSchema) -> RequestFormat:
|
||||
return [config.node_name, "example_action"]
|
||||
|
||||
Integration with PrimAITE ActionManager
|
||||
=======================================
|
||||
|
||||
Any custom actions should then be added to the `ActionManager` class, and the `act_class_identifiers` dictionary. This will map the action class to the corresponding action type string that would be passed through the PrimAITE `request_system`.
|
||||
|
||||
|
||||
Interaction with the PrimAITE Request Manager
|
||||
=============================================
|
||||
|
||||
Where an action would cause a request to be sent through the PrimAITE RequestManager, a `form_request` method is expected to be defined within the Action Class. This should format the action into a format that can be ingested by the `RequestManager`. Examples of this include the `NodeFolderCreateAction`, which sends a formed request to create a folder on a given node (seen below):
|
||||
|
||||
.. code:: Python
|
||||
|
||||
def form_request(self, node_id: int, folder_name: str) -> RequestFormat:
|
||||
"""Return the action formatted as a request which can be ingested by the PrimAITE simulation."""
|
||||
node_name = self.manager.get_node_name_by_idx(node_id)
|
||||
if node_name is None or folder_name is None:
|
||||
return ["do_nothing"]
|
||||
return ["network", "node", node_name, "file_system", "create", "folder", folder_name]
|
||||
|
||||
Action Masking
|
||||
==============
|
||||
|
||||
Agents which use the `ProxyAgent` class within PrimAITE are able to use Action Masking. This allows the agent to know if the actions are valid/invalid based on the current environment.
|
||||
Information on how to ensure this can be applied to your custom action can be found in :ref:`action_masking`
|
||||
45
docs/source/how_to_guides/custom_environments.rst
Normal file
45
docs/source/how_to_guides/custom_environments.rst
Normal file
@@ -0,0 +1,45 @@
|
||||
.. only:: comment
|
||||
|
||||
© Crown-owned copyright 2025, Defence Science and Technology Laboratory UK
|
||||
|
||||
.. _custom_environments:
|
||||
|
||||
Creating Custom Environments for PrimAITE
|
||||
*****************************************
|
||||
|
||||
PrimAITE generates it's training configuration/Environments through ingestion of YAML files. A detailed walkthrough of how to create your own environment can be found within the ``Creating-Custom-Environments`` jupyter notebook.
|
||||
|
||||
You configuration file should follow the hierarchy seen below:
|
||||
|
||||
.. code:: yaml
|
||||
|
||||
metadata:
|
||||
version: 4.0
|
||||
|
||||
required_plugins:
|
||||
- name: Example_Plugin
|
||||
version: 1.0
|
||||
|
||||
io_settings:
|
||||
...
|
||||
game:
|
||||
...
|
||||
agents:
|
||||
...
|
||||
simulation:
|
||||
...
|
||||
|
||||
MetaData
|
||||
========
|
||||
|
||||
It's important to include the metadata tag within your YAML file, as this is used to ensure PrimAITE can interpret the configuration correctly. This should also include any plugins that are required for the defined environment, along with their respective version.
|
||||
|
||||
Required Plugins
|
||||
================
|
||||
|
||||
Should your custom environment need any additional PrimAITE plugins, each must be specified under the `required_plugins` tab, as seen in the above example.
|
||||
|
||||
Configuration Items
|
||||
===================
|
||||
|
||||
For detailed information about the remaining configuration items found within the configuration file, see :ref:`Configurable Items`.
|
||||
48
docs/source/how_to_guides/custom_rewards.rst
Normal file
48
docs/source/how_to_guides/custom_rewards.rst
Normal file
@@ -0,0 +1,48 @@
|
||||
.. only:: comment
|
||||
|
||||
© Crown-owned copyright 2025, Defence Science and Technology Laboratory UK
|
||||
|
||||
.. _custom_rewards:
|
||||
|
||||
Creating Custom Rewards in PrimAITE
|
||||
***********************************
|
||||
|
||||
Rewards within PrimAITE are contained within ``rewards.py``, which details the rewards available for all agents within training sessions, how they are calculated and any other specific information where necessary.
|
||||
|
||||
Rewards within PrimAITE have been updated to facilitate extensability and the creation of plugins with the release of PrimAITE version 4.0. Additional information about this is covered within :ref:`extensible_rewards`.
|
||||
|
||||
Custom Rewards within PrimAITE should inherit from the ``AbstractReward`` class, found in ``rewards.py``. It's important to include an identifier for any class created within PrimAITE.
|
||||
|
||||
.. code:: Python
|
||||
|
||||
class ExampleAward(AbstractReward, identifier="ExampleAward"):
|
||||
"""Example Reward Class """
|
||||
|
||||
def calculate(self, state: Dict, last_action_response: "AgentHistoryItem") -> float:
|
||||
"""Calculate the reward for the current state."""
|
||||
return 1.0
|
||||
|
||||
@classmethod
|
||||
def from_config(cls, config: dict) -> "AbstractReward":
|
||||
"""Create a reward function component from a config dictionary."""
|
||||
return cls()
|
||||
|
||||
|
||||
Custom rewards that have been created should be added to the ``rew_class_identifiers`` dictionary within the ``RewardFunction`` class in ``rewards.py``.
|
||||
|
||||
Including Custom Rewards within PrimAITE configuration
|
||||
======================================================
|
||||
|
||||
Custom rewards can then be included within an agents configuration by it's inclusion within the training session configuration YAML.
|
||||
|
||||
.. code:: yaml
|
||||
|
||||
agents:
|
||||
- ref: agent_name
|
||||
reward_function:
|
||||
reward_components:
|
||||
- type: DUMMY
|
||||
weight: 1.0
|
||||
|
||||
|
||||
More detailed information about rewards within PrimAITE can be found within :ref:`Rewards`
|
||||
80
docs/source/how_to_guides/custom_software.rst
Normal file
80
docs/source/how_to_guides/custom_software.rst
Normal file
@@ -0,0 +1,80 @@
|
||||
.. only:: comment
|
||||
|
||||
© Crown-owned copyright 2025, Defence Science and Technology Laboratory UK
|
||||
|
||||
.. _custom_software:
|
||||
|
||||
Creating Custom Software for PrimAITE
|
||||
*************************************
|
||||
|
||||
This page aims to provide a how-to guide on how to create your own custom software for use within PrimAITE.
|
||||
|
||||
PrimAITE has a base software class which should be inherited from when building custom software. Examples of this can be seen in the ``IOSoftware`` and ``Process`` classes.
|
||||
It's important that any new software created within PrimAITE has the ``identifier`` attribute defined, for use when generating the environment.
|
||||
|
||||
Some default attributes may need to be adjusted to align with the intended application of the custom software.
|
||||
|
||||
|
||||
.. code:: Python
|
||||
|
||||
from src.primaite.simulator.system.software import Software
|
||||
|
||||
class CustomSoftware(Software, identifier="CustomSoftware"):
|
||||
"""
|
||||
An example of Custom Software within PrimAITE.
|
||||
"""
|
||||
|
||||
operating_state: OperatingState
|
||||
"The current operating state of the Custom software"
|
||||
|
||||
def describe_state(self) -> Dict:
|
||||
"""
|
||||
Produce a dictionary describing the current state of this object.
|
||||
|
||||
:return: Current state of this object and child objects.
|
||||
:rtype: Dict
|
||||
"""
|
||||
state = super().describe_state()
|
||||
state.update({"operating_state": self.operating_state.value})
|
||||
|
||||
Default Install
|
||||
###############
|
||||
|
||||
Software can be set to auto-install onto a Node by adding it to the ``SYSTEM_SOFTWARE`` dictionary for the node. An example can be seen in the ``HostNode`` class, which pre-installs some key software that is expected on Nodes, such as the ``NTPClient`` and ``UserManager``.
|
||||
|
||||
Requirements
|
||||
############
|
||||
|
||||
Any custom software will need to provide an implementation of the ``describe_state`` method, and conform to the general Pydantic requirements.
|
||||
It's a good idea, if possible, to also create a ``.show()`` method, as this can be used for visualising the software's status when developing within PrimAITE.
|
||||
|
||||
Interaction with the PrimAITE Request System
|
||||
############################################
|
||||
|
||||
If the software is intended to be used by an agent via a :ref:`custom_action`, then it will likely need an implementation of the ``RequestManager``.
|
||||
Detailed information about the PrimAITE request system can be seen in :ref:`request_system`. An example implementation, derived from the `Application` class is seen below:
|
||||
|
||||
.. code:: Python
|
||||
|
||||
def _init_request_manager(self) -> RequestManager:
|
||||
"""
|
||||
Initialise the request manager.
|
||||
|
||||
More information in user guide and docstring for SimComponent._init_request_manager.
|
||||
"""
|
||||
_is_application_running = Application._StateValidator(application=self, state=ApplicationOperatingState.RUNNING)
|
||||
|
||||
rm = super()._init_request_manager()
|
||||
rm.add_request(
|
||||
"scan",
|
||||
RequestType(
|
||||
func=lambda request, context: RequestResponse.from_bool(self.scan()), validator=_is_application_running
|
||||
),
|
||||
)
|
||||
return rm
|
||||
|
||||
|
||||
Further information
|
||||
###################
|
||||
|
||||
For more detailed information about the implementation of software within PrimAITE, see :ref:`software`.
|
||||
@@ -2,11 +2,11 @@
|
||||
|
||||
© Crown-owned copyright 2025, Defence Science and Technology Laboratory UK
|
||||
|
||||
.. _extensible_actions:
|
||||
|
||||
Extensible Actions
|
||||
******************
|
||||
|
||||
|
||||
Changes to Actions class Structure.
|
||||
===================================
|
||||
|
||||
@@ -32,7 +32,7 @@ The ConfigSchema sub-class of the action must contain all `configurable` variabl
|
||||
|
||||
|
||||
Unique discriminator
|
||||
#################
|
||||
####################
|
||||
|
||||
When declaring a custom class, it must have a unique discriminator string, that allows PrimAITE to generate the correct action when needed.
|
||||
|
||||
@@ -48,6 +48,7 @@ When declaring a custom class, it must have a unique discriminator string, that
|
||||
node_name: str
|
||||
directory_name: str
|
||||
|
||||
@classmethod
|
||||
def form_request(cls, config: ConfigSchema) -> RequestFormat:
|
||||
return ["network",
|
||||
"node",
|
||||
@@ -64,4 +65,4 @@ The above action would fail pydantic validation as the discriminator "node-folde
|
||||
form_request method
|
||||
###################
|
||||
|
||||
PrimAITE actions need to have a `form_request` method, which can be passed to the `RequestManager` for processing. This allows the custom action to be actioned within the simulation environment.
|
||||
PrimAITE actions need to have a `form_request` method, which can be passed to the `RequestManager` for processing. This allows the custom action to be actioned within the simulation environment. Further information and an example of this can be seen in :ref:`custom_actions`.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
© Crown-owned copyright 2025, Defence Science and Technology Laboratory UK
|
||||
|
||||
.. _about:
|
||||
.. _extensible_agents:
|
||||
|
||||
Extensible Agents
|
||||
*****************
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
© Crown-owned copyright 2025, Defence Science and Technology Laboratory UK
|
||||
|
||||
.. _about:
|
||||
.. _extensible_nodes:
|
||||
|
||||
|
||||
Extensible Nodes
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
© Crown-owned copyright 2025, Defence Science and Technology Laboratory UK
|
||||
|
||||
.. _about:
|
||||
.. _extensible_rewards:
|
||||
|
||||
Extensible Rewards
|
||||
******************
|
||||
|
||||
8
docs/source/how_to_guides/using_dev_cli.rst
Normal file
8
docs/source/how_to_guides/using_dev_cli.rst
Normal file
@@ -0,0 +1,8 @@
|
||||
.. only:: comment
|
||||
|
||||
© Crown-owned copyright 2025, Defence Science and Technology Laboratory UK
|
||||
|
||||
Utilising the PrimAITE dev-mode cli
|
||||
***********************************
|
||||
|
||||
A guide for utilising `primaite dev-mode` can be found within the ``How-To-Use-Primaite-Dev-Mode`` jupyter notebook, and also :ref:`Developer Tools`.
|
||||
@@ -42,7 +42,7 @@ Example code where a node is turned on:
|
||||
from primaite.simulator.network.hardware.base import Node
|
||||
from primaite.simulator.network.hardware.node_operating_state import NodeOperatingState
|
||||
|
||||
node = Node(hostname="pc_a")
|
||||
node = Node(config={"hostname":"pc_a"})
|
||||
|
||||
assert node.operating_state is NodeOperatingState.OFF # By default, node is instantiated in an OFF state
|
||||
|
||||
@@ -65,7 +65,7 @@ If the node needs to be instantiated in an on state:
|
||||
from primaite.simulator.network.hardware.base import Node
|
||||
from primaite.simulator.network.hardware.node_operating_state import NodeOperatingState
|
||||
|
||||
node = Node(hostname="pc_a", operating_state=NodeOperatingState.ON)
|
||||
node = Node(config={"hostname":"pc_a", "operating_state":NodeOperatingState.ON})
|
||||
|
||||
assert node.operating_state is NodeOperatingState.ON # node is in ON state
|
||||
|
||||
@@ -76,7 +76,7 @@ Setting ``start_up_duration`` and/or ``shut_down_duration`` to ``0`` will allow
|
||||
from primaite.simulator.network.hardware.base import Node
|
||||
from primaite.simulator.network.hardware.node_operating_state import NodeOperatingState
|
||||
|
||||
node = Node(hostname="pc_a", start_up_duration=0, shut_down_duration=0)
|
||||
node = Node(config={"hostname":"pc_a", "start_up_duration":0, "shut_down_duration":0})
|
||||
|
||||
assert node.operating_state is NodeOperatingState.OFF # node is in OFF state
|
||||
|
||||
@@ -196,21 +196,23 @@ Setting up a Client-Server Network
|
||||
def client_server_network() -> Tuple[Computer, Server, Network]:
|
||||
network = Network()
|
||||
|
||||
client = Computer(
|
||||
hostname="client",
|
||||
ip_address="192.168.1.2",
|
||||
subnet_mask="255.255.255.0",
|
||||
default_gateway="192.168.1.1",
|
||||
start_up_duration=0,
|
||||
client = Computer(config={
|
||||
"hostname":"client",
|
||||
"ip_address":"192.168.1.2",
|
||||
"subnet_mask":"255.255.255.0",
|
||||
"default_gateway":"192.168.1.1",
|
||||
"start_up_duration":0,
|
||||
}
|
||||
)
|
||||
client.power_on()
|
||||
|
||||
server = Server(
|
||||
hostname="server",
|
||||
ip_address="192.168.1.3",
|
||||
subnet_mask="255.255.255.0",
|
||||
default_gateway="192.168.1.1",
|
||||
start_up_duration=0,
|
||||
server = Server(config = {
|
||||
"hostname":"server",
|
||||
"ip_address":"192.168.1.3",
|
||||
"subnet_mask":"255.255.255.0",
|
||||
"default_gateway":"192.168.1.1",
|
||||
"start_up_duration":0,
|
||||
}
|
||||
)
|
||||
server.power_on()
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ we'll use the following Network that has a client, server, two switches, and a r
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
router_1 = Router(hostname="router_1", num_ports=3)
|
||||
router_1 = Router(config={"hostname":"router_1", "num_ports":3})
|
||||
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=2, ip_address="192.168.2.1", subnet_mask="255.255.255.0")
|
||||
@@ -57,9 +57,9 @@ we'll use the following Network that has a client, server, two switches, and a r
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
switch_1 = Switch(hostname="switch_1", num_ports=6)
|
||||
switch_1 = Switch(config={"hostname":"switch_1", "num_ports":6})
|
||||
switch_1.power_on()
|
||||
switch_2 = Switch(hostname="switch_2", num_ports=6)
|
||||
switch_2 = Switch(config={"hostname":"switch_2", "num_ports":6})
|
||||
switch_2.power_on()
|
||||
|
||||
5. Connect the Switches to the Router
|
||||
@@ -75,18 +75,20 @@ we'll use the following Network that has a client, server, two switches, and a r
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
client_1 = Computer(
|
||||
hostname="client_1",
|
||||
ip_address="192.168.2.2",
|
||||
subnet_mask="255.255.255.0",
|
||||
default_gateway="192.168.2.1"
|
||||
client_1 = Computer(config = {
|
||||
"hostname":"client_1",
|
||||
"ip_address":"192.168.2.2",
|
||||
"subnet_mask":"255.255.255.0",
|
||||
"default_gateway":"192.168.2.1",
|
||||
}
|
||||
)
|
||||
client_1.power_on()
|
||||
server_1 = Server(
|
||||
hostname="server_1",
|
||||
ip_address="192.168.1.2",
|
||||
subnet_mask="255.255.255.0",
|
||||
default_gateway="192.168.1.1"
|
||||
server_1 = Server(config= {
|
||||
"hostname":"server_1",
|
||||
"ip_address":"192.168.1.2",
|
||||
"subnet_mask":"255.255.255.0",
|
||||
"default_gateway":"192.168.1.1",
|
||||
}
|
||||
)
|
||||
server_1.power_on()
|
||||
|
||||
|
||||
@@ -128,19 +128,19 @@ Python
|
||||
network = Network()
|
||||
|
||||
|
||||
switch = Switch(hostname="switch", start_up_duration=0, num_ports=4)
|
||||
switch = Switch(config={"hostname":"switch", "start_up_duration":0, "num_ports":4})
|
||||
switch.power_on()
|
||||
|
||||
node_a = Computer(hostname="node_a", ip_address="192.168.0.10", subnet_mask="255.255.255.0", start_up_duration=0)
|
||||
node_a = Computer(config={"hostname":"node_a", "ip_address":"192.168.0.10", "subnet_mask":"255.255.255.0", "start_up_duration":0})
|
||||
node_a.power_on()
|
||||
network.connect(node_a.network_interface[1], switch.network_interface[1])
|
||||
|
||||
node_b = Computer(hostname="node_b", ip_address="192.168.0.11", subnet_mask="255.255.255.0", start_up_duration=0)
|
||||
node_b = Computer(config={"hostname":"node_b", "ip_address":"192.168.0.11", "subnet_mask":"255.255.255.0", "start_up_duration":0})
|
||||
node_b.power_on()
|
||||
|
||||
network.connect(node_b.network_interface[1], switch.network_interface[2])
|
||||
|
||||
node_c = Computer(hostname="node_c", ip_address="192.168.0.12", subnet_mask="255.255.255.0", start_up_duration=0)
|
||||
node_c = Computer(config={"hostname":"node_c", "ip_address":"192.168.0.12", "subnet_mask":"255.255.255.0", "start_up_duration":0})
|
||||
node_c.power_on()
|
||||
network.connect(node_c.network_interface[1], switch.network_interface[3])
|
||||
|
||||
|
||||
@@ -67,12 +67,13 @@ Python
|
||||
from primaite.simulator.system.applications.red_applications.data_manipulation_bot import DataManipulationBot
|
||||
from primaite.simulator.system.applications.database_client import DatabaseClient
|
||||
|
||||
client_1 = Computer(
|
||||
hostname="client_1",
|
||||
ip_address="192.168.10.21",
|
||||
subnet_mask="255.255.255.0",
|
||||
default_gateway="192.168.10.1",
|
||||
operating_state=NodeOperatingState.ON # initialise the computer in an ON state
|
||||
client_1 = Computer(config={
|
||||
"hostname":"client_1",
|
||||
"ip_address":"192.168.10.21",
|
||||
"subnet_mask":"255.255.255.0",
|
||||
"default_gateway":"192.168.10.1",
|
||||
"operating_state":NodeOperatingState.ON # initialise the computer in an ON state
|
||||
}
|
||||
)
|
||||
network.connect(endpoint_b=client_1.network_interface[1], endpoint_a=switch_2.network_interface[1])
|
||||
client_1.software_manager.install(DatabaseClient)
|
||||
|
||||
@@ -48,12 +48,13 @@ Python
|
||||
from primaite.simulator.network.hardware.nodes.host.computer import Computer
|
||||
from primaite.simulator.system.applications.database_client import DatabaseClient
|
||||
|
||||
client = Computer(
|
||||
hostname="client",
|
||||
ip_address="192.168.10.21",
|
||||
subnet_mask="255.255.255.0",
|
||||
default_gateway="192.168.10.1",
|
||||
operating_state=NodeOperatingState.ON # initialise the computer in an ON state
|
||||
client_1 = Computer(config={
|
||||
"hostname":"client_1",
|
||||
"ip_address":"192.168.10.21",
|
||||
"subnet_mask":"255.255.255.0",
|
||||
"default_gateway":"192.168.10.1",
|
||||
"operating_state":NodeOperatingState.ON # initialise the computer in an ON state
|
||||
}
|
||||
)
|
||||
|
||||
# install DatabaseClient
|
||||
|
||||
@@ -45,12 +45,13 @@ Python
|
||||
from primaite.simulator.system.applications.red_applications.dos_bot import dos-bot
|
||||
|
||||
# Create Computer
|
||||
computer = Computer(
|
||||
hostname="computer",
|
||||
ip_address="192.168.1.2",
|
||||
subnet_mask="255.255.255.0",
|
||||
default_gateway="192.168.1.1",
|
||||
start_up_duration=0,
|
||||
computer = Computer(config={
|
||||
"hostname":"computer",
|
||||
"ip_address":"192.168.1.2",
|
||||
"subnet_mask":"255.255.255.0",
|
||||
"default_gateway":"192.168.1.1",
|
||||
"start_up_duration":0,
|
||||
}
|
||||
)
|
||||
computer.power_on()
|
||||
|
||||
|
||||
@@ -70,39 +70,41 @@ The network we use for these examples is defined below:
|
||||
router.configure_port(port=1, ip_address="192.168.1.1", subnet_mask="255.255.255.0")
|
||||
|
||||
# Set up PC 1
|
||||
pc_1 = Computer(
|
||||
hostname="pc_1",
|
||||
ip_address="192.168.1.11",
|
||||
subnet_mask="255.255.255.0",
|
||||
default_gateway="192.168.1.1",
|
||||
start_up_duration=0
|
||||
pc_1 = Computer(config = {
|
||||
"hostname":"pc_1",
|
||||
"ip_address":"192.168.1.11",
|
||||
"subnet_mask":"255.255.255.0",
|
||||
"default_gateway":"192.168.1.1",
|
||||
"start_up_duration":0,
|
||||
}
|
||||
)
|
||||
pc_1.power_on()
|
||||
|
||||
# Set up PC 2
|
||||
pc_2 = Computer(
|
||||
hostname="pc_2",
|
||||
ip_address="192.168.1.12",
|
||||
subnet_mask="255.255.255.0",
|
||||
default_gateway="192.168.1.1",
|
||||
start_up_duration=0
|
||||
pc_2 = Computer(config = {
|
||||
"hostname":"pc_2",
|
||||
"ip_address":"192.168.1.12",
|
||||
"subnet_mask":"255.255.255.0",
|
||||
"default_gateway":"192.168.1.1",
|
||||
"start_up_duration":0,
|
||||
}
|
||||
)
|
||||
pc_2.power_on()
|
||||
pc_2.software_manager.install(DatabaseService)
|
||||
pc_2.software_manager.software["DatabaseService"].start() # start the postgres server
|
||||
|
||||
# Set up PC 3
|
||||
pc_3 = Computer(
|
||||
hostname="pc_3",
|
||||
ip_address="192.168.1.13",
|
||||
subnet_mask="255.255.255.0",
|
||||
default_gateway="192.168.1.1",
|
||||
start_up_duration=0
|
||||
pc_3 = Computer(config = {
|
||||
"hostname";"pc_3",
|
||||
"ip_address":"192.168.1.13",
|
||||
"subnet_mask":"255.255.255.0",
|
||||
"default_gateway":"192.168.1.1",
|
||||
"start_up_duration":0
|
||||
)
|
||||
# Don't power on PC 3
|
||||
|
||||
# Set up the switch
|
||||
switch = Switch(hostname="switch", start_up_duration=0)
|
||||
switch = Switch(config={"hostname":"switch", "start_up_duration":0})
|
||||
switch.power_on()
|
||||
|
||||
# Connect devices
|
||||
|
||||
@@ -52,12 +52,13 @@ Python
|
||||
from primaite.simulator.system.applications.red_applications.RansomwareScript import RansomwareScript
|
||||
from primaite.simulator.system.applications.database_client import DatabaseClient
|
||||
|
||||
client_1 = Computer(
|
||||
hostname="client_1",
|
||||
ip_address="192.168.10.21",
|
||||
subnet_mask="255.255.255.0",
|
||||
default_gateway="192.168.10.1",
|
||||
operating_state=NodeOperatingState.ON # initialise the computer in an ON state
|
||||
client_1 = Computer(config={
|
||||
"hostname":"client_1",
|
||||
"ip_address":"192.168.10.21",
|
||||
"subnet_mask":"255.255.255.0",
|
||||
"default_gateway":"192.168.10.1",
|
||||
"operating_state":NodeOperatingState.ON # initialise the computer in an ON state
|
||||
}
|
||||
)
|
||||
network.connect(endpoint_b=client_1.network_interface[1], endpoint_a=switch_2.network_interface[1])
|
||||
client_1.software_manager.install(DatabaseClient)
|
||||
|
||||
@@ -50,12 +50,13 @@ The :ref:`DNSClient` must be configured to use the :ref:`DNSServer`. The :ref:`D
|
||||
from primaite.simulator.system.applications.web_browser import WebBrowser
|
||||
|
||||
# Create Computer
|
||||
computer = Computer(
|
||||
hostname="computer",
|
||||
ip_address="192.168.1.2",
|
||||
subnet_mask="255.255.255.0",
|
||||
default_gateway="192.168.1.1",
|
||||
start_up_duration=0,
|
||||
computer = Computer(config={
|
||||
"hostname":"computer",
|
||||
"ip_address":"192.168.1.2",
|
||||
"subnet_mask":"255.255.255.0",
|
||||
"default_gateway":"192.168.1.1",
|
||||
"start_up_duration":0,
|
||||
}
|
||||
)
|
||||
computer.power_on()
|
||||
|
||||
|
||||
@@ -55,12 +55,13 @@ Python
|
||||
from primaite.simulator.system.services.database.database_service import DatabaseService
|
||||
|
||||
# Create Server
|
||||
server = Server(
|
||||
hostname="server",
|
||||
ip_address="192.168.2.2",
|
||||
subnet_mask="255.255.255.0",
|
||||
default_gateway="192.168.1.1",
|
||||
start_up_duration=0,
|
||||
server = Server(config = {
|
||||
"hostname":"server",
|
||||
"ip_address":"192.168.2.2",
|
||||
"subnet_mask":"255.255.255.0",
|
||||
"default_gateway":"192.168.1.1",
|
||||
"start_up_duration":0,
|
||||
}
|
||||
)
|
||||
server.power_on()
|
||||
|
||||
|
||||
@@ -45,12 +45,13 @@ Python
|
||||
from primaite.simulator.system.services.dns.dns_client import DNSClient
|
||||
|
||||
# Create Server
|
||||
server = Server(
|
||||
hostname="server",
|
||||
ip_address="192.168.2.2",
|
||||
subnet_mask="255.255.255.0",
|
||||
default_gateway="192.168.1.1",
|
||||
start_up_duration=0,
|
||||
server = Server(config = {
|
||||
"hostname":"server",
|
||||
"ip_address":"192.168.2.2",
|
||||
"subnet_mask":"255.255.255.0",
|
||||
"default_gateway":"192.168.1.1",
|
||||
"start_up_duration":0,
|
||||
}
|
||||
)
|
||||
server.power_on()
|
||||
|
||||
|
||||
@@ -42,12 +42,13 @@ Python
|
||||
from primaite.simulator.system.services.dns.dns_server import DNSServer
|
||||
|
||||
# Create Server
|
||||
server = Server(
|
||||
hostname="server",
|
||||
ip_address="192.168.2.2",
|
||||
subnet_mask="255.255.255.0",
|
||||
default_gateway="192.168.1.1",
|
||||
start_up_duration=0,
|
||||
server = Server(config = {
|
||||
"hostname":"server",
|
||||
"ip_address":"192.168.2.2",
|
||||
"subnet_mask":"255.255.255.0",
|
||||
"default_gateway":"192.168.1.1",
|
||||
"start_up_duration":0,
|
||||
}
|
||||
)
|
||||
server.power_on()
|
||||
|
||||
|
||||
@@ -49,12 +49,13 @@ Python
|
||||
from primaite.simulator.system.services.ftp.ftp_client import FTPClient
|
||||
|
||||
# Create Server
|
||||
server = Server(
|
||||
hostname="server",
|
||||
ip_address="192.168.2.2",
|
||||
subnet_mask="255.255.255.0",
|
||||
default_gateway="192.168.1.10",
|
||||
start_up_duration=0,
|
||||
server = Server(config = {
|
||||
"hostname":"server",
|
||||
"ip_address":"192.168.2.2",
|
||||
"subnet_mask":"255.255.255.0",
|
||||
"default_gateway":"192.168.1.10",
|
||||
"start_up_duration":0,
|
||||
}
|
||||
)
|
||||
server.power_on()
|
||||
|
||||
|
||||
@@ -44,12 +44,13 @@ Python
|
||||
from primaite.simulator.system.services.ftp.ftp_server import FTPServer
|
||||
|
||||
# Create Server
|
||||
server = Server(
|
||||
hostname="server",
|
||||
ip_address="192.168.2.2",
|
||||
subnet_mask="255.255.255.0",
|
||||
default_gateway="192.168.1.1",
|
||||
start_up_duration=0,
|
||||
server = Server(config = {
|
||||
"hostname":"server",
|
||||
"ip_address":"192.168.2.2",
|
||||
"subnet_mask":"255.255.255.0",
|
||||
"default_gateway":"192.168.1.10",
|
||||
"start_up_duration":0,
|
||||
}
|
||||
)
|
||||
server.power_on()
|
||||
|
||||
|
||||
@@ -42,12 +42,13 @@ Python
|
||||
from primaite.simulator.system.services.ntp.ntp_client import NTPClient
|
||||
|
||||
# Create Server
|
||||
server = Server(
|
||||
hostname="server",
|
||||
ip_address="192.168.2.2",
|
||||
subnet_mask="255.255.255.0",
|
||||
default_gateway="192.168.1.1",
|
||||
start_up_duration=0,
|
||||
server = Server(config = {
|
||||
"hostname":"server",
|
||||
"ip_address":"192.168.2.2",
|
||||
"subnet_mask":"255.255.255.0",
|
||||
"default_gateway":"192.168.1.10",
|
||||
"start_up_duration":0,
|
||||
}
|
||||
)
|
||||
server.power_on()
|
||||
|
||||
|
||||
@@ -44,12 +44,13 @@ Python
|
||||
from primaite.simulator.system.services.ntp.ntp_server import NTPServer
|
||||
|
||||
# Create Server
|
||||
server = Server(
|
||||
hostname="server",
|
||||
ip_address="192.168.2.2",
|
||||
subnet_mask="255.255.255.0",
|
||||
default_gateway="192.168.1.1",
|
||||
start_up_duration=0,
|
||||
server = Server(config = {
|
||||
"hostname":"server",
|
||||
"ip_address":"192.168.2.2",
|
||||
"subnet_mask":"255.255.255.0",
|
||||
"default_gateway":"192.168.1.10",
|
||||
"start_up_duration":0,
|
||||
}
|
||||
)
|
||||
server.power_on()
|
||||
|
||||
|
||||
@@ -158,12 +158,13 @@ Python
|
||||
from primaite.simulator.system.services.terminal.terminal import Terminal
|
||||
from primaite.simulator.network.hardware.node_operating_state import NodeOperatingState
|
||||
|
||||
client = Computer(
|
||||
hostname="client",
|
||||
ip_address="192.168.10.21",
|
||||
subnet_mask="255.255.255.0",
|
||||
default_gateway="192.168.10.1",
|
||||
operating_state=NodeOperatingState.ON,
|
||||
client = Computer(config= {
|
||||
"hostname":"client",
|
||||
"ip_address":"192.168.10.21",
|
||||
"subnet_mask":"255.255.255.0",
|
||||
"default_gateway":"192.168.10.1",
|
||||
"operating_state":NodeOperatingState.ON,
|
||||
}
|
||||
)
|
||||
|
||||
terminal: Terminal = client.software_manager.software.get("terminal")
|
||||
@@ -181,9 +182,9 @@ Creating Remote Terminal Connection
|
||||
|
||||
|
||||
network = Network()
|
||||
node_a = Computer(hostname="node_a", ip_address="192.168.0.10", subnet_mask="255.255.255.0", start_up_duration=0)
|
||||
node_a = Computer(config={"hostname":"node_a", "ip_address":"192.168.0.10", "subnet_mask":"255.255.255.0", "start_up_duration":0})
|
||||
node_a.power_on()
|
||||
node_b = Computer(hostname="node_b", ip_address="192.168.0.11", subnet_mask="255.255.255.0", start_up_duration=0)
|
||||
node_b = Computer(config={"hostname":"node_b", "ip_address":"192.168.0.11", "subnet_mask":"255.255.255.0", "start_up_duration":0})
|
||||
node_b.power_on()
|
||||
network.connect(node_a.network_interface[1], node_b.network_interface[1])
|
||||
|
||||
@@ -207,9 +208,9 @@ Executing a basic application install command
|
||||
|
||||
|
||||
network = Network()
|
||||
node_a = Computer(hostname="node_a", ip_address="192.168.0.10", subnet_mask="255.255.255.0", start_up_duration=0)
|
||||
node_a = Computer(config={"hostname":"node_a", "ip_address":"192.168.0.10", "subnet_mask":"255.255.255.0", "start_up_duration":0})
|
||||
node_a.power_on()
|
||||
node_b = Computer(hostname="node_b", ip_address="192.168.0.11", subnet_mask="255.255.255.0", start_up_duration=0)
|
||||
node_b = Computer(config={"hostname":"node_b", "ip_address":"192.168.0.11", "subnet_mask":"255.255.255.0", "start_up_duration":0})
|
||||
node_b.power_on()
|
||||
network.connect(node_a.network_interface[1], node_b.network_interface[1])
|
||||
|
||||
@@ -235,9 +236,9 @@ Creating a folder on a remote node
|
||||
|
||||
|
||||
network = Network()
|
||||
node_a = Computer(hostname="node_a", ip_address="192.168.0.10", subnet_mask="255.255.255.0", start_up_duration=0)
|
||||
node_a = Computer(config={"hostname":"node_a", "ip_address":"192.168.0.10", "subnet_mask":"255.255.255.0", "start_up_duration":0})
|
||||
node_a.power_on()
|
||||
node_b = Computer(hostname="node_b", ip_address="192.168.0.11", subnet_mask="255.255.255.0", start_up_duration=0)
|
||||
node_b = Computer(config={"hostname":"node_b", "ip_address":"192.168.0.11", "subnet_mask":"255.255.255.0", "start_up_duration":0})
|
||||
node_b.power_on()
|
||||
network.connect(node_a.network_interface[1], node_b.network_interface[1])
|
||||
|
||||
@@ -262,9 +263,9 @@ Disconnect from Remote Node
|
||||
|
||||
|
||||
network = Network()
|
||||
node_a = Computer(hostname="node_a", ip_address="192.168.0.10", subnet_mask="255.255.255.0", start_up_duration=0)
|
||||
node_a = Computer(config={"hostname":"node_a", "ip_address":"192.168.0.10", "subnet_mask":"255.255.255.0", "start_up_duration":0})
|
||||
node_a.power_on()
|
||||
node_b = Computer(hostname="node_b", ip_address="192.168.0.11", subnet_mask="255.255.255.0", start_up_duration=0)
|
||||
node_b = Computer(config={"hostname":"node_b", "ip_address":"192.168.0.11", "subnet_mask":"255.255.255.0", "start_up_duration":0})
|
||||
node_b.power_on()
|
||||
network.connect(node_a.network_interface[1], node_b.network_interface[1])
|
||||
|
||||
|
||||
@@ -45,12 +45,13 @@ Python
|
||||
from primaite.simulator.system.services.web_server.web_server import WebServer
|
||||
|
||||
# Create Server
|
||||
server = Server(
|
||||
hostname="server",
|
||||
ip_address="192.168.2.2",
|
||||
subnet_mask="255.255.255.0",
|
||||
default_gateway="192.168.1.1",
|
||||
start_up_duration=0,
|
||||
server = Server(config = {
|
||||
"hostname":"server",
|
||||
"ip_address":"192.168.2.2",
|
||||
"subnet_mask":"255.255.255.0",
|
||||
"default_gateway":"192.168.1.1",
|
||||
"start_up_duration":0,
|
||||
}
|
||||
)
|
||||
server.power_on()
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ See :ref:`Node Start up and Shut down`
|
||||
from primaite.simulator.system.services.service import ServiceOperatingState
|
||||
from primaite.simulator.system.services.web_server.web_server import WebServer
|
||||
|
||||
node = Node(hostname="pc_a", start_up_duration=0, shut_down_duration=0)
|
||||
node = Node(config={"hostname":"pc_a", "start_up_duration":0, "shut_down_duration":0})
|
||||
|
||||
node.power_on()
|
||||
assert node.operating_state is NodeOperatingState.ON
|
||||
|
||||
@@ -1698,7 +1698,7 @@
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.10.12"
|
||||
"version": "3.10.11"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
|
||||
@@ -457,7 +457,7 @@
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "venv",
|
||||
"display_name": ".venv",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
|
||||
@@ -595,7 +595,7 @@
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3 (ipykernel)",
|
||||
"display_name": ".venv",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
@@ -609,7 +609,7 @@
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.10.12"
|
||||
"version": "3.10.11"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK
|
||||
# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK
|
||||
from pathlib import Path
|
||||
from typing import Union
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK
|
||||
# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK
|
||||
import pytest
|
||||
|
||||
from primaite.simulator.network.hardware.nodes.host.computer import Computer
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK
|
||||
# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK
|
||||
import json
|
||||
|
||||
from primaite.session.environment import PrimaiteGymEnv
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK
|
||||
# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK
|
||||
from primaite.simulator.network.hardware.nodes.network.router import ACLAction, Router, RouterARP
|
||||
from primaite.simulator.system.services.arp.arp import ARP
|
||||
from primaite.utils.validation.port import PORT_LOOKUP
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK
|
||||
from primaite.game.agent.observations.file_system_observations import FileObservation
|
||||
from primaite.game.agent.observations.observation_manager import NullObservation
|
||||
from primaite.game.agent.scripted_agents.random_agent import RandomAgent
|
||||
|
||||
Reference in New Issue
Block a user