Merged PR 372: Integrate executed notebooks into primaite docs
## Summary Integrating the notebooks into the primaite sphinx socumentation ## Test process *How have you tested this (if applicable)?* ## Checklist - [X] PR is linked to a **work item** - [X] **acceptance criteria** of linked ticket are met - [X] performed **self-review** of the code - [ ] written **tests** for any new functionality added with this PR - [X] updated the **documentation** if this PR changes or adds functionality - [ ] written/updated **design docs** if this PR implements new functionality - [ ] updated the **change log** - [X] ran **pre-commit** checks for code style - [ ] attended to any **TO-DOs** left in the code Related work items: #2472
This commit is contained in:
@@ -29,6 +29,10 @@ jobs:
|
||||
pip install -e .[dev]
|
||||
displayName: 'Install PrimAITE for docs autosummary'
|
||||
|
||||
- script: |
|
||||
apt-get install pandoc
|
||||
displayName: 'Install Pandoc'
|
||||
|
||||
- script: |
|
||||
primaite setup
|
||||
displayName: 'Perform PrimAITE Setup'
|
||||
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -83,6 +83,9 @@ target/
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
PPO_UC2/
|
||||
# ignore everything but the executed notebooks rst in the docs/source/notebooks directory
|
||||
!docs/source/notebooks/executed_notebooks.rst
|
||||
docs/source/notebooks/**/*
|
||||
|
||||
# IPython
|
||||
profile_default/
|
||||
@@ -152,6 +155,7 @@ docs/source/primaite-dependencies.rst
|
||||
src/primaite/outputs/
|
||||
simulation_output/
|
||||
sessions/
|
||||
PrimAITE-PPO-example-agent.zip
|
||||
|
||||
# benchmark session outputs
|
||||
benchmark/output
|
||||
|
||||
17
README.md
17
README.md
@@ -137,7 +137,22 @@ python -m jupyter notebook
|
||||
```
|
||||
Then, click the URL provided by the jupyter command to open the jupyter application in your browser. You can also open notebooks in your IDE if supported.
|
||||
|
||||
## 📚 Building documentation
|
||||
## 📚 Documentation
|
||||
|
||||
### Pre requisites
|
||||
|
||||
Building the documentation requires the installation of Pandoc
|
||||
|
||||
##### Unix
|
||||
```bash
|
||||
sudo apt-get install pandoc
|
||||
```
|
||||
|
||||
##### Other operating systems
|
||||
Follow the steps in https://pandoc.org/installing.html
|
||||
|
||||
### Building the documentation
|
||||
|
||||
The PrimAITE documentation can be built with the following commands:
|
||||
|
||||
##### Unix
|
||||
|
||||
105
docs/conf.py
105
docs/conf.py
@@ -9,8 +9,10 @@ import datetime
|
||||
# -- Project information -----------------------------------------------------
|
||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
from typing import Any
|
||||
from pathlib import Path
|
||||
from typing import Any, List, Optional
|
||||
|
||||
import furo # noqa
|
||||
|
||||
@@ -48,6 +50,7 @@ extensions = [
|
||||
"sphinx.ext.viewcode", # Add a link to the Python source code for classes, functions etc.
|
||||
"sphinx.ext.todo",
|
||||
"sphinx_copybutton", # Adds a copy button to code blocks
|
||||
"nbsphinx",
|
||||
]
|
||||
|
||||
templates_path = ["_templates"]
|
||||
@@ -64,6 +67,19 @@ html_theme = "furo"
|
||||
html_static_path = ["_static"]
|
||||
html_theme_options = {"globaltoc_collapse": True, "globaltoc_maxdepth": 2}
|
||||
html_copy_source = False
|
||||
nbsphinx_allow_errors = False # set to True to take shortcuts
|
||||
html_scaled_image_link = False
|
||||
|
||||
# make some stuff easier to read
|
||||
nbsphinx_prolog = """
|
||||
.. raw:: html
|
||||
|
||||
<style>
|
||||
.stderr {
|
||||
color: #000 !important
|
||||
}
|
||||
</style>
|
||||
"""
|
||||
|
||||
|
||||
def replace_token(app: Any, docname: Any, source: Any):
|
||||
@@ -74,11 +90,96 @@ def replace_token(app: Any, docname: Any, source: Any):
|
||||
source[0] = result
|
||||
|
||||
|
||||
tokens = {"{VERSION}": release} # Token VERSION is replaced by the value of the PrimAITE version in the version file
|
||||
tokens = {
|
||||
"{VERSION}": release,
|
||||
} # Token VERSION is replaced by the value of the PrimAITE version in the version file
|
||||
"""Dict containing the tokens that need to be replaced in documentation."""
|
||||
|
||||
|
||||
def notebook_assets(ignored_files: Optional[List[str]] = [], include_file_types: Optional[List[str]] = []) -> Any:
|
||||
"""
|
||||
Creates a function to be used with `shutil.copytree`'s `ignore` parameter.
|
||||
|
||||
:param ignored_files: A list of specific file names to ignore. If a file in the directory matches one of these
|
||||
names, it will be excluded from the copy process.
|
||||
:type ignored_files: Optional[List[str]]
|
||||
:param include_file_types: A list of file extensions to include in the copy process. Files that do not match these
|
||||
extensions will be excluded. If this list is empty, all files will be excluded, effectively copying only
|
||||
directories.
|
||||
:type include_file_types: Optional[List[str]]
|
||||
"""
|
||||
|
||||
def ignore_items(directory: List[str], contents: List[str]) -> List[str]:
|
||||
"""
|
||||
Determines which files and directories should be ignored during the copy process.
|
||||
|
||||
:param directory: The directory being copied.
|
||||
:type directory: str
|
||||
:param contents: A list of contents in the directory.
|
||||
:type contents: List[str]
|
||||
:return: A list of items to exclude from the copy process.
|
||||
:rtype: List[str]
|
||||
"""
|
||||
exclude_items = []
|
||||
|
||||
for item in contents:
|
||||
if item in ignored_files:
|
||||
exclude_items.append(item)
|
||||
continue
|
||||
|
||||
if len(include_file_types) > 0:
|
||||
if not any(item.lower().endswith(ext.lower()) for ext in include_file_types) and os.path.isdir(item):
|
||||
exclude_items.append(item)
|
||||
else:
|
||||
# if we dont specify which files to include, exclude everything
|
||||
exclude_items.append(item)
|
||||
|
||||
# exclude files but not directories
|
||||
return [path for path in exclude_items if not (Path(directory) / path).is_dir()]
|
||||
|
||||
return ignore_items
|
||||
|
||||
|
||||
def copy_notebooks_to_docs() -> Any:
|
||||
"""
|
||||
Incredibly over-engineered method that copies the notebooks and its assets to a directory within the docs directory.
|
||||
|
||||
This allows developers to create new notebooks without having to worry about updating documentation when
|
||||
a new notebook is included within PrimAITE.
|
||||
"""
|
||||
notebook_asset_types = [".ipynb", ".png"]
|
||||
notebook_directories = []
|
||||
|
||||
# find paths where notebooks are contained
|
||||
for notebook in Path("../src/primaite").rglob("*.ipynb"):
|
||||
# add parent path to notebook directory if not already added
|
||||
if notebook.parent not in notebook_directories:
|
||||
notebook_directories.append(notebook.parent)
|
||||
|
||||
# go through the notebook directories and copy the notebooks and extra assets
|
||||
for notebook_parent in notebook_directories:
|
||||
shutil.copytree(
|
||||
src=notebook_parent,
|
||||
dst=Path("source") / "notebooks" / notebook_parent.name,
|
||||
ignore=notebook_assets(include_file_types=notebook_asset_types),
|
||||
dirs_exist_ok=True,
|
||||
)
|
||||
|
||||
|
||||
def suppress_log_output():
|
||||
"""Sets the log level while building the documentation."""
|
||||
from primaite import _FILE_HANDLER, _LOGGER, _STREAM_HANDLER
|
||||
|
||||
log_level = "WARN"
|
||||
|
||||
_LOGGER.setLevel(log_level)
|
||||
_STREAM_HANDLER.setLevel(log_level)
|
||||
_FILE_HANDLER.setLevel(log_level)
|
||||
|
||||
|
||||
def setup(app: Any):
|
||||
"""Custom setup for sphinx."""
|
||||
suppress_log_output()
|
||||
copy_notebooks_to_docs()
|
||||
app.add_config_value("tokens", {}, True)
|
||||
app.connect("source-read", replace_token)
|
||||
|
||||
@@ -104,14 +104,19 @@ Head over to the :ref:`getting-started` page to install and setup PrimAITE!
|
||||
:hidden:
|
||||
|
||||
source/getting_started
|
||||
source/primaite_session
|
||||
source/example_notebooks
|
||||
source/simulation
|
||||
source/game_layer
|
||||
source/config
|
||||
source/environment
|
||||
source/customising_scenarios
|
||||
|
||||
.. toctree::
|
||||
:caption: Notebooks:
|
||||
:hidden:
|
||||
|
||||
source/example_notebooks
|
||||
source/notebooks/executed_notebooks
|
||||
|
||||
.. toctree::
|
||||
:caption: Developer information:
|
||||
:hidden:
|
||||
|
||||
@@ -75,7 +75,7 @@ this results in:
|
||||
The human readable name for the link. Not used in code, however is useful for a human to understand what the link is for.
|
||||
|
||||
``endpoint_a_hostname``
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
The ``hostname`` of the node which must be connected.
|
||||
|
||||
@@ -86,7 +86,7 @@ The port on ``endpoint_a_hostname`` which is to be connected to ``endpoint_b_por
|
||||
This accepts an integer value e.g. if port 1 is to be connected, the configuration should be ``endpoint_a_port: 1``
|
||||
|
||||
``endpoint_b_hostname``
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
The ``hostname`` of the node which must be connected.
|
||||
|
||||
|
||||
@@ -5,9 +5,14 @@
|
||||
Example Jupyter Notebooks
|
||||
=========================
|
||||
|
||||
Executed Notebooks
|
||||
------------------
|
||||
|
||||
There are a few example notebooks included which help with the understanding of PrimAITE's capabilities.
|
||||
|
||||
The Jupyter Notebooks can be run via the 2 examples below. These assume that the instructions to install PrimAITE from the :ref:`Getting Started <getting-started>` page is completed as a prerequisite.
|
||||
The PrimAITE documentation includes a pre executed example of notebooks. See :ref:`Executed Notebooks`.
|
||||
|
||||
In order to run the notebooks interactively, :ref:`install PrimAITE <getting-started>` and follow these steps:
|
||||
|
||||
Running Jupyter Notebooks
|
||||
-------------------------
|
||||
|
||||
16
docs/source/notebooks/executed_notebooks.rst
Normal file
16
docs/source/notebooks/executed_notebooks.rst
Normal file
@@ -0,0 +1,16 @@
|
||||
.. only:: comment
|
||||
|
||||
© Crown-owned copyright 2023, Defence Science and Technology Laboratory UK
|
||||
|
||||
.. _Executed Notebooks:
|
||||
|
||||
Executed Jupyter Notebooks
|
||||
==========================
|
||||
|
||||
Below is a list of available pre-executed notebooks.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
:glob:
|
||||
|
||||
**/*
|
||||
@@ -71,7 +71,8 @@ dev = [
|
||||
"setuptools==66",
|
||||
"Sphinx==7.1.2",
|
||||
"sphinx-copybutton==0.5.2",
|
||||
"wheel==0.38.4"
|
||||
"wheel==0.38.4",
|
||||
"nbsphinx==0.9.4"
|
||||
]
|
||||
|
||||
[project.scripts]
|
||||
|
||||
@@ -50,7 +50,7 @@
|
||||
" cfg = yaml.safe_load(f)\n",
|
||||
" make_cfg_have_flat_obs(cfg)\n",
|
||||
"\n",
|
||||
"env = PrimaiteGymEnv(game_config = cfg)\n",
|
||||
"env = PrimaiteGymEnv(env_config = cfg)\n",
|
||||
"obs, info = env.reset()\n",
|
||||
"print('env created successfully')"
|
||||
]
|
||||
@@ -126,7 +126,13 @@
|
||||
"There are two important parts of the YAML config for varying red agent behaviour.\n",
|
||||
"\n",
|
||||
"### Red agent settings\n",
|
||||
"Here is an annotated config for the red agent in the data manipulation scenario.\n",
|
||||
"Here is an annotated config for the red agent in the data manipulation scenario."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"```yaml\n",
|
||||
" - ref: data_manipulation_attacker # name of agent\n",
|
||||
" team: RED # not used, just for human reference\n",
|
||||
@@ -171,10 +177,21 @@
|
||||
" start_step: 25 # first attack at step 25\n",
|
||||
" frequency: 20 # attacks will happen every 20 steps (on average)\n",
|
||||
" variance: 5 # the timing of attacks will vary by up to 5 steps earlier or later\n",
|
||||
"```\n",
|
||||
"\n",
|
||||
"```"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Malicious application settings\n",
|
||||
"The red agent uses an application called `DataManipulationBot` which leverages a node's `DatabaseClient` to send a malicious SQL query to the database server. Here's an annotated example of how this is configured in the yaml *(with impertinent config items omitted)*:\n",
|
||||
"The red agent uses an application called `DataManipulationBot` which leverages a node's `DatabaseClient` to send a malicious SQL query to the database server. Here's an annotated example of how this is configured in the yaml *(with impertinent config items omitted)*:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"```yaml\n",
|
||||
"simulation:\n",
|
||||
" network:\n",
|
||||
@@ -232,7 +249,7 @@
|
||||
" if agent['ref'] == \"data_manipulation_attacker\":\n",
|
||||
" agent['agent_settings'] = change\n",
|
||||
"\n",
|
||||
"env = PrimaiteGymEnv(game_config = cfg)\n",
|
||||
"env = PrimaiteGymEnv(env_config = cfg)\n",
|
||||
"env.reset()\n",
|
||||
"for step in range(100):\n",
|
||||
" step_num = env.game.step_counter\n",
|
||||
@@ -261,7 +278,7 @@
|
||||
"with open(data_manipulation_config_path(), 'r') as f:\n",
|
||||
" cfg = yaml.safe_load(f)\n",
|
||||
"\n",
|
||||
"env = PrimaiteGymEnv(game_config = cfg)\n",
|
||||
"env = PrimaiteGymEnv(env_config = cfg)\n",
|
||||
"env.reset()\n",
|
||||
"for ep in range(12):\n",
|
||||
" env.reset()\n",
|
||||
@@ -307,7 +324,7 @@
|
||||
" if agent['ref'] == \"data_manipulation_attacker\":\n",
|
||||
" agent.update(change)\n",
|
||||
"\n",
|
||||
"env = PrimaiteGymEnv(game_config = cfg)\n",
|
||||
"env = PrimaiteGymEnv(env_config = cfg)\n",
|
||||
"env.reset()\n",
|
||||
"for ep in range(12):\n",
|
||||
" env.reset()\n",
|
||||
@@ -365,7 +382,7 @@
|
||||
" if node['hostname'] in ['client_1', 'client_2']:\n",
|
||||
" node['applications'] = change['applications']\n",
|
||||
"\n",
|
||||
"env = PrimaiteGymEnv(game_config = cfg)\n",
|
||||
"env = PrimaiteGymEnv(env_config = cfg)\n",
|
||||
"env.reset()\n",
|
||||
"for ep in range(5):\n",
|
||||
" env.reset()\n",
|
||||
@@ -410,7 +427,7 @@
|
||||
" if node['hostname'] in ['client_1', 'client_2']:\n",
|
||||
" node['applications'] = change['applications']\n",
|
||||
"\n",
|
||||
"env = PrimaiteGymEnv(game_config = cfg)\n",
|
||||
"env = PrimaiteGymEnv(env_config = cfg)\n",
|
||||
"env.reset()\n",
|
||||
"for ep in range(5):\n",
|
||||
" env.reset()\n",
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
"\n",
|
||||
"The network consists of an office subnet and a server subnet. Clients in the office access a website which fetches data from a database. Occasionally, admins need to access the database directly from the clients.\n",
|
||||
"\n",
|
||||
"[<img src=\"_package_data/uc2_network.png\" width=\"500\"/>](_package_data/uc2_network.png)\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"_(click image to enlarge)_\n",
|
||||
"\n",
|
||||
@@ -160,139 +160,158 @@
|
||||
"### Mappings\n",
|
||||
"\n",
|
||||
"The dict keys for `node_id` are in the following order:\n",
|
||||
"|node_id|node name|\n",
|
||||
"|--|--|\n",
|
||||
"|1|domain_controller|\n",
|
||||
"|2|web_server|\n",
|
||||
"|3|database_server|\n",
|
||||
"|4|backup_server|\n",
|
||||
"|5|security_suite|\n",
|
||||
"|6|client_1|\n",
|
||||
"|7|client_2|\n",
|
||||
"\n",
|
||||
"| node_id | node name |\n",
|
||||
"|---------|------------------|\n",
|
||||
"| 1 | domain_controller|\n",
|
||||
"| 2 | web_server |\n",
|
||||
"| 3 | database_server |\n",
|
||||
"| 4 | backup_server |\n",
|
||||
"| 5 | security_suite |\n",
|
||||
"| 6 | client_1 |\n",
|
||||
"| 7 | client_2 |\n",
|
||||
"\n",
|
||||
"Service 1 on node 2 (web_server) corresponds to the Web Server service. Other services are only there for padding to ensure that each node's observation space has the same shape. They are filled with zeroes.\n",
|
||||
"\n",
|
||||
"Folder 1 on node 3 corresponds to the database folder. File 1 in that folder corresponds to the database storage file. Other files and folders are only there for padding to ensure that each node's observation space has the same shape. They are filled with zeroes.\n",
|
||||
"\n",
|
||||
"The dict keys for `link_id` are in the following order:\n",
|
||||
"|link_id|endpoint_a|endpoint_b|\n",
|
||||
"|--|--|--|\n",
|
||||
"|1|router_1|switch_1|\n",
|
||||
"|1|router_1|switch_2|\n",
|
||||
"|1|switch_1|domain_controller|\n",
|
||||
"|1|switch_1|web_server|\n",
|
||||
"|1|switch_1|database_server|\n",
|
||||
"|1|switch_1|backup_server|\n",
|
||||
"|1|switch_1|security_suite|\n",
|
||||
"|1|switch_2|client_1|\n",
|
||||
"|1|switch_2|client_2|\n",
|
||||
"|1|switch_2|security_suite|\n",
|
||||
"\n",
|
||||
"| link_id | endpoint_a | endpoint_b |\n",
|
||||
"|---------|------------------|-------------------|\n",
|
||||
"| 1 | router_1 | switch_1 |\n",
|
||||
"| 1 | router_1 | switch_2 |\n",
|
||||
"| 1 | switch_1 | domain_controller |\n",
|
||||
"| 1 | switch_1 | web_server |\n",
|
||||
"| 1 | switch_1 | database_server |\n",
|
||||
"| 1 | switch_1 | backup_server |\n",
|
||||
"| 1 | switch_1 | security_suite |\n",
|
||||
"| 1 | switch_2 | client_1 |\n",
|
||||
"| 1 | switch_2 | client_2 |\n",
|
||||
"| 1 | switch_2 | security_suite |\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"The ACL rules in the observation space appear in the same order that they do in the actual ACL. Though, only the first 10 rules are shown, there are default rules lower down that cannot be changed by the agent. The extra rules just allow the network to function normally, by allowing pings, ARP traffic, etc.\n",
|
||||
"\n",
|
||||
"Most nodes have only 1 network_interface, so the observation for those is placed at NIC index 1 in the observation space. Only the security suite has 2 NICs, the second NIC in the observation space is the one that connects the security suite with swtich_2.\n",
|
||||
"\n",
|
||||
"The meaning of the services' operating_state is:\n",
|
||||
"|operating_state|label|\n",
|
||||
"|--|--|\n",
|
||||
"|0|UNUSED|\n",
|
||||
"|1|RUNNING|\n",
|
||||
"|2|STOPPED|\n",
|
||||
"|3|PAUSED|\n",
|
||||
"|4|DISABLED|\n",
|
||||
"|5|INSTALLING|\n",
|
||||
"|6|RESTARTING|\n",
|
||||
"\n",
|
||||
"| operating_state | label |\n",
|
||||
"|-----------------|------------|\n",
|
||||
"| 0 | UNUSED |\n",
|
||||
"| 1 | RUNNING |\n",
|
||||
"| 2 | STOPPED |\n",
|
||||
"| 3 | PAUSED |\n",
|
||||
"| 4 | DISABLED |\n",
|
||||
"| 5 | INSTALLING |\n",
|
||||
"| 6 | RESTARTING |\n",
|
||||
"\n",
|
||||
"The meaning of the services' health_state is:\n",
|
||||
"|health_state|label|\n",
|
||||
"|--|--|\n",
|
||||
"|0|UNUSED|\n",
|
||||
"|1|GOOD|\n",
|
||||
"|2|FIXING|\n",
|
||||
"|3|COMPROMISED|\n",
|
||||
"|4|OVERWHELMED|\n",
|
||||
"\n",
|
||||
"| health_state | label |\n",
|
||||
"|--------------|-------------|\n",
|
||||
"| 0 | UNUSED |\n",
|
||||
"| 1 | GOOD |\n",
|
||||
"| 2 | FIXING |\n",
|
||||
"| 3 | COMPROMISED |\n",
|
||||
"| 4 | OVERWHELMED |\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"The meaning of the files' and folders' health_state is:\n",
|
||||
"|health_state|label|\n",
|
||||
"|--|--|\n",
|
||||
"|0|UNUSED|\n",
|
||||
"|1|GOOD|\n",
|
||||
"|2|COMPROMISED|\n",
|
||||
"|3|CORRUPT|\n",
|
||||
"|4|RESTORING|\n",
|
||||
"|5|REPAIRING|\n",
|
||||
"\n",
|
||||
"| health_state | label |\n",
|
||||
"|--------------|-------------|\n",
|
||||
"| 0 | UNUSED |\n",
|
||||
"| 1 | GOOD |\n",
|
||||
"| 2 | COMPROMISED |\n",
|
||||
"| 3 | CORRUPT |\n",
|
||||
"| 4 | RESTORING |\n",
|
||||
"| 5 | REPAIRING |\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"The meaning of the NICs' operating_status is:\n",
|
||||
"|operating_status|label|\n",
|
||||
"|--|--|\n",
|
||||
"|0|UNUSED|\n",
|
||||
"|1|ENABLED|\n",
|
||||
"|2|DISABLED|\n",
|
||||
"\n",
|
||||
"| operating_status | label |\n",
|
||||
"|------------------|----------|\n",
|
||||
"| 0 | UNUSED |\n",
|
||||
"| 1 | ENABLED |\n",
|
||||
"| 2 | DISABLED |\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"NMNE (number of malicious network events) means, for inbound or outbound traffic, means:\n",
|
||||
"|value|NMNEs|\n",
|
||||
"|--|--|\n",
|
||||
"|0|None|\n",
|
||||
"|1|1 - 5|\n",
|
||||
"|2|6 - 10|\n",
|
||||
"|3|More than 10|\n",
|
||||
"\n",
|
||||
"| value | NMNEs |\n",
|
||||
"|-------|----------------|\n",
|
||||
"| 0 | None |\n",
|
||||
"| 1 | 1 - 5 |\n",
|
||||
"| 2 | 6 - 10 |\n",
|
||||
"| 3 | More than 10 |\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Link load has the following meaning:\n",
|
||||
"|load|percent utilisation|\n",
|
||||
"|--|--|\n",
|
||||
"|0|exactly 0%|\n",
|
||||
"|1|0-11%|\n",
|
||||
"|2|11-22%|\n",
|
||||
"|3|22-33%|\n",
|
||||
"|4|33-44%|\n",
|
||||
"|5|44-55%|\n",
|
||||
"|6|55-66%|\n",
|
||||
"|7|66-77%|\n",
|
||||
"|8|77-88%|\n",
|
||||
"|9|88-99%|\n",
|
||||
"|10|exactly 100%|\n",
|
||||
"\n",
|
||||
"| load | percent utilisation |\n",
|
||||
"|------|---------------------|\n",
|
||||
"| 0 | exactly 0% |\n",
|
||||
"| 1 | 0-11% |\n",
|
||||
"| 2 | 11-22% |\n",
|
||||
"| 3 | 22-33% |\n",
|
||||
"| 4 | 33-44% |\n",
|
||||
"| 5 | 44-55% |\n",
|
||||
"| 6 | 55-66% |\n",
|
||||
"| 7 | 66-77% |\n",
|
||||
"| 8 | 77-88% |\n",
|
||||
"| 9 | 88-99% |\n",
|
||||
"| 10 | exactly 100% |\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"ACL permission has the following meaning:\n",
|
||||
"|permission|label|\n",
|
||||
"|--|--|\n",
|
||||
"|0|UNUSED|\n",
|
||||
"|1|ALLOW|\n",
|
||||
"|2|DENY|\n",
|
||||
"\n",
|
||||
"| permission | label |\n",
|
||||
"|------------|--------|\n",
|
||||
"| 0 | UNUSED |\n",
|
||||
"| 1 | ALLOW |\n",
|
||||
"| 2 | DENY |\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"ACL source / destination node ids actually correspond to IP addresses (since ACLs work with IP addresses)\n",
|
||||
"|source / dest node id|ip_address|label|\n",
|
||||
"|--|--|--|\n",
|
||||
"|0| | UNUSED|\n",
|
||||
"|1| |ALL addresses|\n",
|
||||
"|2| 192.168.1.10 | domain_controller|\n",
|
||||
"|3| 192.168.1.12 | web_server \n",
|
||||
"|4| 192.168.1.14 | database_server|\n",
|
||||
"|5| 192.168.1.16 | backup_server|\n",
|
||||
"|6| 192.168.1.110 | security_suite (eth-1)|\n",
|
||||
"|7| 192.168.10.21 | client_1|\n",
|
||||
"|8| 192.168.10.22 | client_2|\n",
|
||||
"|9| 192.168.10.110| security_suite (eth-2)|\n",
|
||||
"\n",
|
||||
"| source / dest node id | ip_address | label |\n",
|
||||
"|-----------------------|----------------|-------------------------|\n",
|
||||
"| 0 | | UNUSED |\n",
|
||||
"| 1 | | ALL addresses |\n",
|
||||
"| 2 | 192.168.1.10 | domain_controller |\n",
|
||||
"| 3 | 192.168.1.12 | web_server |\n",
|
||||
"| 4 | 192.168.1.14 | database_server |\n",
|
||||
"| 5 | 192.168.1.16 | backup_server |\n",
|
||||
"| 6 | 192.168.1.110 | security_suite (eth-1) |\n",
|
||||
"| 7 | 192.168.10.21 | client_1 |\n",
|
||||
"| 8 | 192.168.10.22 | client_2 |\n",
|
||||
"| 9 | 192.168.10.110 | security_suite (eth-2) |\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"ACL source / destination port ids have the following encoding:\n",
|
||||
"|port id|port number| port use |\n",
|
||||
"|--|--|--|\n",
|
||||
"|0||UNUSED|\n",
|
||||
"|1||ALL|\n",
|
||||
"|2|219|ARP|\n",
|
||||
"|3|53|DNS|\n",
|
||||
"|4|80|HTTP|\n",
|
||||
"|5|5432|POSTGRES_SERVER|\n",
|
||||
"\n",
|
||||
"| port id | port number | port use |\n",
|
||||
"|---------|-------------|-----------------|\n",
|
||||
"| 0 | | UNUSED |\n",
|
||||
"| 1 | | ALL |\n",
|
||||
"| 2 | 219 | ARP |\n",
|
||||
"| 3 | 53 | DNS |\n",
|
||||
"| 4 | 80 | HTTP |\n",
|
||||
"| 5 | 5432 | POSTGRES_SERVER |\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"ACL protocol ids have the following encoding:\n",
|
||||
"|protocol id|label|\n",
|
||||
"|--|--|\n",
|
||||
"|0|UNUSED|\n",
|
||||
"|1|ALL|\n",
|
||||
"|2|ICMP|\n",
|
||||
"|3|TCP|\n",
|
||||
"|4|UDP|\n",
|
||||
"\n",
|
||||
"protocol"
|
||||
"| protocol id | label |\n",
|
||||
"|-------------|-------|\n",
|
||||
"| 0 | UNUSED|\n",
|
||||
"| 1 | ALL |\n",
|
||||
"| 2 | ICMP |\n",
|
||||
"| 3 | TCP |\n",
|
||||
"| 4 | UDP |\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -83,18 +83,11 @@
|
||||
"tune.Tuner(\n",
|
||||
" \"PPO\",\n",
|
||||
" run_config=air.RunConfig(\n",
|
||||
" stop={\"timesteps_total\": 512},\n",
|
||||
" stop={\"timesteps_total\": 5 * 128},\n",
|
||||
" ),\n",
|
||||
" param_space=config\n",
|
||||
").fit()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
|
||||
@@ -74,18 +74,11 @@
|
||||
"tune.Tuner(\n",
|
||||
" \"PPO\",\n",
|
||||
" run_config=air.RunConfig(\n",
|
||||
" stop={\"timesteps_total\": 1e3 * 128}\n",
|
||||
" stop={\"timesteps_total\": 5 * 128}\n",
|
||||
" ),\n",
|
||||
" param_space=config\n",
|
||||
").fit()\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
|
||||
@@ -78,7 +78,7 @@
|
||||
"from stable_baselines3 import PPO\n",
|
||||
"\n",
|
||||
"EPISODE_LEN = 128\n",
|
||||
"NUM_EPISODES = 10\n",
|
||||
"NUM_EPISODES = 5\n",
|
||||
"NO_STEPS = EPISODE_LEN * NUM_EPISODES\n",
|
||||
"BATCH_SIZE = 32\n",
|
||||
"LEARNING_RATE = 3e-4"
|
||||
|
||||
@@ -38,6 +38,13 @@
|
||||
"YAML file with a relative path to the base scenario and a list of paths to be loaded in during each episode.\n",
|
||||
"\n",
|
||||
"It takes the following format:\n",
|
||||
"\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"```yaml\n",
|
||||
"base_scenario: base.yaml\n",
|
||||
"schedule:\n",
|
||||
@@ -47,8 +54,7 @@
|
||||
" 1: # list of variations to load in at episode 1 (after the first env.reset() call)\n",
|
||||
" - laydown_2.yaml\n",
|
||||
" - attack_2.yaml\n",
|
||||
"```\n",
|
||||
"\n"
|
||||
"```\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -11,10 +11,11 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "raw",
|
||||
"cell_type": "markdown",
|
||||
"id": "1",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"``` text\n",
|
||||
" +------------+\n",
|
||||
" | domain_ |\n",
|
||||
" +------------+ controller |\n",
|
||||
@@ -43,7 +44,8 @@
|
||||
" | | | | backup_ |\n",
|
||||
" +------------+ +------------+ server |\n",
|
||||
" | |\n",
|
||||
" +------------+"
|
||||
" +------------+\n",
|
||||
"```"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -256,11 +258,9 @@
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "22",
|
||||
"metadata": {
|
||||
"tags": []
|
||||
},
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Calling `switch.arp.show()` displays the Switch ARP Cache."
|
||||
"Calling `switch.sys_log.show()` displays the Switch system log. By default, only the last 10 log entries are displayed, this can be changed by passing `last_n=<number of log entries>`."
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -271,33 +271,13 @@
|
||||
"tags": []
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"network.get_node_by_hostname(\"switch_1\").arp.show()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "24",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Calling `switch.sys_log.show()` displays the Switch system log. By default, only the last 10 log entries are displayed, this can be changed by passing `last_n=<number of log entries>`."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "25",
|
||||
"metadata": {
|
||||
"tags": []
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"network.get_node_by_hostname(\"switch_1\").sys_log.show()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "26",
|
||||
"id": "24",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Computer/Server Nodes\n",
|
||||
@@ -307,7 +287,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "27",
|
||||
"id": "25",
|
||||
"metadata": {
|
||||
"tags": []
|
||||
},
|
||||
@@ -315,6 +295,26 @@
|
||||
"Calling `computer.show()` displays the NICs on the Computer/Server."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "26",
|
||||
"metadata": {
|
||||
"tags": []
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"network.get_node_by_hostname(\"security_suite\").show()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "27",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Calling `computer.arp.show()` displays the Computer/Server ARP Cache."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
@@ -324,7 +324,7 @@
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"network.get_node_by_hostname(\"security_suite\").show()"
|
||||
"network.get_node_by_hostname(\"security_suite\").arp.show()"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -332,7 +332,7 @@
|
||||
"id": "29",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Calling `computer.arp.show()` displays the Computer/Server ARP Cache."
|
||||
"Calling `computer.sys_log.show()` displays the Computer/Server system log. By default, only the last 10 log entries are displayed, this can be changed by passing `last_n=<number of log entries>`."
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -344,7 +344,7 @@
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"network.get_node_by_hostname(\"security_suite\").arp.show()"
|
||||
"network.get_node_by_hostname(\"security_suite\").sys_log.show()"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -352,7 +352,9 @@
|
||||
"id": "31",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Calling `computer.sys_log.show()` displays the Computer/Server system log. By default, only the last 10 log entries are displayed, this can be changed by passing `last_n=<number of log entries>`."
|
||||
"## Basic Network Comms Check\n",
|
||||
"\n",
|
||||
"We can perform a good old ping to check that Nodes are able to communicate with each other."
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -364,7 +366,7 @@
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"network.get_node_by_hostname(\"security_suite\").sys_log.show()"
|
||||
"network.show(nodes=False, links=False)"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -372,9 +374,7 @@
|
||||
"id": "33",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Basic Network Comms Check\n",
|
||||
"\n",
|
||||
"We can perform a good old ping to check that Nodes are able to communicate with each other."
|
||||
"We'll first ping client_1's default gateway."
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -386,27 +386,27 @@
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"network.show(nodes=False, links=False)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "35",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"We'll first ping client_1's default gateway."
|
||||
"network.get_node_by_hostname(\"client_1\").ping(\"192.168.10.1\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "36",
|
||||
"id": "35",
|
||||
"metadata": {
|
||||
"tags": []
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"network.get_node_by_hostname(\"client_1\").ping(\"192.168.10.1\")"
|
||||
"network.get_node_by_hostname(\"client_1\").sys_log.show(15)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "36",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Next, we'll ping the interface of the 192.168.1.0/24 Network on the Router (port 1)."
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -418,7 +418,7 @@
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"network.get_node_by_hostname(\"client_1\").sys_log.show(15)"
|
||||
"network.get_node_by_hostname(\"client_1\").ping(\"192.168.1.1\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -426,7 +426,7 @@
|
||||
"id": "38",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Next, we'll ping the interface of the 192.168.1.0/24 Network on the Router (port 1)."
|
||||
"And finally, we'll ping the web server."
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -438,7 +438,7 @@
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"network.get_node_by_hostname(\"client_1\").ping(\"192.168.1.1\")"
|
||||
"network.get_node_by_hostname(\"client_1\").ping(\"192.168.1.12\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -446,7 +446,7 @@
|
||||
"id": "40",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"And finally, we'll ping the web server."
|
||||
"To confirm that the ping was received and processed by the web_server, we can view the sys log"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -457,33 +457,13 @@
|
||||
"tags": []
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"network.get_node_by_hostname(\"client_1\").ping(\"192.168.1.12\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "42",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"To confirm that the ping was received and processed by the web_server, we can view the sys log"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "43",
|
||||
"metadata": {
|
||||
"tags": []
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"network.get_node_by_hostname(\"web_server\").sys_log.show()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "44",
|
||||
"id": "42",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Advanced Network Usage\n",
|
||||
@@ -493,12 +473,32 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "45",
|
||||
"id": "43",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Let's attempt to prevent client_2 from being able to ping the web server. First, we'll confirm that it can ping the server first..."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "44",
|
||||
"metadata": {
|
||||
"tags": []
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"network.get_node_by_hostname(\"client_2\").ping(\"192.168.1.12\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "45",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"If we look at the client_2 sys log we can see that the four ICMP echo requests were sent and four ICMP each replies were received:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
@@ -507,33 +507,13 @@
|
||||
"tags": []
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"network.get_node_by_hostname(\"client_2\").ping(\"192.168.1.12\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "47",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"If we look at the client_2 sys log we can see that the four ICMP echo requests were sent and four ICMP each replies were received:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "48",
|
||||
"metadata": {
|
||||
"tags": []
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"network.get_node_by_hostname(\"client_2\").sys_log.show()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "49",
|
||||
"id": "47",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Now we'll add an ACL to block ICMP from 192.168.10.22"
|
||||
@@ -542,7 +522,7 @@
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "50",
|
||||
"id": "48",
|
||||
"metadata": {
|
||||
"tags": []
|
||||
},
|
||||
@@ -562,7 +542,7 @@
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "51",
|
||||
"id": "49",
|
||||
"metadata": {
|
||||
"tags": []
|
||||
},
|
||||
@@ -573,12 +553,32 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "52",
|
||||
"id": "50",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Now we attempt (and fail) to ping the web server"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "51",
|
||||
"metadata": {
|
||||
"tags": []
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"network.get_node_by_hostname(\"client_2\").ping(\"192.168.1.12\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "52",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"We can check that the ping was actually sent by client_2 by viewing the sys log"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
@@ -588,7 +588,7 @@
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"network.get_node_by_hostname(\"client_2\").ping(\"192.168.1.12\")"
|
||||
"network.get_node_by_hostname(\"client_2\").sys_log.show()"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -596,7 +596,7 @@
|
||||
"id": "54",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"We can check that the ping was actually sent by client_2 by viewing the sys log"
|
||||
"We can check the router sys log to see why the traffic was blocked"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -608,7 +608,7 @@
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"network.get_node_by_hostname(\"client_2\").sys_log.show()"
|
||||
"network.get_node_by_hostname(\"router_1\").sys_log.show()"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -616,7 +616,7 @@
|
||||
"id": "56",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"We can check the router sys log to see why the traffic was blocked"
|
||||
"Now a final check to ensure that client_1 can still ping the web_server."
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -627,26 +627,6 @@
|
||||
"tags": []
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"network.get_node_by_hostname(\"router_1\").sys_log.show()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "58",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Now a final check to ensure that client_1 can still ping the web_server."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "59",
|
||||
"metadata": {
|
||||
"tags": []
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"network.get_node_by_hostname(\"client_1\").ping(\"192.168.1.12\")"
|
||||
]
|
||||
@@ -654,7 +634,7 @@
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "60",
|
||||
"id": "58",
|
||||
"metadata": {
|
||||
"tags": []
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user