diff --git a/.azure/azure-ci-build-pipeline.yaml b/.azure/azure-ci-build-pipeline.yaml
index b6f24777..4353b082 100644
--- a/.azure/azure-ci-build-pipeline.yaml
+++ b/.azure/azure-ci-build-pipeline.yaml
@@ -15,37 +15,37 @@ parameters:
type: object
default:
- job_name: 'UbuntuPython39'
- py: '3.9'
+ py: 'v3.9'
img: 'ubuntu-latest'
every_time: false
publish_coverage: false
- job_name: 'UbuntuPython310'
- py: '3.10'
+ py: 'v3.10'
img: 'ubuntu-latest'
every_time: true
publish_coverage: true
- job_name: 'UbuntuPython311'
- py: '3.11'
+ py: 'v3.11'
img: 'ubuntu-latest'
every_time: false
publish_coverage: false
- job_name: 'WindowsPython39'
- py: '3.9'
+ py: 'v3.9'
img: 'windows-latest'
every_time: false
publish_coverage: false
- job_name: 'WindowsPython311'
- py: '3.11'
+ py: 'v3.11'
img: 'windows-latest'
every_time: false
publish_coverage: false
- job_name: 'MacOSPython39'
- py: '3.9'
+ py: 'v3.9'
img: 'macOS-latest'
every_time: false
publish_coverage: false
- job_name: 'MacOSPython311'
- py: '3.11'
+ py: 'v3.11'
img: 'macOS-latest'
every_time: false
publish_coverage: false
diff --git a/.github/workflows/build-sphinx.yml b/.github/workflows/build-sphinx.yml
index 4bfa4f4e..da20fbd3 100644
--- a/.github/workflows/build-sphinx.yml
+++ b/.github/workflows/build-sphinx.yml
@@ -1,8 +1,8 @@
name: build-sphinx-to-github-pages
env:
- GITHUB_ACTOR: {todo:fill in URL}
- GITHUB_REPOSITORY: {todo:fill in URL}/PrimAITE
+ GITHUB_ACTOR: Autonomous-Resilient-Cyber-Defence
+ GITHUB_REPOSITORY: Autonomous-Resilient-Cyber-Defence/PrimAITE
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN}}
on:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index fbcfa707..7c64365e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -39,7 +39,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.
+- Documentation has been updated to include details of extensibility with PrimAITE.
- Software is created in the GOOD health state instead of UNUSED.
- Standardised naming convention for YAML config files using kebab-case.
This naming convention is used for configuring software, observations, actions and node types.
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index dc10edbb..815400e2 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -4,17 +4,17 @@
### **Did you find a bug?**
-* **Ensure the bug was not already reported** by searching on GitHub under [Issues](https://github.com/{todo:fill in URL}/PrimAITE/issues).
-* If you're unable to find an open issue addressing the problem, [open a new one](https://github.com/{todo:fill in URL}/PrimAITE/issues/new?assignees=&labels=bug&projects=&template=bug_report.md&title=%5BBUG%5D+-+%3Cbug+title+goes+here%3E). Be sure to follow our bug report template with the headers **Describe the bug**, **To Reproduce**, **Expected behaviour**, **Screenshots/Outputs**, **Environment**, and **Additional context**
+* **Ensure the bug was not already reported** by searching on GitHub under [Issues](https://github.com/Autonomous-Resilient-Cyber-Defence/PrimAITE/issues).
+* If you're unable to find an open issue addressing the problem, [open a new one](https://github.com/Autonomous-Resilient-Cyber-Defence/PrimAITE/issues/new?assignees=&labels=bug&projects=&template=bug_report.md&title=%5BBUG%5D+-+%3Cbug+title+goes+here%3E). Be sure to follow our bug report template with the headers **Describe the bug**, **To Reproduce**, **Expected behaviour**, **Screenshots/Outputs**, **Environment**, and **Additional context**
### **Do you have a solution to fix the bug?**
-* [Fork the repository](https://github.com/{todo:fill in URL}/PrimAITE/fork).
+* [Fork the repository](https://github.com/Autonomous-Resilient-Cyber-Defence/PrimAITE/fork).
* Install the pre-commit hook with `pre-commit install`.
* Implement the bug fix.
-* Commit the bug fix to the dev branch on your fork. If the bug has an open issue under [Issues](https://github.com/{todo:fill in URL}/PrimAITE/issues), reference the issue in the commit message (e.g. #1 references issue 1).
-* Submit a pull request from your dev branch to the {todo:fill in URL}/PrimAITE dev branch. Again, if the bug has an open issue under [Issues](https://github.com/{todo:fill in URL}/PrimAITE/issues), reference the issue in the pull request description.
+* Commit the bug fix to the dev branch on your fork. If the bug has an open issue under [Issues](https://github.com/Autonomous-Resilient-Cyber-Defence/PrimAITE/issues), reference the issue in the commit message (e.g. #1 references issue 1).
+* Submit a pull request from your dev branch to the Autonomous-Resilient-Cyber-Defence/PrimAITE dev branch. Again, if the bug has an open issue under [Issues](https://github.com/Autonomous-Resilient-Cyber-Defence/PrimAITE/issues), reference the issue in the pull request description.
### **Did you fix whitespace, format code, or make a purely cosmetic patch?**
@@ -22,7 +22,7 @@ Changes that are cosmetic in nature and do not add anything substantial to the s
### **Do you intend to add a new feature or change an existing one?**
-* Submit a [feature request issue](https://github.com/{todo:fill in URL}/PrimAITE/issues/new?assignees=&labels=feature_request&projects=&template=feature_request.md&title=%5BREQUEST%5D+-+%3Crequest+title+goes+here%3E).
+* Submit a [feature request issue](https://github.com/Autonomous-Resilient-Cyber-Defence/PrimAITE/issues/new?assignees=&labels=feature_request&projects=&template=feature_request.md&title=%5BREQUEST%5D+-+%3Crequest+title+goes+here%3E).
* Know how to implement the new feature or change? Follow the same steps in the bug fix section above to fork, build, document, test, commit, and submit a pull request.
### **Do you have questions about the source code?**
diff --git a/LICENSE b/LICENSE
index 9575f430..8038f3d0 100644
--- a/LICENSE
+++ b/LICENSE
@@ -4,24 +4,21 @@ MIT License Conditions
These MIT License conditions confirm the provision of the following artefacts as MIT License by Defence Science and Technology
of this software and associated documentation files (the "Software"), to deal
-request to the QQ or FNC mailbox):
+in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
-- Use Case Release Packs
furnished to do so, subject to the following conditions:
-Suppliers are required to read and confirm acceptance of the {todo:fill in URL} Foundry SyOPs (https://github.com/{todo:fill in URL}/foundry-syops) before being admitted access to material hosted on the {todo:fill in URL} Foundry GitHub site.
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-The material is supplied in confidence to QQ / FNC and their subcontractors under SERAPIS, and is issued to inform only those
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
-
-
-of DSTL. The material must be stored and protected appropriately. All material must be destroyed at the end of the task.
-
diff --git a/README.md b/README.md
index c8f644be..a5375d66 100644
--- a/README.md
+++ b/README.md
@@ -79,7 +79,7 @@ To make your own changes to PrimAITE, perform the install from source (developer
#### 1. Clone the PrimAITE repository
``` unix
-git clone git@github.com:{todo:fill in URL}/PrimAITE.git
+git clone git@github.com:Autonomous-Resilient-Cyber-Defence/PrimAITE.git
```
#### 2. CD into the repo directory
diff --git a/docs/Makefile b/docs/Makefile
index d0f9af01..bc101a07 100644
--- a/docs/Makefile
+++ b/docs/Makefile
@@ -8,16 +8,18 @@ SOURCEDIR = .
BUILDDIR = _build
AUTOSUMMARY="source/_autosummary"
+NOTEBOOKS="source/notebooks/notebooks"
# Remove command is different depending on OS
ifdef OS
- RM = IF exist $(AUTOSUMMARY) ( RMDIR $(AUTOSUMMARY) /s /q )
+ RM = IF exist $(AUTOSUMMARY) (RMDIR $(AUTOSUMMARY) /s /q) & IF exist $(NOTEBOOKS) (RMDIR $(NOTEBOOKS) /s /q)
else
ifeq ($(shell uname), Linux)
- RM = rm -rf $(AUTOSUMMARY)
+ RM = rm -rf $(AUTOSUMMARY) $(NOTEBOOKS)
endif
endif
+
# Put it first so that "make" without argument is like "make help".
help:
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
diff --git a/docs/_templates/custom-class-template.rst b/docs/_templates/custom-class-template.rst
index 71e992bc..c9ad7420 100644
--- a/docs/_templates/custom-class-template.rst
+++ b/docs/_templates/custom-class-template.rst
@@ -12,7 +12,8 @@
.. autoclass:: {{ objname }}
:members:
:show-inheritance:
- :inherited-members:
+ :inherited-members: BaseModel
+ :exclude-members: model_computed_fields, model_config, model_fields
:special-members: __init__, __call__, __add__, __mul__
{% block methods %}
@@ -22,7 +23,14 @@
.. autosummary::
:nosignatures:
{% for item in methods %}
- {%- if not item.startswith('_') %}
+ {%- if not item.startswith('_') and item not in [
+ 'construct', 'copy', 'dict', 'from_orm', 'json', 'model_construct',
+ 'model_copy', 'model_dump', 'model_dump_json', 'model_json_schema',
+ 'model_parametrized_name', 'model_post_init', 'model_rebuild', '',
+ 'model_validate', 'model_validate_json', 'model_validate_strings',
+ 'parse_file', 'parse_obj', 'parse_raw', 'schema', 'schema_json',
+ 'update_forward_refs', 'validate',
+ ] %}
~{{ name }}.{{ item }}
{%- endif -%}
{%- endfor %}
@@ -35,7 +43,12 @@
.. autosummary::
{% for item in attributes %}
+ {%- if not item.startswith('_') and item not in [
+ 'model_computed_fields', 'model_config', 'model_extra', 'model_fields',
+ 'model_fields_set',
+ ] %}
~{{ name }}.{{ item }}
+ {%- endif -%}
{%- endfor %}
{% endif %}
{% endblock %}
diff --git a/docs/build-sphinx-docs-to-github-pages.sh b/docs/build-sphinx-docs-to-github-pages.sh
index a180f168..dc6394cb 100644
--- a/docs/build-sphinx-docs-to-github-pages.sh
+++ b/docs/build-sphinx-docs-to-github-pages.sh
@@ -43,7 +43,7 @@ touch .nojekyll
# Add README
cat > README.md <`_ is used as the basis for AI blue agent interaction with the PrimAITE environment
-* `Networkx `_ is used as the underlying data structure used for the PrimAITE environment
-* `Stable Baselines 3 `_ is used as a default source of RL algorithms (although PrimAITE is not limited to SB3 agents)
-* `Ray RLlib `_ is used as an additional source of RL algorithms
-* `Typer `_ is used for building CLIs (Command Line Interface applications)
+* `Pydantic `_ is used for data validation
+* `Platformdirs `_ is used for storing user data and configuration correctly between platforms
+* `Typer `_ is used for the Command Line Interface
* `Jupyterlab `_ is used as an extensible environment for interactive and reproducible computing, based on the Jupyter Notebook Architecture
-* `Platformdirs `_ is used for finding the right location to store user data and configuration but varies per platform
* `Plotly `_ is used for building high level charts
+* `Stable Baselines 3 `_ is used for ensuring compatibility with RL libraries
+* `Ray RLlib `_ is also used for ensuring compatibility with RL libraries
Getting Started with PrimAITE
-----------------------------
Head over to the :ref:`getting-started` page to install and setup PrimAITE!
-
-..
- Architecture - Nodes and Links
- ******************************
- **Nodes**
- An inheritance model has been adopted in order to model nodes. All nodes have the following base attributes (Class: Node):
- * ID
- * Name
- * Type (e.g. computer, switch, RTU - enumeration)
- * Priority (P1, P2, P3, P4 or P5 - enumeration)
- * Hardware State (ON, OFF, RESETTING, SHUTTING_DOWN, BOOTING - enumeration)
- Active Nodes also have the following attributes (Class: Active Node):
- * IP Address
- * Software State (GOOD, FIXING, COMPROMISED - enumeration)
- * File System State (GOOD, CORRUPT, DESTROYED, REPAIRING, RESTORING - enumeration)
- Service Nodes also have the following attributes (Class: Service Node):
- * List of Services (where service is composed of service name and port). There is no theoretical limit on the number of services that can be modelled. Services and protocols are currently intrinsically linked (i.e. a service is an application on a node transmitting traffic of this protocol type)
- * Service state (GOOD, FIXING, COMPROMISED, OVERWHELMED - enumeration)
- Passive Nodes are currently not used (but may be employed for non IP-based components such as machinery actuators in future releases).
- **Links**
- Links are modelled both as network edges (networkx) and as Python classes, in order to extend their functionality. Links include the following attributes:
- * ID
- * Name
- * Bandwidth (bits/s)
- * Source node ID
- * Destination node ID
- * Protocol list (containing the loading of protocols currently running on the link)
- When the simulation runs, IERs are applied to the links in order to model traffic loading, individually assigned to each protocol. This allows green (background) and red agent behaviour to be modelled, and defensive agents to identify suspicious traffic patterns at a protocol / traffic loading level of fidelity.
- Information Exchange Requirements (IERs)
- ****************************************
- PrimAITE adopts the concept of Information Exchange Requirements (IERs) to model both green agent (background) and red agent (adversary) behaviour. IERs are used to initiate modelling of traffic loading on the network, and have the following attributes:
- * ID
- * Start step (i.e. which step in the training episode should the IER start)
- * End step (i.e. which step in the training episode should the IER end)
- * Source node ID
- * Destination node ID
- * Load (bits/s)
- * Protocol
- * Port
- * Running status (i.e. on / off)
- The application of green agent IERs between a source and destination follows a number of rules. Specifically:
- 1. Does the current simulation time step fall between IER start and end step
- 2. Is the source node operational (both physically and at an O/S level), and is the service (protocol / port) associated with the IER (a) present on this node, and (b) in an operational state (i.e. not FIXING)
- 3. Is the destination node operational (both physically and at an O/S level), and is the service (protocol / port) associated with the IER (a) present on this node, and (b) in an operational state (i.e. not FIXING)
- 4. Are there any Access Control List rules in place that prevent the application of this IER
- 5. Are all switches in the (OSPF) path between source and destination operational (both physically and at an O/S level)
- For red agent IERs, the application of IERs between a source and destination follows a number of subtly different rules. Specifically:
- 1. Does the current simulation time step fall between IER start and end step
- 2. Is the source node operational, and is the service (protocol / port) associated with the IER (a) present on that node and (b) already in a compromised state
- 3. Is the destination node operational, and is the service (protocol / port) associated with the IER present on that node
- 4. Are there any Access Control List rules in place that prevent the application of this IER
- 5. Are all switches in the (OSPF) path between source and destination operational (both physically and at an O/S level)
- Assuming the rules pass, the IER is applied to all relevant links (based on use of OSPF) between source and destination.
- Node Pattern-of-Life
- ********************
- Every node can be impacted (i.e. have a status change applied to it) by either green agent pattern-of-life or red agent pattern-of-life. This is distinct from IERs, and allows for attacks (and defence) to be modelled purely within the confines of a node.
- The status changes that can be made to a node are as follows:
- * All Nodes:
- * Hardware State:
- * ON
- * OFF
- * RESETTING - when a status of resetting is entered, the node will automatically exit this state after a number of steps (as defined by the nodeResetDuration configuration item) after which it returns to an ON state
- * BOOTING
- * SHUTTING_DOWN
- * Active Nodes and Service Nodes:
- * Software State:
- * GOOD
- * FIXING - when a status of FIXING is entered, the node will automatically exit this state after a number of steps (as defined by the osFIXINGDuration configuration item) after which it returns to a GOOD state
- * COMPROMISED
- * File System State:
- * GOOD
- * CORRUPT (can be resolved by repair or restore)
- * DESTROYED (can be resolved by restore only)
- * REPAIRING - when a status of repairing is entered, the node will automatically exit this state after a number of steps (as defined by the fileSystemRepairingLimit configuration item) after which it returns to a GOOD state
- * RESTORING - when a status of repairing is entered, the node will automatically exit this state after a number of steps (as defined by the fileSystemRestoringLimit configuration item) after which it returns to a GOOD state
- * Service Nodes only:
- * Service State (for any associated service):
- * GOOD
- * FIXING - when a status of FIXING is entered, the service will automatically exit this state after a number of steps (as defined by the serviceFIXINGDuration configuration item) after which it returns to a GOOD state
- * COMPROMISED
- * OVERWHELMED
- Red agent pattern-of-life has an additional feature not found in the green pattern-of-life. This is the ability to influence the state of the attributes of a node via a number of different conditions:
- * DIRECT:
- The pattern-of-life described by the configuration file item will be applied regardless of any other conditions in the network. This is particularly useful for direct red agent entry into the network.
- * IER:
- The pattern-of-life described by the configuration file item will be applied to the service on the node, only if there is an IER of the same protocol / service type incoming at the specified timestep.
- * SERVICE:
- The pattern-of-life described by the configuration file item will be applied to the node based on the state of a service. The service can either be on the same node, or a different node within the network.
- Access Control List modelling
- *****************************
- An Access Control List (ACL) is modelled to provide the means to manage traffic flows in the system. This will allow defensive agents the means to turn on / off rules, or potentially create new rules, to counter an attack.
- The ACL follows a standard network firewall format. For example:
- .. list-table:: ACL example
- :widths: 25 25 25 25 25
- :header-rows: 1
- * - Permission
- - Source IP
- - Dest IP
- - Protocol
- - Port
- * - DENY
- - 192.168.1.2
- - 192.168.1.3
- - HTTPS
- - 443
- * - ALLOW
- - 192.168.1.4
- - ANY
- - SMTP
- - 25
- * - DENY
- - ANY
- - 192.168.1.5
- - ANY
- - ANY
- All ACL rules are considered when applying an IER. Logic follows the order of rules, so a DENY or PERMIT for the same parameters will override an earlier entry.
- Observation Spaces
- ******************
- The observation space provides the blue agent with information about the current status of nodes and links.
- PrimAITE builds on top of Gymnasium Spaces to create an observation space that is easily configurable for users. It's made up of components which are managed by the :py:class:`primaite.environment.observations.ObservationsHandler`. Each training scenario can define its own observation space, and the user can choose which information to inlude, and how it should be formatted.
- NodeLinkTable component
- -----------------------
- For example, the :py:class:`primaite.environment.observations.NodeLinkTable` component represents the status of nodes and links as a ``gym.spaces.Box`` with an example format shown below:
- An example observation space is provided below:
- .. list-table:: Observation Space example
- :widths: 25 25 25 25 25 25 25
- :header-rows: 1
- * -
- - ID
- - Hardware State
- - Software State
- - File System State
- - Service / Protocol A
- - Service / Protocol B
- * - Node A
- - 1
- - 1
- - 1
- - 1
- - 1
- - 1
- * - Node B
- - 2
- - 1
- - 3
- - 1
- - 1
- - 1
- * - Node C
- - 3
- - 2
- - 1
- - 1
- - 3
- - 2
- * - Link 1
- - 5
- - 0
- - 0
- - 0
- - 0
- - 10000
- * - Link 2
- - 6
- - 0
- - 0
- - 0
- - 0
- - 10000
- * - Link 3
- - 7
- - 0
- - 0
- - 0
- - 5000
- - 0
- For the nodes, the following values are represented:
- .. code-block::
- [
- ID
- Hardware State (1=ON, 2=OFF, 3=RESETTING, 4=SHUTTING_DOWN, 5=BOOTING)
- Operating System State (0=none, 1=GOOD, 2=PATCHING, 3=COMPROMISED)
- File System State (0=none, 1=GOOD, 2=CORRUPT, 3=DESTROYED, 4=REPAIRING, 5=RESTORING)
- Service1/Protocol1 state (0=none, 1=GOOD, 2=FIXING, 3=COMPROMISED)
- Service2/Protocol2 state (0=none, 1=GOOD, 2=FIXING, 3=COMPROMISED)
- ]
- (Note that each service available in the network is provided as a column, although not all nodes may utilise all services)
- For the links, the following statuses are represented:
- .. code-block::
- [
- ID
- Hardware State (0=not applicable)
- Operating System State (0=not applicable)
- File System State (0=not applicable)
- Service1/Protocol1 state (Traffic load from this protocol on this link)
- Service2/Protocol2 state (Traffic load from this protocol on this link)
- ]
- NodeStatus component
- ----------------------
- This is a MultiDiscrete observation space that can be though of as a one-dimensional vector of discrete states.
- The example above would have the following structure:
- .. code-block::
- [
- node1_info
- node2_info
- node3_info
- ]
- Each ``node_info`` contains the following:
- .. code-block::
- [
- hardware_state (0=none, 1=ON, 2=OFF, 3=RESETTING, 4=SHUTTING_DOWN, 5=BOOTING)
- software_state (0=none, 1=GOOD, 2=PATCHING, 3=COMPROMISED)
- file_system_state (0=none, 1=GOOD, 2=CORRUPT, 3=DESTROYED, 4=REPAIRING, 5=RESTORING)
- service1_state (0=none, 1=GOOD, 2=FIXING, 3=COMPROMISED)
- service2_state (0=none, 1=GOOD, 2=FIXING, 3=COMPROMISED)
- ]
- In a network with three nodes and two services, the full observation space would have 15 elements. It can be written with ``gym`` notation to indicate the number of discrete options for each of the elements of the observation space. For example:
- .. code-block::
- gym.spaces.MultiDiscrete([4,5,6,4,4,4,5,6,4,4,4,5,6,4,4])
- .. note::
- NodeStatus observation component provides information only about nodes. Links are not considered.
- LinkTrafficLevels
- -----------------
- This component is a MultiDiscrete space showing the traffic flow levels on the links in the network, after applying a threshold to convert it from a continuous to a discrete value.
- There are two configurable parameters:
- * ``quantisation_levels`` determines how many discrete bins to use for converting the continuous traffic value to discrete (default is 5).
- * ``combine_service_traffic`` determines whether to separately output traffic use for each network protocol or whether to combine them into an overall value for the link. (default is ``True``)
- For example, with default parameters and a network with three links, the structure of this component would be:
- .. code-block::
- [
- link1_status
- link2_status
- link3_status
- ]
- Each ``link_status`` is a number from 0-4 representing the network load in relation to bandwidth.
- .. code-block::
- 0 = No traffic (0%)
- 1 = low traffic (1%-33%)
- 2 = medium traffic (33%-66%)
- 3 = high traffic (66%-99%)
- 4 = max traffic/ overwhelmed (100%)
- Using ``gym`` notation, the shape of the obs space is: ``gym.spaces.MultiDiscrete([5,5,5])``.
- Action Spaces
- **************
- The action space available to the blue agent comes in two types:
- 1. Node-based
- 2. Access Control List
- 3. Any (Agent can take both node-based and ACL-based actions)
- The choice of action space used during a training session is determined in the config_[name].yaml file.
- **Node-Based**
- The agent is able to influence the status of nodes by switching them off, resetting, or FIXING operating systems and services. In this instance, the action space is a Gymnasium spaces.Discrete type, as follows:
- * Dictionary item {... ,1: [x1, x2, x3,x4] ...}
- The placeholders inside the list under the key '1' mean the following:
- * [0, num nodes] - Node ID (0 = nothing, node ID)
- * [0, 4] - What property it's acting on (0 = nothing, 1 = state, 2 = SoftwareState, 3 = service state, 4 = file system state)
- * [0, 3] - Action on property (0 = nothing, 1 = on / scan, 2 = off / repair, 3 = reset / patch / restore)
- * [0, num services] - Resolves to service ID (0 = nothing, resolves to service)
- **Access Control List**
- The blue agent is able to influence the configuration of the Access Control List rule set (which implements a system-wide firewall). In this instance, the action space is an Gymnasium spaces.Discrete type, as follows:
- * Dictionary item {... ,1: [x1, x2, x3, x4, x5, x6] ...}
- The placeholders inside the list under the key '1' mean the following:
- * [0, 2] - Action (0 = do nothing, 1 = create rule, 2 = delete rule)
- * [0, 1] - Permission (0 = DENY, 1 = PERMIT)
- * [0, num nodes] - Source IP (0 = any, then 1 -> x resolving to IP addresses)
- * [0, num nodes] - Dest IP (0 = any, then 1 -> x resolving to IP addresses)
- * [0, num services] - Protocol (0 = any, then 1 -> x resolving to protocol)
- * [0, num ports] - Port (0 = any, then 1 -> x resolving to port)
- **ANY**
- The agent is able to carry out both **Node-Based** and **Access Control List** operations.
- This means the dictionary will contain key-value pairs in the format of BOTH Node-Based and Access Control List as seen above.
- Rewards
- *******
- A reward value is presented back to the blue agent on the conclusion of every step. The reward value is calculated via two methods which combine to give the total value:
- 1. Node and service status
- 2. IER status
- **Node and service status**
- On every step, the status of each node is compared against both a reference environment (simulating the situation if the red and blue agents had not impacted the environment)
- and the before and after state of the environment. If the comparison against the reference environment shows no difference, then the score provided is "AllOK". If there is a
- difference with respect to the reference environment, the before and after states are compared, and a score determined. See :ref:`config` for details of reward values.
- **IER status**
- On every step, the full IER set is examined to determine whether green and red agent IERs are being permitted to run. Any red agent IERs running incur a penalty; any green agent
- IERs not permitted to run also incur a penalty. See :ref:`config` for details of reward values.
- Future Enhancements
- *******************
- The PrimAITE project has an ambition to include the following enhancements in future releases:
- * Integration with a suitable standardised framework to allow multi-agent integration
- * Integration with external threat emulation tools, either using off-line data, or integrating at runtime
diff --git a/docs/source/action_masking.rst b/docs/source/action_masking.rst
index bee4674b..c779ecb5 100644
--- a/docs/source/action_masking.rst
+++ b/docs/source/action_masking.rst
@@ -2,11 +2,13 @@
© Crown-owned copyright 2025, Defence Science and Technology Laboratory UK
+.. _action_masking:
+
Action Masking
**************
The PrimAITE simulation is able to provide action masks in the environment output. These action masks let the agents know
about which actions are invalid based on the current environment state. For instance, it's not possible to install
-software on a node that is turned off. Therefore, if an agent has a NODE_SOFTWARE_INSTALL in it's action map for that node,
+software on a node that is turned off. Therefore, if an agent has a ``node-software-install`` in it's action map for that node,
the action mask will show `0` in the corresponding entry.
*Note: just because an action is available in the action mask does not mean it will be successful when executed. It just means it's possible to try to execute the action at this time.*
@@ -20,132 +22,127 @@ Masking Logic
=============
The following logic is applied:
-+------------------------------------------+---------------------------------------------------------------------+
-| Action | Action Mask Logic |
-+==========================================+=====================================================================+
-| **do-nothing** | Always Possible. |
-+------------------------------------------+---------------------------------------------------------------------+
-| **node-service-scan** | Node is on. Service is running. |
-+------------------------------------------+---------------------------------------------------------------------+
-| **node-service-stop** | Node is on. Service is running. |
-+------------------------------------------+---------------------------------------------------------------------+
-| **node-service-start** | Node is on. Service is stopped. |
-+------------------------------------------+---------------------------------------------------------------------+
-| **node-service-pause** | Node is on. Service is running. |
-+------------------------------------------+---------------------------------------------------------------------+
-| **node-service-resume** | Node is on. Service is paused. |
-+------------------------------------------+---------------------------------------------------------------------+
-| **node-service-restart** | Node is on. Service is running. |
-+------------------------------------------+---------------------------------------------------------------------+
-| **node-service-disable** | Node is on. |
-+------------------------------------------+---------------------------------------------------------------------+
-| **node-service-enable** | Node is on. Service is disabled. |
-+------------------------------------------+---------------------------------------------------------------------+
-| **node-service-fix** | Node is on. Service is running. |
-+------------------------------------------+---------------------------------------------------------------------+
-| **node-application-execute** | Node is on. |
-+------------------------------------------+---------------------------------------------------------------------+
-| **node-application-scan** | Node is on. Application is running. |
-+------------------------------------------+---------------------------------------------------------------------+
-| **node-application-close** | Node is on. Application is running. |
-+------------------------------------------+---------------------------------------------------------------------+
-| **node-application-fix** | Node is on. Application is running. |
-+------------------------------------------+---------------------------------------------------------------------+
-| **node-application-install** | Node is on. |
-+------------------------------------------+---------------------------------------------------------------------+
-| **node-application-remove** | Node is on. |
-+------------------------------------------+---------------------------------------------------------------------+
-| **node-file-scan** | Node is on. File exists. File not deleted. |
-+------------------------------------------+---------------------------------------------------------------------+
-| **node-file-create** | Node is on. |
-+------------------------------------------+---------------------------------------------------------------------+
-| **node-file-checkhash** | Node is on. File exists. File not deleted. |
-+------------------------------------------+---------------------------------------------------------------------+
-| **node-file-delete** | Node is on. File exists. |
-+------------------------------------------+---------------------------------------------------------------------+
-| **node-file-repair** | Node is on. File exists. File not deleted. |
-+------------------------------------------+---------------------------------------------------------------------+
-| **node-file-restore** | Node is on. File exists. File is deleted. |
-+------------------------------------------+---------------------------------------------------------------------+
-| **node-file-corrupt** | Node is on. File exists. File not deleted. |
-+------------------------------------------+---------------------------------------------------------------------+
-| **node-file-access** | Node is on. File exists. File not deleted. |
-+------------------------------------------+---------------------------------------------------------------------+
-| **node-folder-create** | Node is on. |
-+------------------------------------------+---------------------------------------------------------------------+
-| **node-folder-scan** | Node is on. Folder exists. Folder not deleted. |
-+------------------------------------------+---------------------------------------------------------------------+
-| **node-folder-checkhash** | Node is on. Folder exists. Folder not deleted. |
-+------------------------------------------+---------------------------------------------------------------------+
-| **node-folder-repair** | Node is on. Folder exists. Folder not deleted. |
-+------------------------------------------+---------------------------------------------------------------------+
-| **node-folder-restore** | Node is on. Folder exists. Folder is deleted. |
-+------------------------------------------+---------------------------------------------------------------------+
-| **node-os-scan** | Node is on. |
-+------------------------------------------+---------------------------------------------------------------------+
-| **host-nic-enable** | NIC is disabled. Node is on. |
-+------------------------------------------+---------------------------------------------------------------------+
-| **host-nic-disable** | NIC is enabled. Node is on. |
-+------------------------------------------+---------------------------------------------------------------------+
-| **node-shutdown** | Node is on. |
-+------------------------------------------+---------------------------------------------------------------------+
-| **node-startup** | Node is off. |
-+------------------------------------------+---------------------------------------------------------------------+
-| **node-reset** | Node is on. |
-+------------------------------------------+---------------------------------------------------------------------+
-| **node-nmap-ping-scan** | Node is on. |
-+------------------------------------------+---------------------------------------------------------------------+
-| **node-nmap-port-scan** | Node is on. |
-+------------------------------------------+---------------------------------------------------------------------+
-| **node-network-service-recon** | Node is on. |
-+------------------------------------------+---------------------------------------------------------------------+
-| **network-port-enable** | Node is on. Router is on. |
-+------------------------------------------+---------------------------------------------------------------------+
-| **network-port-disable** | Router is on. |
-+------------------------------------------+---------------------------------------------------------------------+
-| **router-acl-add-rule** | Router is on. |
-+------------------------------------------+---------------------------------------------------------------------+
-| **router-acl-remove-rule** | Router is on. |
-+------------------------------------------+---------------------------------------------------------------------+
-| **firewall-acl-add-rule** | Firewall is on. |
-+------------------------------------------+---------------------------------------------------------------------+
-| **firewall-acl-remove-rule** | Firewall is on. |
-+------------------------------------------+---------------------------------------------------------------------+
-| **configure-database-client** | Node is on. |
-+------------------------------------------+---------------------------------------------------------------------+
-| **configure-ransomware-script** | Node is on. |
-+------------------------------------------+---------------------------------------------------------------------+
-| **c2-server-ransomware-configure** | Node is on. |
-+------------------------------------------+---------------------------------------------------------------------+
-| **configure-dos-bot** | Node is on. |
-+------------------------------------------+---------------------------------------------------------------------+
-| **configure-c2-beacon** | Node is on. |
-+------------------------------------------+---------------------------------------------------------------------+
-| **c2-server-ransomware-launch** | Node is on. |
-+------------------------------------------+---------------------------------------------------------------------+
-| **c2-server-terminal-command** | Node is on. |
-+------------------------------------------+---------------------------------------------------------------------+
-| **c2-server-data-exfiltrate** | Node is on. |
-+------------------------------------------+---------------------------------------------------------------------+
-| **node-account-change-password** | Node is on. |
-+------------------------------------------+---------------------------------------------------------------------+
-| **node-session-remote-login** | Node is on. |
-+------------------------------------------+---------------------------------------------------------------------+
-| **node-session-remote-logoff** | Node is on. |
-+------------------------------------------+---------------------------------------------------------------------+
-| **node-send-remote-command** | Node is on. |
-+------------------------------------------+---------------------------------------------------------------------+
++------------------------------------------+------------------------------------------------+
+| Action | Action Mask Logic |
++==========================================+================================================+
+| **do-nothing** | Always Possible. |
++------------------------------------------+------------------------------------------------+
+| **node-service-scan** | Node is on. Service is running. |
++------------------------------------------+------------------------------------------------+
+| **node-service-stop** | Node is on. Service is running. |
++------------------------------------------+------------------------------------------------+
+| **node-service-start** | Node is on. Service is stopped. |
++------------------------------------------+------------------------------------------------+
+| **node-service-pause** | Node is on. Service is running. |
++------------------------------------------+------------------------------------------------+
+| **node-service-resume** | Node is on. Service is paused. |
++------------------------------------------+------------------------------------------------+
+| **node-service-restart** | Node is on. Service is running. |
++------------------------------------------+------------------------------------------------+
+| **node-service-disable** | Node is on. |
++------------------------------------------+------------------------------------------------+
+| **node-service-enable** | Node is on. Service is disabled. |
++------------------------------------------+------------------------------------------------+
+| **node-service-fix** | Node is on. Service is running. |
++------------------------------------------+------------------------------------------------+
+| **node-application-execute** | Node is on. |
++------------------------------------------+------------------------------------------------+
+| **node-application-scan** | Node is on. Application is running. |
++------------------------------------------+------------------------------------------------+
+| **node-application-close** | Node is on. Application is running. |
++------------------------------------------+------------------------------------------------+
+| **node-application-fix** | Node is on. Application is running. |
++------------------------------------------+------------------------------------------------+
+| **node-application-install** | Node is on. |
++------------------------------------------+------------------------------------------------+
+| **node-application-remove** | Node is on. |
++------------------------------------------+------------------------------------------------+
+| **node-file-scan** | Node is on. File exists. File not deleted. |
++------------------------------------------+------------------------------------------------+
+| **node-file-create** | Node is on. |
++------------------------------------------+------------------------------------------------+
+| **node-file-checkhash** | Node is on. File exists. File not deleted. |
++------------------------------------------+------------------------------------------------+
+| **node-file-delete** | Node is on. File exists. |
++------------------------------------------+------------------------------------------------+
+| **node-file-repair** | Node is on. File exists. File not deleted. |
++------------------------------------------+------------------------------------------------+
+| **node-file-restore** | Node is on. File exists. File is deleted. |
++------------------------------------------+------------------------------------------------+
+| **node-file-corrupt** | Node is on. File exists. File not deleted. |
++------------------------------------------+------------------------------------------------+
+| **node-file-access** | Node is on. File exists. File not deleted. |
++------------------------------------------+------------------------------------------------+
+| **node-folder-create** | Node is on. |
++------------------------------------------+------------------------------------------------+
+| **node-folder-scan** | Node is on. Folder exists. Folder not deleted. |
++------------------------------------------+------------------------------------------------+
+| **node-folder-checkhash** | Node is on. Folder exists. Folder not deleted. |
++------------------------------------------+------------------------------------------------+
+| **node-folder-repair** | Node is on. Folder exists. Folder not deleted. |
++------------------------------------------+------------------------------------------------+
+| **node-folder-restore** | Node is on. Folder exists. Folder is deleted. |
++------------------------------------------+------------------------------------------------+
+| **node-os-scan** | Node is on. |
++------------------------------------------+------------------------------------------------+
+| **host-nic-enable** | NIC is disabled. Node is on. |
++------------------------------------------+------------------------------------------------+
+| **host-nic-disable** | NIC is enabled. Node is on. |
++------------------------------------------+------------------------------------------------+
+| **node-shutdown** | Node is on. |
++------------------------------------------+------------------------------------------------+
+| **node-startup** | Node is off. |
++------------------------------------------+------------------------------------------------+
+| **node-reset** | Node is on. |
++------------------------------------------+------------------------------------------------+
+| **node-nmap-ping-scan** | Node is on. |
++------------------------------------------+------------------------------------------------+
+| **node-nmap-port-scan** | Node is on. |
++------------------------------------------+------------------------------------------------+
+| **node-network-service-recon** | Node is on. |
++------------------------------------------+------------------------------------------------+
+| **network-port-enable** | Node is on. Router is on. |
++------------------------------------------+------------------------------------------------+
+| **network-port-disable** | Router is on. |
++------------------------------------------+------------------------------------------------+
+| **router-acl-add-rule** | Router is on. |
++------------------------------------------+------------------------------------------------+
+| **router-acl-remove-rule** | Router is on. |
++------------------------------------------+------------------------------------------------+
+| **firewall-acl-add-rule** | Firewall is on. |
++------------------------------------------+------------------------------------------------+
+| **firewall-acl-remove-rule** | Firewall is on. |
++------------------------------------------+------------------------------------------------+
+| **configure-database-client** | Node is on. |
++------------------------------------------+------------------------------------------------+
+| **configure-ransomware-script** | Node is on. |
++------------------------------------------+------------------------------------------------+
+| **c2-server-ransomware-configure** | Node is on. |
++------------------------------------------+------------------------------------------------+
+| **configure-dos-bot** | Node is on. |
++------------------------------------------+------------------------------------------------+
+| **configure-c2-beacon** | Node is on. |
++------------------------------------------+------------------------------------------------+
+| **c2-server-ransomware-launch** | Node is on. |
++------------------------------------------+------------------------------------------------+
+| **c2-server-terminal-command** | Node is on. |
++------------------------------------------+------------------------------------------------+
+| **c2-server-data-exfiltrate** | Node is on. |
++------------------------------------------+------------------------------------------------+
+| **node-account-change-password** | Node is on. |
++------------------------------------------+------------------------------------------------+
+| **node-session-remote-login** | Node is on. |
++------------------------------------------+------------------------------------------------+
+| **node-session-remote-logoff** | Node is on. |
++------------------------------------------+------------------------------------------------+
+| **node-send-remote-command** | Node is on. |
++------------------------------------------+------------------------------------------------+
Mechanism
=========
-The environment iterates over the RL agent's ``action_map`` and generates the corresponding simulator request string.
-It uses the ``RequestManager.check_valid()`` method to invoke the relevant ``RequestPermissionValidator`` without
-actually running the request on the simulation.
+The environment iterates over the RL agent's ``action_map`` and generates the corresponding simulator :ref:`request ` string. It uses the :py:meth:`RequestManager.check_valid()` method to invoke the relevant :py:class:`RequestPermissionValidator ` without actually running the request on the simulation.
Current Limitations
===================
-Currently, action masking only considers whether the action as a whole is possible, it doesn't verify that the exact
-parameter combination passed to the action make sense in the current context. For instance, if ACL rule 3 on router_1 is
-already populated, the action for adding another rule at position 3 will be available regardless, as long as that router
-is turned on. This will never block valid actions. It will just occasionally allow invalid actions.
+Currently, action masking only considers whether the action as a whole is possible, it doesn't verify that the exact parameter combination passed to the action make sense in the current context. or instance, if ACL rule 3 on router_1 is already populated, the action for adding another rule at position 3 will be available regardless, as long as that router is turned on. This will never block valid actions. It will just occasionally allow invalid actions.
diff --git a/docs/source/config.rst b/docs/source/config.rst
index 0fa4a4d5..14219c15 100644
--- a/docs/source/config.rst
+++ b/docs/source/config.rst
@@ -2,6 +2,8 @@
© Crown-owned copyright 2025, Defence Science and Technology Laboratory UK
+.. _Configurable_Items:
+
PrimAITE |VERSION| Configuration
********************************
diff --git a/docs/source/configuration/agents.rst b/docs/source/configuration/agents.rst
index c2674e31..96a09448 100644
--- a/docs/source/configuration/agents.rst
+++ b/docs/source/configuration/agents.rst
@@ -13,20 +13,19 @@ Agents can be scripted (deterministic and stochastic), or controlled by a reinfo
.. code-block:: yaml
agents:
- - ref: red_agent_example
- ...
- - ref: blue_agent_example
- ...
- - ref: green_agent_example
- team: GREEN
- type: probabilistic-agent
+ - ref: red_agent_example
+ ...
+ - ref: blue_agent_example
+ ...
+ - ref: green_agent_example
+ team: GREEN
+ type: probabilistic-agent
- agent_settings:
- start_settings:
- start_step: 5
- frequency: 4
- variance: 3
- flatten_obs: False
+ agent_settings:
+ start_step: 5
+ frequency: 4
+ variance: 3
+ flatten_obs: False
``ref``
-------
@@ -97,8 +96,7 @@ Similar to action space, this is defined as a list of components from the :py:mo
``reward_components``
^^^^^^^^^^^^^^^^^^^^^
-TODO: update description
-A list of reward types from :py:mod:`primaite.game.agent.rewards.RewardFunction.rew_class_identifiers`
+A list of available reward types from :py:mod:`primaite.game.agent.rewards.RewardFunction.rew_class_identifiers`
e.g.
@@ -106,7 +104,13 @@ e.g.
reward_components:
- type: dummy
+ weight: 1.0
- type: database-file-integrity
+ weight: 0.40
+ options:
+ node_hostname: database_server
+ folder_name: database
+ file_name: database.db
``agent_settings``
@@ -119,10 +123,9 @@ e.g.
.. code-block:: yaml
agent_settings:
- start_settings:
- start_step: 25
- frequency: 20
- variance: 5
+ start_step: 25
+ frequency: 20
+ variance: 5
``start_step``
^^^^^^^^^^^^^^
@@ -154,4 +157,4 @@ If ``True``, gymnasium flattening will be performed on the observation space bef
-----------------
Agents will record their action log for each step. This is a summary of what the agent did, along with response information from requests within the simulation.
-A summary of the actions taken by the agent can be viewed using the `show_history()` function. By default, this will display all actions taken apart from ``DONOTHING``.
+A summary of the actions taken by the agent can be viewed using the `show_history()` function. By default, this will display all actions taken apart from ``do-nothing``.
diff --git a/docs/source/configuration/game.rst b/docs/source/configuration/game.rst
index b3c139b2..02c2a441 100644
--- a/docs/source/configuration/game.rst
+++ b/docs/source/configuration/game.rst
@@ -42,14 +42,14 @@ The maximum number of episodes a Reinforcement Learning agent(s) can be trained
A list of ports that the Reinforcement Learning agent(s) are able to see in the observation space.
-See :ref:`List of Ports ` for a list of ports.
+See :py:const:`primaite.utils.validation.port.PORT_LOOKUP` for a list of ports.
``protocols``
-------------
A list of protocols that the Reinforcement Learning agent(s) are able to see in the observation space.
-See :ref:`List of IPProtocols ` for a list of protocols.
+See :py:const:`primaite.utils.validation.ip_protocol.PROTOCOL_LOOKUP` for a list of protocols.
``thresholds``
--------------
@@ -59,4 +59,4 @@ These are used to determine the thresholds of high, medium and low categories fo
``seed``
--------
-Used to configure the random seeds used within PrimAITE, ensuring determinism within episode/session runs. If empty or set to -1, no seed is set.
+Used to configure the random seeds used within PrimAITE, ensuring determinism within episode/session runs. If empty or set to -1, no seed is set. The given seed value is logged (by default) in ``primaite//sessions//
\n",
"\n",
- "In this stage, TAP001 uses the **NODE_FOLDER_CREATE** and **NODE_FILE_CREATE** to create a file called ```\"malware_dropper.ps1\"``` within a ```\"Downloads\"``` folder.
\n",
+ "In this stage, TAP001 uses the **node-folder-create** and **node-file-create** to create a file called ```\"malware_dropper.ps1\"``` within a ```\"Downloads\"``` folder.
\n",
"These actions are intended to simulate the malicious payload creating a ```ps1``` (A windows powershell script) malware dropper on the `SOME_TECH` employee's phone. \n",
"\n",
"Currently, PrimAITE cannot simulate hosts joining the simulation mid-episode thus we must treat `ST_PROJ-A-PRV-PC-1`'s as also including the employee's phone.
\n",
@@ -350,7 +350,8 @@
"metadata": {},
"outputs": [],
"source": [
- "tap001.logger.show()"
+ "tap001.logger.show()\n",
+ "tap001.show_history()"
]
},
{
@@ -369,7 +370,7 @@
" \n",
"\n",
"\n",
- "In this stage, TAP001 uses the **NODE_FILE_ACCESS** to increase the number of accesses of the ```\"malware_dropper.ps1\"```.
\n",
+ "In this stage, TAP001 uses the **node-file-access** to increase the number of accesses of the ```\"malware_dropper.ps1\"```.
\n",
"\n",
"These actions represent the employee executing malware dropper created in the previous stage. "
]
@@ -399,7 +400,8 @@
"metadata": {},
"outputs": [],
"source": [
- "tap001.logger.show()"
+ "tap001.logger.show()\n",
+ "tap001.show_history()"
]
},
{
@@ -419,12 +421,10 @@
" \n",
"\n",
"\n",
- "In this stage, TAP001 uses the **NODE_APPLICATION_INSTALL** to install the ransomware application onto the starting host.
\n",
+ "In this stage, TAP001 uses the **node-application-install** to install the ransomware application onto the starting host.
\n",
"\n",
"These actions represent the malware dropper successfully installing ransomware on the host machine. Similarly to the malware dropper, the ransomware currently implemented is intended to be a generic and OS agnostic ransomware which is not intended to represent any specific real world implementation. \n",
"\n",
- "Please see the [Ransomware Notebook](./Ransomware-Kill-Chain-E2E.ipynb) for further information about the current implementation of the ransomware application.\n",
- "\n",
"Future versions of PrimAITE intend to expand the capability of the ransomware application to more faithfully represent a real-world example; for example, a Trickbot variation such as Ryuk or Conti."
]
},
@@ -463,7 +463,7 @@
"|-----|------------|----------|---------|\n",
"|4|Propagate|HOST:NICS:NIC:TRAFFIC:PROTOCOL:PORT:*|The malware attempts to spread to other systems or networks, looking for vulnerable services.|\n",
"\n",
- "In this stage, TAP001 uses **NODE_NMAP_PORT_SCAN**, **NODE_NMAP_PING_SCAN** and **NODE_NMAP_NETWORK_SERVICE_RECON** to scan the simulation in order to search for a valid database target.\n",
+ "In this stage, TAP001 uses **node-nmap-port-scan**, **node-nmap-ping-scan** and **node-nmap-network-service-recon** to scan the simulation in order to search for a valid database target.\n",
"\n",
"Unlike previous stages, the behaviour of this stage is dependant on the simulation and thus will perform differently dependant on the location of the target as well as the topology of the network. Specifically, the ```PROPAGATE``` stage uses three network enumeration actions and their action responses to populate its knowledge of the network.
\n",
"These actions represent the now infected `ST_PROJ-A-PRV-PC-1` searching the UC7 network for valid targets ransomware. \n",
@@ -481,7 +481,7 @@
"source": [
"#### **Kill Chain** | PROPAGATE | Scan walkthrough\n",
"\n",
- "The next juypter cells of this notebook will go through each individual CAOS action that the TAP001 leverages to reach the target host as well as the OBS each action impacts.\n",
+ "The next juypter cells of this notebook will go through each individual nmap action that the TAP001 leverages to reach the target host as well as the OBS each action impacts.\n",
"\n",
"This section uses the following ```PROPAGATE``` relevant TAP001 settings:\n",
"```yaml\n",
@@ -788,7 +788,7 @@
"\n",
"\n",
"\n",
- "For further details please refer to the ``Command-and-Control-E2E-Demonstration`` notebook.\n",
+ "For further details please refer to the [Command-and-Control-E2E-Demonstration notebook](./Command-and-Control-E2E-Demonstration.ipynb).\n",
"\n",
"_Note: The referenced notebook above uses the UC2 scenario for demonstration purposes, however all the OBS impacts and C2 suite functionality is equally applicable to UC7._\n",
"\n"
@@ -921,7 +921,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
- "We can also see that the database.db file was successfully exfiltrated."
+ "We can also see that the `database.db` file was successfully exfiltrated."
]
},
{
@@ -948,16 +948,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
- "## **Post Attack Impacts**\n",
- "\n",
- "Please refer to the [Ransomware E2E Notebook](./Ransomware-Kill-Chain-E2E.ipynb) for an in-depth look on the knock-on affects of the ransomware application."
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "### **Attack Configurations** | Threat Actor Profile Settings"
+ "### **Attack Configurations** "
]
},
{
@@ -1294,7 +1285,7 @@
"\n",
"[1] _PrimAITE does not actually enforce agent type (Red/Green/Blue) specific actions_\n",
"\n",
- "_However, some actions such as ``APPLICATION_EXECUTE`` and ``NETWORK_RECON_SCAN`` require an understanding of the simulation that is beyond the blue agent's current observation and thus are not suitable for use by reinforcement algorithms._\n",
+ "_However, some actions such as `node-application-execute` and `node-nmap-network-service-recon` require an understanding of the simulation that is beyond the blue agent's current observation and thus are not suitable for use by reinforcement algorithms._\n",
"\n",
"_These actions are usually only leveraged by Green or Red agents; hence why they are commonly referenced as such._\n",
"\n",
@@ -1307,7 +1298,7 @@
"source": [
"#### **Attack Configurations** | Mobile Malware Kill Chain | Command and Control Stage\n",
"\n",
- "TAP001's Command and Control stage leverages the C2 beacon which has it's own set of configuration options. In the case of TAP001 some of these settings are already pre-defined based on other settings such as ``target_node``. The table below details the currently available options.\n",
+ "TAP001's Command and Control stage leverages the `c2-beacon` which has it's own set of configuration options. In the case of TAP001 some of these settings are already pre-defined based on other settings such as ``target_node``. The table below details the currently available options.\n",
"\n",
" Command and Control Configuration Settings
\n",
"\n",
@@ -1318,9 +1309,10 @@
"|keep_alive_frequency | How often should the C2 Beacon confirm its connection in timesteps. Defaults to 5 |Int | _Optional_ |\n",
"|masquerade_port | What port should the C2 traffic use? Defaults to TCP. |Str | _Optional_ |\n",
"|masquerade_protocol | What protocol should the C2 traffic masquerade as? Defaults to HTTP. |Str | _Optional_ |\n",
+ "\n",
" \n",
"\n",
- "For further information around the configuration of the C2 beacon please refer to the ``Command-&-Control-E2E-Demonstration`` last section on configurability."
+ "For further information around the configuration of the `c2-beacon` please refer to the [Command-&-Control-E2E-Demonstration notebook](./Command-and-Control-E2E-Demonstration.ipynb)'s last section on configurability."
]
},
{
@@ -1347,7 +1339,7 @@
" cfg['agents'][32]['agent_settings']['kill_chain']['COMMAND_AND_CONTROL'][\"masquerade_protocol\"] = \"TCP\"\n",
"env = PrimaiteGymEnv(env_config = cfg)\n",
"env.reset()\n",
- "# TAP001 runs for exactly 110 timesteps using default TAP settings.\n",
+ "# TAP001 requires around 110 timesteps using default TAP settings.\n",
"for _ in range(110):\n",
" env.step(0)"
]
@@ -1356,7 +1348,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
- "The code cells below use .show() methods to show that the configuration options have successfully altered the C2's suite configuration. For example the C2 beacon's remote connection is now ``REM-PUB-PC-1``'s ip address which is ``192.168.20.2``."
+ "The code cells below use .show() methods to show that the configuration options have successfully altered the C2's suite configuration. For example the `c2-beacon`'s remote connection is now ``REM-PUB-PC-1``'s ip address which is ``192.168.20.2``."
]
},
{
@@ -1448,7 +1440,7 @@
"|exfiltrate | Should TAP001 exfiltrate the target database.db file? |Boolean | _Required_ |\n",
"\n",
"\n",
- "Similar to ``corrupt``, this option is a boolean value which indicates if TAP001 should attempt to exfiltrate the database.db file.\n",
+ "Similar to ``corrupt``, this option is a boolean value which indicates if TAP001 should attempt to exfiltrate the `database.db` file.\n",
"\n",
"By default this is enabled but if users wish to disable the exfiltration for training purposes then this value can be set to ``False`` which will prevent the TAP001 agent from attempting to exfiltrate the database.db file."
]
@@ -1457,14 +1449,14 @@
"cell_type": "markdown",
"metadata": {},
"source": [
- "_If both ``exfiltrate`` and ``corrupt`` options are enabled then the TAP001 agent will exfiltrate the database.db and then launch the ``RansomwareScript`` against the target._"
+ "_If both ``exfiltrate`` and ``corrupt`` options are enabled then the TAP001 agent will exfiltrate the database.db and then launch the ``ransomware-script`` against the target._"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
- "#### Yaml Example:\n",
+ "_yaml config example_\n",
"\n",
"```yaml\n",
" kill_chain:\n",
@@ -1822,7 +1814,7 @@
],
"metadata": {
"kernelspec": {
- "display_name": ".venv",
+ "display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
diff --git a/src/primaite/notebooks/UC7-TAP003-Kill-Chain-E2E.ipynb b/src/primaite/notebooks/UC7-TAP003-Kill-Chain-E2E.ipynb
index 306e070a..f2baf310 100644
--- a/src/primaite/notebooks/UC7-TAP003-Kill-Chain-E2E.ipynb
+++ b/src/primaite/notebooks/UC7-TAP003-Kill-Chain-E2E.ipynb
@@ -4,26 +4,25 @@
"cell_type": "markdown",
"metadata": {},
"source": [
- "# Backdoor & Vulnerability Creation Kill Chain\n",
+ "# TAP003 - Malicious Insider Kill Chain\n",
"\n",
"© Crown-owned copyright 2025, Defence Science and Technology Laboratory UK\n",
"\n",
"**Threat Actor Profile (TAP):** 003
\n",
- "**Kill Chain**: Backdoor & Vulnerability Creation"
+ "**Kill Chain**: Insider"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
- "This notebook demonstrates a new UC7 Kill Chain which aims to represent a different style of attack in comparison to the mobile malware kill chain (TAP001).\n",
- "Kill chains aim to represent a potential real world cyber attack. In this scenario an malicious `SOME_TECH` admin (TAP003) leverages his legitimate credentials and permissions to create purposeful backdoors, establish footholds and use other legitimate features in malicious ways.\n",
+ "This notebook demonstrates the new TAP003 red agent which simulates a malicious insider which performs an internal cyber attack. In this scenario an malicious `some_tech` admin leverages their legitimate credentials and permissions to create purposeful backdoors, establish footholds and use other legitimate features in malicious ways.\n",
"
\n",
"\n",
- "In this version - this scenario is limited in scope. TAP003 opts to alter user accounts and implement malicious ACL rule by using the ``terminal`` service to SSH into target routers. These ACLs block green traffic which trigger a negative reward.\n",
+ "In this version of PrimAITE, TAP003 opts to alter user accounts and implement malicious ACL rule by using the ``terminal`` service to SSH into target routers. These ACLs block green traffic which trigger a negative reward.\n",
"
\n",
"\n",
- "This kill chain intends to introduce a new UC7 attack which is both realistic but also dissimilar to other kill chains."
+ "This TAP was designed to be both realistic but also dissimilar to the more traditional red agents such as [TAP001](./UC7-TAP001-Kill-Chain-E2E.ipynb) or use case 2's [data_manipulation_bot](./Data-Manipulation-Customising-Red-Agent.ipynb)."
]
},
{
@@ -101,10 +100,10 @@
" node = item.parameters.get('node_name')\n",
"\n",
" else:\n",
- " if (node_id := item.parameters.get('node_id')) is not None:\n",
- " node = env.game.agents[agent_name].action_manager.node_names[node_id]\n",
- " if (application_id := item.parameters.get('application_id')) is not None:\n",
- " application = env.game.agents[agent_name].action_manager.application_names[node_id][application_id]\n",
+ " if (item.parameters.get('node_name')) is not None:\n",
+ " node = item.parameters.get('node_name')\n",
+ " if (item.parameters.get('application_name')) is not None:\n",
+ " application = item.parameters.get('application_name')\n",
" if (application_name := item.parameters.get('application_name')) is not None:\n",
" application = application_name\n",
"\n",
@@ -157,39 +156,38 @@
"source": [
"### **Notebook Intro** | **Backdoor & Vulnerability Creation Kill Chain Intro** \n",
"\n",
- "TAP003's kill chain is comprised of a variety of blue actions which are leveraged in unusual ways. This includes introducing malicious ACL's which block green traffic and installing and execute green applications in order to simulate unusual green pattern of life. The rest of this notebook will go through each step in more detail whilst demonstrating the impacts that each step has on both observation and simulation behaviour.\n",
+ "TAP003's kill chain is comprised of a variety of blue actions which are leveraged in unusual ways. This includes introducing malicious ACL's which block green traffic. The rest of this notebook will go through each step in more detail whilst demonstrating the impacts that each step has on both observation and simulation behaviour.\n",
"\n",
- "_Reconnaissance - DONOTHING CAOS Action_\n",
+ "_Reconnaissance_\n",
"\n",
- "|Index | Action Stage| OBS Impact | Narrative |\n",
- "|-----|-------------|-------------|-----------|\n",
- "|1|Reconnaissance|*No Direct Impact*|TAP003 is passively investigating sensitive systems, data and access control mechanisms.|\n",
+ "|Index | Action Stage| OBS Impact | Narrative |action(s)|\n",
+ "|-----|-------------|-------------|-----------|---|\n",
+ "|1|Reconnaissance|*No Direct Impact*|TAP003 is passively investigating sensitive systems, data and access control mechanisms.|`do-nothing`|\n",
"\n",
- "_Planning - DONOTHING CAOS Action_\n",
+ "_Planning_\n",
"\n",
- "|Index| Action Stage| OBS Impact | Narrative |\n",
- "|-----|-------------|------------|-----------|\n",
- "|2|Planning| **No current impact**|TAP003 is devising a plan to exploit their elevated privileges.|\n",
+ "|Index| Action Stage| OBS Impact | Narrative |action(s)|\n",
+ "|-----|-------------|------------|-----------|---|\n",
+ "|2|Planning| **No current impact**|TAP003 is devising a plan to exploit their elevated privileges.|`do-nothing`|\n",
"\n",
- " _Access - DONOTHING CAOS Action__\n",
+ " _Access_\n",
"\n",
- "|Index| Action Stage| OBS Impact | Narrative |\n",
- "|-----|-------------|------------|-----------|\n",
- "|3|Access|**No current impact** |TAP003 uses their legitimate credentials to access the access control settings.|\n",
+ "|Index| Action Stage| OBS Impact | Narrative |action(s)|\n",
+ "|-----|-------------|------------|-----------|---|\n",
+ "|3|Access|**No current impact** |TAP003 uses their legitimate credentials to access the access control settings.|`do-nothing`|\n",
"\n",
- " _Manipulation - HOST:SESSIONS_SEND_REMOTE_COMMAND -> HOST:ACCOUNTS:CHANGE:PASSWORD CAOS ACTION_\n",
+ " _Manipulation_\n",
" \n",
- "|Index| Action Stage| OBS Impact | Narrative |\n",
- "|-----|-------------|------------|-----------|\n",
- "|4|Manipulation| **Target Host(s)** HOST::SESSIONS:REMOTE |TAP003 exploits their insider knowledge/privilege to implement changes for sabotage.|\n",
+ "|Index| Action Stage| OBS Impact | Narrative |action(s)|\n",
+ "|-----|-------------|------------|-----------|---|\n",
+ "|4|Manipulation| **Target Host(s)** HOST::SESSIONS:REMOTE |TAP003 exploits their insider knowledge/privilege to implement changes for sabotage.| - `node-session-remote-login` & `sessions-send-remote-command` (`node-account-change-password`)|\n",
"\n",
- " _Exploit - FIREWALL:ACL:add_rule CAOS ACTION_\n",
+ "_Exploit_\n",
"\n",
- "|Index| Action Stage| OBS Impact | Narrative |\n",
- "|-----|-------------|------------|-----------|\n",
- "|5|Exploit| **Target Host(s)** FIREWALL:ACL:INTERNAL/EXTERNAL:*|TAP003 exploits their insider knowledge/privilege to implement changes for sabotage.|\n",
- "\n",
- "_Only the initial five steps are represented in the this version of this kill-chain._
\n"
+ "|Index| Action Stage| OBS Impact | Narrative |action(s)|\n",
+ "|-----|-------------|------------|-----------|---|\n",
+ "|5|Exploit| **Target Host(s)** ROUTER:ACL:INTERNAL/EXTERNAL:*|TAP003 exploits their insider knowledge/privilege to implement changes for sabotage.| `node-session-remote-login` & `node-session-send-remote-command` (`router-acl-addrule`) |\n",
+ "\n"
]
},
{
@@ -198,9 +196,7 @@
"source": [
"## **Notebook Setup** | **Network Configuration:**\n",
"\n",
- "This notebook uses the same network setup as UC7. \n",
- "\n",
- "Please refer to the main [UC7-E2E-Demo notebook for further reference](./UC7-E2E-Demo.ipynb)."
+ "Any readers unfamiliar with UC7 can refer to the main [UC7-E2E-Demo notebook for further reference](./UC7-E2E-Demo.ipynb)."
]
},
{
@@ -255,7 +251,7 @@
"source": [
"env.reset() # resetting the environment\n",
"# The TAP003 Agent\n",
- "tap003 = env.game.agents['attacker']\n",
+ "tap003: TAP003 = env.game.agents['attacker']\n",
"tap003.logger.logger.setLevel(\"INFO\")"
]
},
@@ -483,7 +479,7 @@
"|-----|-------------|------------|-----------|\n",
"|1|RECONNAISSANCE|*No Direct Impact*|TAP003 is identifying Sensitive systems, data and access control mechanisms in legitimate ways.|\n",
"\n",
- "Currently, this stage in the kill chain is implemented via the 'DONOTHING' CAOS action."
+ "Currently, this stage in the kill chain is implemented via the 'do-nothing' CAOS action."
]
},
{
@@ -566,7 +562,7 @@
"\n",
"|Index| Action Stage| OBS Impact | Narrative |\n",
"|-----|-------------|------------|-----------|\n",
- "|3|Access|_DONOTHING CAOS Action_|TAP003 uses their legitimate credentials to access the access control settings.|\n",
+ "|3|Access|_do-nothing CAOS Action_|TAP003 uses their legitimate credentials to access the access control settings.|\n",
"\n",
"Currently, at this point of the kill chain stage the TAP003 does not perform any simulations actions. Future versions of TAP003 aim to leverage more of the simulation to create and remove accounts at this stage."
]
@@ -1551,7 +1547,7 @@
"|probability|Action Probability - The chance of successfully carrying out this stage in the kill_chain.|str|_Required_|\n",
"|malicious_acls|The configurable ACL that the TAP003 agent adds to the target node.|dict|_Required_|\n",
"\n",
- "The malicious ACL is configured identically to the other ACLs. except from the target router/firewall. \n",
+ "The malicious ACL is configured identically to the other ACLs except from the target router/firewall. \n",
"This option is set to the TAP003's configured target host automatically.\n",
"\n",
"TAP003 intends to leverage these ACL's for malicious purposes. The default configuration is to deny all traffic from and towards the 0.0.0.255 subnet. \n",
@@ -1644,7 +1640,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
- "Unlike the blue agent, TAP003 does not need to use it's action space options for indexing different options, meaning that ACL's are a lot easier to configure.\n",
+ "Unlike the blue agent, TAP003 does not need to use its action space options for indexing different options, meaning that ACLs are a lot easier to configure.\n",
"\n",
"The sandbox below can be used to try out different configuration options and their impact on the simulation."
]
@@ -1692,7 +1688,7 @@
],
"metadata": {
"kernelspec": {
- "display_name": ".venv",
+ "display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
diff --git a/src/primaite/notebooks/UC7-Training.ipynb b/src/primaite/notebooks/UC7-Training.ipynb
index ddaa6844..53e83d2c 100644
--- a/src/primaite/notebooks/UC7-Training.ipynb
+++ b/src/primaite/notebooks/UC7-Training.ipynb
@@ -8,11 +8,11 @@
}
},
"source": [
- "# Training an SB3 Agent\n",
+ "# Training an Agent on UC7\n",
"\n",
"© Crown-owned copyright 2025, Defence Science and Technology Laboratory UK\n",
"\n",
- "This notebook will demonstrate how to use primaite to create and train a PPO agent, using a pre-defined configuration file."
+ "This notebook is identical in content to the [training an SB3 agent](./Training-an-SB3-Agent.ipynb) except this notebook trains an agent on the [use case 7 scenario](./UC7-E2E-Demo.ipynb) rather than [use case 2](./Data-Manipulation-E2E-Demonstration.ipynb). By default, the `uc7_config.yaml` blue agent (`defender`) is setup to defend against Threat Actor Profile (TAP) 001 which can be explored in more detail [here](./UC7-TAP001-Kill-Chain-E2E.ipynb).\n"
]
},
{
@@ -42,10 +42,6 @@
"from primaite import PRIMAITE_PATHS\n",
"from prettytable import PrettyTable\n",
"from deepdiff.diff import DeepDiff\n",
- "from primaite.simulator.network.hardware.nodes.host.server import Server\n",
- "from primaite.simulator.network.hardware.nodes.network.router import Router\n",
- "from primaite.simulator.network.hardware.nodes.host.computer import Computer\n",
- "\n",
"scenario_path = PRIMAITE_PATHS.user_config_path / \"example_config/uc7_config.yaml\""
]
},
@@ -125,21 +121,9 @@
],
"metadata": {
"kernelspec": {
- "display_name": ".venv",
+ "display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
- },
- "language_info": {
- "codemirror_mode": {
- "name": "ipython",
- "version": 3
- },
- "file_extension": ".py",
- "mimetype": "text/x-python",
- "name": "python",
- "nbconvert_exporter": "python",
- "pygments_lexer": "ipython3",
- "version": "3.10.12"
}
},
"nbformat": 4,
diff --git a/src/primaite/notebooks/UC7-attack-variants.ipynb b/src/primaite/notebooks/UC7-attack-variants.ipynb
index 14742796..f1edba19 100644
--- a/src/primaite/notebooks/UC7-attack-variants.ipynb
+++ b/src/primaite/notebooks/UC7-attack-variants.ipynb
@@ -17,9 +17,11 @@
}
},
"source": [
- "This notebook demonstrates the PrimAITE environment with the UC7 network laydown and multiple attack personas. The first attack persona is TAP001 which performs a ransomware attack against the database. The other one is TAP003 which is able to maliciously add ACL rules that block green pattern of life.\n",
+ "This notebook demonstrates the PrimAITE environment with the UC7 network laydown and multiple attack personas. The first threat actor persona is **TAP001** which performs a ransomware attack against the database. The other one is **TAP003** which is able to maliciously add ACL rules that block green pattern of life.\n",
"\n",
- "The environment switches between these two attacks on a pre-defined schedule which is defined in the schedule.yaml file of the scenario folder."
+ "Any users unfamiliar with these red agents should take a look into the [TAP001 notebook](./UC7-TAP001-Kill-Chain-E2E.ipynb) and the [TAP003 notebook](./UC7-TAP003-Kill-Chain-E2E.ipynb) for further details.\n",
+ "\n",
+ "The environment switches between these two attacks on a pre-defined schedule which is defined in the `schedule.yaml` file of the scenario folder."
]
},
{
@@ -44,32 +46,15 @@
"metadata": {},
"outputs": [],
"source": [
- "import yaml\n",
"from primaite.session.environment import PrimaiteGymEnv\n",
"from primaite import PRIMAITE_PATHS\n",
"from prettytable import PrettyTable\n",
"from deepdiff.diff import DeepDiff\n",
"from primaite.session.environment import PrimaiteGymEnv\n",
- "from primaite.simulator.network.hardware.nodes.host.computer import Computer\n",
"from primaite.simulator.network.hardware.nodes.host.server import Server\n",
"from primaite.simulator.network.hardware.nodes.network.router import Router\n",
- "from primaite.simulator.system.services.dns.dns_server import DNSServer\n",
- "from primaite.simulator.system.software import SoftwareHealthState\n",
- "from primaite.simulator.file_system.file_system_item_abc import FileSystemItemHealthStatus\n",
- "from primaite.simulator.network.hardware.nodes.network.switch import Switch\n",
- "from primaite.simulator.system.applications.web_browser import WebBrowser\n",
- "from primaite.simulator.network.container import Network\n",
- "from primaite.simulator.system.services.service import ServiceOperatingState\n",
- "from primaite.simulator.network.hardware.node_operating_state import NodeOperatingState\n",
- "from primaite.simulator.system.services.database.database_service import DatabaseService\n",
- "from primaite.simulator.system.applications.database_client import DatabaseClient\n",
- "from primaite.simulator.network.hardware.nodes.network.firewall import Firewall\n",
- "from primaite.game.game import PrimaiteGame\n",
- "from primaite.simulator.sim_container import Simulation\n",
- "from primaite.config.load import load, _EXAMPLE_CFG\n",
"from primaite.simulator.network.hardware.nodes.host.server import Server\n",
"from primaite.simulator.network.hardware.nodes.network.router import Router\n",
- "from primaite.simulator.network.hardware.nodes.host.computer import Computer\n",
"\n",
"scenario_path = PRIMAITE_PATHS.user_config_path / \"example_config/uc7_multiple_attack_variants\""
]
@@ -220,7 +205,7 @@
"source": [
"The blue agent should be able to prevent the ransomware attack by blocking the red agent's access to the database. Let's run the environment until the observation space shows symptoms of the attack starting.\n",
"\n",
- "Because we are in episode index 1, the red agent will use `ST-PROJ-A-PRV-PC-1` to start the attack. On step 25, the red agent installs `RansomwareScript`."
+ "Because we are in episode index 1, the red agent will use `ST_PROJ-A-PRV-PC-1` to start the attack. On step 25, the red agent installs `ransomware-script`."
]
},
{
@@ -246,9 +231,33 @@
"cell_type": "markdown",
"metadata": {},
"source": [
- "We can see that on HOST0, application index 1 has gone from `operating_status` 0 to 3, meaning there wasn't an application before, but now there is an application in the `INSTALLING` state. The blue agent should be able to detect this and block the red agent's access to the database. Action 43 will block `ST-PROJ-A-PRV-PC-1` from sending POSTGRES traffic to the DB server.\n",
+ "We can see that on HOST0, application index 1 has gone from `operating_status` 0 to 3, meaning there wasn't an application before, but now there is an application in the `INSTALLING` state. The blue agent should be able to detect this and block the red agent's access to the database. Action 43 will block `ST_PROJ-A-PRV-PC-1` from sending POSTGRES traffic to the DB server.\n",
"\n",
- "If this were a different episode, it could have been `ST-PROJ-B-PRV-PC-2` or `ST-PROJ-C-PRV-PC-3` that are affected, and a different defensive action would be required."
+ "If this were a different episode, it could have been `ST_PROJ-B-PRV-PC-2` or `ST_PROJ-C-PRV-PC-3` that are affected, and a different defensive action would be required."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "\n",
+ "```yaml\n",
+ "\n",
+ "# ST_INTRA-PRV-RT-CR | router-acl-add-rule | P2: ST_PROJ-A-PRV-PC-1 !==> ST_DATA-PRV-SRV-DB (TCP:POSTGRES_SERVER)\n",
+ "43:\n",
+ " action: router-acl-add-rule\n",
+ " options:\n",
+ " target_router: ST_INTRA-PRV-RT-CR\n",
+ " position: 1\n",
+ " permission: DENY\n",
+ " src_ip: 192.168.230.2 # (ST_PROJ-A-PRV-PC-1)\n",
+ " src_wildcard: 0.0.255.255\n",
+ " src_port: POSTGRES_SERVER\n",
+ " dst_ip: 192.168.220.3 # (ST_DATA-PRV-SRV-DB)\n",
+ " dst_wildcard: 0.0.255.255\n",
+ " dst_port: POSTGRES_SERVER\n",
+ " protocol_name: TCP\n",
+ "```"
]
},
{
@@ -257,9 +266,73 @@
"metadata": {},
"outputs": [],
"source": [
- "env.step(43)\n",
- "env.step(45)\n",
- "env.step(47)"
+ "env.step(43);"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "```yaml\n",
+ "\n",
+ "# ST_INTRA-PRV-RT-CR | router-acl-add-rule | P3: ST_PROJ-B-PRV-PC-2 !==> ST_DATA-PRV-SRV-DB (TCP:POSTGRES_SERVER)\n",
+ "45:\n",
+ " action: router-acl-add-rule\n",
+ " options:\n",
+ " target_router: ST_INTRA-PRV-RT-CR\n",
+ " position: 2\n",
+ " permission: DENY\n",
+ " src_ip: 192.168.240.3 # (ST_PROJ-B-PRV-PC-2)\n",
+ " src_wildcard: 0.0.255.255\n",
+ " src_port: POSTGRES_SERVER\n",
+ " dst_ip: 192.168.220.3 # (ST_DATA-PRV-SRV-DB)\n",
+ " dst_wildcard: 0.0.255.255\n",
+ " dst_port: POSTGRES_SERVER\n",
+ " protocol_name: TCP\n",
+ "\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "env.step(45);"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "```yaml\n",
+ "\n",
+ "# ST_INTRA-PRV-RT-CR | router-acl-add-rule | P4: ST_PROJ-C-PRV-PC-3 !==> ST_DATA-PRV-SRV-DB (TCP:POSTGRES_SERVER)\n",
+ "47:\n",
+ " action: router-acl-add-rule\n",
+ " options:\n",
+ " target_router: ST_INTRA-PRV-RT-CR\n",
+ " position: 3\n",
+ " permission: DENY\n",
+ " src_ip: 192.168.250.4 # (ST_PROJ-C-PRV-PC-3)\n",
+ " src_wildcard: 0.0.255.255\n",
+ " src_port: POSTGRES_SERVER\n",
+ " dst_ip: 192.168.220.3 # (ST_DATA-PRV-SRV-DB)\n",
+ " dst_wildcard: 0.0.255.255\n",
+ " dst_port: POSTGRES_SERVER\n",
+ " protocol_name: TCP\n",
+ " \n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "env.step(47);"
]
},
{
@@ -319,7 +392,7 @@
"|Target Router | Impact |\n",
"|----------------------|--------|\n",
"|`ST_INTRA-PRV-RT-DR-1`| Blocks all `POSTGRES_SERVER` that arrives at the `ST_INTRA-PRV-RT-DR-1` router. This rule will prevent all ST_PROJ_* hosts from accessing the database (`ST_DATA-PRV-SRV-DB`).|\n",
- "|`ST_INTRA-PRV-RT-CR`| Blocks all `HTTP` traffic that arrives at the`ST_INTRA-PRV-RT-CR` router. This rule will prevent all SOME_TECH hosts from accessing the webserver (`ST-DMZ-PUB-SRV-WEB`)|\n",
+ "|`ST_INTRA-PRV-RT-CR`| Blocks all `HTTP` traffic that arrives at the`ST_INTRA-PRV-RT-CR` router. This rule will prevent all SOME_TECH hosts from accessing the webserver (`ST_DMZ-PUB-SRV-WEB`)|\n",
"|`REM-PUB-RT-DR`| Blocks all `DNS` traffic that arrives at the `REM-PUB-RT-DR` router. This rule prevents any remote site works from accessing the DNS Server (`ISP-PUB-SRV-DNS`).|"
]
},
@@ -418,26 +491,7 @@
"source": [
"## Preventing TAP003 attack\n",
"\n",
- "The blue agent can prevent the red agent from adding ACL rules. TAP003 relies on connecting to the router via SSH, and sending remote ACL_ADDRULE requests. The blue agent can prevent this by pre-emptively changing the admin password on the affected routers or by blocking SSH traffic between the red agent's starting node and the target routers."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "env.reset()\n",
- "obs, reward, term, trunc, info = env.step(0)\n",
- "old = obs\n",
- "for i in range(128): \n",
- " obs, reward, term, trunc, info = env.step(0)\n",
- " new = obs\n",
- "\n",
- "diff = DeepDiff(old,new)\n",
- "print(f\"Step {env.game.step_counter}\") # it's the next step now because the step counter is incremented after the step\n",
- "for d,v in diff.get('values_changed', {}).items():\n",
- " print(f\"{d}: {v['old_value']} -> {v['new_value']}\")"
+ "TAP003 relies on connecting to the routers via SSH, and sending `add_rule` terminal commands. The blue agent can prevent this by pre-emptively changing the admin password on the affected routers or by blocking SSH traffic between the red agent's starting node and the target routers."
]
},
{
@@ -453,8 +507,8 @@
"metadata": {},
"outputs": [],
"source": [
+ "env.reset()\n",
"finish_episode_and_print_reward()\n",
- "\n",
"for ag in env.game.agents.values():\n",
" print(ag.config.ref, ag.reward_function.total_reward)"
]
@@ -473,7 +527,39 @@
"outputs": [],
"source": [
"env.reset()\n",
- "env.step(51) # SSH Blocking ACL on ST-INRA-PRV-RT-R1\n",
+ "env.step(51) # SSH Blocking ACL on ST_INRA-PRV-RT-R1"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "```yaml\n",
+ "\n",
+ "# ST_INTRA-PRV-RT-DR-1 | router-acl-add-rule | P1: ST_INTRA-PRV-RT-DR-1 !==> ANY (TCP:SSH)\n",
+ "51:\n",
+ " action: router-acl-add-rule\n",
+ " options:\n",
+ " target_router: ST_INTRA-PRV-RT-DR-1\n",
+ " position: 1\n",
+ " permission: DENY\n",
+ " src_ip: 192.168.230.2 # (ST_PROJ-A-PRV-PC-1)\n",
+ " src_wildcard: 0.0.255.255\n",
+ " src_port: SSH\n",
+ " dst_ip: ALL\n",
+ " dst_wildcard: 0.0.255.255\n",
+ " dst_port: SSH\n",
+ " protocol_name: TCP\n",
+ "\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
"finish_episode_and_print_reward()\n",
"\n",
"for ag in env.game.agents.values():\n",
@@ -484,7 +570,97 @@
"cell_type": "markdown",
"metadata": {},
"source": [
- "Additionally, another option the blue agent can take is to change the passwords of the different target routers that TAP003 will attack through the `NODE_ACCOUNTS_CHANGE_PASSWORD` action."
+ "Additionally, another option the blue agent can take is to change the passwords of the different target routers that TAP003 will attack through the `node-account-change-password` action."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "env.reset()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "```yaml\n",
+ "\n",
+ "# ST_DATA-PRV-SRV-DB | node-account-change-password | Changes the password of a user account\n",
+ "50:\n",
+ " action: node-account-change-password\n",
+ " options:\n",
+ " node_name: ST_DATA-PRV-SRV-DB\n",
+ " username: admin # default account\n",
+ " current_password: admin # default password\n",
+ " new_password: thr33_alert_wolv3z # A more 'secure' password\n",
+ " \n",
+ "```\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "env.step(50); "
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "```yaml\n",
+ "\n",
+ "# ST_INTRA-PRV-RT-DR-1 | node-account-change-password\n",
+ "52:\n",
+ " action: node-account-change-password\n",
+ " options:\n",
+ " node_name: ST_INTRA-PRV-RT-DR-1\n",
+ " username: admin\n",
+ " current_password: admin\n",
+ " new_password: secure_password\n",
+ "\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "env.step(52);"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "```yaml\n",
+ "\n",
+ "# REM-PUB-RT-DR | node-account-change-password\n",
+ "54:\n",
+ " action: node-account-change-password\n",
+ " options:\n",
+ " node_name: REM-PUB-RT-DR\n",
+ " username: admin\n",
+ " current_password: admin\n",
+ " new_password: secure_password\n",
+ "\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "env.step(54); "
]
},
{
@@ -493,10 +669,6 @@
"metadata": {},
"outputs": [],
"source": [
- "env.reset()\n",
- "env.step(50) # NODE_ACCOUNTS_CHANGE_PASSWORD | ST_INTRA-prv-rt-cr\n",
- "env.step(52) # NODE_ACCOUNTS_CHANGE_PASSWORD | ST_INTRA-prv-rt-dr-1\n",
- "env.step(54) # NODE_ACCOUNTS_CHANGE_PASSWORD | rem-pub-rt-dr\n",
"finish_episode_and_print_reward()\n",
"\n",
"for ag in env.game.agents.values():\n",
@@ -527,15 +699,88 @@
"env.game.simulation.network.get_node_by_hostname(\"REM-PUB-RT-DR\").acl.show()"
]
},
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "```yaml\n",
+ "\n",
+ "```"
+ ]
+ },
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
- "env.step(44) # ROUTER_ACL_REMOVERULE | ST_INTRA-prv-rt-cr\n",
- "env.step(53) # ROUTER_ACL_REMOVERULE | ST_INTRA-prv-rt-dr-1\n",
- "env.step(55) # ROUTER_ACL_REMOVERULE | rem-pub-rt-dr"
+ "env.step(44);"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "```yaml\n",
+ "\n",
+ "# ST_INTRA-PRV-RT-CR | REMOVE_ACL_ADDRULE | Removes a given ACL at position 1\n",
+ "44:\n",
+ " action: router-acl-remove-rule\n",
+ " options:\n",
+ " target_router: ST_INTRA-PRV-RT-CR\n",
+ " position: 1\n",
+ "\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "env.step(53);"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "```yaml\n",
+ "\n",
+ "# ST_INTRA-PRV-RT-DR-1 | router-acl-remove-rule | Removes the given ACL at position 1\n",
+ "53:\n",
+ " action: router-acl-remove-rule\n",
+ " options:\n",
+ " target_router: ST_INTRA-PRV-RT-DR-1\n",
+ " position: 1\n",
+ "\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "env.step(55);"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "```yaml\n",
+ "\n",
+ "# REM-PUB-RT-DR | router-acl-remove-rule | Removes the given ACL at position 1\n",
+ "55:\n",
+ " action: router-acl-remove-rule\n",
+ " options:\n",
+ " target_router: REM-PUB-RT-DR\n",
+ " position: 1\n",
+ "\n",
+ "```"
]
},
{
@@ -546,7 +791,7 @@
"source": [
"env.game.simulation.network.get_node_by_hostname(\"ST_INTRA-PRV-RT-CR\").acl.show()\n",
"env.game.simulation.network.get_node_by_hostname(\"ST_INTRA-PRV-RT-DR-1\").acl.show()\n",
- "env.game.simulation.network.get_node_by_hostname(\"REM-PUB-RT-DR\").acl.show()\n"
+ "env.game.simulation.network.get_node_by_hostname(\"REM-PUB-RT-DR\").acl.show()"
]
},
{
@@ -564,7 +809,7 @@
],
"metadata": {
"kernelspec": {
- "display_name": ".venv",
+ "display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
diff --git a/src/primaite/notebooks/UC7-network_connectivity.ipynb b/src/primaite/notebooks/UC7-network_connectivity.ipynb
index 8aef5465..d2667fcc 100644
--- a/src/primaite/notebooks/UC7-network_connectivity.ipynb
+++ b/src/primaite/notebooks/UC7-network_connectivity.ipynb
@@ -4,11 +4,20 @@
"cell_type": "markdown",
"metadata": {},
"source": [
- "# UC7 Demonstration\n",
+ "# UC7 Network Connectivity\n",
"\n",
"© Crown-owned copyright 2025, Defence Science and Technology Laboratory UK\n"
]
},
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "This notebook is meant as supplementary material to the more expansive [UC7 E2E notebook](./UC7-E2E-Demo.ipynb).\n",
+ "\n",
+ "Focusing on the simulation components of UC7, this notebook primarily intends to demonstrate the default connectivity and prove that the simulation is operating as expected. Additionally, this notebook assumes that the reader is familiar with both UC7 and PrimAITE's networking simulation as no supplementary text cells to explain specific output are provided."
+ ]
+ },
{
"cell_type": "markdown",
"metadata": {},
@@ -46,6 +55,7 @@
"from primaite.simulator.network.container import Network\n",
"from primaite.simulator.network.hardware.nodes.network.firewall import Firewall\n",
"from primaite.game.game import PrimaiteGame\n",
+ "from primaite.simulator.system.services.dns.dns_client import DNSClient\n",
"from primaite.simulator.sim_container import Simulation\n",
"import yaml\n",
"from pprint import pprint\n",
@@ -210,16 +220,6 @@
"home_pub_pc_1.ping(target_ip_address=isp_pub_srv_dns.network_interface[1].ip_address)"
]
},
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "isp_pub_rt_br.show_nic()\n",
- "isp_pub_rt_br.ping(\"192.168.1.2\")\n"
- ]
- },
{
"cell_type": "markdown",
"metadata": {},
@@ -371,15 +371,6 @@
"st_pub_fw.ping(isp_pub_srv_dns.network_interface[1].ip_address)"
]
},
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "# Some Tech DMZ public web serv"
- ]
- },
{
"cell_type": "code",
"execution_count": null,
@@ -391,13 +382,6 @@
"st_pub_fw.ping(isp_pub_rt_br.network_interface[4].ip_address)"
]
},
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": []
- },
{
"cell_type": "markdown",
"metadata": {},
@@ -521,7 +505,7 @@
"metadata": {},
"outputs": [],
"source": [
- "# Some tech intranet router DR 1 --> Public DNS \n",
+ "# Some tech intranet router DR 1 --> Public DNS\n",
"\n",
"st_intra_prv_rt_dr_1.ping(isp_pub_srv_dns.network_interface[1].ip_address)"
]
@@ -583,7 +567,7 @@
"metadata": {},
"outputs": [],
"source": [
- "# ST Home office PC 3 --> ST Router DR 2 \n",
+ "# ST Home office PC 3 --> ST Router DR 2\n",
"\n",
"st_head_office_private_pc_1.ping(st_intra_prv_rt_dr_2.network_interface[1].ip_address)"
]
@@ -623,7 +607,7 @@
"metadata": {},
"outputs": [],
"source": [
- "# ST Human Resources PC 1 --> ST Human Resources PC 2 \n",
+ "# ST Human Resources PC 1 --> ST Human Resources PC 2\n",
"\n",
"st_human_resources_private_pc_1.ping(st_human_resources_private_pc_2.network_interface[1].ip_address)"
]
@@ -645,7 +629,7 @@
"metadata": {},
"outputs": [],
"source": [
- "# ST Human Resources PC 1 --> ST Intranet Router DR 2 \n",
+ "# ST Human Resources PC 1 --> ST Intranet Router DR 2\n",
"\n",
"st_human_resources_private_pc_1.ping(st_intra_prv_rt_dr_2.network_interface[1].ip_address)"
]
@@ -656,7 +640,7 @@
"metadata": {},
"outputs": [],
"source": [
- "# ST Human Resources PC 1 --> Public DNS \n",
+ "# ST Human Resources PC 1 --> Public DNS\n",
"\n",
"st_human_resources_private_pc_1.ping(isp_pub_srv_dns.network_interface[1].ip_address)"
]
@@ -870,7 +854,7 @@
"metadata": {},
"outputs": [],
"source": [
- "# ST Private Project c PC 3 --> Public DNS\n",
+ "# ST Private Project C PC 3 --> Public DNS\n",
"\n",
"st_project_c_private_pc_3.ping(isp_pub_srv_dns.network_interface[1].ip_address)"
]
@@ -881,6 +865,8 @@
"metadata": {},
"outputs": [],
"source": [
+ "# ST Private Project C PC 1 --> ST Private Project B PC 1\n",
+ "\n",
"st_project_c_private_pc_1.ping(st_project_b_private_pc_1.network_interface[1].ip_address)"
]
},
@@ -890,6 +876,8 @@
"metadata": {},
"outputs": [],
"source": [
+ "# ST Private Project B PC 1 --> ST Private Project A PC 1\n",
+ "\n",
"st_project_b_private_pc_1.ping(st_project_a_private_pc_1.network_interface[1].ip_address)"
]
},
@@ -899,6 +887,8 @@
"metadata": {},
"outputs": [],
"source": [
+ "# ST Private Project A PC 1 --> ST Head Office Private PC 1\n",
+ "\n",
"st_project_a_private_pc_1.ping(st_head_office_private_pc_1.network_interface[1].ip_address)"
]
},
@@ -908,6 +898,8 @@
"metadata": {},
"outputs": [],
"source": [
+ "# ST Private Project A PC 1 --> ST Human Resources Private PC 1\n",
+ "\n",
"st_project_a_private_pc_1.ping(st_human_resources_private_pc_1.network_interface[1].ip_address)"
]
},
@@ -924,7 +916,7 @@
"metadata": {},
"outputs": [],
"source": [
- "# DNS Server\n",
+ "# dns-server\n",
"\n",
"isp_pub_srv_dns.software_manager.show()"
]
@@ -935,7 +927,7 @@
"metadata": {},
"outputs": [],
"source": [
- "# Web Server & Web Browser\n",
+ "# web-browser\n",
"\n",
"st_project_a_web_browser = st_project_a_private_pc_1.software_manager.software[\"web-browser\"]\n",
"st_project_a_web_browser.get_webpage()"
@@ -947,6 +939,8 @@
"metadata": {},
"outputs": [],
"source": [
+ "# web-server\n",
+ "\n",
"st_web_server = st_dmz_pub_srv_web.software_manager.software[\"web-server\"]\n",
"st_web_server.sys_log.show()"
]
@@ -957,8 +951,19 @@
"metadata": {},
"outputs": [],
"source": [
+ "# database-client\n",
+ "\n",
"st_database_client = st_project_a_private_pc_1.software_manager.software[\"database-client\"]\n",
- "st_database_client.connect()\n",
+ "st_database_client.connect()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# database-service\n",
"\n",
"st_database = st_data_private_server_database.software_manager.software[\"database-service\"]\n",
"st_database.sys_log.show()"
@@ -970,6 +975,8 @@
"metadata": {},
"outputs": [],
"source": [
+ "# ftp-server\n",
+ "\n",
"st_ftp_server = st_data_private_server_storage.software_manager.software[\"ftp-server\"]\n",
"\n",
"st_ftp_server.sys_log.show()"
@@ -981,24 +988,21 @@
"metadata": {},
"outputs": [],
"source": [
- "from primaite.simulator.system.services.dns.dns_client import DNSClient\n",
- "home_pub_rt_dr.acl.show()\n",
+ "# dns-client \n",
"\n",
"home_pub_pc_1: Computer = network.get_node_by_hostname(\"HOME-PUB-PC-1\")\n",
"dns_client: DNSClient = home_pub_pc_1.software_manager.software[\"dns-client\"]\n",
"\n",
"dns_client.check_domain_exists(target_domain=\"some_tech.com\")\n",
- "dns_client.dns_cache.get(\"some_tech.com\", None) is not None\n",
+ "dns_client.dns_cache.get(\"some_tech.com\", None)\n",
"len(dns_client.dns_cache) == 1\n"
]
},
{
- "cell_type": "code",
- "execution_count": null,
+ "cell_type": "markdown",
"metadata": {},
- "outputs": [],
"source": [
- "env.step(0)"
+ "## UC7 Network | Green & Red Agent (TAP001) default behaviour"
]
},
{
@@ -1007,16 +1011,7 @@
"metadata": {},
"outputs": [],
"source": [
- "env.step(0)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "for _ in range(80):\n",
+ "for _ in range(100):\n",
" env.step(0)"
]
},
@@ -1029,13 +1024,13 @@
"def print_agent_actions_except_do_nothing(agent_name):\n",
" print(f\"\\n{agent_name} actions this episode:\")\n",
" for item in env.game.agents[agent_name].history:\n",
- " if item.action != \"DONOTHING\":\n",
+ " if item.action != \"do-nothing\":\n",
" node, application = 'unknown', 'unknown'\n",
- " if (node_id := item.parameters.get('node_id')) is not None:\n",
- " node = env.game.agents[agent_name].action_manager.node_names[node_id]\n",
- " if (application_id := item.parameters.get('application_id')) is not None:\n",
- " application = env.game.agents[agent_name].action_manager.application_names[node_id][application_id]\n",
- " print(f\"Step: {item.timestep}, action: {item.action}, {node}, {application}, response: {item.response.status}\")\n",
+ " if (item.parameters.get('node_name')) is not None:\n",
+ " node = item.parameters.get('node_name')\n",
+ " if (item.parameters.get('application_name')) is not None:\n",
+ " application = item.parameters.get('application_name')\n",
+ " print(f\"Step: {item.timestep}, action: {item.action}, node: {node}, application: {application}, response: {item.response.status}\")\n",
"\n",
"print_agent_actions_except_do_nothing(\"HOME_WORKER-1-DB\")"
]
@@ -1047,7 +1042,7 @@
"outputs": [],
"source": [
"green_agent = env.game.agents.get(\"HOME_WORKER-1-DB\")\n",
- "green_agent.reward_function.total_reward"
+ "print(f\"Green Agent Ref: {green_agent.config.ref} Reward: {green_agent.reward_function.total_reward}\")"
]
},
{
@@ -1066,7 +1061,7 @@
"outputs": [],
"source": [
"green_agent = env.game.agents.get(\"HOME_WORKER-1-WEB\")\n",
- "green_agent.reward_function.total_reward"
+ "print(f\"Green Agent Ref: {green_agent.config.ref} Reward: {green_agent.reward_function.total_reward}\")"
]
},
{
@@ -1085,26 +1080,17 @@
"metadata": {},
"outputs": [],
"source": [
- "st_project_a_private_pc_1.file_system.show()\n",
+ "# successful TAP001 kill chain\n",
"\n",
- "# # st_project_a_private_pc_1.file_system.folders[\"exfiltration\"].show()\n",
+ "st_project_a_private_pc_1.file_system.show(full=True)\n",
"\n",
- "# st_project_a_private_pc_1.software_manager.show()"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "st_intra_prv_rt_cr.acl.show()"
+ "st_data_private_server_database.file_system.show(full=True)"
]
}
],
"metadata": {
"kernelspec": {
- "display_name": ".venv",
+ "display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
diff --git a/src/primaite/notebooks/Using-Episode-Schedules.ipynb b/src/primaite/notebooks/Using-Episode-Schedules.ipynb
index 4bc49a70..4260cebf 100644
--- a/src/primaite/notebooks/Using-Episode-Schedules.ipynb
+++ b/src/primaite/notebooks/Using-Episode-Schedules.ipynb
@@ -48,7 +48,6 @@
"from primaite.session.environment import PrimaiteGymEnv\n",
"from primaite import PRIMAITE_PATHS\n",
"from prettytable import PrettyTable\n",
- "from primaite.game.agent.scripted_agents import probabilistic_agent, data_manipulation_bot\n",
"scenario_path = PRIMAITE_PATHS.user_config_path / \"example_config/scenario_with_placeholders\""
]
},
@@ -239,7 +238,7 @@
"### Episode 2\n",
"When we reset the environment again, it moves onto episode 2, where it will bring in greens_1 and reds_1 for green and red agent definitions. Let's verify the agent names and that they take actions at the defined frequency.\n",
"\n",
- "Most green actions will be `node_application_execute` while red will `DONOTHING` except at steps 10 and 20."
+ "Most green actions will be `node-application-execute` while red will `do-nothing` except at steps 10 and 20."
]
},
{
@@ -270,7 +269,7 @@
"### Episode 3\n",
"When we reset the environment again, it moves onto episode 3, where it will bring in greens_2 and reds_2 for green and red agent definitions. Let's verify the agent names and that they take actions at the defined frequency.\n",
"\n",
- "Now, green will perform `node_application_execute` only 5% of the time, while red will perform `node_application_execute` more frequently than before."
+ "Now, green will perform `node-application-execute` only 5% of the time, while red will perform `node-application-execute` more frequently than before."
]
},
{
@@ -353,7 +352,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
- "In the 0th episode, simulation_variant_1.yaml is loaded in and the server gets a `DatabaseService`, while client_1 gets `DatabaseClient`."
+ "In the 0th episode, `simulation_variant_1.yaml` is loaded in and the server gets a `database-service`, while client_1 gets `database-client`."
]
},
{
@@ -382,7 +381,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
- "In the 1st episode, `simulation_variant_2.yaml` is loaded in, therefore the server gets a `FTPServer` and client_1 gets a `RansomwareScript`."
+ "In the 1st episode, `simulation_variant_2.yaml` is loaded in, therefore the server gets a `ftp-server` and client_1 gets a `ransomware-script`."
]
},
{
diff --git a/src/primaite/notebooks/multi-processing.ipynb b/src/primaite/notebooks/multi-processing.ipynb
index e56bf362..13fe1549 100644
--- a/src/primaite/notebooks/multi-processing.ipynb
+++ b/src/primaite/notebooks/multi-processing.ipynb
@@ -8,7 +8,7 @@
"\n",
"© Crown-owned copyright 2025, Defence Science and Technology Laboratory UK\n",
"\n",
- "This notebook uses SubprocVecEnv from SB3."
+ "This notebook uses **SubprocVecEnv** from **SB3** to train an agent on the [UC2 scenario](./Data-Manipulation-E2E-Demonstration.ipynb)."
]
},
{
@@ -37,15 +37,7 @@
"from stable_baselines3 import PPO\n",
"from stable_baselines3.common.utils import set_random_seed\n",
"from stable_baselines3.common.vec_env import SubprocVecEnv\n",
- "from primaite.session.environment import PrimaiteGymEnv\n"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
+ "from primaite.session.environment import PrimaiteGymEnv\n",
"from primaite.config.load import data_manipulation_config_path"
]
},
@@ -72,12 +64,11 @@
"metadata": {},
"outputs": [],
"source": [
- "\n",
"EPISODE_LEN = 128\n",
"NUM_EPISODES = 10\n",
"NO_STEPS = EPISODE_LEN * NUM_EPISODES\n",
"BATCH_SIZE = 32\n",
- "LEARNING_RATE = 3e-4\n"
+ "LEARNING_RATE = 3e-4"
]
},
{
diff --git a/src/primaite/session/__init__.py b/src/primaite/session/__init__.py
index 836b79af..bb4e8852 100644
--- a/src/primaite/session/__init__.py
+++ b/src/primaite/session/__init__.py
@@ -1 +1,2 @@
# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK
+"""Gymnasium and other RL environments for interfacing PrimAITE with RL models."""
diff --git a/src/primaite/session/environment.py b/src/primaite/session/environment.py
index fa545dbc..e05c65ab 100644
--- a/src/primaite/session/environment.py
+++ b/src/primaite/session/environment.py
@@ -1,4 +1,5 @@
# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK
+"""Main Gymnasium entrypoint for RL agents into PrimAITE."""
import json
import random
import sys
diff --git a/src/primaite/session/episode_schedule.py b/src/primaite/session/episode_schedule.py
index 126dcf9f..82bc2eac 100644
--- a/src/primaite/session/episode_schedule.py
+++ b/src/primaite/session/episode_schedule.py
@@ -1,4 +1,5 @@
# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK
+"""Strategies for selecting the game configuration between different episodes of a session."""
import copy
from abc import ABC, abstractmethod
from itertools import chain
diff --git a/src/primaite/session/io.py b/src/primaite/session/io.py
index 6c2f4f29..2c40a323 100644
--- a/src/primaite/session/io.py
+++ b/src/primaite/session/io.py
@@ -1,4 +1,5 @@
# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK
+"""Utilities for reading settings and writing of log files."""
import json
from datetime import datetime
from pathlib import Path
diff --git a/src/primaite/session/ray_envs.py b/src/primaite/session/ray_envs.py
index 16c85cb3..69bcd3f4 100644
--- a/src/primaite/session/ray_envs.py
+++ b/src/primaite/session/ray_envs.py
@@ -1,4 +1,5 @@
# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK
+"""Entrypoint for Ray RLLib single- and multi-agent environments."""
import json
from typing import Dict, SupportsFloat, Tuple
diff --git a/src/primaite/setup/reset_demo_notebooks.py b/src/primaite/setup/reset_demo_notebooks.py
index ad4091e3..752106ef 100644
--- a/src/primaite/setup/reset_demo_notebooks.py
+++ b/src/primaite/setup/reset_demo_notebooks.py
@@ -1,4 +1,5 @@
# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK
+"""Clear the user data directory of example notebooks and copy fresh copies in."""
import filecmp
import shutil
from logging import Logger
diff --git a/src/primaite/setup/reset_example_configs.py b/src/primaite/setup/reset_example_configs.py
index a94d6d4a..59dcd148 100644
--- a/src/primaite/setup/reset_example_configs.py
+++ b/src/primaite/setup/reset_example_configs.py
@@ -1,4 +1,5 @@
# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK
+"""Clear user data folder of example config files and put fresh copies in."""
import filecmp
import os
import shutil
diff --git a/src/primaite/simulator/__init__.py b/src/primaite/simulator/__init__.py
index e85a2d1e..701d3757 100644
--- a/src/primaite/simulator/__init__.py
+++ b/src/primaite/simulator/__init__.py
@@ -1,5 +1,5 @@
# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK
-"""Warning: SIM_OUTPUT is a mutable global variable for the simulation output directory."""
+"""The PrimAITE simulation layer."""
from datetime import datetime
from enum import IntEnum
from pathlib import Path
diff --git a/src/primaite/simulator/core.py b/src/primaite/simulator/core.py
index 750372b3..bcc48575 100644
--- a/src/primaite/simulator/core.py
+++ b/src/primaite/simulator/core.py
@@ -1,6 +1,6 @@
# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK
# flake8: noqa
-"""Core of the PrimAITE Simulator."""
+"""Base classes for the PrimAITE Simulator."""
import warnings
from abc import abstractmethod
from typing import Callable, Dict, Iterable, List, Literal, Optional, Tuple, Union
diff --git a/src/primaite/simulator/domain/__init__.py b/src/primaite/simulator/domain/__init__.py
index 836b79af..f82adbc9 100644
--- a/src/primaite/simulator/domain/__init__.py
+++ b/src/primaite/simulator/domain/__init__.py
@@ -1 +1,2 @@
# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK
+"""Currently not used."""
diff --git a/src/primaite/simulator/file_system/__init__.py b/src/primaite/simulator/file_system/__init__.py
index 836b79af..41e25494 100644
--- a/src/primaite/simulator/file_system/__init__.py
+++ b/src/primaite/simulator/file_system/__init__.py
@@ -1 +1,2 @@
# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK
+"""File system for nodes."""
diff --git a/src/primaite/simulator/file_system/file.py b/src/primaite/simulator/file_system/file.py
index 58607bf6..22dee206 100644
--- a/src/primaite/simulator/file_system/file.py
+++ b/src/primaite/simulator/file_system/file.py
@@ -1,4 +1,5 @@
# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK
+"""Simulation of a file on a computer file system."""
from __future__ import annotations
import hashlib
diff --git a/src/primaite/simulator/file_system/file_system.py b/src/primaite/simulator/file_system/file_system.py
index 54e649f2..2b647ca4 100644
--- a/src/primaite/simulator/file_system/file_system.py
+++ b/src/primaite/simulator/file_system/file_system.py
@@ -1,4 +1,5 @@
# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK
+"""Computer file system."""
from __future__ import annotations
from pathlib import Path
diff --git a/src/primaite/simulator/file_system/folder.py b/src/primaite/simulator/file_system/folder.py
index 5b9a6931..4586ccc8 100644
--- a/src/primaite/simulator/file_system/folder.py
+++ b/src/primaite/simulator/file_system/folder.py
@@ -1,4 +1,5 @@
# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK
+"""Simulation of a folder on a computer file system."""
from __future__ import annotations
import warnings
diff --git a/src/primaite/simulator/network/__init__.py b/src/primaite/simulator/network/__init__.py
index 836b79af..71d27bd8 100644
--- a/src/primaite/simulator/network/__init__.py
+++ b/src/primaite/simulator/network/__init__.py
@@ -1 +1,2 @@
# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK
+"""Top-level network object."""
diff --git a/src/primaite/simulator/network/hardware/nodes/network/firewall.py b/src/primaite/simulator/network/hardware/nodes/network/firewall.py
index c872b8b3..d2716f08 100644
--- a/src/primaite/simulator/network/hardware/nodes/network/firewall.py
+++ b/src/primaite/simulator/network/hardware/nodes/network/firewall.py
@@ -57,8 +57,8 @@ class Firewall(Router, discriminator="firewall"):
>>> # Permit HTTP traffic to the DMZ
>>> firewall.dmz_inbound_acl.add_rule(
... action=ACLAction.PERMIT,
- ... protocol=IPProtocol["TCP"],
- ... dst_port=Port["HTTP"],
+ ... protocol=PROTOCOL_LOOKUP["TCP"],
+ ... dst_port=PORT_LOOKUP["HTTP"],
... src_ip_address="0.0.0.0",
... src_wildcard_mask="0.0.0.0",
... dst_ip_address="172.16.0.0",
diff --git a/src/primaite/simulator/network/hardware/nodes/network/router.py b/src/primaite/simulator/network/hardware/nodes/network/router.py
index 33b55b9d..db9eeabe 100644
--- a/src/primaite/simulator/network/hardware/nodes/network/router.py
+++ b/src/primaite/simulator/network/hardware/nodes/network/router.py
@@ -265,7 +265,7 @@ class AccessControlList(SimComponent):
>>> acl = AccessControlList()
>>> acl.add_rule(
... action=ACLAction.PERMIT,
- ... protocol=IPProtocol["TCP"],
+ ... protocol=PROTOCOL_LOOKUP["TCP"],
... src_ip_address="192.168.1.0",
... src_wildcard_mask="0.0.0.255",
... dst_ip_address="192.168.2.0",
@@ -399,11 +399,11 @@ class AccessControlList(SimComponent):
>>> router = Router("router")
>>> router.add_rule(
... action=ACLAction.DENY,
- ... protocol=IPProtocol["TCP"],
+ ... protocol=PROTOCOL_LOOKUP["TCP"],
... src_ip_address="192.168.1.0",
... src_wildcard_mask="0.0.0.255",
... dst_ip_address="10.10.10.5",
- ... dst_port=Port["SSH"],
+ ... dst_port=PORT_LOOKUP["SSH"],
... position=5
... )
>>> # This permits SSH traffic from the 192.168.1.0/24 subnet to the 10.10.10.5 server.
@@ -411,10 +411,10 @@ class AccessControlList(SimComponent):
>>> # Then if we want to allow a specific IP address from this subnet to SSH into the server
>>> router.add_rule(
... action=ACLAction.PERMIT,
- ... protocol=IPProtocol["TCP"],
+ ... protocol=PROTOCOL_LOOKUP["TCP"],
... src_ip_address="192.168.1.25",
... dst_ip_address="10.10.10.5",
- ... dst_port=Port["SSH"],
+ ... dst_port=PORT_LOOKUP["SSH"],
... position=4
... )
diff --git a/src/primaite/simulator/network/transmission/network_layer.py b/src/primaite/simulator/network/transmission/network_layer.py
index 7a6b34c9..6996be0d 100644
--- a/src/primaite/simulator/network/transmission/network_layer.py
+++ b/src/primaite/simulator/network/transmission/network_layer.py
@@ -61,7 +61,7 @@ class IPPacket(BaseModel):
>>> ip_packet = IPPacket(
... src_ip_address=IPv4Address('192.168.0.1'),
... dst_ip_address=IPv4Address('10.0.0.1'),
- ... protocol=IPProtocol["TCP"],
+ ... protocol=PROTOCOL_LOOKUP["TCP"],
... ttl=64,
... precedence=Precedence.CRITICAL
... )
diff --git a/src/primaite/simulator/network/transmission/transport_layer.py b/src/primaite/simulator/network/transmission/transport_layer.py
index 689eea2f..fa258afa 100644
--- a/src/primaite/simulator/network/transmission/transport_layer.py
+++ b/src/primaite/simulator/network/transmission/transport_layer.py
@@ -15,8 +15,8 @@ class UDPHeader(BaseModel):
:Example:
>>> udp_header = UDPHeader(
- ... src_port=Port["HTTP_ALT"],
- ... dst_port=Port["HTTP"],
+ ... src_port=PORT_LOOKUP["HTTP_ALT"],
+ ... dst_port=PORT_LOOKUP["HTTP"],
... )
"""
@@ -54,8 +54,8 @@ class TCPHeader(BaseModel):
:Example:
>>> tcp_header = TCPHeader(
- ... src_port=Port["HTTP_ALT"],
- ... dst_port=Port["HTTP"],
+ ... src_port=PORT_LOOKUP["HTTP_ALT"],
+ ... dst_port=PORT_LOOKUP["HTTP"],
... flags=[TCPFlags.SYN, TCPFlags.ACK]
... )
"""
diff --git a/src/primaite/simulator/sim_container.py b/src/primaite/simulator/sim_container.py
index abc83203..09a35a3c 100644
--- a/src/primaite/simulator/sim_container.py
+++ b/src/primaite/simulator/sim_container.py
@@ -1,4 +1,5 @@
# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK
+"""Top-level simulation object that holds references to all child simulation components."""
from typing import Dict
from primaite.interface.request import RequestResponse
diff --git a/src/primaite/simulator/system/__init__.py b/src/primaite/simulator/system/__init__.py
index 836b79af..928fe478 100644
--- a/src/primaite/simulator/system/__init__.py
+++ b/src/primaite/simulator/system/__init__.py
@@ -1 +1,2 @@
# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK
+"""Software and system on nodes."""
diff --git a/src/primaite/simulator/system/applications/__init__.py b/src/primaite/simulator/system/applications/__init__.py
index 836b79af..cf608a90 100644
--- a/src/primaite/simulator/system/applications/__init__.py
+++ b/src/primaite/simulator/system/applications/__init__.py
@@ -1 +1,2 @@
# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK
+"""Software applications."""
diff --git a/src/primaite/simulator/system/applications/red_applications/dos_bot.py b/src/primaite/simulator/system/applications/red_applications/dos_bot.py
index 1528de57..5b9f0286 100644
--- a/src/primaite/simulator/system/applications/red_applications/dos_bot.py
+++ b/src/primaite/simulator/system/applications/red_applications/dos_bot.py
@@ -128,7 +128,7 @@ class DoSBot(DatabaseClient, discriminator="dos-bot"):
Configure the Denial of Service bot.
:param: target_ip_address: The IP address of the Node containing the target service.
- :param: target_port: The port of the target service. Optional - Default is `Port["HTTP"]`
+ :param: target_port: The port of the target service. Optional - Default is `PORT_LOOKUP["HTTP"]`
:param: payload: The payload the DoS Bot will throw at the target service. Optional - Default is `None`
:param: repeat: If True, the bot will maintain the attack. Optional - Default is `True`
:param: port_scan_p_of_success: The chance of the port scan being successful. Optional - Default is 0.1 (10%)
diff --git a/src/primaite/simulator/system/core/session_manager.py b/src/primaite/simulator/system/core/session_manager.py
index 26e3be79..6690340e 100644
--- a/src/primaite/simulator/system/core/session_manager.py
+++ b/src/primaite/simulator/system/core/session_manager.py
@@ -332,7 +332,7 @@ class SessionManager:
)
# TODO: Only create IP packet if not ARP
# ip_packet = None
- # if dst_port != Port["ARP"]:
+ # if dst_port != PORT_LOOKUP["ARP"]:
# IPPacket(
# src_ip_address=outbound_network_interface.ip_address,
# dst_ip_address=dst_ip_address,
diff --git a/src/primaite/simulator/system/processes/__init__.py b/src/primaite/simulator/system/processes/__init__.py
index 836b79af..818be987 100644
--- a/src/primaite/simulator/system/processes/__init__.py
+++ b/src/primaite/simulator/system/processes/__init__.py
@@ -1 +1,2 @@
# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK
+"""Software processes. Not yet implemented."""
diff --git a/src/primaite/simulator/system/services/__init__.py b/src/primaite/simulator/system/services/__init__.py
index 836b79af..0dc55311 100644
--- a/src/primaite/simulator/system/services/__init__.py
+++ b/src/primaite/simulator/system/services/__init__.py
@@ -1 +1,2 @@
# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK
+"""Software services."""
diff --git a/src/primaite/simulator/system/services/arp/__init__.py b/src/primaite/simulator/system/services/arp/__init__.py
index 836b79af..60df3467 100644
--- a/src/primaite/simulator/system/services/arp/__init__.py
+++ b/src/primaite/simulator/system/services/arp/__init__.py
@@ -1 +1,2 @@
# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK
+"""Address Resolution Protocol software."""
diff --git a/src/primaite/simulator/system/services/arp/arp.py b/src/primaite/simulator/system/services/arp/arp.py
index 3302041d..ea60f27a 100644
--- a/src/primaite/simulator/system/services/arp/arp.py
+++ b/src/primaite/simulator/system/services/arp/arp.py
@@ -1,4 +1,5 @@
# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK
+"""ARP software."""
from __future__ import annotations
from abc import abstractmethod
diff --git a/src/primaite/simulator/system/services/database/__init__.py b/src/primaite/simulator/system/services/database/__init__.py
index 836b79af..b8e01ff0 100644
--- a/src/primaite/simulator/system/services/database/__init__.py
+++ b/src/primaite/simulator/system/services/database/__init__.py
@@ -1 +1,2 @@
# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK
+"""Database simulation."""
diff --git a/src/primaite/simulator/system/services/database/database_service.py b/src/primaite/simulator/system/services/database/database_service.py
index edc3f6b4..1f81ace7 100644
--- a/src/primaite/simulator/system/services/database/database_service.py
+++ b/src/primaite/simulator/system/services/database/database_service.py
@@ -1,4 +1,5 @@
# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK
+"""Database service."""
from ipaddress import IPv4Address
from typing import Any, Dict, List, Literal, Optional, Union
from uuid import uuid4
diff --git a/src/primaite/simulator/system/services/dns/__init__.py b/src/primaite/simulator/system/services/dns/__init__.py
index 836b79af..24376bea 100644
--- a/src/primaite/simulator/system/services/dns/__init__.py
+++ b/src/primaite/simulator/system/services/dns/__init__.py
@@ -1 +1,2 @@
# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK
+"""Domain Name Service software."""
diff --git a/src/primaite/simulator/system/services/dns/dns_client.py b/src/primaite/simulator/system/services/dns/dns_client.py
index 8b16af69..b1f22e07 100644
--- a/src/primaite/simulator/system/services/dns/dns_client.py
+++ b/src/primaite/simulator/system/services/dns/dns_client.py
@@ -1,4 +1,5 @@
# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK
+"""DNS Client."""
from ipaddress import IPv4Address
from typing import Dict, Optional, TYPE_CHECKING
diff --git a/src/primaite/simulator/system/services/dns/dns_server.py b/src/primaite/simulator/system/services/dns/dns_server.py
index 696af993..fced793b 100644
--- a/src/primaite/simulator/system/services/dns/dns_server.py
+++ b/src/primaite/simulator/system/services/dns/dns_server.py
@@ -1,4 +1,5 @@
# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK
+"""DNS Server."""
from ipaddress import IPv4Address
from typing import Any, Dict, Optional
diff --git a/src/primaite/simulator/system/services/ftp/__init__.py b/src/primaite/simulator/system/services/ftp/__init__.py
index 836b79af..4919442a 100644
--- a/src/primaite/simulator/system/services/ftp/__init__.py
+++ b/src/primaite/simulator/system/services/ftp/__init__.py
@@ -1 +1,2 @@
# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK
+"""File Transfer Protocol Software."""
diff --git a/src/primaite/simulator/system/services/ftp/ftp_client.py b/src/primaite/simulator/system/services/ftp/ftp_client.py
index 5e97243a..325e9428 100644
--- a/src/primaite/simulator/system/services/ftp/ftp_client.py
+++ b/src/primaite/simulator/system/services/ftp/ftp_client.py
@@ -1,4 +1,5 @@
# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK
+"""FTP Client."""
from ipaddress import IPv4Address
from typing import Dict, Optional
@@ -215,7 +216,7 @@ class FTPClient(FTPServiceABC, discriminator="ftp-client"):
:param: dest_file_name: The name of the file to be saved on the FTP Server.
:type: dest_file_name: str
- :param: dest_port: The open port of the machine that hosts the FTP Server. Default is Port["FTP"].
+ :param: dest_port: The open port of the machine that hosts the FTP Server. Default is PORT_LOOKUP["FTP"].
:type: dest_port: Optional[Port]
:param: session_id: The id of the session
@@ -276,7 +277,7 @@ class FTPClient(FTPServiceABC, discriminator="ftp-client"):
:param: dest_file_name: The name of the file to be saved on the FTP Server.
:type: dest_file_name: str
- :param: dest_port: The open port of the machine that hosts the FTP Server. Default is Port["FTP"].
+ :param: dest_port: The open port of the machine that hosts the FTP Server. Default is PORT_LOOKUP["FTP"].
:type: dest_port: Optional[int]
"""
self._active = True
diff --git a/src/primaite/simulator/system/services/ftp/ftp_server.py b/src/primaite/simulator/system/services/ftp/ftp_server.py
index 86e07c54..d838b471 100644
--- a/src/primaite/simulator/system/services/ftp/ftp_server.py
+++ b/src/primaite/simulator/system/services/ftp/ftp_server.py
@@ -1,4 +1,5 @@
# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK
+"""FTP Server."""
from typing import Any, Optional
from pydantic import Field
diff --git a/src/primaite/simulator/system/services/ftp/ftp_service.py b/src/primaite/simulator/system/services/ftp/ftp_service.py
index 13acda70..af8c3ac6 100644
--- a/src/primaite/simulator/system/services/ftp/ftp_service.py
+++ b/src/primaite/simulator/system/services/ftp/ftp_service.py
@@ -1,4 +1,5 @@
# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK
+"""FTP Service base class."""
from abc import ABC
from ipaddress import IPv4Address
from typing import Dict, Optional
@@ -114,7 +115,7 @@ class FTPServiceABC(Service, ABC):
:param: dest_ip_address: The IP address of the machine that hosts the FTP Server.
:type: dest_ip_address: Optional[IPv4Address]
- :param: dest_port: The open port of the machine that hosts the FTP Server. Default is Port["FTP"].
+ :param: dest_port: The open port of the machine that hosts the FTP Server. Default is PORT_LOOKUP["FTP"].
:type: dest_port: Optional[Port]
:param: session_id: session ID linked to the FTP Packet. Optional.
diff --git a/src/primaite/simulator/system/services/icmp/__init__.py b/src/primaite/simulator/system/services/icmp/__init__.py
index 836b79af..4f971002 100644
--- a/src/primaite/simulator/system/services/icmp/__init__.py
+++ b/src/primaite/simulator/system/services/icmp/__init__.py
@@ -1 +1,2 @@
# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK
+"""Internet Control Message Protocol."""
diff --git a/src/primaite/simulator/system/services/icmp/icmp.py b/src/primaite/simulator/system/services/icmp/icmp.py
index 207940cf..f25d2c18 100644
--- a/src/primaite/simulator/system/services/icmp/icmp.py
+++ b/src/primaite/simulator/system/services/icmp/icmp.py
@@ -1,4 +1,5 @@
# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK
+"""Internet Control Message Protocol."""
import secrets
from ipaddress import IPv4Address
from typing import Any, Dict, Optional, Tuple, Union
diff --git a/src/primaite/simulator/system/services/icmp/router_icmp.py b/src/primaite/simulator/system/services/icmp/router_icmp.py
deleted file mode 100644
index 4c69e381..00000000
--- a/src/primaite/simulator/system/services/icmp/router_icmp.py
+++ /dev/null
@@ -1,91 +0,0 @@
-# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK
-# class RouterICMP(icmp):
-# """
-# A class to represent a router's Internet Control Message Protocol (icmp) handler.
-#
-# :param sys_log: System log for logging network events and errors.
-# :type sys_log: SysLog
-# :param arp_cache: The arp cache for resolving MAC addresses.
-# :type arp_cache: ARPCache
-# :param router: The router to which this icmp handler belongs.
-# :type router: Router
-# """
-#
-# router: Router
-#
-# def __init__(self, sys_log: SysLog, arp_cache: ARPCache, router: Router):
-# super().__init__(sys_log, arp_cache)
-# self.router = router
-#
-# def process_icmp(self, frame: Frame, from_network_interface: NIC, is_reattempt: bool = False):
-# """
-# Process incoming icmp frames based on icmp type.
-#
-# :param frame: The incoming frame to process.
-# :param from_network_interface: The network interface where the frame is coming from.
-# :param is_reattempt: Flag to indicate if the process is a reattempt.
-# """
-# if frame.icmp.icmp_type == ICMPType.ECHO_REQUEST:
-# # determine if request is for router interface or whether it needs to be routed
-#
-# for network_interface in self.router.network_interfaces.values():
-# if network_interface.ip_address == frame.ip.dst_ip_address:
-# if network_interface.enabled:
-# # reply to the request
-# if not is_reattempt:
-# self.sys_log.info(f"Received echo request from {frame.ip.src_ip_address}")
-# target_mac_address = self.arp.get_arp_cache_mac_address(frame.ip.src_ip_address)
-# src_nic = self.arp.get_arp_cache_network_interface(frame.ip.src_ip_address)
-# tcp_header = TCPHeader(src_port=Port["arp"], dst_port=Port["arp"])
-#
-# # Network Layer
-# ip_packet = IPPacket(
-# src_ip_address=network_interface.ip_address,
-# dst_ip_address=frame.ip.src_ip_address,
-# protocol=IPProtocol["icmp"],
-# )
-# # Data Link Layer
-# ethernet_header = EthernetHeader(
-# src_mac_addr=src_nic.mac_address, dst_mac_addr=target_mac_address
-# )
-# icmp_reply_packet = ICMPPacket(
-# icmp_type=ICMPType.ECHO_REPLY,
-# icmp_code=0,
-# identifier=frame.icmp.identifier,
-# sequence=frame.icmp.sequence + 1,
-# )
-# payload = secrets.token_urlsafe(int(32 / 1.3)) # Standard icmp 32 bytes size
-# frame = Frame(
-# ethernet=ethernet_header,
-# ip=ip_packet,
-# tcp=tcp_header,
-# icmp=icmp_reply_packet,
-# payload=payload,
-# )
-# self.sys_log.info(f"Sending echo reply to {frame.ip.dst_ip_address}")
-#
-# src_nic.send_frame(frame)
-# return
-#
-# # Route the frame
-# self.router.process_frame(frame, from_network_interface)
-#
-# elif frame.icmp.icmp_type == ICMPType.ECHO_REPLY:
-# for network_interface in self.router.network_interfaces.values():
-# if network_interface.ip_address == frame.ip.dst_ip_address:
-# if network_interface.enabled:
-# time = frame.transmission_duration()
-# time_str = f"{time}ms" if time > 0 else "<1ms"
-# self.sys_log.info(
-# f"Reply from {frame.ip.src_ip_address}: "
-# f"bytes={len(frame.payload)}, "
-# f"time={time_str}, "
-# f"TTL={frame.ip.ttl}"
-# )
-# if not self.request_replies.get(frame.icmp.identifier):
-# self.request_replies[frame.icmp.identifier] = 0
-# self.request_replies[frame.icmp.identifier] += 1
-#
-# return
-# # Route the frame
-# self.router.process_frame(frame, from_network_interface)
diff --git a/src/primaite/simulator/system/services/ntp/__init__.py b/src/primaite/simulator/system/services/ntp/__init__.py
index 836b79af..2e5e59b6 100644
--- a/src/primaite/simulator/system/services/ntp/__init__.py
+++ b/src/primaite/simulator/system/services/ntp/__init__.py
@@ -1 +1,2 @@
# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK
+"""Network Time Protocol software."""
diff --git a/src/primaite/simulator/system/services/ntp/ntp_client.py b/src/primaite/simulator/system/services/ntp/ntp_client.py
index 6bd1f4bb..d805cc9f 100644
--- a/src/primaite/simulator/system/services/ntp/ntp_client.py
+++ b/src/primaite/simulator/system/services/ntp/ntp_client.py
@@ -1,4 +1,5 @@
# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK
+"""NTP Client."""
from datetime import datetime
from ipaddress import IPv4Address
from typing import Dict, Optional
diff --git a/src/primaite/simulator/system/services/ntp/ntp_server.py b/src/primaite/simulator/system/services/ntp/ntp_server.py
index 05696d9f..24756b90 100644
--- a/src/primaite/simulator/system/services/ntp/ntp_server.py
+++ b/src/primaite/simulator/system/services/ntp/ntp_server.py
@@ -1,4 +1,5 @@
# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK
+"""NTP Server."""
from datetime import datetime
from typing import Dict, Optional
diff --git a/src/primaite/simulator/system/services/terminal/__init__.py b/src/primaite/simulator/system/services/terminal/__init__.py
index 836b79af..7e81d229 100644
--- a/src/primaite/simulator/system/services/terminal/__init__.py
+++ b/src/primaite/simulator/system/services/terminal/__init__.py
@@ -1 +1,2 @@
# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK
+"""Command line interface on network nodes."""
diff --git a/src/primaite/simulator/system/services/terminal/terminal.py b/src/primaite/simulator/system/services/terminal/terminal.py
index 112f6abc..8ef9ca03 100644
--- a/src/primaite/simulator/system/services/terminal/terminal.py
+++ b/src/primaite/simulator/system/services/terminal/terminal.py
@@ -1,4 +1,5 @@
# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK
+"""Command line interface on network nodes."""
from __future__ import annotations
from abc import abstractmethod
@@ -23,8 +24,6 @@ from primaite.utils.validation.ip_protocol import PROTOCOL_LOOKUP
from primaite.utils.validation.port import PORT_LOOKUP
-# TODO 2824: Since remote terminal connections and remote user sessions are the same thing, we could refactor
-# the terminal to leverage the user session manager's list. This way we avoid potential bugs and code ducplication
class TerminalClientConnection(BaseModel):
"""
TerminalClientConnection Class.
diff --git a/src/primaite/simulator/system/services/web_server/__init__.py b/src/primaite/simulator/system/services/web_server/__init__.py
index 836b79af..7d40a543 100644
--- a/src/primaite/simulator/system/services/web_server/__init__.py
+++ b/src/primaite/simulator/system/services/web_server/__init__.py
@@ -1 +1,2 @@
# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK
+"""HTTP server."""
diff --git a/src/primaite/simulator/system/services/web_server/web_server.py b/src/primaite/simulator/system/services/web_server/web_server.py
index 3f8760c4..b4f8e7ac 100644
--- a/src/primaite/simulator/system/services/web_server/web_server.py
+++ b/src/primaite/simulator/system/services/web_server/web_server.py
@@ -1,4 +1,5 @@
# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK
+"""HTTP server."""
from ipaddress import IPv4Address
from typing import Any, Dict, List, Optional
from urllib.parse import urlparse
diff --git a/src/primaite/simulator/system/software.py b/src/primaite/simulator/system/software.py
index 86b57818..1fb30f2a 100644
--- a/src/primaite/simulator/system/software.py
+++ b/src/primaite/simulator/system/software.py
@@ -1,4 +1,5 @@
# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK
+"""Base class for system software."""
import copy
from abc import ABC, abstractmethod
from datetime import datetime
diff --git a/src/primaite/utils/validation/__init__.py b/src/primaite/utils/validation/__init__.py
index 836b79af..656d04cf 100644
--- a/src/primaite/utils/validation/__init__.py
+++ b/src/primaite/utils/validation/__init__.py
@@ -1 +1,2 @@
# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK
+"""Special validation for use with pydantic."""
diff --git a/src/primaite/utils/validation/ip_protocol.py b/src/primaite/utils/validation/ip_protocol.py
index 654a5156..0d06a257 100644
--- a/src/primaite/utils/validation/ip_protocol.py
+++ b/src/primaite/utils/validation/ip_protocol.py
@@ -1,5 +1,5 @@
# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK
-# Define a custom IP protocol validator
+"""Validation for internet protocols."""
from typing import Any
from pydantic import BeforeValidator, TypeAdapter, ValidationError
diff --git a/src/primaite/utils/validation/ipv4_address.py b/src/primaite/utils/validation/ipv4_address.py
index 1dc6c74e..1e582343 100644
--- a/src/primaite/utils/validation/ipv4_address.py
+++ b/src/primaite/utils/validation/ipv4_address.py
@@ -1,5 +1,5 @@
# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK
-
+"""Validation for IPv4 addresses expressed as strings."""
from ipaddress import IPv4Address
from typing import Any, Final
diff --git a/src/primaite/utils/validation/port.py b/src/primaite/utils/validation/port.py
index 564e843c..71d173e7 100644
--- a/src/primaite/utils/validation/port.py
+++ b/src/primaite/utils/validation/port.py
@@ -1,5 +1,5 @@
# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK
-# Define a custom port validator
+"""Custom port validation."""
from typing import Any
from pydantic import BeforeValidator, TypeAdapter, ValidationError
diff --git a/tests/assets/configs/data_manipulation.yaml b/tests/assets/configs/data_manipulation.yaml
index 59f97644..ea92aab8 100644
--- a/tests/assets/configs/data_manipulation.yaml
+++ b/tests/assets/configs/data_manipulation.yaml
@@ -403,7 +403,7 @@ agents:
action: "router-acl-add-rule"
options:
target_router: router_1
- position: 1
+ position: 0
permission: DENY
src_ip: 192.168.10.21 # client 1
dst_ip: ALL # ALL
@@ -416,7 +416,7 @@ agents:
action: "router-acl-add-rule"
options:
target_router: router_1
- position: 2
+ position: 1
permission: DENY
src_ip: 192.168.10.22 # client 2
dst_ip: ALL # ALL
@@ -429,7 +429,7 @@ agents:
action: "router-acl-add-rule"
options:
target_router: router_1
- position: 3
+ position: 2
permission: DENY
src_ip: 192.168.10.21 # client 1
dst_ip: 192.168.1.12 # web server
@@ -442,7 +442,7 @@ agents:
action: "router-acl-add-rule"
options:
target_router: router_1
- position: 4
+ position: 3
permission: DENY
src_ip: 192.168.10.22 # client 2
dst_ip: 192.168.1.12 # web server
@@ -455,7 +455,7 @@ agents:
action: "router-acl-add-rule"
options:
target_router: router_1
- position: 5
+ position: 4
permission: DENY
src_ip: 192.168.10.21 # client 1
dst_ip: 192.168.1.14 # database
@@ -468,7 +468,7 @@ agents:
action: "router-acl-add-rule"
options:
target_router: router_1
- position: 6
+ position: 5
permission: DENY
src_ip: 192.168.10.22 # client 2
dst_ip: 192.168.1.14 # database
diff --git a/tests/unit_tests/_primaite/_simulator/_network/_hardware/test_node_actions.py b/tests/unit_tests/_primaite/_simulator/_network/_hardware/test_node_actions.py
index 425c0887..fa3d51b9 100644
--- a/tests/unit_tests/_primaite/_simulator/_network/_hardware/test_node_actions.py
+++ b/tests/unit_tests/_primaite/_simulator/_network/_hardware/test_node_actions.py
@@ -61,9 +61,6 @@ def test_node_os_scan(node):
"""Test OS Scanning."""
node.operating_state = NodeOperatingState.ON
- # add process to node
- # TODO implement processes
-
# add services to node
node.software_manager.install(DummyService)
service = node.software_manager.software.get("dummy-service")
@@ -95,7 +92,6 @@ def test_node_os_scan(node):
node.apply_timestep(timestep=i)
# should update the state of all items
- # TODO assert process.health_state_visible == SoftwareHealthState.COMPROMISED
assert service.health_state_visible == SoftwareHealthState.COMPROMISED
assert application.health_state_visible == SoftwareHealthState.COMPROMISED
assert folder.visible_health_status == FileSystemItemHealthStatus.CORRUPT
@@ -107,9 +103,6 @@ def test_node_red_scan(node):
"""Test revealing to red"""
node.operating_state = NodeOperatingState.ON
- # add process to node
- # TODO implement processes
-
# add services to node
node.software_manager.install(DummyService)
service = node.software_manager.software.get("dummy-service")
@@ -138,7 +131,6 @@ def test_node_red_scan(node):
node.apply_timestep(timestep=i)
# should update the state of all items
- # TODO assert process.revealed_to_red is True
assert service.revealed_to_red is True
assert application.revealed_to_red is True
assert folder.revealed_to_red is True