diff --git a/CHANGELOG.md b/CHANGELOG.md index f51fd648..b0ee24be 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,12 +19,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added reward calculation details to AgentHistoryItem. - Added a new Privilege-Escalation-and Data-Loss-Example.ipynb notebook with a realistic cyber scenario focusing on internal privilege escalation and data loss through the manipulation of SSH access and Access Control Lists (ACLs). +- Added a new extendible `NetworkNodeAdder` class for convenient addition of sets of nodes based on a simplified config. ### Changed - File and folder observations can now be configured to always show the true health status, or require scanning like before. - It's now possible to disable stickiness on reward components, meaning their value returns to 0 during timesteps where agent don't issue the corresponding action. Affects `GreenAdminDatabaseUnreachablePenalty`, `WebpageUnavailablePenalty`, `WebServer404Penalty` - Node observations can now be configured to show the number of active local and remote logins. -- Ports, IP Protocols, and airspace frequencies no longer use enums. They are defined in dictionary lookups and are handled by custom validation to enable extendability with plugins. +- Ports and IP Protocolsno longer use enums. They are defined in dictionary lookups and are handled by custom validation to enable extendability with plugins. +- Changed AirSpaceFrequency to a data transfer object with a registry to allow extendability +- Changed the Office LAN creation convenience function to follow the new `NetworkNodeAdder` pattern. Office LANs can now also be defined in YAML config. ### Fixed - Folder observations showing the true health state without scanning (the old behaviour can be reenabled via config) @@ -32,6 +35,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 and `uninstall` methods in the `Node` class. - Updated the `receive_payload_from_session_manager` method in `SoftwareManager` so that it now sends a copy of the payload to any software listening on the destination port of the `Frame`. +- Made the `show` method of `Network` show all node types, including ones registered at runtime ### Removed - Removed the `install` and `uninstall` methods in the `Node` class. diff --git a/docs/index.rst b/docs/index.rst index ff97f60d..1da15b8c 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -30,6 +30,7 @@ What is PrimAITE? source/varying_config_files source/environment source/action_masking + source/node_sets .. toctree:: :caption: Notebooks: diff --git a/docs/source/node_sets.rst b/docs/source/node_sets.rst new file mode 100644 index 00000000..2a1f74ce --- /dev/null +++ b/docs/source/node_sets.rst @@ -0,0 +1,115 @@ +.. only:: comment + + © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK + +.. _network_node_adder: + +Network Node Adder Module +######################### + +This module provides a framework for adding nodes to a network in a standardised way. It defines a base class ``NetworkNodeAdder``, which can be extended to create specific node adders, and utility functions to calculate network infrastructure requirements. + +The module allows you to use the pre-defined node adders, ``OfficeLANAdder``, or create custom ones by extending the base class. + +How It Works +============ + +The main class in the module is ``NetworkNodeAdder``, which defines the interface for adding nodes to a network. Child classes are expected to: + +1. Define a ``ConfigSchema`` nested class to define configuration options. +2. Implement the ``add_nodes_to_net(config, network)`` method, which adds the nodes to the network according to the configuration object. + +The ``NetworkNodeAdder`` base class handles node adders defined in the primAITE config YAML file as well. It does this by keeping a registry of node adder classes, and uses the ``type`` field of the config to select the appropriate class to which to pass the configuration. + +Example Usage +============= + +Via Python API +-------------- + +Adding nodes to a network can be done using the python API by constructing the relevant ``ConfigSchema`` object like this: + +.. code-block:: python + + net = Network() + + office_lan_config = OfficeLANAdder.ConfigSchema( + lan_name="CORP-LAN", + subnet_base=2, + pcs_ip_block_start=10, + num_pcs=8, + include_router=False, + bandwidth=150, + ) + OfficeLANAdder.add_nodes_to_net(config=office_lan_config, network=net) + +In this example, a network with 8 computers connected by a switch will be added to the network object. + + +Via YAML Config +--------------- + +.. code-block:: yaml + simulation: + network: + nodes: + # ... nodes go here + node_sets: + - type: office_lan + lan_name: CORP_LAN + subnet_base: 2 + pcs_ip_block_start: 10 + num_pcs: 8 + include_router: False + bandwidth: 150 + # ... additional node sets can be added below + +``NetworkNodeAdder`` reads the ``type`` property of the config, then constructs and passes the configuration to ``OfficeLANAdder.add_nodes_to_net()``. + +In this example, a network with 8 computers connected by a switch will be added to the network object. Equivalent to the above. + + +Creating Custom Node Adders +=========================== +To create a custom node adder, subclass NetworkNodeAdder and define: + +* A ConfigSchema class that defines the configuration schema for the node adder. +* The add_nodes_to_net method that implements how nodes should be added to the network. + +Example: DataCenterAdder +------------------------ +Here is an example of creating a custom node adder, DataCenterAdder: + +.. code-block:: python + + class DataCenterAdder(NetworkNodeAdder, identifier="data_center"): + class ConfigSchema(NetworkNodeAdder.ConfigSchema): + type: Literal["data_center"] = "data_center" + num_servers: int + data_center_name: str + + @classmethod + def add_nodes_to_net(cls, config: ConfigSchema, network: Network) -> None: + for i in range(config.num_servers): + server = Computer( + hostname=f"server_{i}_{config.data_center_name}", + ip_address=f"192.168.100.{i + 1}", + subnet_mask="255.255.255.0", + default_gateway="192.168.100.1", + start_up_duration=0 + ) + server.power_on() + network.add_node(server) + +**Using the Custom Node Adder:** + +.. code-block:: python + + config = { + "type": "data_center", + "num_servers": 5, + "data_center_name": "dc1" + } + + network = Network() + DataCenterAdder.from_config(config, network)